网站首页 全球最实用的IT互联网站!

人工智能P2P分享Wind搜索发布信息网站地图标签大全

当前位置:诺佳网 > 电子/半导体 > 控制/MCU >

基于RT-Thread和兆易创新GD32F527系列MCU的健康监测站

时间:2026-01-20 17:37

人气:

作者:admin

标签:

导读:本项目为RT-Thread嵌入式大赛获奖作品,基于RT-Thread和兆易创新GD32F527I-EVAL的健康监测站。目录项目概述系统硬件框架结构基础驱动程序实现整体驱动实现工程效果演示视频及代码演示...

本项目为RT-Thread嵌入式大赛获奖作品,基于RT-Thread和兆易创新GD32F527I-EVAL的健康监测站。

目录


项目概述


系统硬件框架结构


基础驱动程序实现


整体驱动实现


工程效果


演示视频及代码


演示视频链接:https://www.bilibili.com/video/BV1WgUoBXE2n/?pop_share=1&vd_source=e1bd226340c8b87027d5dcfc6b0c3344

1 项目概述

1.1 项目背景

血氧、心率监测是人们最常关心的,特别是一些特殊的群体比如老人,患有心血管系统、呼吸系统疾病的人。能方便及时的监测到心率与血氧。本项目主要利用了开发板的大内存、大屏幕,移植LVGL,能够让老年人也看得清楚。

1.2 系统功能介绍

基于GD32F527I-EVAL开发板实现如下功能:

使用管方的RTTherad库,管理整个系统资源。

移植LCD驱动,给LVGL提供显示基础

移植触摸驱动,给LVGL提供用户输入基础

移植USART5,实现与传感器的交互接口

移植LVGL,设计GUI界面

实现传感器驱动,实现血氧、心率的监测。

1.3 系统使用的技术要点

整个系统由国产开源操作系统RT-Thead实现驱动的模块化设计,以及系统调度。

硬件:GD32F527I-EVAL

开发板语言:C、GuiGuider

2 系统硬件框架结构

a40536cc-f5e3-11f0-8ce9-92fbcf53809c.png

2.1 TFT—LCD接口

开发板板载了LCD屏,其原理图如下:

a41d7ff2-f5e3-11f0-8ce9-92fbcf53809c.png

2.2 SDRAM接口

由于开发板上外扩了RAM,给LVGL驱动提供了大内存空间,原理图如下:

a42b9312-f5e3-11f0-8ce9-92fbcf53809c.png

2.3 USART5接口

由于开发板的外设非常多,找到摄像头接口的PC6、PC7作为USART5的接口:

a4499ff6-f5e3-11f0-8ce9-92fbcf53809c.png

3 基础驱动程序实现

3.1 基础工程

3.1.1 下载RT-Thread源码

https://gitee.com/rtthread/rt-thread

下载源码到本地。

3.1.2 同步并打包

下载好源码后,进入rt-thread/tree/master/bsp/gd32/arm/gd32527I-eval目录下面执行pkgs —upgrade-force同步

然后进行pkgs —update

执行scons —target=mdk5

然后进行scons —dist打包单独的工程。

复制打包好的工程到单独的目录下。

现在单独的工程就创建好了。

3.2 移植LVGL

创建好工程后,首先就需要生成用户交互界面。我在论坛有单独的作品:

https://club.rt-thread.org/ask/article/a2dba0eaa063757a.html

3.3 移植触摸驱动

LVGL需要驱动触摸屏,我也有单独的作品:

https://club.rt-thread.org/ask/article/104ea5e6b33e788e.html

3.4 移植mks传感器串口接口

详见单独作品:

https://club.rt-thread.org/ask/article/1074357ba9757cdb.html

4 整体驱动实现

通过上的基工程的实现,接下来就是整合驱动,实整个工程。

4.1 界面设计

4.1.1 GuiGuide设计

我使用开源的GuiGuider设了用户交互界面:

a456c10e-f5e3-11f0-8ce9-92fbcf53809c.png

控件1:btn_start,实现开始、停止测量的复用功能

控件2:label_heart 用于显示测量到的心率值

控件3:label_spo2 用于显示测量到的心率值

控件4:bar 用于显示测量的进度条

其余控件为固定标签

4.1.2 事件添加

为btn_start 添加clicked事件

a46c1f2c-f5e3-11f0-8ce9-92fbcf53809c.png

最后生成C语言的工程。

4.2 LVGL工程移植

4.2.1 复制guiguider工程

在生成工程的目录中,我复制generated文件夹到基础工程下的LVGL目录下面,替换掉原来的generated文件夹。

4.2.2 添加文件到工程

在mdk中,将generated下面的所有.c/h添加到mdk工程中:

a476daca-f5e3-11f0-8ce9-92fbcf53809c.png

