STM32的硬件I2C的BUG

使用前请先充分验证该方法及相关代码 2016.02.03

引子

STM32的硬件I2C很多人都对它望而却步。因为很多电工都说,STM32 硬件 I2C有BUG、不稳定、死机等等……最后都使用GPIO模拟I2C。

的确,模拟I2C好用。但是在我看来在一个72M的Cortex-M3的MCU上这样做非常不妥。一般来说I2C是一种慢速总线,就算工作在400kHz的快速模式上,I2C传送每个字节仍需要至少23us——还没有计算地址、起始信号和结束信号的发送。如果使用GPIO模拟的I2C,这23us的CPU时间都在空转中浪费了,而这23us已经可以做不少的事情了,所以在STM32上I2C还是使用硬件为佳——虽然它多多少少有点缺陷。

这篇文章不是给完全没有接触过STM32 硬件I2C的新手看的,看这篇文章之前至少先阅读STM32的参考手册(RM0008)。

概览

我们先来看一下STM32 I2C硬件的结构

我们可以看见STM32的硬件I2C有两个和数据有关的寄存器“数据寄存器(Data register)”(DR)和“数据移位寄存器(Data shift register)”(DSR),我们的软件写入的是DR, DSR用于I2C数据的移位发送和接收,DR和DSR的数据交换由硬件控制——发送时DSR为空,DR不为空时,硬件自动把DR的数据写进DSR;接收时DR为空,DSR不为空,硬件自动把DSR数据写进DR。连续数据传输时,这样两个寄存器的数据交换使得软件读出和写入DR不会影响I2C总线中的数据接收和发送,使I2C的效率更高,这看起来十分美好,但是正是这个特点在某些情况下会变成电工们的噩梦。原因有二。

1、硬件上,DR和DSR的交换机制存在缺陷。

2、软件上,因为DR和DSR一共能容纳两个字节的数据,导致接收时候NACK的设置有一定的不可预料性。

 

硬件

硬件I2C上的缺陷,新版英文ErrSheet已经写得很清楚,就不引用了,这里只简单说说要点和一些个人总结。

1、EV7, EV7_1, EV6_1, EV6_3, EV2, EV8 和 EV3 必须在当前字节传输前处理完成,不然,有可能会导致数据出错。

这几个事件都涉及到DR和DSR,个人猜测(主要是有个”may be”才敢猜测)可能是读出或者写入DR的同时DSR被填满或清空,导致数据出错。理想情况下“读出或者写入DR的同时DSR被填满或清空”是不可能发生的,中断一来临的时候,CPU马上处理中断请求,读出或者写入DR数据,这时DSR的数据还是“新鲜滚热辣”的,可能连一位都没有接收或发送。但是,在实际使用时,可能有别的中断优先级比I2C的事件中断要高,I2C事件没有及时处理而出现了上述的情况。所以,ST建议把I2C的事件中断设置成最高优先级。

2、产生STOP前DSR必须为空,不然,会导致DSR里的数据左移一位。

这个没什么好说的,就是一个硬件的BUG,保证发送STOP前DSR没有数据就可以了。

3、总线上,开始条件(S)后没有进行数据传输就马上设置停止条件(P),或者S后忘记P会导致硬件I2C不能再次产生S,必须软复位I2C。

这个ST解释成是,STM32严格按照了I2C的标准,S之后没有数据传输是不能P的。其实这点可以体谅,但是,这点如果没有处理好,总线上的错误会导致STM32 I2C陷入瘫痪。

 

软件

由于DR和DSR的存在,编程上需要一些技巧,新版英文ErrSheet和参考手册(RM0008)都有相关的操作介绍(Closing the communication),排除硬件上的缺陷,编程的难点主要在接收时如何可靠地设置NACK上。

在只有DSR的MCU上设置NACK是非常简单的,在读出倒数第二个数据前设置一下就可以了,但是个方法在似乎在STM32上行不通,因为STM32有DR和DSR,在倒数第二个数据被接收的时候(RxNE置位),马上设置NACK,理想情况下没有任何问题,NACK也被正确的发送,但是如果有其他更高优先级的中断打断了这个过程,NACK就不能及时设置,导致从器件收到的是ACK没有释放总线……

ST提供的资料上(笔者所见),给电工们的建议。

1、接收2个字节或1个字节时,切换GPIO模式为OD,然后软件下拉SCL引脚,使硬件I2C发生时钟延展,把下一个字节开始传输的时机延后,设置完NACK后,再把GPIO设置回AFOD,但是这只能解决小于两个字节的接收。

2、大于2个字节用DMA,DMA可以说是特效药,“屡试不爽”。不过要注意,接收大于或等于2个字节时才能使用DMA,不然不能产生EOT-1事件导致NACK不能正确发送。

3、设置I2C事件中断为最高优先等级。

 

方案

