基本的任务是:通过通信线,实现单片机读写外挂模块寄存器的功能。其中至少要实现在指定位置写寄存器和在指定的位置读寄存器这两个功能。
异步时序的优点:省一根时钟线,节约资源;缺点:对事件要求严格,对硬件电路依赖严重
同步时序反过来。
1 I2C通信
I2C(Inter IC Bus)是由Philips公司开发的一种通用数据总线
两根通信线:SCL(Serial Clock)、SDA(Serial Data)
同步,半双工
带数据应答
支持总线挂载多设备(一主多从、多主多从)
一主多从:一个单片机作为主机,挂载一个或者多个模块作为从机。
多主多从:多个主机,多个从机(但是同一时刻只能有一个主机控制)
1.1 硬件电路
所有I2C设备的SCL连在一起,SDA连在一起
设备的SCL和SDA均要配置成开漏输出模式
SCL和SDA各添加一个上拉电阻,阻值一般为4.7KΩ左右
左边的CPU就是单片机,作为总线的主机,主机的权利很大,包括对SCL线的完全控制,任何时候都是主机完全掌控SCL线。空闲状态下,主机可以主动发起对SDA的控制,只有在从机发送数据和从机应答时候,主机才会转交SDA的控制权给从机。下面都是被控制IC,也就是挂载在I2C上总线上的从机,这些从机可以是姿态传感器、OELD、存储器、时钟模块等。从机的权利比较小,对于SCL时钟线,在任何时刻都只能被动的读取,从机不允许控制SCL线。对于SDA数据线,从机不允许主动发起对SDA的控制,只有在主机发送读取从机的命令后或者从机应答的时候,从机才能短暂的取得SDA的控制权,这就是一主多从模型。
主机的SCL是输出,没问题,主机和从机的SDA在输入和输出之间变化,
左边是SCL的结构,右边是SDA的结构。
首先引脚的信号进来,都可以通过一个数据缓冲器或者施密特触发器,进行输入,因为输入对电路没有任何影响。在输出这部分使用的是开漏输出的配置(输出低电平,开关管导通,引脚直接接地,是强下拉;输出高电平,这个开关管断开,引脚什么都不接,处于浮空状态,所有的设备只能输出低电平而不能输出高电平,为了避免高电平造成的引脚浮空,这时需要在总线外面SCL和SDA各外置一个上拉电阻,弱上拉)
好处:
(1)完全杜绝了电源短路现象,保证了电路的安全;
(2)避免了引脚模式的频繁切换,开漏加弱上拉的模式,同时兼具了输入和输出的功能。开漏模式下,输出高电平就相当断开引脚,所以在输入之前,可以直接输出高电平,不用切换输入模式了;
(3)这个模式会有一个“线与”的现象,就是只要有任意一个或多个设备输出了低电平,总线就处于低电平。利用这个特性执行多主机模式下的时钟同步和总线仲裁。
1.2 I2C时序基本单元
(1)起始条件:SCL高电平期间,SDA从高电平切换到低电平
(2)终止条件:SCL高电平期间,SDA从低电平切换到高电平
在I2C处于空闲时,SCL和SDA都处于高电平;SCL高电平期间,SDA从高电平切换到低电平,之后主机要再把SCL拽下来,一方面是占用这个总线,另一方面也是为了这些基本单元的拼接。(低电平开始,低电平结束)
终止条件:SCL先回弹到高电平,之后,SDA再回弹至高电平,这个上升沿触发终止条件。
起始和终止都是由主机产生的。所以在总线空闲状态时,从机必须双手放开,不允许主动跳出来碰总线(允许的话是多主机模型了)
(3)发送一个字节:SCL低电平期间,主机将数据位依次放到SDA线上(高位先行),然后释放SCL(SCL成高电平),从机将在SCL高电平期间读取数据位,所以SCL高电平期间SDA不允许有数据变化,依次循环上述过程8次,即可发送一个字节
起始开始后,第一个字节也必须是主机发送的,最开始SCL是低电平,如果主机想发送0,就拉低SDA到低电平;如果想发送1,就放手,SDA回弹至高电平。在SCL低电平期间,允许改变SDA的电平。当这一位放好后,主机松手时钟线,SCL回弹到高电平,在高电平期间是从机读取SDA的时候,所以高电平期间,SDA不允许变化;SCL处于高电平之后,从机需要尽快的读取SDA,一般是在上升沿这个时刻,从机就已经读取完成了。主机在放手SCL一段时间后,就可以继续拉低SCL了,传输下一位,主机也需要在SCL下降沿之后尽快把数据放到SDA上;数据放完之后,主机再松手SCL,SCL到达高电平,从机读,以此类推。主机拉低SCL,把数据放在SDA上,主机松开SCL,从机读取SDA上的数据,在SCL的同步下依次进行主机的发送和从机的接收。高位先行,所以第一位是第一个字节的最高位B7。SCL和SDA全程由主机掌控,从机只能被动读取。
(4)接收一个字节:SCL低电平期间,从机将数据位依次放到SDA线上(高位先行),然后释放SCL,主机将在SCL高电平期间读取数据位,所以SCL高电平期间SDA不允许有数据变化,依次循环上述过程8次,即可接收一个字节(主机在接收之前,需要释放SDA)
释放SDA就相当于切换成输入模式,所有设备包括主机都处于输入模式,当主机需要发送的时候,就可以主动拉低SDA,而主机在被动接收的时候,就必须先释放SDA(总线是线与的特征)。
接收一个字节和发送一个字节非常相似。
区别是:发送一个字节是低电平主机放数据,高电平从机读数据;
而接收一个字节是低电平从机放数据,高电平主机读数据,
主机在接收数据之前要先释放SDA线,然后这是从机取得SDA的控制权,从机需要发送0,就把SDA拉低;从机需要发送1,就放手SDA线,SDA回弹至高电平。然后同样的,低电平变换数据,高电平读取数据。实线表示主机控制的电平,虚线表示从机控制的电平。SCL全程由主机控制,SDA主机在接收前要释放,交由从机控制,从机的数据变化都是贴着SCL下降沿进行的,而主机可以在SCL任意高电平时刻读取数据,这就是接收一个字节的时序。
(5)发送应答(发送一位):主机在接收完一个字节之后,在下一个时钟发送一位数据,数据0表示应答,数据1表示非应答。
在接收一个字节后,需要给从机一个应答位,发送应答位的目的是告诉从机,是不会还要继续发,如果从机发送一个数据后,得到了主机的应答,那从机就会继续发送;如果从机没有得到主机的应答,那从机就会认为,自己发送了一个数据,主机不理,可能主机不想要了,这时从机就会释放SDA,交出SDA的控制权,防止干扰主机的操作。
(6)接收应答(接收一位):主机在发送完一个字节之后,在下一个时钟接收一位数据,判断从机是否应答,数据0表示应答,数据1表示非应答(主机在接收之前,需要释放SDA)
在调用发送一个字节之后,就要紧跟着调用接收应答的时序,用来判断从机有没有收到刚才给它的数据。如果从机收到了,那在应答位这里,主机释放SDA的时候,从机就应该立刻把SDA拉下来,然后在SCL高电平期间,主动读取应答;如果应答位为0,就说明从机确实收到了。这个场景就是主机刚发送一个字节,问有没有人收到,现在把SDA放手了,如果有人收到的话,就把SDA拽下来,然后主机高电平读取数据,发现确实有人拽下来了,说明有人收到了数据;如果主机发送松手后,SDA跟着回弹到高电平,说明没人回应,没人收到或者收到没回应。
1.3 I2C时序
从机有唯一的设备地址。(7位地址)MPU6050: 1101 000
(1)指定地址写
对于指定设备(Slave Address),在指定地址(Reg Address)下(设备内部地址,寄存器),写入指定数据(Data)
SAD和SDL都处于高电平,开始的时候,拉低SDA,产生起始条件,在起始条件之后,紧跟着的时序必须是发送一个字节的时序,字节的内容必须是从机地址+读写位,从机地址7位,读写位1位, 加起来是一个字节8位,发送从机地址就是确定通信对象,发送读写位就是确定接下来是读出还是写入。具体:低电平期间,SDA变换数据,高电平期间,从机读取SDA。绿色的竖线表示从机读的数据。主机寻找的地址是1101 000(MPU6050的地址),后面的0表示之后的时序主机要进行写入操作,1表示之后的时序要进行读出操作。目前主机是发送一个字节,字节的内容转换成16进制,高位先行,就是0xD0,然后根据协议规定,紧跟着的就是接收从机的应答位(Receive ACK,RA),在这个时刻,主机要释放SDA。
如果单看主机的波形,释放SDA之后,引脚电平回弹到高电平(黄线),但是根据协议规定,从机要在这个时候拉低SDA(绿线),综合两者的波形,在主机释放SDA之后,由于SDA也被从机拽住了,所以主机松手后,SDA并没有回弹高电平,这个过程就表示从机产生了应答,最终高电平期间,主机读SDA,发现是0,就说明进行寻址时,有人给我应答了,传输没问题;如果主机读取SDA发现是1,就说明寻址在应答期间,我松手了,但是没人拽住它,没人给我应答,就直接产生停止条件。后面的上升沿就是应答结束后,从机释放SDA产生的,从机交出SDA的控制权,因为从机要在低电平期间尽快变换数据,所以这个上升沿和SCL的下降沿几乎是同时发生的。继续往后,读写位给了0,所以应答结束后,要继续发送一个字节,同样的时序,再来一遍,第二个字节就可以送到指定设备的内部了,从机设备可以自己定义第二个字节和后续字节的用途,一般第二个字节可以是寄存器地址或者指令控制字等。这里是0x19就表示要操作0x19地址下的寄存器了。
接着同样是从机应答,主机释放SDA,从机拽住SDA,SDA表现为低电平,主机接收到应答位0,表示收到了从机的应答。同样的流程再来一遍,主机再发送一个字节,这里表示要在0x19地址下写入0xAA,最后是接收应答位。如果主机不需要再继续传输了,就可以产生停止条件,在停止条件之前,先拉低SDA,为后续的SDA上升沿做准备,然后释放SCL,再释放SDA,这样就产生了SCL高电平期间,SDA的上升沿。
总结:这个数据帧的目的就是对于指定从机地址为1101 0000的设备,在其内部0x19的寄存器中写入0xAA这个数据。
(2)当前地址读
对于指定设备(Slave Address),在当前地址指针指示的地址下,读取从机数据(Data)
如果主机想要读取从机的数据,就可以执行这个时序。最开始还是SCL高电平期间,拉低SDA,产生起始条件,起始条件开始后,主机必须首先调用发送一个字节,来进行从机的寻址和指定读写标志位,图中表示本次寻址的目标是1101 000的设备,最后一位读写标志位为1,表示主机读取数据,紧跟着,发送一个字节之后,接收一下从机的应答,从机应答0,表示从机接收到了第一个字节,在从机应答之后,在这里开始,数据的传输方向就要反过来了。
主机刚才发出了读的指令,所以在这之后,主机就不能继续发送了,要把SDA的控制权给从机,主机调用接收一个字节的时序,进行接收操作,之后,从机得到主机的允许,可以在SCL低电平期间写入SDA,然后主机在SCL高电平期间读取SDA,最终,主机在SCL高电平期间依次读取8位,就接收到从机发送的一个字节的数据,0000 1111,也就是0x0F,这里没有指定地址环节,这里需要用到当前地址指针了,在从机中,所有寄存器被分配到一个线性区域中,并且会有一个单独的指针变量,指示着其中一个寄存器,一般默认为0地址,每写入和读出一个字节后,这个指针就会自动自增一次,主机没有指定地址的话,从机就会返回当前指针指向的寄存器的值。
(3)指定地址读
对于指定设备(Slave Address),在指定地址(Reg Address)下,读取从机数据(Data)
前面一部分是指定地址的时序,把后面写数据的部分去掉,然后把前面这一部分设置地址,还没有指定写什么数据的时序,追加到当前地址读的时序前,就得到了指定地址读的时序(复合格式)
前面部分是指定地址写,只指定了地址,还没来得及写;后面的部分是当前地址读,加起来就是指定地址读了。
前面依然是启动条件,然后发送一个字节进行寻址,指定从机地址是1101 000,读写标标志位是0,表示进行写操作,经过从机应答后,再发送一个字节,第二个字节用来指定地址,这个数据就写入到从机的地址指针里了,也就是说从机接收到这个数据之后,它的寄存器指针就指向0x19这个位置了,之后要写入的数据,不给它发,直接再来个起始条件(start repeat),然后重新寻址并且指定读的标志位,此时读写标志位是1,表示要开始读了,接着主机接收一个字节,这个字节就是0x19地址下的数据,
进阶版本就是读写多个字节。
1.4 MPU6050简介
MPU6050是一个6轴姿态传感器,可以测量芯片自身X、Y、Z轴的加速度、角速度参数,通过数据融合,可进一步得到姿态角,常应用于平衡车、飞行器等需要检测自身姿态的场景
3轴加速度计(Accelerometer):测量X、Y、Z轴的加速度。具有静态稳定性,不具有动态稳定性
3轴陀螺仪传感器(Gyroscope):测量X、Y、Z轴的角速度。具有动态稳定性,不具有静态稳定性
6轴 = 3轴加速度 + 3轴角速度
9轴 = 3轴加速度 + 3轴角速度 + 3轴磁场强度
10轴 = 3轴加速度 + 3轴角速度 + 3轴磁场强度 + 1气压强度
1.4.1 MPU6050参数
16位ADC采集传感器的模拟信号,量化范围:-32768~32767
加速度计满量程选择:±2、±4、±8、±16(g)
陀螺仪满量程选择: ±250、±500、±1000、±2000(°/sec)
可配置的数字低通滤波器
可配置的时钟源
可配置的采样分频
I2C从机地址:110 1000(AD0=0)(0x68需要左移,融入读写位:0xD0写地址,0xD1读地址)
110 1001(AD0=1)
1.4.2 硬件电路
左边是MPU6050的芯片,左下角是8针的排阵;左上角是LDO低压差线性稳压器。
XCL和XDA是扩展用的;
AD0接低电平,7位从机地址就是1101 000;接高电平的话,7位从机地址就是1101 001。(弱下拉)
这个MPU6050芯片的MDD供电是2.375-3.46V,加了稳压器,就可以在3.3~5V
引脚 | 功能 |
VCC、GND | 电源 |
SCL、SDA | I2C通信引脚 |
XCL、XDA | 主机I2C通信引脚 |
AD0 | 从机地址最低位 |
INT | 中断信号输出 |
1.4.3 MPU6050框图
左上角是时钟系统,有时钟输入脚和时钟输出脚,一般使用内部时钟。灰色的是传感器。
自测:自测响应。
手册
产品说明书
寄存器映像
采样频率分频器、配置寄存器、陀螺仪配置寄存器、加速度计配置寄存器
分别是:加速度计XYZ轴、温度传感器、陀螺仪XYZ轴的数据。_L表示低8位;_H表示高8位
分别是:电源管理寄存器1/2、器件ID号
采样频率分频器:分频越小,内部AD的转换就越快,数据寄存器刷新就越快,反之越慢。
配置寄存器
陀螺仪配置寄存器:高3位是XYZ自测使能位,中间两位是满量程位
自测响应范围
加速度计配置寄存器:高3位是XYZ自测使能位,中间两位是满量程位,后三位配置高通滤波器
加速度计的数据寄存器,直接读取数据寄存器即可,读出高8位和低8位,高8位左移8位或上低8位即可。
器件ID号
这个芯片上电默认是睡眠模式。
2 软件I2C读写MPU6050
完成软件I2C协议的时序和基于I2C协议读写寄存器来控制MPU6050。模块内部自带上拉电阻。
2.1 接线图
目前STM32是主机,MPU6050是从机。AD0修改从机地址的最低位(程序中调试)
框架:首先建立I2C通信层的.c和.h模块,在通信层,写好I2C底层的GPIO初始化和6个时序基本单元(起始,终止,发送一个字节,接收一个字节,发送应答,接收应答);再建立MPU6050的.c和.h模块,在这一层,基于I2C通信的模块,来实现指定地址的读和指定地址写,再实现写寄存器对芯片进行配置,读寄存器得到传感器数据。最后在main.c中调用MPU6050模块,初始化拿到数据,显示数据。
2.2 模块封装
2.2.1 MyI2C
(1)I2C初始化函数
// I2C初始化
void MyI2C_Init(void)
{// 软件I2C只需要用GPIO的读写就可以了,不看库函数// 1把SCL和SDA初始化成开漏输出模式// 2把SCL和SDA置高电平/*开启时钟*/RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE); // 开启GPIOA的时钟/*GPIO初始化*/GPIO_InitTypeDef GPIO_InitStructure;GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_OD; // 将PA1和PA2引脚初始化为开漏输出(输出低电平+浮空输入)GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10 | GPIO_Pin_11;GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;GPIO_Init(GPIOB, &GPIO_InitStructure); /*设置GPIO初始化后的默认电平*/GPIO_SetBits(GPIOB, GPIO_Pin_10 | GPIO_Pin_11); // 设置PA1和PA2引脚为高电平
}
(2)封装SCL和SD读写的函数
// 封装SCL的写函数
void MyI2C_W_SCL(uint8_t bitValue)
{GPIO_WriteBit(GPIOB, GPIO_Pin_10, (BitAction)bitValue);Delay_us(10);
}// 封装SDA的写函数
void MyI2C_W_SDA(uint8_t bitValue)
{GPIO_WriteBit(GPIOB, GPIO_Pin_11, (BitAction)bitValue);Delay_us(10);
}// 封装SDA的读函数
uint8_t MyI2C_R_SDA(void)
{uint8_t bitValue;bitValue = GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_11);Delay_us(10);return bitValue;
}
(3)起始条件
起始条件:SCL高电平期间,SDA从高电平切换到低电平。在I2C处于空闲时,SCL和SDA都处于高电平;SCL高电平期间,SDA从高电平切换到低电平,之后主机要再把SCL拽下来,一方面是占用这个总线,另一方面也是为了这些基本单元的拼接。(低电平开始,低电平结束)
// 起始条件
void MyI2C_Start(void)
{// 确保SCL和SDA都释放,先拉低SDA,再拉低SCLMyI2C_W_SDA(1); // 释放SDA(在前)MyI2C_W_SCL(1); // 释放SCLMyI2C_W_SDA(0); // 先拉低SDAMyI2C_W_SCL(0); // 再拉低SCL
}
(4)终止条件
终止条件:SCL高电平期间,SDA从低电平切换到高电平。终止条件:SCL先回弹到高电平,之后,SDA再回弹至高电平,这个上升沿触发终止条件。
// 终止条件
void MyI2C_Stop(void)
{// 先拉低SDA,再释放SCL,再释放SDAMyI2C_W_SDA(0); // 先拉低SDAMyI2C_W_SCL(1); // 释放SCLMyI2C_W_SDA(1); // 释放SDA
}
(5)发送一个字节
主机拉低SCL,把数据放在SDA上,主机松开SCL,从机读取SDA上的数据,在SCL的同步下依次进行主机的发送和从机的接收。高位先行,所以第一位是第一个字节的最高位B7。SCL和SDA全程由主机掌控,从机只能被动读取。
// 发送一个字节
void MyI2C_SendByte(uint8_t byte)
{// 发送一个字节开始时,SCL是低电平// 趁SCL是低电平,先把byte的最高位放在SDA线上
// MyI2C_W_SDA(byte & 0x80); // 取决byte的最高位
// MyI2C_W_SCL(1); // 释放SCL
// MyI2C_W_SCL(0); // 拉低SCL
//
// MyI2C_W_SDA(byte & 0x40); // 取决byte的次高位
// MyI2C_W_SCL(1); // 释放SCL
// MyI2C_W_SCL(0); // 拉低SCLuint8_t i = 0;for (i = 0; i < 8; i++){MyI2C_W_SDA(byte & (0x80 >> i)); // 取决byte的i高位MyI2C_W_SCL(1); // 释放SCLMyI2C_W_SCL(0); // 拉低SCL}
}
(6)接收一个字节
SCL低电平期间,从机将数据位依次放到SDA线上(高位先行),然后释放SCL,主机将在SCL高电平期间读取数据位,所以SCL高电平期间SDA不允许有数据变化,依次循环上述过程8次,即可接收一个字节(主机在接收之前,需要释放SDA)
// 接收一个字节
uint8_t MyI2C_ReceiveByte(void)
{uint8_t byte = 0x00,i = 0;// SCL低电平,SDA写数据,SCL高电平,读SDA的数据MyI2C_W_SDA(1); // 释放SDA
// MyI2C_W_SCL(1); // 释放SCL
// // 读数据
// if (MyI2C_R_SDA() == 1)
// {
// byte |= 0x80; // 把最高位置1
// }
// MyI2C_W_SCL(0); // 拉低SCLfor (i = 0; i < 8; i++){MyI2C_W_SCL(1); // 释放SCL// 读数据if (MyI2C_R_SDA() == 1){byte |= (0x80 >> i); // 把高位置1}MyI2C_W_SCL(0); // 拉低SCL}return byte;
}
(7)发送应答(发送一个字节的简化版)
// 发送应答(发送一个字节的简化版)
void MyI2C_SendAck(uint8_t ackByte)
{MyI2C_W_SDA(ackByte); // 取决ackByteMyI2C_W_SCL(1); // 释放SCLMyI2C_W_SCL(0); // 拉低SCL
}
(8)接收应答(接收一个字节的简化版)
// 接收应答(接收一个字节的简化版)
uint8_t MyI2C_ReceiveAck(void)
{uint8_t ackByte;MyI2C_W_SDA(1); // 释放SDAMyI2C_W_SCL(1); // 释放SCLackByte = MyI2C_R_SDA(); // 读数据MyI2C_W_SCL(0); // 拉低SCLreturn ackByte;
}
最终的MyI2C.c
#include "stm32f10x.h" // Device header
#include "Delay.h"// 封装SCL的写函数
void MyI2C_W_SCL(uint8_t bitValue)
{GPIO_WriteBit(GPIOB, GPIO_Pin_10, (BitAction)bitValue);Delay_us(10);
}// 封装SDA的写函数
void MyI2C_W_SDA(uint8_t bitValue)
{GPIO_WriteBit(GPIOB, GPIO_Pin_11, (BitAction)bitValue);Delay_us(10);
}// 封装SDA的读函数
uint8_t MyI2C_R_SDA(void)
{uint8_t bitValue;bitValue = GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_11);Delay_us(10);return bitValue;
}// I2C初始化
void MyI2C_Init(void)
{// 软件I2C只需要用GPIO的读写就可以了,不看库函数// 1把SCL和SDA初始化成开漏输出模式// 2把SCL和SDA置高电平/*开启时钟*/RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE); // 开启GPIOA的时钟/*GPIO初始化*/GPIO_InitTypeDef GPIO_InitStructure;GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_OD; // 将PA1和PA2引脚初始化为开漏输出(输出低电平+浮空输入)GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10 | GPIO_Pin_11;GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;GPIO_Init(GPIOB, &GPIO_InitStructure); /*设置GPIO初始化后的默认电平*/GPIO_SetBits(GPIOB, GPIO_Pin_10 | GPIO_Pin_11); // 设置PA1和PA2引脚为高电平
}// 起始条件
void MyI2C_Start(void)
{// 确保SCL和SDA都释放,先拉低SDA,再拉低SCLMyI2C_W_SDA(1); // 释放SDA(在前)MyI2C_W_SCL(1); // 释放SCLMyI2C_W_SDA(0); // 先拉低SDAMyI2C_W_SCL(0); // 再拉低SCL
}// 终止条件
void MyI2C_Stop(void)
{// 先拉低SDA,再释放SCL,再释放SDAMyI2C_W_SDA(0); // 先拉低SDAMyI2C_W_SCL(1); // 释放SCLMyI2C_W_SDA(1); // 释放SDA
}// 发送一个字节
void MyI2C_SendByte(uint8_t byte)
{// 发送一个字节开始时,SCL是低电平// 趁SCL是低电平,先把byte的最高位放在SDA线上
// MyI2C_W_SDA(byte & 0x80); // 取决byte的最高位
// MyI2C_W_SCL(1); // 释放SCL
// MyI2C_W_SCL(0); // 拉低SCL
//
// MyI2C_W_SDA(byte & 0x40); // 取决byte的次高位
// MyI2C_W_SCL(1); // 释放SCL
// MyI2C_W_SCL(0); // 拉低SCLuint8_t i = 0;for (i = 0; i < 8; i++){MyI2C_W_SDA(byte & (0x80 >> i)); // 取决byte的i高位MyI2C_W_SCL(1); // 释放SCLMyI2C_W_SCL(0); // 拉低SCL}
}// 接收一个字节
uint8_t MyI2C_ReceiveByte(void)
{uint8_t byte = 0x00,i = 0;// SCL低电平,SDA写数据,SCL高电平,读SDA的数据MyI2C_W_SDA(1); // 释放SDA
// MyI2C_W_SCL(1); // 释放SCL
// // 读数据
// if (MyI2C_R_SDA() == 1)
// {
// byte |= 0x80; // 把最高位置1
// }
// MyI2C_W_SCL(0); // 拉低SCLfor (i = 0; i < 8; i++){MyI2C_W_SCL(1); // 释放SCL// 读数据if (MyI2C_R_SDA() == 1){byte |= (0x80 >> i); // 把高位置1}MyI2C_W_SCL(0); // 拉低SCL}return byte;
}// 发送应答(发送一个字节的简化版)
void MyI2C_SendAck(uint8_t ackByte)
{MyI2C_W_SDA(ackByte); // 取决ackByteMyI2C_W_SCL(1); // 释放SCLMyI2C_W_SCL(0); // 拉低SCL
}// 接收应答(接收一个字节的简化版)
uint8_t MyI2C_ReceiveAck(void)
{uint8_t ackByte;MyI2C_W_SDA(1); // 释放SDAMyI2C_W_SCL(1); // 释放SCLackByte = MyI2C_R_SDA(); // 读数据MyI2C_W_SCL(0); // 拉低SCLreturn ackByte;
}
测试从机是否给应答
#include "stm32f10x.h" // Device header
#include "Delay.h"
#include "OLED.h"
#include "MyI2C.h"int main()
{OLED_Init(); // 初始化OLED// 测试从机是否给应答MyI2C_Init();// 指定地址写MyI2C_Start(); // 起始条件// 测试总线上是否有从机
// MyI2C_SendByte(0xD0); // 1101 000 0 寻址
// // MyI2C_SendByte(0xA1); // 不存在,因此发送应答是001
// uint8_t ack = MyI2C_ReceiveAck(); // 000/001
// OLED_ShowHexNum(1, 1, ack, 3);// 测试AD0引脚改名// 飞线AD0接到VCC(1101 0010)上,0xD0就不应答了(001),MyI2C_SendByte(0xD0);uint8_t ack = MyI2C_ReceiveAck();OLED_ShowHexNum(1, 1, ack, 3);while (1){}
}
2.2.2 MPU6050
这个模块建立在MyI2C之上的
MPU6050.c
#include "stm32f10x.h" // Device header
#include "MyI2C.h"
#include "MPU6050_Reg.h"#define MPU6050_ADDRESS 0xD0// 指定地址写
// 参数是8位地址和8位数据
void MPU6050_WriteReg(uint8_t regAddress, uint8_t data)
{MyI2C_Start(); // i2c起始MyI2C_SendByte(MPU6050_ADDRESS); // 发送MPU6050的地址MyI2C_ReceiveAck(); // 接收应答MyI2C_SendByte(regAddress); // 发送指定寄存器地址MyI2C_ReceiveAck(); // 接收应答MyI2C_SendByte(data); // 指定写入寄存器的数据 MyI2C_ReceiveAck(); // 接收应答MyI2C_Stop(); // 终止
}// 指定地址读
uint8_t MPU6050_ReadReg(uint8_t regAddress)
{uint8_t data;MyI2C_Start(); // i2c起始MyI2C_SendByte(MPU6050_ADDRESS); // 发送MPU6050的地址MyI2C_ReceiveAck(); // 接收应答MyI2C_SendByte(regAddress); // 发送指定寄存器地址MyI2C_ReceiveAck(); // 接收应答MyI2C_Start(); // i2c起始MyI2C_SendByte(MPU6050_ADDRESS | 0x01); // 读数据 1101 0000写/1101 0001读MyI2C_ReceiveAck(); // 接收应答data = MyI2C_ReceiveByte(); // 总线控制权给从机,从机开始发送一个字节MyI2C_SendAck(0); // 发送应答MyI2C_Stop(); // 终止return data;
}// 初始化MPU6050
void MPU6050_Init(void)
{MyI2C_Init(); // 初始化I2C
}
先小小的测试一下
#include "stm32f10x.h" // Device header
#include "Delay.h"
#include "OLED.h"
#include "MPU6050.h"int main()
{OLED_Init(); // 初始化OLEDMPU6050_Init();// 读取芯片的ID号
// uint8_t id = MPU6050_ReadReg(0x75); // 地址是0x75
// OLED_ShowHexNum(1, 1, id, 2); // ID号0x68// 写寄存器需要解除芯片的睡眠模式MPU6050_WriteReg(0x6B, 0x00); // 电源管理寄存器1MPU6050_WriteReg(0x19, 0xAA); // 采样率分频寄存器,地址是0x19,值是0XAAuint8_t id = MPU6050_ReadReg(0x19);OLED_ShowHexNum(2, 1, id, 2); // 0xAAwhile (1){}
}
宏定义寄存器地址
#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
继续完善MPU6050.c
#include "stm32f10x.h" // Device header
#include "MyI2C.h"
#include "MPU6050_Reg.h"#define MPU6050_ADDRESS 0xD0// 指定地址写
// 参数是8位地址和8位数据
void MPU6050_WriteReg(uint8_t regAddress, uint8_t data)
{MyI2C_Start(); // i2c起始MyI2C_SendByte(MPU6050_ADDRESS); // 发送MPU6050的地址MyI2C_ReceiveAck(); // 接收应答MyI2C_SendByte(regAddress); // 发送指定寄存器地址MyI2C_ReceiveAck(); // 接收应答MyI2C_SendByte(data); // 指定写入寄存器的数据 MyI2C_ReceiveAck(); // 接收应答MyI2C_Stop(); // 终止
}// 指定地址读
uint8_t MPU6050_ReadReg(uint8_t regAddress)
{uint8_t data;MyI2C_Start(); // i2c起始MyI2C_SendByte(MPU6050_ADDRESS); // 发送MPU6050的地址MyI2C_ReceiveAck(); // 接收应答MyI2C_SendByte(regAddress); // 发送指定寄存器地址MyI2C_ReceiveAck(); // 接收应答MyI2C_Start(); // i2c起始MyI2C_SendByte(MPU6050_ADDRESS | 0x01); // 读数据 1101 0000写/1101 0001读MyI2C_ReceiveAck(); // 接收应答data = MyI2C_ReceiveByte(); // 总线控制权给从机,从机开始发送一个字节MyI2C_SendAck(1); // 发送应答// 读取最后一个字节给非应答MyI2C_SendAck(0),之前都给应答MyI2C_Stop(); // 终止return data;
}// 初始化MPU6050
// 配置的是:解除睡眠,选择陀螺仪时钟,6个轴均不待机,采样分频位10,滤波参数最大
// 陀螺仪和加速度计都选择最大量程
void MPU6050_Init(void)
{MyI2C_Init(); // 初始化I2CMPU6050_WriteReg(MPU6050_PWR_MGMT_1, 0x01); // 配置电源管理寄存器1,结合数据手册写MPU6050_WriteReg(MPU6050_PWR_MGMT_2, 0x00); // 配置电源管理寄存器2,结合数据手册写MPU6050_WriteReg(MPU6050_SMPLRT_DIV, 0x09); // 采样率分频寄存器,10分频MPU6050_WriteReg(MPU6050_CONFIG, 0x06); // 配置寄存器MPU6050_WriteReg(MPU6050_GYRO_CONFIG, 0x18); // 陀螺仪配置寄存器MPU6050_WriteReg(MPU6050_ACCEL_CONFIG, 0x18); // 加速度计寄存器
}// 获取数据寄存器,返回6个数据,形参是指针
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); // 读取加速度寄存器X轴的高8位dataL = MPU6050_ReadReg(MPU6050_ACCEL_XOUT_L); // 读取加速度寄存器X轴的低8位*accX = (dataH << 8) | dataL; // 通过指针返回回去dataH = MPU6050_ReadReg(MPU6050_ACCEL_YOUT_H); // 读取加速度寄存器y轴的高8位dataL = MPU6050_ReadReg(MPU6050_ACCEL_YOUT_L); // 读取加速度寄存器y轴的低8位*accY = (dataH << 8) | dataL; // 通过指针返回回去dataH = MPU6050_ReadReg(MPU6050_ACCEL_ZOUT_H); // 读取加速度寄存器z轴的高8位dataL = MPU6050_ReadReg(MPU6050_ACCEL_ZOUT_L); // 读取加速度寄存器z轴的低8位*accZ = (dataH << 8) | dataL; // 通过指针返回回去dataH = MPU6050_ReadReg(MPU6050_GYRO_XOUT_H); // 读取陀螺仪寄存器x轴的高8位dataL = MPU6050_ReadReg(MPU6050_GYRO_XOUT_L); // 读取陀螺仪寄存器x轴的低8位*GyroX = (dataH << 8) | dataL; // 通过指针返回回去dataH = MPU6050_ReadReg(MPU6050_GYRO_YOUT_H); // 读取陀螺仪寄存器y轴的高8位dataL = MPU6050_ReadReg(MPU6050_GYRO_YOUT_L); // 读取陀螺仪寄存器y轴的低8位*GyroY = (dataH << 8) | dataL; // 通过指针返回回去dataH = MPU6050_ReadReg(MPU6050_GYRO_ZOUT_H); // 读取陀螺仪寄存器z轴的高8位dataL = MPU6050_ReadReg(MPU6050_GYRO_ZOUT_L); // 读取陀螺仪寄存器z轴的低8位*GyroZ = (dataH << 8) | dataL; // 通过指针返回回去
}// 获取ID号
uint8_t MPU6050_GetID(void)
{return MPU6050_ReadReg(MPU6050_WHO_AM_I);
}
2.3 主函数
#include "stm32f10x.h" // Device header
#include "Delay.h"
#include "OLED.h"
#include "MPU6050.h"int16_t AX, AY, AZ, GX, GY, GZ;int main()
{OLED_Init(); // 初始化OLEDMPU6050_Init();OLED_ShowString(1, 1, "ID:");OLED_ShowHexNum(1, 4, MPU6050_GetID(), 6);while (1){MPU6050_GetData(&AX, &AY, &AZ, &GX, &GY, &GZ);OLED_ShowSignedNum(2, 1, AX, 5);OLED_ShowSignedNum(3, 1, AY, 5);OLED_ShowSignedNum(4, 1, AZ, 5);OLED_ShowSignedNum(2, 8, GX, 5);OLED_ShowSignedNum(3, 8, GX, 5);OLED_ShowSignedNum(4, 8, GX, 5);}
}
现象:
读取的数据x / 32768 = x / 满量程
x = 1913/32768*16=0.93408203125g/m2。重力加速度。其他的数据也是如此计算。
3 I2C通信外设
简单应用使用软件I2C,对性能有要求,使用硬件I2C
3.1 I2C外设简介
STM32内部集成了硬件I2C收发电路,可以由硬件自动执行时钟生成、起始终止条件生成、应答位收发、数据收发等功能,减轻CPU的负担
支持多主机模型(STM32使用的是可变多主机,谁要做主机就跳出来,一视同仁)
支持7位/10位地址模式
7位地址的模式就是前文中的,起始条件之后,紧跟着的一个字节必须是7位地址+读写位。只有128位,数量少。
I2C起始的第一个字节必须是寻址+读写位。
10位地址:起始之后的前两个字节,都作为寻址。第一个字节有7个空位,第二个字节有8个空位,加起来15个空位,多出的5位做标志位了(11110)。如果第二个字节也是寻址,那第一个字节的前5位就必须是11110,此时第一个字节剩下的2位+第二个字节的8位都作为寻址,就是10位寻址了。
支持不同的通讯速度,标准速度(高达100 kHz),快速(高达400 kHz)
支持DMA
兼容SMBus协议(system management bus,系统管理总线,主要用于电源管理中)
STM32F103C8T6 硬件I2C资源:I2C1、I2C2
3.1.1 I2C外设框图
坐标是这个外设的通信引脚SDA和SCL,下面的SMBALERT是SMBus使用的。这个引脚一般都是借用GPIO的复用模式与外界相连的。
接着看I2C的功能框图
上面是数据控制部分,其中核心的是数据寄存器和数据移位寄存器。当需要发送数据的时候,可以把一个字节的数据写到数据寄存器DR中,当移位寄存器没有数据移位时,数据寄存器的值就会进一步转到移位寄存器里,在移位的过程中就可以直接把下一个数据放到数据寄存器里了,一旦前一个数据移位完成,下一个数据就可以无缝衔接,继续发送。当数据由数据寄存器转到移位寄存器的时候,就会置状态寄存器TXE位为1,表示发送寄存器位空,这是发送的流程。在接收的时候,也是这一路,输入的数据一位一位的从引脚移入到移位寄存器,当一个字节的数据收齐之后,数据就整体从移位寄存器转到数据寄存器,同时置标志位RXNE,表示接收寄存器非空,这时就可以把数据从数据寄存器读出来。(与串口不同,这里是半双工,串口是全双工)。需要写入控制寄存器的对应位来应对何时读何时写。
STM32作为从机的时候使用,被主机召唤,自身需要地址。
下面是SCL时钟控制。
3.1.2 I2C基本模型图
首先移位寄存器和数据寄存器的相互配合是通信的核心部分,I2C是高位先行,所以这个移位寄存器是向左移位。一个SCL时钟移位一次,移位8次就移一个字节。接收的时候,数据通过GPIO口从右边以此移进来,最终移8次,一个字节的接收就完成了。GPIO口需要配置成复用开漏输出模式(复用就是GPIO的状态交由片上外设来控制的,开漏输出就是I2C协议要求的端口配置模式)。
3.1.3 主机发送
当STM32想要执行指定地址写的时候,就按照这个图来进行。这里有7位地址的主发送和10位地址的主发送,区别是:7位地址起始条件之后的一个字节是寻址,10位地址起始条件后的两个字节是寻址(其中前一个字节这里写的是帧头,内容是5位的标志位11110+2位地址+1位读写位),后一个字节的内容就是纯粹的8位地址了,两个字节加一起构成了10位寻址。主要关注7位。
7位地址:流程是起始、从机地址、应答,后面是数据1,应答,数据2,应答,......,数据N,应答,终止。(MPU6050规定的是数据1是指定寄存器地址,数据2是指定寄存器地址下的数据)。
这是一个典型的指定地址写的时序流程。首先初始化之后,总线默认是空闲状态,STM32默认是从模式,为了产生一个起始条件,STM32要写入控制寄存器(查手册);
之后STM32由从模式转为主模式,也就是多主机模型下STM32有数据要发送,就要跳出来。控制玩硬件电路之后,检查标志位,看看硬件有没有达到想要的状态。把EV5事件当作标志位;
检测起始条件已经发送时,就可以发送一个字节的从机地址了,从机地址需要写到数据寄存器DR中,写入DR之后,硬件电路就会自动地把这一个字节转到移位寄存器里,再把这一个字节发送到IIC总线上,之后硬件会自动接收应答位并判断,如果没有应答,硬件就会置应答失败的标志位,这个标志位可以申请中断来提醒我们。在寻址完成之后,会发生EV6事件,EV8_1事件,
3.1.4 主机接收
当前地址读的格式
3.1.5 软件/硬件波形对比
4 硬件I2C读写MPU6050
4.1 接线图
和软件I2C读写MPU6050一样
4.2 模块封装
1配置I2C外设,对I2C2外设进行初始化
(1)开启I2C外设和对应GPIO的时钟
(2)把I2C外设对应的GPIO口初始化为复用开漏模式
(3)使用结构体对整个I2C进行配置
(4)I2C_Cmd,使能I2
2控制外设电路,实现指定地址写的时序
3控制外设电路,实现指定地址读的时序
库函数
void I2C_DeInit(I2C_TypeDef* I2Cx);
void I2C_Init(I2C_TypeDef* I2Cx, I2C_InitTypeDef* I2C_InitStruct);
void I2C_StructInit(I2C_InitTypeDef* I2C_InitStruct);
void I2C_Cmd(I2C_TypeDef* I2Cx, FunctionalState NewState);// I2C生成起始条件/生成终止条件
void I2C_GenerateSTART(I2C_TypeDef* I2Cx, FunctionalState NewState);
void I2C_GenerateSTOP(I2C_TypeDef* I2Cx, FunctionalState NewState);
// 配置在收到一个字节后,是否给从机应答
void I2C_AcknowledgeConfig(I2C_TypeDef* I2Cx, FunctionalState NewState);
// 发送数据,写数据到数据寄存器DR中
void I2C_SendData(I2C_TypeDef* I2Cx, uint8_t Data);
// 接收数据,读取DR寄存器的数据
uint8_t I2C_ReceiveData(I2C_TypeDef* I2Cx);
// 发送7位地址
void I2C_Send7bitAddress(I2C_TypeDef* I2Cx, uint8_t Address, uint8_t I2C_Direction);
// 状态监控
I2C_CheckEvent();
MPU6050.c
#include "stm32f10x.h" // Device header
#include "MPU6050_Reg.h"#define MPU6050_ADDRESS 0xD0// 封装I2C_CheckEvent函数,加上超时,防止卡死
void MPU6050_CheckEvent(I2C_TypeDef* I2Cx, uint32_t I2C_EVENT)
{uint32_t timeOut = 10000;while (I2C_CheckEvent(I2Cx, I2C_EVENT) != SUCCESS){timeOut--;if (timeOut == 0){break;}}
}// 指定地址写
// 参数是8位地址和8位数据
void MPU6050_WriteReg(uint8_t regAddress, uint8_t data)
{// 2控制外设电路,实现指定地址写的时序I2C_GenerateSTART(I2C2, ENABLE); // 生成起始条件// 等待EV5事件
// while (I2C_CheckEvent(I2C2, I2C_EVENT_MASTER_MODE_SELECT) != SUCCESS);MPU6050_CheckEvent(I2C2, I2C_EVENT_MASTER_MODE_SELECT);// 发送从机地址,自带接收应答,同样接收数据也自带发送应答I2C_Send7bitAddress(I2C2, MPU6050_ADDRESS, I2C_Direction_Transmitter);// 等待EV6事件MPU6050_CheckEvent(I2C2, I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED);I2C_SendData(I2C2, regAddress); // 直接写入DR,发送数据// 等待EV8事件MPU6050_CheckEvent(I2C2, I2C_EVENT_MASTER_BYTE_TRANSMITTING);I2C_SendData(I2C2, data); // 直接写入DR,发送数据// 连续发送过程等待EV8,最后一位等待EV8_2事件MPU6050_CheckEvent(I2C2, I2C_EVENT_MASTER_BYTE_TRANSMITTED);I2C_GenerateSTOP(I2C2, ENABLE); // 生成终止条件
}// 指定地址读
uint8_t MPU6050_ReadReg(uint8_t regAddress)
{// 3控制外设电路,实现指定地址读的时序I2C_GenerateSTART(I2C2, ENABLE); // 生成起始条件// 等待EV5事件MPU6050_CheckEvent(I2C2, I2C_EVENT_MASTER_MODE_SELECT);// 发送从机地址,自带接收应答,同样接收数据也自带发送应答I2C_Send7bitAddress(I2C2, MPU6050_ADDRESS, I2C_Direction_Transmitter);// 等待EV6事件MPU6050_CheckEvent(I2C2, I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED);I2C_SendData(I2C2, regAddress); // 直接写入DR,发送数据// 等待EV8事件MPU6050_CheckEvent(I2C2, I2C_EVENT_MASTER_BYTE_TRANSMITTED);I2C_GenerateSTART(I2C2, ENABLE); // 生成起始条件// 等待EV5事件MPU6050_CheckEvent(I2C2, I2C_EVENT_MASTER_MODE_SELECT);// 接收数据也自带发送应答I2C_Send7bitAddress(I2C2, MPU6050_ADDRESS, I2C_Direction_Receiver);// 等待EV6事件MPU6050_CheckEvent(I2C2, I2C_EVENT_MASTER_RECEIVER_MODE_SELECTED);I2C_AcknowledgeConfig(I2C2, DISABLE); // 配置ACK位I2C_GenerateSTOP(I2C2, ENABLE); // 生成终止条件// 等待EV7事件MPU6050_CheckEvent(I2C2, I2C_EVENT_MASTER_BYTE_RECEIVED);uint8_t data = I2C_ReceiveData(I2C2);I2C_AcknowledgeConfig(I2C2, ENABLE); // 配置ACK位return data;
}// 初始化MPU6050
// 配置的是:解除睡眠,选择陀螺仪时钟,6个轴均不待机,采样分频位10,滤波参数最大
// 陀螺仪和加速度计都选择最大量程
void MPU6050_Init(void)
{
// MyI2C_Init(); // 初始化I2C// 1配置I2C外设,对I2C2外设进行初始化// (1)开启I2C外设和对应GPIO的时钟RCC_APB1PeriphClockCmd(RCC_APB1Periph_I2C2, ENABLE);RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);// (2)把I2C外设对应的GPIO口初始化为复用开漏模式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); // (3)使用结构体对整个I2C进行配置I2C_InitTypeDef I2C_InitStructure;I2C_InitStructure.I2C_Mode = I2C_Mode_I2C; // I2C的模式I2C_InitStructure.I2C_ClockSpeed = 50000; // 时钟速度I2C_InitStructure.I2C_DutyCycle = I2C_DutyCycle_2; // 时钟占空比,小于于100KHz是1:1I2C_InitStructure.I2C_Ack = I2C_Ack_Enable; // ACK应答位I2C_InitStructure.I2C_AcknowledgedAddress = I2C_AcknowledgedAddress_7bit; // 7位地址I2C_InitStructure.I2C_OwnAddress1 = 0x00; // 自身地址,用不到I2C_Init(I2C2, &I2C_InitStructure);// (4)I2C_Cmd,使能I2CI2C_Cmd(I2C2, ENABLE);MPU6050_WriteReg(MPU6050_PWR_MGMT_1, 0x01); // 配置电源管理寄存器1,结合数据手册写MPU6050_WriteReg(MPU6050_PWR_MGMT_2, 0x00); // 配置电源管理寄存器2,结合数据手册写MPU6050_WriteReg(MPU6050_SMPLRT_DIV, 0x09); // 采样率分频寄存器,10分频MPU6050_WriteReg(MPU6050_CONFIG, 0x06); // 配置寄存器MPU6050_WriteReg(MPU6050_GYRO_CONFIG, 0x18); // 陀螺仪配置寄存器MPU6050_WriteReg(MPU6050_ACCEL_CONFIG, 0x18); // 加速度计寄存器
}// 获取数据寄存器,返回6个数据,形参是指针
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); // 读取加速度寄存器X轴的高8位dataL = MPU6050_ReadReg(MPU6050_ACCEL_XOUT_L); // 读取加速度寄存器X轴的低8位*accX = (dataH << 8) | dataL; // 通过指针返回回去dataH = MPU6050_ReadReg(MPU6050_ACCEL_YOUT_H); // 读取加速度寄存器y轴的高8位dataL = MPU6050_ReadReg(MPU6050_ACCEL_YOUT_L); // 读取加速度寄存器y轴的低8位*accY = (dataH << 8) | dataL; // 通过指针返回回去dataH = MPU6050_ReadReg(MPU6050_ACCEL_ZOUT_H); // 读取加速度寄存器z轴的高8位dataL = MPU6050_ReadReg(MPU6050_ACCEL_ZOUT_L); // 读取加速度寄存器z轴的低8位*accZ = (dataH << 8) | dataL; // 通过指针返回回去dataH = MPU6050_ReadReg(MPU6050_GYRO_XOUT_H); // 读取陀螺仪寄存器x轴的高8位dataL = MPU6050_ReadReg(MPU6050_GYRO_XOUT_L); // 读取陀螺仪寄存器x轴的低8位*GyroX = (dataH << 8) | dataL; // 通过指针返回回去dataH = MPU6050_ReadReg(MPU6050_GYRO_YOUT_H); // 读取陀螺仪寄存器y轴的高8位dataL = MPU6050_ReadReg(MPU6050_GYRO_YOUT_L); // 读取陀螺仪寄存器y轴的低8位*GyroY = (dataH << 8) | dataL; // 通过指针返回回去dataH = MPU6050_ReadReg(MPU6050_GYRO_ZOUT_H); // 读取陀螺仪寄存器z轴的高8位dataL = MPU6050_ReadReg(MPU6050_GYRO_ZOUT_L); // 读取陀螺仪寄存器z轴的低8位*GyroZ = (dataH << 8) | dataL; // 通过指针返回回去
}// 获取ID号
uint8_t MPU6050_GetID(void)
{return MPU6050_ReadReg(MPU6050_WHO_AM_I);
}
4.3 主函数
同2.3,现象也和2.3一样