Working with STM32 and AT24C32 EEPROM Part2: Read/Write Multibyte

In part one of this guide series, we took a look at the EEPROM and type of memory. Also we took a look at AT24C32 and it’s features and how to read and write single byte of data.

In part two, we shall see how develop a driver that will allow use to read/write to/from multiple memory locations within AT24C32.

In this guide, we shall cover the following:

  • Sequential write process.
  • Sequential read process.
  • Main code.
  • Results.

1. Sequential Write Process:

A page write is initiated the same way as a byte write, but the microcontroller does not send a stop condition after the first data word is clocked in. Instead, after the EEPROM acknowledges receipt of the first data word, the microcontroller can transmit up to 31 more data words. The EEPROM will respond with a zero after each data word received. The microcontroller must terminate the page write sequence with a stop condition (refer to figure below).

  1. Send start condition and the slave address with the R/W bit set to WRITE.
  2. Wait for an acknowledge
  3. Send the first word address
  4. Wait for ACK
  5. Send second word address
  6. Wait for ACK
  7. Send one byte of data
  8. Wait for ACK
  9. Repeat steps 8 & 9 until we are ready to stop sending bytes or we’ve reached our 32 byte limit
  10. Send STOP condition

Hence, the code as following:

void AT24C32_WritePage(uint16_t address, uint8_t *data, uint8_t len)
{
	while (I2C1->SR2 & I2C_SR2_BUSY);           //wait until bus not busy

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

	while (!(I2C1->SR1 & I2C_SR1_SB)){;}	    //wait until start is generated

	I2C1->DR = AT24C32Address<< 1;              // Send slave address

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

	(void) I2C1->SR2; 						      //Clear SR2

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

	I2C1->DR=address>>8;						//Send MSB of the address

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

	I2C1->DR=address&0x0F;						// send LSB of the address

	for (int i=0;i<len;i++)
	{
		while(!(I2C1->SR1&I2C_SR1_TXE)){;}			//Wait until Data register empty

		I2C1->DR = *data++; 						//Write the data to the EEPROM
	}
	while (!(I2C1->SR1 & I2C_SR1_BTF));      	//wait until transfer finished

	I2C1->CR1 |=I2C_CR1_STOP;					//Generate Stop

}

2. Sequential Read Process:

Sequential reads are initiated by either a current address read or a random address read. After the microcontroller receives a data word, it responds with an acknowledge. As long as the EEPROM receives an acknowledge, it will continue to increment the data word address and serially clock out sequential data words. When the memory address limit is reached, the data word address will “roll over” and the sequential read will continue. The sequential read operation is terminated when the microcontroller does not respond with a zero but does generate a following stop condition (refer to Figure below).

Explained with words, that will look like this:

  1. Send start condition and the slave address with the R/W bit set to WRITE.
  2. Wait for an acknowledge
  3. Send the first word address
  4. Wait for ACK
  5. Send second word address
  6. Wait for ACK
  7. Send another start condition and the slave address with the R/W bit set to READ (Wire.requestFrom does this for us as explained earlier)
  8. Wait for ACK
  9. Receive the byte of data from the AT24cxx
  10. Send ACK to AT24cxx
  11. Repeat steps 9 & 10 until we are finished reading as many bytes as we want to
  12. Send a NACK. This tells the AT24cxx to stop sending us data
  13. Send a STOP condition to end communication.

Hence, the read sequence as following:

