Working with STM32F7 and I2C: Write Mode

In this guide, we shall see how to configure I2C in STM32F7 to write to a specific register of an I2C device (DS3231 in this case).

In this guide, we will cover the following:

  • I2C write mode.
  • I2C write code.
  • Code.
  • Demo.

1. I2C write mode:

In order for the I2C peripheral to write to slave device, it starts with sending the slave address with LSB (least significant bit) set to zero as shown in the picture below:

Then when the slave acknowledge that it received the address by setting the SDA line to low indicates that the slave detected the address and it is ready to receive the data.

After the acknowledgment, the master ( STM32F7 in this case) starts sending the data, the fist data which is 0x00 indicates the memory address (0x00 hex is for the seconds register), the slave acknowledge that it received data by setting the SDA line to low again and the process repeats until the stop condition is generated by setting the SCL to high followed by SDA to high.

2. I2C write code:

In this guide, we shall introduce two method of writing, one involves a memory address and another without.

For writing with memory address.

We start off by declaring a function that takes four arguments:

  • Slave address.
  • Memory address.
  • Pointer to array that holds the data to be sent.
  • Number of bytes to be sent.
void i2c_write_memory(uint8_t slav_add, uint8_t memadd, uint8_t *data, uint8_t length)

Inside the function, we declare an array with size of length +1 as following:

uint8_t send_arr[length+1];

Then the first element of the array shall be the memory address:

send_arr[0]=memadd;

The next we shall fill the array with the data to be sent as following:

	for (int i=1;(i<length+1);i++)
		{
		send_arr[i]=*data++;
		}

Next we start configuring the I2C to transmit the data.

First enable I2C as following:

	/*Enable I2C*/
	I2C1->CR1 |=I2C_CR1_PE;

Set the slave address as following:

I2C1->CR2=(slav_add<<1);

Set the addressing mode to 7-bit:

I2C1->CR2&=~I2C_CR2_ADD10;

Then we set number of transfer to be length+1:

	/*Set number to transfer to length+1 for write operation*/
	I2C1->CR2|=((length+1)<<I2C_CR2_NBYTES_Pos);

Then set the mode to be write mode as following:

	/*Set the mode to write mode*/
	I2C1->CR2&=~I2C_CR2_RD_WRN;

Then we generate a start as following:

 	/*Generate start*/
	I2C1->CR2|=I2C_CR2_START;

Then declare a local variable:

int i=0;

Then we wait until the stop is generated and inside the while loop, we shall transmit the data:

	while(!(I2C1->ISR & I2C_ISR_STOPF))
	{
		/*Check if TX buffer is empty*/
		if(I2C1->ISR & I2C_ISR_TXE)
		{
			/*send memory address*/
			I2C1->TXDR =send_arr[i] ;
			i++;
		}
	}

Finally, we disable the i2c peripheral:

	/*Disable I2C*/
	I2C1->CR1 &=~I2C_CR1_PE;

For writing without memory address:

void i2c_write(uint8_t slav_add, uint8_t *data, uint8_t length)
	{
	/*Enable I2C*/
		I2C1->CR1 |=I2C_CR1_PE;
		/*Set slave address*/
		I2C1->CR2=(slav_add<<1);
		/*7-bit addressing*/
		I2C1->CR2&=~I2C_CR2_ADD10;
		/*Set number to transfer to length for write operation*/
		I2C1->CR2|=(length<<I2C_CR2_NBYTES_Pos);
		/*Set the mode to write mode*/
		I2C1->CR2&=~I2C_CR2_RD_WRN;
		/*hardware end*/
		I2C1->CR2|=I2C_CR2_AUTOEND;
		/*Generate start*/
		I2C1->CR2|=I2C_CR2_START;
		int i=0;
		while(!(I2C1->ISR & I2C_ISR_STOPF))
		{
			/*Check if TX buffer is empty*/
			if(I2C1->ISR & I2C_ISR_TXE)
			{
				/*send memory address*/
				I2C1->TXDR =*data++ ;
			}
		}
		/*Disable I2C*/
		I2C1->CR1 &=~I2C_CR1_PE;

	}

In our main loop, we shall write random to ds3231:

#include "stm32f7xx.h"
#include "uart.h"
#include "i2c.h"
#include "stdlib.h"
#define slave_add (0x68)
uint8_t data[3];
uint8_t data_send[3];
int bcd_to_decimal(unsigned char x) {
    return x - 6 * (x >> 4);
}

int main()
	{
	uart3_tx_init();
	i2c_init(0x00303D5B);
	printf("init\r\n");
	for (int i=0;i<100000;i++);
	while(1)
		{
		i2_read(slave_add,0x0,data,3);
		for (volatile int i=0;i<3;i++)
			{
			data[i]=bcd_to_decimal(data[i]);
			}

		if(data[0]==5){
		data_send[0]=0x00;
		data_send[1]=(rand() % 5);
		data_send[2]=(rand() % 5);
		i2c_write_memory(slave_add,0x00,data_send,3);
		}

		printf("rtc data %d %d %d\r\n",data[2],data[1],data[0]);
		for (int i=0;i<100000;i++);
		}

	}

3. Code:

You may download from here:

4. Demo:

Happy coding 🙂

7 Comments

  • Simon Morrison Posted April 29, 2022 1:36 pm

    I have found that it’s very helpful to be able to code this sort of stuff bare metal because often the drivers in the manufacturers’ APIs are lacking in capabilities or have weak timing.

    • Husamuldeen Posted April 29, 2022 7:14 pm

      Indeed

  • Ganeshaperumal.D Posted June 27, 2023 9:21 am

    Sir, i am used this program for one i2c based Digital Potentiometer, it is not working, even simple print also

    • Husamuldeen Posted June 27, 2023 3:36 pm

      Hi,
      double check the address.

      • Ganeshaperumal.D Posted June 28, 2023 5:18 am

        its working perfectly with Arduino sir, so no issues in the address

  • Ganeshaperumal.D Posted June 27, 2023 12:19 pm

    This code is not working for me. can you give your contact details to me please

Add Comment

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