1. 简介
MCU 经常作为主机与 EEPROM 之间使用 I2C 进行通信,当 I2C 主机在通信的过程中发生异 常复位时,会有概率出现再无法与 EEPROM 通信,我们称之为总线锁死。为解决此问题,本文提供了采用软件配置释放 I2C 总线的方法。
2. I2C 总线锁死
2.1. I2C 总线锁死现象
I2C 的主从机之间若要完成一次正常的通信任务,在建立通信之前,首先需要主机检测 I2C 总 线的状态。当 I2C 总线的 SCL 和 SDA 线均为高电平时,I2C 总线为空闲状态,主机在 SCLA 为高电平时,拉低 SDA 信号,从而产生一个 START 起始信号。 I2C 主从机在结束一次通信任务时,需要主机产生一个停止信号,即在 SCL 为高电平时,拉高 SDA 信号。
图 2-1. I2C 总线的起始和停止信号
在正常情况下,I2C 总线协议能够保证总线正常的读写操作。但是,当 I2C 主设备复位时(看门 狗动作,板上电源异常导致复位芯片动作,手动按钮复位等等),而从机未复位,可能导致 I2C 总线死锁。在总线锁死的状态下,SCL 保持高电平状态,SDA 保持低电平状态。
2.2. I2C 总线锁死原因
在 I2C 主机读写的过程中,有两种情况会导致总线锁死。
1. 主机在发送 START 信号后,控制 SCL 产生 8 个时钟脉冲,然后拉低 SCL 信号为低电平, 在这个时候,从设备输出应答信号,将 SDA 信号拉为低电平。如果这个时候主机异常复 位,SCL 就会被释放为高电平。此时,如果从机没有复位,就会继续 I2C 的应答,将 SDA 一直拉为低电平,直到 SCL 变为低电平,才会结束应答信号。而由于 I2C 主机复位后检 测总线的状态,如果 SDA 信号为低电平,则 I2C 总线被占用,会一直等待 SCL 和 SDA 信号变为高电平。因此,在 I2C 主机等待从机释放 SDA 信号时,I2C 从机又在等待主机 将 SCL 信号拉低以释放应答信号,两者相互等待,I2C 总线进入死锁状态。
2. 当 I2C 主机在读数据时,I2C 从机应答后输出数据,如果在这个时刻 I2C 主机异常复位, 而此时 I2C 从机输出的数据位正好为 0,也会导致 I2C 总线进入死锁状态。
图 2-2. I2C 总线锁死时序
3. I2C 总线锁死解决方法
I2C 总线锁死状态下。通过复位从机也可实现释放总线。但 EEPROM 作为从机时,无法使用 软件复位从机的方法,而在某些场合又无法使用硬件的方法进行复位。故需要在 I2C 主机在建 立新的通信时,增加总线释放功能。由于总线锁死是概率性的,可以增加总线 BUSY 态超时功 能。二者结合可以提高系统的鲁棒性。以下提供两种软件解决方法。
3.1. 强制拉高 SDA 和 SCL
在 I2C 主机复位后,主机检测 I2C 总线一直为 BUSY 状态,且超过设定的时间,则总线被锁 死。可通过将 I2C 的 SCL 和 SDA 引脚初始化成普通 GPIO 功能,配置成推挽输出。先拉高 SCL 信号,在拉高 SDA 信号,模拟产生一个 STOP 信号,然后再配置为 I2C 的引脚复用功 能。GD32 工程下的软件配置如下表所示。
表 3-1. GD 工程下强制拉高 SDA 和 SCL 的配置
/*!
\brief reset i2c bus
\param[in] none
\param[out] none
\retval none
*/
void i2c_bus_reset()
{
GPIO_BC(GPIOB) |= GPIO_PIN_6 | GPIO_PIN_7;
gpio_init(GPIOB, GPIO_MODE_OUT_PP, GPIO_OSPEED_50MHZ,
GPIO_PIN_6|GPIO_PIN_7);
__nop();
__nop();
__nop();
__nop();
__nop();
GPIO_BOP(GPIOB) |= GPIO_PIN_6;
__nop();
__nop();
__nop();
__nop();
__nop();
GPIO_BOP(GPIOB) |= GPIO_PIN_7;
gpio_init(GPIOB, GPIO_MODE_AF_OD, GPIO_OSPEED_50MHZ, GPIO_PIN_6 |
GPIO_PIN_7);
}
/*!
\brief check the I2C is or not busy
\param[in] none
\param[out] none
\retval none
*/
void check_bus_status(void)
{
while(i2c_flag_get(I2C0,I2C_FLAG_I2CBSY))
{
if(--time_out == 0){
i2c_bus_reset();
}
}
}
3.2. SCL 时钟信号释放总线
在 I2C 主机中增加 I2C 总线恢复程序。每次 I2C 主设备复位后,如果检测到 SDA 数据线被拉 低,则控制 I2C 中的 SCL 时钟线产生 9 个时钟脉冲(针对 8 位数据的情况),这样 I2C 从设备 就可以完成被挂起的操作,从死锁状态中恢复过来。 I2C 主机通过将 SCL 引脚初始化为普通 GPIO 功能,配置成推挽输出。保证连续发送 9 个时 钟脉冲,为保证后续 I2C 正常通信,先将 I2C 模块复位,再置位,最后再配置为 I2C 的引脚复 用功能。GD32 工程下的软件配置如下表所示。
表 3-2. GD 工程下的 SCL 时钟信号释放总线配置
/*!
\brief reset i2c bus
\param[in] none
\param[out] none
\retval none
*/
void i2c_bus_reset()
{
uint8_t I = 0;
gpio_init(GPIOB, GPIO_MODE_OUT_PP, GPIO_OSPEED_50MHZ, GPIO_PIN_6);
/* SCL output clock signal */
for(I = 0; I < 10; i++){
gpio_bit_reset(GPIOB, GPIO_PIN_6);
delay_1us(2);
gpio_bit_set(GPIOB, GPIO_PIN_6);
delay_1us(2);
}
/* reset I2C */
i2c_software_reset_config(I2C0, I2C_SRESET_RESET);
i2c_software_reset_config(I2C0, I2C_SRESET_SET);
gpio_init(GPIOB, GPIO_MODE_AF_OD, GPIO_OSPEED_50MHZ, GPIO_PIN_6 |
GPIO_PIN_7);
}
/*!
\brief check the I2C is or not busy
\param[in] none
\param[out] none
\retval none
*/
void check_bus_status(void)
{
while(i2c_flag_get(I2C0,I2C_FLAG_I2CBSY))
{
if(--time_out == 0){
i2c_bus_reset();
}
}
}
3.3. 测试结果
在 GD32F303 的平台上测试两种总线释放的方法。其测试结果如图 3-1. 强制拉高 SDA 和 SCL 测试和图 3-2. SCL 时钟信号释放总线测试所示。
强制拉高 SDA 和 SCL 测试如下图所示,I2C 总线锁死时,SDA 为低电平状态,SCL 为高电 平状态。总线释放时,将 SCL 先拉低,再拉高,然后再将 SDA 拉高。最后 I2C 总线上出现 STOP 信号,总线被释放。主机可以开始建立新的通信。
图 3-1. 强制拉高 SDA 和 SCL 测试
SCL 时钟信号释放总线测试如下图所示,I2C 总线锁死后,连续发送 9 个时钟信号,最后 SDA 和 SCL 均被拉高,主机检测到总线为空闲状态,可以开始建立新的通信。
图 3-2. SCL 时钟信号释放总线测试
参考连接
AN036_CN_Rev1.0.pdf (gd32mcu.com)https://www.gd32mcu.com/data/documents/userManual/AN036_CN_Rev1.0.pdf一个判断 I2C 总线通信异常原因的方法_i2c通信正常和通信不正常-CSDN博客https://blog.csdn.net/anlog/article/details/131660862?ops_request_misc=%257B%2522request%255Fid%2522%253A%2522171593218516800213071294%2522%252C%2522scm%2522%253A%252220140713.130102334.pc%255Fblog.%2522%257D&request_id=171593218516800213071294&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2~blog~first_rank_ecpm_v1~rank_v31_ecpm-1-131660862-null-null.nonecase&utm_term=%E6%AD%BB%E9%94%81&spm=1018.2226.3001.4450
总结:简单发送脉冲的方案最简单。
特此记录
anlog
2024年5月17日