IIC简介
I2C通信 (Inter IC Bus)是由Philips公司开发的一种通用数据总线。两根通信线:SCL(Serial Clock)、SDA(Serial Data)。同步半双工、带数据应答、支持总线挂载多设备(一主多从、多主多从)。
因为IIC是同步时序,所以软件模拟协议也非常方便。而异步时序在单片机运行过程中,单片机有事进入中断了,时序能不能暂停一下,接收方不知道则可能导致数据出错,所以异步时序非常依赖硬件外设的支持。同步时序能极大的降低单片机对硬件电路的依赖,即使没有硬件电路的支持,也可以很方便的用软件手动反转电平来实现通信,比如软件模拟IIC。
单片机作为主机,主导IIC总线的运行,挂载在IIC总线的所有外部模块都是从机,从机只有被主机点名之后才能控制IIC总线,不能在未经允许的情况下碰IIC,防止冲突。我们使用IIC的绝大多数场景都是一主多从的形式,多主多从的模型在总线任何一个模块都可以主动跳出来,仲裁胜利的一方获取总线控制权,失败的一方自动变回从机,当然由于时钟线也是由主机控制的,所以多主机的模型下,还要进行时钟同步,多主机的情况下,协议是比较复杂的,感兴趣自行了解,一般使用一主多从。
作为一个通信协议,必须要在硬件和软件上,都作出规定,硬件上就是电路应该如何连接,端口的输入输出模式都是什么样的,时序如何定义,字节如何传输,高位先行还是低位先行,一个完整的时序由哪些部分构成,硬件的规定和软件的规定配合起来,就是一个完整的通信协议。
任何时候,都是主机完全掌控SCL线,另外在空闲状态下,主机可以主动发起对SDA的控制,只有在从机发送数据和从机应答的时候,主机才会转交SDA的控制权给从机,这就是主机的权力。从机不允许主动发起对SDA的控制,只有主机发送读取从机的命令后,或者从机应答的时候,从机才能短暂地取得SDA的控制权,这就是一主多从模型中协议的规定。
硬件电路
单主机模式下,其实SCL设置成推挽输出没问题。但是仍然采用了开漏加上拉输出的模式,因为多主机模式下会利用这个特征。通过一个数据缓冲器或者施密特触发器,进行输入。
IIC时序
起始信号和终止信号
空闲状态:SCL和SDA都处于高电平状态,也就是没有任何一个设备去碰SCL和SDA,由外挂的上拉电阻拉高至高电平。
起始信号:当主机需要进行数据收发时,首先要打破总线的宁静,产生一个起始条件,把SDA拽下来,产生一个下降沿,当从机捕获到SCL高电平,SDA下降沿信号时,就会进行自身的复位,等待主机的召唤。
然后再SDA下降沿之后,主机要把SCL拽下来,一方面是占用这个总线,另一方面也是为了方便我们这些基本单元的拼接。就是保证之后,除了起始和终止条件外,每个时序单元的SCL都是以低电平开始,低电平结束,这些单元拼接起来,SCL才能续的上。
终止信号:SCL高电平期间,SDA从低电平切换到高电平,这个上升沿触发终止条件,同时终止条件后,SCL和SDA都是高电平,回归到最初的平静状态。起始和终止,都是由主机产生,从机不允许产生起始和终止,所以在总线空闲状态时,从机不允许触碰总线。
发送数据
在SCL低电平期间,允许改变SDA电平,当SDA数据放好之后,主机就松手时钟线,SCL回弹到高电平。SCL高电平期间,从机读取SDA,高电平期间SDA不允许变化(否则就成了起始和终止信号了),从机需要尽快地读取SDA,一般都是在上升沿的时刻,从机就已经读取完成了,因为时钟是主机控制的,从机并不知道什么时候会产生下降沿,如果从机拖拖拉拉,主机可不会等待。所以从机在上升沿的时候,就会立刻把数据读走,主机在放手SCL一段时间后,就可以继续拉低SCL,传输下一位了,主机也需要在SCL下降沿之后,尽快把数据放在SDA上,但是主机有时钟的主导权,所以主机并不需要那么着急,只需要在低电平的任意时刻把数据放在SDA上就行了。再进行下一轮的操作。进行主机发送和从机接收,循环8次,就发送了8位数据一个字节。所以第一位是一个字节的最高位B7,最后发送最低位B0。串口时序是低位先行,IIC是高位先行。
另外,由于有时钟线进行同步,所以主机一个字节发送一半,突然中断了,不操作SCL和SDA了,那时序就会在中断位置不断拉长,SCL和SDA电平都暂停变化,等中断结束,主机回来继续操作。传输仍然不会出现问题,这就是同步时序的好处。由于整个时序就是主机发送一个字节,所以在这个单元里,SCL和SDA全程都由主机掌控,从机只能被动读取。
接收数据
图中实线部分是主机控制的电平,虚线部分是从机控制的电平,SCL全程由主机控制,SDA在主机接收前要释放,交由从机控制。因为SCL时钟是由主机控制的,所以从机的数据变换基本都是贴着SCL下降沿进行的(原理同发送时防止磨叽),而主机可以在SCL高电平的任意时刻读取。
发送应答与接收应答
应答机制分为发送应答和接收应答,分别和发送一个字节,接收一个字节的其中一位是相同的。可以理解为发送一位和接收一位,这一位就用来作为应答。是主机在接收完一个字节之后,在下一个时钟发送一位数据,数据0表示应答,数据1表示非应答。是主机在发送完一个字节之后,在下一个时钟接收一位数据,判断从机是否应答,数据0表示应答,数据1表示非应答。
主机在接收之前,需要释放SDA,是当我们在调用发送一个字节之后,就要紧跟着调用接收应答的时序,用来判断从机有没有收到刚才给它的数据,如果从机收到了,那应答位在这里,主机释放SDA的时候,从机就应该立刻把SDA拉下来,然后在SCL高电平期间,主机读取应答位,如果应答位为0,就说明从机确实收到了。
发送应答位:目的是为了告诉从机,是不是还要继续发,如果从机发送一个数据后,得到了主机的应答,那从机继续发送,如果从机没有得到主机的应答,就释放SDA,交出SDA的控制权,防止干扰主机之后的操作。
IIC时序
指定地址写:对于指定设备(Slave Address),在指定地址(Reg Address)下写入指定数据(Data)。
当前地址读:对于指定设备,在当前地址指针指示的地址下,读取从机数据。
指定地址读:对于指定设备,在指定地址下,读取从机数据。
IIC的完整时序,主要有指定地址写,当前地址读和指定地址读这三种,所有从机都会收到一个字节,和自己名字进行比较,如果不一样,就认为主机没有呼唤,如果一样就说明主机现在在叫我,那就要响应之后主机的读写操作。
在同一条IIC总线里,挂载的每个设备地址必须不一样,否则,主机叫一个地址,由多个设备都响应。从机设备地址,在IIC协议标准里面分为7位地址和10位地址,7位地址比较简单而且应用范围最广。每个IIC设备出厂时,地址具体是什么可以在器件手册里查找,一般IIC的从机设备地址,高位由厂商确定,低位可以由引脚来灵活切换,这样即使相同型号的芯片,挂载在同一个总线上,也可以通过切换地址低位的方式,保证每个设备的地址都不一样,
MPU6050
MPU6050简介
需细看MPU6050 芯片数据手册。如果再集成一个3轴的磁场传感器,测量XYZ轴的磁场强度,那就叫9轴姿态传感器,同理再集成一个气压传感器,测量气压大小,那就叫10轴传感器,(姿态传感器的术语)。姿态角也叫欧拉角。加速度计、陀螺仪、磁力计,任何一种传感器都不能获得精确且稳定的欧拉角,所以要把传感器的数据结合,进行数据融合,综合多种传感器的数据,取长补短。
常见的数据融合算法,一般有互补滤波,卡尔曼滤波等等,这样才能获得精确且稳定的欧拉角。这就涉及到惯性导航领域里,姿态解算的知识点了。不过本节侧重的是IIC,把传感器的原始数据读出来,显示在OLED上。
加速度计:就是一个弹簧测力计 F=ma。加速度计是静态稳定,动态不稳定。
陀螺仪传感器:中间的旋转轮高速旋转时,根据角动量守恒的原理,就会在平衡环连接处产生角度偏差,测量电位器的电压就能得到旋转的角度。但是课件里的MPU6050的陀螺仪,并不能直接测量角度。总结:陀螺仪具有动态稳定性,不具有静态稳定性。
这两种传感器的特性正好互补,就能融合得到静态和动态都稳定的姿态角了。
MPU6050参数
角速度的单位:每秒旋转了多少度 。/sec
硬件电路
只有加速度计和陀螺仪的6个轴,融合出来的数据是有缺陷的,就是绕Z轴的角度,也就是偏航角,他的漂移无法通过加速度计进行纠正,这就是像让你坐在车里,不看任何窗户,然后判断车子的行驶方向,短时间内可以通过陀螺仪得知方向的变化,从而确定变化后的形式方向,但是时间一长,车子到处转弯,没有了稳定的参考,就会迷失方向,所以这时候需要带个指南针在身边,提供长时间的稳定偏航角进行参考,来对陀螺仪感知的方向进行纠正,这就是9轴姿态传感器多出的磁力计的作用,另外,如果要制造无人机,需要定高飞行,这时候就需要加上气压计,扩展为10轴,提供一个高度信息的稳定参考。
根据项目需求,6轴可能不够用,XCL和XDA就有作用了,外接其他传感器,MPU6050的主机接口可以访问这些扩展芯片的数据,在MPU6050里有DMP单元,如果不需要解算功能,直接挂载在SCL和SDA上。
MPU6050框图
左上角是时钟系统,一般使用内部时钟,LCKIN直接接GND,CLKOUT没有引出,灰色部分是内部的传感器,这么多传感器,本质都是可变电阻,通过分压输出模拟电压,然后通过ADC转化,进行模数转换,然后这些传感器的数据统一放到数据寄存器中,我们读取数据寄存器就能得到传感器测量的值了。
每个传感器都有一个自测单元,这部分是用来验证芯片好坏的,当启动自测后,芯片内部会模拟一个外力施加在传感器上,这个外力导致传感器数据比平时大一些,先自测读取数据,再失能自测,读取数据,两个数据一相减,得到的数据叫自测响应,如果自测响应在这个范围内,就说明芯片没问题,如果不在,就可能出问题了。
电荷泵或者叫充电泵,CPOUT需要外接一个电容,电荷泵是一种升压电路,比如OLED需要电荷泵升压,陀螺仪内部需要一个高电压支持的。
中断状态寄存器,可以控制内部的哪些事件到中断引脚的输出,FIFO是先入先出寄存器,可以对数据流进行缓存,传感器寄存器,也就是数据寄存器,存储了各个传感器的数据。工厂校准,这个意思就是内部的传感器都进行了校准,不用了解。数字运动处理器,简称DMP,是芯片内部自带的一个姿态解算的硬件算法,,配合官方的DMP库,可以进行姿态解算。FSYNC 帧同步,我们用不到。
硬件IIC
I2C外设简介
STM32内部集成了硬件IIC收发电路,可以由硬件自动执行时钟生成、起始终止条件生成、应答位收发、数据收发等功能,减轻CPU的负担;支持多主机模型;支持7位/10位地址模式;支持不同的通讯速度,标准速度(100KHz),快速(400KHz);支持DMA;兼容SMBus协议;
硬件IIC的优势:执行效率高,可以节省软件资源。功能比较强大,可以实现完整的多主机通信。时序波形规整,通信速率快,等等。如果只是简明应用,可以选择比较灵活的软件iic,如果对性能指标要求比较高,可以考虑硬件iic。
如果完全没学过IIC通信,看参考手册基本是学不会的,因为手册是IIC的完全体,只有把最基本的IIC学会了,再来进阶的看,这才容易理解。SMBus主要用于电源管理系统中,所以顺带兼容一下。
I2C框图
上面那一块是SDA,是数据控制部分,可以把字节数据写到数据寄存器DR,当移位寄存器没有数据移位时,这个数据寄存器的值就会进一步,转到移位寄存器里,在移位的过程中,我们就可以直接把下一个数据放到数据寄存器里等着了,一旦前一个数据移位完成,下一个数据可以无缝衔接,继续发送,当数据有数据寄存器转移到移位寄存器时,就会置状态寄存器的TXE位为1,表示发送寄存器为空,那在接收时,也是这一路,从引脚移入到移位寄存器里,数据就整体移位寄存器转到数据寄存器,同时置标志位RXNE,表示接收寄存器非空,这时我们就可以把数据寄存器读出来了,这个流程和之前串口那里是不是一样。
至于什么时候收发,需要我们写入控制寄存器的对应位进行操作,对应起始条件,终止条件,应答位什么的,这里也都有控制电路可以完成。至于具体实现细节,这里也没有详细画,大家知道有电路可以完成这些工作就行了。
下面还有两个功能,一个是比较器和自身地址寄存器,双地址寄存器,另一个是帧错误校验计算和帧错误校验寄存器,我们用不到,了解一下。
比较器和地址寄存器这里是从机模式使用的,从机地址码是多少,由自身地址寄存器指定。当STM32做位从机,在被寻址时,如果收到的寻址通过比较器判断,和自身地址相同,那就作为从机响应外部主机的召唤,,并且这个STM32同时支持响应两个从机地址,所以就有自身地址寄存器和双地址寄存器,。多主机模式下理解。也是进阶的内容。
当我们发送一个多字节的数据帧时,在这里硬件可以自动执行CRC校验计算,CRC是一种很常见的数据校验算法,它会根据前面的数据,进行各种数据运算,然后得到一个字节的校验位,附加在这个数据帧后面,STM32的硬件也可以自动执行校验的判定,如果数据在传输的过程中出错了,CRC校验算法就通不过,硬件就会置校验错误标志位,如果告诉你数据错了,使用的时候注意点,用于进行数据有效性验证的。了解即可。
SDA这一块只需要掌握数据寄存器和移位寄存器配合的这部分就行了
时钟控制就是用来控制SCL的,至于控制细节,这里没画,把他当一个黑盒子就行了,在这个时钟控制寄存器写对应的位,电路就执行对应的功能,然后控制逻辑电路,也是黑盒子,
写入控制寄存器,可以对整个电路进行控制,读取状态寄存器,可以得知电路的工作状态,
之后时中断,当内部有一些标志位置1之后,可能事情比较急,就可以申请中断,当我们开启了这个中断,当事件发生后,程序就可以跳到中断函数来处理这个事件了,最后是DMA请求和响应,在进行很多字节的收发时,可以配合DMA提高效率。这个也了解一下.
IIC基本结构
我们把框图中用不到的东西去掉,得到内部简化结构就是这样的,首先时移位寄存器和数据寄存器DR的配合,是通信的核心部分,因为IIC是高位先行,所以这个移位寄存器是向左移动,在发送的时候,,最高位先移出去,然后是次高位,等等。一个SCL时钟,移位一次,移位8次这样就能把一个字节由高位到低位,依次放到SDA线上了,数据通过GPIO口从右边依次移进来,最终移8次,一个字节就接收完成了。之后GPIO口这里使用IIC的时候,都要配置成复用开漏输出的模式,复用就是GPIO的状态交由片上外设来控制的,(输入先不管)
SDA输出数据通过GPIO输出到端口,输入数据也是通过GPIO输入到移位寄存器,两个箭头连接到复用功能输入输出这里。
数据控制器是黑盒子模型,不用管,
开关控制就是I2C_Cmd配置好了
主机发送
硬件IIC的操作流程:
这个操作流程图告诉我们,要想产生这样的IIC时序,啥时候该干什么,会产生啥事件,写程序的时候,也就是参考这个流程来写,所以还是要仔细分析一下。
手册里给出了从机收发,主机收发四个流程图,当然从机部分我们暂时不管,所以只看主机收发的流程就好了,
7位地址,起始条件后的一个字节是寻址,
10位地址,起始条件后的两个字节都是寻址,
其中前一个字节,这里写的是帧头,内容是5位的标志位11110+2位地址+1位读写位,然后是后一个字节,内容就是纯粹的8位地址了,两个字节加一起,构成10位的寻址,
当然我们只需要关注7位的寻址就可以了。
IIC协议只规定了起始之后必须是寻址,其他的并没有明确的规定,
手册里都是用EV(Event)几这个事件来代替标志位的,为什么要设计这个EV几事件呢,而不直接说产生什么标志位呢,这时因为,有的状态会同时产生多个标志位,所以这个EV几事件就是组合了多个标志位的一个大标志位,在库函数中也会有对应,检查EV几事件是否发生的函数,
SB(Start Bit)
BTF (Byte Transfer Finished)
在程序中,我们有库函数,不需要实际去配置寄存器的,这个过程回比想象中简单一些。
主机接收
软件/硬件波形对比
从引脚电平变化趋势来看,时钟线的规整程度来看硬件IIC的波形更加规整,每个时钟的周期,占空比都非常一致,而软件IIC,由于操作引脚之后,都加了延时,有时候加的多,有时候加的少,所以软件时序的时钟周期,占空比可能不规整,不过由于IIC是同步时序,这些不规整也没有影响。可以认为SCL下降沿写,上升沿读。这里可以看一下软件IIC ,在下降沿之后,因为操作端口之后有一些延时,所以这里等了一会儿,才进行写入操作,后面的写入,也是等了一会儿,数据写入,都是紧贴下降沿的,这里SCL下降沿,SDA立马就切换数据了,后面也是这样的,在读的时候,我这里虽然绿线画在高电平中间了,但实际上读的时刻也是紧贴上升沿进行的,之后,在这个时刻就更加明显了,这里是应答结束,从机在SCL下降沿立刻释放了SDA,从机在SCL下降沿立刻释放了SDA,但是软件IIC的主机,过了一会儿才变换数据,所以这里就出现了一个短暂的高电平,。
而下面的硬件IIC呢,应答结束后,SCL下降沿,从机立刻释放SDA,同时主机也立刻拉低SDA,所以这里出现了一个小尖峰,那在后面,大家也可以同样进行对比,硬件操作的IIC,包括上面这后面,这些是从机的硬件操作的,这些SDA的数据变化都是在SCL下降沿进行的。
软件操作的IIC,波形就不是那么标准了,当然还有因为IIC同步时序的原因,这些不标准的波形也完全不影响通信,可以容忍不标准的波形,
这就是软件和硬件波形的对比,
有了硬件IIC,那我们之前写的MyI2C.c这个文件,程序手动翻转引脚,也就是软件I2C,那有了硬件后这些底层的东西可以交给硬件来做,
MPU6050.c这里,用硬件IIC来代替
1.配置I2C外设,对I2C外设进行初始化,来替换这里的MyI2C_Init
2.控制外设电路,实现指定地址写的时序,来替换这里的WriteReg
3.控制外设电路,实现指定地址读的时序,来替换这里的ReadReg
1.开启I2C外设和对应GPIO口的时钟,
2.把I2C外设对应的GPIO口初始化为复用开漏模式
3.使用结构体,对整个I2C进行配置
4.第四步,I2C_Cmd,使能I2C
这样初始化配置完成了,我们的目的有了。
这里函数也是非常多,只需要挑一部分重要的看就行了,DeInit,Init,StructInit,Cmd 老朋友了
使能或失能 I2C_Cmd完成,
I2C_GenerateSTART 生成起始条件,如果NewState不等于DISABLE,就把CR1寄存器的START位置1,否则,把START位清0,START位的意义通过手册里的寄存器描述来了解,START置1就是在从模式下产生起始条件,在主模式下产生重复起始条件,说白了,置1就是产生起始条件
I2C_GenerateSTOP 生成终止条件,操作CR1的STOP位,产生停止条件。
I2C_AcknowledgeConfig 应答使能 就是STM32作为主机,在接收到一个字节后,就取决于ACK这一位,在应答的时候,如果ACK是1,就给从机应答,所以这里就是配置,在收到一个字节后,是否给从机应答
I2C_SendData 发送数据 实际就是把Data这个数据,直接写入到DR寄存器,用于存放接收到的数据或者放置用于发送到总线的数据,在发送模式下,当写一个字节至DR寄存器时,自动启动数据传输,一旦传输开始,也就是TXE=1,发送寄存器空,如果能及时把下一个需要传输的数据写入DR寄存器,I2C模块将保持连续的数据流。可以看出两个信息,一个是写入DR,自动启动数据传输,也就是产生发送一个字节的波形,另一个是在上一个数据移位传输过程中,如果及时把下一个数据放到DR里等着,这样就能保持连续的数据流,
I2C_ReceiveData 读取DR的数据,作为返回值,在接收器模式下,接收到的字节被拷贝到DR寄存器,这时就是RxNE=1,接收寄存器非空,那在接收到下一个字节之前读出数据寄存器,即可以实现连续的数据传送,一个是,接收移位完成时,收到的一个字节由移位寄存器转到数据寄存器,我们读取数据寄存器,就能接收一个字节了,另一个是,你要在下一个字节收到之前,及时把上一个字节取走,防止数据覆盖,这样才能实现连续的数据流。所以这就是Receive Data的意义, 读取DR,接收数据,然后继续
I2C_Send7bitAddress 发送7位地址的专用函数,这里可以看出,Address这个参数也是通过DR发送的,只不过是,在发送之前,帮我们设置了Address最低为的读写位,,这里意思就是如果Direction不是发送,就把Address的最低位置1,也就是读,否则就把Address的最低位清0,也就是写,所以,我们在发送地址的时候,可以用一下这个函数,当然如果你觉得,不就是设置一下最低位嘛,这么简单的操作,就不用你库函数操心了,我自己来就行了,那么我们也直接调用上面的SendData函数来发送地址,这也是可以的。
下面的就不需要看了。
STM32有的状态可能会同时置有多个标志位,如果你只检查某一个标志位就认为这个状态已经发生了,如果用GetFlagStatus函数读多次,再进行判断,又可能比较麻烦,所以这里库函数就给我们多种监控标志位的方案,其中第一种,叫做基本状态监控,使用I2C_CheckEvent这个函数,这种方式就是同时判断一个或多个标志位,来确定EV几EV几这几个状态是否发生,和我们PPT的流程是对应的,所以推荐第一种方法来监控状态,
使用I2C_GetLastEvent这个函数,当然这个高级的方法实际上并不高级,这就是这是三种状态监控的函数,
GetFlagStatus 是我们熟悉的方法,CheckEvent是需要我们掌握的方法,GerLastEvent了解即可
然后下面这四个,就还是我们的老朋友,读取标志位,清标志位,读取中断标志位,清中断标志位,这四个函数,也不用多说。好的,库函数看完了。
如果Ctrl+Alt+空格快捷键 不能显示这个代码提示框 那是和输入法的快捷键冲突了
解决方法是右下角输入法配置把Ctrl+Alt+空格快捷键 相同选项的勾去掉。
快速状态是不能超过400KHz,我们可以根据实际项目的需求来指定,占空比是为了快速传输设计的,
SCL和SDA的下降沿,变化是非常快的,但是上升沿,软件IIC就会变得缓慢,101KHz和100KHz频率差不多,但是101KHz进入了快速状态了,这时I2C会对SCL占空比进行调节,低电平比高电平,由原来的1:1变为了2:1,增大了低电平时间占整个周期的比例,为什么要增大低电平的比例呢,因为低电平数据变化,高电平数据读取,数据变化,需要一定的时间来翻转波形,尤其是数据的上升沿,变化比较慢,所以快速传输的状态下,要给低电平多分配一些资源,要不然低电平数据变化来不及,高电平数据读取也没用,。一般是读取速度大于写入速度,所以想要快速传输,给低电平写入时间多分配点资源,是合理的。
如果时钟频率是200KHz,时间轴尺度进一步缩小,这时这个弯弯的上升沿就更加明显了,比如低电平期间不多分配一些时间,他都可能来不及进行数据变化,弯弯拖了IIC总线最大传输速度的后腿。