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
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,·üЂ²»Áˊ±¼䊉 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); // 触发深度睡眠}
}
相关文章:
STM32智能手表——任务线程部分
RTOS和LVGL我没学过,但是应该能硬啃这个项目例程 ├─Application/User/Tasks # 用于存放任务线程的函数 │ ├─user_TaskInit.c # 初始化任务 │ ├─user_HardwareInitTask.c # 硬件初始化任务 │ ├─user_RunModeTasks.c…...
Java线程池详解
摘要:线程池是Java高并发编程的核心组件,有效管理线程生命周期并提升系统性能。本文将深入剖析Java线程池的实现原理、配置策略及生产环境中的实战技巧,助您构建高效稳定的多线程应用。 一、线程池核心价值 1.1 为什么需要线程池?…...
Git Fetch 和 Git Pull 的区别
Git fetch和git pull的区别 二者都能够从远程获取最新版本到本地。 1. Git fetch 仅从远程获取最新版本到本地,不会进行 merge(合并)操作。 操作示例 从远程的 origin的 master 主分支上获取最新版本到 origin/master 分支上:…...
《2核2G阿里云神操作!Ubuntu+Ollama低成本部署Deepseek模型实战》
简介: “本文为AI开发者揭秘如何在阿里云2核2G轻量级ECS服务器上,通过Ubuntu系统与Ollama框架实现Deepseek模型的高效部署。无需昂贵硬件,手把手教程涵盖环境配置、资源优化及避坑指南,助力初学者用极低成本在云端跑通行业领先的大…...
Rust闭包详解
文章目录 闭包捕获外部变量移动和借用闭包的特性闭包和性能闭包和生命周期 闭包 Rust中的闭包是一种匿名函数,可以捕获并存储环境中的变量,有点类似于Lambda表达式 闭包允许在其定义的作用域之外访问变量,并且可以在需要时将其移动或者借用…...
科技潮流出行新体验 方程豹全新车型钛3正式开启预售
科技潮流出行新体验,比亚迪个性化品牌方程豹旗下全新车型钛3正式开启预售,钛3定位科技潮品SUV,搭载独有的潮流配置“1机3舱”,以及“iCT”安全三件套、“E2C”智能三件套,实现了科技越级、空间越级、配置越级ÿ…...
如何将AI模型返回的字符串转为html元素?
场景: 接入deepseek模型的api到我们平台,返回的字符串需要做下格式化处理。 返回的数据是这样的: {"role": "assistant","content": "<think>\n嗯,用户问的是“星体是什么”。首先&am…...
使用SpringBoot + Thymeleaf + iText实现动态PDF导出
使用SpringBoot Thymeleaf iText实现动态PDF导出 1.前端模版代码,需要注意,iText有很多高级样式兼用性不好,需要自己试错: <!DOCTYPE html> <html lang"en" xmlns:th"http://www.thymeleaf.org"…...
Redis 源码硬核解析系列专题 - 扩展篇:Gossip协议的具体实现
1. 引言 Redis Cluster使用Gossip协议实现节点间的状态同步和一致性维护。Gossip协议是一种去中心化的通信机制,通过节点间的“谣言传播”方式交换信息,具有高容错性和扩展性。本篇将深入剖析Redis中Gossip协议的具体实现,包括消息格式、传播机制和故障检测逻辑。 2. Gossi…...
scGPT环境安装
scGPT环境安装 conda create -n scgpt_2 conda activate scgpt_2 conda install python3.10.11 cudatoolkit11.7 cudatoolkit-dev gxx>6.0.0,<12.0 cudnn -c conda-forge pip install torch1.13.0cu117 torchvision0.14.0cu117 torchaudio0.13.0 --extra-index-url https…...
linux服务器组建与管理
环境: DNSSamba服务器 ip:192.168.177.153 FTP服务器 ip:192.168.177.152 pc ip:192.168.177.151 这里先把DNS的ip及DNS固定给固定了,免得我关机了还会更改 网络配置:(前面的命令是DNS/Samba的后面的是FTP的,下面那张是示例图) sudo nmcli con mod ens33 ipv4.addres…...
vue3 生命周期函数(挂载、更新、销毁)
在这之前,相必用户也是用过vue2的经历,所以,在讲解之前先对vue2和vue3的生命周期进行对比: Option API组合APIbeforeCreate-setupcreated-setupbeforeMountonBeforeMountmountedonMountedbeforeUpdateonBeforeUpdateupdatedonUpd…...
树莓派超全系列教程文档--(20)树莓派配置自动息屏
树莓派配置自动息屏 配置自动息屏桌面Raspberry Pi 配置CLI 控制台设置控制台模式自动息屏查看当前自动息屏设置 文章来源: http://raspberry.dns8844.cn/documentation 原文网址 配置自动息屏 您可以将 Raspberry Pi 配置为在一段时间不活动后自动息屏。默认情况…...
基于 Qt / HTTP/JSON 的智能天气预报系统测试报告
目录 一、项目概述 1.1项目背景 1.2项目目标 二、功能需求 2.1 用户界面功能 2.2 后台功能 三、技术选择 3.1 开发框架与工具 3.2 第三方 API 四、UI设计 4.1界面展示 4.2stylesheet样式 五、代码实现 1.构造函数 2.网络请求响应处理函数 3.处理json数据 4.更新…...
Oracle数据库数据编程SQL<3.7 PL/SQL 触发器(Trigger)>
触发器是Oracle数据库中的一种特殊存储过程,它会在特定数据库事件发生时自动执行。触发器通常用于实现复杂的业务规则、数据验证、审计跟踪等功能。 目录 一、触发器基本概念 1. 触发器特点 2. 触发器组成要素 二、触发器类型 1. DML触发器 2. DDL触发器 3.…...
反常积分和定积分的应用 1
网课 还是得跟上网课的进度。但是不要给自己太大的压力。看到数学题确实有点慌张。老师为什么说写对了不要打对号,我感觉打对号可以给自己充足的正反馈。关键问题就是能做对的题不多。这篇笔记主要整理网课的一些笔记。网课落下的比较多,大概还需要补好…...
Day49 | 11. 盛最多水的容器、16. 最接近的三数之和、33. 搜索旋转排序数组、36. 有效的数独
11. 盛最多水的容器 题目链接:11. 盛最多水的容器 - 力扣(LeetCode) 题目难度:中等 代码: class Solution {public int maxArea(int[] height) {int i0,jheight.length-1,res0;while(i<j){resheight[i]<heig…...
31天Python入门——第20天:魔法方法详解
你好,我是安然无虞。 文章目录 魔法方法1. __new__和__del__2. __repr__和__len__3. __enter__和__exit__4. 可迭代对象和迭代器5. 中括号[]数据操作6. __getattr__、__setattr__ 和 __delattr__7. 可调用的8. 运算符 魔法方法 魔法方法: Python中的魔法方法是一类…...
WPF 浅述IsHitTestVisible属性
WPF 浅述IsHitTestVisible属性 IsHitTestVisible 属性是 WPF 中一个非常重要的属性,它决定了一个控件是否可以作为 hit test 的一部分被检测到。理解这个属性对于处理交互事件(如鼠标点击、触摸等)非常重要。 IsHitTestVisible 属性的含义&am…...
WSN 经典定位算法
WSN 经典定位算法 包括: Centoid, Bounding_box, Grid_Scan, RSSI, DV_hop, MDS_MAP,APIT-WSN Localization/Amorphous/Amorphous.m , 3351 Localization/APIT/APIT.m , 4169 Localization/APIT/PPIT.m , 3889 Localization/Bounding Box/Bounding_Box.…...
LLM 优化技术(1)——Scaled-Dot-Product-Attention(SDPA)
在 Transformer 中抛弃了传统的 CNN 和 RNN,整个网络结构完全由Scaled Dot Product Attention 和Feed Forward Neural Network组成。一个基于 Transformer 的可训练的神经网络可以通过堆叠 Transformer 的形式进行搭建,Attention is All You Need论文中通…...
【深度学习】嘿马深度学习目标检测教程第1篇:商品目标检测要求、目标,1.1 项目演示【附代码文档】
教程总体简介:要求 目标 1.1 项目演示 学习目标 1.1 图像识别背景 1.2 什么是目标检测 1.2.1 目标检测定义 1.2.1.1 物体 1.3 目标检测应用场景 1.3.1 行业 1.3.2 应用类别 1.4 开发环境搭建 目标检测概述 3.1 目标检测任务描述 3.1.4 目标定位的简单实现 项目实现 …...
【蓝桥杯】单片机设计与开发,RTC实时时钟
一、RTC-DS1302概述 二、BCD码 三、三线协议概述 四、RTC的应用 五、DS1302的驱动函数 六、操作流程 七、三线协议驱动程序...
Java 各版本的新特性
Java 各版本的新特性主要集中在提升开发效率、性能优化、语言功能增强和模块化支持等方面。以下是 JDK 8 到 JDK 21(截至2023年)的主要新特性概览: JDK 8 (2014) - LTS Lambda 表达式:支持函数式编程,简化匿名内部类。…...
OpenGL中EBO的使用及原理
EBO 是什么? 在OpenGL中,EBO(Element Buffer Object),也称为索引缓冲对象 IBO(Index Buffer Object),是一种用于存储顶点索引数据的缓冲区对象。它的核心作用是通过复用顶点数据来减…...
应用分享 | AWG技术突破:操控钻石氮空位色心,开启量子计算新篇章!
利用AWG操作钻石中的氮空位色彩中心 金刚石中的颜色中心是晶格中的缺陷,其中碳原子被不同种类的原子取代,而相邻的晶格位点则是空的。由于色心具有明亮的单光子发射和光学可触及的自旋,因此有望成为未来量子信息处理和量子网络的固态量子发射…...
【Ultralytics YOLO COCO 评估脚本 | 获得COCO评价指标】
文章目录 Ultralytics YOLO COCO 评估脚本 (coco_evaluate.py)1. 描述2. 依赖项3. 使用方法4. 输入文件格式5. 输出6. 注意7. 完整代码 Ultralytics YOLO COCO 评估脚本 (coco_evaluate.py) 这是一个 Python 脚本,用于评估以 COCO JSON 格式提供的目标检测结果。它…...
聊一聊,元件封装知多少?
目录 01 | 简 介 02 | 常见的无源器件封装 03 | 集成(IC)类封装 04 | 功率器件类封装 05 | 连接器类封装 06 | 总 结 01 | 简 介 由于平时工作中,经常需要查看封装的样式,以便初步规划PCB布局;遂萌发对常用的元件封装进行一次总结。 …...
企业需要使用防病毒系统保障数据安全的原因
数据作为企业的重要资产,正面临勒索病毒等极大威胁。在复杂严峻的网络安全形势下,企业的业务运营、数据安全和声誉遭遇诸多来自网络的挑战。2023年,国内发生多起严重网络安全事件,例如数据库漏洞导致数据泄露、钓鱼邮件窃取信息、…...
使用无人机进行露天矿运输道路分析
使用无人机进行露天矿运输道路分析 无人机正在彻底改变采矿业,为露天矿场的运输道路收集数据和分析提供了一种新方法。通过使用 UAS 技术,采矿公司可以更全面地了解道路状况,确定磨损区域,并提高安全性和效率。 本文介绍了无人机用…...
基于Vue.js网页开发相关知识:Vue-router
一、基础知识 vue-router 是 Vue.js 官方的路由管理器,用于实现单页面应用(SPA)的路由功能。以下从几个方面对 vue-router 进行详细分析: 1. 核心概念 路由配置 vue-router 通过定义路由配置对象来管理应用的路由。每个路由配置…...
同时使用Telnet和SSH登录思科交换机
同时使用Telnet和SSH登录思科交换机 1. 配置管理IP地址 首先,为交换机配置一个管理IP地址,以便可以通过网络进行远程管理: Switch(config)# interface vlan [VLAN_ID] Switch(config-if)# ip address [IP地址] [子网掩码] Switch(config-i…...
presto行转列
presto的行列转换和spark、hive一样也是通过外链语句实现的,只不过语法和关键子有点不同,如下 with tmp1 as (select 1,2,3 as a1,4,5,6 as a2 ) select * from tmp1 cross join unnest(split(tmp1.a1, ,),split(tmp1.a2, ,) ) as b(a1s,a2s) 结果如下...
App Usage v5.57 Pro版 追踪手机及应用使用情况
手机使用监控神器:让你的手机使用情况一目了然 现代人的生活已经离不开手机——通讯、娱乐、支付、购物…每天我们花在手机上的时间越来越多。你是否好奇: 每天在各个应用上花费了多少时间?一天中查看了多少次手机?哪些应用在后…...
24.3 CogView3多模态生成实战:从API调优到1024高清图像生成全解析
CogView3多模态生成实战:从API调优到1024高清图像生成全解析 CogView3 & CharGLM:多模态生成技术深度解析 关键词:CogView3 API 调用,图像生成与编辑,多模态提示工程,GLM 技术栈集成,参数优化策略 1. 智谱清言平台演示 CogView-3 核心能力 1.1 CogView3 技术架构…...
操作系统高频(六)linux内核
操作系统高频(六)linux内核 1.内核态,用户态的区别⭐⭐⭐ 内核态和用户态的区别主要在于权限和安全性。 权限:内核态拥有最高的权限,可以访问和执行所有的系统指令和资源,而用户态的权限相对较低&#x…...
Ubuntu系统安装Cpolar 实现内网穿透教程
文章目录 方法 1:使用官方脚本快速安装(推荐)方法 2:手动下载安装包配置与使用常见问题 方法 1:使用官方脚本快速安装(推荐) 下载安装脚本 打开终端,执行以下命令下载并运行安装脚本…...
Trustworthy Machine Learning
1. 可信任机器学习的核心概念 1.1 可信任性的定义 稳健性(Robustness): 机器学习模型在面对数据噪声、分布变化或对抗性攻击时仍能维持其预测性能的能力。 公平性(Fairness): 避免 AI 决策对某些群体存在…...
Enovia许可管理系统的特点
在当今竞争激烈的市场环境中,企业对于产品生命周期管理(PLM)的需求日益增加。Enovia许可管理系统,作为一款先进的许可证管理工具,凭借其卓越的特点,助力企业实现资源的高效管理和最大化利用。本文将详细介绍…...
【CSS】样式与效果
个人主页:Guiat 归属专栏:HTML CSS JavaScript 文章目录 1. CSS盒模型1.1 盒模型基础1.2 盒模型类型1.2.1 标准盒模型1.2.2 IE盒模型 2. CSS选择器2.1 基本选择器2.2 组合选择器2.3 伪类和伪元素 3. CSS布局技术3.1 Flexbox布局3.2 Grid布局3.3 定位 4. …...
Python中常用网络编程模块
学习籽料在下方自拿 一、网络基础 网络由下往上分为:物理层、数据链路层、网络怪、传输层、会话层、表示层和应用层。 TCP/IP协议是传输层协议,主要解决数据如何在网络中传输;socket则是对TCP/IP协议的封装,它本身不是协议&…...
python-flask
1.定时任务的时候一定要加--preload,防止 --preload gunicorn --config gunicorn-conf.py --preload index:app 2.source /usr/local/nginx/html/prod/pypd/venv/bin/activate 启动linux的python环境 3.pip freeze > requirements.txt 生成所有依赖 4.p…...
OpenIPC开源FPV之Adaptive-Link信号干扰
OpenIPC开源FPV之Adaptive-Link信号干扰 1. 源由2. 现象3. 分析3.1 冲突弃包3.2 传输丢包 4. 逻辑4.1 可调整参数4.2 可监测参数4.3 逻辑思路 5. 总结6. 参考资料 1. 源由 虽然,OpenIPC作为FPV图传在延时方面使用广播wfb-ng,性能上已经非常棒了。 在权…...
C++ 结构体与函数
一.结构体 1.概念: 结构体(struct)是一种用户自定义复合数据类型,其中可以包含不同类型的不同成员 2.结构体的应用场景: 我们在使用多个变量描述一个对象时,虽然也可以做到,但是难免显得杂乱…...
【Java全栈】Java + Vue 项目框架与运行流程详解
文章目录 ⭐前言⭐一、框架介绍🌟1、后端框架(Java Spring Boot)🌟2、前端框架(Vue 3 Element Plus) ⭐二、项目结构🌟1、后端目录(Spring Boot)🌟2、前端目…...
JAVA:利用 JSONPath 操作JSON数据的技术指南
1、简述 JSONPath 是一种强大的工具,用于查询和操作 JSON 数据。类似于 SQL 的语法,它为处理复杂的 JSON 数据结构提供了简单且高效的解决方案。✨ 代码样例:https://gitee.com/lhdxhl/springboot-example.git 本文将介绍 JSONPath 的基本…...
5.2.1 WPF 通过ItemControl自己做柱状图
1. 最终效果如下图: 1.1 准备数据 ViewModel public class PrimaryItemModel{public double Value { get; set; }public string XLabel { get; set; }}public class MainViewModel{public ObservableCollection<PrimaryItemModel> PrimaryList { get; set; }…...
3.31 代码随想录第三十一天打卡
1049.最后一块石头的重量II (1)题目描述: (2)解题思路: class Solution { public:int lastStoneWeightII(vector<int>& stones) {vector<int> dp(15001, 0);int sum 0;for (int i 0; i < stones.size(); i) sum stones[i];int target sum / 2;for (in…...
基于网启PXE服务器的批量定制系统平台
一.项目背景 公司新购了一批服务器和台式机,需要为台式机和服务器安装系统,一部分需要安装国产OpenEuler,一部分要求安装CentOS 7.9,同时也要满足定制化需求,即按要求分区安装相应软件。 二.项目环境 安装win10/11 …...
Unity光线传播体积(LPV)技术实现详解
一、LPV技术概述 光线传播体积(Light Propagation Volumes)是一种实时全局光照技术,通过将场景中的间接光信息存储在3D网格中,实现动态物体的间接光照效果。 核心优势: 实时性能:相比传统光照贴图,支持动态场景 硬件…...