使用LVGL模拟示波器创建正弦函数波形

使用LVGL模拟示波器创建正弦函数波形

代码

oscilloscope_sine_wave.c

c 复制代码
/**
 * @file oscilloscope_sine_wave.c
 * @brief LVGL 正弦函数示波器动画 - 实现文件
 * @details 本文件实现了完整的示波器波形显示面板,包括:
 *          1. 正弦波数据生成(带相位偏移的连续动画)
 *          2. Canvas 网格背景绘制(深蓝色主题)
 *          3. 波形线条绘制(绿色荧光效果)
 *          4. 定时器驱动的动画循环
 *          5. 时基/触发状态 UI 显示
 *
 * @note  需要 LVGL v8.x 或 v9.x 库支持
 * @note  建议使用 320x480 分辨率的屏幕以获得最佳显示效果
 * @note  正弦波公式:y = sin((i + phase) / count * 2 * PI)
 *        其中 i 为采样点索引,phase 为动画相位偏移量
 */

#include "oscilloscope_sine_wave.h"
#include <stdio.h>
#include <math.h>
#include <stdlib.h>


/* ==================== 常量定义 ==================== */

/** 波形显示区域尺寸 */
#define WAVEFORM_AREA_WIDTH      800   /**< 波形区域宽度(像素),与屏幕宽度一致 */
#define WAVEFORM_AREA_HEIGHT     380   /**< 波形区域高度(像素),留出上下空间给标签 */

/** 网格线数量 */
#define GRID_LINES_HORIZONTAL    8   /**< 水平网格线数量(将高度等分为 8 段) */
#define GRID_LINES_VERTICAL      10  /**< 垂直网格线数量(将宽度等分为 10 段) */

/** 波形采样点数量 */
#define WAVEFORM_SAMPLE_COUNT    800 /**< 正弦波采样点数,决定波形分辨率 */

/** 定时器刷新间隔(毫秒) */
#define WAVEFORM_TIMER_INTERVAL  50  /**< 50ms = 20Hz 刷新率,保证动画流畅 */

/** 相位步进值(每次定时器回调增加的相位值) */
#define WAVEFORM_PHASE_STEP      5   /**< 相位步进,值越大波形移动越快 */

/* ==================== 静态变量定义 ==================== */

/** 示波器页面容器对象 */
static lv_obj_t *oscilloscope_page = NULL;

/** 波形绘制画布对象(使用 lv_obj 或 lv_canvas 作为绘制区域) */
static lv_obj_t *waveform_canvas = NULL;

/** 时基显示标签 */
static lv_obj_t *timebase_label = NULL;

/** 触发状态显示标签 */
static lv_obj_t *trigger_label = NULL;

/** 波形更新定时器 */
static lv_timer_t *waveform_timer = NULL;

/** 波形采样数据缓冲区(浮点型,范围 -1.0 ~ 1.0) */
static float waveform_data[WAVEFORM_SAMPLE_COUNT];

/** 当前动画相位偏移量(0~359 度循环) */
static uint32_t waveform_phase = 0;

/** 正弦波 PI 常量 */
static const float PI = 3.14159265358979f;

/* ==================== 正弦波数据生成 ==================== */

/**
 * @brief 生成正弦波采样数据
 *
 * @param phase 当前相位偏移量(0~359),用于产生动画效果
 * @param data  输出数据数组指针(调用者提供的缓冲区)
 * @param count 采样点数量
 *
 * @details 正弦波生成公式:
 *          angle = (i + phase) / count * 2 * PI
 *          data[i] = sin(angle)
 *
 *          其中:
 *          - i: 采样点索引(0 ~ count-1)
 *          - phase: 动画相位偏移(每帧递增,产生波形移动效果)
 *          - count: 总采样点数
 *          - PI: 圆周率常量
 *
 *          波形范围:-1.0(波谷) ~ +1.0(波峰)
 *
 * @note  该函数为静态函数,仅在本文件内部使用
 */
static void generate_sine_wave(uint32_t phase, float *data, uint16_t count)
{
    uint16_t i;
    float angle;

    for (i = 0; i < count; i++) {
        /* 计算当前采样点的角度(弧度制)
         * (i + phase) / count 将采样点归一化到 [0, 2*PI) 范围
         * 乘以 2*PI 得到完整的正弦周期角度
         * phase 的加入使波形产生"流动"动画效果
         */
        angle = (float)(i + phase) * 3.0f / (float)count * 2.0f * PI;
        
        /* 计算正弦值,结果范围 [-1.0, 1.0] */
        data[i] = sinf(angle);
    }
}

