[STM32H7] STM32 HAL_I2C_Mem_Write / Mem_Read 在连续快速调用时出现通信异常

[复制链接]
108|15
yunfu1117 发表于 2026-8-1 23:33 | 显示全部楼层 |阅读模式
我的STM32H730 搭配 M24C64 进行项目开发。使用HAL_I2C_Mem_Write从 EEPROM 读取数据。
现在连续快速调用HAL_I2C_Mem_Write或者HAL_I2C_Mem_Read时,读出的数据是错误的。
我已经在每次读写操作后加入 1ms 延时,但测试发现需要延长至大约 10ms 才能稳定工作,这会严重影响性能。
调试过程中如果使用调试器单步慢速执行,读写完全正常;全速运行缺少足够延时就出错。


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);

}

}

目前最奇怪的一点是即便读出 / 写入的数据是错误的,所有读写函数依然返回 HAL_OK。
有没有别的函数可以调用,用来等待 EEPROM 真正就绪、能够接收新的通信指令?

公羊子丹 发表于 2026-8-3 21:18 | 显示全部楼层
核心问题不是I2C总线没走完,是M24C64页写后自带内部烧录延时,HAL函数只等总线应答,不等芯片内部操作。每次写完用HAL_I2C_IsDeviceReady循环轮询,别固定死延时,能大幅提速。
周半梅 发表于 2026-8-3 21:19 | 显示全部楼层
之前H7驱动同款EEPROM踩过完全一样的坑,固定1ms延时根本不够,芯片写入时拉低SDA拒应答,总线函数返回OK但数据没存,轮询设备就绪就能替换掉长延时,性能提升特别明显。
帛灿灿 发表于 2026-8-3 21:19 | 显示全部楼层
你这固定HAL_Delay属于治标不治本的笨办法,调试单步慢刚好给足EEPROM烧写时间,全速跑直接翻车,HAL_OK只是总线层面没问题,芯片内部还在干活没响应。
童雨竹 发表于 2026-8-3 21:20 | 显示全部楼层
想问下你CubeMX里I2C时钟速度设的多少?H7主频高,I2C速率开太高也会加剧连续读写异常,先降到100K标准模式测试,看看报错频率会不会降低。
万图 发表于 2026-8-3 21:21 | 显示全部楼层
给你个替换延时的简易逻辑,写完数据加while(HAL_I2C_IsDeviceReady(&hi2c,DevAddress,1,50)!=HAL_OK);循环等待,芯片内部操作完自动退出,不用死板卡10ms。
Wordsworth 发表于 2026-8-3 21:22 | 显示全部楼层
M24C64每页写完有5ms左右自写入周期,这段时间芯片不会响应任何I2C指令,HAL_Mem_Write只确认总线收发完成,不会校验存储是否落盘,所以返回OK但读出来错乱。
Bblythe 发表于 2026-8-3 21:23 | 显示全部楼层
你可以分开测试定位问题:先单次读写不加延时看是否出错;再写完立刻读,对比加轮询函数后的效果,就能确定是EEPROM内部烧写阻塞导致的数据异常。
Pulitzer 发表于 2026-8-3 21:24 | 显示全部楼层
批量擦除、页写场景别每次都等固定延时,封装一个等待就绪函数,只在写操作后调用,读操作不用等待,这样大批量读写的时候整体运行速度能快好几倍。
Uriah 发表于 2026-8-3 21:25 | 显示全部楼层
你现在不管读写都加延时完全没必要,只有Mem_Write之后芯片需要烧写等待,Mem_Read不需要内部存储操作,把读函数里的HAL_Delay删掉,只处理写后的等待逻辑就行。
Clyde011 发表于 2026-8-3 21:26 | 显示全部楼层
顺便检查下I2C上拉电阻,H7高速IO驱动能力强,上拉4.7k最合适,电阻太小总线电平不稳,连续高速通信容易出现假性应答,叠加EEPROM延时问题会更难排查。
powerantone 发表于 2026-8-6 17:26 | 显示全部楼层
你的1ms延时太短,M24C64页写入需高达5ms内部编程时间,这期间芯片不响应任何指令,延时不足直接导致数据错误。
classroom 发表于 2026-8-6 17:56 | 显示全部楼层
HAL_I2C_Mem_Write返回HAL_OK不代表写入完成,它只表示I2C总线传输完成,不代表EEPROM内部编程结束,后续操作需要等待芯片就绪
cr315 发表于 2026-8-6 18:27 | 显示全部楼层
推荐使用HAL_I2C_IsDeviceReady()循环查询EEPROM应答,这是官方示例中的标准做法,比固定延时高效可靠。
duo点 发表于 2026-8-6 18:58 | 显示全部楼层
写操作后应调用HAL_I2C_IsDeviceReady()替代固定延时,它能准确检测EEPROM内部编程完成并回应ACK。
flycamelaaa 发表于 2026-8-6 19:29 | 显示全部楼层
官方示例代码在每次页写后都使用while(HAL_I2C_IsDeviceReady(...) == HAL_TIMEOUT)等待,建议直接参考,你的代码缺少这一步。
jcky001 发表于 2026-8-6 20:00 | 显示全部楼层
你的write_block每32字节边界切分正确,但每块写完后缺少等待就绪的查询,导致连续跨页写入时第二页无响应。
onlycook 发表于 2026-8-6 20:29 | 显示全部楼层
大量EEPROM页写入后紧接着读操作,读到的数据错误极大概率是因为EEPROM还在忙,忽视了内部编程时序要求。
probedog 发表于 2026-8-6 21:04 | 显示全部楼层
调试器单步正常而全速异常,是因为单步执行给了EEPROM足够的内部编程时间,全速时你不给时间,问题就会暴露。
solty 发表于 2026-8-6 21:30 | 显示全部楼层
你的erase_all调用clear_block全片擦除,每次都按32字节分块写入零,但没有在每块写入后等待就绪,这种连续写入最容易触发EEPROM忙状态。
您需要登录后才可以回帖 登录 | 注册

本版积分规则

217

主题

219

帖子

0

粉丝
快速回复 在线客服 返回列表 返回顶部
0