image.png

inter(内部) ic(集成电路) bus(总线):集成电路间总线

第一个是MPU6050陀螺仪加速度传感器,实现的目标,软件或者硬件实现I2C读取MPU6050

image.png

其实逻辑就是通过软件I2C通信,对MPU6050芯片内部的寄存器进行读写, 写入到配置寄存器中,就可以对外挂的这个陀螺仪加速度传感器进行配置,读出数据寄存器呢,就会获得外挂模块的数据,最后把读出的数据显示在OLED上。

分别是设备的ID号,一般用来测试设备的读取功能是不是正常。

左边三个是加速度传感器得出的数据,分别是X,Y,Z轴的加速度,右边三个,是陀螺仪传感器的数据,分别是X,Y,Z轴的角速度。

第一张图第二个器件是OLED模块,可以显示字符图片等信息,也是用的I2C协议,图三是ACT24C02,是存储器模块,图四,DS3231实时时钟模块,也是使用I2C通信。

SCL串行时钟线(可实现同步),SDA串行数据线(可实现半双工,一根线实现数据的收发)。

/*****************/

全双工模式:

两个独立的通道用于接收和发送,可以同时进行,互不干扰。 当端口设置为全双工模式时,端口可以在发送数据包的同时接收数据包。

半双工模式:

半双工:接收和发送共用同一个信道,只能同时进行发送或接收。 因此,半双工可能会发生冲突。 当端口设置为半双工模式时,该端口只能同时发送数据包或接收数据包。

/****************/

一主多从的意思就是,单片机作为主机,所有挂载着I2C的设备都作为从机,被主机指导工作,只有在被主机点名的时候才进行工作。

多主多从的模型,就是从机可以随时打断主机的控制,只要经过I2C协议进行判决可以取代,就可以成为主机,主机也会变成从机。由于时钟线是主机控制的,这时候要进行时钟同步。

I2C典型电路图:

image.png

每条I2C总线上连接的设备都应该有一个唯一的地址,在起始条件之后,主机还会发送一个字节代表从机的地址名称,用来识别从机,该地址与从机各设备地址对比,如果相同则接收接下来的时序,否则不管,从机设备的地址在I2C标准协议里分为7位地址以及10位地址,7位地址应用最广泛也最简单,具体地址厂商规定,数据手册有写。如果相同的设备挂载到一条总线上,就可以用到地址的可变部分了,一般地址的后几位可以进行改变,比如MPU6050,最后一位地址可以由它上面的AD0引脚确定,引脚接低电平就是0,高电平就是1.

左边的CPU是我们的单片机,作为总线的主机,主机的权利很大,对SCL线完全控制,任何时候,都是主机掌握SCL线,另外在空闲的时候,主机可以主动发动对SDA的控制,只有在从机发送数据或者从机应答的时候,主机才会转交SDA的控制权给从机, 这就是主机的权利,被控IC都是挂在I2C总线上的从机,从机可以是姿态传感器,OLED,存储器,时钟模块等。

从机的权利很小,对于SCL时钟线只能被动地读取,对于SDA数据线,从机不允许主动发动对SDA的控制,只有在主机发送读取从机的命令后,或者从机应答的时候,从机才能暂时的获得SDA的控制权。

因为现在是一主多从,主机拥有SCL的绝对控制权,所以主机的SCL可以配置成推挽输出,所有从机的SCL都配置成浮空输入或者上拉输入。

数据流向是主机发送,所有从机接收,但是到SDA线这里就比较麻烦了,因为这是半双工的协议,所以主机的SDA在发送的时候是输出,在接收的时候是输入。同样,从机的sda也会在输入和输出之间反复切换。如果你能协调好输入输出的切换时机。那其实也没问题,但这样做如果总线时序没协调好,极有可能发生两个引脚同时处于输出的状态,如果这时又正好是一个输出高电平,一个输出低电平,那这个状态就是电源短路。

这个状态是要极力避免的,所以为了避免总线没协调好,导致电源短路这个问题。I2C的设计是禁止所有设备输出强上拉的高电平,采用外置弱上拉电阻加开漏输出的电路结构,这两点规定就是上面的这两条啊,设备的SCL和SDA均要配置成开漏输出模式,SCL和sda各添加一个上拉电阻,阻值一般为4.7千欧左右,对应下面这个图呢,就是这样,所有的设备,包括CPU和被控IC,它引角的内部结构都是右图这样的啊,左边儿这一块儿是SCL的结构,这里SCLK就是SCL的意思啊,右边儿这一块儿是sda的结构,这里data就是sda的意思。

首先引脚的信号进来都可以通过一个数据缓冲器或者是斯密特触发器进行输入,输入电路没有任何影响啊,所以任何设备在任何时刻都是可以输入的,但是在输出的这部分采用的是开漏输出的配置。正常的推推挽输出如右下角所示:

image.png

上面一个开关管儿接到正极,下面一个开关管接到负极。上面导通输出高电平啊,下面导通输出低点名,因为这是通过开关管直接接到正极和负极的,所以这个是强上拉和强下拉的模式,而开漏输出呢,就是去掉这个强上拉的开关管,输出低电平时下管导通是强下拉,输出高电平时下管断开,但是没有上管的,此时引脚处于浮空的状态,这就是开漏输出。

和这里图示是一样的,输出低电平这个开关管导通直接接地强下拉。

image.png

输出高电平,这个开关管断开,人家什么都不接,处于浮空状态,这样的话,所有的设备都只能输出低电频,而不能输出高电瓶。

为了避免高电平造成的影引脚浮空,这时就需要在总线外面SCL和SDA各自外置一个上拉电阻,这是通过一个电阻拉到高电平的,所以这是一个弱上拉。

用我们之前的弹簧和杆子的模型来解释就是SCL或sda就是一根杆子,为了防止有人向上推杆子,有人向下拉杆子造成冲突外,我们就规定所有的人不准向上推杆子,只能选择向下拉或者放手。然后我们在外置一根弹簧向上拉,你要输出低电平就往下拽,这个弹簧肯定算不赢的你的呀,所以弹簧被拉伸,杆着处于低电平状态。

你要输出高电平,就放手杆子,在弹簧的拉力下回弹到高电平。这就是一个弱上拉的高点平,但是完全不影响数据传输,这样做有什么好处呢?

第一,完全杜绝了电源短路现象,保证电路的安全。使用开漏输出,只能输出低电平,那不存在所谓的电源短路。

第二,避免了引脚模式的频繁切换。开漏加弱上拉的模式,同时兼具了输入和输出的功能,你要是想输出,因为开漏模式下输出高电平就相当于断开硬件,所以在输入之前可以直接输出高电平不需要再切换成输入模式。

