2024-10-04 02:38 AM - last edited on 2024-10-04 02:56 AM by SofLit
Hello,
I'm trying to use the RTC of my NUCLEO-L476RG including the sub-seconds feature.
I've activated the RTC clock source in the .ioc and configured LSE as clock source (32.768KHz).
I've set up an initial value to the RTC and I'm reading it out every 100ms:
void Set_RTC_Time(void) {
RTC_TimeTypeDef sTime = {0};
RTC_DateTypeDef sDate = {0};
sTime.Hours = 10;
sTime.Minutes = 30;
sTime.Seconds = 0;
sDate.WeekDay = RTC_WEEKDAY_THURSDAY;
sDate.Month = RTC_MONTH_OCTOBER;
sDate.Date = 3;
sDate.Year = 24;
HAL_RTC_SetTime(&hrtc, &sTime, RTC_FORMAT_BIN);
HAL_RTC_SetDate(&hrtc, &sDate, RTC_FORMAT_BIN);
}
// Function to get RTC time with sub-second precision
void Get_RTC_Time_With_Milliseconds(void) {
RTC_TimeTypeDef sTime = {0};
RTC_DateTypeDef sDate = {0};
uint32_t subsecond = 0;
float fraction = 0;
// Retrieve current RTC time and date
HAL_RTC_GetTime(&hrtc, &sTime, RTC_FORMAT_BIN);
HAL_RTC_GetDate(&hrtc, &sDate, RTC_FORMAT_BIN);
// Calculate the milliseconds from subseconds
subsecond = hrtc.Instance->SSR;
fraction = ((float)(hrtc.Init.SynchPrediv - subsecond) / (hrtc.Init.SynchPrediv + 1)) * 1000;
// Print the time to UART with millisecond precision
printf("Date: %02d-%02d-20%02d, Time: %02d:%02d:%02d.%03d\r\n",
sDate.Date, sDate.Month, sDate.Year,
sTime.Hours, sTime.Minutes, sTime.Seconds,
(int)fraction);
}
This is the output:
Date: 03-10-2024, Time: 10:30:00.000
Date: 03-10-2024, Time: 10:30:00.386
Date: 03-10-2024, Time: 10:30:00.488
Date: 03-10-2024, Time: 10:30:00.593
Date: 03-10-2024, Time: 10:30:00.695
Date: 03-10-2024, Time: 10:30:00.800
Date: 03-10-2024, Time: 10:30:00.906
Date: 03-10-2024, Time: 10:30:00.007
Then, with a button press I want to either reset the date, time and milliseconds to a certain value or just set the milliseconds to a certain value (the purpose is synchronization with GPS or with other boards). However, the milliseconds value is always reset to 0. Here is the code:
void Update_RTC_From_GPS(GPS_Time_t *gps_time) {
RTC_TimeTypeDef sTime = {0};
RTC_DateTypeDef sDate = {0};
uint32_t subsecond = 0;
// Set the GPS time values
sTime.Hours = gps_time->hours;
sTime.Minutes = gps_time->minutes;
sTime.Seconds = gps_time->seconds;
// Calculate the sub-seconds to achieve millisecond precision
// SubSeconds should match the fraction of the second
subsecond = (uint32_t)((((float)gps_time->milliseconds / 1000.0) * (hrtc.Init.SynchPrediv + 1)));
// Set the calculated sub-second value
sTime.SubSeconds = hrtc.Init.SynchPrediv - subsecond;
sDate.Date = gps_time->day;
sDate.Month = gps_time->month;
sDate.Year = gps_time->year;
// Update RTC with the new time and date values
if (HAL_RTC_SetTime(&hrtc, &sTime, RTC_FORMAT_BIN) != HAL_OK) {
printf("Failed to update RTC Time\r\n");
} else if (HAL_RTC_SetDate(&hrtc, &sDate, RTC_FORMAT_BIN) != HAL_OK) {
printf("Failed to update RTC Date\r\n");
} else {
printf("RTC Updated to Date: %02d-%02d-20%02d, Time: %02d:%02d:%02d.%03d\r\n",
gps_time->day, gps_time->month, gps_time->year,
gps_time->hours, gps_time->minutes, gps_time->seconds,
gps_time->milliseconds);
}
}
void Adjust_RTC_On_PPS(uint16_t milliseconds) {
RTC_TimeTypeDef sTime = {0};
// Retrieve the current time
HAL_RTC_GetTime(&hrtc, &sTime, RTC_FORMAT_BIN);
// Calculate sub-second ticks corresponding to the specified milliseconds
uint32_t subsecond_ticks = (uint32_t)(((float)milliseconds / 1000.0) * (hrtc.Init.SynchPrediv + 1));
// Set the new SubSeconds value based on the exact PPS timing
sTime.SubSeconds = hrtc.Init.SynchPrediv - subsecond_ticks;
// Re-set the RTC to match the exact second of the PPS
if (HAL_RTC_SetTime(&hrtc, &sTime, RTC_FORMAT_BIN) != HAL_OK) {
printf("Failed to adjust RTC on PPS\r\n");
} else {
printf("RTC adjusted to exact millisecond on PPS (%d)\r\n", milliseconds);
}
}
GPS_Time_t GPS_CurrentTime = {
.hours = 11,
.minutes = 12,
.seconds = 13,
.milliseconds = 456,
.day = 3,
.month = RTC_MONTH_OCTOBER,
.year = 24,
};
void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin) {
if (GPIO_Pin == B1_Pin) {
static int togglebutton = 0;
if (togglebutton == 0) {
printf("GPS\n");
Update_RTC_From_GPS(&GPS_CurrentTime);
} else {
printf("PPS\n");
Adjust_RTC_On_PPS(300);
}
togglebutton = !togglebutton;
}
}
This is the result:
Date: 03-10-2024, Time: 10:30:03.578
Date: 03-10-2024, Time: 10:30:03.683
Date: 03-10-2024, Time: 10:30:03.785
GPS
RTC Updated to Date: 03-16-2024, Time: 11:12:13.456
Date: 03-10-2024, Time: 11:12:13.039
Date: 03-10-2024, Time: 11:12:13.144
Date: 03-10-2024, Time: 11:12:13.250
Date: 03-10-2024, Time: 11:12:13.351
PPS
RTC adjusted to exact millisecond on PPS (300)
Date: 03-10-2024, Time: 11:12:14.023
Date: 03-10-2024, Time: 11:12:14.128
Date: 03-10-2024, Time: 11:12:14.230
Date: 03-10-2024, Time: 11:12:14.335
As you can see, the date and time change works, but the milliseconds resets to 0. I've also tried setting the sub-seconds with the shift register, but with no luck:
void Adjust_RTC_On_PPS(uint16_t milliseconds) {
uint32_t shiftValue;
// Calculate shift value for subseconds
shiftValue = ((hrtc.Init.SynchPrediv + 1) * milliseconds) / 1000;
// Set the SUBFS field in RTC_SHIFTR
hrtc.Instance->SHIFTR = (shiftValue << RTC_SHIFTR_SUBFS_Pos) | RTC_SHIFTR_ADD1S;
// Wait for the shift operation to complete
while ((hrtc.Instance->ISR & RTC_ISR_SHPF) != 0) {}
printf("RTC adjusted to exact millisecond on PPS (%03d ms)\r\n", milliseconds);
}
Can anyone help to resolve this issue?
Thanks in advance,
Bruno
2024-10-04 04:27 AM - edited 2024-10-04 04:28 AM
Subseconds register cannot be written directly, that's probably the reason why the HAL function does not write it (and clearing it is consequence of setting the RTC to INIT mode needed to set date/time).
Shift is the way to go. As RTC_SHIFTR is a write protected RTC register, and Cube/HAL probably keeps the RTC registers locked, you need to follow the "unlock" procedure as outlined in RTC.
There may be a Cube/HAL function to do this, I don't use Cube.
JW
2024-10-07 02:12 AM
Hi @waclawek.jan, thanks for pointing to the appropriate HAL function to perform the shift register update. I've taken a look and seen that the RTC register locking and unlocking is performed inside that function.
I've modified my functions as follows:
void Update_RTC_From_GPS(GPS_Time_t *gps_time) {
RTC_TimeTypeDef sTime = {0};
RTC_DateTypeDef sDate = {0};
uint32_t subsecond_shift = 0;
// Set the time from GPS data
sTime.Hours = gps_time->hours;
sTime.Minutes = gps_time->minutes;
sTime.Seconds = gps_time->seconds;
sTime.TimeFormat = RTC_HOURFORMAT12_AM; // Assuming 24-hour format
// Set the date from GPS data
sDate.Date = gps_time->day;
sDate.Month = gps_time->month;
sDate.Year = gps_time->year;
// First set the time and date using HAL functions
if (HAL_RTC_SetTime(&hrtc, &sTime, RTC_FORMAT_BIN) != HAL_OK) {
printf("Failed to set RTC Time\r\n");
} else {
printf("Set RTC Time to %02d:%02d:%02d\r\n", gps_time->hours, gps_time->minutes, gps_time->seconds);
}
if (HAL_RTC_SetDate(&hrtc, &sDate, RTC_FORMAT_BIN) != HAL_OK) {
printf("Failed to set RTC Date\r\n");
} else {
printf("Set RTC Date to %02d-%02d-20%02d\r\n", gps_time->day, gps_time->month, gps_time->year);
}
// Calculate the sub-second shift to match the provided milliseconds
// Convert milliseconds to sub-second ticks based on RTC prescaler settings
subsecond_shift = (uint32_t)(((float)gps_time->milliseconds / 1000.0) * (hrtc.Init.SynchPrediv + 1));
// Use HAL_RTCEx_SetSynchroShift to apply the precise sub-second shift
if (HAL_RTCEx_SetSynchroShift(&hrtc, RTC_SHIFTADD1S_RESET, hrtc.Init.SynchPrediv - subsecond_shift) != HAL_OK) {
printf("Failed to adjust RTC sub-seconds\r\n");
} else {
printf("Adjusted RTC to exact millisecond from GPS time (%03d ms)\r\n", gps_time->milliseconds);
}
}
void Adjust_RTC_On_PPS(uint16_t milliseconds) {
uint32_t subsecond_shift;
// Calculate sub-second ticks to match the desired millisecond value
subsecond_shift = (uint32_t)(((float)milliseconds / 1000.0) * (hrtc.Init.SynchPrediv + 1));
// Adjust the sub-second value using HAL_RTCEx_SetSynchroShift
// ShiftAdd1S = 0: Do not add an additional second
// SubFrac = hrtc.Init.SynchPrediv - calculated sub-second ticks
if (HAL_RTCEx_SetSynchroShift(&hrtc, RTC_SHIFTADD1S_RESET, hrtc.Init.SynchPrediv - subsecond_shift) != HAL_OK) {
printf("Failed to adjust RTC sub-seconds with PPS\r\n");
} else {
printf("RTC adjusted to exact millisecond on PPS (%03d ms)\r\n", milliseconds);
}
}
However, the result is still unsatisfactory:
Date: 03-10-2024, Time: 10:30:00.000
Date: 03-10-2024, Time: 10:30:00.101
Date: 03-10-2024, Time: 10:30:00.207
Date: 03-10-2024, Time: 10:30:00.308
Date: 03-10-2024, Time: 10:30:00.414
GPS
Set RTC Time to 11:12:13
Set RTC Date to 03-16-2024
Adjusted RTC to exact millisecond from GPS time (456 ms)
Date: 03-10-2024, Time: 11:12:13.2147483647
Date: 03-10-2024, Time: 11:12:13.2147483647
Date: 03-10-2024, Time: 11:12:13.2147483647
Date: 03-10-2024, Time: 11:12:13.2147483647
Date: 03-10-2024, Time: 11:12:13.2147483647
Date: 03-10-2024, Time: 11:12:13.093
PPS
RTC adjusted to exact millisecond on PPS (750 ms)
Date: 03-10-2024, Time: 11:12:13.2147483647
Date: 03-10-2024, Time: 11:12:13.082
Date: 03-10-2024, Time: 11:12:13.187
Date: 03-10-2024, Time: 11:12:13.289
Sometimes I get this value (2147483647, INT32_MAX) in the seconds fraction the first couple times after updating the time - sometimes not e.g. when I debug and step through lines - but in no case I see the correct value.
The procedure to update the RTC subseconds seems now to be correct, is it a problem with the sub-seconds value?
Regards,
Bruno
2024-10-07 03:21 AM
This is not trivial to pull out absolutely correctly.
The shift works by adding the value by which you shift, to the current value of SSR register.
As SSR is downcounter, after you set time, it contains a number equal or just below the synchronous prescaler. If you add a value at that moment, the resulting SSR value is larger than the prescaler, and your calculation
fraction = ((float)(hrtc.Init.SynchPrediv - subsecond) / (hrtc.Init.SynchPrediv + 1)) * 1000;
overflows (subsecond is defined as uint32_t, thus the result of subtraction is a value slightly below 0xFFFF'FFFF -- btw. this formula results in implicitly using double which is probably not something you'd want), and the result overflows the range of the fraction variable, so the compiler is free to return whatever value in it (or to do anything else including a crash)).
You may want to print out the raw value of subseconds.
Btw. the Cube/HAL function reading the time reads subseconds, too. Also, don't read subseconds register just like that, as that locks the time/date and subsequent read then may return a surprisingly older value.
JW
2024-10-14 03:41 AM
Thanks @waclawek.jan for pointing me in the right direction. I first got rid of "double" in the intermediate calculation step (although this was a non-optimized testing code).
I have researched the HAL_RTCEx_SetSynchroShift function plus the RTC_SHIFTR and successfully subtracted a fraction of a second: the RTC subsecond counter is a downward timer and the shift register can add a certain value to the timer (delay the RTC subtracting a second fraction), so if the current RTC subsecond value is lower than the value you want to set, the difference is passed to the SetSynchroShift to achieve the change.
Date: 03-10-2024, Time: 11:12:14.714 (subs: 72)
PPS
Set to 25ms (subs 249)
Add 195 to current 54
Adjusted RTC to exact millisecond from GPS time (025 ms)
Date: 03-10-2024, Time: 11:12:14.089 (subs: 232)
Date: 03-10-2024, Time: 11:12:14.195 (subs: 205)
However, in order to advance the clock adding a fraction of a second, a full second needs to be added to the RTC using ADD1S bit and then subtract a fraction. I've tried it in a couple different ways with no luck. For me it's unclear which value I need to write into SUBFS. For example, if the current value of RTC subsecond is 127 and want to set it to 100, the SetSynchroShift will get value ADD1S and:
None of these worked:
(1: adds 1 full second but doesn't subtract time to desired value)
Date: 14-10-2024, Time: 11:12:19.695 (subs: 77)
PPS
Set to 825ms (subs 44)
Add 1s - 22 to current 66
Adjusted RTC to exact millisecond from GPS time (825 ms)
Date: 14-10-2024, Time: 11:12:20.742 (subs: 65)
Date: 14-10-2024, Time: 11:12:20.847 (subs: 38)
Date: 14-10-2024, Time: 11:12:20.953 (subs: 11)
(2: no effect)
Date: 03-10-2024, Time: 10:30:08.769 (subs: 58)
PPS
Set to 899ms (subs 25)
Add 1s and set to 25
Adjusted RTC to exact millisecond from GPS time (899 ms)
Date: 03-10-2024, Time: 10:30:09.808 (subs: 48)
Date: 03-10-2024, Time: 10:30:09.914 (subs: 21)
Date: 03-10-2024, Time: 10:30:10.019 (subs: 250)
(3: overflow)
Date: 14-10-2024, Time: 11:12:13.699 (subs: 76)
PPS
Set to 825ms (subs 44)
Add 1s + 250 (256 + 44 - 50) to current 50
Adjusted RTC to exact millisecond from GPS time (825 ms)
Date: 14-10-2024, Time: 11:12:14.4294967295 (subs: 291)
Date: 14-10-2024, Time: 11:12:14.4294967295 (subs: 264)
Date: 14-10-2024, Time: 11:12:14.070 (subs: 237)
Date: 14-10-2024, Time: 11:12:14.175 (subs: 210)
In the end, I'm still stuck because I have a code snippet does work only half the times (when the current millisecond value is higher than the one I want to set).
Let's see if we can make the last part work.
Regards, Bruno
2024-10-14 05:42 AM - edited 2024-10-14 05:43 AM
3. is correct (except that there appears to be some delay, probably due to using printf?)
Verify by comparing to the precise time source after adjustment.
Overflow is only temporary and it's in your time calculation, so just ignore it for that max. 1 second after time adjustments. You could also subtract the correction to the calculated time
Date: 14-10-2024, Time: 11:12:14.4294967295 (subs: 291)
=> before calculating milliseconds and displaying time, perform:
if (SSR > 255) {
SSR = SSR - 256;
subtract_one_second_from_time();
}
subtract_one_second_from_time() {
if (seconds == 0) {
seconds = 59;
if (minutes == 0) {
minutes = 59;
if (hours == 0) {
hours = 23;
subtract_one_day(); // and this is tricky
} else {
hours--;
}
} else {
minutes--;
}
} else {
seconds--;
}
}
but that gets nontrivial around midnight as you'd need to adjust the rollover to date too.
JW