STM32智能手表——任務線程部分

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

functhread function.
argumentpointer that is passed to the thread function as start argument.
attrthread 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_countmaximum number of messages in queue.
msg_sizemaximum message size in bytes.
attrmessage queue attributes; NULL: default values.

3. 定時器

  /* start timers, add new ones, ... */IdleTimerHandle = osTimerNew(IdleTimerCallback, osTimerPeriodic, NULL, NULL);osTimerStart(IdleTimerHandle,100);//100ms

Parameters

funcfunction pointer to callback function.
typeosTimerOnce?for one-shot or?osTimerPeriodic?for periodic behavior.
argumentargument to the timer callback function.
attrtimer 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_idmessage queue ID obtained by?osMessageQueueNew.
msg_ptrpointer to buffer with message to put into a queue.
msg_priomessage priority.
timeoutTimeout 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);  // 觸發深度睡眠}
}

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/pingmian/75590.shtml
繁體地址,請注明出處:http://hk.pswp.cn/pingmian/75590.shtml
英文地址,請注明出處:http://en.pswp.cn/pingmian/75590.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

ubuntu22.04LTS設置中文輸入法

打開搜狗網址直接下載軟件&#xff0c;軟件下載完成后&#xff0c;會彈出安裝教程說明書。 網址:搜狗輸入法linux-首頁搜狗輸入法for linux—支持全拼、簡拼、模糊音、云輸入、皮膚、中英混輸https://shurufa.sogou.com/linux

SQL Server數據庫異常-[SqlException (0x80131904): 執行超時已過期] 操作超時問題及數據庫日志已滿的解決方案

&#x1f9d1; 博主簡介&#xff1a;CSDN博客專家、CSDN平臺優質創作者&#xff0c;獲得2024年博客之星榮譽證書&#xff0c;高級開發工程師&#xff0c;數學專業&#xff0c;擁有高級工程師證書&#xff1b;擅長C/C、C#等開發語言&#xff0c;熟悉Java常用開發技術&#xff0c…

php8 ?-> nullsafe 操作符 使用教程

簡介 PHP 8 引入了 ?->&#xff08;Nullsafe 操作符&#xff09;&#xff0c;用于簡化 null 檢查&#xff0c;減少繁瑣的 if 語句或 isset() 代碼&#xff0c;提高可讀性。 ?-> Nullsafe 操作符的作用 在 PHP 7 及以下&#xff0c;訪問對象的屬性或方法時&#xff0…

WORD+VISIO輸出PDF圖片提高清晰度的方法

WORDVISIO輸出PDF圖片提高清晰度的方法 part 1: visio 繪圖part 2: word 導出 part 1: visio 繪圖 先在visio中把圖片和對應的文字調整為適合插入到文章中的尺寸&#xff1b; 在visio中把所有元素進行組合&#xff1b; 把組合后的圖片長和寬等比例放縮&#xff0c;如放大10倍…

重要頭文件下的函數

1、<cctype> #include<cctype>加入這個頭文件就可以調用以下函數&#xff1a; 1、isalpha(x) 判斷x是否為字母 isalpha 2、isdigit(x) 判斷x是否為數字 isdigit 3、islower(x) 判斷x是否為小寫字母 islower 4、isupper(x) 判斷x是否為大寫字母 isupper 5、isa…

基于大模型預測不穩定性心絞痛的多維度研究與應用

目錄 一、引言 1.1 研究背景與意義 1.2 研究目的 1.3 國內外研究現狀 二、不穩定性心絞痛概述 2.1 定義與分類 2.2 發病機制 2.3 臨床表現 三、大模型技術原理與應用基礎 3.1 大模型介紹 3.2 在醫療領域的應用現狀 3.3 用于不穩定性心絞痛預測的可行性 四、術前預…

第一講—函數的極限與連續(一)

思維導圖 筆記 雙曲正弦函數及其反函數

Mac VM 卸載 win10 安裝win7系統

卸載 找到相應直接刪除&#xff08;移動到廢紙簍&#xff09; 可參考&#xff1a;mac如何卸載虛擬機win 下載 win7下載地址

免費送源碼:Java+SSM+Android Studio 基于Android Studio游戲搜索app的設計與實現 計算機畢業設計原創定制

摘要 本文旨在探討基于SSM框架和Android Studio的游戲搜索App的設計與實現。首先&#xff0c;我們詳細介紹了SSM框架&#xff0c;這是一種經典的Java Web開發框架&#xff0c;由Spring、SpringMVC和MyBatis三個開源項目整合而成&#xff0c;為開發企業級應用提供了高效、靈活、…

網絡安全的現狀與防護措施