第三就是这个模式会有个线与的现象,就是只要有任意一个或多个设备输出了低电平,总线就处于低电平,只有所有的设备都输入高电平,总线处于高电平,I2C可以利用这个电路特征,执行多主机模式下的时钟同步和总线仲裁。所以这里SCL虽然在一主多从模式下可以用推挽输出,但是它仍然采用了开漏加上拉输出的模式,因为在多主机模式下会利用到这个特征。

image.png

 在总线处于空闲状态时SCL和SDA都处于高电平状态也就是没有任何一个设备去碰s cl和sda,SCL和sda由外挂的上拉电阻拉高至高电平,总线处于平静的高电平状态当主机需要进行数据收发时首先就要打破总线的宁静,产生一个起始条件这个起始条件就是SCL属于高电平不去动它,然后把sda拽下来产生一个下降沿,当从机捕获到这个SCL高点平SDA下降沿信号时,就会进行自身的复位,等待主敌的召唤,然后在sda下降之后主机要再把scl拽下来拽下来,scl一方面是占用这个总线,另一方面也是为了方便我们这些基本单元的拼接,就是我们之后会保证除了起始和终止条件,每个时序单元都是以低电平开始,低电平结束。

终止条件是SCL高电平期间,sda从低电平切换到高电平,SCL先放走回弹到高电平,Sda再放走回弹高电平。产生一个上升沿,这个上层沿触发终止条件,同时终止条件之后SCL和sda都是高电平,回到最初的平静状态。这个起始条件和终止条件就类似串口时序里的起止位和停止位,一个完整的数据帧总是以提示条件开始,终止条件结束。起始和终止都是由主机产生的,从机不允许产生起始和终止,所以在总结空闲状态时,从机必须始终双手放开,不允许主动跳出来去碰总线,如果允许的话,那就是多主机模型。

image.png

在起始条件之后,这时就可以紧跟着一个发送一个字节的时序单元,就是SCL低电平期间,主机将数据位依次放在SDA线上高位先行,然后把SCL置于高电平,从机将在SCL高电平期间读取数据位。

所以SCL高电平期间SDA不允许有数据变化,一次循环上述过程八次即可发送一个字节,起始条件之后第一个字节也必须是主机发送的,就是最开始SCL低电平主机如果想发送零,就拉低sda到低电平,如果想发送一,就放手sda回弹到高电平,在SCL低电平期间允许改变sda的电平,当这一位放好之后,主机就松手使总线SCL回弹的高电平,在高定平期间是从机读取sda的时候,所以高电平期间sda不允许变化,SCL处以高点平之后,从机需要尽快的读取,SCL一般都是在上升沿这个时刻,从机就已经读取完成了,因为始终是主机控制的,从机并不知道什么时候就会产生下降沿了,你从机要是磨磨唧唧的,主机可不会等你的,所以从机在上升沿时就会立刻把数据读走,那主机在放手SCL一段时间后,就可以继续拉低SCL传出下一位了。主机也需要在SCL下降沿之后尽快把数据放在SDA上,但是主机有时钟的主导权,所以主机并不需要那么着急,只需要在低电平的任意时刻把数据放在sda上就行了,晚点也没关系啊,数据放完之后,主机再置于SCL高电平,从机读取这一位。

主机拉低SCL,把数据放在SDA上,主机松开SCL重计读取sda的数据,在SCR的同步下,依次进行主机发送和重机接收,循环八次就发送了八位数据,也就是一个字节。另外注意啊,这里是高位线性,所以第一位是一个字节的最高位B7。然后依次是次高位B6等等啊,最后发送最低位B0,这个和串口是不一样的,串口时序是低位先行,这里I2C是高位先行,这个要注意一下,另外由于这里有时钟线进行同步,所以如果主机一个字节发送一半儿,突然进中断了,不操作SCL和sda的,那时序就会在中断的位置不断拉长,SCL和sda电平都暂停变化,传输也完全暂停,等中断结束后,主机回来继续操作,传输仍然不会出问题,这就是同步时序的好处,最后就是由于这整个时序是主机发送一个字节,所以在这个单元里SCL和sda全程都由主机掌控,从机只能被动读取,就是发送一个字节的时序。

 

接收一个字节:

image.png

 主机在接收之前,相当于释放SDA,也就相当于切换成输入模式,总线是线与的模式,只要有一个是低电平,总线都是低电平,所以要释放SDA,这样SDA才会有高电平波动。

SDA下面那条线是主机,上面那条线是从机,主机要先释放SDA交给从机控制SDA总线,如果从机要发送0,就置低电平,把电平拉低,在SCL为高电平的时候,把数据读取过去,在SCL为低电平的时候,从机可以改变高低电平。主机可以在SCL高电平的任意时刻读取数据,而读取完了之后,SDA就可以变换数值了,所以他基本是贴着SCL下降沿变化的。

image.png

发送应答以及接收应答的时序,分别跟发送一个字节、接收一个字节的一位的时钟是相同的。

发送应答:发送应答位的目的就是,告诉从机是否要继续发,如果从机发送一个数据后,得到了主机的应答,那从机还会继续发送,否则从机释放SDA,交出SDA控制权,防止干扰主机后续操作。

主机发送一位数据,告知从机要不要接着发送数据,若为非应答,从机停止发送数据,把SDA控制权交还主机,若为0从机接着发送。

接收应答:当主机在调用发送一个字节之后,就要紧跟着调用接收应答的时序,用来判断从机是否接收到主机的数据,如果从机接收到了,那在应答位这里,主机置高电平释放SDA的时候,从机就把电平拉下来,在SCL高电平期间,主机读取SDA的数据,否则就是没有收到,或者收到了没有给予回应。

image.png

 空闲的时候,SCL以及SDA都是高电平,主机要给从机写入数据的时候,首先在SCL高电平期间,拉低SDA,产生起始条件,在起始条件之后,紧跟着的时序是发送一个字节的时序,字节的内容是地址+读写位,从机是七位,读写位是一位,加起来刚好是一个字节,8位,分别确定了从机对象以及进行读或者写的操作,在SCL低电平的时间,SDA变换数据,高电平期间,从机从SDA读取数据,绿色的线就是从机读取到的数据,持续八次就发送一个字节,读写位写0表示主机要向从机进行写入操作,1表示之后的时序主机要进行读取操作,发送次序是高位先行,所以所以这里转为十六进制就是0xD0,紧接着的单元就是接收从机的应答位(RA),这时候主机会释放SDA,这时候单独看主机对SDA的电平,是置高电平,如下图所示:

image.png

按照协议来说,这时候从机应该在这个位置把电平拉低,也就是像这样 :

image.png

 应答结束之后,从机放开SDA,主机接管SDA。由于线与的特性,SDA还是保持在低电平的样子,这就代表着从机产生了应答,最后高电平期间,读取SDA的值,发现是0,证明寻址有所应答,找到了地址所在从机,传输没问题,到从机放开SDA控制权的时候,置高电平,这时候总体才高电平,如果主机松手了,高电平,没有从机相应,也就是没有从机把高电平拽下来,在RA位置有个高电平,是从机释放SDA产生的,这时候两个都是高电平,但是随机主机响应,把电平拉低,获得SDA控制权,由于SDA只能在SCL低电平的时候变换数据,所以SDA变化速度很快,上升沿以及下降沿基本同时发生。

