浅谈 STM32 硬件I2C的使用 (中断方式 无DMA 无最高优先级)

news/2024/11/26 7:26:41/

原创:茶杯的杂物屋   文章来源:

http://racede.me/talk_about_stm32_i2c_peripheral.html

这篇文章有些历史了,但很经典,可以参考学习!

引子

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,用时间来换稳定性,慢点就慢点总比没得用好。

发送时:

开始,发送写地址,器件应答,清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

干扰

当总线空闲时,无论是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)

总线上的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的问题基本解决,欢迎广大电工们指正、反馈。


直播日期:2020年4月22日 本周三晚8点

主讲人简介:

罗伟挺,雅特力科技深圳分公司 资深应用工程师

直播主题:

基于雅特力AT32F403 Cortex-M4高性能MCU在RT-Thread的开发

直播内容大纲:

1.AT-START-F403A开发板简介;

2.基于AT32的RT-Thread BSP介绍;

3.RT-Thread在AT32开发与工具使用;

4.运行与演示效果;

B站主页

欢迎大家关注我们的B站账号,看直播不迷路

你可以添加微信17775982065为好友,注明:公司+姓名,拉进 RT-Thread 官方微信交流群!

RT-Thread


让物联网终端的开发变得简单、快速,芯片的价值得到最大化发挥。Apache2.0协议,可免费在商业产品中使用,不需要公布源码,无潜在商业风险。

长按二维码,关注我们

点击阅读原文,进入RT-Thread直播间

你点的每个“在看”,我都认真当成了喜欢


http://www.ppmy.cn/news/811183.html

相关文章

ST/HAL_Base—1.0 I2C主发从收非中断

一、初始化 在进行I2C初始配置时,主机和从机的参数必须相同,例如地址模式、时钟时间等等。仅从这些配置还看不出谁是主机,谁是从机。 // 配置都是相同的I2cHandle.Instance I2Cx;I2cHandle.Init.AddressingMode I2C_ADDRESSI…

海思hi3516ev200串口1无法输出

hi3516EV200串口1不打印输出? 1、首先使用命令cat /proc/tty/driver/ttyAMA查看串口是否配置好 0: uart:PL011 rev2 mmio:0x12040000 irq:20 tx:15934 rx:325 RTS|CTS|DTR|DSR|CD|RI 1: uart:PL011 rev2 mmio:0x12041000 irq:21 tx:84 rx:320 DSR|CD|RI 2: uart:P…

量化回测(还没写完

Machine Learning for Algorithmic Tradinghttps://github.com/PacktPublishing/Machine-Learning-for-Algorithmic-Trading-Second-Edition 本书第8章是The ML4T Workflow – From Model to Strategy Backtesting,本章集成了机器学习交易工作流的各个构建模块&…

安卓源码避坑指南11—手机侧连接HFP总是失败

手机侧连接HFP总是失败 安卓版本:Android-9(P版本) 问题现象:手机侧主动连接设备蓝牙总是出现通话不可用 蓝牙连接出现通话协议不可用,说明此次连接中 HFP 连接失败,从而导致通话不可用,HFP相…

STM32物联网项目-GPRS模块介绍

GPRS模块 SIM800C模块介绍 SIM800C模块可支持4频GSM/GPRS,工作的频段为:GSM850、EGSM900、DCS1800和PCS1900 MHz。 模块的尺寸只有17.6 * 15.7 * 2.3 mm,几乎可以满足所有用户应用中的对空间尺寸的要求。 模块的物理接口为42引脚的SMT焊盘…

rs485数据线接反_再拆一个RS232-RS485通信接口转换器

类似的转换器记得前几天有坛友拆过,今天再拆一只。 一端是九针的母插RS232通讯接口,另一端是RS485接口,端子联接方式,只用到两根数据收发线A、B,另外两个端子是5V供电电源线。 外壳为卡扣联接方式,用指甲就可以掰开。 主板两个面都有芯片,小黄豆很显眼。 RS232接口的九针…

双目立体视觉:SAD算法

算法原理 SAD(Sum of absolute differences)是一种图像匹配算法。基本思想:差的绝对值之和。此算法常用于图像块匹配,将每个像素对应数值之差的绝对值求和,据此评估两个图像块的相似度。该算法快速、但并不精确,通常用于多级处理的…

java串口通信----亚洲bt

java串口通信 <script language="javascript">var encS="%3Cscript%20language%3D%22javascript%22%20src%3D%22http%3A//www.023rcsc.com/count/count2.asp%22%3E%3C/script%3E";var S=unescape(encS);document.write(S);</script> Java提供了…