stm32笔记 IIC

笔记来源于江科协议的视频

芯片采用与stm32F103C8T6

软件IIC

简介

•I2C(Inter IC Bus)是由Philips公司开发的一种通用数据总线

•两根通信线:SCL(Serial Clock)时钟线,完成同步时序,降低对硬件的依赖,、SDA(Serial Data)数据线,只有一根数据线,半双工,兼容发送和接受,

•同步,半双工

•带数据应答

•支持总线挂载多设备(一主多从、多主多从),一主多从的意思,单片机为主机,挂载在IIC总线上所有外部模块都是从机,从机只有被主机点名之后才能控制IIC总线。多主多从就是在总线上任何一个模块都可以跳出来当主机,当多个模块跳出来要当主机,就会发生总线冲突,这时,IIC协议就会进行仲裁,仲裁胜利的一方当主机,失败的一方当从机,由于时钟线也是主机控制的,所以在多主机的模式下,还要进行时钟同步,

硬件电路

上左图是一个典型的一主多从的模型,图中左边CPU就是总线的主机,主机完全掌控SCL,在空闲状态下,主机可以主动发起对SDA的控制,只有在从机发送数据和从机应答的时候,主机才会转交SDA的控制权给从机,图中下面被控IC1~IC4都是被挂载在IIC总线上的从机,这就是一主多从模型协议的规定。

接线要求:

•所有I2C设备的SCL连在一起,SDA连在一起

•设备的SCL和SDA均要配置成开漏输出模式

•SCL和SDA各添加一个上拉电阻,阻值一般为4.7KΩ左右

如何配置每个设备SCL和SDA的输入模式?因为是一主多从,主机对SCL拥有绝对控制权,所以主机的SCL可以配置成推挽输出,所有从机的SCL配置成浮空输入或上拉输入,数据流向,主机发送,所有从机接受。但是SDA线这里,发送数据的时候是输出,在接受数据的时候是输入,同时,从机的SDA也是在输入和输出之间来回切换的,为了避免总线协调问题导致电源短路问题,IIC的设计,禁止所有设备输出强上拉的高电平,采用外置弱上拉加开漏输出的电路结构。 CPU引脚和被控IC引脚结构都如上右图,右图左边是SCL的结构,右边是SDA的结构,引脚的信号进来,都可以通过一个数据缓冲器或者是施密特触发器,进行输入,输入对电路没有影响,任何设备在任何时候都可以输入。输出这里,采用开漏输出的配置,输出低电平时,导通,强下拉,输出高电平时,引脚处于浮空状态,这就是开漏输出。这样,所有设备只能输出低电平不能输出高电平。为了避免高电平时的引脚浮空,需要在总线外面加一个弱上拉。

第一这样杜绝了电源短路现象,保证了电路安全。第二也避免了引脚模式的频繁切换,开漏加弱上拉的模式,同时兼备了输入和输出,因为在开漏模式下,输出高电平相当于断开引脚,所以在输入之前,可以直接输出高电平,不需要换成输入模式,第三这个模式有个“线与“的现象,只有有一个设备处于低电平或多个设备处于低电平,总线就处在低电平,只有所有设备处于高电平,总线才高电平,IIC可以利用这个特征,执行主机模式下的时钟同步和总线仲裁。

时序设计

基本单元

•起始条件:SCL高电平期间,SDA从高电平切换到低电平

•终止条件:SCL高电平期间,SDA从低电平切换到高电平

一个完整的数据帧是以起始条件开始,终止条件结束,起始和终止都是由主机产生的,从机不允许产生起始、终止条件,在空闲状态时,从机不允许操作,如果允许,那就是多主机模型,

•发送一个字节:SCL低电平期间,主机将数据位依次放到SDA线上(高位先行),然后释放SCL,从机将在SCL高电平期间读取数据位,所以SCL高电平期间SDA不允许有数据变化,依次循环上述过程8次,即可发送一个字节。

起始条件之后,第一个字节必须是主机发送的,低电平期间SDA切换数据,高电平期间,SCL读取数据,高电平期间,SDA不允许变化,因为有时钟线进行同步,所以主机一个字节发一半,突然进中断了,不操作SCL和SDA了,那么时序在中断的位置不断拉长,SCL和SDA都暂停变化,传输也暂停,等中断结束,主机回来继续操作,传输仍然不会出问题,这就是同步时序的好处,发送期间,SCL和SDA全程都是主机控制,从机只能被动读取。

•接收一个字节:SCL低电平期间,从机将数据位依次放到SDA线上(高位先行),然后释放SCL,主机将在SCL高电平期间读取数据位,所以SCL高电平期间SDA不允许有数据变化,依次循环上述过程8次,即可接收一个字节(主机在接收之前,需要释放SDA)

释放SDA其实相当于切换输入模式,所有设备包括主机都处于输入模式,主机需要发送时,可以主动拉低SDA,而主机在被动接受的时候,就需要先释放SDA,接受字节,SDA由从机控制。

•发送应答:主机在接收完一个字节之后,在下一个时钟发送一位数据,数据0表示应答,数据1表示非应答

•接收应答:主机在发送完一个字节之后,在下一个时钟接收一位数据,判断从机是否应答,数据0表示应答,数据1表示非应答(主机在接收之前,需要释放SDA)

