通信协议见(STM32——SPI)
一、I2C协议
1.1 I2C协议介绍;
I2C是(Inter IC Bus)是由Philips公司开发的一种通用数据总线;
有多根通信线;
一根SDA(串行通信线);
一根SCL(串行时钟线);
共地GND;
VCC电源线;
同步半双工;
支持总线挂载多设备(一主多从,多主多从);
带数据应答(主机接收一个数据后,会返回应答位,告诉从机是否接收到了数据);
1.2 I2C协议对硬件的规定;
所有设备的SDA连接在一起,SCL连接在一起,GND连接在一起,如果从设备没有单独供电,还需要外接电源VCC;
主机对SCL时钟线具有绝对的控制权,从机只能输入,不能控制时钟线,此时SCL可以配置为推挽输出;
但是SDA主机和从机即可以输入也可以输出,为了避免主机输出同时,从机也输出,形成短路,I2C设计中是禁止所有设备输出强上拉的高电平;
采样外接弱上拉电阻加开漏输出的模式;(阻值一般为4.7千欧);
结构图如下:
原理:设计为开漏模式,此时输入时,直接经过斯密特触发器整流任何时刻都可以输入,输出时,经过开漏设计,低电平导通,为拉下状态低电平,高电平截至处于浮空状态,引脚电平外接上拉电阻弱上拉为高电平;
设计的优点:
作用:
1.避免电路出现短路现象;
2.避免频繁切换引脚模式;
3.线与:一个或者多个输出低电平,呈现低电平,只有全部都输出高电平才输出高电平;执行多主机下的时钟同步和总线仲裁;
过程:当I2C处于空闲状态时,SCL和SDA均由外挂的电阻,拉高至高电平;总线处于高电平状态;当总线需要传输数据时候,SCL保持不变,将SDA从高电平转换为低电平,产生一个下降沿,当从机检测到SCL高电平,SDA下降沿时候,会复位,进入就绪状态;在SDA下降沿后,SCL也从高电平到低电平,一方面占用这个总线,同时拼接单元,完成后传输后,SCL回弹到高电平,产生上升沿后,SDA也回弹到高电平,从机检测后,进入终止;终止后俩个都处于高电平,恢复到平静状态;
1.3 I2C协议对软件的规定;
1.3.1时序单元:
由起始条件,传输字节,应答位,终止条件组成;
起始条件:标志着一个数据帧的开始,SCL高电平的时候,SDA由高电平变成低电平产生下降沿;
终止条件:标志着一个数据帧的间隔,SLC高电平的时候,SDA由低电平转换为高电平产生上升沿;
传输字节:发送一个字节,接收一个字节;
发送一个字节;
分析:SCL高电平期间,SDA由高电平变成低电平,产生下降沿,触发起始条件,之后再SCL低电平时,主机将数据首位放在SDA上,在SCL高电平时,从机从SDA进行采样(读取数据),所以SCL高电平期间SDA不允许有数据变化,依次循环上述过程8次,即可发送一个字节;
接收一个字节时序:
分析:SCL高电平期间,SDA由高电平变成低电平,产生下降沿,触发起始条件,之后再SCL低电平时,从机机将数据首位放在SDA上,在SCL高电平时,主机从SDA进行采样(读取数据),所以SCL高电平期间SDA不允许有数据变化,依次循环上述过程8次,即可发送一个字节;主机在接受前,需要先释放总线(需要先使总线恢复到空闲状态,然后从机才能拿到掌控权);
发送和接收一个应答位时序:
分析:
发送一个应答位:主机在接收完一个字节之后,在下一个时钟发送一位数据,数据0表示应答,数据1表示非应答;
接收一个应答位:主机发送完数据后,在下一个时钟接收一位数据,判断从机是否应答,数据0表示应答,数据1表示非应答(主机在接收之前,需要释放SDA);
一个完整的时序过程:
I2C一主多从的模型,主机可以访问总线上的任何一个设备,通过地址来确定是哪个设备,首先将每个从机确定一个唯一的设备地址,主机在起始条件后,发送一个字节,从机匹配,响应主机,在同一个I2C总线上,从机的地址必须不同;
从机地址在I2C中分为7位和10位地址;
每个芯片在出厂时候,都会设置一个同类型芯片相同,不同类型芯片不同的7位地址;如果在一个I2C总线上接相同的芯片,此时可以根据芯片的最后几位地址,即可变地址,
根据引脚改变,例如高电平则位1101 0000 低电平位1010 0000;
1.3.2示波器时序分析:
时序1:指定地址写
对于指定设备(从机地址),在指定地址(设备内部寄存器地址)下,写入指定数据;
首先产生起始条件,然后发送一个地址字节(七位或者十位基地址,如果是十位需要发送两个字节第一个字节由5位表示十位基地址的标识+3位地址位,第二字节为剩余7位地址位+一位读写标志位,选择从设备和读写方式),发送一位应答位,之后发送设备内部寄存器地址,然后一位应答位,之后发送有效数据位,如果要连续发送数据位,在指定地址下,连续依次向后写入,就依次发送数据,直到数据位发送完毕后,发送一位应答位,最后发送终止条件;
(因为寄存器是在线系空间连续存放的,通过指针进行操作,所以每次读写后,指针自动++,指向下一位地址,要连续操作几位,就可以找到指定地址后,通过连续写入字节即可)
过程:首先处于平稳状态下,SCL和SDA都是处于高电平,主机需要给从机写入数据时候,在SLC高电平期间,拉低SDA,产生起始条件,随后紧跟的时序,必须是发送一个字节的时序,内容必须是7位从机地址加1位读写位;拉低SCL,产生下降沿,主机开始输出数据,SCL低电平时候,主机将数据位依次放在SDA上(高位先行),然后SCL拉高,从机读取SDA上的数据位,读取过程中即SCL高电平期间,SDA不允许改变,循环八次得到的结果是:从机地址:1101 000 写操作: 0(读操作1),数据时序结束后,紧跟着是应答时序,主机需要释放SDA,根据协议规定,从机需要在此时立刻拉低SDA,产生一个信号,给主机,主机判断从机是否应答(根据线与的关系主机释放SDA后,从机立刻下拉SDA,所以SDA依旧保持低电平,这个过程就表示了应答);SCL为高电平时,主机读取数据,判断结果;从机发送完成后,结束对SDA控制,SDA回调至高电平,因为从机要在低电平尽快变化数据,所以SCL下降沿和SDA上升沿同步发送;读写为给的0为写入操作,所以在应答时序完成后,我们立刻要写入一个字节,类似地址发送的方法重复八次(从机可以自己定义第二个字节的用途),重复应答时序,然后在重新重复写入时序,进行应答时序,直到主机结束发送时候,产生停止条件,即先拉带SDA,为后续SDA上升沿做准备,释放SCL,最后释放SDA;形成一个完整的数据帧;
时序2:指定地址读:当前地址读 对于指定设备(Slave Address),在当前地址指针指示的地址下,读取从机数据(Data));
首先产生起始条件,之后发送一个地址字节(设备内部地址(7)+读写标志位(1)),之后读出当前指针指示的地址下的数据;
时序3:指定地址读 对于指定设备(Slave Address),在指定地址(Reg Address)下,读取从机数据(Data)
首先首先产生起始条件,然后发送一个地址字节(七位地址,一位写标志位,选择从设备),发送一位应答位,之后发送设备内部寄存器地址,然后一位应答位,之后重新起始位,再次发送地址字节(七位地址,一位读标志位,选择从设备),在指定地址下,连续依次向后读出;
二、I2C外设;
2.1I2C外设介绍:
I2C外设是STM32内部集成的硬件电路,可以自动执行时钟生成和数据时序的收发,减轻CPU的负担;
硬件自动实现时序,软件只需要写入控制寄存器CR(产生起始条件等),数据寄存器DR(写入读取数据),读取状态寄存器SR(通过标志位判断当前状态);
支持多主机模式;分为固定多主机和可变多主机;
固定多主机即为主机个数固定,从机个数固定,主机掌控数据总线,从机只能通过主机允许才可以短暂的掌控数据总线,当多个主机同时控制总线使用权时,总线进行仲裁,胜利的一方获得总线使用权;
可变多主机,I2C总线上挂在多个设备,没有固定的主机和从机,任何一个设备都可以在总线空闲的时候作为主机,然后指定其他设备进行通信,当通信完成后,主机又变成从机,当多个设备同时申请时,总线进行仲裁,胜利的一方获得总线使用权;
支持7位/10位地址模式;
A.7位地址,起始条件后,第一个时序是必须是寻址+读写位,主机想要通信的从机的七位地址+一位读写位;
B.十位地址则为起始条件后,第一、二个时序是必须是寻址+读写位,是主机想要通信的从机的十位地址+一位读写位,此时俩个时序为15位+读写位共16位,15位由5位的十位地址标志位帧头11110+十位地址组成;
*并且同一个厂商生成的同一种芯片的I2C外挂地址是相同的,如果要在一条I2C总线上挂载俩个相同地址的芯片,可以通过配置芯片的可变地址,即地址的最低位,来配置不同的地址,在一条I2C总线上,不能有相同的地址;
支持不同的通信速度,标准速度(高达100khz),快速(400KHZ);
作为同步时序,只需要不超过最大值即可;
支持DMA;
在多字节操作时候,可以提高效率;
兼容SMbus(系统管理总线)协议;包括CRC码的生成和校验、SMBus(系统管理总线—System Management Bus)和PMBus(电源管理总线—Power Management Bus)主要用于电源管理系统中;。
2.2 I2C外设的结构图;
分析:I2C外设引脚都是通过复用GPIO实现的,具体参考映射表;
数据接收与发送:
数据控制:通过数据控制,控制数据的接收发送,
接收一个数据:当接收一个数据时候,数据一位一位的从SDA输入到移位寄存器中,低位先行
,一位一位的放入数据寄存器中,当一个字节数据接收完毕后,移位寄存器的值会被移入RDR
数据寄存器中,并置标志位RXNE为1,接收寄存器非空,此时可以读取寄存器,获取寄存器中的值;
发送一个数据:把要发送的数据写入数据寄存器TDR中,当移位寄存器为空时,数据寄存器的值会被立刻移入移位寄存器中,并置标志位TXE为1,发送寄存器空,此时新的数据可以存放在数据寄存器中;
比较器,自身地址寄存器和双地址寄存器作用是,当STM32作为从机的时候,即可变多主机模式下,STM32也是可以作为从机与其他主机通信,此时STM32的从机地址通过自身的地址寄存器来配置,当主机通信时,通过比较器判断是否相同,选择是否响应通信;并且支持同时响应俩个从机地址;
帧错误校验计算:I2C自带数据校验;
时钟控制:通过配置时钟控制寄存器,控制时钟控制电路,控制SCL输出的时钟频率;
控制逻辑电路:
中断:当内部某些标志位置1时,可以开启中断执行一些事件;
DMA:可以开启DMA通道转运数据;
通过配置控制寄存器CR1和CR2,实现控制;
通过读取状态寄存器SR1和SR2读取状态,例如TXE,RXNE位;
SMBALERT:
总功能图:
2.3 I2C外设的实现过程;
配置过程:
1.RCC开始GPIO和SPI时钟;
2.初始化GPIO,配置为开漏输出和上拉输入模式;
3.初始化SPI,配置SPI;
4.使能CMDSPI;
2.4 时序图
主机发送:
过程分析:
起始条件S+寻址+读写标志+数据1+数据2+....+数据N+终止条件p(数据后包括A响应);
首先:初始化后,总线处于空闲状态,STM32默认为从模式,产生起始条件,写入控制寄存器,STM32又从模式转换为主模式,发送EV5事件,即是否产生标志位,产生标志位后即起始条件产生完成,在数据寄存器中写入一个字节的从机地址,数据寄存器转到移位寄存器中,再把这个字节发送到I2C总线上,并硬件自动判断是否应答,可以产生中断标志位,寻址完成后,发送EV6事件,表示地址发送结束,EV6结束,发生EV8_1事件,TXE移位寄存器空,数据寄存器空,此时可以写入数据,写入数据可以因为移位寄存器也为空,此时数据寄存器立马转移数据到移位寄存器,并产生事件EV8,数据寄存器空,移位寄存器非空,TXE=1,数据1时序产生,写入DR清除该事件,接收应答为结束后,然后移位寄存器空,数据寄存器立马转移数据到移位寄存器,并产生事件EV8,一直重复上述过程,直达没有新的数据发送,此时当前移位寄存器完成时,数据寄存器空,移位寄存器也为空,此时事件为EV8_2,标志位TXE1,BTF字节发送结束;
主机接收:
过程:
首先写入控制寄存器DR,S位,产生起始条件S,然后进入EV5事件,表示起始条件已发送,之后寻址,应答,产生EV6事件,代表寻址已完成,数据1代表数据正通过移位寄存器进行输入,EV6_1事件,只适用于接收一个字节接收,接收后,硬件自动发送应答位,当时序结束后,表示移入的一个字节已转移到数据寄存器了,产生EV7事件,即收到了数据,当把数据读走后,该位清除,从重复进行,当不需要接收时候,需要在最后一个时序单元发送时,提前把应答位控制寄存器ACK置0,并设置终止条件请求,即EV7_1事件,完成后给出非应答,产生终止事件P;
五、API实现;
5.1软件模拟I2C实现对MPU的控制;
5.1.1程序规划:
首先明确想实现的功能:通过封装GPIO通信引脚,模拟I2C时序,实现读写MPU;根据功能将程序主要分为三部分:通信层(底层),驱动层(上层),应用层(main)
5.1.1.1建立I2C通信层模块(底层):
1.主要对通信引脚的封装,初始化(GPIO初始化,引脚电平变化封装);
2.以及I2C的三个时序组成部分:起始,终止,交换字节;
5.1.1.2硬件驱动层(上层)
基于I2C通信层模块建立MPU6050模块,在这个模块里调用底层的拼图,组成完整的时序(写使能,擦除,页编程,读数据等)
5.1.1.3应用层
mian函数里调用驱动层函数,实现功能;
5.1.2模块封装
首先建立模块,MYI2C,MPU6050模块对应.c,.h文件,并且包含在文件中,完成基础配置(参考STM32_程序建立)
首先编写第一个模块,I2C底层通信:主要实现功能,初始化GPIO,配置GPIO引脚为开漏输出模式,封装通信引脚,配置时序单元(拼图);
功能:RCC开启GPIOB时钟,初始化GPIOB,配置PB10,11为开漏输出模式,模拟I2C输出,空闲状态下SDA和SCL为高电平(外接电阻上拉至高电平);
参数:无:
返回值:无:
封装通信引脚:
功能:将PB10引脚封装成通信引脚SCL,通过改变PB10的电平,实现模拟I2C输出;
说明:在SMT32主频高,变化快,要求在SCL电平变化时,立刻读走数据,I2C对读取速率有要求,太快无法读取,所以加入延时函数;
参数:高低电平0/1;
返回值:无
功能:将PB11引脚封装成通信引脚SDA,通过改变PB11的电平,实现模拟I2C输出;
参数:高低电平0/1;
返回值:
功能:将PB11引脚封装成通信引脚SDA,通过读取PB11的电平,实现模拟接收从机输入;
参数:无;
返回值:接收到的值;
时序单元:起始条件,终止条件,发送应答位,接收应答位,发送一个字节的数据,接收一个字节的数据;
功能:产生起始条件(空闲条件下,SCL和SDA均为高电平,在SCL高电平时候,SDA由高电平转变为低电平,之后在拉低SCL),标志着时序的开始;
说明:*先将释放SDA,在释放SCL,确保在重复起始条件时,不出错;如果先置SCL高电平,在置SDA高电平,会判断为终止条件;
参数:无
返回值:无
终止条件时序
功能:标志着数据帧的结束/间隔;
说明:(发送数据或者接受数据的最后一位是SCL低电平的时候放在SDA上,高电平时从SDA上读出,之后发送完毕,SCL变为低电平,但是SDA不确定,在此之前需要将SDA先置低电平,能够产生上升沿,之后在SCL高电平时候,SDA由低电平转变为高电平),标志着时序的开始;
参数:无;
返回值:无;
发送一个字节时序
功能:实现主机发送一个字节;
说明:在SCL低电平的时,主机把字节的一位放在SDA上,在SCL高电平时候,从机读取SDA上的数据,起始条件后,SDA为低电平,所以直接放入数据,之后拉高SCL,从机从SDA上读取数据,之后拉低SCL主机放入下一位数据;
参数:要发送的字节;
返回值:无;
接收一个字节的时序:
功能:接收一个字节的时序;
说明:先将释放SDA相当于切换为输入模式,通过低电平写,高电平读,实现读写分离,进来之后SCL是低电平,主机释放SDA,从机把数据放在SDA上,SCL切换为高电平,主机从SDA上读取数据,之后拉低SCL,从机放入下一位数据;
参数:无;
返回值:接收到的数据;
发送应答位
功能:主机发送应答位;
说明:SCL低电平时,主机写入SDA,SCL高电平时,从机接收SDA上的数据,开始SCL低电平,主机放入数据SDA上,随后拉低SCL,从机读取SDA,之后在拉低SCL,写入下一个数据;
参数:发送的应答位;
返回值:无;
接收应答位
功能:从机发送应答位
说明:首先释放SDA,SCL低电平时,从机写入SDA,SCL高电平时,主机接收SDA上的数据,开始SCL低电平,从机放入数据SDA上,随后拉低SCL,主机读取SDA,之后在拉低SCL,写入下一个数据;
参数:无;
返回值:接收的值;
在.H文件中声明:
第一个模块底层通信层封装完成;
下面封装第二个模块硬件驱动层(上层),将底层的时序单元,拼接成一个完整的时序,实现功能;
指定地址写:
功能:对于指定设备(Slave Address),在指定地址(Reg Address)下,写入指定数据(Data);
说明:首先是产生起始条件,触发起始条件后,主机发送的第一个字节是寻址(发送从设地址,选择通信的目标),第二个字节是从设置的寄存器地址,第三个字节是要发送的数据,每次发送后,接收从机的应答位,最后终止条件,时序接收;
参数:1.从设备的寄存器地址;
2.发送的数据;
返回值:无;
指定地址读:
功能:对于指定设备(Slave Address),在指定地址(Reg Address)下,读取从机数据(Data)(Data)
说明:在通过指针对指定地址进行读写操作的,所以先通过在指定地址写,将指针指向我们需要的地址,所以先重复在指定地址写的,首先是产生起始条件,触发起始条件后,主机发送的第一个字节是寻址(发送从设地址,选择通信的目标),第二个字节是从设置的寄存器地址,之后重复起始条件,寻址时,将在指定地址写,改为在指定地址读,通过|,修改最后一位为读;然后直接读取数据,再把发送应答位为1,表示非应答,从机将不会在发送数据;
如果要进阶连续读取多个数据,则将重复读取数据,发送应答位写0,直到最后一位不再需要读取,发送应答位写1;
参数:1.从设备的寄存器地址;
返回值:读到的数据;
如果要进阶连续读取多个数据,则将重复读取数据,发送应答位写0,直到最后一位不再需要读取,发送应答位写1;
读取寄存器不同的位置,获取的内容,参考手册;
要想写入寄存器,首先要解除芯片的睡眠模式,通过写入电源管理寄存器1;
初始化MPU6050
功能:初始化
说明:在初始化之后,要写入一些寄存器,对MP6050硬件电路进行初始化配置;初始化完成后,MPU内部就会进行连续不断的数据转换,输出的数据保存在数据寄存器中;
参数:无;
功能:无;
(使用宏定义修改,这样不用频繁的查看手册,而且直接写入数字,可读性不高)
获取数据寄存器的值:
功能:获取数据寄存器的值,获取XYZ对应的加速度值和陀螺仪值;
说明:定义俩个变量,先读取陀螺仪X轴的高八位,在读取陀螺仪x轴的低八位,再把他们|在一起,的、得到16位数据后,在用指针传递进来的地址,把读到的数据,通过指针返回回去;
参数:无;
功能:返回6个16位的值,分别代表XYZ的加速度值和陀螺仪值;
*因为函数正常只能返回一个返回值,这里需要六个返回值,多返回值的方法:1在函数外定义六个全局变量,子函数读到的数据直接写道全局变量里,六个全局变量在主函数中共享,相当于返回六个值;
2.用指针,进行变量的地址传递,来实现多返回值;
3.用结构体对多个变量进行打包;
使用第二种方法:定义六个指针变量,之后在主函数中定义变量;通过指针,把主函数变量的地址传递到子函数来,子函数通过传递过来的地址,操作主函数的变量,这样子函数结束后,主函数里的变量的值就是子函数想要返回的值;
为什么读取的值是16位——>因为是通过ADC采集电压值,所以是16位(详细可见MPU6050;)
因为接收值是16的,所以八位数据会进行自动强制类型转换,所以左移八位不会出错
因为这些寄存器是连续的,我们可以通过连续读取多个字节,一次性读取14个字节加速度,陀螺仪XYZ数据+两位温度;
在.H文件中声明
测试显示:
根据在MPU6050介绍的模型:1943/32768=x/16g;x大约为1g,测得Z轴加速度值为1g
加速度计模型,我们选择最大量程16g,测得数据是1945,1945/32768
陀螺仪:测得数据/32768=x/16(满量程);
5.2硬件SPI实现对MPU的控制;
将软件改成硬件实现,只需要更改底层通信层代码的操作,驱动层不需要修改,因为他们是调用底层通信函数来实现功能的,这就是代码隔离封装的好处;
主要实现步骤:1.RCC开启GPIO和I2C的时钟;
2.初始化GPIO,将引脚配置成开漏输出;
3.初始化I2C外设;
4.生成时序单元;
5.使能12C;
首先查看库函数:
通过结构体初始化12C;
生成时序:起始条件:查看手册通过配置CR1寄存器的START位;
产生终止条件:
产生应答位:
发送一个字节:
接收一个字节:
发送7位地址:
如果不是写入操作,就把Address最低为置0,否则就置1;(可以用代替函数代替)
查看标志位状态监控:
分为三种:1基本状态监控,同时判断一个或者多个标志位,确定那几个EVEN发生;
2高级状态监控;(把SR1和SR2两个状态寄存器拼接为16的值输出)
3.基于标志位的监控状态;判断某一个标志位;
读取标志位,清除标志位,读取中断标志位,清除中断标志位;
1.RCC开始GPIO和I2C时钟;
2.初始化GPIO,配置输出模式为,AF_OD复用开漏输出,选择引脚PB11和PB10;
3.初始化I2C,配置IC2的应答位,时钟频率,时钟占空比,响应地址位数,I2C作为从设备时的响应地址;
4.使能I2C
因为I2C是低电平,产生下降沿的时候,是强下拉,所以下降沿变化很快,但是输出高电平,是释放,外部上拉电阻产生弱上拉,所以上升沿有一个缓冲,随着时钟频率越来越大,缓冲影响越大,大于100Khz时,我们通过改变占空比,使低电平时间逐渐增多,原因是因为,低电平改变数据,高电平读取数据,数据变化需要一定的时间来翻转波形,所以在快速模式下要给低电平更多的时间,要不低电平来不及数据变化,高电平读取也无效,所以在小于100kHz时,占空比是1:1,大于100kHz时,低电平占空比越来越大;
这就是为什么频率过高的时候,I2C的传输速度会收到限制的原因;
5.时序单元
起始条件:
功能:产生一个起始条件;
参数:无;
返回值:无;
//软件配置我们通过Delay,进行阻塞式的流程,函数运行完后,对于的波形也发送完成,但是硬件生成时,是直接把寄存器对于位进行修改,波形是否完成需要进行标志位判断,根据时序图,进行判断;
参数是指定要检查哪个事件,返回值是SUCCESS表示最后一次事件等于我们指定的事件,ERROR表示不等于;
停止条件:
发送一个字节:
接收一个字节:
不需要应答位,因为在我们发送一个字节和接收一个字节后,硬件会自动产生应答位;
六、实际应用;