本项目基于 GD32VW553H-EVAL 开发板,使用 RT-Thread 实时操作系统,实现了一款功能完整的智能电表显示终端。通过 ADC 采集电位器电压模拟电流输入,在 SPI LCD 屏幕上使用 LVGL 渲染出专业的电表界面,实时显示电网状态、电压、电流、频率、功率、功率因数、THD 和累计电能等参数。
目录
项目基本信息
开发环境与资源使用
项目体验与性能测试
1 项目基本信息
工程代码链接:https://gitee.com/YIYIGET/GD32VW553_Smart-meter
演示视频链接:https://www.bilibili.com/video/BV17eVf6wE79/?spm_id_from=333.1387.upload.video_card.click
ADC相关功能文章链接:https://club.rt-thread.org/ask/article/e0635c54055589ca.html
实物照片:

2 开发环境与资源使用
| 类别 | 详情 | |
| RT-Thread 版本 | RT-Thread v5.0.2 | |
| 开发板 | GD32VW553H-EVAL | |
| 开发工具/IDE | VS Code + RT-Thread scons命令行 | |
| 编译工具链 | RISC-V Embedded GCC | |
| 烧录工具 | GD32 All-in-One Programmer | |
| 图形界面 | LVGL v8.3.11 | |
| 显示屏 | SPI LCD (ILI9341 240×320) | |
| 主要外设 | ADC (电位器模拟电流采样)SPI (LCD驱动) | |
3 项目体验与性能测试
3.1 项目简介
本项目基于 GD32VW553H-EVAL 开发板,使用 RT-Thread 实时操作系统,实现了一款功能完整的智能电表显示终端。通过 ADC 采集电位器电压模拟电流输入,在 SPI LCD 屏幕上使用 LVGL 渲染出专业的电表界面,实时显示电网状态、电压、电流、频率、功率、功率因数、THD 和累计电能等参数。
3.2 亮点与优势
界面采用卡片式布局,顶部状态栏显示运行状态,橙色灰色UI, 整体风格简洁清晰,适合工业仪表场景。
ADC采样对数据的实时性要求较高,LVGL刷新数据能够达到很好的显示效果。

关键代码解析:
1.ADC 采样使用adc,PA1,可调电位器模拟电流数据采样(与真实应用同原理),这部分不过多介绍有疑问的可以看上文提到的ADC相关实践文章链接。
#define ADC_DEV_NAME "adc" /* ADC 设备名称 */#define ADC_DEV_CHANNEL 1 /* ADC 通道 */#define REFER_VOLTAGE 330 /* 参考电压 3.3V,数据精度乘以100保留2位小数*/#define CONVERT_BITS (1 << 12) /* 转换位数为12位 */#define ADC_SAMPLE_DELAY 100 /* 采样间隔 100ms,可修改 */intmain(void){ rt_kprintf("Hello GD32VW553H\n"); rt_adc_device_t adc_dev; rt_err_t ret = RT_EOK; /* 创建互斥锁 */ adc_mutex = rt_mutex_create("adc_mtx", RT_IPC_FLAG_FIFO); /* 查找设备 */ adc_dev = (rt_adc_device_t)rt_device_find(ADC_DEV_NAME); /* 使能设备 */ ret = rt_adc_enable(adc_dev, ADC_DEV_CHANNEL); while (1) { /* 读取采样值 */ rt_uint32_t value = rt_adc_read(adc_dev, ADC_DEV_CHANNEL); /* 转换为对应电压值 (修正了原本的变量名笔误) */ rt_uint32_t new_vol = value * REFER_VOLTAGE * 3/ CONVERT_BITS; /* 【核心修改】获取互斥锁,保护全局变量的写入 */ if (rt_mutex_take(adc_mutex, RT_WAITING_FOREVER) == RT_EOK) { adc_vol = new_vol; rt_mutex_release(adc_mutex); // 释放互斥锁 } rt_kprintf("the value is :%d \n", adc_value); rt_kprintf("the voltage is :%d.%02d \n", adc_vol / 100, adc_vol % 100); // /* 延时控制采样频率 */ rt_thread_mdelay(ADC_SAMPLE_DELAY); } return RT_EOK;}
上下、左右滑动查看
2.lvgl数据实时刷新,这里只使用了输出设备显示屏,未使用输入设备需注释掉,本项目由于数据量较小在lvgl线程中完成的数据刷新,也可以创建定时器完成数据刷新。
staticvoidlvgl_thread_entry(void *parameter){#if LV_USE_LOG lv_log_register_print_cb(lv_rt_log);#endif/* LV_USE_LOG */ lv_init(); lv_port_disp_init(); // lv_port_indev_init();//输入设备未使用 // lv_user_gui_init(); rt_tick_t last_update_tick = rt_tick_get(); constrt_tick_t update_interval = RT_TICK_PER_SECOND / 5; // 200ms 刷新一次 lv_meter_ui_create(); while(1) { rt_tick_t current_tick = rt_tick_get(); if (current_tick - last_update_tick >= update_interval) { last_update_tick = current_tick; rt_uint32_t display_vol = 0; rt_uint32_t display_vol_p = 0; if (rt_mutex_take(adc_mutex, RT_WAITING_FOREVER) == RT_EOK) { display_vol = adc_vol; rt_mutex_release(adc_mutex); // 释放锁 } display_vol_p = (display_vol * 220) / 1000; //刷新 Label 显示 if (lbl_val_a != NULL) { lv_label_set_text_fmt(lbl_val_a, "%d.%02d", display_vol / 100, display_vol % 100); } if (lbl_val_p != NULL) { lv_label_set_text_fmt(lbl_val_p, "%d.%02d", display_vol_p / 100, display_vol_p % 100 ); } } lv_task_handler(); lv_tick_inc(10); rt_thread_mdelay(10); }}
上下、左右滑动查看
3.3 遇到的问题和解决方式
1.scons 找不到 RISC-V 编译器
解决办法:RTT_EXEC_PATH 未设置,设置为编译工具链所在路径
set RTT_EXEC_PATH=...(所在位置)...\RISCEmbeddedGCC\bin
2.多线程同时读写同一个全局变量,结果不可预期-数据竞争(Race Condition)
解决办法:使用互斥锁对数据的读写进行限制
3.lvgl页面刷新慢
解决办法:由画点函数draw_point() 改为色块填充函数lcd_fill()提高刷新速度。
全部0条评论
快来发表一下你的评论吧 !