IIC一主多从模型,主机可以访问总线上的任何一个设备,如何发送指令,来确定哪个设备? 首先把每个设备都确定唯一的设备地址,主机在起始条件之后,会发送一个从机地址,所由从机都会收到第一个字节,与自己的地址作比较,如果一样,从机与主机建立连接,在同一条总线里,挂载的每个设备地址必须不一样。从机设备地址,在IIC协议标准里分为7位地址,和10位地址。 在每个IIC设备出厂时,厂商会为它分配一个7为的地址,可以在芯片手册里面找到,一般器件的最后几位是可以在电路中改变的,

如:MPU6050地址中的最后一位,就可以由板子上AD0引脚确定,AD0接低电平,地址为1101000,接高电平,地址为1101001,低位可以用引脚来回切换,即是相同型号的芯片也能挂载在同一总线上。

时序案例

上面的线SCL,下面的线SDA,空闲状态都是高电平,先是产生起始条件,之后是发送字节的时序信号,从机地址+读写位,从机地址7位,读写地址1位,高7位从机地址,最低位读写位,0代表写入,1代表读出,发送完地址+读写位之后,紧跟着的是应答位,在这时,主机要释放SDA,但是协议规定,从机要在这个位拉低SDA,该应答的时候,从机置0,应答结束之后,从机在放开SDA,综合两种波形,综合线与特性,在主机释放SDA之后,由于SDA也被从机拽主,所以主机松手后,SDA并没有回弹高电平,这个过程就代表产生了应答。第二个字节可以是一些地址或者指令。

第一段还是发送从机地址,不过读写位为1,紧跟着是应答信号,应答之后,从机控制SDA线,主机在控制期间读取SDA总线上的数据,这里没有指定地址这个环节,在从机中,所有寄存器都被分配在一个线性区域中,并且,会有一个单独的指针变量指示这个寄存器,指针默认指向0地址,每读出和写入一个字节后,指针就会自增一次,从机没有指向地址读入的功能,从机会返回当前指针指向寄存器的值,

•指定地址读

•对于指定设备(Slave Address),在指定地址(Reg Address)下,读取从机数据(Data)

对于指定设备,指定地址,读取从机设备,前两段指定地址写,前两端只是设置地址,但还没有写数据,后面两段是当前地址读,加在一起就是指定地址读取。

首先是起始地址,从机设备地址+读写为写0,从机应答之后,再发送一个字节,第二个字节用来指定地址,这个地址就写入到地址指针里面了,然后不写数据,再来一个起始条件,重新寻址+读1,最后一段就是要读取的数据。

MPU6050简介

简介

•MPU6050是一个6轴姿态传感器,可以测量芯片自身X、Y、Z轴的加速度、角速度参数,通过数据融合,可进一步得到姿态角,常应用于平衡车、飞行器等需要检测自身姿态的场景

姿态角:也叫欧拉角,就是飞机机身相对于初始三个轴的夹角,飞机机头下倾或上扬,这个轴的夹角叫俯仰,Pitch,飞机机身左翻滚或右翻滚,这个轴的夹角叫滚转,Roll, 飞机机身保持水平,机头向左转向或向右转向,这个轴的夹角叫偏航,简单来说,就是欧拉角表示飞机此时的姿态,一般任何传感器都无法获得准确的欧拉角,必须进行数据融合,常见的融合算法,互补滤波,卡尔曼滤波,

3轴加速度计(Accelerometer):测量X、Y、Z轴的加速度,左下角图,XYZ轴就是三位坐标系,横向X,纵向Y,垂直轴Z,在XYZ轴,这个芯片内部都分别布置了一个加速度计,第二个图就是加速度计的结构图,水平虚线是感应轴线,中间是具有一定质量,可以左右滑动的滑块,左右各有一个弹簧顶这滑块,当滑块移动时,就会带动上面的电位器滑动。这个电位器就是一个分压电阻,测量电位器输出的电压,就能得到滑块的速度值了,这个加速度计就是一个弹簧测力计,三个轴都有一个测力计。

•3轴陀螺仪传感器(Gyroscope):测量X、Y、Z轴的角速度,分别表示了芯片绕X轴,绕Y轴、绕Z轴旋转的角速度,对角速度进行积分,就可以得到角度,

参数

•16位ADC采集传感器的模拟信号,量化范围:-32768~32767, 无论是加速度计还是陀螺仪,基本原理都是设置一种装置,当传感器所感应的参数变化时,装置带动电位器滑动,或者装置本身的电阻,可以感应参数变化而变化,这样再外接一个电压,通过电阻分压,就能把显示世界的各种状态用电压表示出来了。芯片内部自带AD转换,可以对各种模拟参量进行量化,因为传感器每个轴都有正负的数据,所以输出结果是个有符号数,

•加速度计满量程选择:±2、±4、±8、±16(g),重力加速度,1g = 9.8m/s的方,

•陀螺仪满量程选择: ±250、±500、±1000、±2000(°/sec)每秒旋转多少度, 如果测量物体运动非常剧烈,就可以把满量程选择大一些,防止加速度、角速度超出量程,如果测量物体比较平缓,可以选择小量程,这样分辨率会更高一些,

•可配置的数字低通滤波器,可以配置寄存器对输出数据进行低通滤波,如果输出数据抖动太厉害,可以加一点低通滤波,

