[STM32F1] STM32F103 I2C硬件缺陷详述及解决方案

[复制链接]
2616|18
 楼主| v26g7l 发表于 2023-6-28 20:29 | 显示全部楼层 |阅读模式
记得刚开始接触STM32单片机的时候,就从很多地方听说STM32F103的硬件I2C是有缺陷的,但是当时在网上查了很久也没有查到具体的缺陷是什么,后面在工作中正好有相关问题,在经过一段时间深入了解后,写下这篇文章来盘一盘硬件I2C的缺陷。

I2C的协议中规定,在传输数据时,接收方在接收到数据后都会向发送方发出一个响应信号ACK,如果传输双方有一个是主接收器,它必须在不产生时钟的最后一个字节发出一个NACK,来告知发送器此次数据传输过程结束,如下图所示:
67162649c27866ca3a.png
I2C中的ACK信号为一个低电平,NACK信号为高电平,软件流程中,只要不是最后一个数据,在主接收器接收到数据后默认都会发出去一个ACK信号,那么问题来了,当主接收器在接受到最后一个字节后,I2C的主发送器发送出去一个ACK信号会导致什么样的结果发生呢?

 楼主| v26g7l 发表于 2023-6-28 20:31 | 显示全部楼层
此时,总线上的从发送器收到ACK,以为还要继续发送数据,所以将SDA拉住等待主机的时钟继续发送数据,而主接收器呢,因为自身已经接收到最后一个字节数据,所以不会继续向从机发送时钟,导致SDA一直被从机拉住无法释放,整个通信陷入死锁。从逻辑分析仪上抓取的波形表现可以很清楚的看到现象: 1987649c280479a32.png
 楼主| v26g7l 发表于 2023-6-28 20:32 | 显示全部楼层
代码如下:
  1. uint32_t I2C_EE_BufferRead(u8* pBuffer, u8 ReadAddr, u16 NumByteToRead)
  2. {  
  3.    
  4.   while(I2C_GetFlagStatus(EEPROM_I2Cx, I2C_FLAG_BUSY));
  5.   /* Send START condition */
  6.   I2C_GenerateSTART(EEPROM_I2Cx, ENABLE);
  7.   
  8.   /* Test on EV5 and clear it */
  9.   while(!I2C_CheckEvent(EEPROM_I2Cx, I2C_EVENT_MASTER_MODE_SELECT));
  10.   
  11.   /* Send EEPROM address for write */
  12.   I2C_Send7bitAddress(EEPROM_I2Cx, EEPROM_ADDRESS, I2C_Direction_Transmitter);

  13.   /* Test on EV6 and clear it */
  14.   while(!I2C_CheckEvent(EEPROM_I2Cx, I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED));
  15.    
  16.   /* Clear EV6 by setting again the PE bit */
  17.   I2C_Cmd(EEPROM_I2Cx, ENABLE);

  18.   /* Send the EEPROM's internal address to write to */
  19.   I2C_SendData(EEPROM_I2Cx, ReadAddr);  

  20.   /* Test on EV8 and clear it */
  21.   while(!I2C_CheckEvent(EEPROM_I2Cx, I2C_EVENT_MASTER_BYTE_TRANSMITTED));
  22.    
  23.   /* Send STRAT condition a second time */  
  24.   I2C_GenerateSTART(EEPROM_I2Cx, ENABLE);

  25.   /* Test on EV5 and clear it */
  26.   while(!I2C_CheckEvent(EEPROM_I2Cx, I2C_EVENT_MASTER_MODE_SELECT));
  27.    
  28.   /* Send EEPROM address for read */
  29.   I2C_Send7bitAddress(EEPROM_I2Cx, EEPROM_ADDRESS, I2C_Direction_Receiver);

  30.   while(!I2C_CheckEvent(EEPROM_I2Cx, I2C_EVENT_MASTER_RECEIVER_MODE_SELECTED));
  31.   
  32.   /* While there is data to be read */
  33.   while(NumByteToRead)  
  34.   {
  35.    
  36.                 while(I2C_CheckEvent(EEPROM_I2Cx, I2C_EVENT_MASTER_BYTE_RECEIVED)==0);
  37.                
  38.          
  39.       /* Read a byte from the EEPROM */
  40.       *pBuffer = I2C_ReceiveData(EEPROM_I2Cx);

  41.       /* Point to the next location where the byte read will be saved */
  42.       pBuffer++;
  43.       
  44.       /* Decrement the read bytes counter */
  45.       NumByteToRead--;        
  46.       
  47.   }


  48.         /* Disable Acknowledgement */
  49.         I2C_AcknowledgeConfig(EEPROM_I2Cx, DISABLE);
  50.       
  51.   /* Send STOP Condition */
  52.   I2C_GenerateSTOP(EEPROM_I2Cx, ENABLE);

  53.   /* Enable Acknowledgement to be ready for another reception */
  54.   I2C_AcknowledgeConfig(EEPROM_I2Cx, ENABLE);
  55.   
  56.     return 1;
  57. }
 楼主| v26g7l 发表于 2023-6-28 20:32 | 显示全部楼层
  1. int main(void)
  2. {
  3.   LED_GPIO_Config();

  4.   /* 串口初始化 */
  5.         USART_Config();

  6.         /* I2C 外设初(AT24C02)始化 */
  7.         I2C_EE_Init();

  8.   I2C_EE_BufferRead(I2c_Buf_Read, EEP_Firstpage, 4);
  9.   while (1)
  10.   {      

  11.   }
  12. }

 楼主| v26g7l 发表于 2023-6-28 20:34 | 显示全部楼层
