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);  // 触发深度睡眠
    }
}
相关推荐
可待电子单片机设计定制(论文)18 分钟前
【STM32设计】数控直流稳压电源的设计与实现(实物+资料+论文)
stm32·嵌入式硬件·mongodb
小麦嵌入式1 小时前
Linux驱动开发实战(十一):GPIO子系统深度解析与RGB LED驱动实践
linux·c语言·驱动开发·stm32·嵌入式硬件·物联网·ubuntu
触角010100013 小时前
STM32F103低功耗模式深度解析:从理论到应用实践(上) | 零基础入门STM32第九十二步
驱动开发·stm32·单片机·嵌入式硬件·物联网
昊虹AI笔记3 小时前
使用STM32CubeMX和Keil在STM32上创建并运行一个简单的FreeRTOS多任务程序
stm32·单片机·嵌入式硬件
阿超爱嵌入式5 小时前
STM32学习笔记之RCC模块(实操篇)
笔记·stm32·学习
charlie1145141916 小时前
STM32F103C8T6单片机硬核原理篇:讨论GPIO的基本原理篇章1——只讨论我们的GPIO简单输入和输出
c语言·stm32·单片机·嵌入式硬件·gpio·数据手册
阿让啊6 小时前
bootloader+APP中,有些APP引脚无法正常使用?
c语言·开发语言·stm32·单片机·嵌入式硬件
Lhj061617 小时前
stm32 can 遥控帧的问题
stm32·单片机·嵌入式硬件
昊虹AI笔记19 小时前
STM32_HAL之程序编写、编译、烧写、上板测试初体验
stm32
CHHC188019 小时前
STM32 + keil5 跑马灯
stm32·单片机·嵌入式硬件