Getting Started with STM32G0 and STM32CubeIDE: I2C Full Duplex

In this guide, we shall take a look at the I2C protocol and how to use HAL API to get the RTC data from DS3231 and write data to DS3231.

In this guide, we shall cover the following:

  • I2C Protocol.
  • DS3231 Module Connection with STM32G070.
  • Driver Development.
  • Results.

1. I2C Protocol:

I2C or Inter Integrated Circuit is type of synchronous serial communication that capable to communicate with up to 127 slave devices as show in figure below.

Like UART communication, I2C only uses two wires to transmit data between devices:

  • SDA (Serial Data):  The line for the master and slave to send and receive data. 
  • SCL (Serial Clock): The line that carries the clock signal.

1.2: HOW I2C WORKS

With I2C, data is transferred in messages. Messages are broken up into frames of data. Each message has an address frame that contains the binary address of the slave, and one or more data frames that contain the data being transmitted. The message also includes start and stop conditions, read/write bits, and ACK/NACK bits between each data frame:

Introduction to I2C - Message, Frame, and Bit

Start Condition: The SDA line switches from a high voltage level to a low voltage level before the SCL line switches from high to low.

Stop Condition: The SDA line switches from a low voltage level to a high voltage level after the SCL line switches from low to high.

Address Frame: A 7 or 10 bit sequence unique to each slave that identifies the slave when the master wants to talk to it.

Read/Write Bit: A single bit specifying whether the master is sending data to the slave (low voltage level) or requesting data from it (high voltage level).

ACK/NACK Bit: Each frame in a message is followed by an acknowledge/no-acknowledge bit. If an address frame or data frame was successfully received, an ACK bit is returned to the sender from the receiving device.

For more information and detailed explanations refer to this NXP documentation (here).

2. DS3231 Module Connection:

Since this guide uses DS3231 module as shown in picture below:

This module has the required parts to make I2C working like the external Pull-up resistors etc.

Hence, the connection as following:

STM32G070DS3231
5VVcc
GNDGND
PB6SCL
PB7SDA

Note: It is also possible to run the module from 3V3 if you want.

3. Driver Development:

We start off by creating new project with name of I2C_DS3231. For how to create project, please refer to this guide.

From pinout and configuration, then communication, then I2C1, enable the peripheral and set PB6 and PB7 to I2C pins as following:

Keep the I2C configuration as is.

Also, enable the UART to print some data, please refer to this guide for UART.

Save the project and that shall generate the project.

In main.c

In user code begin includes, add the following header files:

/* USER CODE BEGIN Includes */
#include "stdio.h"
#include "stdlib.h"
/* USER CODE END Includes */

The stdio for printf functionality and stdlib for random operation (useful later).

In user code begin PD, declare the address of the DS3231 as following:

#define DS3231_Addr (0x68<<1)

The address can be obtain from read/write figures in the datasheet of the DS3231:

We also need to shift the address to left by for the read/write bit which is bit0 when the master transmit the read/write request.

In user code begin PV, declare an array of 3 elements to hold the RTC data as following:

/* USER CODE BEGIN PV */
uint8_t rtc_data[3];
/* USER CODE END PV */

In user code begin 0, declare the following two functions:

int __io_putchar(int ch)
{
	HAL_UART_Transmit(&huart2, &ch, 1, 5);

	return ch;
}


int bcd_to_decimal(unsigned char x) {
    return x - 6 * (x >> 4);
}

First one shall retarget printf to print over UART.

Second one will convert the BCD to decimal value which is readable by human.

In user code begin 3 in while 1 loop:

First read the I2C memory location as following:

 HAL_I2C_Mem_Read(&hi2c1, DS3231_Addr, 0x00, 0x01, rtc_data, 3, 100);

This function shall take 7 parameters as following:

  • Pointer to a I2C_HandleTypeDef structure (hi2c1).
  • Target device address (DS3231_Addr).
  • Internal memory address (0x00 the starting address of the seconds).
  • Size of internal memory address(0x01 which is 1).
  • Pointer to data buffer (rtc_data).
  • Amount of data to be read (size of the buffer which is 3).
  • Timeout duration (100 millisecond).

After reading the data, convert the BCD to decimal:

	  for (int i=0;i<3;i++)
	  {
		  rtc_data[i]=bcd_to_decimal(rtc_data[i]);
	  }

Print the data:

printf("RTC DATA H=%d M=%d S=%d\r\n",rtc_data[2],rtc_data[1],rtc_data[0]);

Next, check if the seconds part is 5 seconds ad following:

if(rtc_data[0]==5)

If yes, fill the buffer with random numbers for minutes and hours and reset seconds to 0 as following:

rtc_data[0]=0x00;
rtc_data[1]=(rand() % 5);
rtc_data[2]=(rand() % 5);

Write the new data using HAL_I2C_Mem_Write as following:

HAL_I2C_Mem_Write(&hi2c1, DS3231_Addr, 0x00, 0x01, rtc_data, 3, 100);

It takes the same parameters as the read function.

Finally delay by 200ms.

HAL_Delay(200);

Save the project, build and run it on your MCU.

4. Results:

Open your favourite terminal applicaton, set the buadrate to be 115200 and you should get the following:

Note when second part becomes 5, new data for hours and minutes are written.

Happy coding 😉

Add Comment

Your email address will not be published. Required fields are marked *