/* ==================== Canvas 绘制回调 ==================== */

/**
 * @brief Canvas 主绘制事件回调函数
 *
 * @param e 事件对象指针
 *
 * @details 该函数在 LVGL 绘制循环中被调用,负责绘制:
 *          1. 深蓝色背景矩形
 *          2. 水平/垂直网格线(模拟示波器刻度)
 *          3. 中心十字线(加粗高亮)
 *          4. 绿色正弦波形曲线
 *
 * @note  仅在 LV_EVENT_DRAW_MAIN 事件时执行,避免重复绘制
 */
static void canvas_draw_event_cb(lv_event_t *e)
{
    lv_event_code_t event_code = lv_event_get_code(e);
    lv_obj_t *obj = lv_event_get_target(e);
    lv_obj_t *parent;
    int32_t width, height;
    lv_draw_ctx_t *draw_ctx;
    lv_area_t grid_area;
    lv_draw_rect_dsc_t rect_dsc;
    lv_draw_line_dsc_t line_dsc;
    int32_t h_step, v_step;
    int32_t x_step, center_y, amplitude;
    lv_point_t p1, p2;
    lv_point_t h_center_p1, h_center_p2;
    lv_point_t v_center_p1, v_center_p2;
    uint16_t i;

    /* 仅在主绘制事件时执行,避免在刷新/覆盖等事件时重复绘制 */
    if (event_code != LV_EVENT_DRAW_MAIN) {
        return;
    }

    parent = lv_obj_get_parent(obj);
    if (parent == NULL) {
        return;
    }

    if (!(const lv_area_t*)lv_event_get_param(e)) {
        return;
    }

    width = lv_obj_get_width(obj);
    height = lv_obj_get_height(obj);
    draw_ctx = lv_event_get_draw_ctx(e);

    /* ========== 1. 绘制网格背景 ========== */
    grid_area.x1 = 0;
    grid_area.y1 = 0;
    grid_area.x2 = width - 1;
    grid_area.y2 = height - 1;

    lv_draw_rect_dsc_init(&rect_dsc);
    /* 深蓝色背景(#1a1a2e),模拟示波器暗色屏幕 */
    rect_dsc.bg_color = lv_color_hex(0x1a1a2e);
    rect_dsc.bg_opa = LV_OPA_COVER;
    lv_draw_rect(draw_ctx, &rect_dsc, &grid_area);

    /* ========== 2. 绘制网格线 ========== */
    lv_draw_line_dsc_init(&line_dsc);
    /* 网格线颜色(#3d3d5c),半透明显示 */
    line_dsc.color = lv_color_hex(0x3d3d5c);
    line_dsc.opa = LV_OPA_50;
    line_dsc.width = 1;

    /* 水平网格线:将高度等分为 GRID_LINES_HORIZONTAL 段 */
    h_step = height / GRID_LINES_HORIZONTAL;
    for (i = 0; i <= GRID_LINES_HORIZONTAL; i++) {
        p1.x = 0;
        p1.y = i * h_step;
        p2.x = width - 1;
        p2.y = i * h_step;
        lv_draw_line(draw_ctx, &line_dsc, &p1, &p2);
    }

    /* 垂直网格线:将宽度等分为 GRID_LINES_VERTICAL 段 */
    v_step = width / GRID_LINES_VERTICAL;
    for (i = 0; i <= GRID_LINES_VERTICAL; i++) {
        p1.x = i * v_step;
        p1.y = 0;
        p2.x = i * v_step;
        p2.y = height - 1;
        lv_draw_line(draw_ctx, &line_dsc, &p1, &p2);
    }

    /* ========== 3. 绘制中心十字线(加粗高亮) ========== */
    line_dsc.color = lv_color_hex(0x5d5d7c);
    line_dsc.width = 2;

    /* 水平中心线 */
    h_center_p1.x = 0;
    h_center_p1.y = height / 2;
    h_center_p2.x = width - 1;
    h_center_p2.y = height / 2;
    lv_draw_line(draw_ctx, &line_dsc, &h_center_p1, &h_center_p2);

    /* 垂直中心线 */
    v_center_p1.x = width / 2;
    v_center_p1.y = 0;
    v_center_p2.x = width / 2;
    v_center_p2.y = height - 1;
    lv_draw_line(draw_ctx, &line_dsc, &v_center_p1, &v_center_p2);

    /* ========== 4. 绘制正弦波形 ========== */
    if (WAVEFORM_SAMPLE_COUNT > 1) {
        lv_draw_line_dsc_init(&line_dsc);
        /* 波形线条颜色(#00ff88),模拟示波器绿色荧光 */
        line_dsc.color = lv_color_hex(0x00ff88);
        line_dsc.opa = LV_OPA_COVER;
        line_dsc.width = 3;  /**< 线条加粗,模拟示波器荧光余辉效果 */
        line_dsc.round_end = 1;
        line_dsc.round_start = 1;

        /* 计算每个采样点的 X 轴间距 */
        x_step = width / (WAVEFORM_SAMPLE_COUNT - 1);

        /* 波形中心 Y 坐标(屏幕垂直中点) */
        center_y = height / 2;

        /* 波形振幅(使用屏幕高度的 40%,留出上下边距) */
        amplitude = height * 2 / 5;

        /* 将采样数据转换为屏幕坐标点并绘制连线 */
        lv_point_t points[WAVEFORM_SAMPLE_COUNT];
        for (i = 0; i < WAVEFORM_SAMPLE_COUNT; i++) {
            /* X 坐标:从左到右均匀分布 */
            points[i].x = i * x_step;

            /* Y 坐标:以中心线为基准,振幅缩放
             * 注意 LVGL 坐标系 Y 轴向下为正
             * 因此用 center_y - data[i] * amplitude(减去是因为波形向上为负 Y)
             */
            points[i].y = center_y - (int32_t)(waveform_data[i] * amplitude);
        }

        /* 使用 lv_draw_line 绘制连续波形线 */
        for (i = 0; i < WAVEFORM_SAMPLE_COUNT - 1; i++) {
            lv_draw_line(draw_ctx, &line_dsc, &points[i], &points[i + 1]);
        }
    }
}

