2023-10-19 12:46 PM
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?
2023-10-20 11:19 AM
HAL_I2C_IsDeviceReady
2023-10-20 11:21 AM
This sounds like exactly what I was looking for. Thank you!
2023-10-20 11:25 AM
Does this look like correct use of the function? This waits to do the write until the device is ready:
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;
}
while( HAL_I2C_IsDeviceReady(hi2c, DevAddress, 1, 10) != HAL_OK ){ }
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;
}
}
2023-10-20 12:59 PM - edited 2023-10-20 01:02 PM
Better replace while( HAL_I2C_IsDeviceReady(hi2c, DevAddress, 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
}
2023-10-20 01:22 PM - edited 2023-10-20 01:23 PM
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.
2023-10-20 01:25 PM
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?
2023-10-20 01:35 PM
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)
2023-10-20 01:36 PM
That is 20 attempts at 5ms interval which is more reasonable, but still non-ideal
2023-10-20 03:10 PM
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.
2023-10-21 04:07 AM - edited 2023-10-21 04:17 AM
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.