后续的第二个字节就可以传输进入从机设备内部了,从机设备可以自己定义第二个字节以及后续字节的作用,一般第二个字节可以是寄存器地址或者指令控制字等等,比如MPU6050就是寄存器地址,AD转换器,第二个地址就是指令控制字,存储器的话,就是存储器地址,第二个字节,就是主机向从机发送了一个0x19的数据,在MPU6050表示,我们要操作0x19地址的寄存器了,接着是从机应答,主机释放SDA,为高电平,从机拽住SDA,低电平,两者相与,低电平,所以RA表现为0。表示接收到了从机的应答。接着在按照这个原理发送一遍,接下来的字节就是写入0x19地址下寄存器的内容了。

这里发送了0xAA的波形,就代表在0x19地址的寄存器下,写入了0xAA这个数据。

P是停止位,在停止条件之前,要拉低SDA,为后续SDA上升沿做准备,先释放SCL在释放SDA,这样就产生了SCL高电平期间,SDA上升沿。发送完毕。

总的来说这个数据帧的意思就是,在地址为….的从机设备中的0x19寄存器中写入0xAA这个数据。

image.png

 主机从从机读取数据,执行着一个时序,SCL还是高电平的时候,拉低SDA,产生起始条件,之后再发送一个字节确定从机地址以及读写标志位,主机读到RA为0,证明从机已经接收到了这个字节,因为是读操作,所以主机不再发送,要把SDA控制权放给从机,主机调用接收一个字节的时序,进行接收操作,之后从机得到允许,可以在SCL低电平的时候,在SDA写入数据,主机在高电平的时候读取SDA的数据,一次读取八位。

主机一旦在读写位写上1之后,就立马接着是读操作了,没有指定对象寄存器地址,

所有的寄存器都被分配到一个线性的区域中,并且会有单独的一个指针变量指示着其中的一个寄存器,指针上电默认一般是0地址,每写入和读出一个字节后,这个指针就会自增1,在调用当前地址读时序的时候,没有指定地址,从机就会返回当前指针所指寄存器的值,由于不能指定,用的不是很多。

image.png

 这个指定地址读是一个复合结构,可以分成两半来看,前面是准备写入的操作,后面是读的操作,就是前面的两者相结合。

前面寻址部分跟写操作寻址一样,在读写位写0,而不是1,从机应答后,然后再后面发送一个一个地址时序,这个地址写入到寄存器指针中,指定寄存器指针的指向,确定操作的是哪个寄存器,sr指的是重复起始条件,相当于另起一个时序,因为读写标志位只能跟着起始条件的第一个字节,想要改变位读模式,只能这样sr。接着重新寻从机设备地址,然后进行数据读取。或者在sr之前加上一个停止位,然后重新其实条件读取,这样就是两个完整的时序了。这里的复合时序相当于一个数据帧,起始,再起始,然后停止,是I2C规定的复合时序。

 指针在读一次或者写一次的时候会自增1,这时候如果要发送多个字节,就在发送数据哪里连续输入多个发送数据的时序以及应答位,不过要注意,无论读写,一次之后就会自增1,所以读取的时候,就是不断把各个寄存器的数值读取出来,写也是一样的。如果仅仅想读取一个数值后就停止的话,就在SA(Send ack)那里置非应答,从机收到主机非应答的信号后,就会把SDA释放,控制权还给主机。如果主机读完仍然给从机应答了,从机就会发送下一个数据,如果要读或者写连续字节,在最后一个字节非应答,其余字节应答即可。

MPU6050简介

上面我们已经知道,即便外挂设备的各种寄存器不存在,我们也能通过I2C通信协议读写外挂芯片的寄存器的功能。

相关的数据手册在提供的模块资料中,PS是产品说明书,RM是寄存器映像。

image.png

注意,右边两个图,表示的并不是芯片内部的结构,而是相当于可以把芯片内部的结构这么进行理解,至于里面是如何进行完成的,这就是厂家秘籍。

之所以叫六轴,是因为3轴+3轴,如果芯片内部再集成一个三轴磁场传感器,测量XYZ轴的磁场强度,就叫九轴姿态传感器,再加一个气压传感器,就叫测量气压的大小,就叫十轴姿态传感器,气压一般反映的是高度信息,海拔越高,气压越低,所以气压计是单独测量垂直地面的高度信息的。

通过这种信息融合,可以得到一个姿态角,也就是欧拉角,例如飞机正在飞行,飞机机头上扬或者下沉,这个轴的夹角叫做俯仰(pitch),飞机机身左滚还是右滚,这个轴的夹角叫做翻滚(roll),飞机保持机身水平,机头左转或者右转,这个轴的夹角叫做偏航(yaw),欧拉角就代表了飞机此时的姿态,若要调试一个精确地飞控算法,一个精确且稳定地欧拉角尤为重要,上面提到的单一的传感器是没办法得到精确地获得欧拉角,而把上面那些传感器结合起来,取长补短,就能获得较为精确且稳定的欧拉角。

常见的融合算法一般有互补滤波以及卡尔曼滤波,涉及到姿态解算的内容。

目前我们要用的是I2C通信,只需要把那些数据读出来在oled显示就算结束。

看左下角的图,分别是这样设定XYZ轴的,xyz轴芯片内部分别都存在一个加速度计,也就是中间的那个图,中间是一个可以左右滑动的小块,左右滑动会改变变阻器,然后影响输出电压。

XYZ轴各有一个这样的加速度计,可以把这样的方式想象成在一个小正方体中心有一个单位重量的球,球的六个面都有一根弹簧,对面代表一正一负,以此来确定XYZ坐标的值,弹簧那里被压了就是那里受力了,正方体六个面所测的力,就是三个轴加速度的值,加速度具有静态特性,不具有动态特性。

比如你把芯片静置放在地上,那就只用底面测力计受到小球的压力,所以此时数据输出就是XY轴输度为零,Z轴输出一个g的加速度值。如果此时芯片正处于自由落体啊,那就是所有面儿都不受力,此时数据输出就是XYZ轴都为零。如果此时芯片向左倾斜放置。那就是底面儿和左面儿都受力,这时就是求一个三角函数,就能得到向左的倾角θ了。不过这个清角只有芯片静置的时候才是正确的,因为加速度分为重力加速度和运动加速度,如果芯片运动起来了,这个三角函数求得的倾角就会受运动加速度的影响。举个例子,比如你坐在汽车里,现在汽车突然向前加速,你是不是感觉椅子底面和靠背都受力啊?这时如果用三角函数求角度,得到的结果就是你的车停在一个斜坡上,停在斜坡上也是已知底面和靠背都受力对吧?但实际上车的状态开始水平向前加速。所以即使用加速度计求角度啊,只能在物体静止的时候使用,当物体运动起来了,这个角度就会受运动加速度的影响而变得不准确。好,这就是加速度计的测量原理和特征。总结一下就是加速度计具有静态稳定性,不具有动态稳定性。