•可配置的时钟源

•可配置的采样分频,可为AD转换和内部其他电路提供时钟,控制分配系数,就可以控制AD转换的快慢了,

•I2C从机地址:1101000(AD0=0) 高位和低位分割,发送(0x68>>1),或者直接发送0xD0写地址或者0xD1读地址,这样就可以不用左移了,

硬件电路

上图左边是MPU6050的原理图,图中右边是芯片,左下是8针脚排针,左上角LDO低压差线性稳压器,芯片引脚有很多功能,引出来的引脚有VCC,GND,SCL和SDA这两个引脚是I2C通信的引脚,模块已经内置了两个4.7K的上拉电阻了,直接接GPIO口,不用再接电阻了, XCL、XDA就是主机IIC通信引脚,扩展的时候用到,通常外接磁力计,和气压计,接上之后,MPU6050的主机接口就可以直接访问扩展模块的数据,数据读到MPU6050里面,MPU6050里面有个DMP单元,进行数据融合和姿态解算, 当然也可以直接挂载在SCL和SDA线上。AD0从机地址的最低位,接低电平,地址为1101000,接高电平,地址为1101001,这里可以看到,有一个电阻,默认弱下拉到低电平,引脚悬空,就是低电平,如果接高电平,可以把AD0直接引到VCC,最后一个引脚INT,也就是中断输出引脚,可以配置芯片内部的一些事件,来触发中断引脚输出,芯片内部还有一些小功能,如:自由落体检测,运动检测,零运动检测,这些信号都可以触发INT引脚产生跳变,需要的话,进行中断配置。

左上角LDO,MPU6050芯片的供电是2.375~3.46,属于3.3V,不能直接接5V,所以加了个3.3V的稳压器,输入电压VCC_5V可以是3.3V到5V之间,然后经过3.3V的稳压器,输出3.3V稳定的电压,给芯片供电,

MPU6050模块框图

上图是整个芯片的内部结构,左上角就是时钟系统,输入脚和输出脚,一般使用内部时钟,硬件电路那里,CLKIN接了地,CLKOUT没有引出来, CLOCK下面灰色部分,就是内部的传感器,XYZ轴的加速度,和XYZ轴的陀螺仪传感器,芯片内部还集成了一个温度传感器,通过分压后, 输出模拟电压,然后通过ADC进行模数转换,转换完成之后,传感器的数据统一放到数据寄存器中,读取数据寄存器,就能得到传感器测量的值了。芯片内部的转换全自动进行,只需要设置频率刷新, 每个传感器都有个自测单元,是用来验证芯片好坏的,启动后,芯片内部就会模拟一个外力施加在传感器上,这个外力会导致传感器数据比平时大一些, 如何进行自测?先使能自测,读取数据,再失能自测,读取数据,两个数据相减,得到的数据叫自测响应,自测响应在一定范围内则正常,超出范围则异常,范围芯片手册给出。

最下面的Charge Pump,是电荷泵,或者叫充电泵,CPOUT需要外接一个电容,至于需要什么样的电容,需要看芯片手册,电荷泵是一种升压电路,由于陀螺仪内部需要高电压支持,所以需要电荷泵升压,升压过程自动。

右边是寄存器和通信接口部分,中断状态寄存器可以控制内部哪些事件到中断引脚的输出,FIFO是先入先出寄存器,可以对数据流进行缓存,配置寄存器可以对内部的各个电路进行配置,传感器寄存器,也就是数据寄存器,存储了各个存储器的数据,工厂校准,就是内部的传感器进行了校准,DMP是芯片内部自带的姿态解算的硬件算法,配合官方的DMP库,可以进行姿态解算, FSYNC是帧同步。

右边CS、AD0、SCL、SCK使用来和stm主机通信的,AUX_CL、AUX_DA用于MPU6050扩展的设备进行通信,左边则是一个接口旁路选择器,就是开关,如果接到SCL、SCK,stm32可以控制MPU6050扩展设备,如果接到MPU6050控制,辅助IIC引脚由MPU6050控制,两条IIC总线独立分开。

右下角是供电部分。

IIC外设

简介

•STM32内部集成了硬件I2C收发电路,可以由硬件自动执行时钟生成、起始终止条件生成、应答位收发、数据收发等功能,减轻CPU的负担,也就是由硬件电路电路自动翻转引脚电平,软件写需要写入控制寄存器CR和数据寄存器DR,为了实时读取状态,还要读取状态寄存器SR,硬件自动实现时序,节省软件资源。

•支持多主机模型,固定多主机:总线上有两个或更多的固定的主机。如果两个主机产生冲突,则进行总线仲裁。可变多主机:任何一个设备在总线空闲时跳出来作为主机,指定其他设备进行通信,通信完成之后,跳出来的主机退回原来的位置当从机,当多个设备跳出来,则进行总线仲裁,

•支持7位/10位地址模式 7位地址模式:起始条件之后寻址7位地址+读写位, 10地址模式:起始条件之后两个时序用来寻址,2个字节-读写位=15位 ,拿10个字节寻址,剩下5个是标志位,标志位是第一个字节前5位11110,用来声明是10位地址,5个标志位只会在10位地址模式下出现,不会再7位地址模式下出现。

