RTOS和LVGL我沒學過,但是應該能硬啃這個項目例程
├─Application/User/Tasks # 用于存放任務線程的函數
│ ├─user_TaskInit.c # 初始化任務
│ ├─user_HardwareInitTask.c # 硬件初始化任務
│ ├─user_RunModeTasks.c # 運行模式任務
│ ├─user_KeyTask.c # 按鍵任務
│ ├─user_DataSaveTask.c # 數據保存任務
│ ├─user_MessageSendTask.c # 消息發送任務
│ ├─user_ChargeCheckTask.c # 充電檢查任務
│ ├─user_SensUpdateTask.c # 傳感器更新任務
│ ├─user_ScrRenewTask.c # 屏幕刷新任務
一、TaskInit 任務初始化
該文件初始化嵌入式系統任務
1.?Tasks
線程ID和成員
osThreadId_t LvHandlerTaskHandle;
const osThreadAttr_t LvHandlerTask_attributes = {.name = "LvHandlerTask",.stack_size = 128 * 24, // 3KB棧空間.priority = osPriorityLow,
};
我們聯系一下 Linux 的線程:
首先是定義一個線程 ID
//CMSIS-RTOS2
osThreadId_t LvHandlerTaskHandle;//Linux
pthread_t threadId;
然后我們查看官網提供的API文檔:
這段代碼,只對 name,stack_size以及 priority 成員進行賦值。
name:線程的名字
stack_size:棧的大小
由于內存的最小尋址單元通常是1Byte,那么這段代碼所開辟的棧空間為:
128*24B=2^7*2^3*3B=2^10*3=3KB
priority:優先級
這里要提到優先級,其枚舉類型為:
typedef enum {osPriorityIdle = -3, ///< Priority: idle (lowest)osPriorityLow = -2, ///< Priority: lowosPriorityBelowNormal = -1, ///< Priority: below normalosPriorityNormal = 0, ///< Priority: normal (default)osPriorityAboveNormal = +1, ///< Priority: above normalosPriorityHigh = +2, ///< Priority: highosPriorityRealtime = +3, ///< Priority: realtime (highest)osPriorityError = 0x84, ///< System cannot determine priority or illegal priority.osPriorityReserved = 0x7FFFFFFF ///< Prevents enum down-size compiler optimization.
} osPriority;
創建線程
void User_Tasks_Init(void)
{/* add threads, ... */LvHandlerTaskHandle = osThreadNew(LvHandlerTask, NULL, &LvHandlerTask_attributes);KeyTaskHandle = osThreadNew(KeyTask, NULL, &KeyTask_attributes);ScrRenewTaskHandle = osThreadNew(ScrRenewTask, NULL, &ScrRenewTask_attributes);TimeRenewTaskHandle = osThreadNew(TimeRenewTask, NULL, &TimeRenewTask_attributes);HomeUpdataTaskHandle = osThreadNew(HomeUpdata_Task, NULL, &HomeUpdataTask_attributes);}
類似于 linux 的 create,返回類型為線程 ID。
Parameters
func | thread function. |
argument | pointer that is passed to the thread function as start argument. |
attr | thread attributes; NULL: default values. |
2.?Message queues?
首先也是類似于線程一樣,定義 ID
//Key message
osMessageQueueId_t Key_MessageQueue;
osMessageQueueId_t Idle_MessageQueue;
osMessageQueueId_t Stop_MessageQueue;
osMessageQueueId_t IdleBreak_MessageQueue;
osMessageQueueId_t HomeUpdata_MessageQueue;
osMessageQueueId_t DataSave_MessageQueue;
和上面一樣,也是創建隊列:?
/* add queues, ... */Key_MessageQueue = osMessageQueueNew(1, 1, NULL);Idle_MessageQueue = osMessageQueueNew(1, 1, NULL);Stop_MessageQueue = osMessageQueueNew(1, 1, NULL);IdleBreak_MessageQueue = osMessageQueueNew(1, 1, NULL);HomeUpdata_MessageQueue = osMessageQueueNew(1, 1, NULL);DataSave_MessageQueue = osMessageQueueNew(2, 1, NULL);
Parameters
msg_count | maximum number of messages in queue. |
msg_size | maximum message size in bytes. |
attr | message queue attributes; NULL: default values. |
3. 定時器
/* start timers, add new ones, ... */IdleTimerHandle = osTimerNew(IdleTimerCallback, osTimerPeriodic, NULL, NULL);osTimerStart(IdleTimerHandle,100);//100ms
Parameters
func | function pointer to callback function. |
type | osTimerOnce?for one-shot or?osTimerPeriodic?for periodic behavior. |
argument | argument to the timer callback function. |
attr | timer attributes; NULL: default values. |
返回值為?timer ID。
4. LVGL Tick
void TaskTickHook(void)
{//to increase the LVGL ticklv_tick_inc(1);//to increase the timerpage's timer(put in here is to ensure the Real Time)if(ui_TimerPageFlag){ui_TimerPage_ms+=1;if(ui_TimerPage_ms>=10){ui_TimerPage_ms=0;ui_TimerPage_10ms+=1;}if(ui_TimerPage_10ms>=100){ui_TimerPage_10ms=0;ui_TimerPage_sec+=1;uint8_t IdleBreakstr = 0;osMessageQueuePut(IdleBreak_MessageQueue, &IdleBreakstr, 0, 0);}if(ui_TimerPage_sec>=60){ui_TimerPage_sec=0;ui_TimerPage_min+=1;}if(ui_TimerPage_min>=60){ui_TimerPage_min=0;}}
}
LVGL心跳:每毫秒調用lv_tick_inc(1),驅動LVGL內部動畫和事件。
計時器邏輯:更新計時器時間,并在用戶操作時通過消息隊列打斷空閑狀態。
心跳更新
void?lv_tick_inc(uint32_t?tick_period)
tick_period?– 此函數的調用周期(以毫秒為單位)
傳送信息給消息隊列
osMessageQueuePut(IdleBreak_MessageQueue, &IdleBreakstr, 0, 0);
Put a Message into a Queue or timeout if Queue is full.
Parameters
mq_id | message queue ID obtained by?osMessageQueueNew. |
msg_ptr | pointer to buffer with message to put into a queue. |
msg_prio | message priority. |
timeout | Timeout Values?or 0 in case of no time-out. |
阻塞函數?osMessageQueuePut?將?msg_ptr?指向的消息放入參數?mq_id?指定的消息隊列中。參數?msg_prio?用于在插入時根據消息的優先級(數字越高表示優先級越高)對消息進行排序。
參數?timeout?指定系統等待將消息放入隊列的時間。在系統等待時,調用此函數的線程將進入阻塞狀態。參數?timeout?可以具有以下值:
- 當?timeout?為?0?時,函數立即返回(即 try 語義)。
- 當?timeout?設置為?osWaitForever?時,該函數將等待無限時間,直到消息被傳遞(即等待語義)。
- 所有其他值在 kernel ticks 中指定超時時間(即定時等待語義)。
5. LvHandlerTask (LVGL處理任務)
空閑檢測:通過lv_disp_get_inactive_time獲取用戶無操作時間。
實時性:osDelay(1)確保界面流暢響應。
void LvHandlerTask(void *argument) {while (1) {if (lv_disp_get_inactive_time(NULL) < 1000) {// 如果用戶1秒內無操作,發送空閑打斷消息osMessageQueuePut(IdleBreak_MessageQueue, &IdleBreakstr, 0, 0);}lv_task_handler(); // LVGL任務處理osDelay(1); // 1ms延遲}
}
lv_disp_get_inactive_time(NULL) < 1000
Get elapsed time since last user activity on a display (e.g. click)
獲取自上次用戶活動以來在顯示器上經過的時間(例如,單擊)
Parameters:
disp?– pointer to a display (NULL to get the overall smallest inactivity)
disp – 指向顯示的指針(NULL表示總的最小不活動)
Returns:
elapsed ticks (milliseconds) since the last activity
6. 看門狗
開發者這段代碼注釋掉了,后面再看看。
7. 小結
接下來,我們把初始化代碼的框架給出來:
?
二、KeyTask 按鍵任務
這部分很簡單,按下就將向消息隊列發送數據。
void KeyTask(void *argument)
{uint8_t keystr=0;uint8_t Stopstr=0;uint8_t IdleBreakstr=0;while(1){switch(KeyScan(0)){case 1://向兩個消息隊列發送數據keystr = 1;osMessageQueuePut(Key_MessageQueue, &keystr, 0, 1);//退出空閑狀態osMessageQueuePut(IdleBreak_MessageQueue, &IdleBreakstr, 0, 1);break;case 2:break;}osDelay(1);}
}
三、ScrRenewTask 屏幕更新任務
上面的按鍵任務,很自然會想到屏幕更新,肯定要切屏的。
而棧可以實現切換頁面。
而 PageStack.c 實現了導航棧:
#include "PageStack.h"uint8_t user_Stack_Push(user_Stack_T* stack, StackData_t datain)
{if(stack->Top_Point == MAX_DEPTH - 1){return -1;}stack->Data[stack->Top_Point++] = datain;return 0;
}uint8_t user_Stack_Pop(user_Stack_T* stack)
{if(stack->Top_Point == 0){return -1;}stack->Data[--stack->Top_Point] = NULL;return 0;
}uint8_t user_Stack_isEmpty(user_Stack_T* stack)
{if(stack->Top_Point == 0){return 1;} return 0;
}void user_Stack_Clear(user_Stack_T* stack)
{while(!user_Stack_isEmpty(stack)){user_Stack_Pop(stack);}
}
回到更新屏幕的代碼中:?
void ScrRenewTask(void *argument)
{uint8_t keystr=0;//將主頁壓入導航棧user_Stack_Push(&ScrRenewStack,(long long int)&ui_HomePage);while(1){//檢查按鍵消息隊列if(osMessageQueueGet(Key_MessageQueue,&keystr,NULL,0)==osOK){//key1 pressedif(keystr == 1){// 彈出當前頁面user_Stack_Pop(&ScrRenewStack);// 檢查棧是否為空if(user_Stack_isEmpty(&ScrRenewStack)){// 棧空時初始化并跳轉到菜單頁ui_MenuPage_screen_init();lv_scr_load_anim(ui_MenuPage,LV_SCR_LOAD_ANIM_MOVE_RIGHT,0,0,true);// 重建導航棧(主頁->菜單頁)user_Stack_Push(&ScrRenewStack,(long long int)&ui_HomePage);user_Stack_Push(&ScrRenewStack,(long long int)&ui_MenuPage);}// 當前是主頁else if(ScrRenewStack.Data[ScrRenewStack.Top_Point-1] == (long long int)&ui_HomePage){// 刷新主頁ui_HomePage_screen_init();lv_scr_load_anim(ui_HomePage,LV_SCR_LOAD_ANIM_MOVE_RIGHT,0,0,true);}// 當前是菜單頁else if(ScrRenewStack.Data[ScrRenewStack.Top_Point-1] == (long long int)&ui_MenuPage){// 刷新菜單頁ui_MenuPage_screen_init();lv_scr_load_anim(ui_MenuPage,LV_SCR_LOAD_ANIM_MOVE_RIGHT,0,0,true);//傳感器休眠代碼//HR sensor sleep//EM7028_hrs_DisEnable();//sensor sleep//LSM303DLH_Sleep();//SPL_Sleep();}//其他頁面處理,游戲頁面、設置頁面、時間設置頁面else if(ScrRenewStack.Data[ScrRenewStack.Top_Point-1] == (long long int)&ui_GameSelectPage){ui_GameSelectPage_screen_init();lv_scr_load_anim(ui_GameSelectPage,LV_SCR_LOAD_ANIM_MOVE_RIGHT,0,0,true);}else if(ScrRenewStack.Data[ScrRenewStack.Top_Point-1] == (long long int)&ui_SetPage){ui_SetPage_screen_init();lv_scr_load_anim(ui_SetPage,LV_SCR_LOAD_ANIM_MOVE_RIGHT,0,0,true);}else if(ScrRenewStack.Data[ScrRenewStack.Top_Point-1] == (long long int)&ui_DateTimeSetPage){ui_DateTimeSetPage_screen_init();lv_scr_load_anim(ui_DateTimeSetPage,LV_SCR_LOAD_ANIM_MOVE_RIGHT,0,0,true);}} //key2 pressed// 按鍵2處理(返回主頁)else if(keystr == 2){// 清空導航棧user_Stack_Clear(&ScrRenewStack);// 初始化并跳轉到主頁ui_HomePage_screen_init();lv_scr_load_anim(ui_HomePage,LV_SCR_LOAD_ANIM_MOVE_RIGHT,0,0,true);// 將主頁壓入棧user_Stack_Push(&ScrRenewStack,(long long int)&ui_HomePage);// 傳感器休眠代碼//HR sensor sleep//EM7028_hrs_DisEnable();//sensor sleep//LSM303DLH_Sleep();//SPL_Sleep();}} osDelay(10);}
}
獲取消息隊列的按鍵值
osMessageQueueGet(Key_MessageQueue,&keystr,NULL,0)==osOK
參數
mq_id | 通過 osMessageQueueNew?獲取的消息隊列 ID。 |
msg_ptr | 指向要從隊列中獲取的消息的緩沖區的指針。 |
msg_prio | 指向消息優先級或 NULL 的緩沖區的指針。 |
超時 | 超時值或 0(如果沒有超時) |
可能的?osStatus_t?返回值:
- osOK:已從隊列中檢索到消息。
- osErrorTimeout:在給定時間內無法從隊列中檢索消息(定時等待語義)。
- osErrorResource:沒有要從隊列中獲取的內容(嘗試語義)。
- osErrorParameter:參數?mq_id?為 NULL?或無效,ISR 中指定的非零超時。
- osErrorSafetyClass:調用線程安全等級低于指定消息隊列的安全等級。
屏幕加載函數
void lv_scr_load_anim(lv_obj_t * new_scr, // 新屏幕對象lv_scr_load_anim_t anim_type, // 動畫類型uint32_t time, // 動畫持續時間(毫秒)uint32_t delay, // 動畫延遲時間(毫秒)bool auto_del // 是否自動刪除舊屏幕
);
四、HomePageTask 主頁時間更新
void TimeRenewTask(void *argument)
{uint8_t value_strbuf[10];while(1){//檢查當前顯示的是否為主頁if(ScrRenewStack.Data[ScrRenewStack.Top_Point-1] == (long long int)&ui_HomePage){/*閃爍效果lv_obj_set_style_text_opa(ui_TimeColonLabel, 0, LV_PART_MAIN | LV_STATE_DEFAULT);osDelay(500);lv_obj_set_style_text_opa(ui_TimeColonLabel, 255, LV_PART_MAIN | LV_STATE_DEFAULT);*///time get and renew the screenRTC_DateTypeDef nowdate;RTC_TimeTypeDef nowtime;HAL_RTC_GetTime(&hrtc,&nowtime,RTC_FORMAT_BIN);//?ψgettime,·ü?2?áˊ±?? HAL_RTC_GetDate(&hrtc,&nowdate,RTC_FORMAT_BIN);//變化時更新顯示if(ui_TimeMinuteValue != nowtime.Minutes){ui_TimeMinuteValue = nowtime.Minutes;sprintf(value_strbuf,"%02d",ui_TimeMinuteValue);lv_label_set_text(ui_TimeMinuteLabel, value_strbuf);}if(ui_TimeHourValue != nowtime.Hours){ui_TimeHourValue = nowtime.Hours;sprintf(value_strbuf,"%2d",ui_TimeHourValue);lv_label_set_text(ui_TimeHourLabel, value_strbuf);}if(ui_DateDayValue != nowdate.Date){ui_DateDayValue = nowdate.Date;ui_DataWeekdayValue = nowdate.WeekDay;sprintf(value_strbuf,"%2d-%02d",ui_DateMonthValue,ui_DateDayValue);lv_label_set_text(ui_DateLabel, value_strbuf);lv_label_set_text(ui_DayLabel, ui_Days[ui_DataWeekdayValue-1]);}if(ui_DateMonthValue != nowdate.Month){ui_DateMonthValue = nowdate.Month;ui_DateDayValue = nowdate.Date;ui_DataWeekdayValue = nowdate.WeekDay;sprintf(value_strbuf,"%2d-%02d",ui_DateMonthValue,ui_DateDayValue);lv_label_set_text(ui_DateLabel, value_strbuf);lv_label_set_text(ui_DayLabel, ui_Days[ui_DataWeekdayValue-1]);}}osDelay(500);}
}/*** @brief homepage check the battery power and other data* @param argument: Not used* @retval None*/
void HomeUpdata_Task(void *argument)
{while(1){uint8_t HomeUpdataStr;if(osMessageQueueGet(HomeUpdata_MessageQueue,&HomeUpdataStr,NULL,0)==osOK){/*//batuint8_t value_strbuf[5];//計算電池電量ui_BatArcValue = PowerCalculate();if(ui_BatArcValue>0 && ui_BatArcValue<=100){}else{ui_BatArcValue=0;}//steps步數統計if(!Sensor_MPU_Erro){unsigned long STEPS = 0;if(!Sensor_MPU_Erro)dmp_get_pedometer_step_count(&STEPS);ui_StepNumValue = (uint16_t)STEPS;}//temp and humi 溫濕度讀取if(!Sensor_AHT21_Erro){//temp and humi messurefloat humi,temp;AHT_Read(&humi,&temp);//checkif(temp>-10 && temp<50 && humi>0 && humi<100){ui_EnvTempValue = (int8_t)temp;ui_EnvHumiValue = (int8_t)humi;}}//set text 更新UI顯示if(ScrRenewStack.Data[ScrRenewStack.Top_Point-1] == (long long int)&ui_HomePage){//bat set text 電池電量顯示更新lv_arc_set_value(ui_BatArc, ui_BatArcValue);sprintf(value_strbuf,"%2d%%",ui_BatArcValue);lv_label_set_text(ui_BatNumLabel, value_strbuf);//step set text 步數顯示更新sprintf(value_strbuf,"%d",ui_StepNumValue);lv_label_set_text(ui_StepNumLabel, value_strbuf);//send data save message queue 發送數據保存消息uint8_t Datastr = 3;osMessageQueuePut(DataSave_MessageQueue, &Datastr, 0, 1);//humi and temp set text 溫濕度顯示更新lv_arc_set_value(ui_TempArc, ui_EnvTempValue);lv_arc_set_value(ui_HumiArc, ui_EnvHumiValue);sprintf(value_strbuf,"%d",ui_EnvTempValue);lv_label_set_text(ui_TempNumLabel, value_strbuf);sprintf(value_strbuf,"%d",ui_EnvHumiValue);lv_label_set_text(ui_HumiNumLabel, value_strbuf);}*/}osDelay(500);}
}
五、SensorPageTask
/*** @brief 心率數據更新任務* @param argument: RTOS任務參數(未使用)* @retval None*/
void HRDataRenewTask(void *argument)
{uint8_t value_strbuf[4]; // 用于格式化顯示的字符串緩沖區uint8_t IdleBreakstr = 0; // 空閑狀態打斷標志uint16_t dat = 0; // 臨時數據存儲uint8_t hr_temp = 0; // 臨時心率值存儲while(1) // 任務主循環{// 檢查當前顯示的是否為心率頁if(ScrRenewStack.Data[ScrRenewStack.Top_Point-1] == (long long int)&ui_HRPage){// 發送空閑狀態打斷消息osMessageQueuePut(IdleBreak_MessageQueue, &IdleBreakstr, 0, 1);/*// 注釋掉的心率傳感器處理代碼// 喚醒心率傳感器EM7028_hrs_Enable();// 檢查傳感器是否就緒if(!Sensor_EM_Erro){// 計算心率值(臨界區保護)vTaskSuspendAll(); // 掛起所有任務hr_temp = HR_Calculate(EM7028_Get_HRS1(), user_HR_timecount);xTaskResumeAll(); // 恢復任務調度// 檢查心率值是否有效并更新顯示if(ui_HRValue != hr_temp && hr_temp>50 && hr_temp<120){// 更新UI顯示ui_HRValue = hr_temp;sprintf(value_strbuf, "%d", ui_HRValue);lv_label_set_text(ui_HRPageNumLabel, value_strbuf);}}*/}osDelay(50); // 每50ms執行一次}
}/*** @brief 傳感器數據更新任務* @param argument: RTOS任務參數(未使用)* @retval None*/
void SensorDataRenewTask(void *argument)
{uint8_t value_strbuf[6]; // 用于格式化顯示的字符串緩沖區uint8_t IdleBreakstr = 0; // 空閑狀態打斷標志while(1) // 任務主循環{// 檢查當前顯示的是否為血氧頁if(ScrRenewStack.Data[ScrRenewStack.Top_Point-1] == (long long int)&ui_SPO2Page){// 發送空閑狀態打斷消息osMessageQueuePut(IdleBreak_MessageQueue, &IdleBreakstr, 0, 1);// 血氧傳感器喚醒代碼(待實現)// sensor wake up}// 檢查當前顯示的是否為指南針頁else if(ScrRenewStack.Data[ScrRenewStack.Top_Point-1] == (long long int)&ui_CompassPage){// 發送空閑狀態打斷消息osMessageQueuePut(IdleBreak_MessageQueue, &IdleBreakstr, 0, 1);// 指南針數據處理代碼(待實現)}osDelay(300); // 每300ms執行一次}
}
心率部分,作者用的是原 FreeRTOS 的API,比如掛起和恢復任務調度:
vTaskSuspendAll()
xTaskResumeAll()
對應CMSIS 應該為:
osKernelSuspend()
osKernelResume()
六、MessageSendTask 藍牙
// BLE消息數據結構
struct {RTC_DateTypeDef nowdate; // 當前日期RTC_TimeTypeDef nowtime; // 當前時間int8_t humi; // 濕度值int8_t temp; // 溫度值uint8_t HR; // 心率值uint8_t SPO2; // 血氧值uint16_t stepNum; // 步數
} BLEMessage;// 時間設置消息結構
struct {RTC_DateTypeDef nowdate; // 要設置的日期RTC_TimeTypeDef nowtime; // 要設置的時間
} TimeSetMessage;/* Private function prototypes -----------------------------------------------*//*** @brief 從字符串中提取命令* @param str: 輸入字符串* @param cmd: 輸出命令緩沖區* @retval None*/
void StrCMD_Get(uint8_t *str, uint8_t *cmd)
{uint8_t i = 0;// 提取'='前的命令部分while(str[i] != '=') {cmd[i] = str[i];i++;}
}/*** @brief 解析時間格式字符串并設置RTC* @param str: 時間格式字符串(格式:OV+ST=20230629125555)* @retval None*/
uint8_t TimeFormat_Get(uint8_t *str)
{// 解析年月日時分秒(從字符串中提取)TimeSetMessage.nowdate.Year = (str[8]-'0')*10 + str[9]-'0';TimeSetMessage.nowdate.Month = (str[10]-'0')*10 + str[11]-'0';TimeSetMessage.nowdate.Date = (str[12]-'0')*10 + str[13]-'0';TimeSetMessage.nowtime.Hours = (str[14]-'0')*10 + str[15]-'0';TimeSetMessage.nowtime.Minutes = (str[16]-'0')*10 + str[17]-'0';TimeSetMessage.nowtime.Seconds = (str[18]-'0')*10 + str[19]-'0';// 檢查時間有效性if(TimeSetMessage.nowdate.Year>0 && TimeSetMessage.nowdate.Year<99 && TimeSetMessage.nowdate.Month>0 && TimeSetMessage.nowdate.Month<=12&& TimeSetMessage.nowdate.Date>0 && TimeSetMessage.nowdate.Date<=31&& TimeSetMessage.nowtime.Hours>=0 && TimeSetMessage.nowtime.Hours<=23&& TimeSetMessage.nowtime.Minutes>=0 && TimeSetMessage.nowtime.Minutes<=59&& TimeSetMessage.nowtime.Seconds>=0 && TimeSetMessage.nowtime.Seconds<=59){// 設置RTC時間和日期RTC_SetDate(TimeSetMessage.nowdate.Year, TimeSetMessage.nowdate.Month, TimeSetMessage.nowdate.Date);RTC_SetTime(TimeSetMessage.nowtime.Hours, TimeSetMessage.nowtime.Minutes, TimeSetMessage.nowtime.Seconds);printf("TIMESETOK\r\n"); // 發送設置成功響應}
}/*** @brief BLE消息發送任務* @param argument: RTOS任務參數(未使用)* @retval None*/
void MessageSendTask(void *argument)
{while(1) // 任務主循環{// 檢查是否有新數據通過UART接收if(HardInt_uart_flag){HardInt_uart_flag = 0; // 清除標志位// 發送空閑狀態打斷消息uint8_t IdleBreakstr = 0;osMessageQueuePut(IdleBreak_MessageQueue, &IdleBreakstr, NULL, 1);printf("RecStr:%s\r\n", HardInt_receive_str); // 打印接收到的字符串// 命令解析和處理if(!strcmp(HardInt_receive_str, "OV")) {printf("OK\r\n"); // 簡單響應}else if(!strcmp(HardInt_receive_str, "OV+VERSION")) {printf("VERSION=V2.3\r\n"); // 返回固件版本}else if(!strcmp(HardInt_receive_str, "OV+SEND")) {// 獲取當前各項數據HAL_RTC_GetTime(&hrtc, &(BLEMessage.nowtime), RTC_FORMAT_BIN);HAL_RTC_GetDate(&hrtc, &BLEMessage.nowdate, RTC_FORMAT_BIN);BLEMessage.humi = ui_EnvHumiValue;BLEMessage.temp = ui_EnvTempValue;BLEMessage.HR = ui_HRValue;BLEMessage.SPO2 = ui_SPO2Value;BLEMessage.stepNum = ui_StepNumValue;// 打印各項數據printf("data:%2d-%02d\r\n", BLEMessage.nowdate.Month, BLEMessage.nowdate.Date);printf("time:%02d:%02d:%02d\r\n", BLEMessage.nowtime.Hours, BLEMessage.nowtime.Minutes, BLEMessage.nowtime.Seconds);printf("humidity:%d%%\r\n", BLEMessage.humi);printf("temperature:%d\r\n", BLEMessage.temp);printf("Heart Rate:%d%%\r\n", BLEMessage.HR);printf("SPO2:%d%%\r\n", BLEMessage.SPO2);printf("Step today:%d\r\n", BLEMessage.stepNum);}// 處理時間設置命令(格式:OV+ST=20230629125555)else if(strlen(HardInt_receive_str) == 20) {uint8_t cmd[10];memset(cmd, 0, sizeof(cmd));StrCMD_Get(HardInt_receive_str, cmd); // 提取命令// 檢查是否為時間設置命令且系統處于應用模式if(user_APPSy_EN && !strcmp(cmd, "OV+ST")) {TimeFormat_Get(HardInt_receive_str); // 解析并設置時間}}memset(HardInt_receive_str, 0, sizeof(HardInt_receive_str)); // 清空接收緩沖區}osDelay(1000); // 每1秒檢查一次}
}
七、StopEnterTask
/*** @brief 進入空閑狀態任務(降低亮度)* @param argument: RTOS任務參數(未使用)* @retval None*/
void IdleEnterTask(void *argument)
{uint8_t Idlestr = 0; // 進入空閑狀態消息uint8_t IdleBreakstr = 0; // 退出空閑狀態消息while(1) // 任務主循環{// 檢查是否收到進入空閑狀態消息(降低背光)if(osMessageQueueGet(Idle_MessageQueue, &Idlestr, NULL, 1) == osOK){LCD_Set_Light(5); // 設置最低背光亮度(5%)}// 檢查是否收到退出空閑狀態消息(恢復背光)if(osMessageQueueGet(IdleBreak_MessageQueue, &IdleBreakstr, NULL, 1) == osOK){IdleTimerCount = 0; // 重置空閑計時器LCD_Set_Light(ui_LightSliderValue); // 恢復用戶設置的背光亮度}osDelay(10); // 每10ms檢查一次}
}/*** @brief 進入停止模式任務(深度睡眠)* @param argument: RTOS任務參數(未使用)* @retval None*/
void StopEnterTask(void *argument)
{uint8_t Stopstr; // 進入停止模式消息uint8_t HomeUpdataStr; // 主頁更新消息uint8_t Wrist_Flag = 0; // 手腕檢測標志while(1){// 檢查是否收到進入停止模式消息if(osMessageQueueGet(Stop_MessageQueue, &Stopstr, NULL, 0) == osOK){/***** 睡眠前操作 *****/sleep:IdleTimerCount = 0; // 重置空閑計時器// 關閉外設以省電LCD_RES_Clr(); // 復位LCDLCD_Close_Light(); // 關閉背光CST816_Sleep(); // 觸摸屏進入睡眠/***** 進入停止模式 *****/vTaskSuspendAll(); // 掛起所有任務(防止任務調度干擾)// 關閉看門狗(根據實際情況可選)//WDOG_Disnable();// 禁用SysTick中斷(防止喚醒)CLEAR_BIT(SysTick->CTRL, SysTick_CTRL_TICKINT_Msk);// 進入STOP模式(保持主穩壓器開啟,WFI喚醒)HAL_PWR_EnterSTOPMode(PWR_MAINREGULATOR_ON, PWR_STOPENTRY_WFI);/***** 喚醒后操作 *****/// 重新啟用SysTickSET_BIT(SysTick->CTRL, SysTick_CTRL_TICKINT_Msk);// 重新配置系統時鐘(STOP模式會關閉HSI)HAL_SYSTICK_Config(SystemCoreClock / (1000U / uwTickFreq));SystemClock_Config();// 恢復任務調度xTaskResumeAll();/***** 喚醒后初始化 *****//*// 可選:MPU6050手腕檢測邏輯if(user_MPU_Wrist_EN){uint8_t hor = MPU_isHorizontal(); // 檢測設備是否水平if(hor && user_MPU_Wrist_State == WRIST_DOWN){user_MPU_Wrist_State = WRIST_UP;Wrist_Flag = 1; // 標記為手腕抬起喚醒}else if(!hor && user_MPU_Wrist_State == WRIST_UP){user_MPU_Wrist_State = WRIST_DOWN;IdleTimerCount = 0;goto sleep; // 如果手腕放下,重新進入睡眠}}*/// 檢查喚醒源(按鍵1、充電狀態或手腕抬起)if(!KEY1 || HardInt_Charg_flag || Wrist_Flag){Wrist_Flag = 0; // 清除標志// 繼續執行喚醒流程}else{IdleTimerCount = 0;goto sleep; // 無有效喚醒源,重新睡眠}// 重新初始化外設LCD_Init(); // 初始化LCDLCD_Set_Light(ui_LightSliderValue); // 恢復背光CST816_Wakeup(); // 喚醒觸摸屏// 可選:充電檢測// if(ChargeCheck()) { HardInt_Charg_flag = 1; }// 發送主頁更新消息osMessageQueuePut(HomeUpdata_MessageQueue, &HomeUpdataStr, 0, 1);}osDelay(100); // 每100ms檢查一次}
}/*** @brief 空閑計時器回調函數* @param argument: RTOS定時器參數(未使用)* @retval None*/
void IdleTimerCallback(void *argument)
{IdleTimerCount += 1; // 計數器遞增(每100ms觸發一次)// 達到背光關閉時間閾值(ui_LTimeValue單位秒×10)if(IdleTimerCount == (ui_LTimeValue * 10)){uint8_t Idlestr = 0;osMessageQueuePut(Idle_MessageQueue, &Idlestr, 0, 1); // 觸發降低背光}// 達到系統休眠時間閾值(ui_TTimeValue單位秒×10)if(IdleTimerCount == (ui_TTimeValue * 10)){uint8_t Stopstr = 1;IdleTimerCount = 0; // 重置計數器osMessageQueuePut(Stop_MessageQueue, &Stopstr, 0, 1); // 觸發深度睡眠}
}