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
H753 CPP I2C DS3231 RTC 01

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).

How it works

After the CubeMX peripheral init, main.c:

  1. clears the terminal and hides the cursor;
  2. 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);
  3. 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;
  4. the main loop redraws the day name, date and time on two terminal lines once a second.

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".

Setting the clock

  1. First run on a board: nothing to do. The EEPROM holds no marker, so the clock is set to the build time and the marker is written.
  2. Every later run with FORCE_SET_TIME at 0: the marker is found and the clock is left alone.
  3. To set it again: set 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.

Hardware

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):

  1. It charges the battery. The module has a charging path from VCC through a 200 ohm resistor and a 1N4148 diode to the battery pin. At 5 V the diode's output is about 4.3 V, well above a CR2032's roughly 3 V, so current flows into the cell. A CR2032 is not rechargeable and can leak or swell. At 3.3 V the diode's output is about 2.6 V, below the cell, so the diode stays reverse-biased and nothing is charged. (If you must use 5 V, fit a rechargeable LIR2032, or remove the diode.)
  2. It puts 5 V on the I2C lines. The module's pull-ups on SCL, SDA, SQW and 32K go to its own VCC, so at 5 V those lines idle at 5 V. Many MCU pins, and any other device on the bus, are not 5 V tolerant. At 3.3 V they idle at the MCU's own logic level.

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).

Settings in 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

Files

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).

Terminal output

On a normal run after the first (the clock redraws in place; the time is shown in bold):

DS3231 real-time clock
-----------------------
Oscillator : stop flag clear, the time is valid
EEPROM : marker found, the clock was set before
Clock : left alone
Wednesday, October 7, 2026
23:30:58

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.

Documentation

The Doxygen configuration (Doxyfile) is in the repository. Run doxygen Doxyfile in the project folder, then open docs/html/index.html.

License

MIT License. Copyright (c) 2026 MicroControllersTech. See LICENSE.