EEPROM

STM32 write and read EEPROM over I2C bus

EEPROM write and read

EEPROM sounds intimidating for the beginners, probably because there are few rules to comply. First, all EEPROMs share the same address on I2C bus, at least first page, and that is 0x50. I will give example for Atmel 24C08 chip, which has 8 kbit (!) memory. This number is NOT killo-bytes, but 1024 x 8 bits. So, practically ‘only’ 1 KB of memory space. Second rule is that writing must be done in sequence(s) of 8 or 16 bytes, depending of memory type. 1k and 2k EEPROMs can write only 8 bytes at a time, but 4k/8k/16k can write 16 bytes at a time. Between each write cycles and write then read cycle should be about 2 mS delay. This delay is some intrinsic property of the memory, and we can’t do anything about that. Only follow the rule. Read is possible whole ‘page’ of 256 bytes at once. Also, there is no restriction between two readings. Only after writing even singly byte, must be some delay, experimentally found 1.68 ms, so better use 2 mS (2000 uS) for sure.

Splitting data into groups of 16 bytes

That is how it should works. I made relatively simple code for STM32f10x family of the MCUs. In this code, there is two examples, one writing just 16 bytes, another one writing more than that in few steps with delay of 2 mS between each ‘packets’ of 16 bytes. Second example uses second of four pages. First example is on first page. Each page has actually its own I2C address ranging from 0x50 to 0x57 for 16k EEPROMs. I have only one chip that has 8k, so it covers four pages; page 0 = 0x50, page 1 = 0x51, page 2 = 0x52, and page 3 = 0x53. I found this chip below board with STM32f103VET6, that was surprise for me. Did not found any data about that board, nor it is mentioned in STM32 literature. And since this STM32 board has no ‘name’ as is for example Arduino uno, no data about this one except few words on eBay (plus price tag 😀 ).

In the example code I did not make algorithm for writing whole chip, because in practice this type of memory is just for few variables, maybe some calibration data or whatever user need to change after programming MCU, or during. For example, some servo has offset where middle position is not exactly in the middle. So, we can make code that scan buttons which moves servo, and when servo is where we want to be, another button press save calibration data into EEPROM. Since I did not use this chip in the past, I can’t give any example for now, but for sure it will be here in the future.

Code(s) not complete. Why?!