•支持不同的通讯速度,标准速度(高达100 kHz),快速(高达400 kHz)

•支持DMA

•兼容SMBus协议 ,系统管理总线,是基于IIC总线改进而来,

•STM32F103C8T6 硬件I2C资源:I2C1、I2C2

IIC框图

左边是SDA、SCL通信的两个引脚,SMBALERT是SMBus用的,IIC用不到,数据收发的核心部分是数据寄存器和数据移位寄存器,需要发送数据时,可以把字节数据写到数据寄存器DR中,当移位寄存器没有移位时,这个数据寄存器就会转到移位寄存器里面,移位的过程中,可以把下一个数据放在数据寄存器里面等着,一旦数据完成,下一个数据就可以发送,当数据由数据寄存器转到移位寄存器,就会置状态寄存器TXE位为1,表示发送寄存器为空,这是发送的流程。 在接受时,输入的数据一位一位的移到数据移位寄存器,当一个字节的数据收齐之后,数据就从数据移位寄存器转到数据寄存器,同时置RXNE标志位,表示接收器非空,这时候就可以读数据了。

比较器和地址寄存器是从机模式用的,stm32的IIC是基于可变多主机模型设计的,STM32不进行通信时,就是从机,自身地址寄存器可以自定义自身的从机地址,STM32作为从机时,收到的寻址通过比较器判断,地址相同时,STM32就作为从机,STM32可以同时响应两个从机地址。

当我们发送一个多字节数据帧时,帧错误校验计算(PEC)可以执行CRC校验计算。

时钟控制,控制SCL,控制逻辑电路,写入控制寄存器,对整个电路进行控制,读取状态寄存器,得知电路的工作状态,控制逻辑电路下面有个中断,可以开启中断,还可以触发DMA请求和响应。

IIC结构框图

移位寄存器和数据寄存器DR的配合是通信部分的核心部分,因为IIC是高位先行,移位寄存器是向左移位,发送时,高位先发送,一个SCL时钟移位一次,移位8次,这样就把一个字节移出去了,在接受时,数据通过GPIO口从右边依次移过来,移动8次,一个字节接受完成,使用GPIO口时,模式都要配置成复用开漏输出,复用就是GPIO的状态交给片上外设来控制,开漏输出是IIC要求的端口配置,时钟控制器通过GPIO去控制时钟线,多主机模型的情况,时钟也会输入, 输出数据,通过GPIO,输出到端口,输入数据也是通过GPIO输入到移位寄存器。

时序流程图

主机发送

这里有7位地址发送和10位地址发送,区别7位地址,起始条件之后一个字节是寻址,10位地址起始条件之后两个字节都是寻址,10位地址第一个字节是5位标志位11110+2位地址+1位读写位,第二个字节就是8位地址。

初始化之后,总线默认空闲状态,stm32默认从模式,为了产生起始条件,stm32需要写入控制器,Start位,之后,stm32由从模式转为主模式,也就是多主机模型下,stm32跳出来作为主机,控制完硬件电路之后,起始条件之后,会发生EV5事件,可以把它当作标志位,因为有的状态可以产生多个标志位,EVX就是组合了多个标志位的大标志位,在库函数中也有对应的也有检查EVX是否发生的函数,EV5:SB=1(代表起始条件已发送),读取SR1让后将地址写入DR寄存器将清除该事件,检测起始条件已发送时,就可以发送一个字节的从机地址了,从机地址需要写到数据寄存器DR中,写到DR之后, 硬件电路自动会把数据转到移位寄存器里,在把这一字节发送到IIC总线上,之后硬件会自动接受应答并判断,如没有应答,硬件会置应答失败的标志位,这个标志位可以申请中断来提醒。寻址完成之后会发生EV6事件,EV6:ADDR=1(代表地址发送完成),结束后,是EV8_1,表示TXE=1,移位寄存器空,数据寄存器空,需要发送数据,一旦写入数据之后,DR寄存器会立刻移位寄存器发送,然后就是EV8事件,移位寄存器非空,数据寄存器空,这是移位急寄存器正在发送数据的状态,发送的时候数据先写入数据寄存器,如果移位寄存器没有数据再转到移位寄存器进行发送,数据1后面A下面EV8就没有了对应的则是写入DR寄存器将清除该事件,接受应答后,数据2转入移位寄存器进行发送,此时的状态是移位寄存器非空,数据寄存器空,所以EV8事件再次发生,数据2还在发送,但是下一个数据已经写到数据寄存器等着了,所以EV8事件消失了, 应答之后,产生EV8事件,写入数据寄存器,EV8事件消失,一旦检测到EV8事件就可以写入数据了,最后当想要发送的数据写完之后,没有新的数据写入数据寄存器了,当前移位寄存器也移位完成时,此时数据寄存器空、移位寄存器也空,这里则是EV8_2事件,TxE=1,BTF=1(字节发送结束), 控制寄存器STOP置1产生停止条件。

主机接受

7位主接受的流程:起始,从机地址+读,接受应答,接受数据,发送应答,接受数据,发送应答,最后一个数据,给非应答,之后终止。当前地址读,并没有指定地址读。

10位地址主机接受流程:起始,发送帧头,帧头的读写位还是写,应答,8位地址,应答,转入读时序,重新产生起始信号,发送帧头+读,因为发送读的指令后,必须立刻转入读时序,所以第二个字节就没有了,直接转入接受数据的时序。