/* ==================== 定时器动画回调 ==================== */

/**
 * @brief 波形更新定时器回调函数
 *
 * @param timer 定时器对象指针(本实现中未使用)
 *
 * @details 该函数由 LVGL 定时器自动调用,负责:
 *          1. 递增动画相位偏移量(0~359 度循环)
 *          2. 根据新相位重新生成正弦波采样数据
 *          3. 标记 Canvas 需要重绘
 *
 * @note  定时器刷新率为 20Hz(50ms/帧),保证动画流畅
 */
static void waveform_timer_cb(lv_timer_t *timer)
{
    (void)timer;

    /* 递增相位偏移量(0~359 度循环)
     * 每次增加 5 度,约 72 帧完成一个完整周期(72 * 50ms = 3.6s)
     */
    waveform_phase = (waveform_phase + WAVEFORM_PHASE_STEP) % 360;

    /* 根据新相位重新生成正弦波采样数据 */
    generate_sine_wave(waveform_phase, waveform_data, WAVEFORM_SAMPLE_COUNT);

    /* 标记 Canvas 对象需要重绘,触发 canvas_draw_event_cb */
    if (waveform_canvas != NULL) {
        lv_obj_invalidate(waveform_canvas);
    }
}

/* ==================== 公共 API 实现 ==================== */

/**
 * @brief 创建示波器页面
 *
 * @param parent 父对象指针(通常为 NULL)
 * @return 示波器页面容器对象指针
 *
 * @details 创建流程:
 *          1. 创建全屏页面容器
 *          2. 创建 Canvas 绘制区域
 *          3. 创建时基和触发状态标签
 *          4. 创建 20Hz 动画定时器
 *          5. 生成初始正弦波数据并触发首次绘制
 */
