关于stm32 i2c的bug,转载一篇
时间:12-12
整理:3721RD
点击:
http://blog.csdn.net/wangyoufeng8889/article/details/9498355
浅谈 STM32 硬件 I2C 的使用 (中断方式 无 DMA 无最高优先级)
分类: stm32 2013-07-26 17:02 1215 人阅读 评论 (1) 收藏 举报
stm32
目录 (?)[+]
引子
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)。
转载请注明出处 http://racede.me/talk_about_stm32_i2c_peripheral.html
概览
我们先来看一下 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。
转载请注明出处 http://racede.me/talk_about_stm32_i2c_peripheral.html
干扰
当总线空闲时,无论是 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)
总结
这些都是我调 STM32 硬件 I2C 的一些心得。上文提及到的中断接收和发送方法,我用 TIM 自动更新,产生最高占先优先级的中断,并在中断里停留 70us 左右,且重装载值是一个素数的情况下,STM32F103VET6 400kHz 的 I2C 跑了近一周没有发现数据错误。
至此,STM32 I2C 的问题基本解决,欢迎广大电工们指正、反馈。
转载请注明出处 http://racede.me/talk_about_stm32_i2c_peripheral.html
浅谈 STM32 硬件 I2C 的使用 (中断方式 无 DMA 无最高优先级)
分类: stm32 2013-07-26 17:02 1215 人阅读 评论 (1) 收藏 举报
stm32
目录 (?)[+]
引子
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)。
转载请注明出处 http://racede.me/talk_about_stm32_i2c_peripheral.html
概览
我们先来看一下 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。
转载请注明出处 http://racede.me/talk_about_stm32_i2c_peripheral.html
干扰
当总线空闲时,无论是 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)
总结
这些都是我调 STM32 硬件 I2C 的一些心得。上文提及到的中断接收和发送方法,我用 TIM 自动更新,产生最高占先优先级的中断,并在中断里停留 70us 左右,且重装载值是一个素数的情况下,STM32F103VET6 400kHz 的 I2C 跑了近一周没有发现数据错误。
至此,STM32 I2C 的问题基本解决,欢迎广大电工们指正、反馈。
转载请注明出处 http://racede.me/talk_about_stm32_i2c_peripheral.html