以下都是我个人的思考若有什么地方理解有误欢迎大家在评论区指正和提问。
手表的低功耗需要配合RTC的wakeup中断以及EXTI外部中断完成;主要的思路是:当stopenter任务收到stop队列中的数据后开始执行:将空闲时间计数清零、关闭耗电的外设、挂起所有的任务、看门狗失能(防止程序复位)、禁用systick 的中断、最后通过PWR_ENTERSTOP函数使芯片进入低功耗(STOP)模式,其中参数选择PWR_MAINREGULATOR_ON和PWR_STOPENTRY_WFI 保持主稳压器开启和中断唤醒,原因是我们需要RTC定时唤醒;低功耗(STOP)模式期间外设时钟和内部高速时钟关闭以降低功耗(systick中断前面已经由我们手动禁用)、而LSE外部低速时钟保持开启(为RTC提供时钟,可触发中断)、EXTI中断依旧、电路图中电池的bat通过二极管和电阻接入芯片的VBAT,备份域(Backup SRAM)启用。
按照RTCWakeup的配置,2000的重装载值和16分频可计算得差不多1秒钟产生一次唤醒中断;进入低功耗模式后每秒执行中断唤醒一次,芯片唤醒后从PWR_EnterSTOPMode后开始执行:重新启用systic的中断、时钟,并喂一次狗防止看门狗复位、随后把所有的任务恢复,通过(按键是否按下和是否处于抬腕状态)判断是否需要将所有外设唤醒,若判断到按键按下或抬腕则重新打开关闭的外设并往HomeUpdata_MessageQueue中放入数据,这样在传感器更新任务当中接收到数据后开始读取传感器的值并进行数据更新,反之则程序重新回到sleep继续执行进入STOP模式的操作。
【当然在芯片进入低功耗模式期间,用户按下按键出发了外部中断也能够将芯片唤醒,唤醒后判断到了按键按下,程序不会重新回到睡眠模式,并打开外设....】
在芯片的数据手册当中有明确写到:可以通过任意的EXTI线对芯片进行唤醒。
当按键2按下且处于主界面时、空闲时间计数(软件计时器)超过UI_TTtime后都会发送stop的消息;
if(HAL_RTCEx_SetWakeUpTimer_IT(&hrtc, 2000, RTC_WAKEUPCLOCK_RTCCLK_DIV16) != HAL_OK)
{
Error_Handler();
}
//初始化时启用RTC的wakeup中断
if(osMessageQueueGet(Stop_MessageQueue,&Stopstr,NULL,0)==osOK)
{
/*************************** your operations before sleep***************************/
sleep:
IdleTimerCount = 0;
//sensors
//usart
HAL_UART_MspDeInit(&huart1);
//lcd
LCD_RES_Clr();
LCD_Close_Light();
//touch
CST816_Sleep();
/***********************************************************************************/
/****************************** enter wakeup operations *****************************/
vTaskSuspendAll();
//Disnable Watch Dog
WDOG_Disnable();
//systick int
CLEAR_BIT(SysTick->CTRL, SysTick_CTRL_TICKINT_Msk);
//enter stop mode
HAL_PWR_EnterSTOPMode(PWR_MAINREGULATOR_ON,PWR_STOPENTRY_WFI);
//here is the sleep period
/***********************************************************************************/
/****************************** quit wakeup operations *****************************/
//resume run mode and reset the sysclk
SET_BIT(SysTick->CTRL, SysTick_CTRL_TICKINT_Msk);
HAL_SYSTICK_Config(SystemCoreClock / (1000U / uwTickFreq));
SystemClock_Config();
WDOG_Feed();
xTaskResumeAll();
/***********************************************************************************/
/****************************** your wakeup operations *****************************/
//MPU Check
if(HWInterface.IMU.wrist_is_enabled)
{
uint8_t hor;
hor = MPU_isHorizontal();
if(hor && HWInterface.IMU.wrist_state == WRIST_DOWN)
{
HWInterface.IMU.wrist_state = WRIST_UP;
Wrist_Flag = 1;
//resume, go on
}
else if(!hor && HWInterface.IMU.wrist_state == WRIST_UP)
{
HWInterface.IMU.wrist_state = WRIST_DOWN;
IdleTimerCount = 0;
goto sleep;
}
}
//
if(!KEY1 || KEY2 || HardInt_Charg_flag || Wrist_Flag)
{
Wrist_Flag = 0;
//resume, go on
}
else
{
IdleTimerCount = 0;
goto sleep;
}
//usart
HAL_UART_MspInit(&huart1);
//lcd
LCD_Init();
LCD_Set_Light(ui_LightSliderValue);
//touch
CST816_Wakeup();
//check if is Charging
if(ChargeCheck())
{HardInt_Charg_flag = 1;}
//send the Home Updata message
osMessageQueuePut(HomeUpdata_MessageQueue, &HomeUpdataStr, 0, 1);
/**************************************************************************************/
}
osDelay(100);
}
}
这里是软件定时器(空闲时间计数)用于计算用户未操作的时间;
IdleTimerHandle = osTimerNew(IdleTimerCallback, osTimerPeriodic, NULL, NULL);
osTimerStart(IdleTimerHandle,100);//100ms
void IdleTimerCallback(void *argument)
{
IdleTimerCount+=1;
//make sure the LightOffTime<TurnOffTime
if(IdleTimerCount == (ui_LTimeValue*10))
{
uint8_t Idlestr=0;
//send the Light off message
osMessageQueuePut(Idle_MessageQueue, &Idlestr, 0, 1);
//计数值达到LTtime将0放入Idle消息队列,IdleEnterTask接收到消息后进入空闲状态
}
if(IdleTimerCount == (ui_TTimeValue*10))
{
uint8_t Stopstr = 1;
IdleTimerCount = 0;
//send the Stop message
osMessageQueuePut(Stop_MessageQueue, &Stopstr, 0, 1);
//计数值达到TTtime将1放入stop消息队列,StopEnterTask接收到消息后进入stop状态
}
}
若用户有触摸屏幕和按下按键等操作或进入了环境/心率检测窗口就会往IdleBreak写入消息,这样IdleEnterTask任务接收到IdleBreak消息后会重置空闲时间的计数。
void IdleEnterTask(void *argument)
{
uint8_t Idlestr=0;
uint8_t IdleBreakstr=0;
while(1)
{
//light get dark
if(osMessageQueueGet(Idle_MessageQueue,&Idlestr,NULL,1)==osOK)
{
LCD_Set_Light(5);//空闲状态降低背光亮度
}
if(osMessageQueueGet(IdleBreak_MessageQueue,&IdleBreakstr,NULL,1)==osOK)
{
IdleTimerCount = 0;//清零空闲时间计数
LCD_Set_Light(ui_LightSliderValue);
}
osDelay(10);
}
}
DataSaveTask数据保存任务每100ms检查一次消息队列,在SensorDataUpdateTask传感器更新任务中会往DataSave_MessageQueue中放数据,当DataSaveTask数据保存任务读取到DataSave_MessageQueue的数据后会执行:往eeprom中写入时间日期和步数的数据。
void DataSaveTask(void *argument)
{
while(1)
{
uint8_t Datastr=0;
if(osMessageQueueGet(DataSave_MessageQueue,&Datastr,NULL,1)==osOK)
{
/****************
Setting change
date change
Step change
****************/
uint8_t dat[3];
dat[0] = HWInterface.IMU.wrist_is_enabled;
dat[1] = ui_APPSy_EN;
SettingSave(dat,0x10,2);
RTC_DateTypeDef nowdate;
HAL_RTC_GetDate(&hrtc,&nowdate,RTC_FORMAT_BIN);
SettingGet(dat,0x20,3);
if(dat[0] != nowdate.Date)
{
if(!HWInterface.IMU.ConnectionError)
HWInterface.IMU.SetSteps(0);
dat[0] = nowdate.Date;
dat[2] = 0;
dat[1] = 0;
SettingSave(dat,0x20,3);
}
else
{
uint16_t temp = HWInterface.IMU.GetSteps();
dat[0] = nowdate.Date;
dat[2] = temp & 0xff;
dat[1] = temp>>8 & 0xff;
SettingSave(dat,0x20,3);
}
}
osDelay(100);
}
}
任务中先读取eeprom当中的数据,检查日期有没有变化,如果有变化则重新写入日期数据并将步数的数据重置,若没变化就只改变步数的数据。
程序当中的消息队列大部分都是只有一个消息的队列(只能为空或满),而且一般都用于判断是否接收到消息(我们都知道接收了消息后消息会被从队列中删除),其实可以直接使用二值信号量进行替换(效果是一样的)。