void AT24C32_ReadPage(uint16_t address, uint8_t *data, uint8_t len)
{
	while (I2C1->SR2 & I2C_SR2_BUSY);           //wait until bus not busy

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

	while (!(I2C1->SR1 & I2C_SR1_SB)){;}	    //wait until start is generated

	I2C1->DR = AT24C32Address<< 1;              // Send slave address

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

	(void) I2C1->SR2; 						      //Clear SR2

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

	I2C1->DR=address>>8;						//Send MSB of the address

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

	I2C1->DR=address&0x0F;						// send LSB of the address

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

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

	while (!(I2C1->SR1 & I2C_SR1_SB)){;}	    //wait until start is generated

	I2C1->DR = (AT24C32Address<< 1)|1;          // Send slave address with read operation

	while(!(I2C1->SR1&I2C_SR1_ADDR)){;}			//Wait for address flag to set

	(void) I2C1->SR2; 						      //Clear SR2

	I2C1->CR1|=I2C_CR1_ACK;						//Enable acknowledgment

	while(len > 0U)
	{
		/*if one byte*/
		if(len == 1U)
		{
			/* Disable Acknowledge */
			I2C1->CR1 &= ~I2C_CR1_ACK;

			/* Generate Stop */
			I2C1->CR1 |= I2C_CR1_STOP;

			/* Wait for RXNE flag set */
			while (!(I2C1->SR1 & I2C_SR1_RXNE)){}

			/* Read data from DR */
			*data++ = I2C1->DR;
			break;
		}
		else
		{
			/* Wait until RXNE flag is set */
			while (!(I2C1->SR1 & I2C_SR1_RXNE)){}

			/* Read data from DR */
			(*data++) = I2C1->DR;

			len--;
		}
	}

}

Hence, the updated source file as following:

#include "AT24C32.h"

#include "stm32f4xx.h"

#define AT24C32Address 0x57



void AT24C32_ReadByte(uint16_t address, uint8_t *data)
{
	while (I2C1->SR2 & I2C_SR2_BUSY);           //wait until bus not busy

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

	while (!(I2C1->SR1 & I2C_SR1_SB)){;}	    //wait until start is generated

	I2C1->DR = AT24C32Address<< 1;              // Send slave address

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

	(void) I2C1->SR2; 						      //Clear SR2

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

	I2C1->DR=address>>8;						//Send MSB of the address

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

	I2C1->DR=address&0x0F;						// send LSB of the address

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


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

	while (!(I2C1->SR1 & I2C_SR1_SB)){;}	    //wait until start is generated

	I2C1->DR = (AT24C32Address<< 1)|1;          // Send slave address with read operation

	while(!(I2C1->SR1&I2C_SR1_ADDR)){;}			//Wait for address flag to set

	I2C1->CR1&=~I2C_CR1_ACK;					//Disable acknowledgment

	(void)I2C1->SR2;							// Clear SR2

	I2C1->CR1|=I2C_CR1_STOP;					// Generate stop condition

	while(!(I2C1->SR1&I2C_SR1_RXNE)){;}			//Wait for receiver buffer to be filled

	*data=I2C1->DR;								//Store the I2C bus.

}


void AT24C32_WriteByte(uint16_t address, uint8_t data)
{
	while (I2C1->SR2 & I2C_SR2_BUSY);           //wait until bus not busy

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

	while (!(I2C1->SR1 & I2C_SR1_SB)){;}	    //wait until start is generated

	I2C1->DR = AT24C32Address<< 1;              // Send slave address

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

	(void) I2C1->SR2; 						      //Clear SR2

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

	I2C1->DR=address>>8;						//Send MSB of the address

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

	I2C1->DR=address&0x0F;						// send LSB of the address

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

	I2C1->DR = data; 							//Write the data to the EEPROM

	while (!(I2C1->SR1 & I2C_SR1_BTF));      	//wait until transfer finished

	I2C1->CR1 |=I2C_CR1_STOP;					//Generate Stop

}


