本項(xiàng)目為RT-Thread嵌入式大賽獲獎(jiǎng)作品,基于RT-Thread和兆易創(chuàng)新GD32F527I-EVAL的健康監(jiān)測(cè)站。
目錄
項(xiàng)目概述
系統(tǒng)硬件框架結(jié)構(gòu)
基礎(chǔ)驅(qū)動(dòng)程序?qū)崿F(xiàn)
整體驅(qū)動(dòng)實(shí)現(xiàn)
工程效果
演示視頻及代碼
演示視頻鏈接:https://www.bilibili.com/video/BV1WgUoBXE2n/?pop_share=1&vd_source=e1bd226340c8b87027d5dcfc6b0c3344
1 項(xiàng)目概述
1.1 項(xiàng)目背景
血氧、心率監(jiān)測(cè)是人們最常關(guān)心的,特別是一些特殊的群體比如老人,患有心血管系統(tǒng)、呼吸系統(tǒng)疾病的人。能方便及時(shí)的監(jiān)測(cè)到心率與血氧。本項(xiàng)目主要利用了開發(fā)板的大內(nèi)存、大屏幕,移植LVGL,能夠讓老年人也看得清楚。
1.2 系統(tǒng)功能介紹
基于GD32F527I-EVAL開發(fā)板實(shí)現(xiàn)如下功能:
使用管方的RTTherad庫(kù),管理整個(gè)系統(tǒng)資源。
移植LCD驅(qū)動(dòng),給LVGL提供顯示基礎(chǔ)
移植觸摸驅(qū)動(dòng),給LVGL提供用戶輸入基礎(chǔ)
移植USART5,實(shí)現(xiàn)與傳感器的交互接口。
移植LVGL,設(shè)計(jì)GUI界面
實(shí)現(xiàn)傳感器驅(qū)動(dòng),實(shí)現(xiàn)血氧、心率的監(jiān)測(cè)。
1.3 系統(tǒng)使用的技術(shù)要點(diǎn)
整個(gè)系統(tǒng)由國(guó)產(chǎn)開源操作系統(tǒng)RT-Thead實(shí)現(xiàn)驅(qū)動(dòng)的模塊化設(shè)計(jì),以及系統(tǒng)調(diào)度。
硬件:GD32F527I-EVAL
開發(fā)板語(yǔ)言:C、GuiGuider
2 系統(tǒng)硬件框架結(jié)構(gòu)

2.1 TFT—LCD接口
開發(fā)板板載了LCD屏,其原理圖如下:

由于開發(fā)板上外擴(kuò)了RAM,給LVGL驅(qū)動(dòng)提供了大內(nèi)存空間,原理圖如下:

2.3 USART5接口
由于開發(fā)板的外設(shè)非常多,找到攝像頭接口的PC6、PC7作為USART5的接口:

3 基礎(chǔ)驅(qū)動(dòng)程序?qū)崿F(xiàn)
3.1 基礎(chǔ)工程
3.1.1 下載RT-Thread源碼
https://gitee.com/rtthread/rt-thread
下載源碼到本地。
3.1.2 同步并打包
下載好源碼后,進(jìn)入rt-thread/tree/master/bsp/gd32/arm/gd32527I-eval目錄下面執(zhí)行pkgs —upgrade-force同步
然后進(jìn)行pkgs —update
執(zhí)行scons —target=mdk5
然后進(jìn)行scons —dist打包單獨(dú)的工程。
復(fù)制打包好的工程到單獨(dú)的目錄下。
現(xiàn)在單獨(dú)的工程就創(chuàng)建好了。
3.2 移植LVGL
創(chuàng)建好工程后,首先就需要生成用戶交互界面。我在論壇有單獨(dú)的作品:
https://club.rt-thread.org/ask/article/a2dba0eaa063757a.html
3.3 移植觸摸驅(qū)動(dòng)
LVGL需要驅(qū)動(dòng)觸摸屏,我也有單獨(dú)的作品:
https://club.rt-thread.org/ask/article/104ea5e6b33e788e.html
3.4 移植mks傳感器串口接口
詳見單獨(dú)作品:
https://club.rt-thread.org/ask/article/1074357ba9757cdb.html
4 整體驅(qū)動(dòng)實(shí)現(xiàn)
通過上的基工程的實(shí)現(xiàn),接下來就是整合驅(qū)動(dòng),實(shí)整個(gè)工程。
4.1 界面設(shè)計(jì)
4.1.1 GuiGuide設(shè)計(jì)
我使用開源的GuiGuider設(shè)了用戶交互界面:

控件1:btn_start,實(shí)現(xiàn)開始、停止測(cè)量的復(fù)用功能
控件2:label_heart 用于顯示測(cè)量到的心率值
控件3:label_spo2 用于顯示測(cè)量到的心率值
控件4:bar 用于顯示測(cè)量的進(jìn)度條
其余控件為固定標(biāo)簽。
4.1.2 事件添加
為btn_start 添加clicked事件

最后生成C語(yǔ)言的工程。
4.2 LVGL工程移植
4.2.1 復(fù)制guiguider工程
在生成工程的目錄中,我復(fù)制generated文件夾到基礎(chǔ)工程下的LVGL目錄下面,替換掉原來的generated文件夾。
4.2.2 添加文件到工程
在mdk中,將generated下面的所有.c/h添加到mdk工程中:

4.2.3 添加bnt事件代碼
根據(jù)傳感器的手冊(cè),我們開始測(cè)量時(shí),是向串口發(fā)送數(shù)據(jù)0x8A,停止是向串口發(fā)送0x88。
添加按鍵的的驅(qū)動(dòng)代碼如下:
externlv_ui guider_ui;externintmks_cmd(intargc,char*argv[]);// 全局/靜態(tài)變量:控制進(jìn)度條和定時(shí)器(確?;卣{(diào)中可訪問)staticlv_timer_t*progress_timer =NULL; // 進(jìn)度條定時(shí)器staticint current_progress =0; // 當(dāng)前進(jìn)度值(0-100)// 進(jìn)度條定時(shí)器回調(diào)函數(shù)(每隔 200ms 觸發(fā)一次)staticvoidprogress_timer_cb(lv_timer_t*timer){ current_progress++; // 每次進(jìn)度 +1%(200ms × 100 = 20 秒) // 更新進(jìn)度條值(關(guān)閉動(dòng)畫,避免和定時(shí)器沖突) lv_bar_set_value(guider_ui.screen_bar, current_progress, LV_ANIM_OFF); // 進(jìn)度達(dá)到 100%,停止定時(shí)器并重置狀態(tài) if(current_progress >=100) { lv_timer_del(progress_timer); // 刪除定時(shí)器(釋放資源) progress_timer =NULL; // 重置定時(shí)器指針 current_progress =0; // 重置進(jìn)度值 char*argv[] = {"mks_cmd","stop",NULL}; intargc =2; mks_cmd(argc, argv); // 可選:進(jìn)度完成后,自動(dòng)將按鈕切回「開始」 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"); // 停止已存在的定時(shí)器(避免重復(fù)啟動(dòng),防止進(jìn)度加速) if(progress_timer !=NULL) { lv_timer_del(progress_timer); progress_timer =NULL; } // 初始化進(jìn)度狀態(tài) current_progress =0; lv_bar_set_range(guider_ui.screen_bar,0,100); // 設(shè)置進(jìn)度條范圍:0-100(百分比) lv_bar_set_value(guider_ui.screen_bar,0, LV_ANIM_OFF); // 進(jìn)度條歸零 // 創(chuàng)建并啟動(dòng)定時(shí)器:周期 200ms,觸發(fā)回調(diào)函數(shù) progress_timer =lv_timer_create(progress_timer_cb,200,NULL); lv_timer_resume(progress_timer); // 啟動(dòng)定時(shí)器(LVGL 定時(shí)器默認(rèn)創(chuàng)建后啟動(dòng),保險(xiǎn)起見手動(dòng)調(diào)用) // 構(gòu)造參數(shù)并調(diào)用 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) { // 停止并刪除定時(shí)器 if(progress_timer !=NULL) { lv_timer_del(progress_timer); progress_timer =NULL; } // 進(jìn)度條歸零,狀態(tài)重置 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 心跳包的任務(wù),與初始化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); //在這里啟用兩個(gè)任務(wù) 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;}
將程序下載到開發(fā)板后,按下按鍵,能在USART5上面看到的0x8A、0x88輸出,說明程序運(yùn)轉(zhuǎn)正常。
4.3 mks傳感器驅(qū)動(dòng)
4.3.1 mks傳感器通信協(xié)議:

根據(jù)通信協(xié)議進(jìn)行串口的代碼實(shí)現(xiàn)。
4.3.2 mks串口添加:
在menuconfig中,打開usart5,保存工程并重新生成工程。
4.3.3 mks串口驅(qū)動(dòng)實(shí)現(xiàn)
在驅(qū)動(dòng)中,按照rtthead srial的標(biāo)準(zhǔn)驅(qū)動(dòng)。
由于mkd傳感器的波特率為38400,因此需要在初始化后,重新配置serial的驅(qū)動(dòng)結(jié)構(gòu)體,并進(jìn)行配置。
定義他為uart5 實(shí)現(xiàn)中斷接收功能,詳見驅(qū)動(dòng)代碼如下:
接收傳感器并實(shí)現(xiàn)解碼,并實(shí)時(shí)將接收到的數(shù)據(jù)通過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"/* 用于接收消息的信號(hào)量 */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// --- 內(nèi)部接收緩沖區(qū) ---staticuint8_tmks_rx_buffer[MKS_PACKET_SIZE];staticuint8_tmks_bytes_received =0;// lvglexternlv_ui guider_ui;staticchar buf[4];/* 接收數(shù)據(jù)回調(diào)函數(shù) */staticrt_err_tuart_input(rt_device_tdev,rt_size_tsize){ /* 串口接收到數(shù)據(jù)后產(chǎn)生中斷,調(diào)用此回調(diào)函數(shù),然后發(fā)送接收信號(hào)量 */ 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; // 初始無新數(shù)據(jù) // 初始化數(shù)據(jù)為0 memset(mks_rx_buffer,0, RECEIVE_LENGTH); mks_heart_rate =0; mks_spo2 =0; while(1) { /* 從串口讀取一個(gè)字節(jié)的數(shù)據(jù),沒有讀取到則等待接收信號(hào)量 */ while(rt_device_read(serial,-1, &ch,1) !=1) { /* 阻塞等待接收信號(hào)量,等到信號(hào)量后再次讀取數(shù)據(jù) */ 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; // 配置結(jié)構(gòu)體 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); } /* 查找系統(tǒng)中的串口設(shè)備 */ serial =rt_device_find(uart_name); if(!serial) { rt_kprintf("find %s failed!\n", uart_name); returnRT_ERROR; } cfg.baud_rate = BAUD_RATE_38400; // 目標(biāo)波特率(可改為 38400、19200 等) cfg.data_bits = DATA_BITS_8; // 8 數(shù)據(jù)位 cfg.stop_bits = STOP_BITS_1; // 1 停止位 cfg.parity = PARITY_NONE; // 無校驗(yàn) cfg.bit_order = BIT_ORDER_LSB; // 低位優(yōu)先(默認(rèn)) 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); /* 初始化信號(hào)量 */ rt_sem_init(&rx_sem,"rx_sem",0, RT_IPC_FLAG_FIFO); /* 以中斷接收及輪詢發(fā)送模式打開串口設(shè)備 */ rt_device_open(serial, RT_DEVICE_FLAG_INT_RX); /* 設(shè)置接收回調(diào)函數(shù) */ rt_device_set_rx_indicate(serial, uart_input); /* 發(fā)送字符串 */ rt_device_write(serial,0, str, (sizeof(str) -1)); /* 創(chuàng)建 serial 線程 */ rt_thread_tthread =rt_thread_create("serial", serial_thread_entry, RT_NULL,1024,25,10); /* 創(chuàng)建成功則啟動(dòng)線程 */ 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; // 要發(fā)送的字節(jié)數(shù)據(jù) // 第一步:檢查串口設(shè)備是否已初始化(首次使用時(shí)查找并打開) if(serial == RT_NULL) { // 查找串口設(shè)備 serial =rt_device_find(SAMPLE_UART_NAME); if(serial == RT_NULL) { rt_kprintf("錯(cuò)誤:未找到串口設(shè)備 %s!\n", SAMPLE_UART_NAME); returnRT_ERROR; } // 打開串口(RT_DEVICE_OFLAG_RDWR:讀寫模式) if(rt_device_open(serial, RT_DEVICE_OFLAG_RDWR) != RT_EOK) { rt_kprintf("錯(cuò)誤:打開串口設(shè)備 %s 失??!\n", SAMPLE_UART_NAME); serial = RT_NULL; // 打開失敗,重置句柄 returnRT_ERROR; } } // 第二步:解析命令參數(shù) if(argc ==2) // 僅支持 "mks start" 或 "mks stop"(2個(gè)參數(shù)) { // 比較第二個(gè)參數(shù)(argv[1]) if(strcmp(argv[1],"start") ==0) { send_data =0x8A; // start 對(duì)應(yīng)發(fā)送 0x8A rt_kprintf("執(zhí)行 mks start,發(fā)送數(shù)據(jù):0x%02X\n", send_data); } elseif(strcmp(argv[1],"stop") ==0) { send_data =0x88; // stop 對(duì)應(yīng)發(fā)送 0x88 rt_kprintf("執(zhí)行 mks stop,發(fā)送數(shù)據(jù):0x%02X\n", send_data); } else { // 無效參數(shù):提示正確用法 rt_kprintf("錯(cuò)誤:無效命令參數(shù)!\n"); rt_kprintf("正確用法:\n"); rt_kprintf(" mks start - 發(fā)送 0x8A\n"); rt_kprintf(" mks stop - 發(fā)送 0x88\n"); return-RT_EINVAL; } // 第三步:通過串口發(fā)送數(shù)據(jù)(發(fā)送1個(gè)字節(jié)) ret =rt_device_write(serial,0, &send_data,1); if(ret !=1) // rt_device_write 返回實(shí)際發(fā)送的字節(jié)數(shù),成功應(yīng)為 1 { rt_kprintf("錯(cuò)誤:串口發(fā)送失?。》祷刂担?d\n", ret); return-RT_ERROR; } } else { // 參數(shù)個(gè)數(shù)錯(cuò)誤:提示正確用法 rt_kprintf("錯(cuò)誤:命令格式錯(cuò)誤!\n"); rt_kprintf("正確用法:\n"); rt_kprintf(" mks start - 發(fā)送 0x8A\n"); rt_kprintf(" mks stop - 發(fā)送 0x88\n"); return-RT_EINVAL; } returnret;}// 3. 導(dǎo)出命令到 FinSH 終端(支持在終端直接輸入 mks 命令)MSH_CMD_EXPORT(mks_cmd, mks command: mks start/stop);/* 導(dǎo)出到 msh 命令列表中 */MSH_CMD_EXPORT(uart_sample, uart device sample);
接收傳感器并實(shí)現(xiàn)解碼,并實(shí)時(shí)將接收到的數(shù)據(jù)通過LVGL顯示到界面中。
5 工程效果

