cancel
Showing results for 
Search instead for 
Did you mean: 

Why STM32WL Alarm always wakeup advance 1 second

SimonLaw
Associate

I use STM32WL RTC Alarm as low power mode timer for achieve ms level accrancy and any duration sleep. System will convert sleep time to Alarm time before enter low power mode. I have tested different  sleep time, verified convertion result is correct but system will always wakeup by Alarm advance 1 second than Alarm time. below is my RTC init and Alarm Setting Code:

```c

void MX_RTC_Init(void)
{

  /* USER CODE BEGIN RTC_Init 0 */

  /* USER CODE END RTC_Init 0 */

  RTC_TimeTypeDef sTime = {0};
  RTC_DateTypeDef sDate = {0};
  RTC_AlarmTypeDef sAlarm = {0};

  /* USER CODE BEGIN RTC_Init 1 */

  /* USER CODE END RTC_Init 1 */

  /** Initialize RTC Only
  */
  hrtc.Instance = RTC;
  hrtc.Init.HourFormat = RTC_HOURFORMAT_24;
  hrtc.Init.AsynchPrediv = RTC_PREDIV_A;
  hrtc.Init.SynchPrediv = RTC_PREDIV_S;
  hrtc.Init.OutPut = RTC_OUTPUT_DISABLE;
  hrtc.Init.OutPutRemap = RTC_OUTPUT_REMAP_NONE;
  hrtc.Init.OutPutPolarity = RTC_OUTPUT_POLARITY_HIGH;
  hrtc.Init.OutPutType = RTC_OUTPUT_TYPE_OPENDRAIN;
  hrtc.Init.OutPutPullUp = RTC_OUTPUT_PULLUP_NONE;
  hrtc.Init.BinMode = RTC_BINARY_MIX;
  hrtc.Init.BinMixBcdU = RTC_BINARY_MIX_BCDU_2;
  if (HAL_RTC_Init(&hrtc) != HAL_OK)
  {
    Error_Handler();
  }

  /* USER CODE BEGIN Check_RTC_BKUP */

  /* USER CODE END Check_RTC_BKUP */

  /** Initialize RTC and set the Time and Date
  */
  sTime.Hours = 0x0;
  sTime.Minutes = 0x0;
  sTime.Seconds = 0x0;
  sTime.DayLightSaving = RTC_DAYLIGHTSAVING_NONE;
  sTime.StoreOperation = RTC_STOREOPERATION_RESET;
  if (HAL_RTC_SetTime(&hrtc, &sTime, RTC_FORMAT_BCD) != HAL_OK)
  {
    Error_Handler();
  }
  sDate.WeekDay = RTC_WEEKDAY_SATURDAY;
  sDate.Month = RTC_MONTH_JANUARY;
  sDate.Date = 0x1;
  sDate.Year = 0x0;

  if (HAL_RTC_SetDate(&hrtc, &sDate, RTC_FORMAT_BCD) != HAL_OK)
  {
    Error_Handler();
  }
  if (HAL_RTCEx_SetSSRU_IT(&hrtc) != HAL_OK)
  {
    Error_Handler();
  }

  /** Enable the Alarm A
  */
  sAlarm.AlarmTime.Hours = 0x0;
  sAlarm.AlarmTime.Minutes = 0x0;
  sAlarm.AlarmTime.Seconds = 0x0;
  sAlarm.AlarmTime.SubSeconds = 0x0;
  sAlarm.AlarmTime.DayLightSaving = RTC_DAYLIGHTSAVING_NONE;
  sAlarm.AlarmTime.StoreOperation = RTC_STOREOPERATION_RESET;
  sAlarm.AlarmMask = RTC_ALARMMASK_NONE;
  sAlarm.AlarmSubSecondMask = RTC_ALARMSUBSECONDMASK_SS14_10;
  sAlarm.AlarmDateWeekDaySel = RTC_ALARMDATEWEEKDAYSEL_DATE;
  sAlarm.AlarmDateWeekDay = 0x1;
  sAlarm.Alarm = RTC_ALARM_A;
  if (HAL_RTC_SetAlarm_IT(&hrtc, &sAlarm, RTC_FORMAT_BCD) != HAL_OK)
  {
    Error_Handler();
  }
  /* USER CODE BEGIN RTC_Init 2 */
  lptimer_bkup_write_msbticks(RTC_TIMESTAMP_MSB_INIT_VAL);
  lptimer_bkup_write_second(RTC_INIT_SYSTIME_SECOND_COMPENSATION);
  /* USER CODE END RTC_Init 2 */

}
 
void lptimer_set_timeout(uint32_t timeout)
{

  RTC_TimeTypeDef rtcTime = {0};
  RTC_DateTypeDef rtcDate = {0};

  HAL_RTC_GetTime(&hrtc, &rtcTime, RTC_FORMAT_BIN);
  HAL_RTC_GetDate(&hrtc, &rtcDate, RTC_FORMAT_BIN);

//   LOG_I("RTC: %u %u:%u:%u %u", rtcDate.Date, rtcTime.Hours, rtcTime.Minutes, rtcTime.Seconds, rtcTime.SubSeconds);
  subsecond_add(&rtcTime, &rtcDate, timeout);
  /* USER CODE END TIMER_IF_StartTimer */
  RTC_AlarmTypeDef sAlarm = {0};
  /*Stop timer if one is already started*/
  lptimer_stop();
  /* starts timer*/
  sAlarm.BinaryAutoClr = RTC_ALARMSUBSECONDBIN_AUTOCLR_NO;
  sAlarm.AlarmMask = RTC_ALARMMASK_NONE;
  sAlarm.AlarmSubSecondMask = RTC_ALARMSUBSECONDMASK_SS14_10;
  sAlarm.AlarmTime = rtcTime;
  sAlarm.AlarmDateWeekDay = rtcDate.Date;
  sAlarm.AlarmDateWeekDaySel = RTC_ALARMDATEWEEKDAYSEL_DATE;
  sAlarm.Alarm = RTC_ALARM_A;
  if (HAL_RTC_SetAlarm_IT(&hrtc, &sAlarm, RTC_FORMAT_BIN) != HAL_OK)
  {
    Error_Handler();
  }
//   LOG_I("set next timeout: %u", timeout);
//   LOG_I("ALARM: %u %u:%u:%u %u", rtcDate.Date, rtcTime.Hours, rtcTime.Minutes, rtcTime.Seconds, rtcTime.SubSeconds);
}

```