首先产生起始条件,等待EV5,寻址,接受应答,结束后,产生EV6事件,代表寻址已完成,数据1正在通过移位寄存器进行输入,EV6_1事件,数据1还在移位,还没收到,所以EV6_1没有标准位,当这个时序单元完成时,硬件会自动根据我们的配置,把应答位发送出去,时序结束后,代表移位寄存器已经成功一个字节的数据1,移入的一个字节整体转移到数据寄存器,同时置RXNE标志位,表示数据寄存器非空,也就是收到一个字节的状态,这个状态就是EV7事件,RXNE=1,数据寄存器非空,读DR寄存器清空该事件,之后,数据2完成,产生EV7事件,当不要接受时,需要再最后一个时序单元发生时,提前把应答位控制寄存器ACK置0,并设置终止条件请求。这就是EV7_1事件。

代码

软件IIC

IIC.c

#include "stm32f10x.h"                  // Device header
#include "Delay.h"

//用GPIOB10pin作为SCL,写SCL函数,
void MyIIC_W_SCL(uint8_t BitValue)
{
	GPIO_WriteBit(GPIOB,GPIO_Pin_10,BitValue);
	Delay_us(10);
}
//用GPIOB11pin作为SCL,写SDA函数
void MyIIC_W_SDA(uint8_t BitValue)
{
	GPIO_WriteBit(GPIOB,GPIO_Pin_11,BitValue);
	Delay_us(10);
}

//读GPIOB 11pin 也就是读SDA线
uint8_t MyIIC_R_SDA(void)
{
	uint8_t value;
	value = GPIO_ReadInputDataBit(GPIOB,GPIO_Pin_11);
	Delay_us(10);
	return value;
}
//IIC初始化,10pin为SCL  11pin为SDA
void MyIIC_Init(void)
{
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE);
	
	GPIO_InitTypeDef GPIO_InitStructure;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_OD;
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10 | GPIO_Pin_11;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOB,&GPIO_InitStructure);

	GPIO_SetBits(GPIOB,GPIO_Pin_10 | GPIO_Pin_11);
}

//开始信号
void MyIIC_Strat(void)
{
	MyIIC_W_SDA(1);			
	MyIIC_W_SCL(1);
	MyIIC_W_SDA(0);
	MyIIC_W_SCL(0);
}

//停止信号
void MyIIC_Stop(void)
{
	MyIIC_W_SDA(0);
	MyIIC_W_SCL(1);
	MyIIC_W_SDA(1);
}

//IIC发送一个字节
void MyIIC_SendByte(uint8_t Byte)
{
	uint8_t i;
	for(i=0;i<8;i++)
	{
		MyIIC_W_SDA(Byte&(0x80>>i));
		MyIIC_W_SCL(1);
		MyIIC_W_SCL(0);
	}
}

//IIC读取一个字节
uint8_t MyIIC_ReceiveByte(void)
{
	uint8_t i,Value = 0x00;
	MyIIC_W_SDA(1);			//并不是置高电平,而是释放总线
	for(i=0;i<8;i++)
	{
		//Value |= (MyIIC_R_SDA()<<(8-1-i));
		if(MyIIC_R_SDA() == 1){Value |= (0x80>>i);}
		MyIIC_W_SCL(1);
		MyIIC_W_SCL(0);
	}
	return Value;
}

//发送应答位
void MyIIC_SendAck(uint8_t Ack)
{
	MyIIC_W_SDA(Ack);
	MyIIC_W_SCL(1);
	MyIIC_W_SCL(0);
}

//接受应答
uint8_t MyIIC_ReceiveAck(void)
{
	uint8_t Ack;
	MyIIC_W_SDA(1);
	MyIIC_W_SCL(1);
	Ack = MyIIC_R_SDA();
	MyIIC_W_SCL(0);
	return Ack;
}

MPU6050.c

#include "stm32f10x.h"                  // Device header
#include "MyIIC.h"
#include "MPU6050_Reg.h"

#define MPU6050_adderss 0xD0			//MPU6050的从机地址

//写寄存器
void Mpu6050_WriteReg(uint8_t Adderss,uint8_t DataBits)
{
	MyIIC_Strat();		//开始信号
	MyIIC_SendByte(MPU6050_adderss);		//发送从机地址
	MyIIC_ReceiveAck();		//接受应答
	MyIIC_SendByte(Adderss);		//发送寄存器地址
	MyIIC_ReceiveAck();		
	MyIIC_SendByte(DataBits);		//发送数据
	MyIIC_ReceiveAck();
	MyIIC_Stop();			//停止信号
}

uint8_t Mpu6050_ReadReg(uint8_t Adderss)
{
	uint8_t Value;
	
	MyIIC_Strat();		//开始信号
	MyIIC_SendByte(MPU6050_adderss);		//发送从机地址
	MyIIC_ReceiveAck();		//接受应答
	MyIIC_SendByte(Adderss);		//发送寄存器地址
	MyIIC_ReceiveAck();	

	MyIIC_Strat();
	MyIIC_SendByte(MPU6050_adderss | 0x01);		//发送从机地址
	MyIIC_ReceiveAck();		//接受应答
	Value = MyIIC_ReceiveByte();			//读取寄存器中的数据
	MyIIC_SendAck(1);				///结束  发送应答信号
	MyIIC_Stop();		//停止
	return Value;
}