6 演示視頻及代碼
演示視頻鏈接:https://www.bilibili.com/video/BV1WgUoBXE2n/?pop_share=1&vd_source=e1bd226340c8b87027d5dcfc6b0c3344
源代碼:https://club.rt-thread.org/file_download/a6fe3781d8bcf12d
-
mcu
+關(guān)注
關(guān)注
147文章
18881瀏覽量
396618 -
RT-Thread
+關(guān)注
關(guān)注
32文章
1602瀏覽量
44716 -
兆易創(chuàng)新
+關(guān)注
關(guān)注
23文章
710瀏覽量
83549
發(fā)布評(píng)論請(qǐng)先 登錄
基于兆易創(chuàng)新GD32F527系列MCU的多媒體門禁系統(tǒng)解決方案
兆易創(chuàng)新加入RT-Thread高級(jí)會(huì)員合作伙伴計(jì)劃 | 戰(zhàn)略新篇
基于RT-Thread與兆易創(chuàng)新GD32F527的工業(yè)級(jí)網(wǎng)絡(luò)-CAN透?jìng)骶W(wǎng)關(guān)設(shè)計(jì)與實(shí)現(xiàn) | 技術(shù)集結(jié)
基于GD32F310開發(fā)板在rt-thread上添加finsh及其shell功能
兆易創(chuàng)新GD32F4xx系列MCU固件庫(kù)使用指南
【直播預(yù)告】GD32F527高性能MCU全方位解析,與RT-Thread的全棧開發(fā)實(shí)戰(zhàn) | 博觀講堂
基于RT-Thread與GD32F527I-EVAL的多媒體門禁系統(tǒng) | 技術(shù)集結(jié)
基于RT-Thread和兆易創(chuàng)新GD32F527系列MCU的健康監(jiān)測(cè)站 | 技術(shù)集結(jié)
評(píng)論