Below is gpio reflect sleep time monitored by logic analyzer:

st_post_1.png

 

2 REPLIES 2
VictorD
ST Employee

Hello Simon,

Could you please provide some more information about the RTC configuration, including:

  • RTC input clock (you can retrieve this info on CubeMX or in the function HAL_RTC_MspInit() in the stm32wlxx_hal_msp.c file)
  • RTC_PREDIV_A value (the default value of RTC_PREDIV_A corresponds to 127. If the current clock input is LSE, which is the recommended input clock, it means ck_apre = 32768/128 = 256 Hz. With your current setting of RTC_BINARY_MIX_BCDU_2, the "1 second increment" is done every 4 seconds).

Regarding your configuration for sAlarm.AlarmSubSecondMask, since you are using the Binary_Mix mode, you should use RTC_ALARMSUBSECONDBINMASK_SS31_10 from the group RTCEx_Alarm_Sub_Seconds_binary_Masks_Definitions.

Also, could you please provide the code for the function subsecond_add()?

Best regards,

Victor

Hi, Victor,

I really appreciated the information you gave me. the more relative code may address the question:

rtc init in ./cubemx/Src/rtc.c

RTC_HandleTypeDef hrtc;

/* RTC init function */
void MX_RTC_Init(void)
{

  /* USER CODE BEGIN RTC_Init 0 */

  /* USER CODE END RTC_Init 0 */

  RTC_TimeTypeDef sTime = {0};
  RTC_DateTypeDef sDate = {0};
  RTC_AlarmTypeDef sAlarm = {0};

  /* USER CODE BEGIN RTC_Init 1 */

  /* USER CODE END RTC_Init 1 */

  /** Initialize RTC Only
  */
  hrtc.Instance = RTC;
  hrtc.Init.HourFormat = RTC_HOURFORMAT_24;
  hrtc.Init.AsynchPrediv = RTC_PREDIV_A;
  hrtc.Init.SynchPrediv = RTC_PREDIV_S;
  hrtc.Init.OutPut = RTC_OUTPUT_DISABLE;
  hrtc.Init.OutPutRemap = RTC_OUTPUT_REMAP_NONE;
  hrtc.Init.OutPutPolarity = RTC_OUTPUT_POLARITY_HIGH;
  hrtc.Init.OutPutType = RTC_OUTPUT_TYPE_OPENDRAIN;
  hrtc.Init.OutPutPullUp = RTC_OUTPUT_PULLUP_NONE;
  hrtc.Init.BinMode = RTC_BINARY_MIX;
  hrtc.Init.BinMixBcdU = RTC_BINARY_MIX_BCDU_2;
  if (HAL_RTC_Init(&hrtc) != HAL_OK)
  {
    Error_Handler();
  }

  /* USER CODE BEGIN Check_RTC_BKUP */

  /* USER CODE END Check_RTC_BKUP */

  /** Initialize RTC and set the Time and Date
  */
  sTime.Hours = 0x0;
  sTime.Minutes = 0x0;
  // when RTC work in mix mode, readout RTC value will less 1 second than correct value,
  // see RM0461 page 906: "SS can be larger than PREDIV_S only after a shift operation.
  // In that case, the correct time/date is one second less than as indicated by RTC_TR/RTC_DR."
  sTime.Seconds = 0x2;
  sTime.DayLightSaving = RTC_DAYLIGHTSAVING_NONE;
  sTime.StoreOperation = RTC_STOREOPERATION_RESET;
  if (HAL_RTC_SetTime(&hrtc, &sTime, RTC_FORMAT_BCD) != HAL_OK)
  {
    Error_Handler();
  }
  sDate.WeekDay = RTC_WEEKDAY_SATURDAY;
  sDate.Month = RTC_MONTH_JANUARY;
  sDate.Date = 0x1;
  sDate.Year = 0x0;

  if (HAL_RTC_SetDate(&hrtc, &sDate, RTC_FORMAT_BCD) != HAL_OK)
  {
    Error_Handler();
  }
  if (HAL_RTCEx_SetSSRU_IT(&hrtc) != HAL_OK)
  {
    Error_Handler();
  }

  /** Enable the Alarm A
  */
  sAlarm.AlarmTime.Hours = 0x0;
  sAlarm.AlarmTime.Minutes = 0x0;
  sAlarm.AlarmTime.Seconds = 0x0;
  sAlarm.AlarmTime.SubSeconds = 0x0;
  sAlarm.AlarmTime.DayLightSaving = RTC_DAYLIGHTSAVING_NONE;
  sAlarm.AlarmTime.StoreOperation = RTC_STOREOPERATION_RESET;
  sAlarm.AlarmMask = RTC_ALARMMASK_NONE;
  sAlarm.AlarmSubSecondMask = RTC_ALARMSUBSECONDMASK_SS14_10;
  sAlarm.AlarmDateWeekDaySel = RTC_ALARMDATEWEEKDAYSEL_DATE;
  sAlarm.AlarmDateWeekDay = 0x1;
  sAlarm.Alarm = RTC_ALARM_A;
  if (HAL_RTC_SetAlarm_IT(&hrtc, &sAlarm, RTC_FORMAT_BCD) != HAL_OK)
  {
    Error_Handler();
  }
  /* USER CODE BEGIN RTC_Init 2 */
//   lptimer_bkup_write_msbticks(RTC_TIMESTAMP_MSB_INIT_VAL);
//   lptimer_bkup_write_second(RTC_INIT_SYSTIME_SECOND_COMPENSATION);
  /* USER CODE END RTC_Init 2 */

}