读到这里你可能会想,硬件有缺陷,软件也得这么“猥琐”,可以说是寸步难行。真的没有其他办法了吗?其实,我们可以把DR和DSR两个当一个用,全部判断BTF,不理会TxE和RxE,用时间来换稳定性,慢点就慢点总比没得用好。

[important]

发送时:

开始,发送写地址,器件应答,清ADDR,一字节数据到写DR,硬件把DR数据写入到DSR,当DSR传输完毕时,DR也为空,BTF置位,这时我们再写一字节数据到DR,如此循环,最后一次BTF置位的时候发送P或者重起始(R)。这样操作,“硬件把DR数据写入到DSR”执行的时间是我们可以预料的,不存在上面提及的冲突问题。

接收时:

1、接收一个字节:按照ST给的方法。开始,发送读地址,器件应答,清ADDR前软件下拉SCL,写完NACK、STOP和DR后软件再释放SCL。RxNE时读DR。

2、接收两个字节:也是按照ST的方法。开始,发送读地址,器件应答,设置POS和ACK,下拉SCL,清ADDR,设置NACK,释放SCL。BTF时,软件拉低SCL,发送STOP,读DR,释放SCL,再读DR。

3、接收两个以上字节:开始,发送读地址,器件应答,直接清ADDR。BTF时,读DR一次。再BTF,再读DR一次,如此循环。倒数第二次BTF时设置NACK(注意DR和DSR各有一字节的数据),读DR一次。再等到最后一次BTF时,软件拉低SCL,发送STOP,读DR,释放SCL,再读DR。

4、请注意在读取SR2到操作其他I2C寄存器期间使用软件产生时钟延展。 2016.02.03  thx:iguesser

[/important]

 

干扰

当总线空闲时,无论是SCL的跳变(电平高低高),还是SDA的跳变,都会导致STM32的硬件I2C瘫痪,不能产生下一个S。当总线正在传输数据时,总线上的信号干扰对STM32的硬件I2C来说是致命的。

1、空闲时SDA跳变,会产生一个S和一个P,幸好这个P会产生一个中断,我们可以用一个收到P就软复位硬件I2C的策略。这样能避免空闲时SDA跳变带来的干扰。

2、空闲时SCL跳变,这是一个I2C的错误信号,但是STM32却会认为这是一个S,所以SCL跳变会导致BUSY置位,而且不会像SDA跳变那样会产生一个P中断。如果在单主的情况下,你可以为I2C的S做一个超时,超时了就软复位I2C就可以,当然最简单的方法还是空闲时关闭I2C(PE置零)。在多从机的情况下,只能等待别的主机发送的一个P,或者伺机软复位。

3、传输途中因干扰,产生总线错误(BERR)。单主接收途中出现BERR,可以在关闭硬件I2C后,连续模拟产生9个以上的SCL,在保证SDA为高电平的情况下软复位I2C。

4、传输途中因干扰,导致仲裁丢失(ARLO)。单主时和BERR的处理方法相同。

 

其他

还有什么值得注意的?

很多电工们反映,上电也是一个大问题,I2C一上电就马上BUSY了,第一次的S都不能发送,我是没有遇上这个问题。Google了一下很多都说是初始化顺序的问题。说说我的初始化吧,打开I2C外设的时钟、打开I2C引脚所在的GPIO的时钟、配置 GPIO_AF_OD、 I2C_DeInit、 I2C_Init、 I2C_Cmd,没有什么特别。还有一种可能就是,上电时上电的脉冲干扰了总线,导致某个从设锁死了总线(拉低了SDA)导致的BUSY置位,这个可以用处理BERR的方法,使总线恢复正常。(2012 Jun 6)

[important]

总线上的P产生后最好不要配置CR1的ACK位。STOP发送后配置ACK位——作为主机接收最后一字节时需要发送NACK,同时我们需要响应自己的从机地址,这时需要重新配置ACK为”1″——有可能导致下一次作为主机通信发送地址时,硬件不发送地址而直接发送P——这应该是一个硬件BUG,暂时还没有看见相关资料——具体表现为EV6死循环。推荐的做法是设置P前,软件下拉SCL,设置P,设置ACK,释放SCL,这样总线上的P将在释放SCL后产生。(2012 Jul 3)

[/important]

 

总结

这些都是我调STM32硬件I2C的一些心得。上文提及到的中断接收和发送方法,我用TIM自动更新,产生最高占先优先级的中断,并在中断里停留70us左右,且重装载值是一个素数的情况下,STM32F103VET6 400kHz的I2C跑了近一周没有发现数据错误。

至此,STM32 I2C的问题基本解决,欢迎广大电工们指正、反馈。