然后我们接着看下一个传感器,三轴陀螺仪传感器,简称是GYRO或者G,那它的作用是测量XYZ轴的角速度,看一下右下角这个图片,这个就是陀螺仪的机械模型,中间是一个有一定质量的旋转轮儿。外面是三个轴的平衡环,当中间这个旋转轮儿高速旋转时啊,根据角动量守恒的原理,这个旋转轮儿具有保持它原有角动量的趋势,这个趋势可以保持旋转轴方向不变。当你外部物体的方向转动时。内部的旋转轴方向并不会转动。这就会在平衡环连接处产生角度偏差。

如果我们在连接处放一个旋转的定位器,测量电位器的电压,就能得到旋转的角度了。从这里分析啊,陀螺仪应该是可以直接得到角度的,但是我们这个MPU6050的陀螺仪并不能直接测量角度,可能是结构的差异呀,或者是工艺的限制,我们这个芯片儿内部的陀螺仪测量的实际上是角速度而不是角度啊。这个注意一下,陀螺仪测量XYZ轴的角速度值,分别表示了此时芯片绕X轴、绕Y轴和绕Z轴儿旋转的角速度。这里也可以用一个模型来辅助理解这个测量角速度的陀螺仪,你可以把它想象成是游乐园的旋转飞车。中间的轴儿转的越快。这个椅子飞的就越远,最终我们测量一下对象两个椅子飞起来的距离或者飞起来的夹角,就能得到中间轴轴的角速度了,这个是测量角速度的陀螺仪,那如果我们想通过角速度得到角度的话,我们只需要对角速度进行积分即可。角速度积分就是角度和加速度地测角度一样啊,这个角速度积分得到的角度也有局限性,就是当物体静止时啊,角速度值会因为噪声无法完全归零。然后经过积分的不断累积,就会导致计算出来的角度产生缓慢的漂移。也就是角速度积分得到的角度经不起时间的考验,不过这个角度呢,无论是运动还是静止都是没问题的,它不会受物体运动的影响。所以总结下来就是陀螺仪具有动态稳定性,不具有静态稳定性。这个陀螺仪是动态稳定,静态不稳定,之前加速飞机是静态稳定,动态不稳定。

那种传感器的特性正好互补,所以我们取长补短,进行一下互补滤波,就能融合得到静态和动态都稳定的姿态点儿了,就是姿态结算的大体思路,当实际的姿态结算肯定会更加复杂一些了。

image.png

 电子的传感器最终也是输出一个随姿态变化而变化的电压,要想量化这个电压信号,那就离不开ad转换器了,所以这个芯片内部也是自带了ad转换器,可以对各个模拟量进行量化。这个ADC是16位的,那量化输出的数据变化范围就是二的十六次方,如果作为无符号儿数的话,就是零到65535,这里因为传感器每个轴儿都有正负的数据,所以这个输出结果是一个有符号数,量化范围是负的32768到正的32767,数据是十六位的,可以分为两个字节存储,加速度计满量程选择就是当模拟信号达到最大的时候,对应的物理参量是多少。g是重力加速度,9.8m/s²,下面那个就是每秒转了多少度。满量程越大,测量范围越大,满量程越小,测量分辨率越大。抖动厉害可以使用低通滤波器,时钟源跟采样分频一起使用,为电路中其他部分提供时钟,控制分频系数,就可以控制AD转换的快慢了。

在IIC协议中,从机地址是7位的二进制数,其中最高位是固定为0的,因此在STM32中使用IIC协议时,需要将从机地址左移一位,然后再按位与0x00或0x01进行或操作,以便将最高位设置为读或写的标志位。对于MPU6050来说,其从机地址是0x68,转换成二进制是01101000,左移一位后变成11010000,然后再按位与0x00或0x01进行或操作,就可以得到读或写的地址了。如果要写入数据,就将最低位设置为0,如果要读取数据,就将最低位设置为1。

AD0是从板子上引出来一个引脚,可以改变高低电平来改变读或者写。

如果用二进制表示从机地址是没有什么问题的,但是如果用到十六进制,一般有两种表示方式,第一种就是单纯把它分开,变成高三位以及低四位。就是0x86.

在IIC协议中,从机地址是7位二进制数,加上读写位是八位,有两种表示方法,一种是加上读写位的表示方法,比如上图的1101000,可以表示为0xD0,而下面这一个0xD1,这是融合了读写标志位的写法,还有一种就仅仅是用七位表示从机地址的写法,对上图来说就是0x68。

还有一种表示方法,就是把左移一位之后的数据当成从机地址,0x68左移一位,就是0xD0,发送第一个字节的时候,如果是写操作,就是直接把它当做从机地址,如果要读操作,就还要或0x01,也就是0xD1当做第一个字节。这种方法把读写位融入从地址了。

image.png

左上角LDO,低压差线性稳压电路。右边是MPU6050芯片。

REGOUT以及FSYNC可以实现帧同步,左下角的XCL以及XDA可以连接气压计或者磁力计,这时候主机MPU6050就可以直接读取外接外设芯片的数据,MPU6050中的有DMP单元,可以进行数据融合以及姿态解算,弥补没有这两者的偏航角缺陷,右图可知,在芯片内部已经外挂了两个上拉电阻,所以SDA以及SCL直接接GPIO口就行。气压计测高度,磁力计测方向。

如果不需要MPU6050的解算功能,就可以直接把气压计或者磁力计直接挂载在MPU6050的IIC通道口中,IIC可以挂载多设备,这里挂载上去没什么问题。

AD0就是七位从机地址的最低位,高电平为1,低电平为0.

INT中断输出引脚,可以配置芯片的一些事件,来触发中断引脚的输出,

image.png

这个就是右边的内部框图,左上角是时钟系统,分别是时钟的输入输出,不过我们一般使用内部时钟,灰色部分是芯片内部的传感器,包括XYZ轴的加速度计,以及陀螺仪。白色那个是温度传感器,传感器其实就是可变电阻,经过分压后输出模拟电压,在经过ADC进行模数转换,然后存放在数据寄存器中,然后需要数据的时候就可以到数据寄存器中获取。芯片内部是全自动转换的,每一个ADC输出对应的是16位寄存器,不存在数据覆盖的问题,配置好转换频率之后,每个数据自动以我们设置好的转换频率刷新到数据寄存器中。

selftest是自测单元,用来检验芯片的好坏,开启自测的时候,自测单元会模拟一个力,导致传感器的数据比平时大一点,使能自测读取数据,失能自测读取数据,两数相减得到一个数据,这个数据叫自测响应,如果这个数值在芯片手册一定范围内,芯片正常,否则不正常。