void HAL_RTC_MspInit(RTC_HandleTypeDef* rtcHandle)
{

  RCC_PeriphCLKInitTypeDef PeriphClkInitStruct = {0};
  if(rtcHandle->Instance==RTC)
  {
  /* USER CODE BEGIN RTC_MspInit 0 */
    HAL_NVIC_ClearPendingIRQ(TAMP_STAMP_LSECSS_SSRU_IRQn);
    HAL_NVIC_ClearPendingIRQ(RTC_Alarm_IRQn);

  /* USER CODE END RTC_MspInit 0 */

  /** Initializes the peripherals clocks
  */
    PeriphClkInitStruct.PeriphClockSelection = RCC_PERIPHCLK_RTC;
    PeriphClkInitStruct.RTCClockSelection = RCC_RTCCLKSOURCE_LSE;

    if (HAL_RCCEx_PeriphCLKConfig(&PeriphClkInitStruct) != HAL_OK)
    {
      Error_Handler();
    }

    /* RTC clock enable */
    __HAL_RCC_RTC_ENABLE();
    __HAL_RCC_RTCAPB_CLK_ENABLE();

    /* RTC interrupt Init */
    HAL_NVIC_SetPriority(TAMP_STAMP_LSECSS_SSRU_IRQn, 0, 0);
    HAL_NVIC_EnableIRQ(TAMP_STAMP_LSECSS_SSRU_IRQn);
    HAL_NVIC_SetPriority(RTC_Alarm_IRQn, 0, 0);
    HAL_NVIC_EnableIRQ(RTC_Alarm_IRQn);
  /* USER CODE BEGIN RTC_MspInit 1 */
   rt_kprintf("rtc init\n");
  /* USER CODE END RTC_MspInit 1 */
  }
}

 