lv_obj_t* oscilloscope_sine_wave_create(lv_obj_t *parent)
{
    /* 创建页面容器对象 */
    oscilloscope_page = lv_obj_create(parent);

    /* 设置页面为全屏大小 */
    lv_obj_set_size(oscilloscope_page, LV_HOR_RES, LV_VER_RES);

    /* 创建波形绘制 Canvas 区域 */
    waveform_canvas = lv_obj_create(oscilloscope_page);
    lv_obj_set_size(waveform_canvas, WAVEFORM_AREA_WIDTH, WAVEFORM_AREA_HEIGHT);
    lv_obj_align(waveform_canvas, LV_ALIGN_TOP_MID, 0, 10); /**< 顶部居中,留 10px 上边距 */

    /* 注册 Canvas 绘制事件回调 */
    lv_obj_add_event_cb(waveform_canvas, canvas_draw_event_cb, LV_EVENT_DRAW_MAIN, NULL);

    /* 创建时基显示标签 */
    timebase_label = lv_label_create(oscilloscope_page);
    lv_label_set_text(timebase_label, "Timebase: 5.0ms/div");
    lv_obj_align(timebase_label, LV_ALIGN_BOTTOM_LEFT, 5, -5);

    /* 创建触发状态显示标签 */
    trigger_label = lv_label_create(oscilloscope_page);
    lv_label_set_text(trigger_label, "Trigger: READY | Level: 0.00V");
    lv_obj_align(trigger_label, LV_ALIGN_BOTTOM_RIGHT, -5, -5);

    /* 生成初始正弦波数据(phase=0) */
    generate_sine_wave(0, waveform_data, WAVEFORM_SAMPLE_COUNT);

    /* 创建 20Hz 动画定时器,自动驱动波形刷新 */
    waveform_timer = lv_timer_create(waveform_timer_cb, WAVEFORM_TIMER_INTERVAL, NULL);
    lv_timer_ready(waveform_timer); /**< 立即触发一次回调,显示初始波形 */


    return oscilloscope_page;
}

/**
 * @brief 用外部数据更新波形显示
 *
 * @param data 采样数据数组指针(浮点型,范围 -1.0 ~ 1.0)
 * @param count 采样数据个数
 *
 * @note 当 count 超过 WAVEFORM_SAMPLE_COUNT 时自动截断
 */
void oscilloscope_sine_wave_update(const float *data, uint16_t count)
{
    /* 防止数组越界 */
    if (count > WAVEFORM_SAMPLE_COUNT) {
        count = WAVEFORM_SAMPLE_COUNT;
    }

    /* 将外部数据复制到内部缓冲区 */
    for (uint16_t i = 0; i < count; i++) {
        waveform_data[i] = data[i];
    }

    /* 触发 Canvas 重绘 */
    if (waveform_canvas != NULL) {
        lv_obj_invalidate(waveform_canvas);
    }
}

/**
 * @brief 设置时基(Timebase)显示值
 *
 * @param value_ms 时基值,单位为毫秒/格(ms/div)
 *
 * @note 自动根据数值大小选择显示单位:
 *       >= 1000ms -> 秒(s/div)
 *       >= 1ms   -> 毫秒(ms/div)
 *       < 1ms    -> 微秒(us/div)
 */
void oscilloscope_sine_wave_set_timebase(float value_ms)
{
    char buffer[32];

    if (timebase_label == NULL) {
        return;
    }

    if (value_ms >= 1000.0f) {
        /* 大于等于 1 秒,显示为秒 */
        snprintf(buffer, sizeof(buffer), "Timebase: %.1fms/div", (float)value_ms / 1000.0f);
    } else if (value_ms >= 1.0f) {
        /* 1ms ~ 999ms,显示为毫秒 */
        snprintf(buffer, sizeof(buffer), "Timebase: %.1fms/div", (float)value_ms);
    } else {
        /* 小于 1ms,显示为微秒 */
        snprintf(buffer, sizeof(buffer), "Timebase: %.1fms/div", (float)value_ms * 1000.0);
    }

    lv_label_set_text(timebase_label, buffer);
}

/**
 * @brief 设置触发状态
 *
 * @param triggered 触发状态:true=ACTIVE(触发激活),false=READY(待触发)
 * @param level 触发电平电压值(单位:伏特 V)
 */
void oscilloscope_sine_wave_set_trigger(bool triggered, float level)
{
    char buffer[64];

    if (trigger_label == NULL) {
        return;
    }

    snprintf(buffer, sizeof(buffer),
             "Trigger: %s | Level: %.2fV",
             triggered ? "ACTIVE" : "READY", level);

    lv_label_set_text(trigger_label, buffer);
}

/**
 * @brief 获取示波器页面容器对象指针
 *
 * @return 示波器页面容器对象指针
 */
lv_obj_t* oscilloscope_sine_wave_get_obj(void)
{
    return oscilloscope_page;
}

/**
 * @brief 启动波形动画定时器
 *
 * @note 如果定时器已在运行则不会重复创建
 */
void oscilloscope_sine_wave_start_animation(void)
{
    if (waveform_timer == NULL) {
        waveform_timer = lv_timer_create(waveform_timer_cb, WAVEFORM_TIMER_INTERVAL, NULL);
        lv_timer_ready(waveform_timer); /**< 立即触发一次 */
    }
}

/**
 * @brief 停止波形动画定时器
 *
 * @note 停止后波形将不再自动刷新
 */