pump是升压泵,因为这个芯片内部需要一个高压,其工作原理是,电源与电容并联,充电,充满后电源电压等于电容电压,然后快速串联,就相当于把电源电压升高了两倍,给芯片供电,由于电容的电荷较少,很快电压就会下降,就需要充电,所以有切换为与电源并联充电,如此循环往复,给芯片供电。

image.png

由上到下是中断寄存器,FIFO先入先出寄存器,可以对数据流进行缓存,配置寄存器,可以对内部各个电路进行配置,传感器寄存器,也就是数据寄存器,储存了各个传感器的数据,工厂校准,就是内部的传感器都进行了校准;数据运动处理器DMP,是芯片内部自带的一个姿态结算硬件算法,配合官方的DMP库可以进行姿态结算啊,因为姿态结算还是比较难的,而且算法也很复杂,所以如果使用了内部的DMP进行姿态结算,姿态结算就会方便一些。

这个FSYNC是正同步,我们用不到,最后上面这块就是通信接口部分的,上面一部分就是从机的IIC和SPI同一接口,用于和SM32通信。下面这一部分是主机的IIC通信接口,用于和MPU6050扩展的设备进行通信。这里有个接口,SIBM旁路选择器就是一个开关,如果拨到上面辅助的IIC放在引件儿就和正常的放在引角儿接到一起,这样两路总线就合在一起了,STM32可以控制所有设备,这时SM32是大哥。MPU6050和这个扩展设备都是STM32的小弟,如果拨到上面辅助的IIC的引角,就由MPU6050控制。两条安放总线独立分开。这时SD32是m pu6050的大哥,M pu6050呢,又是扩展设备的大哥,这样来连接的,如果使用的话可以再详细研究,当然我们本节课程不会用到这个扩展功能。然后最后下面这里是供电的部分,按照手册里的电压要求和参考电路来接线就行了。

MPU6050以及MPU6000用同一个手册,具体看手册:

总结下来就是两点,一是mpu6050,有一个独立的VIOGLC,可以支持供电和IO口不一样的电平等级。而M6000没有。二是MPU6000同时支持IIc和SPI接口。而m pU6050仅支持iic。

image.png

 系统时钟可以选择内部时钟,陀螺仪内部的精准时钟(陀螺仪本身需要),外部的时钟32.768kHZ或者19.2Mhz。

mpu6050没有封装好的寄存器,所以需要进一步了解手动配置一下寄存器,具体手册有写。

从左到右依次是,十六进制地址,十进制地址,寄存器名称,读写权限,以及寄存器内每一位的名字。

image.png

寄存器黄色部分名称从上到下依次是:采样频率分频器,配置寄存器,陀螺仪配置寄存器,加速度计配置寄存器。

下图依次是:

下面这一大块是数据寄存器,H表示高八位,L表示低八位,59-64代表加速度XYZ轴,6566表示温度传感器,、GYRO是陀螺仪XYZ轴。

image.png

 

107跟108分别是电源管理寄存器1跟2,剩下一个是器件ID号。

image.png

 

采样频率分频器:8位作为一个整体作为一个分频值,分频越小AD转换越快,数据寄存器刷新越快,看下面的公式,采样频率就相当于数据刷新率,等于陀螺仪输出时钟频率/分频系数+1,

注意事项:不适用低通滤波器的时候,陀螺仪时钟频率位8kHz,使用了就是1khz。

image.png

配置寄存器:内部有两部分,外部设置以及低通滤波器设置,下图是低通滤波器的设置:他可以让输出的数据更加平滑,配置的参数越大,数据的抖动越小,0表示不用低通滤波器,陀螺仪时钟为8khz,也就是第一行。7是保留为,没有用到。 

 image.png

配置陀螺仪寄存器,高三位是XYZ的自测使能位,中间两位是满量程选择位,后三位没用到。

自测:自测响应=自测使能的数据-自测失能的数据,上电后使能自测读取数据,而后失能自测读取数据,再相减。 

image.png

这就是符合的范围。

image.png

满量程的选择有如上四种,量程越大范围越广,量程越小,分辨率越高。 

image.png

加速度计配置寄存器,高三位是自测使能位,中间是量程选择位,后面是配置高通滤波器的,内置小功能,运动检测用的,对数据输出没有影响。

image.png

加速度计数据寄存器:想读取数据直接读取这里就好了, 这是一个16位的有符号数以二进制的补码存储,读取高八位以及低八位,高八位左移八次,或上低八位,存放在一个uint16_t变量中,就得到了数据了。

image.png

温度传感器以及陀螺仪传感器也是一样的也是一样的

image.png

 

电源管理寄存器1:7:设备复位,写1所有的寄存器都恢复到原本的配置,6睡眠模式:芯片不工作,进入低功耗,5循环模式:芯片进入低功耗,隔一段时间之后,启动一次; 并且唤醒的频率由电源管理寄存器的开始两位决定。比较省电。3温度传感器失能,1则进制。最后三位是选中时钟源,如图下所示:

image.png

 非常建议使用陀螺仪时钟,标胶精确。

image.png

76两位就是那个所谓的休眠后苏醒的频率了,后面六位可以分别控制6个轴进入待机状态,如果仅仅只需要部分轴的数据,可以让其他的轴待机,省点电。

image.png

 中间的固定位110100实际就是芯片IIC的地址,他的最高位以及最低位都是0,那么读出的值就固定是0x68,AD0引脚的值并不反映在这个寄存器之上,之前说过的IIC地址可以通过AD0引脚配置,但是这里不可以,

image.png

所有寄存器一开始都是0x00,但是除了107以及117,看107是电源管理器1,它默认是睡眠模式,操作的时候要先解除睡眠模式,否则所有寄存器无效。 

软件I2C读取MPU6050

由于本代码使用软件I2C,所以不用看库函数(也就是I2C.C文件),只要GPIO的读写就可以了。分为两个步骤:

1.把SCL以及SDA都设置为开漏输出模式

2.把SCL以及SDA置高电平,此时i2c总线属于空闲状态

开漏输出不代表只能输出,还可以输入,输入的时候,先输出1,再读取输入数据寄存器之中的数值就可以了,I2C硬件规定的时候说过。

MYI2C.c文件:

image.png

接下来完成I2C的六个基本时序单元:

起始单元:

image.png

要让SCL以及SDA同时处于低电平,首先将SDA释放,然后把SCL释放。

在stm32f103中,为了方便对TB6612的IN1和IN2进行电平操作,可以采取如下的写法,将x强制转换为BitAction类型也就是枚举类型,x=0就是Bit_RESET,x=1就是Bit_SET

/**/void GPIO_WriteBit(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin, BitAction BitVal); 

 //右轮需要PB12,PB13,左轮需要PB14,PB15