rtc clock prescale define in main.h

#define RTC_N_PREDIV_S 10
#define RTC_PREDIV_A ((1<<(15-RTC_N_PREDIV_S))-1)
#define RTC_PREDIV_S ((1<<RTC_N_PREDIV_S)-1

 

convert between epoch and rtc; alarm setting code in drv_lptimer.h

static const uint8_t days_in_month[] = {0,  31, 28, 31, 30, 31, 30,
                                        31, 31, 30, 31, 30, 31};

// NOTE: generated by chatgpt
static inline uint32_t is_leap_year(uint32_t year)
{
    year += 2000;
    return (year % 4 == 0 && year % 100 !=0) || year % 400 == 0;
}

static inline uint8_t month_days(uint32_t year, uint8_t month) {
    if (month == 2 && is_leap_year(year)) {
        return 29;
    } else {
        return days_in_month[month];
    }
}

// TODO: execute time optimize
static void calendar_add(RTC_DateTypeDef *rtc_date, uint32_t days)
{
    while (days > 0) {
        uint8_t month_days_count = month_days(rtc_date->Year, rtc_date->Month);
        if (days >= month_days_count - rtc_date->Date + 1U) {
            days -= (month_days_count - rtc_date->Date + 1U);
            rtc_date->Date = 1;
            if (rtc_date->Month == 12) {
                rtc_date->Year++;
                rtc_date->Month = 1;
            } else {
                rtc_date->Month++;
            }
        } else {
            rtc_date->Date += days;
            days = 0;
        }
    }
}

