STM32 I2C通信详解及应用示例

通信协议见(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.初始化I2C,配置I2C‘

4.使能CMDI2C;

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表示不等于;

停止条件:

发送一个字节:

接收一个字节:

不需要应答位,因为在我们发送一个字节和接收一个字节后,硬件会自动产生应答位;

六、实际应用;

物联沃分享整理
物联沃-IOTWORD物联网 » STM32 I2C通信详解及应用示例

发表评论