隨著數字化和信息化的迅猛發展&#xff0c;互聯網已成為人們日常生活、工作和學習不可或缺的一部分。然而&#xff0c;隨著網絡技術的普及&#xff0c;網絡安全問題也日益突出。近年來&#xff0c;數據泄露、惡意軟件、網絡攻擊等事件層出不窮&#xff0c;給企業和個人帶來了巨…

android databinding使用教程

Android DataBinding 是一種可以將 UI 組件與數據源綁定的框架&#xff0c;能夠減少 findViewById 的使用&#xff0c;并提高代碼的可維護性。下面是 DataBinding 的完整使用教程&#xff1a; 1. 啟用 DataBinding 在 build.gradle&#xff08;Module 級別&#xff09;中啟用 …

python如何快速刪除文件夾中的大量文件

在 Python 中&#xff0c;刪除文件夾中的大量小圖片文件可以通過使用 os 模塊或 shutil 模塊來實現。以下是一個示例代碼&#xff0c;展示了如何快速刪除指定文件夾中的所有文件。如果你只需要刪除小圖片文件&#xff0c;可以添加額外的邏輯來檢查文件大小。 以下是一個示例代…

如何使用 IntelliJ IDEA 開發命令行程序(或 Swing 程序)并手動管理依賴(不使用 pom.xml)

以下是詳細步驟&#xff1a; 1. 創建項目 1.1 打開 IntelliJ IDEA。 1.2 在啟動界面&#xff0c;點擊 Create New Project&#xff08;創建新項目&#xff09;。 1.3 選擇 Java&#xff0c;然后點擊 Next。 1.4 確保 Project SDK 選擇了正確的 JDK 版本&#x…

FastAPI-Cache2: 高效Python緩存庫

FastAPI-Cache2是一個強大而靈活的Python緩存庫&#xff0c;專為提升應用性能而設計。雖然其名稱暗示與FastAPI框架的緊密集成&#xff0c;但實際上它可以在任何Python項目中使用&#xff0c;為開發者提供簡單而高效的緩存解決方案。 在現代應用開發中&#xff0c;性能優化至關…

android開發:zxing-android-embedded豎屏掃描功能

Android 點擊按鈕調用豎屏二維碼掃描 提示&#xff1a;zxing-android-embedded插件已過時&#xff0c;建議更換別的。 場景&#xff1a;Home頁面上有個掃描按鈕&#xff0c;點擊后打開攝像頭完成掃描功能&#xff0c;掃描時要求豎屏。 方案&#xff1a;使用zxing-android-embe…

指令補充+樣式綁定+計算屬性+監聽器

一、指令補充 1. 指令修飾符 1. 作用: 借助指令修飾符, 可以讓指令更加強大 2. 分類: 1> 按鍵修飾符: 用來檢測用戶的按鍵, 配合鍵盤事件使用. keydown 和 keyup 語法: keydown.enter/v-on:keydown.enter 表示當enter按下的時候觸發 keyup.enter/v-on:keyup.enter 表示當…

Python基于時間序列分析的降雨量預測系統的設計與實現【附源碼、文檔說明】

博主介紹&#xff1a;?Java老徐、7年大廠程序員經歷。全網粉絲12w、csdn博客專家、掘金/華為云/阿里云/InfoQ等平臺優質作者、專注于Java技術領域和畢業項目實戰? &#x1f345;文末獲取源碼聯系&#x1f345; &#x1f447;&#x1f3fb; 精彩專欄推薦訂閱&#x1f447;&…

【2022】【論文筆記】基于相變材料的光學激活的、用于THz光束操作的編碼超表面——

前言 類型 太赫茲 + 超表面 太赫茲 + 超表面 太赫茲+超表面 期刊 A D V A N C E D ?? O P T I C A L ?? M A T E R I A L S ADVANCED \; OPTICAL \; MATERIALS

[VolgaCTF 2025] Baby-Welcome,BrokeBroke,Field Rules

這個就做了倆題&#xff0c;其實再努力點就是3個。不過最近也算是好成績了&#xff0c;45名。國外的比賽對我來說還算是比較難的。 Baby-Welcome 這個流程還挺麻煩&#xff0c;先是注冊用戶&#xff0c;登錄的用戶可以給其它用戶發消息。收到消息的用戶可以顯示消息&#xff…

再見VS Code!Google IDE 正顛覆傳統開發體驗

云端開發的革命&#xff1a;Google Project IDX 如何顛覆傳統開發體驗 在軟件開發領域&#xff0c;Google 最新推出的 Project IDX 絕非僅僅是另一個“基于瀏覽器的 VS Code”——它是一次真正的范式轉變。與 VS Code、Cursor 等傳統工具不同&#xff0c;IDX 是一個完全云原生的…