main函数里是读取4个eeprom的数据,从I2C的标准通信协议来看,这段代码在流程上是没有问题的,但是从波形上可以看到有两个很奇怪的现象:
1. NACK和STOP信号都没有发出去
2. 本来应该主接收器应该读取4个数据,却多发出去一个时钟,读到5个数据

从分析得出,总线的死锁是由于NACK和STOP信号没发出去导致的,那么为什么NACK和STOP没发出去呢?其实这个问题又是由于第二个问题的本身导致的。

STM32F103的I2C设计为了加快数据读取速度,在读取一个数据后,如果没有检测到NACK信号的话,会多读取一个数据,即多发送一个字节的时钟去读取数据,但是由于其中过程太快,如果想在接收到最后一个字节后再去发送NACK,那么下一个时钟已经出去了,导致NACK和STOP信号都没发出去,也就导致了总线死锁。
 楼主| v26g7l 发表于 2023-6-28 20:34 | 显示全部楼层
解决方法:

使用DMA或者中断,中断或DMA方式速度更快,可以让NACK可以及时在接收到最后一个字节之后发出去
如果还是要使用轮询方式的话,就需要修改代码流程,将发送NACK和STOP信号的时间提前到倒数第二个数据,具体如下:
 楼主| v26g7l 发表于 2023-6-28 20:36 | 显示全部楼层
  1.   while(NumByteToRead)  
  2.   {
  3.    
  4.                 if(NumByteToRead == 1)
  5.                 {
  6.                         /* Disable Acknowledgement */
  7.                         I2C_AcknowledgeConfig(EEPROM_I2Cx, DISABLE);
  8.                                        
  9.                         /* Send STOP Condition */
  10.                         I2C_GenerateSTOP(EEPROM_I2Cx, ENABLE);
  11.                 }
  12.                
  13.                 while(I2C_CheckEvent(EEPROM_I2Cx, I2C_EVENT_MASTER_BYTE_RECEIVED)==0);
  14.                
  15.          
  16.       /* Read a byte from the EEPROM */
  17.       *pBuffer = I2C_ReceiveData(EEPROM_I2Cx);

  18.       /* Point to the next location where the byte read will be saved */
  19.       pBuffer++;
  20.       
  21.       /* Decrement the read bytes counter */
  22.       NumByteToRead--;        
  23.       
  24.   }
 楼主| v26g7l 发表于 2023-6-28 20:37 | 显示全部楼层
此时从逻辑分析仪上抓取到的波形显示正常:

39905649c2951151ee.png
降低I2C的传输速度到几十KHz,原理一致,I2C发送速度慢了以后,NACK就可以及时发出。
Clyde011 发表于 2024-1-23 12:24 | 显示全部楼层

由于铜更难以磨削并且可能导致钻头断裂,
万图 发表于 2024-1-23 14:20 | 显示全部楼层

相反电感量小其阻碍能力也小,它在电路当中抑制的是共模信号
万图 发表于 2024-1-23 14:20 | 显示全部楼层

相反电感量小其阻碍能力也小,它在电路当中抑制的是共模信号
Uriah 发表于 2024-1-23 15:23 | 显示全部楼层

在动态测试中,电源的负载能力是最主要的测试参数
您需要登录后才可以回帖 登录 | 注册

本版积分规则

29

主题

230

帖子

0

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