Please look carefully the examples. First example is not implemented correctly. I have doubt – do I need finish everything to show you, or you can learn something and recognize how to solve ‘the puzzle’? Second example, just un-comment (remove ‘//’) two separate functions twiSend(), twiReceive() and one printMsg() . That is last printMsg() which read all 255 bytes from second page at 0x51.  Also, you may notice that there are three strange variables included: ‘num’, ‘mantissa’ and ‘fraction’. Variable ‘num’ uses function strlen(test2) to get number of characters needed for two ‘for(;;)’ loops. In for example we have 92 characters, then 92/16 =5.75. Mantissa is number 5 (currently no needed in those examples), 0.75 is fraction, but (!) expressed in remaining bytes, that is 0.75*16=12.

Very interesting first loop:

This one uses number of characters (for example 92), subtract fraction (say 12), then it goes NOT from 0 to 92, but from 0 to 80 in steps of 16. Then some conversion of characters into uint8_t form. Not ideal, but… Then function twiSend(0x51,p,16) sends first 16 bytes, then another 16 until reaches 80. Then it exits for(;;) loop, and send the remaining 12 bytes twiSend(0x51,(num-fraction),fraction). At this time, ‘num-fraction’ is 92-12=80, which means that it begins to write at position 80 in EEPROM memory, for next ‘fraction’, which is 12 bytes.  After you copy/paste those codes, please align everything, because operation copy/paste onto this page can ruing alignment.

Here are the codes:

Copy/paste all codes and save in the same directory for Keil. I am not sure but I think the same codes can work in other editors/compilers/assemblers, but I am not familiar with those.

3 thoughts on “STM32 write and read EEPROM over I2C bus”

  1. Hi Milan,

    Great tutorials, thank you. This reading code works fine for me.

    void i2cRead(uint8_t devAddr, uint8_t dataAddr, uint8_t size)
    {
    //I2C1->OAR1 |= ( 0x68 <CR1 |= I2C_CR1_START | I2C_CR1_ACK;
    while(!(I2C1->SR1 & I2C_SR1_SB));

    // say “hello, i want to write” then control if address is matched
    I2C1->DR = (devAddr <SR1 & I2C_SR1_ADDR)| !(I2C1->SR2 & I2C_SR2_BUSY));

    // sending data address that i want to read
    I2C1->DR = dataAddr;
    while(!(I2C1->SR1 & I2C_SR1_TXE));

    // restart required for changing mode to reading mode
    I2C1->CR1 |= I2C_CR1_START;
    while(!(I2C1->SR1 & I2C_SR1_SB));

    // say “hello, i want to read data from data address that i just send” then control if address is matched
    I2C1->DR = (devAddr <SR1 & I2C_SR1_ADDR)| !(I2C1->SR2 & I2C_SR2_BUSY));

    // this part is needed for reading multiple bytes
    if(size > 1)
    {
    for(uint8_t i = 0; i SR1 & I2C_SR1_RXNE));
    received[i] = I2C1->DR;
    }
    }

    // stop communication and read last byte
    I2C1->CR1 &= ~I2C_CR1_ACK;
    I2C1->CR1 |= I2C_CR1_STOP;
    while(I2C1->CR1 & I2C_CR1_STOP);
    received[size – 1] = I2C1->DR;
    delay(1000);
    }

  2. Hi, Mustafa K

    I am glad you found better solution. Just there is something weird with part of the code, can you repeat? For example, here:
    for(uint8_t i = 0; i SR1 & I2C_SR1_RXNE));
    received[i] = I2C1->DR;
    Seems that something is mixed during copy/paste. Okay, I will contact you over the e-mail. Thanks.

  3. Sorry for late response Milan. Here is main.

    Mustafa

    /**
    ******************************************************************************
    * @file main.c
    * @date 30-March-2019
    * @brief Read MPU6050 sensor data through I2C
    * @brief B6 – MPU6050 SCL
    * @brief B7 – MPU6050 SDA
    ******************************************************************************
    */

    #include “stm32f10x.h”

    #define MPU6050Address 0x68
    #define PWR_MGMT_1 0x6B
    #define WHO_AM_I 0x75
    #define MPU6050_WAKEUP 0x00
    #define MPU6050_SLEEP 0x40
    #define TEMP_OUT_H 0x41
    #define TEMP_OUT_L 0x42

    void delay(uint32_t delay);
    void i2cInitialization(void);
    void i2cRead(uint8_t devAddr, uint8_t regAddr, uint8_t size);
    void i2cWrite(uint8_t devAddr, uint8_t regAddr, uint8_t data);
    int16_t i2cMPU6050Temp(void);

    uint8_t received[8];
    int16_t temp = 0x0000;

    #if 1
    int main(void)
    {
    i2cInitialization();

    i2cWrite(MPU6050Address, PWR_MGMT_1, MPU6050_WAKEUP);

    while(1)
    {
    int16_t temp = i2cMPU6050Temp();
    delay(1000000);
    }
    }
    #endif

    #if 0
    int main(void)
    {
    // Enable clock for port B

    RCC->APB1ENR &= ~(RCC_APB1ENR_I2C2EN);
    I2C2->CR1 = 0x00;
    I2C2->CR2 =0x00;

    RCC->APB2ENR |= RCC_APB2ENR_IOPBEN;
    RCC->APB1ENR |= RCC_APB1ENR_I2C2EN;

    I2C2->CR2 |= 36; // 36 MHz APB1ENR
    I2C2->CCR = 45; // 400kHz I2C
    I2C2->CCR |= I2C_CCR_FS;
    I2C2->TRISE |= 37;
    I2C2->CR1 |= I2C_CR1_ACK;

    // GPIO Settings | Alternate Function | Open Drain | 50MHz
    GPIOB->CRH |= GPIO_CRH_CNF10 | GPIO_CRH_MODE10;
    GPIOB->CRH |= GPIO_CRH_CNF11 | GPIO_CRH_MODE11;

    I2C2->OAR1 |= ( 0x68 <CR1 |= I2C_CR1_START;
    while(I2C2->SR1 & I2C_SR1_SB);

    I2C2->DR = 0xd0;
    while(I2C2->SR1 & I2C_SR1_ADDR);

    I2C2->DR = 0x75;
    while(!(I2C2->SR1 & I2C_SR1_TXE));

    I2C2->CR1 |= I2C_CR1_START;
    while(I2C2->SR1 & I2C_SR1_SB);

    I2C2->DR = 0xd1;
    while(!(I2C2->SR1 & I2C_SR1_TXE));

    while(!(I2C2->SR1 & I2C_SR1_RXNE));
    char received = I2C2->DR;

    I2C2->CR1 |= I2C_CR1_STOP;
    while(I2C2->CR1 & I2C_CR1_STOP);

    while(1)
    {

    }
    }
    #endif

    void delay(uint32_t delay)
    {
    int i = 0;
    while(i OAR1 |= ( 0x68 <CR1 |= I2C_CR1_START | I2C_CR1_ACK;
    while(!(I2C1->SR1 & I2C_SR1_SB));

    // say “hello, i want to write” then control if address is matched
    I2C1->DR = (devAddr <SR1 & I2C_SR1_ADDR)| !(I2C1->SR2 & I2C_SR2_BUSY));

    // sending to data address that i want to read
    I2C1->DR = regAddr;
    while(!(I2C1->SR1 & I2C_SR1_TXE));

    // restart required for changing mode to reading mode
    I2C1->CR1 |= I2C_CR1_START;
    while(!(I2C1->SR1 & I2C_SR1_SB));

    // say “hello, i want to read data from data address that i just send” then control if address is matched
    I2C1->DR = (devAddr <SR1 & I2C_SR1_ADDR) | !(I2C1->SR2 & I2C_SR2_BUSY));

    // this part is needed for reading multiple bytes
    if(size > 1)
    {
    for(uint8_t i = 0; i SR1 & I2C_SR1_RXNE));
    received[i] = I2C1->DR;
    }
    }

    // stop communication and read last byte
    I2C1->CR1 &= ~I2C_CR1_ACK;
    I2C1->CR1 |= I2C_CR1_STOP;
    while(I2C1->CR1 & I2C_CR1_STOP);
    received[size – 1] = I2C1->DR;
    delay(1000);
    }

    void i2cInitialization(void)
    {
    RCC->APB2ENR |= RCC_APB2ENR_IOPBEN;
    // GPIO Settings | Alternate Function | Open Drain | 50MHz
    GPIOB->CRL |= GPIO_CRL_CNF6 | GPIO_CRL_MODE6;
    GPIOB->CRL |= GPIO_CRL_CNF7 | GPIO_CRL_MODE7;

    delay(100000);

    // I2C Initialization
    RCC->APB1ENR |= RCC_APB1ENR_I2C1EN;
    I2C1->CR2 |= 50; // 50MHz
    //I2C1->CCR |= I2C_CCR_FS;
    I2C1->CCR |= 180;
    I2C1->TRISE |= 51;
    I2C1->CR1 |= I2C_CR1_PE;

    delay(1000);
    }

    void i2cWrite(uint8_t devAddr, uint8_t regAddr, uint8_t data)
    {
    // Set ACK and Start then control start bit
    I2C1->CR1 |= I2C_CR1_START | I2C_CR1_ACK;
    while(!(I2C1->SR1 & I2C_SR1_SB));

    // say “hello, i want to write” then control if address is matched
    I2C1->DR = (devAddr <SR1 & I2C_SR1_ADDR)| !(I2C1->SR2 & I2C_SR2_BUSY));

    // sending to data address that i want to read
    I2C1->DR = regAddr;
    while(!(I2C1->SR1 & I2C_SR1_TXE));

    // sending to data that i want to read
    I2C1->DR = data;
    while(!(I2C1->SR1 & I2C_SR1_TXE));

    // stop communication and read last byte
    I2C1->CR1 &= ~I2C_CR1_ACK;
    I2C1->CR1 |= I2C_CR1_STOP;
    while(I2C1->CR1 & I2C_CR1_STOP);
    delay(1000);
    }

    int16_t i2cMPU6050Temp(void)
    {
    i2cRead(MPU6050Address, TEMP_OUT_H, 2);
    temp |= received[0] << 8 | received[1];
    temp = (float)(temp / 340) + 36.53;
    return temp;
    }

Leave a Reply

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

This site uses Akismet to reduce spam. Learn how your comment data is processed.