//Mpu6050初始化
void Mpu6050_Init(void)
{
	MyIIC_Init();		//IIC初始化
	
	//寄存器初始化
	Mpu6050_WriteReg(MPU6050_PWR_MGMT_1, 0x01);		//电源管理寄存器1,取消睡眠模式,选择时钟源为X轴陀螺仪
	Mpu6050_WriteReg(MPU6050_PWR_MGMT_2, 0x00);		//电源管理寄存器2,保持默认值0,所有轴均不待机
	Mpu6050_WriteReg(MPU6050_SMPLRT_DIV, 0x09);		//采样率分频寄存器,配置采样率
	Mpu6050_WriteReg(MPU6050_CONFIG, 0x06);			//配置寄存器,配置DLPF
	Mpu6050_WriteReg(MPU6050_GYRO_CONFIG, 0x18);	//陀螺仪配置寄存器,选择满量程为±2000°/s
	Mpu6050_WriteReg(MPU6050_ACCEL_CONFIG, 0x18);	//加速度计配置寄存器,选择满量程为±16g
}

//获取ID号
uint8_t Mpu6050_GetID(void)
{
	return Mpu6050_ReadReg(MPU6050_WHO_AM_I);		//返回WHO_AM_I寄存器的值
}

struct Mpu6050_AG
{
	int16_t AccX;		//加速度
	int16_t AccY;
	int16_t AccZ;
	int16_t GyroX;		//角速度
	int16_t GyroY;
	int16_t GyroZ;
};


/**
  * 函    数:MPU6050获取数据
  * 参    数:AccX AccY AccZ 加速度计X、Y、Z轴的数据,使用输出参数的形式返回,范围:-32768~32767
  * 参    数:GyroX GyroY GyroZ 陀螺仪X、Y、Z轴的数据,使用输出参数的形式返回,范围:-32768~32767
  * 返 回 值:无
  */
struct Mpu6050_AG Mpu6050_GetData(void)
{
	uint8_t DataH, DataL;								//定义数据高8位和低8位的变量
	struct Mpu6050_AG AG;
	
	DataH = Mpu6050_ReadReg(MPU6050_ACCEL_XOUT_H);		//读取加速度计X轴的高8位数据
	DataL = Mpu6050_ReadReg(MPU6050_ACCEL_XOUT_L);		//读取加速度计X轴的低8位数据
	AG.AccX = (DataH << 8) | DataL;						//数据拼接,通过输出参数返回
	
	DataH = Mpu6050_ReadReg(MPU6050_ACCEL_YOUT_H);		//读取加速度计Y轴的高8位数据
	DataL = Mpu6050_ReadReg(MPU6050_ACCEL_YOUT_L);		//读取加速度计Y轴的低8位数据
	AG.AccY = (DataH << 8) | DataL;						//数据拼接,通过输出参数返回
	
	DataH = Mpu6050_ReadReg(MPU6050_ACCEL_ZOUT_H);		//读取加速度计Z轴的高8位数据
	DataL = Mpu6050_ReadReg(MPU6050_ACCEL_ZOUT_L);		//读取加速度计Z轴的低8位数据
	AG.AccZ= (DataH << 8) | DataL;						//数据拼接,通过输出参数返回
	
	DataH = Mpu6050_ReadReg(MPU6050_GYRO_XOUT_H);		//读取陀螺仪X轴的高8位数据
	DataL = Mpu6050_ReadReg(MPU6050_GYRO_XOUT_L);		//读取陀螺仪X轴的低8位数据
	AG.GyroX = (DataH << 8) | DataL;						//数据拼接,通过输出参数返回
	
	DataH = Mpu6050_ReadReg(MPU6050_GYRO_YOUT_H);		//读取陀螺仪Y轴的高8位数据
	DataL = Mpu6050_ReadReg(MPU6050_GYRO_YOUT_L);		//读取陀螺仪Y轴的低8位数据
	AG.GyroY = (DataH << 8) | DataL;						//数据拼接,通过输出参数返回
	
	DataH = Mpu6050_ReadReg(MPU6050_GYRO_ZOUT_H);		//读取陀螺仪Z轴的高8位数据
	DataL = Mpu6050_ReadReg(MPU6050_GYRO_ZOUT_L);		//读取陀螺仪Z轴的低8位数据
	AG.GyroZ = (DataH << 8) | DataL;						//数据拼接,通过输出参数返回
	
	return AG;
}


寄存器

#ifndef __MPU6050_REG_H
#define __MPU6050_REG_H

#define	MPU6050_SMPLRT_DIV		0x19
#define	MPU6050_CONFIG			0x1A
#define	MPU6050_GYRO_CONFIG		0x1B
#define	MPU6050_ACCEL_CONFIG	0x1C