转载出处 http://racede.me/talk_about_stm32_i2c_peripheral.html

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
### 回答1: 实现STM32I2C硬件bug版,我们需要注意以下几个关键点。 首先,要正确配置STM32I2C外设寄存器。这些寄存器包括I2C控制寄存器(CR1和CR2)、I2C地址寄存器(OAR1和OAR2)、I2C数据寄存器(DR)等。我们需要根据具体需求设置这些寄存器的值,如通信速率、设备地址、主从模式等。 其次,要注意I2C总线的物理连接。确保SCL(时钟线)和SDA(数据线)正确地连接到I2C设备的相应引脚。同时,应尽可能避免干扰源,例如与其他设备共用同一I2C总线导致的干扰。 接着,需要根据具体应用编写适当的I2C读写操作代码。例如,使用STM32I2C发送启动信号、发送设备地址、发送数据、接收应答等操作。在进行这些操作时,我们要确保操作的顺序和时机正确,以避免读写错误或传输失败。 此外,为了确保I2C通信的可靠性,我们可以考虑使用硬件的错误处理机制。例如,使用I2C的中断或DMA功能,可以在传输过程中检测到错误,并进行相应的处理。这样可以提高系统的稳定性和容错能力。 最后,我们还需要进行适当的测试和调试。可以使用逻辑分析仪或示波器等工具监测I2C信号的波形,以验证数据的正确传输。同时,还应针对不同的应用场景进行全面的功能性测试,以确保I2C硬件实现的完整性和稳定性。 总之,要实现STM32的无bugI2C硬件实现,我们需要正确配置寄存器、注意物理连接、编写适当的读写操作代码、利用硬件的错误处理机制以及进行全面的测试和调试。 ### 回答2: stm32是一款广泛用于嵌入式系统开发的微控制器系列,而I2C是一种常用的串行通信协议。要实现无bug版的stm32 I2C硬件,需要进行以下步骤: 1. 配置I2C控制器:首先,在stm32的开发环境中,我们需要正确设置I2C控制器的时钟频率、传输速率以及其他相关参数。确保配置正确是避免bug的关键。 2. 连接I2C设备:将I2C设备正确连接到stm32单片机的I2C总线上。确保连接正确可靠,并避免电路接地或连接错误导致的bug。 3. 初始化I2C控制器:通过配置寄存器,初始化I2C控制器的工作模式、地址模式、中断/轮询模式等。确保初始化代码正确,以避免I2C通信过程中产生的bug。 4. 完成I2C数据传输:使用I2C控制器提供的函数,实现正确的读写操作。在读写数据之前,首先需要发送起始信号,然后按照I2C协议规定的流程进行数据传输。 5. 错误处理:当I2C通信过程中遇到错误时,需要及时采取措施进行错误处理,如重新初始化I2C控制器、重新尝试传输等。确保错误处理代码的正确性,以避免发生未处理的错误。 6. 测试和调试:在完成硬件实现之后,进行全面的测试和调试工作。验证I2C通信的稳定性和正确性,查找并修复可能存在的bug。 综上所述,实现无bug版的stm32 I2C硬件需要正确配置I2C控制器,并保证正确连接I2C设备,同时需要正确初始化控制器、实现正确的数据传输和错误处理,并进行全面的测试和调试。只有在这些步骤都正确无误的情况下,才能实现无bug版的stm32 I2C硬件。 ### 回答3: 在实现STM32 I2C硬件的无bug版本时,以下是一些关键步骤和注意事项: 1. 确保正确配置I2C控制器:通过设置正确的时钟频率、I2C模式(主/从模式)、地址寄存器和其他必要的寄存器来配置I2C控制器。 2. 确保GPIO引脚正确连接:检查并确保I2C总线的SCL和SDA信号正确连接到正确的GPIO引脚,并配置引脚作为I2C功能。 3. 确保正确的时序:根据连接的外设要求和I2C总线速度设置适当的I2C时序。在启动、停止和数据传输等各个阶段,正确的时序对于I2C通信的稳定性和可靠性非常重要。 4. 使用适当的时钟源:选择适当的I2C时钟源,通常可以选择系统时钟或外部时钟,以确保I2C通信的稳定性和精确性。 5. 使用适当的超时机制:在数据传输时,设置适当的超时机制,以避免I2C通信的无限等待和系统堵塞。 6. 处理错误和中断:对于可能发生的错误和中断,设置适当的中断处理程序或错误处理机制,以处理和纠正传输错误,并记录错误日志供后续调试使用。 7. 测试和验证:在实现完整的I2C通信功能后,进行严格的测试和验证,确保没有任何数据丢失、传输错误或冲突。 8. 清晰的代码和注释:编写清晰、规范的代码,并添加详细的注释,以便于他人理解和维护代码。 综上所述,在实现STM32 I2C硬件的无bug版本时,需要正确配置I2C控制器和GPIO引脚,保证正确的时序和时钟源,并添加适当的超时机制、错误处理和中断处理机制。最后,进行严格的测试和验证,并编写清晰的代码和注释,以确保I2C通信的稳定性和可靠性。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值