STM32学习笔记—软件IIC读取陀螺仪(基于标准库)
一.IIC介绍
IIC是一种设备间进行通讯的协议,第一次接触是在51系列单片机上面遇到的(AT24C02、EEPROM),它是由飞利浦公司开发的两线式串行总线,,只用两根线就可以实现主从机之间的信息交流(SDA、SCL),标准的IIC传输速率式100kbps,高速的IIC传输速率可达400kbps。其实现在大部分的MCU板载的都有IIC的硬件资源,虽然使用硬件资源可以大大节省代码量,但是我看网上说,STM32的IIC硬件不仅十分复杂,还特别的不稳定,所以我推荐软件IIC。
二.陀螺仪MPU6050介绍
MPU6050是一个6轴姿态传感器,可以测量芯片自身X、Y、Z轴的加速度、角速度参数,通过数据融合,可进一步得到姿态角,常应用于平衡车、飞行器等需要检测自身姿态的场景
3轴加速度计(Accelerometer):测量X、Y、Z轴的加速度
3轴陀螺仪传感器(Gyroscope):测量X、Y、Z轴的角速度
三.软件设计
3.1 软件IIC部分
关于IIC,我们只用了解开始信号、停止信号、发送一个字节、接受一个字节、发送应答。接收应答即可;现在让我们参照IIC的时序图,去实现代码的编写吧!
1.开始信号
由上图可知:在时钟线高电平时,数据线拉低;代码如下:
void IIC_Start(void)
{
IIC_W_SDA(1);//数据线拉高
IIC_W_SCL(1);//时钟线拉高
IIC_W_SDA(0);//数据线拉低
IIC_W_SCL(0);//时钟线拉低
}
2.停止信号
由上图可知:在时钟线高电平时,数据线拉高;代码如下:
void IIC_Stop(void)
{
IIC_W_SDA(0);//数据线拉低
IIC_W_SCL(1);//时钟线拉高
IIC_W_SDA(1);//数据线拉高
}
3.发送一个字节
由上图可知:在时钟线低电平时准备数据,在时钟线拉高的时候发送数据,先这样循环八次就实现了发送一个字节,代码如下:
void IIC_SendByte(uint8_t Byte)
{
uint8_t i;
for(i = 0;i < 8;i++)
{
IIC_W_SDA(Byte & (0x80 >> i));//写数据
IIC_W_SCL(1);
IIC_W_SCL(0);
}
}
4.接收一个字节
由上图可知:在时钟线拉高的时候读取1bit数据 循环八次实现读一个字节数据;代码如下:
uint8_t IIC_ReceiveByte(void)
{
uint8_t Byte = 0x00;
IIC_W_SDA(1); //数据线拉高
for(uint8_t i = 0;i < 8;i++)
{
IIC_W_SCL(1);//时钟线拉高准备读数据
Byte = ((IIC_R_SDA << (7-i)) | Byte);
IIC_W_SCL(0);
}
return Byte;
}
5.发送应答
由上图可知:在时钟线低电平的时候准备数据,在时钟线拉高的时候发送应答位(0:应答 1:非应答)我在看数据手册的时候发现,这个应答位发0的时候就可以理解为还需要继续读数据,发1的时候可以理解为读完一个字节,不想读了,释放总线。差不多就是这个意思吧,反正你收完之后还收不收数据都要给对方一个回应,不回应的话,对方还以为你出毛病了,大概就是这么个意思;代码如下:
void IIC_SendACK(uint8_t ACKValue)
{
IIC_W_SDA(ACKValue);
IIC_W_SCL(1);//时钟线拉高 开始发送数据
IIC_W_SCL(0);//时钟线拉低 准备数据
}
6.接收应答
由上图可知:在时钟线拉高的时候读数据线;其实这里我们也可以发现发送应答和接受应答的流程就是和发送和接受0、1;代码如下:
uint8_t IIC_ReciveACK(void)
{
uint8_t ACK = 0;
IIC_W_SDA(1);//主机时钟线拉高
IIC_W_SCL(1);//时钟线拉高 开始读数据
ACK = IIC_R_SDA;
IIC_W_SCL(0);//时钟线拉低 准备数据
return ACK;
}
7.验证IIC代码
在写完代码之后我需要验证一下代码的正确性,因为一旦我这里写的出现了什么问题的话,那么我后面的MPU6050写的再对也无法实现实验效果。现在的话,我们的STM32连着MPU6050,我们可以对它进行点名,如果它应答了那就说明我的IIC程序部分写的没问题,就可以进行下一步的程序编写了;
int main(void)
{
OLED_Init();
MPU6050_Init();
IIC_Start();
IIC_SendByte(0xd0);
uint8_t ACK = IIC_ReciveACK();
while(1)
{
OLED_ShowNum(1,1,ACK,2);
}
}
我这边是用OLED显示应答位是多少,验证了下我这边代码写的是没任何问题的奥,对了,这里的话0xD0是这个MPU6050地址的高七位+最低位的一个读写位奥,还有一个就是,我这里的话用的是七位寻址的方式进行数据的读写,那也就是说明最多支持128个地址,那对于32这种MCU来说只能挂载128个设备的话,也太低级了是吧,看资料上说的是STM32是可以支持10位寻址的,那就对应有1024个地址可以写进去是吧,那这可挂载的设备数量一下子就多了起来是吧。至于如何进行10位寻址我也看了下,如果10位寻址的话,就得发送两个字节才能包含这个设备的地址。这么算下来的话,就是会多出来5位,这高五位32说的是把它当作标识符,固定为11010,好像是感兴趣的话可以去查下奥。
3.2 MPU6050
其他的也没什么,这里和之前的EEPROM还有51板子上面接触过的24C02也没什么太大的区别,简单点来说就是这里的话就是包含MPU的初始化函数、MPU寄存器写入数据和读出数据(当作从机),你想读什么数据,就去读那个寄存器,这些东西数据手册上面都有,寄存器的地址啊什么的,初始化的话,就是初始化下电源,还有这个陀螺仪的话,你得知道加速度,那就得有一个参照,就像ADC采集一样有个3.3V的参照,这里也是一样操作下寄存器,设置下参照就可以了。直接上代码:
#include "MPU6050.h"
#if ENABLE_MPU6050_DIS
void MPU6050_WriteReg(uint8_t RegAddress,uint8_t Data)
{
IIC_Start();
IIC_SendByte(MPU6050_ADDRESS);//发送从机写地址(最低位)
while(IIC_ReciveACK()); //等待从机应答
IIC_SendByte(RegAddress);//发送从机地址
// IIC_ReciveACK(); //等待从机应答 0:应答 1:非应答
while(IIC_ReciveACK());
IIC_SendByte(Data); //发送从机地址
// IIC_ReciveACK();//等待从机应答
while(IIC_ReciveACK());
IIC_Stop();
}
uint8_t MPU6050_ReadReg(uint8_t RegAddress)
{
IIC_Start();
IIC_SendByte(MPU6050_ADDRESS); //发送从机地址
// IIC_ReciveACK();
while(IIC_ReciveACK()); //等待从机应答
IIC_SendByte(RegAddress); //发送从机地址
// IIC_ReciveACK();//等待从机应答
while(IIC_ReciveACK()); //等待从机应答
IIC_Start();
IIC_SendByte(MPU6050_ADDRESS | 0x01); //发送从机读地址
// IIC_ReciveACK(); //等待从机应答
while(IIC_ReciveACK()); //等待从机应答
uint8_t Data = IIC_ReceiveByte();
IIC_SendACK(1); //主机不应答 主机不想读了
IIC_Stop();
return Data;
}
void MPU6050_Init(void)
{
IIC_Init();
MPU6050_WriteReg(MPU6050_PWR_MGMT_1, 0x01);
MPU6050_WriteReg(MPU6050_PWR_MGMT_2, 0x00);
MPU6050_WriteReg(MPU6050_SMPLRT_DIV, 0x09);
MPU6050_WriteReg(MPU6050_CONFIG, 0x06);
MPU6050_WriteReg(MPU6050_GYRO_CONFIG, 0x18);
MPU6050_WriteReg(MPU6050_ACCEL_CONFIG, 0x18);
}
void MPU6050_GetData(int16_t *ACCx,int16_t *ACCy,int16_t *ACCz,
int16_t *GYROx,int16_t *GYROy,int16_t *GYROz)
{
int8_t DataH = 0x00;
int8_t DataL = 0x00;
DataH = MPU6050_ReadReg(MPU6050_ACCEL_XOUT_H);
DataL = MPU6050_ReadReg(MPU6050_ACCEL_XOUT_L);
*ACCx = (DataH << 8) | DataL;
DataH = MPU6050_ReadReg(MPU6050_ACCEL_YOUT_H);
DataL = MPU6050_ReadReg(MPU6050_ACCEL_YOUT_L);
*ACCy = (DataH << 8) | DataL;
DataH = MPU6050_ReadReg(MPU6050_ACCEL_ZOUT_H);
DataL = MPU6050_ReadReg(MPU6050_ACCEL_ZOUT_L);
*ACCz = (DataH << 8) | DataL;
DataH = MPU6050_ReadReg(MPU6050_GYRO_XOUT_H);
DataL = MPU6050_ReadReg(MPU6050_GYRO_XOUT_H);
*GYROx = (DataH << 8) | DataL;
DataH = MPU6050_ReadReg(MPU6050_GYRO_YOUT_H);
DataL = MPU6050_ReadReg(MPU6050_GYRO_YOUT_H);
*GYROy = (DataH << 8) | DataL;
DataH = MPU6050_ReadReg(MPU6050_GYRO_ZOUT_H);
DataL = MPU6050_ReadReg(MPU6050_GYRO_ZOUT_H);
*GYROz = (DataH << 8) | DataL;
}
#endif
四.实验现象
实验现象的话就是很完美,地标:黑龙江(g:1.26g);如果重力加速度有偏差的话应该是元件的问题