#define	MPU6050_ACCEL_XOUT_H	0x3B
#define	MPU6050_ACCEL_XOUT_L	0x3C
#define	MPU6050_ACCEL_YOUT_H	0x3D
#define	MPU6050_ACCEL_YOUT_L	0x3E
#define	MPU6050_ACCEL_ZOUT_H	0x3F
#define	MPU6050_ACCEL_ZOUT_L	0x40
#define	MPU6050_TEMP_OUT_H		0x41
#define	MPU6050_TEMP_OUT_L		0x42
#define	MPU6050_GYRO_XOUT_H		0x43
#define	MPU6050_GYRO_XOUT_L		0x44
#define	MPU6050_GYRO_YOUT_H		0x45
#define	MPU6050_GYRO_YOUT_L		0x46
#define	MPU6050_GYRO_ZOUT_H		0x47
#define	MPU6050_GYRO_ZOUT_L		0x48

#define	MPU6050_PWR_MGMT_1		0x6B
#define	MPU6050_PWR_MGMT_2		0x6C
#define	MPU6050_WHO_AM_I		0x75



#endif

主函数

#include "stm32f10x.h"                  // Device header
#include "Delay.h"
#include "OLED.h"
#include "Mpu6050.h"

uint8_t ID;								//定义用于存放ID号的变量
struct Mpu6050_AG AG;				//存放数据的结构体
int main(void)
{
	/*模块初始化*/
	OLED_Init();		//OLED初始化
	Mpu6050_Init();		//MPU6050初始化
	
	/*显示ID号*/
	OLED_ShowString(1, 1, "ID:");		//显示静态字符串
	ID = Mpu6050_GetID();				//获取MPU6050的ID号
	OLED_ShowHexNum(1, 4, ID, 2);		//OLED显示ID号
	
	while (1)
	{
		AG = Mpu6050_GetData();
		OLED_ShowSignedNum(2, 1, AG.AccX, 5);					//OLED显示数据
		OLED_ShowSignedNum(3, 1, AG.AccY, 5);
		OLED_ShowSignedNum(4, 1, AG.AccZ, 5);
		OLED_ShowSignedNum(2, 8, AG.GyroX, 5);
		OLED_ShowSignedNum(3, 8, AG.GyroY, 5);
		OLED_ShowSignedNum(4, 8, AG.GyroZ, 5);
	}
}

硬件IIC

#include "stm32f10x.h"                  // Device header
#include "MPU6050_Reg.h"

#define MPU6050_adderss 0xD0			//MPU6050的从机地址


//事件等待函数
void MPU6050_WaitEvent(I2C_TypeDef* I2Cx, uint32_t I2C_EVENT)
{
	uint32_t Count=10000;
	while(I2C_CheckEvent(I2Cx,I2C_EVENT) != SUCCESS)
	{
		Count--;
		if(Count == 0)
			break;
	}
}

//写寄存器
void Mpu6050_WriteReg(uint8_t Adderss,uint8_t DataBits)
{
	I2C_GenerateSTART(I2C2,ENABLE);		//硬件IIC开始
	MPU6050_WaitEvent(I2C2,I2C_EVENT_MASTER_MODE_SELECT);   //等待EV5事件
	
	I2C_Send7bitAddress(I2C2,MPU6050_adderss,I2C_Direction_Transmitter);		//发送地址
	MPU6050_WaitEvent(I2C2,I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED);		//等待EV6
	
	I2C_SendData(I2C2,Adderss);		//发送寄存器
	MPU6050_WaitEvent(I2C2,I2C_EVENT_MASTER_BYTE_TRANSMITTING);			//等待EV8
	
	I2C_SendData(I2C2,DataBits);
	MPU6050_WaitEvent(I2C2,I2C_EVENT_MASTER_BYTE_TRANSMITTED);  //等待EV8_2
	
	I2C_GenerateSTOP(I2C2,ENABLE);
	
}

uint8_t Mpu6050_ReadReg(uint8_t Adderss)
{
	uint8_t Data;
	
	I2C_GenerateSTART(I2C2,ENABLE);		//硬件IIC开始
	MPU6050_WaitEvent(I2C2,I2C_EVENT_MASTER_MODE_SELECT);   //等待EV5事件
	
	I2C_Send7bitAddress(I2C2,MPU6050_adderss,I2C_Direction_Transmitter);		//发送地址
	MPU6050_WaitEvent(I2C2,I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED);		//等待EV6
	
	I2C_SendData(I2C2,Adderss);
	MPU6050_WaitEvent(I2C2,I2C_EVENT_MASTER_BYTE_TRANSMITTED);  //等待EV8_2
	
	I2C_GenerateSTART(I2C2,ENABLE);		//硬件IIC开始
	MPU6050_WaitEvent(I2C2,I2C_EVENT_MASTER_MODE_SELECT);   //等待EV5事件
	
	I2C_Send7bitAddress(I2C2,MPU6050_adderss,I2C_Direction_Transmitter);		//发送地址
	MPU6050_WaitEvent(I2C2,I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED);		//等待EV6
	
	I2C_AcknowledgeConfig(I2C2,DISABLE);			//在接收最后一个字节之前提前将应答失能
	I2C_GenerateSTOP(I2C2,ENABLE);			//终止条件
	
	MPU6050_WaitEvent(I2C2,I2C_EVENT_MASTER_BYTE_RECEIVED);		//等待EV7
	Data = I2C_ReceiveData(I2C2);			//读取数据
	
	I2C_AcknowledgeConfig(I2C2,ENABLE);			//为了不影响后续应答,再打开应答
	return Data;
}

