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和成员

cpp 复制代码
osThreadId_t LvHandlerTaskHandle;
const osThreadAttr_t LvHandlerTask_attributes = {
  .name = "LvHandlerTask",
  .stack_size = 128 * 24,  // 3KB栈空间
  .priority = osPriorityLow,
};

我们联系一下 Linux 的线程:

首先是定义一个线程 ID

cpp 复制代码
//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:优先级

这里要提到优先级,其枚举类型为:

cpp 复制代码
typedef enum {
  osPriorityIdle          = -3,         ///< Priority: idle (lowest)
  osPriorityLow           = -2,         ///< Priority: low
  osPriorityBelowNormal   = -1,         ///< Priority: below normal
  osPriorityNormal        =  0,         ///< Priority: normal (default)
  osPriorityAboveNormal   = +1,         ///< Priority: above normal
  osPriorityHigh          = +2,         ///< Priority: high
  osPriorityRealtime      = +3,         ///< Priority: realtime (highest)
  osPriorityError         = 0x84,       ///< System cannot determine priority or illegal priority.
  osPriorityReserved      = 0x7FFFFFFF  ///< Prevents enum down-size compiler optimization.
} osPriority;

创建线程

cpp 复制代码
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

cpp 复制代码
//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;

和上面一样,也是创建队列:

cpp 复制代码
  /* 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. 定时器

cpp 复制代码
  /* 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

cpp 复制代码
void TaskTickHook(void)
{
	//to increase the LVGL tick
	lv_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内部动画和事件。

计时器逻辑:更新计时器时间,并在用户操作时通过消息队列打断空闲状态。

心跳更新

cpp 复制代码
void lv_tick_inc(uint32_t tick_period)

tick_period -- 此函数的调用周期(以毫秒为单位)

传送信息给消息队列

cpp 复制代码
 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. |

阻塞函数 osMessageQueuePutmsg_ptr 指向的消息放入参数 mq_id 指定的消息队列中。参数 msg_prio 用于在插入时根据消息的优先级(数字越高表示优先级越高)对消息进行排序。

参数 timeout 指定系统等待将消息放入队列的时间。在系统等待时,调用此函数的线程将进入阻塞状态。参数 timeout 可以具有以下值:

  • timeout 为 0 时,函数立即返回(即 try 语义)。
  • timeout 设置为 osWaitForever 时,该函数将等待无限时间,直到消息被传递(即等待语义)。
  • 所有其他值在 kernel ticks 中指定超时时间(即定时等待语义)。

5. LvHandlerTask (LVGL处理任务)

空闲检测:通过lv_disp_get_inactive_time获取用户无操作时间。

实时性:osDelay(1)确保界面流畅响应。

cpp 复制代码
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延迟
  }
}
cpp 复制代码
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 按键任务

这部分很简单,按下就将向消息队列发送数据。

cpp 复制代码
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 实现了导航栈:

cpp 复制代码
#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);
	}
}

回到更新屏幕的代码中:

cpp 复制代码
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 pressed
			if(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);
	}
}

获取消息队列的按键值

cpp 复制代码
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:*调用线程安全等级低于指定消息队列的安全等级。

屏幕加载函数

cpp 复制代码
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 主页时间更新

cpp 复制代码
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 screen
			RTC_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)
		{
			/*
			//bat
			uint8_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 messure
				float humi,temp;
				AHT_Read(&humi,&temp);
				//check
				if(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

cpp 复制代码
/**
  * @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,比如挂起和恢复任务调度:

cpp 复制代码
vTaskSuspendAll()
xTaskResumeAll()

对应CMSIS 应该为:

cpp 复制代码
osKernelSuspend()
osKernelResume()

六、MessageSendTask 蓝牙

cpp 复制代码
// 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

cpp 复制代码
/**
  * @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();        // 复位LCD
            LCD_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);
            
            /***** 唤醒后操作 *****/
            // 重新启用SysTick
            SET_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();                      // 初始化LCD
            LCD_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);  // 触发深度睡眠
    }
}
相关推荐
十月的皮皮12 小时前
stm20260720-从新手 C 到量产 STM32 工程:程序设计推导指南
c语言·开发语言·stm32·stm32cubemx·hal库
开发笔记-阿牛12 小时前
解析一款有意思的产品||CK6159A语音芯片设计的儿童专注力训练机-舒尔特玩具
stm32·单片机·嵌入式硬件·音频
搞全栈小苏15 小时前
SIMetrix_simplis8.4安装教程——附安装包
stm32·单片机·嵌入式硬件·电力电子
qq_5739678215 小时前
STM32控制器
stm32·单片机·嵌入式硬件
某林21216 小时前
履带小车底盘stm32F103RCT6开发
人工智能·stm32·单片机·嵌入式硬件·架构·人机交互·ros2
做萤石二次开发的哈哈18 小时前
萤石 ERTC Windows SDK 包含哪些类型定义?
stm32·单片机·嵌入式硬件
亿道电子Emdoor18 小时前
【Aitium】PCB导出DXF文件出现eDuplicateRecordName报错
stm32·单片机·嵌入式硬件·altium
巴巴媛6661 天前
STM32学习笔记【38.DHT11实验】
笔记·stm32·学习
深圳市泰凌微电子1 天前
HK航顺 HK32F407RGT7替STM32F405RGT6 微控制器32位MCU
stm32·单片机·嵌入式硬件
小心亦新2 天前
STM32学习16--定时器输出比较2直流电机
stm32·嵌入式硬件·学习