内容
使用软件模拟I2C时序写MPU6050寄存器进行相关配置,并读取六轴传感器的值在OLED显示
使用STM32F103C8的PB10、PB11引脚模拟SCL和SDA,产生以上波形。因为I2C为同步半双工通信,使用软件模拟不会有太大问题。
I2C总线空闲时需要保持为高电平。但是在MPU6050上已经内置上拉电阻,所以PB10、PB11设置为复用开漏输出。
低耦合高内聚,先写好底层的I2C代码,再写MPU6050的控制与通讯代码,最后在主函数中调用。
I2C底层
在初始化函数中打开GPIO时钟并初始化GPIO_Pin_10 与 GPIO_Pin_11为复用开漏输出。
根据上图依次写 通讯开始、通讯结束、发送字节、接受字节、发送应答位、接受应答位的模拟电平。
通讯开始时,在SDA、SCL均拉高时,先拉低SDA,再拉低SCL。
通讯结束时,在SDA、SCL均拉低时,先拉高SCL,再拉高SDA。
SCL拉低时主机写SDA,SCL拉高时从机读SDA。
SCL拉低时主机松开SDA,从机写SDA,SCL拉高时主机读SDA。
#include "MyI2C.h"
#include "Delay.h"
//封装度端口读写 方便移植或时序替换
void MyI2C_W_SCL(uint8_t BitValue)
{
GPIO_WriteBit(GPIOB, GPIO_Pin_10, (BitAction)BitValue);
Delay_us(10);
}
void MyI2C_W_SDA(uint8_t BitValue)
{
GPIO_WriteBit(GPIOB, GPIO_Pin_11, (BitAction)BitValue);
Delay_us(10);
}
uint8_t MyI2C_R_SDA(void)
{
uint8_t BitValue;
BitValue = GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_11);
Delay_us(10);
return BitValue;
}
void MyI2C_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 MyI2C_Start(void)
{
//先拉高SDA再拉高SCL 方便时序拼接
MyI2C_W_SDA(1);
MyI2C_W_SCL(1);
MyI2C_W_SDA(0);
MyI2C_W_SCL(0);
}
void MyI2C_End(void)
{
MyI2C_W_SDA(0);
MyI2C_W_SCL(1);
MyI2C_W_SDA(1);
}
void MyI2C_SendByte(uint8_t Byte)
{
//先高后低按位取数据
for(uint8_t i = 0x80; i >= 0x01; i >>= 1)
{
//低电平写数据
MyI2C_W_SDA(Byte & i);
//翻转电平驱动时钟
MyI2C_W_SCL(1);
MyI2C_W_SCL(0);
}
}
uint8_t MyI2C_ReceiveByte(void)
{
//缓存数据
uint8_t Byte = 0x00;
//主机释放SDA
MyI2C_W_SDA(1);
for(uint8_t i = 0x80; i >= 0x01; i >>= 1)
{
//SCL置高电平主机读数据
MyI2C_W_SCL(1);
if(MyI2C_R_SDA() == 1)
{
Byte |= i;
}
//SCL置低电平从机写数据
MyI2C_W_SCL(0);
}
//返回值
return Byte;
}
void MyI2C_SendAck(uint8_t AckBit)
{
//低电平写数据
MyI2C_W_SDA(AckBit);
//翻转电平驱动时钟
MyI2C_W_SCL(1);
MyI2C_W_SCL(0);
}
uint8_t MyI2C_ReceiveAck(void)
{
//缓存接受位
uint8_t AckBit = 0;
//主机释放SDA
MyI2C_W_SDA(1);
//SCL置高电平
MyI2C_W_SCL(1);
//主机读数据
AckBit = MyI2C_R_SDA();
//SCL置低电平从机写数据
MyI2C_W_SCL(0);
//返回值
return AckBit;
}
MPU6050配置
MPU6050_Reg.h
将需要操作的寄存器地址预定义在MPU6050_Reg.h中,方便记忆与使用
#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.h
在MPU6050.h中预定义数据结构体用于返回数据给主函数,预定义相关函数
#ifndef __MPU6050_H
#define __MPU6050_H
#include "stm32f10x.h" // Device header
#include "MyI2C.h"
typedef struct
{
int16_t AccX;
int16_t AccY;
int16_t AccZ;
int16_t GyroX;
int16_t GyroY;
int16_t GyroZ;
} MPU6050_Data;
void MPU6050_WriteReg(uint8_t RegAddress, uint8_t Data);
uint8_t MPU6050_ReadReg(uint8_t RegAddress);
uint8_t MPU6050_GetID(void);
MPU6050_Data MPU6050_GetDATA(void);
void MPU6050_Init(void);
#endif
MPU6050.c
先根据写I2C通讯格式编写指定地址写和指定地址读的函数,在根据这两个函数编写MPU6050初始化、读取相关寄存器的函数。
开始信号->发送7位地址和1位读写位(写)->接收应答位->发送要操作的寄存器地址->接受应答位->发送要往寄存器内写的数据->接受应答位->结束信号
开始信号->发送7位地址和1位读写位(写)->接收应答位->发送要操作的寄存器地址->接受应答位->重新发送开始信号->发送7位地址和1位读写位(读)->接受应答位->接受8位数据->发送应答位->结束信号
#include "MPU6050.h"
#include "MPU6050_Reg.h"
#define MPU6050_ADDRESS 0xD0
//指定地址写
void MPU6050_WriteReg(uint8_t RegAddress, uint8_t Data)
{
//通讯开始
MyI2C_Start();
//指定设备地址
MyI2C_SendByte(MPU6050_ADDRESS);
//接受应答位
MyI2C_ReceiveAck();
//指定寄存器地址
MyI2C_SendByte(RegAddress);
//接受应答位
MyI2C_ReceiveAck();
//写数据
MyI2C_SendByte(Data);
//接受应答位
MyI2C_ReceiveAck();
//通讯结束
MyI2C_End();
}
uint8_t MPU6050_ReadReg(uint8_t RegAddress)
{
//通讯开始
MyI2C_Start();
//指定设备地址
MyI2C_SendByte(MPU6050_ADDRESS);
//接受应答位
MyI2C_ReceiveAck();
//指定寄存器地址
MyI2C_SendByte(RegAddress);
//接受应答位
MyI2C_ReceiveAck();
//开始时序
MyI2C_Start();
//指定设备地址 改成读地址
MyI2C_SendByte(MPU6050_ADDRESS | 0x01);
//接受应答位
MyI2C_ReceiveAck();
//缓存数据
uint8_t Data = MyI2C_ReceiveByte();
//不需要其他数据 不返回应答
MyI2C_SendAck(1);
//通讯结束
MyI2C_End();
return Data;
}
void MPU6050_Init(void)
{
//手册内容
MyI2C_Init();
//电源管理
//解除睡眠 选择陀螺仪时钟
MPU6050_WriteReg(MPU6050_PWR_MGMT_1, 0x01);
//6轴均不待机
MPU6050_WriteReg(MPU6050_PWR_MGMT_2, 0x00);
//MPU6050配置
//采样分频为10
MPU6050_WriteReg(MPU6050_SMPLRT_DIV , 0x09);
//配置外部同步 滤波最大
MPU6050_WriteReg(MPU6050_CONFIG , 0x06);
//陀螺仪配置为最大量程
MPU6050_WriteReg(MPU6050_GYRO_CONFIG , 0x18);
//加速度仪配置为最大量程
MPU6050_WriteReg(MPU6050_ACCEL_CONFIG, 0x18);
}
uint8_t MPU6050_GetID(void)
{
return MPU6050_ReadReg(MPU6050_WHO_AM_I);
}
MPU6050_Data MPU6050_GetDATA(void)
{
MPU6050_Data Data;
Data.AccX =
(MPU6050_ReadReg(MPU6050_ACCEL_XOUT_H)<<8)
| MPU6050_ReadReg(MPU6050_ACCEL_XOUT_L);
Data.AccY =
(MPU6050_ReadReg(MPU6050_ACCEL_YOUT_H)<<8)
| MPU6050_ReadReg(MPU6050_ACCEL_YOUT_L);
Data.AccZ =
(MPU6050_ReadReg(MPU6050_ACCEL_ZOUT_H)<<8)
| MPU6050_ReadReg(MPU6050_ACCEL_ZOUT_L);
Data.GyroX =
(MPU6050_ReadReg(MPU6050_GYRO_XOUT_H)<<8)
| MPU6050_ReadReg(MPU6050_GYRO_XOUT_L);
Data.GyroY =
(MPU6050_ReadReg(MPU6050_GYRO_YOUT_H)<<8)
| MPU6050_ReadReg(MPU6050_GYRO_YOUT_L);
Data.GyroZ =
(MPU6050_ReadReg(MPU6050_GYRO_ZOUT_H)<<8)
| MPU6050_ReadReg(MPU6050_GYRO_ZOUT_L);
return Data;
}
主函数
在主函数循环体外进行初始化,在循环体内读取MPU6050的寄存器并显示在OLED上
#include "stm32f10x.h" // Device header
#include "Delay.h"
#include "OLED.h"
#include "MPU6050.h"
MPU6050_Data data;
int main(void)
{
OLED_Init();
MPU6050_Init();
OLED_ShowString(1,1,"PerhID:");
OLED_ShowHexNum(1,9,MPU6050_GetID(),4);
while(1)
{
data = MPU6050_GetDATA();
OLED_ShowSignedNum(2,1,data.AccX,5);
OLED_ShowSignedNum(3,1,data.AccY,5);
OLED_ShowSignedNum(4,1,data.AccZ,5);
OLED_ShowSignedNum(2,9,data.GyroX,5);
OLED_ShowSignedNum(3,9,data.GyroY,5);
OLED_ShowSignedNum(4,9,data.GyroZ,5);
}
}