#define Motor1AIN1(x)             GPIO_WriteBit(GPIOB,GPIO_Pin_13,(BitAction)x)

#define Motor1AIN2(x)             GPIO_WriteBit(GPIOB,GPIO_Pin_12,(BitAction)x)

#define Motor2BIN1(x)             GPIO_WriteBit(GPIOB,GPIO_Pin_15,(BitAction)x)

#define Motor2BIN2(x)             GPIO_WriteBit(GPIOB,GPIO_Pin_14,(BitAction)x)

/**************************************/

由于读与写不是一个寄存器,相关操作也同样有所不同,所以封装函数的时候分为两个封装。

image.png

 分别封装成三个函数,两个是分别写入10以及11的高电平,第三个是读取数据,返回的是读到SDA线的电平,封装的好处就是在移植的时候,只需要修改部分即可,不用全部修改。

/*******执行起始条件********/

void MyI2C_Start(void)

{

    MyI2C_W_SDA(1);

    MyI2C_W_SCL(1);

    MyI2C_W_SDA(0);

    MyI2C_W_SCL(0);

}

/*****************************/

这个起始条件要兼容起始条件以及重复起始条件,在重复起始条件的时候,SCL起始低电平,SDA电平不确定,保险起见,先释放SDA为高电平,然后释放SCL为高电平,然后根据要求拉低。

/*********终止条件*************

void MyI2C_Stop(void)

{

    MyI2C_W_SDA(0);

    MyI2C_W_SCL(1);

    MyI2C_W_SDA(1);

}

/***********************/

如果要进行终止条件的时候,SDA以及SCL都是低电平的时候,先释放SCL再释放SDA就可以了,但是SDA不一定是低电平,为了确保SDA是低电平,就一定要先拉低SDA的电平,然后进行释放操作。

/************发送一个字节****************/

void MyI2C_SendByte(uint8_t Byte)

{

    uint8_t i;//趁SCL低电平,把字节的每一位放在SDA线上

    for (i = 0; i < 8; i ++)

    {

        MyI2C_W_SDA(Byte & (0x80 >> i));//作用是取出字节的最高位,用按位与的操作,这里的具体看下图红笔的列式。原理就是需要取出哪一位,就让那一位为1,然后与需要取位的相与就行,不过对于0x80,得出的结果要么是0,要么是0x80,所以在后续使用的时候要注意,0x80的时候代表是1。同样的,需要取第7位置的时候就与上0x40。我们是要一位一位放到SDA上面,>>表示右移符号,整体移动,移出去的就不要了,不够就补0.

        MyI2C_W_SCL(1);//置高电平的时候,就把这个数据读取出去

        MyI2C_W_SCL(0);

    }

}

/***************************************/

image.png

SCL低电平,SDA变换数据,高电平,保持数据稳定,由于高位先行,变换数据的时候,按照逻辑顺序现房最高位,再放次高位,依次把一个字节的每一位放到数据线上,每方位一位,执行释放SCL,拉低SCL的操作,驱动时钟运转。

 image.png

/***************接收一个字节*************/ 

uint8_t MyI2C_ReceiveByte(void)

{

    uint8_t i, Byte = 0x00;

    MyI2C_W_SDA(1);//主机释放SDA

    for (i = 0; i < 8; i ++)

    {

        MyI2C_W_SCL(1);//主机释放SCL

        if (MyI2C_R_SDA() == 1){Byte |= (0x80 >> i);}//从机在SDA放入每一位数据。

如果返回的是1,就用或等于把初始值的0x00置1,否则保持原状,就是0,实现置1,就是用或等于来实现。每一次独到的数据是从机控制的,都是在变的,所以程序中虽然没有改变SDA的值,但是还是不一样的,也就是读到的是从机发送的字节。

        MyI2C_W_SCL(0);//主机拉低SCL

    }

    return Byte;

}

/*************************************************/

image.png

 SCL低电平的时候,主机放开对SDA的控制,从机控制SDA,然后变换数据,1上拉,0下拉,然后在SCL高电平的时候读取数据,循环往复。如果在SCL高电平期间SDA变了的话,那就是起始条件以及终止条件。

/******************发送应答以及接收应答****************/

void MyI2C_SendAck(uint8_t AckBit)

{

    MyI2C_W_SDA(AckBit);

    MyI2C_W_SCL(1);

    MyI2C_W_SCL(0);

}

uint8_t MyI2C_ReceiveAck(void)

{

    uint8_t AckBit;

    MyI2C_W_SDA(1);

    MyI2C_W_SCL(1);

    AckBit = MyI2C_R_SDA();

    MyI2C_W_SCL(0);

    return AckBit;

}

/******************************/

这两者其实也就是,把发送以及接收一位而已,发送接收是八位,而它是一位。

或许会有人有疑问,前面在一个函数之中,先把SDA置1了,然后有读取SDA的值,有什么意义?

1.I2C的引脚都是开漏输出加上弱上拉的配置,主机输出1,并不是强制SDA为高电平,而是释放sda,把控制权交给从机

2.I2C是在通信,主机释放SDA,从机并不是在看戏,它是有义务要把电平拉低的,然后读取,读到1代表从机没给应答,读到0代表从机给了应答。这是接收应答的流程。

测试给不给应答:使用对象是MPU6050与单片机

image.png

 按照下图的逻辑进行指定地址写操作,上面的代码是先不管具体寄存器,先看看会不会应答,就直接开始,然后找到指定的地址,最后结束,在显示屏上面显示返回值。对于上面的程序,增加一个for循环,不断扫描各部分的地址,然后看哪个应答了,就是要寻找的地址。改地址的话,就是在AD0那里接上高电平,就是1100001,就相当于改地址了。如果有AD1,AD0引脚,也可以一起使用搭配改名名字,有更多改的更多名字,一条总线上面有多个相同设备的时候,就可以搭配起来一起使用,不会造成名字冲突。

image.png

 建立MPU6050的.c以及.h文件

/**********指定地址读*********/

#define MPU6050_ADDRESS        0xD0//MPU6050的地址

void MPU6050_WriteReg(uint8_t RegAddress, uint8_t Data)

{

    MyI2C_Start();//其实条件

    MyI2C_SendByte(MPU6050_ADDRESS);//发送MPU6050地址

    MyI2C_ReceiveAck();//应答

    MyI2C_SendByte(RegAddress);//具体寄存器地址

    MyI2C_ReceiveAck();//应答

    MyI2C_SendByte(Data);//发送数据

    MyI2C_ReceiveAck();//应答

    MyI2C_Stop();//停止

}

/*****************************/

跟下面这个时序的过程是一致的:

image.png

如果想指定地址写多个字节的话,就用for循环把这两句套起来,依次把数组的多个字节发送出去:

 MyI2C_SendByte(Data);//发送数据

    MyI2C_ReceiveAck();//应答