4.2.3 添加bnt事件代码

根据传感器的手册,我们开始测量时,是向串口发送数据0x8A,停止是向串口发送0x88。

添加按键的的驱动代码如下:

externlv_ui guider_ui;externintmks_cmd(intargc,char*argv[]);// 全局/静态变量:控制进度条和定时器(确保回调中可访问)staticlv_timer_t*progress_timer =NULL; // 进度条定时器staticint current_progress =0; // 当前进度值(0-100)// 进度条定时器回调函数(每隔 200ms 触发一次)staticvoidprogress_timer_cb(lv_timer_t*timer){ current_progress++; // 每次进度 +1%(200ms × 100 = 20 秒) // 更新进度条值(关闭动画,避免和定时器冲突) lv_bar_set_value(guider_ui.screen_bar, current_progress, LV_ANIM_OFF); // 进度达到 100%,停止定时器并重置状态 if(current_progress >=100) { lv_timer_del(progress_timer); // 删除定时器(释放资源) progress_timer =NULL; // 重置定时器指针 current_progress =0; // 重置进度值 char*argv[] = {"mks_cmd","stop",NULL}; intargc =2; mks_cmd(argc, argv); // 可选:进度完成后,自动将按钮切回「开始」 lv_label_set_text(guider_ui.screen_btn_start_label,"Start"); lv_bar_set_value(guider_ui.screen_bar,0, LV_ANIM_OFF); }}staticvoidscreen_btn_start_event_handler(lv_event_t*e){ lv_event_code_tcode =lv_event_get_code(e); switch(code) { caseLV_EVENT_CLICKED: { constchar *btn_text =lv_label_get_text(guider_ui.screen_btn_start_label);; if(btn_text !=NULL&&strcmp(btn_text,"Start") ==0) { rt_kprintf("start test\n"); // 停止已存在的定时器(避免重复启动,防止进度加速) if(progress_timer !=NULL) { lv_timer_del(progress_timer); progress_timer =NULL; } // 初始化进度状态 current_progress =0; lv_bar_set_range(guider_ui.screen_bar,0,100); // 设置进度条范围:0-100(百分比) lv_bar_set_value(guider_ui.screen_bar,0, LV_ANIM_OFF); // 进度条归零 // 创建并启动定时器:周期 200ms,触发回调函数 progress_timer =lv_timer_create(progress_timer_cb,200,NULL); lv_timer_resume(progress_timer); // 启动定时器(LVGL 定时器默认创建后启动,保险起见手动调用) // 构造参数并调用 char*argv[] = {"mks_cmd","start",NULL}; intargc =2; mks_cmd(argc, argv); lv_label_set_text(guider_ui.screen_btn_start_label,"Stop"); } elseif(btn_text !=NULL&&strcmp(btn_text,"Stop") ==0) { // 停止并删除定时器 if(progress_timer !=NULL) { lv_timer_del(progress_timer); progress_timer =NULL; } // 进度条归零,状态重置 current_progress =0; lv_bar_set_value(guider_ui.screen_bar,0, LV_ANIM_OFF); char*argv[] = {"mks_cmd","stop",NULL}; intargc =2; mks_cmd(argc, argv); lv_label_set_text(guider_ui.screen_btn_start_label,"Start"); lv_obj_center(guider_ui.screen_btn_start_label); } break; } default: break; }}

4.2.4 添加初始化gui代码

在main.c中添加用于lvgl 心跳包的任务,与初始化lvgl的代码:

#defineLED1_PIN GET_PIN(E, 3)#defineLVGL_TASK_PERIOD 10 // ms#defineLVGL_TICK_PERIOD 5 // mslv_ui guider_ui;staticrt_thread_tlvgl_task_thread = RT_NULL;staticrt_thread_tlvgl_tick_thread = RT_NULL;staticvoidlvgl_tick_thread_entry(void*parameter){ while(1) { lv_tick_inc(5); rt_thread_delay(5); }}staticvoidlvgl_task_thread_entry(void*parameter){ lv_init(); lcd_init(); lv_port_disp_init(); lv_port_indev_init(); setup_ui(&guider_ui); setup_ui(&guider_ui); events_init(&guider_ui); while(1) { lv_task_handler(); rt_thread_delay(10); }}intmain(void){ /* set LED1 pin mode to output */ rt_pin_mode(LED1_PIN, PIN_MODE_OUTPUT); exmc_synchronous_dynamic_ram_init(EXMC_SDRAM_DEVICE0); //在这里启用两个任务 lvgl_task_thread =rt_thread_create("lvgl_task", lvgl_task_thread_entry, RT_NULL,4096,25,10); if(lvgl_task_thread != RT_NULL) rt_thread_startup(lvgl_task_thread); lvgl_tick_thread =rt_thread_create("lvgl_tick", lvgl_tick_thread_entry, RT_NULL,512,25,10); if(lvgl_tick_thread != RT_NULL) rt_thread_startup(lvgl_tick_thread); while(1) { rt_thread_mdelay(1000);// 主线程可以做其他事情 } returnRT_EOK;}

