因为stm32的硬件I2C有问题(好像F4以后的没问题了)
我用的gd32,所以可以用硬件,但我不用这是个用硬件i2c的文章
硬件i2c工作时不能被中断打断,
然后工作在低频率下还好,频率高了就要寄了(这是有问题的硬件i2c,正常的话应该模拟的工作更慢)
这里的工作就是把普通io模拟i2c时序,与mpu6050通讯
模拟I2C
因为是上拉的,空闲都是高电平
时序
顺序:
起始信号,数据,应答信号,停止信号
起始信号:SCL高电平时,SDA有下降沿(高电平变低电平)
发送数据:SCL高电平时,【稳定】的SDA高就是1,低就是0;以8bit的数据发送。先发送高电平。 【发送数据这里,SDA一定要再SCL低电平时变化】
应答信号(ACK):高电平是表示没收到数据(nack),低电平是收到了数据(ack);在发送数据的后面。
停止信号:SCL高电平时,SDA有上升沿
初始化
这里我们创建个i2c.c和i2c.h专门放这些函数吧,看着好看。
延时
为什么要延时呢,在这说一下看这个表,i2c时序
这是有最低的高低电平时间的,为了超过最低延时时间,就设置了2us延时。
写在i2c.c文件里
首先定义一个iic_delay函数,目的就是控制IIC的读写速度,通过示波器检测读写速度在250KHz内,所以一秒钟传送500Kb数据,换算一下即一个bit位需要2us,在这个延时时间内可以让器件进行获得一个稳定性的数据采集。
不想写delay_us的函数,用__nop代替吧,,写144个__nop();
void IIC_DELAY(){
__nop();__nop();__nop();__nop();__nop();__nop();__nop();__nop();__nop();__nop();__nop();__nop();__nop();__nop();__nop();__nop();__nop();__nop();__nop();__nop();
__nop();__nop();__nop();__nop();__nop();__nop();__nop();__nop();__nop();__nop();__nop();__nop();__nop();__nop();__nop();__nop();__nop();__nop();__nop();__nop();
__nop();__nop();__nop();__nop();__nop();__nop();__nop();__nop();__nop();__nop();__nop();__nop();__nop();__nop();__nop();__nop();__nop();__nop();__nop();__nop();
__nop();__nop();__nop();__nop();__nop();__nop();__nop();__nop();__nop();__nop();__nop();__nop();__nop();__nop();__nop();__nop();__nop();__nop();__nop();__nop();
__nop();__nop();__nop();__nop();__nop();__nop();__nop();__nop();__nop();__nop();__nop();__nop();__nop();__nop();__nop();__nop();__nop();__nop();__nop();__nop();
__nop();__nop();__nop();__nop();__nop();__nop();__nop();__nop();__nop();__nop();__nop();__nop();__nop();__nop();__nop();__nop();__nop();__nop();__nop();__nop();
__nop();__nop();__nop();__nop();__nop();__nop();__nop();__nop();__nop();__nop();__nop();__nop();__nop();__nop();__nop();__nop();__nop();__nop();__nop();__nop();
__nop();__nop();__nop();__nop();
}
gpio
SCL可以推挽也可以开漏
SDA一定要配置开漏输出,因为SDA又要输出数据,又要接收数据
开漏输出可以输入输出共用,避免了io模式频繁切换
先初始化引脚开漏输出,高速(gpio电平设置高)
一些宏
写在i2c.h文件里
上面初始化gpio我改了user label,所以引脚名字是SCL_Pin和SDA_Pin,
这里写一下设置引脚高低和读取引脚电平的宏,后面用着方便。
#include "stm32f1xx_hal.h"
#include "gpio.h"
#define IIC_SCL(x) do{ x ? \
HAL_GPIO_WritePin(GPIOB,SCL_Pin,GPIO_PIN_SET): \
HAL_GPIO_WritePin(GPIOB,SCL_Pin,GPIO_PIN_RESET);\
}while(0)
#define IIC_SDA(x) do{ x ? \
HAL_GPIO_WritePin(GPIOB,SDA_Pin,GPIO_PIN_SET): \
HAL_GPIO_WritePin(GPIOB,SDA_Pin,GPIO_PIN_RESET);\
}while(0)
#define IIC_READ_SDA HAL_GPIO_ReadPin(GPIOB, SDA_Pin)
起始信号
写在i2c.c文件里
SCL高电平时,SDA有下降沿
先让SDA为高电平,保证SCL高的时候只产生下降沿。
void IIC_START()
{
IIC_SDA(1);
IIC_SCL(1);
IIC_DELAY();
IIC_SDA(0); /* START信号: 当SCL为高时, SDA从高变成低, 表示起始信号 */
IIC_DELAY();
IIC_SCL(0); /* 钳住I2C总线,准备发送或接收数据 */
IIC_DELAY();
}
停止信号
SCL高电平时,SDA有上升沿
先让SDA为低电平,保证SCL高的时候只产生上升沿。
void IIC_STOP(){
IIC_SDA(0);
IIC_DELAY();
IIC_SCL(1); /* STOP信号: 当SCL为高时, SDA从低变成高, 表示停止信号 */
IIC_DELAY();
IIC_SDA(1); /* 发送I2C总线结束信号 */
IIC_DELAY();
}
检测ACK信号
发送完数据后,SCL高时,SDA高就表示没收到,SDA低就是收到了
因为要返回值,所以不用void了
说一下循环里面:
数据发送完,SDA是释放掉的,是高电平,直接会进入循环。然后等待一段时间
如果收到了ack信号,那么IIC_READ_SDA会变成0,跳出while循环,rack=0,返回0表示接收成功。
如果收到nack信号,也就是SDA一直是1,那么一段时间后进入if语句,停止IIC,让rack变成1,break退出while循环,返回1表示接收失败
检测完成后记得让SCL变成0(钳住SCL),以便于下一个SDA的变化。
uint8_t IIC_WAIT_ACK(){
uint8_t waittime=0 ,rack=0;
//在发送数据那里释放过SDA线,所以不重复写浪费时间了
//在SCL高电平时读取SDA发送来的信号,高电平是nack,低电平是ack
IIC_SCL(1);
IIC_DELAY();
while(IIC_READ_SDA){
waittime++;
if(waittime>250)
{
IIC_STOP();
rack=1;
break;
}
}
IIC_SCL(0);
IIC_DELAY();
return rack;
}
发送ack信号
MCU也会接收数据的,那么也要给从设备发送ack信号
接收数据结束后,SDA被释放,当SCL高时,SDA高就是接收失败 (nack),SDA低就是接收成功(ack)
这里写发送接收成功的ack
SCL高电平时有一个稳定的SDA低电平
记得发送完后让SCL变0以便于之后SDA的变化,并且释放掉SDA线,让从机使用。
void IIC_ACK(){
IIC_SDA(0);
IIC_DELAY();
IIC_SCL(1);
IIC_DELAY();
//钳住SCL并释放SDA线
IIC_SCL(0);
IIC_DELAY();
IIC_SDA(1);
IIC_DELAY();
}
发送nack信号
SCL高电平时有一个稳定的SDA高电平
void IIC_NACK(){
IIC_SDA(1);
IIC_DELAY();
IIC_SCL(1);
IIC_DELAY();
//钳住SCL并释放SDA线,不过本来SDA就是高,就不写了浪费时间了
IIC_SCL(0);
IIC_DELAY();
}
发送1byte数据
iic一次发送8位数据,用一个循环
iic从高位开始发送,所以读取高位,设置SDA
SCL低的时候更改SDA,SCL高的时候SDA要稳定
发送完成,为了获取从机的应答信号,要释放SDA线,让从机可以选择下拉SDA,给出应答
void IIC_SEND_BYTE(uint8_t data)
{
uint8_t t;
for(t=0;t<8;t++)
{
IIC_SDA((data & 0x80)>>7); //高位先发送,读取data高位
IIC_DELAY();
IIC_SCL(1);
IIC_DELAY();
IIC_SCL(0);
data<<=1; //左移1位,用于下一次发送;uint8,最高位左移没了,次高位变最高位
}
IIC_SDA(1); //发送完成, 主机释放SDA线
}
读取1BYTE数据
IIC又要发送数据,又要接收数据,那读取也必不可少
定义一个变量用来存放数据,最后返回这个变量
说一下第一次循环里的左移操作,因为本来就是0,所以左移没有意义,不要纠结这个。之后的循环里,左移会让低位变成高位,从而把8位数据都存进去
uint8_t IIC_READ_BYTE(uint8_t ack)
{
uint8_t receive=0;
for(uint8_t t=0;t<8;t++){
//先来的数据是高位的,那么接收一次就向左移位一次。最低为就变0
receive<<=1;
IIC_SCL(1);
IIC_DELAY();
if(IIC_READ_SDA)receive++; //如果接收到是1,receive最低位就变1.
IIC_SCL(0);
IIC_DELAY();
}
if(ack){
IIC_ACK();
}
else{
IIC_NACK();
}
return receive;
}
MPU6050
看之前最好准备一下MPU6050的数据手册与寄存器手册;主要得看寄存器手册。
基本的函数
这一部分函数依然写在i2c.c里面
上面我们做好了IIC的基本操作的函数,可以发送接收数据;但还不够,我们要直接的给从设备发送信息。
但是我们一个IIC上面挂了很多设备,怎么才能确定这个数据是给谁的呢
起始信号-----从机地址+读/写-----从机的应答信号-----从机的寄存器地址-----【数据–应答信号】-----【数据–应答信号】–停止信号
写入:
读取:
那么从机地址是多少呢
MPU-60X0 的Slave 地址为b110100X,7 位字长,最低有效位X 由AD0 管脚上的逻辑电平决定。这样就可以允许两个MPU-60X0 连接到同一条I2C 总线,此时,一个设备的
地址为b1101000(AD0 为逻辑低电平),另一个为b1101001(AD0 为逻辑高)。
咱们这个AD0是下拉的,0x68
0x68,写成2进制,0110 1000,而我们要发送的应该是高7位地址,最低位1/0(读/写)。
那么到时候数据记得把0x68左移一位,再加上最低位的读或者写。
读取写入IIC设备的数据
看着表写
主机发送【开始信号】,发送【从机地址+写】,等待【从机ACK信号】,发送【寄存器地址】,等待【从机ACK信号】,发送【开始信号】,发送【从机地址+读】,等待【从机ACK信号】,接收【从机数据】,主机发送【ACK信号】,接收【从机数据】……主机发送【NACK】,主机发送【停止信号】。
当然,连续读或写,不是连续往这一个寄存器重复操作,里面有递增寄存器,会自动往后面的寄存器操作的。
读取的最后一个应答信号一定是NACK,不然结束不了,等着后面操作报错吧
读取IIC设备1Byte数据
先写一个只读取1Byte数据的代码,连续读取下面再写。
三个参数,从机地址,寄存器地址,用来存数据的变量
uint8_t IIC_ReadByteFromSlave(uint8_t I2C_Addr,uint8_t reg,uint8_t *buf)
{
IIC_START();
IIC_SEND_BYTE(I2C_Addr<<1|0); //写
if(IIC_WAIT_ACK()){
//printf("从机地址发送出错 \r\n");
return 1;
}
IIC_SEND_BYTE(reg);
if(IIC_WAIT_ACK()){
//printf("寄存器reg地址发送出错 \r\n");
return 2;
}
IIC_START();
IIC_SEND_BYTE(I2C_Addr<<1|1); //读
IIC_WAIT_ACK();
*buf = IIC_READ_BYTE(0);
IIC_STOP();
return 0;
}
看这个代码: IIC_SEND_BYTE(I2C_Addr<<1|1);,也许有人会犯傻比,想着I2C_ADDR与1相或,那不I2C_ADDR的8位全变成1了吗。那你就得想想0110 1000和0000 0001相与,结果是啥了。
读取whoami寄存器
用这个函数,就可以尝试读取一下寄存器了,这个寄存器可以让你知道你是不是被坑了,比如买9250,买到6500
只要你是6050,你读whoami寄存器,就一定读到0x68
寄存器地址这个表里写了,0x75
test=IIC_ReadByteFromSlave(0x68,0x75,&IIC_BUFFER);
printf("MPU6050 0x%x\r\n",IIC_BUFFER);
HAL_Delay(1000);
在main函数的while循环复制下这个,就OK了
记得去cubeMX初始化一下usart1,把print函数重写一下,不知道可以看这,最下面的重写printf,代码找个地方复制一下就行,不在主函数也没问题,重写了到处都能用。
连续读取IIC数据
不细说了,对着上面的时许表来自己看吧
四个参数:从设备地址,寄存器地址,连续读取长度,存放数据的数组
uint8_t IIC_ReadMultibyteFromSlave(uint8_t I2C_Addr,uint8_t reg,uint8_t len,uint8_t *data)
{
uint8_t count = 0;
IIC_START();
IIC_SEND_BYTE(I2C_Addr<<1|0); //写
if(IIC_WAIT_ACK()){
//printf("从机地址发送出错 \r\n");
return 1;
}
IIC_SEND_BYTE(reg);
if(IIC_WAIT_ACK()){
//printf("寄存器reg地址发送出错 \r\n");
return 2;
}
IIC_START();
IIC_SEND_BYTE(I2C_Addr<<1|1); //读
IIC_WAIT_ACK();
for(count=0;count<(len-1);count++) //除了最后一次接收数据,都是发送ACK应答
{
*data = IIC_READ_BYTE(1);
data ++;
}
*data = IIC_READ_BYTE(0); //最后一次接收数据,发送NACK应答
IIC_STOP();
return 0;
}
写1BYTE数据
uint8_t IIC_WriteByteFromSlave(uint8_t I2C_Addr,uint8_t reg,uint8_t data)
{
IIC_START();
IIC_SEND_BYTE(I2C_Addr<<1 | 0);
if(IIC_WAIT_ACK()){
//printf("从机地址发送出错 \r\n");
return 1;
}
IIC_SEND_BYTE(reg);
if(IIC_WAIT_ACK()){
//printf("寄存器reg地址发送出错 \r\n");
return 2;
}
IIC_SEND_BYTE(data);
if(IIC_WAIT_ACK()){
//printf("发送数据出错 \r\n");
return 3;
}
IIC_STOP();
return 0;
}
连续写数据
仔细看的人就会发现了,连续读与连续写两个函数里面的for循环,我用了两种方法获取数据,
当然,这没什么意义,但你们可以看着这两种方法想想自己还记不记得。
uint8_t IIC_WriteMultibyteFromSlave(uint8_t I2C_Addr,uint8_t reg,uint8_t len,uint8_t* data)
{
uint8_t count=0;
IIC_START();
IIC_SEND_BYTE(I2C_Addr<<1 | 0);
if(IIC_WAIT_ACK()){
//printf("从机地址发送出错 \r\n");
return 1;
}
IIC_SEND_BYTE(reg);
if(IIC_WAIT_ACK()){
//printf("寄存器reg地址发送出错 \r\n");
return 2;
}
for(count=0;count<len;count++)
{
IIC_SEND_BYTE(data[count]);
if(IIC_WAIT_ACK()){
//printf("发送数据出错 \r\n");
return (count+3);
}
}
IIC_STOP();
return 0;
}
与6050通讯的流程
这一部分函数在mpu6050.c里面写
MPU6050一上电(刚复位过)是睡眠状态的,读不到whoami寄存器以外的信息很正常
初始化MPU6050
1,检查MCU(主机)与MPU6050(姿态传感器)是否通信成功
2,设置MPU6050为复位状态,并且100ms延时,确保复位完成
3,唤醒MPU6050,选择PLL为时钟源
4,配置6轴数据全部输出(3轴加速度与3轴陀螺仪)
5,禁止所有中断(避免传感器读取到一个数据就中断,而耽误系统进程)
6,设置MPU6050的内部采样频率以及低通滤波器(小的飞行器一一般设施在20~30Hz即可)
7,设置陀螺仪与加速度计的满量程范围
1.检查是否通讯成功
这一步在上面做过了,去看读取whoami寄存器这一块
whoami寄存器什么时候都能通讯,也就可以拿来测试通讯是否成功
void MPU6050_TestConnection(){
uint8_t buf;
if(IIC_ReadByteFromSlave(0x68,0x75,&buf)){
printf("MPU6050连接成功:0x%x",buf);
}
}
2.设置复位状态
看表格,这个电源寄存器1比较重要
最高位就是复位
设置DEVICE_RESET为1,就会复位,然后这一位会自动清零
都复位了,就不读取其他寄存器是什么,再或上1了,直接让最高位是1,其他位随便搞【0x80】;当然,复位需要时间,具体要多久我不清楚,但大家都设置延时上100ms再操作。
那么复位以后各种寄存器都变成什么呢,我们在寄存器手册里面的第三章能看到
很多博客里面写复位后107寄存器复位值是0010 0000,那是他们写错了,好像是9250的是这样的,被他们复制到了6050的教程里面
IIC_WriteByteFromSlave(0x68,0x6B,0x80);
HAL_Delay(100);
3.唤醒并配置时钟
还是上面哪个107寄存器,电源寄存器1
这个寄存器配置的还蛮多的,一块说了吧:
1.bit7最高位复位:不说了
2.bit6次高位休眠:休眠的时候功耗低,但我们像要让MPU6050工作,就不能让他睡眠,后面要设置成0
3.bit5CYCLE,循环模式:这一位如果置1,并且SLEEP置0(没休眠),那么设备会再睡眠模式与唤醒模式中循环。我们要让他一直工作,所以这一位置0
4.bit3使能温度传感器:置0的时候就使能温度传感器了,如果温度高了,要通过温度传感器来抑制温漂。(我不用)
5.bit2~0时钟:看上面的表,我们用X轴PLL时钟,也就是1配置001;选0的话内置8M RC时钟精度不高,不如锁相环时钟,外部时钟的话我也不清楚哪来的外部时钟,所以选内部的X锁相环。
总结下来就是配置0x01(0000 0001)
IIC_WriteByteFromSlave(0x68,0x6B,0x01);
4.配置六轴数据输出
这里要用的是108寄存器;电源管理寄存器2
如果之前配置循环模式,那么用到bit7,bit6
但我们没用循环,就直接唤醒的,我们要高频率的使用他,所以让六轴全部使能,LP_WAKE_CTRL没用,随便设置;我设置0x00(00000000)
IIC_WriteByteFromSlave(0x68,0x6C,0x00);
5.禁止中断
不知道说啥,我用不到中断,所以我禁止了中断0x00(0000 0000)
这里用的56寄存器;中断使能寄存器
MOT_EN:用到运动检测中断,打开他
FIFO_OFLOW_EN:这里的FIFO一般用在DMP解算,如果用到可以打开它
I2C_MST_INT_EN:使能I2C主机所有中断源产生中断,,,,抱歉没看明白,不知道咋说
DATA_RDY_EN:数据就绪中断,所有寄存器,只要有写操作完成,就产生中断(在IMU_INT引脚产生)
IIC_WriteByteFromSlave(0x68,0x38,0x00);
6.设置内部采样频率和低通滤波器
内部采样频率
加速度计获取加速度,陀螺仪获取角速度,都需要一个采样频率,就在这里设置
那么采样频率要设置多少才合适呢,这要根据姿态解算来设置
比如我们姿态解算用500Hz,那么我们采样频率起码不能低于这个500Hz,我记得哪一门课有讲过采样频率多少比较好,好像是数字信号处理这门课是吧,记得的人在下面说说。那么我们设置1KHz的采样频率
采样定理,又称香农采样定理,奈奎斯特采样定理,只要采样频率大于或等于有效信号最高频率的两倍,采样值就可以包含原始信号的所有信息,被采样的信号就可以不失真地还原成原始信号。
用到25寄存器,采样频率分频器
当输出频率为1K的时候,SMPLRT_DIV配置0,那么
那么我们知道了分配系数要怎么选
数字低通滤波器
这时候聪明的小伙伴就要问了,哎我屮你说这么多,他陀螺仪输出频率是多少呢
我们上面图里,有提到过DLPF(数字低通滤波器),当低通滤波器disabled,输出频率是8K,当低通滤波器enabled,输出频率是1K,下面这个表就更清楚了
滤波就很重要,想想我们板子如果震动,对数据影响有多大。【滤波的Bandwidth设置多大就与板子的震动有关了】
但是我们要配置让多大的频率导通呢-----(为了消除干扰,把一些杂乱的信号去掉)
从原子哥那里看到的:设置
希望知道为什么设置1/2采样率的说一下,我不懂
重点来了,我不知道该配置多少,我去抄了一个别人的无人机的代码,他用的采样频率1K(也就是25寄存器写0x00),DLPF设置20Hz。我也这样吧
前面哪个FSYNC帧同步的,咱们也不用,配置0就行。
那么25寄存器配置0x00(0000 0000),DLPF配置4(20Hz带宽)26寄存器配置0x04(0000 0100)
IIC_WriteByteFromSlave(0x68,0x19,0x00); //分频0(采样频率1K)
IIC_WriteByteFromSlave(0x68,0x1A,0x04); //数字低通滤波器带宽20Hz
7.陀螺仪与加速度计满量程范围配置
陀螺仪
陀螺仪的ADC是16位的,±32768,也就是能有65536个数值;他的量程范围有±250,500,1000,2000(°/S),一般设置±2000的范围,他的灵敏度是
测量范围大,灵敏度就低。
如果用9250,这里低两位还要设置一个参数,用来配合使用数字低通滤波器
从这个bit3,bit4设置,另外三位是设置是否自检的,我就不自检了
那就是FS_SEL设置3(11)
IIC_WriteByteFromSlave(0x68,0x1B,0x18); //设置陀螺仪量程±2000,不自检
加速度计
加速度计ADC也是16位的,量程范围有±2,4,8,16g
想想这小飞机能飞多块,咱们设置±4g吧,从下面看是设置AFS_SEL 为1(01)
还是三位是设置自检,两位设置量程
IIC_WriteByteFromSlave(0x68,0x1C,0x08); //设置加速度计量程±4g,不自检
一些可能会以后用,但这个项目不会用的常见寄存器
FIFO使能寄存器(0x23):用DMP的时候少不了用这个
INT引脚/旁路有效 使能寄存器(0x37):这里能设置让主设备直接访问MPU6050的从设备(可以和MPU6050同时和主设备通讯)(不过这样为什么不挂在一根I2C上呢)
总体看下来
void MPU6050_Init()
{
MPU6050_TestConnection(); //检测连接是否成功
IIC_WriteByteFromSlave(0x68,0x6B,0x80); //复位
HAL_Delay(100);
IIC_WriteByteFromSlave(0x68,0x6B,0x01); //唤醒并启用时钟
IIC_WriteByteFromSlave(0x68,0x6C,0x00); //配置六轴输出
IIC_WriteByteFromSlave(0x68,0x38,0x00); //关闭中断
IIC_WriteByteFromSlave(0x68,0x19,0x00); //分频0(采样频率1K)
IIC_WriteByteFromSlave(0x68,0x1A,0x04); //数字低通滤波器带宽20Hz
IIC_WriteByteFromSlave(0x68,0x1B,0x18); //设置陀螺仪量程±2000,不自检
IIC_WriteByteFromSlave(0x68,0x1C,0x08); //设置加速度计量程±4g,不自检
}
获取原始数据
6050初始化完了,就可以拿数据了,为什么是原始数据,因为后面我们还要处理一下才能用
加速度计原始数据
看表格就能知道,连续的六个寄存器,放着XYZ轴的加速度数据
我们要做的是创建一个函数,在里面读取这六个寄存器,然后X的高低两个寄存器的数据放到一块,Y的放一块,Z的放一块
对了,这个ADC要表示有符号的数,咱们的用来保存数据的参数就不要用uint了,用int
6个寄存器,用长度为6的数组存放,然后高位寄存器左移8位,或上低位寄存器,传给参数。
void MPU6050_AccRead(int16_t *accData)
{
uint8_t buf[6];
IIC_ReadMultibyteFromSlave(0x68,0x3B,6,buf);
accData[0] = (int16_t)((buf[0]<<8)|buf[1]); //X
accData[1] = (int16_t)((buf[2]<<8)|buf[3]); //Y
accData[2] = (int16_t)((buf[4]<<8)|buf[5]); //Z
}
陀螺仪原始数据
陀螺仪就和上面一样的操作
void MPU6050_GyroRead(int16_t *gyroData)
{
uint8_t buf[6];
IIC_ReadMultibyteFromSlave(0x68,0x3B,6,buf);
gyroData[0] = (int16_t)((buf[0]<<8)|buf[1]); //X
gyroData[1] = (int16_t)((buf[2]<<8)|buf[3]); //Y
gyroData[2] = (int16_t)((buf[4]<<8)|buf[5]); //Z
}
温度数据
也是差不多的,只是这个只有两个寄存器
温度就不算什么原始数据了,拿到这个温度就是最终数据
像什么36.8℃之类的,咱们用的是浮点对吧,所有传参数用浮点类型
看上面图片,单位是摄氏度,计算公式为:
void MPU6050_tempRead(float *tempData)
{
uint8_t buf[2];
short data;
IIC_ReadMultibyteFromSlave(0x68,0x41,2,buf);
data = (int16_t)((buf[0]<<8)|buf[1]);
*tempData = (data/340)+36.53;
}
为什么用short类型数据,因为short是16位能存下数据,而且有符号
中途测试
做这么多有没有人还没去测试的,下面是我的main函数,你可以对着写一下,看看输出出来是多少
int main(void)
{
/* USER CODE BEGIN 1 */
/* USER CODE END 1 */
/* MCU Configuration--------------------------------------------------------*/
/* Reset of all peripherals, Initializes the Flash interface and the Systick. */
HAL_Init();
/* USER CODE BEGIN Init */
int16_t gyroData[3],accData[3];
float tempData;
/* USER CODE END Init */
/* Configure the system clock */
SystemClock_Config();
/* USER CODE BEGIN SysInit */
/* USER CODE END SysInit */
/* Initialize all configured peripherals */
MX_GPIO_Init();
MX_USART1_UART_Init();
/* USER CODE BEGIN 2 */
RGB_Color(33,125,155);
RGB_Color(255,0,0);
RGB_Color(0,255,0);
RGB_Color(0,0,255);
IIC_STOP();
MPU6050_Init();
/* USER CODE END 2 */
/* Infinite loop */
/* USER CODE BEGIN WHILE */
while (1)
{
/* USER CODE END WHILE */
MPU6050_tempRead(&tempData);
MPU6050_GyroRead(gyroData);
MPU6050_AccRead(accData);
printf("温度:%f \r\n",tempData);
printf("角速度:X:%d,Y:%d,Z:%d \r\n",gyroData[0],gyroData[1],gyroData[2]);
printf("加速度:X:%d,Y:%d,Z:%d \r\n",accData[0],accData[1],accData[2]);
HAL_Delay(1000);
/* USER CODE BEGIN 3 */
}
/* USER CODE END 3 */
}
姿态解算
我们现在手里的数据,很难用到,我们要把他变成欧拉角,来方便我们使用
欧拉角也就是航向角(yaw),横滚角(roll),俯向角(pitch)
MPU6050里面内置了DMP数字运动处理器。可以将原始数据直接转化为四元数输出,然后我们再通过四元数转换成欧拉角;用DMP可以大大简化代码设计,并且MCU的工作减少了,可以省出资源拿去做其他工作
这一篇太长了,我们下一篇再讲吧,来下一篇看姿态解算