{"id":4305,"date":"2019-09-04T20:00:57","date_gmt":"2019-09-04T18:00:57","guid":{"rendered":"https:\/\/msalamon.pl\/?p=4305"},"modified":"2025-12-27T19:23:21","modified_gmt":"2025-12-27T18:23:21","slug":"insanely-accurate-ds3231-rtc-on-stm32","status":"publish","type":"post","link":"https:\/\/msalamon.pl\/en\/insanely-accurate-ds3231-rtc-on-stm32\/","title":{"rendered":"Insanely Accurate DS3231 RTC on STM32"},"content":{"rendered":"\n<p>Not everyone may know this, but real-time clocks, or RTCs, are only as accurate as their clock source. Most often, such systems are driven by a 32.768 kHz frequency. STM32 has a built-in RTC, but exactly the same rule applies to its accuracy. Generally, what STM32 offers as an internal RC oscillator for the RTC is not of the highest quality and is susceptible to temperature changes. Connecting an <\/p>\n\n\n\n<!--more-->\n\n\n\n<p>external quartz resonator also does not guarantee that it will operate identically at every possible temperature. Is there a solution? Yes \u2013 temperature compensation of the oscillator frequency.<\/p>\n\n\n\n<p>But wait! You can compensate for frequency differences manually. Yes. You can perform oscillator frequency calibration. It\u2019s a slightly complicated process and doesn\u2019t guarantee results in all conditions. Using cheap quartz resonators doesn\u2019t help either. Their error vs. temperature characteristic looks like this (from the STM32F103 RTC documentation):<\/p>\n\n\n<div class=\"wp-block-image\">\n<figure class=\"aligncenter\"><a href=\"http:\/\/msalamon.pl\/wp-content\/uploads\/2019\/09\/ds3231_clasic_crystal_accuracy.png\"><img loading=\"lazy\" decoding=\"async\" width=\"300\" height=\"135\" src=\"http:\/\/msalamon.pl\/wp-content\/uploads\/2019\/09\/ds3231_clasic_crystal_accuracy-300x135.png\" alt=\"\" class=\"wp-image-1148\" srcset=\"https:\/\/msalamon.pl\/wp-content\/uploads\/2019\/09\/ds3231_clasic_crystal_accuracy-300x135.png 300w, https:\/\/msalamon.pl\/wp-content\/uploads\/2019\/09\/ds3231_clasic_crystal_accuracy-1024x461.png 1024w, https:\/\/msalamon.pl\/wp-content\/uploads\/2019\/09\/ds3231_clasic_crystal_accuracy-768x346.png 768w, https:\/\/msalamon.pl\/wp-content\/uploads\/2019\/09\/ds3231_clasic_crystal_accuracy-24x11.png 24w, https:\/\/msalamon.pl\/wp-content\/uploads\/2019\/09\/ds3231_clasic_crystal_accuracy-36x16.png 36w, https:\/\/msalamon.pl\/wp-content\/uploads\/2019\/09\/ds3231_clasic_crystal_accuracy-160x72.png 160w, https:\/\/msalamon.pl\/wp-content\/uploads\/2019\/09\/ds3231_clasic_crystal_accuracy.png 1061w\" sizes=\"auto, (max-width: 300px) 100vw, 300px\" \/><\/a><\/figure>\n<\/div>\n\n\n<p>At an ambient temperature of about 0\u00b0C, the accuracy drops below -20 ppm. According to the <a href=\"https:\/\/www.maximintegrated.com\/en\/design\/tools\/calculators\/product-design\/rtc.cfm\" target=\"_blank\" rel=\"noopener\">calculator<\/a>, an error at this level results in a delay of 1.73 seconds\/day, i.e. 10.51 minutes\/year. Who wants to set the clock every month? It\u2019s less of a problem when the RTC has consistently similar conditions, such as in an apartment. Then you can save the day with software calibration. What if it\u2019s in an outdoor device? Let\u2019s say it\u2019s installed in winter in an info booth with a huge, heat-generating display. On a sunny winter day, when the booth is open, the temperature in the electronics compartment might hit 40\u00baC at the extreme, giving an error of about -10 ppm. As evening approaches, the system cools down slowly moving left along the characteristic. Nice, because the error decreases for a while. Unfortunately at night, when the device is asleep, the temperature drops to -5\u00baC and the oscillator accuracy is -30 ppm. Are we able to perfectly compensate such errors in software? Probably we can, but who would want to\u2026<\/p>\n\n\n\n<p>Especially knowing that for little money you can get an RTC that compensates for such errors automatically. One of the precise chips on the market is the <a href=\"https:\/\/sklep.msalamon.pl\/produkt\/modul-ds3231-rtc-24c32-eeprom-z-gniazdem-na-baterie\/?utm_source=blog&amp;utm_medium=article&amp;utm_campaign=ds3231&amp;utm_content=Text\">DS3231<\/a>.<\/p>\n\n\n\n<h1 class=\"wp-block-heading\">DS3231 \u2013 an extremely accurate RTC<\/h1>\n\n\n<div class=\"wp-block-image\">\n<figure class=\"aligncenter\"><a href=\"https:\/\/sklep.msalamon.pl\/produkt\/modul-ds3231-rtc-24c32-eeprom-z-gniazdem-na-baterie\/?utm_source=blog&amp;utm_medium=banner&amp;utm_campaign=ds3231&amp;utm_content=ds3231\"><img loading=\"lazy\" decoding=\"async\" width=\"1200\" height=\"400\" src=\"http:\/\/msalamon.pl\/wp-content\/uploads\/2019\/09\/DS3231_baner.jpg\" alt=\"\" class=\"wp-image-1164\" srcset=\"https:\/\/msalamon.pl\/wp-content\/uploads\/2019\/09\/DS3231_baner.jpg 1200w, https:\/\/msalamon.pl\/wp-content\/uploads\/2019\/09\/DS3231_baner-300x100.jpg 300w, https:\/\/msalamon.pl\/wp-content\/uploads\/2019\/09\/DS3231_baner-1024x341.jpg 1024w, https:\/\/msalamon.pl\/wp-content\/uploads\/2019\/09\/DS3231_baner-768x256.jpg 768w, https:\/\/msalamon.pl\/wp-content\/uploads\/2019\/09\/DS3231_baner-24x8.jpg 24w, https:\/\/msalamon.pl\/wp-content\/uploads\/2019\/09\/DS3231_baner-36x12.jpg 36w, https:\/\/msalamon.pl\/wp-content\/uploads\/2019\/09\/DS3231_baner-160x53.jpg 160w\" sizes=\"auto, (max-width: 1200px) 100vw, 1200px\" \/><\/a><\/figure>\n<\/div>\n\n\n<p><\/p>\n\n\n\n<p>Mr. Mateusz, how accurate is \u201caccurate\u201d? According to the <a href=\"http:\/\/msalamon.pl\/download\/1149\/\">documentation<\/a> it is:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>\u00b12 ppm for temperatures 0 \u00f7 +40\u00baC<\/li>\n\n\n\n<li>\u00b13.5 ppm for temperatures -40 \u00f7 +85\u00baC<\/li>\n<\/ul>\n\n\n\n<p>Entering 2 ppm into the calculator mentioned above shows that the <a href=\"https:\/\/sklep.msalamon.pl\/produkt\/modul-ds3231-rtc-24c32-eeprom-z-gniazdem-na-baterie\/?utm_source=blog&amp;utm_medium=article&amp;utm_campaign=ds3231&amp;utm_content=Text\">DS3231<\/a> will be late by\u2026 a minute per year. Per day that\u2019s 0.17 seconds. Is that acceptable? Much more than 10 minutes per year! This is a very good result.<\/p>\n\n\n\n<p>How is it so accurate? The Maxim chip has an integrated quartz oscillator together with the crystal. The oscillator also has temperature compensation. This allows the manufacturer to control all the most important components that determine accuracy. Thanks to compensation, they squeeze the absolute maximum out of them, resulting in those \u00b12 ppm.&nbsp;<\/p>\n\n\n\n<p>In Poland, detailed tests of <a href=\"https:\/\/sklep.msalamon.pl\/produkt\/modul-ds3231-rtc-24c32-eeprom-z-gniazdem-na-baterie\/?utm_source=blog&amp;utm_medium=article&amp;utm_campaign=ds3231&amp;utm_content=Text\">DS3231<\/a> compensation were carried out by Mirek Karda\u015b. Allow me not to roast a chip connected to an oscilloscope \ud83d\ude42<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">The most important things from the specification<\/h2>\n\n\n\n<p>Besides accuracy, you may be interested in a few other things. The most important is probably how the chip connects to the microcontroller. The communication interface is I\u00b2C, so it\u2019s easy. In addition to the SDA and SCL pins, our RTC has a few other outputs.<\/p>\n\n\n\n<p>As is typical with clock systems, we want the option to connect a battery to keep time. Here there is a dedicated pin, and inside the package there is a power-fail detection circuit and automatic switchover to battery power.<\/p>\n\n\n\n<p><a href=\"https:\/\/sklep.msalamon.pl\/produkt\/modul-ds3231-rtc-24c32-eeprom-z-gniazdem-na-baterie\/?utm_source=blog&amp;utm_medium=article&amp;utm_campaign=ds3231&amp;utm_content=Text\">DS3231<\/a> has two clock outputs. One is 32.768 kHz straight from the built-in temperature-compensated oscillator and is called <strong><em>32kHz<\/em><\/strong>. This source is perfect for clocking other circuits in low-power mode.<\/p>\n\n\n\n<p>There is one more clock output. It is on a pin with a dual function called <strong>INT\/SQW<\/strong>. Here we can get one of four programmable frequencies: 1 Hz, 1024 Hz, 4096 Hz, or 8192 Hz. This is a suitably divided oscillator frequency.<\/p>\n\n\n\n<p>The second function of the <strong>INT\/SQW<\/strong> pin, as the first part of the name suggests, is interrupts. According to the documentation, an interrupt is asserted when an alarm is active and its time arrives. Does anyone use alarms?<\/p>\n\n\n\n<p>Importantly \u2013 both clock outputs are <em>Open Drain<\/em>. You need to provide pull-up resistors, which fortunately are already placed on the PCB in ready-made modules.<\/p>\n\n\n\n<p>How are time and date stored? Of course, there are registers for hours, minutes, and seconds. As for the calendar, it is fairly extensive. In addition to year, month, and day, there is day of the week, century, and leap year support.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Schematics and software<a href=\"https:\/\/sklep.msalamon.pl\/kategoria-produktu\/dev-boardy\/stm32-nucleo\/?utm_source=blog&amp;utm_medium=banner&amp;utm_campaign=other_rtc&amp;utm_content=Text\"><\/a><\/h2>\n\n\n\n<p>Today I\u2019ll use a <a href=\"https:\/\/sklep.msalamon.pl\/kategoria-produktu\/dev-boardy\/stm32-nucleo\/?utm_source=blog&amp;utm_medium=article&amp;utm_campaign=ds3231&amp;utm_content=Text\">Nucleo<\/a> board with an STM32F410RB that I received from <strong>Kamami<\/strong>, for which I am very grateful! \ud83d\ude42<\/p>\n\n\n\n<p>Connecting the module to the <a href=\"https:\/\/sklep.msalamon.pl\/kategoria-produktu\/dev-boardy\/stm32-nucleo\/?utm_source=blog&amp;utm_medium=article&amp;utm_campaign=ds3231&amp;utm_content=Text\">Nucleo<\/a> is, as usual, trivially simple. You only need to connect the I\u00b2C pins and the INT\/SQW pin for the RTC interrupt.<\/p>\n\n\n<div class=\"wp-block-image\">\n<figure class=\"aligncenter\"><a href=\"http:\/\/msalamon.pl\/wp-content\/uploads\/2019\/09\/ds3231_schematic_nucleo.png\"><img loading=\"lazy\" decoding=\"async\" width=\"300\" height=\"162\" src=\"http:\/\/msalamon.pl\/wp-content\/uploads\/2019\/09\/ds3231_schematic_nucleo-300x162.png\" alt=\"\" class=\"wp-image-1152\" srcset=\"https:\/\/msalamon.pl\/wp-content\/uploads\/2019\/09\/ds3231_schematic_nucleo-300x162.png 300w, https:\/\/msalamon.pl\/wp-content\/uploads\/2019\/09\/ds3231_schematic_nucleo-1024x554.png 1024w, https:\/\/msalamon.pl\/wp-content\/uploads\/2019\/09\/ds3231_schematic_nucleo-768x415.png 768w, https:\/\/msalamon.pl\/wp-content\/uploads\/2019\/09\/ds3231_schematic_nucleo-24x13.png 24w, https:\/\/msalamon.pl\/wp-content\/uploads\/2019\/09\/ds3231_schematic_nucleo-36x19.png 36w, https:\/\/msalamon.pl\/wp-content\/uploads\/2019\/09\/ds3231_schematic_nucleo-148x80.png 148w, https:\/\/msalamon.pl\/wp-content\/uploads\/2019\/09\/ds3231_schematic_nucleo.png 1416w\" sizes=\"auto, (max-width: 300px) 100vw, 300px\" \/><\/a><\/figure>\n<\/div>\n\n\n<p>For the software I of course used STM32CubeIDE version 1.0.2. The HAL F4 libraries are version 1.24.1. Let\u2019s go!<\/p>\n\n\n\n<p>I could show you the classic approach, i.e., crude, blocking reads from the RTC every so often in the main loop. A better solution would of course be to read in the RTC interrupt, which occurs every second. However, such a crude, blocking read in an interrupt is also not optimal\u2026<\/p>\n\n\n\n<p>Remember that STM32 has something called DMA, of which I am a huge fan. You don\u2019t have to wait idly while I\u00b2C communication is taking place. DMA can handle data collection, and the CPU can later convert BCD to DEC. I\u2019ll show you a comparison at the end.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Configuring and handling DMA for the RTC<\/h3>\n\n\n\n<p>First, you need to configure the I\u00b2C interface. I chose <em>I2C1.<\/em> Choose the interface speed. I <strong>recommend 400 kHz<\/strong>, especially since the <a href=\"https:\/\/sklep.msalamon.pl\/produkt\/modul-ds3231-rtc-24c32-eeprom-z-gniazdem-na-baterie\/?utm_source=blog&amp;utm_medium=article&amp;utm_campaign=ds3231&amp;utm_content=Text\">DS3231<\/a> supports <em>Fast Mode<\/em>. If your microcontroller cannot operate with that clock, the classic 100 kHz is fine. Use whichever pins you prefer. I set <strong>PB9<\/strong> and <strong>PB8<\/strong> because they are routed to the <em>Arduino<\/em> connector. That way I can connect a logic analyzer to the <em>Morpho<\/em> pins.<\/p>\n\n\n<div class=\"wp-block-image\">\n<figure class=\"aligncenter\"><a href=\"http:\/\/msalamon.pl\/wp-content\/uploads\/2019\/09\/ds3231_cube_i2c.png\"><img loading=\"lazy\" decoding=\"async\" width=\"451\" height=\"532\" src=\"http:\/\/msalamon.pl\/wp-content\/uploads\/2019\/09\/ds3231_cube_i2c.png\" alt=\"\" class=\"wp-image-1155\" srcset=\"https:\/\/msalamon.pl\/wp-content\/uploads\/2019\/09\/ds3231_cube_i2c.png 451w, https:\/\/msalamon.pl\/wp-content\/uploads\/2019\/09\/ds3231_cube_i2c-254x300.png 254w, https:\/\/msalamon.pl\/wp-content\/uploads\/2019\/09\/ds3231_cube_i2c-20x24.png 20w, https:\/\/msalamon.pl\/wp-content\/uploads\/2019\/09\/ds3231_cube_i2c-31x36.png 31w, https:\/\/msalamon.pl\/wp-content\/uploads\/2019\/09\/ds3231_cube_i2c-68x80.png 68w\" sizes=\"auto, (max-width: 451px) 100vw, 451px\" \/><\/a><\/figure>\n<\/div>\n\n\n<p>The next thing you need to set is <strong>DMA<\/strong> for our <em>I2C1<\/em> interface. You can conveniently do this from the I\u00b2C configuration panel. Enable DMA for both receive and transmit.&nbsp;<\/p>\n\n\n<div class=\"wp-block-image\">\n<figure class=\"aligncenter\"><a href=\"http:\/\/msalamon.pl\/wp-content\/uploads\/2019\/09\/ds3231_cube_dma.png\"><img loading=\"lazy\" decoding=\"async\" width=\"677\" height=\"328\" src=\"http:\/\/msalamon.pl\/wp-content\/uploads\/2019\/09\/ds3231_cube_dma.png\" alt=\"\" class=\"wp-image-1156\" srcset=\"https:\/\/msalamon.pl\/wp-content\/uploads\/2019\/09\/ds3231_cube_dma.png 677w, https:\/\/msalamon.pl\/wp-content\/uploads\/2019\/09\/ds3231_cube_dma-300x145.png 300w, https:\/\/msalamon.pl\/wp-content\/uploads\/2019\/09\/ds3231_cube_dma-24x12.png 24w, https:\/\/msalamon.pl\/wp-content\/uploads\/2019\/09\/ds3231_cube_dma-36x17.png 36w, https:\/\/msalamon.pl\/wp-content\/uploads\/2019\/09\/ds3231_cube_dma-160x78.png 160w\" sizes=\"auto, (max-width: 677px) 100vw, 677px\" \/><\/a><\/figure>\n<\/div>\n\n\n<p>To preview data from the RTC, also configure <strong>UART2<\/strong> in the classic way, which is routed to the ST-Link USB. I leave the standard values, i.e., 115200 8n1.<\/p>\n\n\n<div class=\"wp-block-image\">\n<figure class=\"aligncenter\"><a href=\"http:\/\/msalamon.pl\/wp-content\/uploads\/2019\/09\/ds3231_cube_uart.png\"><img loading=\"lazy\" decoding=\"async\" width=\"668\" height=\"507\" src=\"http:\/\/msalamon.pl\/wp-content\/uploads\/2019\/09\/ds3231_cube_uart.png\" alt=\"\" class=\"wp-image-1157\" srcset=\"https:\/\/msalamon.pl\/wp-content\/uploads\/2019\/09\/ds3231_cube_uart.png 668w, https:\/\/msalamon.pl\/wp-content\/uploads\/2019\/09\/ds3231_cube_uart-300x228.png 300w, https:\/\/msalamon.pl\/wp-content\/uploads\/2019\/09\/ds3231_cube_uart-24x18.png 24w, https:\/\/msalamon.pl\/wp-content\/uploads\/2019\/09\/ds3231_cube_uart-36x27.png 36w, https:\/\/msalamon.pl\/wp-content\/uploads\/2019\/09\/ds3231_cube_uart-105x80.png 105w\" sizes=\"auto, (max-width: 668px) 100vw, 668px\" \/><\/a><\/figure>\n<\/div>\n\n\n<p>You will also need a pin to handle the interrupt from the <a href=\"https:\/\/sklep.msalamon.pl\/produkt\/modul-ds3231-rtc-24c32-eeprom-z-gniazdem-na-baterie\/?utm_source=blog&amp;utm_medium=article&amp;utm_campaign=ds3231&amp;utm_content=Text\">DS3231<\/a> chip. You can set any GPIO to the <strong>GPIO_EXTI<\/strong> function. I chose the <strong>PA9<\/strong> pin. Name it <strong>DS3231_INT<\/strong> and set it to trigger on the falling edge. In practice it doesn\u2019t matter which edge you choose \ud83d\ude42 The important thing is to use one of the two so you don\u2019t read the same time twice unnecessarily. <strong>Also remember to enable the interrupt for the proper EXTI in CubeMX!<\/strong><\/p>\n\n\n\n<p>You can leave the clock at the default 84 MHz if you generated the project \u201cfrom the board\u201d rather than by selecting the MCU. If you have it set differently, enter 84 for HCLK so our projects are consistent. After configuring the project like this, you can save it and generate the code. Also remember to select separate file generation for each peripheral.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Code<\/h3>\n\n\n\n<p>I prepared a simple library for the <a href=\"https:\/\/sklep.msalamon.pl\/produkt\/modul-ds3231-rtc-24c32-eeprom-z-gniazdem-na-baterie\/?utm_source=blog&amp;utm_medium=article&amp;utm_campaign=ds3231&amp;utm_content=Text\">DS3231<\/a>. It\u2019s not a masterpiece that supports every function of the chip. I skipped alarms and reading the chip\u2019s temperature (since there is temperature compensation, there is also the ability to measure temperature). I think the most important and useful part is reading the time and date anyway.&nbsp;<\/p>\n\n\n\n<p>You\u2019ll be interested in one or two defines. These are <strong>#define DS3231_USE_DMA<\/strong> and&nbsp;<strong>#define DS3231_ADDRESS (0x68&lt;&lt;1)<\/strong>. The first, if uncommented, selects the function to read from the RTC via DMA. If it is commented out, it will work with the classic, blocking ones.<\/p>\n\n\n\n<p>The second define is the chip address. <strong>The module without jumpers has address 0x68<\/strong>. If you have or placed jumpers, you will need to change this line according to your address.<\/p>\n\n\n\n<p>Let\u2019s move on to the functions that interest you. The first is of course <strong>initialization<\/strong>.<\/p>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"c\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">void DS3231_Init(I2C_HandleTypeDef *hi2c);<\/pre>\n\n\n\n<p>You pass in the handler of the selected I\u00b2C interface. Inside the initialization I set a 1 Hz waveform on the SQW output and disable the 32 kHz output.<\/p>\n\n\n\n<p>The next important function is <strong>setting the clock<\/strong>.<\/p>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"c\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">void DS3231_SetDateTime(RTCDateTime *DateTime);<\/pre>\n\n\n\n<p>As an argument you pass a pointer to a previously prepared <em>RTCDateTime<\/em> structure, which is defined in the <a href=\"https:\/\/sklep.msalamon.pl\/produkt\/modul-ds3231-rtc-24c32-eeprom-z-gniazdem-na-baterie\/?utm_source=blog&amp;utm_medium=article&amp;utm_campaign=ds3231&amp;utm_content=Text\">DS3231<\/a> library.<\/p>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"c\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">typedef struct\n{\n\tuint16_t Year;\n\tuint8_t Month;\n\tuint8_t Day;\n\tuint8_t Hour;\n\tuint8_t Minute;\n\tuint8_t Second;\n\tuint8_t DayOfWeek;\n}RTCDateTime;\n<\/pre>\n\n\n\n<p>The last important element is <strong>receiving data from the RTC<\/strong> and converting it into the <em>RTCDateTime<\/em> structure.&nbsp;Depending on which type of handling you choose, for blocking\/interrupt-based you will have:<\/p>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"c\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">void DS3231_GetDateTime(RTCDateTime *DateTime);<\/pre>\n\n\n\n<p>This function will fetch the data via I\u00b2C and store it in the variable pointed to by <em>*DateTime.<\/em><\/p>\n\n\n\n<p>And the most important ones from the DMA perspective. Functions that receive data and place it into the structure.<\/p>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"c\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">void DS3231_ReceiveDateTimeDMA(void);\nvoid DS3231_CalculateDateTime(RTCDateTime *DateTime);<\/pre>\n\n\n\n<p>Why two? I\u2019ll explain.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">DMA reception<\/h3>\n\n\n\n<p>I used the <strong>DS3231_ReceiveDateTimeDMA<\/strong> function in the EXTI interrupt generated by the RTC thanks to the 1 Hz signal on the SQW output. Its task is to start receiving from the <a href=\"https:\/\/sklep.msalamon.pl\/produkt\/modul-ds3231-rtc-24c32-eeprom-z-gniazdem-na-baterie\/?utm_source=blog&amp;utm_medium=article&amp;utm_campaign=ds3231&amp;utm_content=Text\">DS3231<\/a> via DMA all the data related to time and date. Fortunately, RTC manufacturers place this data one after another.<\/p>\n\n\n\n<p>When the reception is complete, a DMA reception complete interrupt is triggered. That\u2019s where I placed the <strong>DS3231_CalculateDateTime<\/strong> function to unpack the received data into time and date values.&nbsp;<\/p>\n\n\n\n<p>That\u2019s all the philosophy \ud83d\ude42 How to use these functions in interrupts? You need to override the default <em>_weak<\/em> functions provided by the HAL libraries. I did this in the main file, and it looks like this:<\/p>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"c\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">\/* USER CODE BEGIN 4 *\/\nvoid HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)\n{\n  if(GPIO_Pin == DS3231_INT_Pin)\n  {\n    DS3231_ReceiveDateTimeDMA();\n  }\n}\n\nvoid HAL_I2C_MemRxCpltCallback(I2C_HandleTypeDef *hi2c)\n{\n  DS3231_CalculateDateTime(&amp;amp;r);\n}\n\/* USER CODE END 4 *\/<\/pre>\n\n\n\n<p>And that\u2019s it! Simple, right?<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Results, performance<\/h2>\n\n\n\n<p>If this isn\u2019t your first time reading me, you know I like to check code execution times, especially CPU time. This time I won\u2019t let it slide either!<\/p>\n\n\n\n<p>First, I\u2019ll show you what blocking data reception from the <a href=\"https:\/\/sklep.msalamon.pl\/produkt\/modul-ds3231-rtc-24c32-eeprom-z-gniazdem-na-baterie\/?utm_source=blog&amp;utm_medium=article&amp;utm_campaign=ds3231&amp;utm_content=Text\">DS3231<\/a> looks like. First 100 kHz.<\/p>\n\n\n<div class=\"wp-block-image\">\n<figure class=\"aligncenter\"><a href=\"http:\/\/msalamon.pl\/wp-content\/uploads\/2019\/09\/ds3231_interrupt_100k-1.png\"><img loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"137\" src=\"http:\/\/msalamon.pl\/wp-content\/uploads\/2019\/09\/ds3231_interrupt_100k-1-1024x137.png\" alt=\"\" class=\"wp-image-1158\" srcset=\"https:\/\/msalamon.pl\/wp-content\/uploads\/2019\/09\/ds3231_interrupt_100k-1-1024x137.png 1024w, https:\/\/msalamon.pl\/wp-content\/uploads\/2019\/09\/ds3231_interrupt_100k-1-300x40.png 300w, https:\/\/msalamon.pl\/wp-content\/uploads\/2019\/09\/ds3231_interrupt_100k-1-768x102.png 768w, https:\/\/msalamon.pl\/wp-content\/uploads\/2019\/09\/ds3231_interrupt_100k-1-1536x205.png 1536w, https:\/\/msalamon.pl\/wp-content\/uploads\/2019\/09\/ds3231_interrupt_100k-1-24x3.png 24w, https:\/\/msalamon.pl\/wp-content\/uploads\/2019\/09\/ds3231_interrupt_100k-1-36x5.png 36w, https:\/\/msalamon.pl\/wp-content\/uploads\/2019\/09\/ds3231_interrupt_100k-1-160x21.png 160w, https:\/\/msalamon.pl\/wp-content\/uploads\/2019\/09\/ds3231_interrupt_100k-1.png 1920w\" sizes=\"auto, (max-width: 1024px) 100vw, 1024px\" \/><\/a><\/figure>\n<\/div>\n\n\n<p>and 400 kHz<\/p>\n\n\n<div class=\"wp-block-image\">\n<figure class=\"aligncenter\"><a href=\"http:\/\/msalamon.pl\/wp-content\/uploads\/2019\/09\/ds3231_interrupt_400k-1.png\"><img loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"136\" src=\"http:\/\/msalamon.pl\/wp-content\/uploads\/2019\/09\/ds3231_interrupt_400k-1-1024x136.png\" alt=\"\" class=\"wp-image-1159\" srcset=\"https:\/\/msalamon.pl\/wp-content\/uploads\/2019\/09\/ds3231_interrupt_400k-1-1024x136.png 1024w, https:\/\/msalamon.pl\/wp-content\/uploads\/2019\/09\/ds3231_interrupt_400k-1-300x40.png 300w, https:\/\/msalamon.pl\/wp-content\/uploads\/2019\/09\/ds3231_interrupt_400k-1-768x102.png 768w, https:\/\/msalamon.pl\/wp-content\/uploads\/2019\/09\/ds3231_interrupt_400k-1-1536x204.png 1536w, https:\/\/msalamon.pl\/wp-content\/uploads\/2019\/09\/ds3231_interrupt_400k-1-24x3.png 24w, https:\/\/msalamon.pl\/wp-content\/uploads\/2019\/09\/ds3231_interrupt_400k-1-36x5.png 36w, https:\/\/msalamon.pl\/wp-content\/uploads\/2019\/09\/ds3231_interrupt_400k-1-160x21.png 160w, https:\/\/msalamon.pl\/wp-content\/uploads\/2019\/09\/ds3231_interrupt_400k-1.png 1920w\" sizes=\"auto, (max-width: 1024px) 100vw, 1024px\" \/><\/a><\/figure>\n<\/div>\n\n\n<p>Hmm\u2026 920 \u00b5s and 237 \u00b5s. Given that this code runs exactly every second, it\u2019s easy to calculate that the read at 100 kHz takes 0.92% of CPU time, and for 400 kHz only 0.23%. Come on, Salamon, what more do you want from it?<\/p>\n\n\n\n<p><a href=\"http:\/\/msalamon.pl\/wp-content\/uploads\/2019\/09\/ds3231_interrupt_400k.png\"><\/a>Notice that the CPU is idly waiting for the data to be received over I\u00b2C. This means there is still a small room for improvement thanks to DMA. The effect of my DMA code looks like this:<\/p>\n\n\n<div class=\"wp-block-image\">\n<figure class=\"aligncenter\"><a href=\"http:\/\/msalamon.pl\/wp-content\/uploads\/2019\/09\/ds3231_DMA_100k.png\"><img loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"137\" src=\"http:\/\/msalamon.pl\/wp-content\/uploads\/2019\/09\/ds3231_DMA_100k-1024x137.png\" alt=\"\" class=\"wp-image-1160\" srcset=\"https:\/\/msalamon.pl\/wp-content\/uploads\/2019\/09\/ds3231_DMA_100k-1024x137.png 1024w, https:\/\/msalamon.pl\/wp-content\/uploads\/2019\/09\/ds3231_DMA_100k-300x40.png 300w, https:\/\/msalamon.pl\/wp-content\/uploads\/2019\/09\/ds3231_DMA_100k-768x103.png 768w, https:\/\/msalamon.pl\/wp-content\/uploads\/2019\/09\/ds3231_DMA_100k-1536x206.png 1536w, https:\/\/msalamon.pl\/wp-content\/uploads\/2019\/09\/ds3231_DMA_100k-24x3.png 24w, https:\/\/msalamon.pl\/wp-content\/uploads\/2019\/09\/ds3231_DMA_100k-36x5.png 36w, https:\/\/msalamon.pl\/wp-content\/uploads\/2019\/09\/ds3231_DMA_100k-160x21.png 160w, https:\/\/msalamon.pl\/wp-content\/uploads\/2019\/09\/ds3231_DMA_100k.png 1920w\" sizes=\"auto, (max-width: 1024px) 100vw, 1024px\" \/><\/a><\/figure>\n<\/div>\n\n<div class=\"wp-block-image\">\n<figure class=\"aligncenter\"><a href=\"http:\/\/msalamon.pl\/wp-content\/uploads\/2019\/09\/ds3231_DMA_400k.png\"><img loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"135\" src=\"http:\/\/msalamon.pl\/wp-content\/uploads\/2019\/09\/ds3231_DMA_400k-1024x135.png\" alt=\"\" class=\"wp-image-1161\" srcset=\"https:\/\/msalamon.pl\/wp-content\/uploads\/2019\/09\/ds3231_DMA_400k-1024x135.png 1024w, https:\/\/msalamon.pl\/wp-content\/uploads\/2019\/09\/ds3231_DMA_400k-300x40.png 300w, https:\/\/msalamon.pl\/wp-content\/uploads\/2019\/09\/ds3231_DMA_400k-768x102.png 768w, https:\/\/msalamon.pl\/wp-content\/uploads\/2019\/09\/ds3231_DMA_400k-1536x203.png 1536w, https:\/\/msalamon.pl\/wp-content\/uploads\/2019\/09\/ds3231_DMA_400k-24x3.png 24w, https:\/\/msalamon.pl\/wp-content\/uploads\/2019\/09\/ds3231_DMA_400k-36x5.png 36w, https:\/\/msalamon.pl\/wp-content\/uploads\/2019\/09\/ds3231_DMA_400k-160x21.png 160w, https:\/\/msalamon.pl\/wp-content\/uploads\/2019\/09\/ds3231_DMA_400k.png 1920w\" sizes=\"auto, (max-width: 1024px) 100vw, 1024px\" \/><\/a><\/figure>\n<\/div>\n\n\n<p>100 kHz400 kHzNow there are two shorter segments during which the CPU is needed. The first is dispatching the DMA reception, the second is converting BCD to DEC and assigning to the struct variable. To calculate the CPU time, you need to sum them, which is simple. Thus, for 100 kHz the total is 301 \u00b5s, and for 400 kHz only 85 \u00b5s. Converted to % of CPU time, that\u2019s 0.3% and 0.08%, respectively.<\/p>\n\n\n\n<p>In both cases we have about a 3x gain. Not bad, right? An operation like reading the time should be as little burden on the system as possible. After all, it has more serious things to do.<\/p>\n\n\n\n<h1 class=\"wp-block-heading\"><figure><a href=\"https:\/\/sklep.msalamon.pl\/kategoria-produktu\/dev-boardy\/stm32-nucleo\/?utm_source=blog&amp;utm_medium=banner&amp;utm_campaign=ds3231&amp;utm_content=nucleo\"><img loading=\"lazy\" decoding=\"async\" src=\"http:\/\/msalamon.pl\/wp-content\/uploads\/2020\/07\/Nucleo-64-baner.jpg\" alt=\"\" width=\"1200\" height=\"400\" class=\"aligncenter wp-image-1593 size-full\" srcset=\"https:\/\/msalamon.pl\/wp-content\/uploads\/2020\/07\/Nucleo-64-baner.jpg 1200w, https:\/\/msalamon.pl\/wp-content\/uploads\/2020\/07\/Nucleo-64-baner-300x100.jpg 300w, https:\/\/msalamon.pl\/wp-content\/uploads\/2020\/07\/Nucleo-64-baner-1024x341.jpg 1024w, https:\/\/msalamon.pl\/wp-content\/uploads\/2020\/07\/Nucleo-64-baner-768x256.jpg 768w\" sizes=\"auto, (max-width: 1200px) 100vw, 1200px\"><\/a><\/figure><\/h1>\n\n\n\n<h2 class=\"wp-block-heading\">Summary<\/h2>\n\n\n\n<p>I hope that if you weren\u2019t already, you\u2019re now convinced of the high accuracy of the RTC clock that is the <a href=\"https:\/\/sklep.msalamon.pl\/produkt\/modul-ds3231-rtc-24c32-eeprom-z-gniazdem-na-baterie\/?utm_source=blog&amp;utm_medium=article&amp;utm_campaign=ds3231&amp;utm_content=Text\">DS3231<\/a>. Although I feel that a comparison with other chips in terms of timekeeping accuracy would still be useful. I already have an idea \ud83d\ude42<\/p>\n\n\n\n<p>Admit it too, I\u2019ve given you another reason to use DMA. Even in such \u2013 seemingly \u2013 trivial situations it\u2019s worth doing.<\/p>\n\n\n\n<p><strong>And what do you think about such an RTC?<\/strong> Is it worth buying one? Surprisingly, modules are sometimes cheaper than other, less accurate chips that need several additional components around them. <strong>Check out the RTC modules <a href=\"https:\/\/sklep.msalamon.pl\/produkt\/modul-ds3231-rtc-24c32-eeprom-z-gniazdem-na-baterie\/?utm_source=blog&amp;utm_medium=article&amp;utm_campaign=ds3231&amp;utm_content=Text\">in my store<\/a>. By buying from me, you support the development of the blog and you get a cool sticker, e.g., for your laptop<\/strong> \ud83d\ude09<\/p>\n\n\n\n<p>You\u2019ll find the full project along with the library on my GitHub as usual: <a href=\"https:\/\/github.com\/lamik\/DS3231_RTC_STM32_HAL\" target=\"_blank\" rel=\"noopener\">link<\/a><\/p>\n\n\n\n<p><span>If you noticed any error, disagree with something, would like to add something important, or simply want to discuss the topic, leave a comment. Remember that the discussion should be polite and in accordance with the rules of the Polish language.<\/span><\/p>\n\n\n<div class=\"kk-star-ratings kksr-auto kksr-align-left kksr-valign-bottom\"\n    data-payload='{&quot;align&quot;:&quot;left&quot;,&quot;id&quot;:&quot;4305&quot;,&quot;slug&quot;:&quot;default&quot;,&quot;valign&quot;:&quot;bottom&quot;,&quot;ignore&quot;:&quot;&quot;,&quot;reference&quot;:&quot;auto&quot;,&quot;class&quot;:&quot;&quot;,&quot;count&quot;:&quot;0&quot;,&quot;legendonly&quot;:&quot;&quot;,&quot;readonly&quot;:&quot;&quot;,&quot;score&quot;:&quot;0&quot;,&quot;starsonly&quot;:&quot;&quot;,&quot;best&quot;:&quot;5&quot;,&quot;gap&quot;:&quot;0&quot;,&quot;greet&quot;:&quot;&quot;,&quot;legend&quot;:&quot;0\\\/5 - (0 votes)&quot;,&quot;size&quot;:&quot;24&quot;,&quot;title&quot;:&quot;Insanely Accurate DS3231 RTC on STM32&quot;,&quot;width&quot;:&quot;0&quot;,&quot;_legend&quot;:&quot;{score}\\\/{best} - ({count} {votes})&quot;,&quot;font_factor&quot;:&quot;1.25&quot;}'>\n            \n<div class=\"kksr-stars\">\n    \n<div class=\"kksr-stars-inactive\">\n            <div class=\"kksr-star\" data-star=\"1\" style=\"padding-right: 0px\">\n            \n\n<div class=\"kksr-icon\" style=\"width: 24px; height: 24px;\"><\/div>\n        <\/div>\n            <div class=\"kksr-star\" data-star=\"2\" style=\"padding-right: 0px\">\n            \n\n<div class=\"kksr-icon\" style=\"width: 24px; height: 24px;\"><\/div>\n        <\/div>\n            <div class=\"kksr-star\" data-star=\"3\" style=\"padding-right: 0px\">\n            \n\n<div class=\"kksr-icon\" style=\"width: 24px; height: 24px;\"><\/div>\n        <\/div>\n            <div class=\"kksr-star\" data-star=\"4\" style=\"padding-right: 0px\">\n            \n\n<div class=\"kksr-icon\" style=\"width: 24px; height: 24px;\"><\/div>\n        <\/div>\n            <div class=\"kksr-star\" data-star=\"5\" style=\"padding-right: 0px\">\n            \n\n<div class=\"kksr-icon\" style=\"width: 24px; height: 24px;\"><\/div>\n        <\/div>\n    <\/div>\n    \n<div class=\"kksr-stars-active\" style=\"width: 0px;\">\n            <div class=\"kksr-star\" style=\"padding-right: 0px\">\n            \n\n<div class=\"kksr-icon\" style=\"width: 24px; height: 24px;\"><\/div>\n        <\/div>\n            <div class=\"kksr-star\" style=\"padding-right: 0px\">\n            \n\n<div class=\"kksr-icon\" style=\"width: 24px; height: 24px;\"><\/div>\n        <\/div>\n            <div class=\"kksr-star\" style=\"padding-right: 0px\">\n            \n\n<div class=\"kksr-icon\" style=\"width: 24px; height: 24px;\"><\/div>\n        <\/div>\n            <div class=\"kksr-star\" style=\"padding-right: 0px\">\n            \n\n<div class=\"kksr-icon\" style=\"width: 24px; height: 24px;\"><\/div>\n        <\/div>\n            <div class=\"kksr-star\" style=\"padding-right: 0px\">\n            \n\n<div class=\"kksr-icon\" style=\"width: 24px; height: 24px;\"><\/div>\n        <\/div>\n    <\/div>\n<\/div>\n                \n\n<div class=\"kksr-legend\" style=\"font-size: 19.2px;\">\n            <span class=\"kksr-muted\"><\/span>\n    <\/div>\n    <\/div>\n","protected":false},"excerpt":{"rendered":"<p>Not everyone may know this, but real-time clocks, or RTCs, are only as accurate as their clock source. Most often, such systems are driven by a 32.768 kHz frequency. STM32 has a built-in RTC, but exactly the same rule applies to its accuracy. Generally, what STM32 offers as an internal [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":3283,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"footnotes":"","_links_to":"","_links_to_target":""},"categories":[160],"tags":[175,178,176,174,177],"class_list":["post-4305","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-stm32","tag-electronics","tag-kursstm32","tag-programming","tag-stm32","tag-stm32cubemx"],"_links":{"self":[{"href":"https:\/\/msalamon.pl\/en\/wp-json\/wp\/v2\/posts\/4305","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/msalamon.pl\/en\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/msalamon.pl\/en\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/msalamon.pl\/en\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/msalamon.pl\/en\/wp-json\/wp\/v2\/comments?post=4305"}],"version-history":[{"count":3,"href":"https:\/\/msalamon.pl\/en\/wp-json\/wp\/v2\/posts\/4305\/revisions"}],"predecessor-version":[{"id":4412,"href":"https:\/\/msalamon.pl\/en\/wp-json\/wp\/v2\/posts\/4305\/revisions\/4412"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/msalamon.pl\/en\/wp-json\/wp\/v2\/media\/3283"}],"wp:attachment":[{"href":"https:\/\/msalamon.pl\/en\/wp-json\/wp\/v2\/media?parent=4305"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/msalamon.pl\/en\/wp-json\/wp\/v2\/categories?post=4305"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/msalamon.pl\/en\/wp-json\/wp\/v2\/tags?post=4305"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}