F103没有HAL_RTCEx_SetWakeUpTimer_IT()怎么办

芯片进入stop模式后,F103系列不具备RTC唤醒功能,而L0系列具备。使用L0芯片实现周期唤醒,可以直接调用库函数HAL_RTCEx_SetWakeUpTimer_IT()来实现,但F103没有这个函数。

RTC闹钟唤醒stop模式

查芯片手册可知,在这种情况下,只能通过外部中断唤醒。但幸运的是,F103的RTC闹钟中断由EXTI17管理,因此,可以通过RTC闹钟触发外部中断,实现内部唤醒(本质上还是EXTI17外部中断唤醒)。

RTC闹钟原理

根据STM32中文参考手册:

  1. STM32 原本处于待机状态,可由闹钟事件或 WKUP 事件 (外部唤醒事件,属于 EXTI 模块,不属于 RTC) 使它退出待机模式。闹钟事件是在 * 计数器 RTC_CNT* 的值等于闹钟寄存器 RTC_ALR 的值时触发的
  2. 在备份域中所有寄存器都是 16 位的,RTC 控制相关的寄存器也不例外。它的计数器RTC_CNT 的32 位由 RTC_CNTL RTC_CNTH 两个寄存器组成,分别保存定时计数值的低 16 位和高 16 位。
  3. 在配置 RTC 模块的时钟时,通常把输入的 32768Hz RTCCLK 进行 32768 分频得到实际驱动计 数器的时钟 TR_CLK = RTCCLK/32768= 1 Hz,计时周期为 1 秒,计时器在TR_CLK 的驱动下计 数,即每秒计数器 RTC_CNT 的值加 1。
因此,原理就很清楚了,当RTC_CNT闹钟寄存器 RTC_ALR 相等时,触发闹钟中断。RTC_ALR我们会提前设置,所以我们控制RTC_CNT的累加速度,就可以控制闹钟中断触发的快慢了。例如,TR_CLK =2Hz时,计时周期为 500ms,即RTC_CNT的值每500ms加 1。所以我们实现了500ms后触发闹钟中断。

cubeMX配置

最后自己生成工程文件就行了。

代码示例

//函数入口
int main(void)
{
  HAL_Init();
  SystemClock_Config();

  MX_GPIO_Init();
  MX_RTC_Init();

  while (1)
  {
    HAL_GPIO_TogglePin(GPIOA, GPIO_PIN_8);//指示灯闪烁
    Enter_StopMode();
  }
}

//BCD转二进制
uint8_t BYTE_2_BCD(uint8_t BYTE)
{
  uint8_t temp = 0;
  temp = (BYTE/10)<<4;
  temp |= BYTE%10;
  return temp;
}
//二进制转BCD
uint8_t BCD_2_BYTE(uint8_t BCD)
{
  uint8_t temp = 0;
  temp = (BCD>>4)*10;
  temp += BCD&0XFU;
  return temp;
}

