Skip to main content
Graduate
October 19, 2023
Question

STM32 HAL_I2C_Mem_Write/Read Errors In Quick Sequential Operations

  • October 19, 2023
  • 8 replies
  • 11746 views

Hi, I am working on a project using the STM32H730 with the M24C64 I2C EEPROM chip. 

In my project I am using the HAL_I2C_Mem_Write functions to load and save values from the EEPROM. I'm running into an issue where the wrong value gets read if there are sequential calls to HAL_I2C_Mem_Write or HAL_I2C_Mem_Read functions. I already have a short delay of 1ms after the reads and writes, but it seems longer is necessary, more in the realm of 10ms, which is a major drag on performance. I discovered this when debugging because I do not get read/write errors when stepping through slowly with a debugger, as this adds a time delay between the I2C functions being called. 

Here are my I2C read / write functions:

/**

* @brief Erases the entire i2c_eeprom memory

*/

void persistent_memory_i2c_eeprom::erase_all(void) {

clear_block(0, mem_size);

}

 

/**

* @brief Writes a byte to i2c_eeprom memory

*/

void persistent_memory_i2c_eeprom::write_val(uint32_t offset, uint8_t val) {

HAL_StatusTypeDef res = HAL_I2C_Mem_Write(hi2c, DevAddress, mem_base_address + offset, I2C_MEMADD_SIZE_16BIT, &val, 1, 100);

HAL_Delay(1);

}

 

/**

* @brief Reads a byte from i2c_eeprom memory

*/

uint8_t persistent_memory_i2c_eeprom::read_val(uint32_t offset) {

uint8_t data;

HAL_StatusTypeDef res = HAL_I2C_Mem_Read(hi2c, DevAddress, mem_base_address + offset, I2C_MEMADD_SIZE_16BIT, &data, 1, 100);

HAL_Delay(1);

return data;

}

 

 

/**

* @brief Writes a block of data to i2c_eeprom memory

*/

void persistent_memory_i2c_eeprom::write_block(uint32_t offset, uint8_t * vals, uint32_t size) {

 

uint32_t write_remaining = size;

uint32_t mem_addr = mem_base_address + offset;

uint32_t incr = 0;

while(write_remaining > 0){

uint16_t block_portion = 32 - (mem_addr % 32);

if(block_portion > write_remaining){

block_portion = write_remaining;

}

HAL_StatusTypeDef res = HAL_I2C_Mem_Write(hi2c, DevAddress, mem_addr, I2C_MEMADD_SIZE_16BIT, &vals[incr], block_portion, 100);

mem_addr += block_portion;

write_remaining -= block_portion;

incr += block_portion;

HAL_Delay(1);

}

}

 

/**

* @brief Reads a block of data from i2c_eeprom memory

*/

void persistent_memory_i2c_eeprom::read_block(uint32_t offset, uint8_t * vals, uint32_t size) {

 

HAL_StatusTypeDef res = HAL_I2C_Mem_Read(hi2c, DevAddress, mem_base_address + offset, I2C_MEMADD_SIZE_16BIT, vals, size, 100);

HAL_Delay(1);

}

 

void persistent_memory_i2c_eeprom::clear_block( uint32_t offset, uint32_t size ) {

uint8_t vals[32] = {};

uint32_t write_remaining = size;

uint32_t mem_addr = mem_base_address + offset;

uint32_t incr = 0;

while(write_remaining > 0){

uint16_t block_portion = 32 - (mem_addr % 32);

if(block_portion > write_remaining){

block_portion = write_remaining;

}

HAL_StatusTypeDef res = HAL_I2C_Mem_Write(hi2c, DevAddress, mem_addr, I2C_MEMADD_SIZE_16BIT, &vals[incr], block_portion, 100);

mem_addr += block_portion;

write_remaining -= block_portion;

incr += block_portion;

HAL_Delay(1);

}

}

 

Here is an example of a call that leads to an error unless I insert an artificial delay:

/* Write each preset to memory */

clear_block(mem_offset, 1);

write_block(mem_offset, &temp[i], 1);

 