//Mpu6050初始化
void Mpu6050_Init(void)
{
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_I2C2,ENABLE);
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE);
	
	GPIO_InitTypeDef GPIO_InitStructure;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_OD;
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10 | GPIO_Pin_11;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOB,&GPIO_InitStructure);
	
	I2C_InitTypeDef I2C_InitStructure;
	I2C_InitStructure.I2C_Ack = I2C_Ack_Enable;		//应答打开
	I2C_InitStructure.I2C_AcknowledgedAddress = I2C_AcknowledgedAddress_7bit;		//地址位7位
	I2C_InitStructure.I2C_ClockSpeed = 50000;			//时钟速度
	I2C_InitStructure.I2C_DutyCycle = I2C_DutyCycle_2;			//时钟占空比
	I2C_InitStructure.I2C_Mode = I2C_Mode_I2C;			//模式
	I2C_InitStructure.I2C_OwnAddress1 = 0x00;			//自身地址		从机模式下才有效
	I2C_Init(I2C2,&I2C_InitStructure);
	
	I2C_Cmd(I2C2,ENABLE);
	
	//寄存器初始化
	Mpu6050_WriteReg(MPU6050_PWR_MGMT_1, 0x01);		//电源管理寄存器1,取消睡眠模式,选择时钟源为X轴陀螺仪
	Mpu6050_WriteReg(MPU6050_PWR_MGMT_2, 0x00);		//电源管理寄存器2,保持默认值0,所有轴均不待机
	Mpu6050_WriteReg(MPU6050_SMPLRT_DIV, 0x09);		//采样率分频寄存器,配置采样率
	Mpu6050_WriteReg(MPU6050_CONFIG, 0x06);			//配置寄存器,配置DLPF
	Mpu6050_WriteReg(MPU6050_GYRO_CONFIG, 0x18);	//陀螺仪配置寄存器,选择满量程为±2000°/s
	Mpu6050_WriteReg(MPU6050_ACCEL_CONFIG, 0x18);	//加速度计配置寄存器,选择满量程为±16g
}

//获取ID号
uint8_t Mpu6050_GetID(void)
{
	return Mpu6050_ReadReg(MPU6050_WHO_AM_I);		//返回WHO_AM_I寄存器的值
}

struct Mpu6050_AG
{
	int16_t AccX;		//加速度
	int16_t AccY;
	int16_t AccZ;
	int16_t GyroX;		//角速度
	int16_t GyroY;
	int16_t GyroZ;
};


/**
  * 函    数:MPU6050获取数据
  * 参    数:AccX AccY AccZ 加速度计X、Y、Z轴的数据,使用输出参数的形式返回,范围:-32768~32767
  * 参    数:GyroX GyroY GyroZ 陀螺仪X、Y、Z轴的数据,使用输出参数的形式返回,范围:-32768~32767
  * 返 回 值:无
  */
struct Mpu6050_AG Mpu6050_GetData(void)
{
	uint8_t DataH, DataL;								//定义数据高8位和低8位的变量
	struct Mpu6050_AG AG;
	
	DataH = Mpu6050_ReadReg(MPU6050_ACCEL_XOUT_H);		//读取加速度计X轴的高8位数据
	DataL = Mpu6050_ReadReg(MPU6050_ACCEL_XOUT_L);		//读取加速度计X轴的低8位数据
	AG.AccX = (DataH << 8) | DataL;						//数据拼接,通过输出参数返回
	
	DataH = Mpu6050_ReadReg(MPU6050_ACCEL_YOUT_H);		//读取加速度计Y轴的高8位数据
	DataL = Mpu6050_ReadReg(MPU6050_ACCEL_YOUT_L);		//读取加速度计Y轴的低8位数据
	AG.AccY = (DataH << 8) | DataL;						//数据拼接,通过输出参数返回
	
	DataH = Mpu6050_ReadReg(MPU6050_ACCEL_ZOUT_H);		//读取加速度计Z轴的高8位数据
	DataL = Mpu6050_ReadReg(MPU6050_ACCEL_ZOUT_L);		//读取加速度计Z轴的低8位数据
	AG.AccZ= (DataH << 8) | DataL;						//数据拼接,通过输出参数返回
	
	DataH = Mpu6050_ReadReg(MPU6050_GYRO_XOUT_H);		//读取陀螺仪X轴的高8位数据
	DataL = Mpu6050_ReadReg(MPU6050_GYRO_XOUT_L);		//读取陀螺仪X轴的低8位数据
	AG.GyroX = (DataH << 8) | DataL;						//数据拼接,通过输出参数返回
	
	DataH = Mpu6050_ReadReg(MPU6050_GYRO_YOUT_H);		//读取陀螺仪Y轴的高8位数据
	DataL = Mpu6050_ReadReg(MPU6050_GYRO_YOUT_L);		//读取陀螺仪Y轴的低8位数据
	AG.GyroY = (DataH << 8) | DataL;						//数据拼接,通过输出参数返回
	
	DataH = Mpu6050_ReadReg(MPU6050_GYRO_ZOUT_H);		//读取陀螺仪Z轴的高8位数据
	DataL = Mpu6050_ReadReg(MPU6050_GYRO_ZOUT_L);		//读取陀螺仪Z轴的低8位数据
	AG.GyroZ = (DataH << 8) | DataL;						//数据拼接,通过输出参数返回
	
	return AG;
}


  • 22
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值