static uint32_t calendar_diff(RTC_DateTypeDef *after, RTC_DateTypeDef *prev)
{
    uint32_t prev_elapse_days = 0; // prev date elpase days in the year
    uint32_t after_elapse_days = 0; // after date elpase days from the previous year

    for (uint8_t i = 1; i < prev->Month; ++i) {
        prev_elapse_days += days_in_month[i];
    }
    if (prev->Month > 2 && is_leap_year(prev->Year)) {
        prev_elapse_days += 1;
    }
    prev_elapse_days += prev->Date;

    for (uint8_t i = 1; i < after->Month; ++i) {
        after_elapse_days += days_in_month[i];
    }
    if (after->Month > 2 && is_leap_year(after->Year)) {
        prev_elapse_days += 1;
    }
    after_elapse_days += after->Date;

    for (uint32_t year = prev->Year; year < after->Year; year++) {
        after_elapse_days += is_leap_year(year) ? 366 : 365;
    }
    return after_elapse_days - prev_elapse_days;
}

static void second_add(RTC_TimeTypeDef * rtc_time, RTC_DateTypeDef * rtc_date, uint32_t seconds)
{
    rtc_time->Seconds = rtc_time->Seconds + (seconds % 60U);
    if (rtc_time->Seconds > 59U) {
        rtc_time->Seconds = rtc_time->Seconds % 60;
        rtc_time->Minutes = rtc_time->Minutes + 1;
    }
    rtc_time->Minutes = rtc_time->Minutes + (seconds / 60U % 60U);
    if (rtc_time->Minutes > 59U) {
        rtc_time->Minutes = rtc_time->Minutes % 60;
        rtc_time->Hours = rtc_time->Hours + 1;
    }
    rtc_time->Hours = rtc_time->Hours + (seconds / 3600U % 24);
    if (rtc_time->Hours > 23U) {
        rtc_time->Hours = rtc_time->Hours % 24;
        calendar_add(rtc_date, seconds / 3600U / 24U + 1U);
    } else {
        calendar_add(rtc_date, seconds / 3600U / 24U);
    }
}

static uint32_t second_diff(RTC_DateTypeDef *after_date, RTC_TimeTypeDef *after_time,
                       RTC_DateTypeDef *prev_date, RTC_TimeTypeDef *prev_time)
{
    uint32_t day_diff = calendar_diff(after_date, prev_date);
    return day_diff * 3600 * 24 +
           ((int)(after_time->Hours) - (int)(prev_time->Hours)) * 3600 +
           ((int)(after_time->Minutes) - (int)(prev_time->Minutes)) * 60 +
           (int)(after_time->Seconds) - (int)(prev_time->Seconds);
}

static void subsecond_add(RTC_TimeTypeDef * rtc_time, RTC_DateTypeDef * rtc_date, uint32_t ticks)
{
    uint32_t alarm_subsec = ticks & RTC_PREDIV_S;
    /* if underflowed, carry to second */
    if (alarm_subsec > (rtc_time->SubSeconds & RTC_PREDIV_S)) {
        second_add(rtc_time, rtc_date, ticks / (1 << RTC_N_PREDIV_S) + 1);
    } else {
        second_add(rtc_time, rtc_date, ticks / (1 << RTC_N_PREDIV_S));
    }
    rtc_time->SubSeconds -= alarm_subsec;
}

void lptimer_timestamp2localtime(const uint32_t timestamp, struct tm *localtime)
{
    RTC_DateTypeDef rtc_date;
    RTC_TimeTypeDef rtc_time;

    rtc_date.Date = 1;
    rtc_date.Month = 1;
    rtc_date.Year = 0;

    rtc_time.Seconds = 0;
    rtc_time.Minutes = 0;
    rtc_time.Hours = 0;

    second_add(&rtc_time, &rtc_date, timestamp - SECOND_BETWEEN_1970_2000);
    localtime->tm_sec = rtc_time.Seconds;
    localtime->tm_min = rtc_time.Minutes;
    localtime->tm_hour = rtc_time.Hours;
    localtime->tm_mday = rtc_date.Date;
    localtime->tm_mon = rtc_date.Month;
    localtime->tm_year = rtc_date.Year + 100;
    localtime->tm_isdst = -1;
}

