Working with STM32 and I2C: Reading single Byte

In the previous guide (here), we saw how to use I2C to scan the bus for the slave devices address. In this guide, we shall see how to read a single byte using I. In this guide, we shall use DS3231.

In this guide, we shall cover the following:

  • DS3231
  • Connection diagram
  • Read Byte using I2C code
  • Code
  • Results

1. DS3231

The DS3231 is a low-cost, extremely accurate I 2 C real-time clock (RTC) with an integrated temperature compensated crystal oscillator (TCXO) and crystal. The device incorporates a battery input, and maintains accurate timekeeping when main power to the device is interrupted. The integration of the crystal resonator enhances the long-term accuracy of the device as well as reduces the piece-part count in a manufacturing line.

The RTC maintains seconds, minutes, hours, day, date, month, and year information. The date at the end of the month is automatically adjusted for months with fewer than 31 days, including corrections for leap year. The clock operates in either the 24-hour or 12-hour format with an AM/PM indicator. Two programmable time-of-day alarms and a programmable square-wave output are provided. Address and data are transferred serially through an I 2 C bidirectional bus.

DS3231

The module can work on either 3.3 or 5 V which makes it suitable for many development platforms or microcontrollers. The battery input is 3V and a typical CR2032 3V battery can power the module and maintain the information for more than a year.

2. Connection diagram

We shall connect the DS3231 as following:

3. I2C read byte function

For how to initialize clock and GPIO for I2C, please refer to this topic (here)

We start the function name and the arguments. The functions takes three arguments as following:

  • Slave address
  • Memory address within the slave
  • Pointer to the variable to store the read data
char i2c_readByte(char saddr,char maddr, char *data)

Then we declare volatile variable to clear the status register

volatile int tmp;

Then we wait until the I2C bus is free as following

while(I2C1->SR2&I2C_SR2_BUSY){;}

After the bus being free, we send the start condition and wait for start flag to set as following:

I2C1->CR1|=I2C_CR1_START;
while(!(I2C1->SR1&I2C_SR1_SB)){;}

Now we send the slave address shifted to left by 1

I2C1->DR=saddr<<1;

Wait for address flag to set and clear SR2 (status register 2)

while(!(I2C1->SR1&I2C_SR1_ADDR)){;}
tmp=I2C1->SR2;

Now, we wait until data register is empty and then send the memory address as following:

while(!(I2C1->SR1&I2C_SR1_TXE)){;}
I2C1->DR=maddr;

After that, we shall wait to data register to be empty as following

while(!(I2C1->SR1&I2C_SR1_TXE)){;}

So far, we are only writing to the slave, now we need to read the contents of the memory location.

To read from the slave, we send start condition again (called restart) and wait for start flag to set as following:

I2C1->CR1|=I2C_CR1_START;
while(!(I2C1->SR1&I2C_SR1_SB)){;}	

Now, we send the slave address shifted by 1 to left with read bit (bit 0 is set to 1) and wait to address to set

I2C1->DR=saddr<<1|1;

Wait for the address flag to set

while(!(I2C1->SR1&I2C_SR1_ADDR)){;}

Since we are reading single byte, we need to disable acknowledgement bit as following:

I2C1->CR1&=~I2C_CR1_ACK;

clear status register 2 (SR2) as following

tmp =I2C1->SR2;

after this, we generate stop condition as following

I2C1->CR1|=I2C_CR1_STOP;

Now wait for data register to be empty as following:

while(!(I2C1->SR1&I2C_SR1_RXNE)){;}

after this, we store the read data to the assigned variable as following:

*data++=I2C1->DR;

and finally return 0

return 0;

Hence, the entire function is as following:

char i2c_readByte(char saddr,char maddr, char *data)
{
volatile int tmp;
while(I2C1->SR2&I2C_SR2_BUSY){;}
I2C1->CR1|=I2C_CR1_START;
while(!(I2C1->SR1&I2C_SR1_SB)){;}
I2C1->DR=saddr<<1;
while(!(I2C1->SR1&I2C_SR1_ADDR)){;}
tmp=I2C1->SR2;
while(!(I2C1->SR1&I2C_SR1_TXE)){;}
I2C1->DR=maddr;
while(!(I2C1->SR1&I2C_SR1_TXE)){;}
I2C1->CR1|=I2C_CR1_START;
while(!(I2C1->SR1&I2C_SR1_SB)){;}	
I2C1->DR=saddr<<1|1;
while(!(I2C1->SR1&I2C_SR1_ADDR)){;}
I2C1->CR1&=~I2C_CR1_ACK;
tmp =I2C1->SR2;
I2C1->CR1|=I2C_CR1_STOP;
while(!(I2C1->SR1&I2C_SR1_RXNE)){;}
*data=I2C1->DR;
return 0;
}

Since the RTC returns value in BCD format, we shall use this small function to convert BCD to decimal which can be read by human

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

In main program we shall declare variable called seconds as following

int seconds;

then in the loop:

i2c_readByte(0x68,0x00,&seconds);
			seconds=bcd_to_decimal(seconds);
			sprintf(string,"Register 0x00 value is %d \r\n", seconds);
			UART_Write_String(string);

Hence the main.c will be as following:

#include "i2c.h"
#include "uart.h"
int seconds;
char string[40];

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

int main()
	{
		USART2_Init();
		i2c_init();
		i2c_bus_scan();
	while(1)
		
		{
			i2c_readByte(0x68,0x00,&seconds);
			seconds=bcd_to_decimal(seconds);
			sprintf(string,"Register 0x00 value is %d \r\n", seconds);
			UART_Write_String(string);
		}

	}
	

4.Code:

You may download the entire project from here (based on keil uVision):

5. Results:

After compile and loading the code unto your board, open your serial terminal program and set the baud rate to 115200 and you shall get something like this and the value shall be changed each second

Happy coding 🙂

Add Comment

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