/********************************指定地址读一个字节时序******************************************/

uint8_t MPU6050_ReadReg(uint8_t RegAddress)

{

    uint8_t Data;

    //指定地址还没写

    MyI2C_Start();

    MyI2C_SendByte(MPU6050_ADDRESS);

    MyI2C_ReceiveAck();

    MyI2C_SendByte(RegAddress);

    MyI2C_ReceiveAck();

    

    MyI2C_Start();//重新开始

    MyI2C_SendByte(MPU6050_ADDRESS | 0x01);//把写改为读(这个地址是包括读写位的)

    MyI2C_ReceiveAck();//接收应答之后,主线控制权交给从机了

    Data = MyI2C_ReceiveByte();//返回值就是接收到的数据

    MyI2C_SendAck(1);//从机给主机的应答,1代表应答,0代表非应答,非应答就会继续发送过来,用以多次发送

    MyI2C_Stop();

    

    return Data;

}

image.png

/**********************************读取MPU6050ID号码*****************************************/

int main(void)

{

    OLED_Init();

    MPU6050_Init();//初始化

    

    OLED_ShowString(1, 1, "ID:");

    ID = MPU6050_ReadReg(0x75);//ID号的寄存器的地址是0x75,直接读就行了

    OLED_ShowHexNum(1, 4, ID, 2);

}

image.png

 /********************************写寄存器*****************************/

1.解除睡眠模式:睡眠模式是由于下图地阿奴按压1的某一位控制的,直接往寄存器中写入0x00就可以了。

 MPU6050_WriteReg(0x6B,0x00);//解除睡眠模式,就可以用MPU6050_WriteReg(uint8_t RegAddress, uint8_t Data)进行写操作了,不然无效,可以又写又读试验一下:

int main(void)

{

    OLED_Init();

    MPU6050_Init();//初始化

     MPU6050_WriteReg(0x6B,0x00);

 MPU6050_WriteReg(0x19,0xAA);

    OLED_ShowString(1, 1, "ID:");

    ID = MPU6050_ReadReg(0x19);//ID号的寄存器的地址是0x75,直接读就行了

    OLED_ShowHexNum(1, 4, ID, 2);

}

image.png

存起比较多的情况下,可以直接新建一个文件,然后宏定义表示寄存器的地址,直接.h文件就行

image.png

 image.png

依次是:设备复位给0不复位,睡眠模式给0解除睡眠,循环模式0不循环,无关位0,温度传感器失能给0,不失能,最后三位选择时钟000,内部时钟,001,陀螺仪时钟。

/*****************************配置MPU6050的寄存器初始化**************************************/

void MPU6050_Init(void)

{

    MyI2C_Init();

    MPU6050_WriteReg(MPU6050_PWR_MGMT_1, 0x01);//电源管理寄存器1

    MPU6050_WriteReg(MPU6050_PWR_MGMT_2, 0x00);//电源管理寄存器2

    MPU6050_WriteReg(MPU6050_SMPLRT_DIV, 0x09);//分频器,决定数据的快慢

    MPU6050_WriteReg(MPU6050_CONFIG, 0x06);//配置寄存器,参数分别是:外部同步,数据低通滤波器

    MPU6050_WriteReg(MPU6050_GYRO_CONFIG, 0x18);//陀螺仪配置寄存器:前三位是自测试能(手册写漏了,写000),然后是满量程选择,写11是最大量程。

    MPU6050_WriteReg(MPU6050_ACCEL_CONFIG, 0x18);//加速度计配置寄存器,自测,量程,高通滤波器。

}

//配置完之后,陀螺仪内部就连续不断进行数据转换了,输出的数据,就存放在特定的数据寄存器中,也就是下图中的中间部分:想获取数据就得写一个获取数据的函数就可以了,接着往下看:

image.png

/**************************************************************************************************/

/*********************************获取数据******************************************/

问题是需要多个返回值。

方法1.读到的数存在全局变量中,然后共享(适合比较小的项目)

方法2.用指针进行变量的地址传递(如下)

方法3.用结构体对变量进行打包,进行统一传递

void MPU6050_GetData(int16_t *AccX, int16_t *AccY, int16_t *AccZ, 

                        int16_t *GyroX, int16_t *GyroY, int16_t *GyroZ)//定义指针

{

    uint8_t DataH, DataL;

    

    DataH = MPU6050_ReadReg(MPU6050_ACCEL_XOUT_H);//读取寄存器中高八位低八位的数值

    DataL = MPU6050_ReadReg(MPU6050_ACCEL_XOUT_L);

    *AccX = (DataH << 8) | DataL;//高八位左移,或上低八位复制给指针指向的地址,虽然DataH是8位,左移经过验证是可以的,不会溢出什么的,因为赋值的对象是16位的,不放心就改为16位。这个16位数是补码表示的16位有符号数,所以用int16_t。还可以使用I2C读取数据的连续读取把这片连续的寄存器的值读出来。

    

    DataH = MPU6050_ReadReg(MPU6050_ACCEL_YOUT_H);

    DataL = MPU6050_ReadReg(MPU6050_ACCEL_YOUT_L);

    *AccY = (DataH << 8) | DataL;

    

    DataH = MPU6050_ReadReg(MPU6050_ACCEL_ZOUT_H);

    DataL = MPU6050_ReadReg(MPU6050_ACCEL_ZOUT_L);

    *AccZ = (DataH << 8) | DataL;

    

    DataH = MPU6050_ReadReg(MPU6050_GYRO_XOUT_H);

    DataL = MPU6050_ReadReg(MPU6050_GYRO_XOUT_L);

    *GyroX = (DataH << 8) | DataL;

    

    DataH = MPU6050_ReadReg(MPU6050_GYRO_YOUT_H);

    DataL = MPU6050_ReadReg(MPU6050_GYRO_YOUT_L);

    *GyroY = (DataH << 8) | DataL;

    

    DataH = MPU6050_ReadReg(MPU6050_GYRO_ZOUT_H);

    DataL = MPU6050_ReadReg(MPU6050_GYRO_ZOUT_L);

    *GyroZ = (DataH << 8) | DataL;

}

/****************************************************************************************/

image.png

 主函数这里把地址传过去就行啦,与之呼应:

void MPU6050_GetData(int16_t *AccX, int16_t *AccY, int16_t *AccZ, 

                        int16_t *GyroX, int16_t *GyroY, int16_t *GyroZ)//定义指针

小球正方体模型中,z是上下的受力,要想让z位负值,就要上面受力,把芯片反过来,芯片上有标明哪边是x,哪边是y。这是加速度。

角速度就是绕哪个轴旋转就是。

读取数据/32768=x/满量程,其中x就是角速度

读取数据/32768=x/满量程,其中x就是加速度

I2C通信外设

