|
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
|
A bare-metal (no RTOS) firmware for a NUCLEO-H753ZI that reads a DS3231 real-time clock over I2C1 and prints the date and time on USART1. The clock is set once from the time the firmware was compiled, and a small marker kept in the AT24C32 EEPROM on the same ZS-042 module remembers that it was set, so a reset or a power cycle never sets it back. It uses two plain-C libraries as git submodules: STM32_DS3231 and STM32_AT24C32.
Status: tested on a breadboard (NUCLEO-H753ZI and a ZS-042 module with a CR2032): the first run sets the clock and writes the marker, later runs find the marker and leave the clock alone, FORCE_SET_TIME sets it again, and the clock kept its time through a power cycle with the cell fitted. Not tested: a Release build on the board, and the lost-power warning (it needs the cell removed).
After the CubeMX peripheral init, main.c:
ds3231Init() and at24c32Init() check that both chips answer on I2C1 (the program halts with the HAL status and the I2C error code if one does not);startClock() reports the DS3231 oscillator stop flag and reads the clock marker from the EEPROM. With no marker, or with FORCE_SET_TIME set to 1, it sets the clock from the build time, clears the stop flag, and only then writes the marker and reads it back. Otherwise it leaves the clock alone;The marker is 8 bytes at EEPROM address 0: a magic number and its bitwise inverse. A write cut short by a power loss leaves a pair that does not match and counts as "no marker".
FORCE_SET_TIME at 0: the marker is found and the clock is left alone.FORCE_SET_TIME to 1 in main.c, build, run once, then set it back to 0 and rebuild. Left at 1, every reset would set the clock back to that build time.The time comes from the compiler's __DATE__ and __TIME__, which is the local time of the computer that compiled main.c; there is no time-zone conversion. It is the time main.c was compiled, so the clock is behind by the compile, link and flash delay: measure it once and enter it in SET_TIME_OFFSET_SECONDS. Do not flash an old build with FORCE_SET_TIME at 1 later, or it sets a stale time.
| ZS-042 module | NUCLEO-H753ZI | Notes |
|---|---|---|
| VCC | 3.3 V | Not 5 V: see below |
| GND | GND | |
| SCL | PB8 (Arduino D15) | I2C1_SCL; the module already pulls SCL up |
| SDA | PB9 (Arduino D14) | I2C1_SDA; the module already pulls SDA up |
| SQW, 32K | not connected | Open-drain outputs, unused |
USART1 (PA9 TX, PA10 RX, 19200 baud) is the terminal; it goes to a USB serial adapter.
Power the module from 3.3 V, not 5 V. There are two reasons, both from the ZS-042's own circuit (the seller's schematic; check your board):
The DS3231 and the AT24C32 both work from 3.3 V, so nothing is lost.
The module's AT24C32 shares SCL and SDA and answers at 0x57 (its address pins are pulled up).
main.c| Macro | Default | What it does |
|---|---|---|
FORCE_SET_TIME | 0 | 1 sets the clock from the build time at start-up even though the EEPROM marker says it was set before. Set it back to 0 and rebuild afterwards |
SET_TIME_OFFSET_SECONDS | 0 | Seconds added to the build time to cover the delay between compiling and running |
CLOCK_ROW | 11 | Terminal row of the date line of the clock; the time is on the row below |
ANSI_HIDE_CURSOR | "\x1b[?25l" | Escape sequence that hides the terminal cursor so it does not flicker on the clock |
CLOCK_MARKER_ADDRESS | 0 | EEPROM address of the "clock already set" marker |
CLOCK_MARKER_MAGIC | 0x44533332 | First half of the marker (the ASCII letters "DS32"); the second half is its bitwise inverse |
CLOCK_MARKER_SIZE | 8 | Size of the marker in bytes |
| Path | What it is |
|---|---|
Core/Src/main.c | The start-up sequence and the helper functions |
STM32_DS3231/ | Submodule: the DS3231 driver (time get/set, epoch conversion, oscillator stop flag) |
STM32_AT24C32/ | Submodule: the AT24C32 driver (read, page-aware write with acknowledge polling) |
H753_CPP_I2C_DS3231_RTC_01.ioc | CubeMX configuration (I2C1 on PB8/PB9, USART1) |
After cloning, fetch the libraries with git submodule update --init. In CubeIDE each library folder is a source folder and is on the include paths for both the C and C++ compilers (Project > Properties > C/C++ General > Paths and Symbols, and Project > C/C++ Build > Settings).
On a normal run after the first (the clock redraws in place; the time is shown in bold):
The two clock lines redraw once a second. The day and month names come from two string arrays in main.c, indexed by the weekday (1 = Monday to 7 = Sunday) and month numbers the driver returns.
On the first run, or with FORCE_SET_TIME at 1, the status lines instead show that the clock was set to the build time and the marker written.
The Doxygen configuration (Doxyfile) is in the repository. Run doxygen Doxyfile in the project folder, then open docs/html/index.html.
MIT License. Copyright (c) 2026 MicroControllersTech. See LICENSE.