Getting Started with STM32F103: I2C Read Byte

In the previous guide (here), we took a look how to write a single byte with memory address to a slave device (DS3231) and successfully wrote a byte to the seconds part of the RTC. In this guide, we shall combine the write function with read function to reset the seconds part of RTC each five seconds.

In this guide, we shall cover the following:

  • I2C Read with memory address.
  • I2C Read without memory address.
  • Code.
  • Results.

1. I2C Read with memory address:

From the previous source code, open i2c.c source file and declare the following function:

void i2c1_readMemoryByte(uint8_t saddr,uint8_t maddr, uint8_t *data)

The function takes three arguments as following:

  • Slave address.
  • Memory address to be read.
  • Pointer to the data to be read.

Within the function, we start off by waiting until the bus is free by ready busy bit from SR1 register:

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

After the bus is freed, send the start condition by setting bit start in CR1 to 1 as following:

I2C1->CR1|=I2C_CR1_START;                 	/*generate start*/

Then send the slave address shifted to left by 1 bit:

The reason behind the shifting is the address takes the bit1 to bit7 from the data register and bit0 is used for read/write operation. When bit0 is 0, it the operation is write operation and when it is 1, it means read operation.

Here we want to send the memory address to be read.

2C1->DR = saddr<< 1;                 	 	/* Send slave address with write operation*/

Wait until the address is matched by checking ADDR bit in SR1 register:

while(!(I2C1->SR1&I2C_SR1_ADDR)){;}      	/*wait until address flag is set*/

Clear the SR2 register (recommended):

(void)I2C1->SR2; 						/*clear SR2 by reading it */

Wait until transmit buffer is empty by checking TXE bit in SR1 register:

while(!(I2C1->SR1&I2C_SR1_TXE)){;}       /*Wait until Data register empty*/

Send the memory address:

I2C1->DR=maddr;

Wait until transmit buffer is empty:

while(!(I2C1->SR1&I2C_SR1_TXE)){;}       /*Wait until Data register empty*/

In order to change the direction from write to read, we need to send restart condition followed by sending the slave address in read mode.

To generate the restart condition, generate another start condition as following:

I2C1->CR1|=I2C_CR1_START;

Wait until the start condition is generated:

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

Send the salve address with read operation:

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

Wait until the address is matched:

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

Disable the acknowledgment bit in CR1 register:

I2C1->CR1&=~I2C_CR1_ACK;

Clear SR2 register once again:

(void)I2C1->SR2;

Generate stop condition:

I2C1->CR1|=I2C_CR1_STOP;

Wait until receiver is not empty bit in SR1 register:

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

Finally update the value which is passed as reference:

*data=I2C1->DR;

Hence, the entire function as following:

void i2c1_readMemoryByte(uint8_t saddr,uint8_t maddr, uint8_t *data)
{
	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)){;}
	(void)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;
	(void)I2C1->SR2;
	I2C1->CR1|=I2C_CR1_STOP;
	while(!(I2C1->SR1&I2C_SR1_RXNE)){;}
	*data=I2C1->DR;
}

And also update the header file as following:

void i2c1_readMemoryByte(uint8_t saddr,uint8_t maddr, uint8_t *data);

2. I2C read without memory address:

In order to read from slave device without memory, it is as simple as following:

void i2c1_readByte(uint8_t saddr, uint8_t *data)
{
	while(I2C1->SR2&I2C_SR2_BUSY){;}
	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;
	(void)I2C1->SR2;
	I2C1->CR1|=I2C_CR1_STOP;
	while(!(I2C1->SR1&I2C_SR1_RXNE)){;}
	*data=I2C1->DR;

}

Also, update the header file as following:

void i2c1_readByte(uint8_t saddr, uint8_t *data);

3. Code:

You may download the entire code from here:

4. Results:

To test both write and read functionality, the main.c is as following:

#include "stm32f1xx.h"
#include "i2c.h"
#include "uart.h"
#include "stdio.h"

uint8_t seconds;

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



int main(void)
{
	uart2_init();
	i2c_init();
	i2c1_scan_bus();
	while(1)
	{
		i2c1_readMemoryByte(0x68,0x00,&seconds);
		seconds=bcd_to_decimal(seconds);

		if(seconds==5){i2c1_MemoryWrite_Byte(0x68,0x00,00);}

		printf("Register 0x00 value is %d \r\n", seconds);
		for (int i=0;i<100000;i++);
	}
}

Once you upload the code to STM32F103C8, connect an FTDI TTL-USB converter where PA2 is connected to RX pin on the FTDI, set buadrate to 115200 and you should see something like this:

Happy coding 🙂

Add Comment

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