void AT24C32_ReadPage(uint16_t address, uint8_t *data, uint8_t len)
{
	while (I2C1->SR2 & I2C_SR2_BUSY);           //wait until bus not busy

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

	while (!(I2C1->SR1 & I2C_SR1_SB)){;}	    //wait until start is generated

	I2C1->DR = AT24C32Address<< 1;              // Send slave address

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

	(void) I2C1->SR2; 						      //Clear SR2

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

	I2C1->DR=address>>8;						//Send MSB of the address

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

	I2C1->DR=address&0x0F;						// send LSB of the address

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

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

	while (!(I2C1->SR1 & I2C_SR1_SB)){;}	    //wait until start is generated

	I2C1->DR = (AT24C32Address<< 1)|1;          // Send slave address with read operation

	while(!(I2C1->SR1&I2C_SR1_ADDR)){;}			//Wait for address flag to set

	(void) I2C1->SR2; 						      //Clear SR2

	I2C1->CR1|=I2C_CR1_ACK;						//Enable acknowledgment

	while(len > 0U)
	{
		/*if one byte*/
		if(len == 1U)
		{
			/* Disable Acknowledge */
			I2C1->CR1 &= ~I2C_CR1_ACK;

			/* Generate Stop */
			I2C1->CR1 |= I2C_CR1_STOP;

			/* Wait for RXNE flag set */
			while (!(I2C1->SR1 & I2C_SR1_RXNE)){}

			/* Read data from DR */
			*data++ = I2C1->DR;
			break;
		}
		else
		{
			/* Wait until RXNE flag is set */
			while (!(I2C1->SR1 & I2C_SR1_RXNE)){}

			/* Read data from DR */
			(*data++) = I2C1->DR;

			len--;
		}
	}

}

void AT24C32_WritePage(uint16_t address, uint8_t *data, uint8_t len)
{
	while (I2C1->SR2 & I2C_SR2_BUSY);           //wait until bus not busy

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

	while (!(I2C1->SR1 & I2C_SR1_SB)){;}	    //wait until start is generated

	I2C1->DR = AT24C32Address<< 1;              // Send slave address

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

	(void) I2C1->SR2; 						      //Clear SR2

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

	I2C1->DR=address>>8;						//Send MSB of the address

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

	I2C1->DR=address&0x0F;						// send LSB of the address

	for (int i=0;i<len;i++)
	{
		while(!(I2C1->SR1&I2C_SR1_TXE)){;}			//Wait until Data register empty

		I2C1->DR = *data++; 						//Write the data to the EEPROM
	}
	while (!(I2C1->SR1 & I2C_SR1_BTF));      	//wait until transfer finished

	I2C1->CR1 |=I2C_CR1_STOP;					//Generate Stop

}

Also, the header file:

#ifndef AT24C32_H_
#define AT24C32_H_

#include "stdint.h"




void AT24C32_ReadByte(uint16_t address, uint8_t *data);

void AT24C32_WriteByte(uint16_t address, uint8_t data);

void AT24C32_ReadPage(uint16_t address, uint8_t *data, uint8_t len);

void AT24C32_WritePage(uint16_t address, uint8_t *data, uint8_t len);


#endif /* AT24C32_H_ */

3. Main code:

In main.c:

#include "i2c.h"
#include "time_base.h"
#include "uart.h"
#include "stdio.h"
#include "AT24C32.h"

uint8_t data_read[4];

uint8_t data_write[4]={100,200,50,30};

uint16_t address=100;


int main(void)
{
	Ticks_Init(16000000);

	uart2_rxtx_init();

	i2c_init();

	AT24C32_WritePage(address,data_write,4);

	printf("Data written to address %d are: \r\n",address);

	for (int i=0;i<4;i++)
	{
		printf("%d \t",data_write[i]);
	}
	printf("\r\n");

	delay(100);

	AT24C32_ReadPage(address,data_read,4);

	printf("Data read from address %d are: \r\n",address);

	for (int i=0;i<4;i++)
	{
		printf("%d \t",data_read[i]);
	}
	printf("\r\n");

	delay(100);


	while(1)
	{
	}

}

4. Results:

Open serial terminal and set the buadrate to be 115200 and you should get the following results:

Happy coding 🙂

Add Comment

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