Is there a reason this is happening? Currently the weird thing is that all the read / write functions are returning HAL_OK, even when they're reading / writing the incorrect values. Is there a different function I can call to wait until the EEPROM is actually ready to accept new messages?

    This topic has been closed for replies.

    8 replies

    Super User
    October 19, 2023

    You are not waiting long enough for the WRITE operations to complete before accessing the chip again (either read or write).  See the tWR (Internal Write cycle duration) parameter in the datasheet.  You can either increase your dumb HAL_Delay() delays to that amount, or use ACK polling to find out when the write operation is done (section 5.1.6).

    EPala.2Author
    Graduate
    October 19, 2023

    Thanks, how would I set up the ACK polling using the HAL memory library?

    I read that section, but I thought waiting for acknowledgement was part of the HAL Blocking I2C functions with the timeout. If the HAL_I2C_Mem_Write function wasn't getting an acknowledgement back within the timeout period wouldn't it return a HAL_ERROR of some kind? It always returns HAL_OKAY.

    Super User
    October 20, 2023

    > It always returns HAL_OKAY.

    Sure it does, when you're stepping through it with a debugger. But you don't see the errors in that case either.

    Monitor the return status and actually do something with that info. Your code just silently ignores whatever return value occurs.

    Super User
    October 20, 2023

    Clock stretching, maybe multi-master busy status as well.

    Super User
    October 20, 2023

    Better replace while( HAL_I2C_IsDeviceReady(hi2cDevAddress, 1, 10) != HAL_OK ){ } to a better check for errors. HAL_I2C_IsDeviceReady() does several retries itself. Smth. like

     

     

    #define EEPROM_MAX_BUSY_MS 15 /* check in datasheet */
    
    status = HAL_I2C_IsDeviceReady(hi2c, DevAddress, EEPROM_MAX_BUSY_MS, 1);
    if (status != HAL_OK) {
     // HAL_TIMEOUT or other error 
    }

     

     

     

    EPala.2Author
    Graduate
    October 20, 2023

    This seems to be failing: 

     

    void persistent_memory_i2c_eeprom::wait_for_acknowledge(void){

    HAL_StatusTypeDef status = HAL_I2C_IsDeviceReady(hi2c, DevAddress, 100, 1);

    if (status != HAL_OK) {

    last_res = status;

    }

    }

    Consistently reaching the HAL_ERROR state when called in quick succession. Seems strange since this should poll the device for an acknowledge bit for an amount of time far greater than it's max busy period.

    EPala.2Author
    Graduate
    October 20, 2023

    Seems to work after I change the timeout to max busy time, but this seems really non-ideal. Basically the same as inserting the huge delay in my code:

    void persistent_memory_i2c_eeprom::wait_for_acknowledge(void){

    HAL_StatusTypeDef status = HAL_I2C_IsDeviceReady(hi2c, DevAddress, 20, 5);

    if (status != HAL_OK) {

    last_res = status;

    }

    }

    Anyone have thoughts on how this might be improved?

     

    Super User
    October 20, 2023

    Sorry, my bad.  HAL_I2C_IsDeviceReady returns HAL_ERROR even on timeout. You can detect the timeout condition by checking hi2c->ErrorCode.

    if (hi2c->ErrorCode & HAL_I2C_ERROR_TIMEOUT) => then timeout 

    But you say that it takes 5 attempts with 20 ms intervals ... this is too much. Please check that the HAL timer frequency is correct. (i.e. HAL_Delay(1000) delays for ~ 1s)

    EPala.2Author
    Graduate
    October 20, 2023

    That is 20 attempts at 5ms interval which is more reasonable, but still non-ideal

    Super User
    October 20, 2023

    Where do you get the "20 attempts at 5m intervals" from?

    Your HAL_I2C_IsDeviceReady() call shown above will indeed try 20 times.  BUT, that "5" you pass as the "timeout" parameter does NOT mean that each pass will wait 5ms.  That is the total maximum time it will wait for bus busy and/or clock stretching by the slave.  Each attempt will complete as soon as the STM32 can get access to the bus (i.e. not busy), send the address byte then clock in the ACK/NAK bit.  Typically much shorter than 5ms.

    Super User
    October 21, 2023

    Again my bad. Yes, 20 attempts at 5ms. Yes the last one can be shorter than 5 ms. To tell how long it took actually:

     

     

    uint32_t t1 = HAL_GetTick();
    HAL_StatusTypeDef status = HAL_I2C_IsDeviceReady(hi2c, DevAddress, 20, 5);
    uint32_t t2 = HAL_GetTick() - t1;
    printf("It took %u ms\n", (unsigned)t2);
    if (status != HAL_OK) {
     if(hi2c->ErrorCode & HAL_I2C_ERROR_TIMEOUT) ...
     else ...
    }

     

    If it takes19 attempts of 5ms, still not good. Need to use a scope to see the pulse forms.

    Graduate II
    October 21, 2023

    If on NACK situation the HAL_I2C_Mem_Write() stops and returns HAL_ERROR, then why don't you just wait and retry in that case? Introducing another function, which does an additional I2C transaction just for checking the device status, just wastes the total transfer time and increases the code size.

    Super User
    October 22, 2023

    You're right of course.

     

    Btw, I've tested an i2c eeprom on some custom STM32F7 board, and couple of times managed to drive it to a weird state: HAL_I2C_IsDeviceReady returned with error=timeout  almost immediately, without waiting the specified time (measured with HAL_GetTick around the call gives delta <= 1). After a power cycle all returned to normal working state and this does not reproduce again. Looked in the source of HAL_I2C_IsDeviceReady, couldn't find apparent cause of that.