H753_CPP_I2C_DS3231_RTC_01 1.0
STM32H753 DS3231 real-time clock over I2C, with an AT24C32 EEPROM marker that stops the clock being set twice, built on the STM32_DS3231 and STM32_AT24C32 libraries
Loading...
Searching...
No Matches
DS3231.c
1/*
2 * DS3231.c
3 *
4 * STM32 HAL driver for the DS3231 I2C real-time clock. See DS3231.h for the
5 * overview, error handling, wiring and thread-safety notes.
6 *
7 * Created on: Oct 7, 2026
8 * Author: johng
9 */
10
11#include "DS3231.h"
12
13/* Register addresses and bits, from the DS3231 datasheet register map. */
14#define REG_SECONDS 0x00U /* first of the seven timekeeping registers 00h-06h */
15#define REG_STATUS 0x0FU /* control/status register */
16#define TIME_REG_COUNT 7U /* seconds, minutes, hours, day, date, month, year */
17#define HOURS_12H_BIT 0x40U /* hours register: 1 = 12-hour mode */
18#define HOURS_PM_BIT 0x20U /* hours register, 12-hour mode: 1 = PM */
19#define STATUS_OSF 0x80U /* status register: oscillator stop flag */
20#define STATUS_EN32KHZ 0x08U /* status register: 32 kHz output enable */
21#define STATUS_ALARM_FLAGS 0x03U /* status register: A2F and A1F */
22#define STATUS_FIXED_ZERO 0x70U /* status register bits 6:4 always read 0 */
23
24/* I2C error codes that a second attempt can fix (HAL_I2C_GetError()). */
25#define I2C_TRANSIENT_ERRORS (HAL_I2C_ERROR_BERR | HAL_I2C_ERROR_ARLO | HAL_I2C_ERROR_AF | \
26 HAL_I2C_ERROR_OVR | HAL_I2C_ERROR_TIMEOUT)
27
33static uint8_t decToBcd(uint8_t value)
34{
35 return (uint8_t)(((value / 10U) << 4) | (value % 10U));
36}
37
43static uint8_t bcdToDec(uint8_t value)
44{
45 return (uint8_t)(((value >> 4) * 10U) + (value & 0x0FU));
46}
47
53static bool isBcd(uint8_t value)
54{
55 return ((value >> 4) <= 9U) && ((value & 0x0FU) <= 9U);
56}
57
63static bool isLeapYear(uint16_t year)
64{
65 return ((year % 4U) == 0U && (year % 100U) != 0U) || ((year % 400U) == 0U);
66}
67
74static uint8_t daysInMonth(uint16_t year, uint8_t month)
75{
76 static const uint8_t days[12] = { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };
77
78 if (month == 2U && isLeapYear(year)) {
79 return 29U;
80 }
81 return days[month - 1U];
82}
83
91static int32_t daysFromCivil(int32_t year, int32_t month, int32_t day)
92{
93 int32_t y = (month <= 2) ? year - 1 : year;
94 int32_t era = y / 400; /* years are >= 2000 here, so no negative division */
95 int32_t yearOfEra = y - era * 400;
96 int32_t dayOfYear = (153 * (month + ((month > 2) ? -3 : 9)) + 2) / 5 + day - 1;
97 int32_t dayOfEra = yearOfEra * 365 + yearOfEra / 4 - yearOfEra / 100 + dayOfYear;
98
99 return era * 146097 + dayOfEra - 719468;
100}
101
109static void civilFromDays(int32_t days, uint16_t *year, uint8_t *month, uint8_t *day)
110{
111 int32_t z = days + 719468;
112 int32_t era = z / 146097;
113 int32_t dayOfEra = z - era * 146097;
114 int32_t yearOfEra = (dayOfEra - dayOfEra / 1460 + dayOfEra / 36524 - dayOfEra / 146096) / 365;
115 int32_t dayOfYear = dayOfEra - (365 * yearOfEra + yearOfEra / 4 - yearOfEra / 100);
116 int32_t mp = (5 * dayOfYear + 2) / 153;
117 int32_t d = dayOfYear - (153 * mp + 2) / 5 + 1;
118 int32_t m = (mp < 10) ? mp + 3 : mp - 9;
119 int32_t y = yearOfEra + era * 400 + ((m <= 2) ? 1 : 0);
120
121 *year = (uint16_t)y;
122 *month = (uint8_t)m;
123 *day = (uint8_t)d;
124}
125
133static uint8_t weekdayOf(uint16_t year, uint8_t month, uint8_t day)
134{
135 int32_t days = daysFromCivil(year, month, day);
136
137 /* 1970-01-01 was a Thursday, which is 4 when Monday is 1. */
138 return (uint8_t)(((days + 3) % 7) + 1);
139}
140
149static bool isTransient(I2C_HandleTypeDef *hi2c, HAL_StatusTypeDef status)
150{
151 if (status == HAL_BUSY || status == HAL_TIMEOUT) {
152 return true;
153 }
154 if (status == HAL_ERROR) {
155 return (HAL_I2C_GetError(hi2c) & I2C_TRANSIENT_ERRORS) != 0U;
156 }
157 return false;
158}
159
175static DS3231_Status_t transfer(DS3231_t *dev, bool write, uint8_t reg, uint8_t *buffer, uint16_t length)
176{
177 uint8_t attempt;
178
179 for (attempt = 1U; attempt <= dev->maxAttempts; attempt++) {
180 HAL_StatusTypeDef status;
181
182 if (write) {
183 status = HAL_I2C_Mem_Write(dev->hi2c, DS3231_I2C_ADDRESS, reg, I2C_MEMADD_SIZE_8BIT,
184 buffer, length, DS3231_I2C_TIMEOUT_MS);
185 } else {
186 status = HAL_I2C_Mem_Read(dev->hi2c, DS3231_I2C_ADDRESS, reg, I2C_MEMADD_SIZE_8BIT,
187 buffer, length, DS3231_I2C_TIMEOUT_MS);
188 }
189
190 if (status == HAL_OK) {
191 return DS3231_OK;
192 }
193
194 dev->lastStatus = status;
195
196 if (!isTransient(dev->hi2c, status)) {
197 break;
198 }
199 HAL_Delay(1);
200 }
201
202 Error_Handler();
203 return DS3231_ERR_BUS;
204}
205
217{
218 if (time == NULL) {
219 return false;
220 }
221 if (time->year < DS3231_YEAR_MIN || time->year > DS3231_YEAR_MAX) {
222 return false;
223 }
224 if (time->month < 1U || time->month > 12U) {
225 return false;
226 }
227 if (time->day < 1U || time->day > daysInMonth(time->year, time->month)) {
228 return false;
229 }
230 return (time->hour <= 23U) && (time->minute <= 59U) && (time->second <= 59U);
231}
232
243DS3231_Status_t ds3231TimeToEpoch(const DS3231_TIME_t *time, uint32_t *epochSeconds)
244{
245 int32_t days;
246
247 if (epochSeconds == NULL || !ds3231IsTimeValid(time)) {
248 return DS3231_ERR_PARAM;
249 }
250
251 days = daysFromCivil(time->year, time->month, time->day);
252 *epochSeconds = ((uint32_t)days * 86400U) + ((uint32_t)time->hour * 3600U) +
253 ((uint32_t)time->minute * 60U) + time->second;
254 return DS3231_OK;
255}
256
268{
269 uint32_t secondsOfDay;
270 int32_t days;
271
272 if (time == NULL || epochSeconds < DS3231_EPOCH_MIN || epochSeconds > DS3231_EPOCH_MAX) {
273 return DS3231_ERR_PARAM;
274 }
275
276 days = (int32_t)(epochSeconds / 86400U);
277 secondsOfDay = epochSeconds % 86400U;
278
279 civilFromDays(days, &time->year, &time->month, &time->day);
280 time->weekday = (uint8_t)(((days + 3) % 7) + 1);
281 time->hour = (uint8_t)(secondsOfDay / 3600U);
282 time->minute = (uint8_t)((secondsOfDay % 3600U) / 60U);
283 time->second = (uint8_t)(secondsOfDay % 60U);
284 return DS3231_OK;
285}
286
302DS3231_Status_t ds3231Init(DS3231_t *dev, I2C_HandleTypeDef *hi2c, uint8_t maxAttempts)
303{
304 HAL_StatusTypeDef status;
305 uint8_t statusRegister;
306
307 if (dev == NULL || hi2c == NULL) {
308 return DS3231_ERR_PARAM;
309 }
310
311 dev->hi2c = hi2c;
312 dev->maxAttempts = (maxAttempts == 0U) ? (uint8_t)DS3231_DEFAULT_MAX_ATTEMPTS : maxAttempts;
313 dev->lastStatus = HAL_OK;
314
315 status = HAL_I2C_IsDeviceReady(hi2c, DS3231_I2C_ADDRESS, dev->maxAttempts, DS3231_I2C_TIMEOUT_MS);
316 if (status != HAL_OK) {
317 dev->lastStatus = status;
319 }
320
321 if (transfer(dev, false, REG_STATUS, &statusRegister, 1U) != DS3231_OK) {
322 return DS3231_ERR_BUS;
323 }
324 if ((statusRegister & STATUS_FIXED_ZERO) != 0U) {
326 }
327 return DS3231_OK;
328}
329
344{
345 uint8_t raw[TIME_REG_COUNT];
346 uint8_t hours;
347 uint8_t month;
348 uint8_t i;
349
350 if (dev == NULL || time == NULL) {
351 return DS3231_ERR_PARAM;
352 }
353
354 if (transfer(dev, false, REG_SECONDS, raw, TIME_REG_COUNT) != DS3231_OK) {
355 return DS3231_ERR_BUS;
356 }
357
358 /* Mask the unused bits and the century bit, then make sure every field is BCD. */
359 raw[0] &= 0x7FU;
360 raw[1] &= 0x7FU;
361 raw[4] &= 0x3FU;
362 raw[5] &= 0x1FU;
363 for (i = 0U; i < TIME_REG_COUNT; i++) {
364 if (i == 2U || i == 3U) {
365 continue; /* hours are checked below, the day-of-week register is not used */
366 }
367 if (!isBcd(raw[i])) {
369 }
370 }
371
372 if ((raw[2] & HOURS_12H_BIT) != 0U) {
373 uint8_t hours12 = (uint8_t)(raw[2] & 0x1FU);
374
375 if (!isBcd(hours12)) {
377 }
378 hours12 = bcdToDec(hours12);
379 if (hours12 < 1U || hours12 > 12U) {
381 }
382 hours = (uint8_t)((hours12 % 12U) + (((raw[2] & HOURS_PM_BIT) != 0U) ? 12U : 0U));
383 } else {
384 if (!isBcd((uint8_t)(raw[2] & 0x3FU))) {
386 }
387 hours = bcdToDec((uint8_t)(raw[2] & 0x3FU));
388 }
389
390 month = bcdToDec(raw[5]);
391
392 time->second = bcdToDec(raw[0]);
393 time->minute = bcdToDec(raw[1]);
394 time->hour = hours;
395 time->day = bcdToDec(raw[4]);
396 time->month = month;
397 time->year = (uint16_t)(DS3231_YEAR_MIN + bcdToDec(raw[6]));
398 time->weekday = 0U;
399
400 if (!ds3231IsTimeValid(time)) {
402 }
403 time->weekday = weekdayOf(time->year, time->month, time->day);
404 return DS3231_OK;
405}
406
420{
421 uint8_t raw[TIME_REG_COUNT];
422
423 if (dev == NULL || !ds3231IsTimeValid(time)) {
424 return DS3231_ERR_PARAM;
425 }
426
427 raw[0] = decToBcd(time->second);
428 raw[1] = decToBcd(time->minute);
429 raw[2] = decToBcd(time->hour); /* 12/24 bit left 0: 24-hour mode */
430 raw[3] = weekdayOf(time->year, time->month, time->day);
431 raw[4] = decToBcd(time->day);
432 raw[5] = decToBcd(time->month); /* century bit left 0 */
433 raw[6] = decToBcd((uint8_t)(time->year - DS3231_YEAR_MIN));
434
435 return transfer(dev, true, REG_SECONDS, raw, TIME_REG_COUNT);
436}
437
444DS3231_Status_t ds3231GetEpoch(DS3231_t *dev, uint32_t *epochSeconds)
445{
446 DS3231_TIME_t time;
447 DS3231_Status_t result;
448
449 if (epochSeconds == NULL) {
450 return DS3231_ERR_PARAM;
451 }
452
453 result = ds3231GetTime(dev, &time);
454 if (result != DS3231_OK) {
455 return result;
456 }
457 return ds3231TimeToEpoch(&time, epochSeconds);
458}
459
471DS3231_Status_t ds3231SetEpoch(DS3231_t *dev, uint32_t epochSeconds)
472{
473 DS3231_TIME_t time;
474 DS3231_Status_t result;
475
476 result = ds3231EpochToTime(epochSeconds, &time);
477 if (result != DS3231_OK) {
478 return result;
479 }
480 return ds3231SetTime(dev, &time);
481}
482
495{
496 uint8_t statusRegister;
497
498 if (dev == NULL || stopped == NULL) {
499 return DS3231_ERR_PARAM;
500 }
501
502 if (transfer(dev, false, REG_STATUS, &statusRegister, 1U) != DS3231_OK) {
503 return DS3231_ERR_BUS;
504 }
505 *stopped = (statusRegister & STATUS_OSF) != 0U;
506 return DS3231_OK;
507}
508
521{
522 uint8_t statusRegister;
523
524 if (dev == NULL) {
525 return DS3231_ERR_PARAM;
526 }
527
528 if (transfer(dev, false, REG_STATUS, &statusRegister, 1U) != DS3231_OK) {
529 return DS3231_ERR_BUS;
530 }
531
532 statusRegister = (uint8_t)((statusRegister & STATUS_EN32KHZ) | STATUS_ALARM_FLAGS);
533 return transfer(dev, true, REG_STATUS, &statusRegister, 1U);
534}
Types, constants and function prototypes of the STM32_DS3231 driver.
DS3231_Status_t ds3231GetEpoch(DS3231_t *dev, uint32_t *epochSeconds)
Read the current time as seconds since 1970-01-01 00:00:00.
Definition DS3231.c:444
DS3231_Status_t ds3231SetTime(DS3231_t *dev, const DS3231_TIME_t *time)
Set the date and time.
Definition DS3231.c:419
#define DS3231_YEAR_MAX
Definition DS3231.h:85
DS3231_Status_t ds3231TimeToEpoch(const DS3231_TIME_t *time, uint32_t *epochSeconds)
Convert a date and time to seconds since 1970-01-01 00:00:00 (UTC, no leap seconds).
Definition DS3231.c:243
DS3231_Status_t ds3231EpochToTime(uint32_t epochSeconds, DS3231_TIME_t *time)
Convert seconds since 1970-01-01 00:00:00 (UTC, no leap seconds) to a date and time.
Definition DS3231.c:267
#define DS3231_DEFAULT_MAX_ATTEMPTS
Definition DS3231.h:76
#define DS3231_YEAR_MIN
Definition DS3231.h:82
DS3231_Status_t
Definition DS3231.h:94
@ DS3231_OK
Definition DS3231.h:95
@ DS3231_ERR_BUS
Definition DS3231.h:99
@ DS3231_ERR_NOT_FOUND
Definition DS3231.h:97
@ DS3231_ERR_PARAM
Definition DS3231.h:96
@ DS3231_ERR_INVALID_DATA
Definition DS3231.h:98
#define DS3231_EPOCH_MAX
Definition DS3231.h:91
#define DS3231_I2C_ADDRESS
Definition DS3231.h:73
#define DS3231_I2C_TIMEOUT_MS
Definition DS3231.h:79
DS3231_Status_t ds3231GetTime(DS3231_t *dev, DS3231_TIME_t *time)
Read the current date and time.
Definition DS3231.c:343
bool ds3231IsTimeValid(const DS3231_TIME_t *time)
Check that a date and time are valid for the DS3231.
Definition DS3231.c:216
DS3231_Status_t ds3231SetEpoch(DS3231_t *dev, uint32_t epochSeconds)
Set the time from seconds since 1970-01-01 00:00:00.
Definition DS3231.c:471
DS3231_Status_t ds3231ClearOscillatorStopFlag(DS3231_t *dev)
Clear the oscillator stop flag.
Definition DS3231.c:520
DS3231_Status_t ds3231IsOscillatorStopped(DS3231_t *dev, bool *stopped)
Find out whether the oscillator has stopped since the flag was last cleared.
Definition DS3231.c:494
DS3231_Status_t ds3231Init(DS3231_t *dev, I2C_HandleTypeDef *hi2c, uint8_t maxAttempts)
Set up a driver handle and check that a DS3231 answers on the bus.
Definition DS3231.c:302
A calendar date and time, always 24-hour clock.
Definition DS3231.h:109
uint8_t second
Definition DS3231.h:116
uint8_t weekday
Definition DS3231.h:113
uint8_t month
Definition DS3231.h:111
uint8_t minute
Definition DS3231.h:115
uint8_t hour
Definition DS3231.h:114
uint16_t year
Definition DS3231.h:110
uint8_t day
Definition DS3231.h:112
HAL_StatusTypeDef lastStatus
Definition DS3231.h:123
uint8_t maxAttempts
Definition DS3231.h:122
I2C_HandleTypeDef * hi2c
Definition DS3231.h:121