uint32_t lptimer_localtime2timestamp(struct tm *localtime)
{
    RTC_DateTypeDef prev_date;
    RTC_TimeTypeDef prev_time;
    RTC_DateTypeDef after_date;
    RTC_TimeTypeDef after_time;

    prev_date.Date = 1;
    prev_date.Month = 1;
    prev_date.Year = 0;
    prev_time.Seconds = 0;
    prev_time.Minutes = 0;
    prev_time.Hours = 0;

    after_time.Seconds = localtime->tm_sec;
    after_time.Minutes = localtime->tm_min;
    after_time.Hours = localtime->tm_hour;
    after_date.Date = localtime->tm_mday;
    after_date.Month = localtime->tm_mon;
    after_date.Year = localtime->tm_year - 100;
    return second_diff(&after_date, &after_time, &prev_date, &prev_time) + SECOND_BETWEEN_1970_2000;
}

void lptimer_set_timeout(uint32_t timeout)
{

  RTC_TimeTypeDef rtcTime = {0};
  RTC_DateTypeDef rtcDate = {0};

  HAL_RTC_GetTime(&hrtc, &rtcTime, RTC_FORMAT_BIN);
  HAL_RTC_GetDate(&hrtc, &rtcDate, RTC_FORMAT_BIN);

  LOG_I("RTC: %u %u:%u:%u %u", rtcDate.Date, rtcTime.Hours, rtcTime.Minutes, rtcTime.Seconds, rtcTime.SubSeconds);

  // when RTC work in mix mode, should need set RTC alarm add extra 1 second (equals RTC_PREDIV_S + 1
  // tick), see RM0461 page 906: "SS can be larger than PREDIV_S only after a shift operation.
  // In that case, the correct time/date is one second less than as indicated by RTC_TR/RTC_DR."
  subsecond_add(&rtcTime, &rtcDate, timeout + RTC_PREDIV_S + 1);
  /* USER CODE END TIMER_IF_StartTimer */
  RTC_AlarmTypeDef sAlarm = {0};
  /*Stop timer if one is already started*/
  lptimer_stop();
  /* starts timer*/
  sAlarm.BinaryAutoClr = RTC_ALARMSUBSECONDBIN_AUTOCLR_NO;
  sAlarm.AlarmMask = RTC_ALARMMASK_NONE;
  sAlarm.AlarmSubSecondMask = RTC_ALARMSUBSECONDMASK_SS14_10;
  sAlarm.AlarmTime = rtcTime;
  sAlarm.AlarmDateWeekDay = rtcDate.Date;
  sAlarm.AlarmDateWeekDaySel = RTC_ALARMDATEWEEKDAYSEL_DATE;
  sAlarm.Alarm = RTC_ALARM_A;
  if (HAL_RTC_SetAlarm_IT(&hrtc, &sAlarm, RTC_FORMAT_BIN) != HAL_OK)
  {
    Error_Handler();
  }
//   LOG_I("set next timeout: %u", timeout);
//   LOG_I("ALARM: %u %u:%u:%u %u", rtcDate.Date, rtcTime.Hours, rtcTime.Minutes, rtcTime.Seconds, rtcTime.SubSeconds);
}

void lptimer_stop(void)
{
  /* Clear RTC Alarm Flag */
  __HAL_RTC_ALARM_CLEAR_FLAG(&hrtc, RTC_FLAG_ALRAF);
  /* Disable the Alarm A interrupt */
  HAL_RTC_DeactivateAlarm(&hrtc, RTC_ALARM_A);
  /*overload RTC feature enable*/
  hrtc.IsEnabled.RtcFeatures = UINT32_MAX;
}