void Enter_StopMode(void)
{
  //获取当前时间
  RTC_TimeTypeDef RTime = {0};
  HAL_RTC_GetTime(&hrtc,&RTime,RTC_FORMAT_BCD);
  uint8_t h = RTime.Hours;
  uint8_t m = RTime.Minutes;
  uint8_t s = RTime.Seconds;

  //设置唤醒时间(下一秒)
  h = BCD_2_BYTE(h);
  m = BCD_2_BYTE(m);
  s = BCD_2_BYTE(s)+1;
  if(s>59){ s = 0;  m += 1;}
  if(m>59){ m = 0;  h += 1;}
  if(h>23)  h = 0;
  
  //设置闹钟
  RTC_AlarmTypeDef sAlarm;
  sAlarm.Alarm = RTC_ALARM_A;
  sAlarm.AlarmTime.Hours   = BYTE_2_BCD(h);
  sAlarm.AlarmTime.Minutes = BYTE_2_BCD(m);
  sAlarm.AlarmTime.Seconds = BYTE_2_BCD(s);
  HAL_RTC_SetAlarm_IT(&hrtc,&sAlarm,RTC_FORMAT_BCD);
  
  __HAL_PWR_CLEAR_FLAG(PWR_FLAG_WU);
  HAL_PWR_EnterSTOPMode(PWR_LOWPOWERREGULATOR_ON,PWR_STOPENTRY_WFI);
  SystemClock_Config();//唤醒后重新初始化系统时钟
}
//RTC初始化
void MX_RTC_Init(void)
{
  RTC_TimeTypeDef sTime = {0};
  RTC_DateTypeDef DateToUpdate = {0};

  //RTC初始化
  hrtc.Instance = RTC;
  hrtc.Init.AsynchPrediv = 8192;// 将32768分频,得到4Hz的计数频率,即每250ms计数器RTC_CNT累加1
  hrtc.Init.OutPut = RTC_OUTPUTSOURCE_ALARM;
  if (HAL_RTC_Init(&hrtc) != HAL_OK)
  {
    Error_Handler();
  }

  //设置时分秒
  sTime.Hours = 0x23;
  sTime.Minutes = 0x57;
  sTime.Seconds = 0x0;
  if (HAL_RTC_SetTime(&hrtc, &sTime, RTC_FORMAT_BCD) != HAL_OK)
  {
    Error_Handler();
  }

  //设置年月日周
  DateToUpdate.WeekDay = RTC_WEEKDAY_TUESDAY;
  DateToUpdate.Month = RTC_MONTH_SEPTEMBER;
  DateToUpdate.Date = 0x16;
  DateToUpdate.Year = 0x25;
  if (HAL_RTC_SetDate(&hrtc, &DateToUpdate, RTC_FORMAT_BCD) != HAL_OK)
  {
    Error_Handler();
  }
}

void HAL_RTC_MspInit(RTC_HandleTypeDef* rtcHandle)
{
  if(rtcHandle->Instance==RTC)
  {
    //HAL_PWR_EnableBkUpAccess();//访问备份寄存器时,再解除屏蔽
    //__HAL_RCC_BKP_CLK_ENABLE();

    __HAL_RCC_RTC_ENABLE();

    HAL_NVIC_SetPriority(RTC_Alarm_IRQn, 0, 0);//设置闹钟中断优先级
    HAL_NVIC_EnableIRQ(RTC_Alarm_IRQn);        //使能中断
  }
}

void HAL_RTC_MspDeInit(RTC_HandleTypeDef* rtcHandle)
{
  if(rtcHandle->Instance==RTC)
  {
    __HAL_RCC_RTC_DISABLE();
    HAL_NVIC_DisableIRQ(RTC_Alarm_IRQn);
  }
}
//编写中断函数
void RTC_Alarm_IRQHandler(void)
{
  HAL_RTC_AlarmIRQHandler(&hrtc);
}

void HAL_RTC_AlarmAEventCallback(RTC_HandleTypeDef *hrtc)
{
    HAL_GPIO_TogglePin(GPIOC, GPIO_PIN_10);//闪灯提示触发了闹钟中断
}

疑惑

通过以上内容可知,RTC_CNT == RTC_ALR 时,触发闹钟中断。

HAL_RTC_SetAlarm_IT()设置RTC_ALR

RTC时钟源32768Hz,分频系数16384,分频后得到2Hz,即每秒计数2次(RTC_CNT每500ms加1)。

在理论上,如果将唤醒时间始终设置为当前时间的下一秒,即RTC_ALR RTC_CNT +1。而RTC_CNT却每500ms加1,所以实际上,只隔了500ms就触发了闹钟中断,最终实现了500ms的中断周期。

以上是理论逻辑,但在实际操作时,需要将分频系数设为8192(分频后得到4Hz,每秒计数4次),才能得到500ms的中断周期。

也就是说,设置250ms的周期,得到的却是500ms周期;设置500ms周期,得到的却是1000ms周期。

这让我难以理解,希望各位高手略微出手,帮我解答一下疑惑。

总结

F103可以通过RTC闹钟实现周期唤醒stop模式。

Logo

智能硬件社区聚焦AI智能硬件技术生态,汇聚嵌入式AI、物联网硬件开发者,打造交流分享平台,同步全国赛事资讯、开展 OPC 核心人才招募,助力技术落地与开发者成长。

更多推荐