软件I2C就是通过代码手动拉低软件电平进行时序的发送,这里开始讲硬件。I2C是同步时序,对于软件模拟I2C,因为每一位持续的时间长点短点都没有关系,即便是暂停一小段时间也没关系,所有可以用软件模拟,但是也有对应的硬件收发电路。

串口通信由于是异步时序,每一位的时间要求很严格,不能长不能短更不能中途暂停一会,虽然可以用软件模拟,但是比较困难。而且串口通信的硬件收发器比较普及,基本一些外设都会有。

就是用难易程度上来看,软件I2C比硬件简单,但是硬件I2C有它独特的优势。如果只是简单的应用,就用软件,如果对性能指标要求比较高,就用硬件I2C,比如通讯速率快,能够完整是用多主机模型。

image.png

 有了硬件收发电路,只要应软件写入控制寄存器CR,以及数据寄存器DR就可以了,为了试试检测到状态,软件还会湿湿的读取状态寄存器SR。如果使用硬件收发,直接调用函数,库函数就会自动进行配置了,硬件自动实现时序,就会减轻CPU负担,节省软件资源,单独有特定的硬件电路执行,更加专注,时序生成的性能以及效率也会更高。

手册上面写的是I2C通信的完全体,属于进阶内容的时候去看。

多主机模式:

1.固定多主机,固定有多少个多少个主机,下面挂载着多个从机,当主机出现冲突的时候,请求总线制裁,失败的让出总线控制权。

2.可变多主机:总线上挂载着多个从机设备,都是从机没有主机,当需要通信的时候,从机就会跳出来成为主机,与指定的对象进行通信,通信完毕有变回从机,如果多个从机一起跳出来请求成为主机,总线就会进行制裁,失败的让出总线控制权。STM32的设计就是按照这个设计的。

7位寻址:在第一个字节这里,前七位是地址,最后一位是读写位

10位寻址,就是前两个字节都作为地址,读写位不变,第一个字节有七位,第二个字节有8位,加起来15位,前五位用来当做标志位,标志着10位寻址这个模式,标志位时11110,。

通信速率只要不超过最高的就多少都可以。

DMA:在多字节传输的时候提高传输速率

SMBus:(system management bus)系统管理总线,这个总线从I2C总线上改进过来的,与I2C非常像,主要用于电源管理系统中。

这款芯片中,只有两条硬件I2C总线,资源比较少,但是对于软件I2C,只要软件能够存的下来,就能开辟新的I2C总线。

image.png

对于引脚,SMBALET是给SMBUS总线用的,对于上面两个引脚,一般与IO口进行复用,还可以进行重映射,主要看那个引脚定义表。

右边:因为是半双工,所以数据收发是一组数据寄存器以及数据移位寄存器,串口通信是全双工,数据收发是两组不同的。

把数据写入数据寄存器,如果数据移位寄存器没有数据正在移动,就可以吧数据寄存器中的数据整体放到一位寄存器中,然后经过数据控制一位一位发送出去;读取数据的时候也是这样,一位一位的放入数据一位寄存器中,然后总体放到数据寄存器中。CR控制寄存器用来控制什么时候收什么时候发。移位的时候高位先行,先是移动最高位然后是次高位。一个时钟移动一位,一次放到SDA线上。

至于比较器以及下面的自身地址寄存器是从机模式才使用的,STM32是可变多从机模式,SRM32不进行通信的时候,就是从机,可以被别人召唤,就是存储地址名称的地方,可以自定义从机地址写入其中,比较器就是寻找一个地址的时候,用目标地址与自身地址寄存器的进行比较,都是从机。因为有双地址寄存器,意思就是改STM32支持同时相应两个从机地址。也就是当stm32作为从机的时候才使用,如果是使用一主多从的模式,是不需要用的。

帧错误校验计算,说的是发送一个数据帧多个字节的时候,硬件电路会自动进行CRC校验,然后再数据帧后面置一个校验位,如果校验不通过,就在错误标志位置位提醒。

状态寄存器可以通过读取得知电路的状态,控制寄存器就是对电路整个的控制,时钟控制寄存器,就是在对应的位写,就会实现对应的功能。

image.png

GPIO口要配成复用开漏输出的模式,复用就是GPIO口的状态就是交由片上外设来控制的,开漏输出是I2C要求的配置。虽说是开漏输出的模式,也是可以进行输入的。上图只适用于一主多从的模式。下面GPIO那块,上面那根线是GPIO复用输出,下面那条是GPIO复用输入。最后还有开光控制,控制整个I2C外设的开启以及关闭。

image.png

 

 /*******************************************主机发送*********************************************/

image.png

七位寻址以及十位寻址,使用方法是在对应的寄存器中写入数据就能实现相应的功能,比如要发起其条件,就在状态寄存器CR1中某一位写入对应的值开启。具体如何看手册,可以有硬件清除。

对于EV5可以看做是一个标志位,这里解释是SB标志位,就是状态寄存器中的一位,反映的是STM32的状态,在状态寄存器SR1中。 这位不需要手动清除,在读取之后就会自动清除。之后便进行地址字节的发送,先把地址字节写入DR寄存器中,然后通过移位寄存器发送出去,自动接收应答,应答成功就会让ADDR=1,在状态寄存器SR1中,如下图。

至于EV8_1以及EV8来看,就是那个数据寄存器以及数据移位寄存器工作的流程了,EV8事件代表数据正在移位发送,写入数据寄存器,然后EV8消,一旦检测到EV8事件就可以写入下一个数据。

最后就是数据移位寄存器发送完成之后,数据寄存器以及数据移位寄存器是空的时候,BTF的意思是,(byte transfer finish)数据发送完成。具体在SR1寄存器中。

检测到EV8_2就可以产生结束条件了,在cr1寄存器中。

 

image.png

 image.png

 

 控制寄存器CR就是产生什么条件呀,干什么呀的;而状态寄存器SR就是看事件寄存器的EV几的。

/********************************主机接收***************************************************************/

image.png

 这里是当前地址读的格式,并不是指定地址读的格式。

数据移位寄存器中的数据整体转移到数据寄存器中,然后RXNE置1,表示数据寄存器非空,这就是EV7事件,读走自动清零;在最后一个时序发生时,要把应答寄存器ack置0,

image.png

 硬件i2c波形比较规整,两者大体上没有什么区别,硬件的时候,数据写入都是紧贴下降沿的,而软件是经过一段时间的延时之后才进行的写操作,电平任意时候都可以读写,但是一般来说一般来说是认为SCL下降沿写,上升沿读,在应答结束之后,从机立刻释放了SDA的控制权,然后主机接手SDA立马拉低下拉变成低电平,但是软件有一定延时,有一段时间的高电平。但是因为是同步时序的原因,即便是软件有些不标准的波形也不影响通信。

手册内容在24章I2C接口那里。

————————————————

版权声明:本文为CSDN博主「笔下觅封侯」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。

原文链接:https://blog.csdn.net/m0_63148816/article/details/130647965