将程序下载到开发板后,按下按键,能在USART5上面看到的0x8A、0x88输出,说明程序运转正常。

4.3 mks传感器驱动

4.3.1 mks传感器通信协议:

a486f8c4-f5e3-11f0-8ce9-92fbcf53809c.png

根据通信协议进行串口的代码实现。

4.3.2 mks串口添加:

在menuconfig中,打开usart5,保存工程并重新生成工程。

4.3.3 mks串口驱动实现

在驱动中,按照rtthead srial的标准驱动。

由于mkd传感器的波特率为38400,因此需要在初始化后,重新配置serial的驱动结构体,并进行配置。

定义他为uart5 实现中断接收功能,详见驱动代码如下:

接收传感器并实现解码,并实时将接收到的数据通过LVGL显示到界面中。

#include"app.h"#include"string.h"#include#include#include#include"lvgl.h"#include"gui_guider.h"#defineMAX_BUFFSIZE 128#defineRECEIVE_LENGTH 88#definePACKET_HEADER 0xFF#defineSAMPLE_UART_NAME "uart5"/* 用于接收消息的信号量 */staticstructrt_semaphorerx_sem;staticrt_device_tserial;// --- 全局变量定义 ---int8_tmks_waveform_data[MKS_WAVEFORM_SAMPLES];uint8_tmks_heart_rate =0;uint8_tmks_spo2 =0;volatileuint8_tmks_new_data_flag =0; // Use volatile as it might be checked in timer/ISR context later// --- 内部接收缓冲区 ---staticuint8_tmks_rx_buffer[MKS_PACKET_SIZE];staticuint8_tmks_bytes_received =0;// lvglexternlv_ui guider_ui;staticchar buf[4];/* 接收数据回调函数 */staticrt_err_tuart_input(rt_device_tdev,rt_size_tsize){ /* 串口接收到数据后产生中断,调用此回调函数,然后发送接收信号量 */ rt_sem_release(&rx_sem); returnRT_EOK;}/*** 串口接受线程* @param parameter*/staticvoidserial_thread_entry(void*parameter){ charch; mks_bytes_received =0; // Reset buffer state mks_new_data_flag =0; // 初始无新数据 // 初始化数据为0 memset(mks_rx_buffer,0, RECEIVE_LENGTH); mks_heart_rate =0; mks_spo2 =0; while(1) { /* 从串口读取一个字节的数据,没有读取到则等待接收信号量 */ while(rt_device_read(serial,-1, &ch,1) !=1) { /* 阻塞等待接收信号量,等到信号量后再次读取数据 */ rt_sem_take(&rx_sem, RT_WAITING_FOREVER); } if(ch == PACKET_HEADER) { mks_bytes_received =1; mks_new_data_flag =0; mks_rx_buffer[0] = (uint8_t)ch; } else { if(mks_bytes_received>0) { //mks_bytes_received++; mks_rx_buffer[mks_bytes_received++] = (uint8_t)ch; if(mks_bytes_received >= RECEIVE_LENGTH) { mks_new_data_flag =1; mks_bytes_received =0; sprintf(buf, "%d", mks_rx_buffer[65]); lv_label_set_text(guider_ui.screen_label_heart, buf);//更新到LVGL sprintf(buf, "%d", mks_rx_buffer[66]); lv_label_set_text(guider_ui.screen_label_spo2, buf);//更新到LVGL memset(mks_rx_buffer,0, RECEIVE_LENGTH); mks_new_data_flag =0; } } } }}staticintuart_sample(intargc,char*argv[]){ rt_err_tret = RT_EOK; struct serial_configurecfg; // 配置结构体 charuart_name[RT_NAME_MAX]; charstr[] ="hello RT-Thread!\r\n"; if(argc ==2) { rt_strncpy(uart_name, argv[1], RT_NAME_MAX); } else { rt_strncpy(uart_name, SAMPLE_UART_NAME, RT_NAME_MAX); } /* 查找系统中的串口设备 */ serial =rt_device_find(uart_name); if(!serial) { rt_kprintf("find %s failed!\n", uart_name); returnRT_ERROR; } cfg.baud_rate = BAUD_RATE_38400; // 目标波特率(可改为 38400、19200 等) cfg.data_bits = DATA_BITS_8; // 8 数据位 cfg.stop_bits = STOP_BITS_1; // 1 停止位 cfg.parity = PARITY_NONE; // 无校验 cfg.bit_order = BIT_ORDER_LSB; // 低位优先(默认) cfg.invert = NRZ_NORMAL; cfg.bufsz = RT_SERIAL_RB_BUFSZ; cfg.flowcontrol = RT_SERIAL_FLOWCONTROL_NONE; cfg.reserved =0; rt_device_control(serial, RT_DEVICE_CTRL_CONFIG, &cfg); /* 初始化信号量 */ rt_sem_init(&rx_sem,"rx_sem",0, RT_IPC_FLAG_FIFO); /* 以中断接收及轮询发送模式打开串口设备 */ rt_device_open(serial, RT_DEVICE_FLAG_INT_RX); /* 设置接收回调函数 */ rt_device_set_rx_indicate(serial, uart_input); /* 发送字符串 */ rt_device_write(serial,0, str, (sizeof(str) -1)); /* 创建 serial 线程 */ rt_thread_tthread =rt_thread_create("serial", serial_thread_entry, RT_NULL,1024,25,10); /* 创建成功则启动线程 */ if(thread != RT_NULL) { rt_thread_startup(thread); } else { ret = RT_ERROR; } returnret;}intmks_cmd(intargc,char*argv[]){ rt_err_tret = RT_EOK; uint8_tsend_data =0; // 要发送的字节数据 // 第一步:检查串口设备是否已初始化(首次使用时查找并打开) if(serial == RT_NULL) { // 查找串口设备 serial =rt_device_find(SAMPLE_UART_NAME); if(serial == RT_NULL) { rt_kprintf("错误:未找到串口设备 %s!\n", SAMPLE_UART_NAME); returnRT_ERROR; } // 打开串口(RT_DEVICE_OFLAG_RDWR:读写模式) if(rt_device_open(serial, RT_DEVICE_OFLAG_RDWR) != RT_EOK) { rt_kprintf("错误:打开串口设备 %s 失败!\n", SAMPLE_UART_NAME); serial = RT_NULL; // 打开失败,重置句柄 returnRT_ERROR; } } // 第二步:解析命令参数 if(argc ==2) // 仅支持 "mks start" 或 "mks stop"(2个参数) { // 比较第二个参数(argv[1]) if(strcmp(argv[1],"start") ==0) { send_data =0x8A; // start 对应发送 0x8A rt_kprintf("执行 mks start,发送数据:0x%02X\n", send_data); } elseif(strcmp(argv[1],"stop") ==0) { send_data =0x88; // stop 对应发送 0x88 rt_kprintf("执行 mks stop,发送数据:0x%02X\n", send_data); } else { // 无效参数:提示正确用法 rt_kprintf("错误:无效命令参数!\n"); rt_kprintf("正确用法:\n"); rt_kprintf(" mks start - 发送 0x8A\n"); rt_kprintf(" mks stop - 发送 0x88\n"); return-RT_EINVAL; } // 第三步:通过串口发送数据(发送1个字节) ret =rt_device_write(serial,0, &send_data,1); if(ret !=1) // rt_device_write 返回实际发送的字节数,成功应为 1 { rt_kprintf("错误:串口发送失败!返回值:%d\n", ret); return-RT_ERROR; } } else { // 参数个数错误:提示正确用法 rt_kprintf("错误:命令格式错误!\n"); rt_kprintf("正确用法:\n"); rt_kprintf(" mks start - 发送 0x8A\n"); rt_kprintf(" mks stop - 发送 0x88\n"); return-RT_EINVAL; } returnret;}// 3. 导出命令到 FinSH 终端(支持在终端直接输入 mks 命令)MSH_CMD_EXPORT(mks_cmd, mks command: mks start/stop);/* 导出到 msh 命令列表中 */MSH_CMD_EXPORT(uart_sample, uart device sample);

接收传感器并实现解码,并实时将接收到的数据通过LVGL显示到界面中。

5 工程效果

a48f5c44-f5e3-11f0-8ce9-92fbcf53809c.jpg

6 演示视频及代码

演示视频链接:https://www.bilibili.com/video/BV1WgUoBXE2n/?pop_share=1&vd_source=e1bd226340c8b87027d5dcfc6b0c3344

源代码:https://club.rt-thread.org/file_download/a6fe3781d8bcf12d


温馨提示:以上内容整理于网络,仅供参考,如果对您有帮助,留下您的阅读感言吧!
相关阅读
本类排行
相关标签
本类推荐

CPU | 内存 | 硬盘 | 显卡 | 显示器 | 主板 | 电源 | 键鼠 | 网站地图

Copyright © 2025-2035 诺佳网 版权所有 备案号:赣ICP备2025066733号
本站资料均来源互联网收集整理,作品版权归作者所有,如果侵犯了您的版权,请跟我们联系。

关注微信