void oscilloscope_sine_wave_stop_animation(void)
{
    if (waveform_timer != NULL) {
        lv_timer_del(waveform_timer);
        waveform_timer = NULL;
    }
}

oscilloscope_sine_wave.h

c 复制代码
/**
 * @file oscilloscope_sine_wave.h
 * @brief LVGL 正弦函数示波器动画 - 头文件
 * @details 本文件定义了示波器波形显示面板的公共接口,
 *          包括页面创建、波形数据更新、时基设置、触发状态设置、动画启停等功能。
 * @note  需要 LVGL v8.x 或 v9.x 库支持,建议使用 320x480 分辨率屏幕。
 */

#ifndef OSCILLOSCOPE_SINE_WAVE_H
#define OSCILLOSCOPE_SINE_WAVE_H

#include "lvgl/lvgl.h"
#include <stdint.h>
#include <stdbool.h>

#ifdef __cplusplus
extern "C" {
#endif

/* ==================== 公共函数声明 ==================== */

/**
 * @brief 创建示波器页面
 *
 * @param parent 父对象指针(通常为 NULL,表示全屏页面)
 * @return 示波器页面容器对象指针
 *
 * @note 该函数会初始化所有内部资源,包括 Canvas 对象、
 *       定时器、网格参数等。调用后页面自动可见。
 */
lv_obj_t* oscilloscope_sine_wave_create(lv_obj_t *parent);

/**
 * @brief 用外部数据更新波形显示
 *
 * @param data 采样数据数组指针(浮点型,范围 -1.0 ~ 1.0)
 * @param count 采样数据个数
 *
 * @note 当 count 超过内部缓冲区大小时会自动截断。
 *       调用后会自动触发 Canvas 重绘。
 */
void oscilloscope_sine_wave_update(const float *data, uint16_t count);

/**
 * @brief 设置时基(Timebase)显示值
 *
 * @param value_ms 时基值,单位为毫秒/格(ms/div)
 *
 * @note 函数会自动根据数值大小选择显示单位:
 *       >= 1000ms 显示为秒(s/div),>= 1ms 显示为毫秒(ms/div),
 *       < 1ms 显示为微秒(us/div)。
 */
void oscilloscope_sine_wave_set_timebase(float value_ms);

/**
 * @brief 设置触发状态
 *
 * @param triggered 触发状态:true=触发激活(ACTIVE),false=待触发(READY)
 * @param level 触发电平电压值(单位:伏特 V)
 *
 * @note 该函数仅更新 UI 标签显示,不影响波形生成逻辑。
 */
void oscilloscope_sine_wave_set_trigger(bool triggered, float level);

/**
 * @brief 获取示波器页面容器对象指针
 *
 * @return 示波器页面容器对象指针
 */
lv_obj_t* oscilloscope_sine_wave_get_obj(void);

/**
 * @brief 启动波形动画定时器
 *
 * @note 启动后定时器将以 20Hz 频率回调,自动更新相位并刷新波形。
 *       如果定时器已在运行,则不会重复创建。
 */
void oscilloscope_sine_wave_start_animation(void);

/**
 * @brief 停止波形动画定时器
 *
 * @note 停止后波形将不再自动刷新,需手动调用 update 函数才能更新显示。
 */
void oscilloscope_sine_wave_stop_animation(void);


#ifdef __cplusplus
}
#endif

#endif /* OSCILLOSCOPE_SINE_WAVE_H */

实验结果

示波器动画

相关推荐
辣知1 小时前
辣知·化智71 四川广汉三星堆的金铜贝
学习
天天进步20151 小时前
90天AI接单学习路线:从LLM应用到AI Agent
人工智能·学习
vx_Biye_Design1 小时前
springboot小学生英语学习APP62773-计算机课程设计、毕业设计
java·vue.js·spring boot·后端·python·学习·课程设计
@Mike@2 小时前
13-数据库学习笔记(查询执行处理模型)
数据库·笔记·学习
传奇开心果编程2 小时前
【ArkUI进阶练中学】第13课:元服务与卡片开发
学习·ui·华为·harmonyos
Highcharts.js3 小时前
可视化商用图表库对比,如何选择?
前端·javascript·学习·信息可视化·highcharts·前端可视化
@Mike@3 小时前
12-数据库学习笔记(五种连接算法)
数据库·笔记·学习
韩明君3 小时前
alacrity学习笔记
笔记·学习
humors2213 小时前
【转】逻辑基础知识
学习·考研·读书·教育·逻辑·升学·语文