非常依赖硬件外设的支持,必须USART电路才能使用
,同步时序比异步时序稳定性更加高,可以暂停传输(同时暂停时钟线)所有I2C设备的SCL连在一起,SDA连在一起
主机对SCL线是完全控制,对SDA线不是,在从机发送数据和从机应答的时候,主机才会转交SDA控制器给从机,从机的SCL线只能被动读取。
为了防止总线没有协调好导致电源短路问题,I2C禁止所有设备输出强上拉高电平【不能是推挽输出,会输出高电平】,采用外置弱上拉电阻+开漏输出的电路结构,可以同时兼具输入和输出功能
。输入的时候先输出1,再读取输入数据寄存器
总结:
在读写时,SCL低电平时,SDA写,SCL高电平时,SDA不变
在起始和结束时,SCL高电平,SDA变化
空闲状态,SCL和SDA由于外挂的上拉电阻拉高致高电平
起始和终止都是由主机产生的,从机始终不会去碰SCL和SDA线
串口通信是低位先行,i2c是高位先行
发送应答和接收应答就是发送数据的一位和接收数据的一位
发送应答:主机在接收完一个字节之后,在下一个时钟发送一位数据
,数据0表示应答,数据1表示非应答
接收应答:主机在发送完一个字节之后,在下一个时钟接收一位数据
,判断从机是否应答,数据0表示应答,数据1表示非应答(主机在接收之前,需要释放SDA)
从机设备地址分为7位和10位,一般相同型号的芯片地址是一样的
当相同型号的芯片挂载在同一总线上时,可以通过最后几位区分,例如mpu6050的最后一位可以由AD0引脚控制,接低电平时地址是1101000,接高电平时地址是1101001
对于指定设备(Slave Address),在指定地址(Reg Address)下,写入指定数据(Data)
对于指定设备(Slave Address),在当前地址指针指示的地址下,读取从机数据(Data)
指定地址写+当前地址读(第三个字节不写入内容就直接开始读)
对于指定设备(Slave Address),在指定地址(Reg Address)下,读取从机数据(Data)
通过数据融合,可进一步得到姿态角(欧拉角)
,常应用于平衡车、飞行器等需要检测自身姿态的场景常见的数据融合算法有卡尔曼滤波,互补滤波,互补滤波和卡尔曼滤波能够一定程度解决航向角偏移的问题,而且对于随机干扰也有更优秀的表现,但是是较为复杂的算法。
MPU6050内部有DMP单元,可以进行数据融合和姿态解算,避免了复杂的算法操作和外围电路。可以减轻外围微处理器的工作负担且避免了繁琐的滤波和数据融合,使用方便,只需要通过简单读取寄存器操作便可以获得小车的姿态信息。。
MPU6050的陀螺仪测量的是角速度,角度可以通过角速度积分得到
,具有动态稳定性,不具有静态稳定性16位ADC采集传感器的模拟信号,量化
范围:-32768~32767
加速度计满量程选择:±2、±4、±8、±16(g)【量化范围对应加速度计的量程范围,变化大则选大量程,测量范围越广,变化小则选小量程,精度更高】
陀螺仪满量程选择: ±250、±500、±1000、±2000(°/sec)
可配置的数字低通滤波器(平缓信号抖动)
可配置的时钟源
可配置的采样分频
I2C从机地址:
INT引脚可以设置电平跳变,用于输出芯片内部的中断事件
十六位的有符号数以二进制补码的形式存储,读出高八位左移八位,或上低八位
MyI2C.c
#include "stm32f10x.h" // Device header
#include "Delay.h"
/*
封装三个函数操作相应的IO口便于移植
MyI2C_W_SCL
MyI2C_W_SDA
MyI2C_R_SDA
*/
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)
{
MyI2C_W_SDA(1);//为了兼容sr重复起始条件,先释放sda后释放scl
MyI2C_W_SCL(1);
MyI2C_W_SDA(0);
MyI2C_W_SCL(0);
}
void MyI2C_Stop(void)
{
MyI2C_W_SDA(0);//为了确保释放sda是高电平,先拉低sda
MyI2C_W_SCL(1);
MyI2C_W_SDA(1);
}
void MyI2C_SendByte(uint8_t Byte)
{
uint8_t i;
for (i = 0; i < 8; i ++)
{
MyI2C_W_SDA(Byte & (0x80 >> i));//高位先行,按位与取出相应位数,右移得到相应的位
MyI2C_W_SCL(1);//高位读取sda数据
MyI2C_W_SCL(0);
}
}
uint8_t MyI2C_ReceiveByte(void)
{
uint8_t i, Byte = 0x00;//定义一个字节的数据
MyI2C_W_SDA(1);//主机释放sda
for (i = 0; i < 8; i ++)
{
MyI2C_W_SCL(1);//scl高电平时放入数据
if (MyI2C_R_SDA() == 1){
Byte |= (0x80 >> i);//输出寄存器为1则在该字节的相应位置写入1
}
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;
MyI2C_W_SDA(1);//释放sda,而不是输出1
MyI2C_W_SCL(1);
AckBit = MyI2C_R_SDA();//读到0则表示从机应答
MyI2C_W_SCL(0);
return AckBit;
}
MyI2C.h
#ifndef __MYI2C_H
#define __MYI2C_H
void MyI2C_Init(void);
void MyI2C_Start(void);
void MyI2C_Stop(void);
void MyI2C_SendByte(uint8_t Byte);
uint8_t MyI2C_ReceiveByte(void);
void MyI2C_SendAck(uint8_t AckBit);
uint8_t MyI2C_ReceiveAck(void);
#endif
MPU6050.c
#include "stm32f10x.h" // Device header
#include "MyI2C.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_Stop();
}
//指定地址读
uint8_t MPU6050_ReadReg(uint8_t RegAddress)
{
uint8_t Data;
MyI2C_Start();
MyI2C_SendByte(MPU6050_ADDRESS);
MyI2C_ReceiveAck();
MyI2C_SendByte(RegAddress);
MyI2C_ReceiveAck();
//重复起始条件
MyI2C_Start();
MyI2C_SendByte(MPU6050_ADDRESS | 0x01);//指定地址写,修改最低位为1
MyI2C_ReceiveAck();
Data = MyI2C_ReceiveByte();
MyI2C_SendAck(1);//主机收回控制权
MyI2C_Stop();
return Data;
}
//指定地址写之前需要取消睡眠模式
void MPU6050_Init(void)
{
MyI2C_Init();
MPU6050_WriteReg(MPU6050_PWR_MGMT_1, 0x01);//最后三位选择时钟,0x01选择陀螺仪x轴的时钟
MPU6050_WriteReg(MPU6050_PWR_MGMT_2, 0x00);//不需要待机
MPU6050_WriteReg(MPU6050_SMPLRT_DIV, 0x09);//10分频
MPU6050_WriteReg(MPU6050_CONFIG, 0x06);//后三位给110,低通滤波器
MPU6050_WriteReg(MPU6050_GYRO_CONFIG, 0x18);//中间2位是满量程,给11是最大量程
MPU6050_WriteReg(MPU6050_ACCEL_CONFIG, 0x18);//中间2位是满量程,给11是最大量程
}
uint8_t MPU6050_GetID(void)
{
return MPU6050_ReadReg(MPU6050_WHO_AM_I);//指定地址读
}
//获取数据寄存器函数,参数为指针,指向各个数据寄存器的地址
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);
DataL = MPU6050_ReadReg(MPU6050_ACCEL_XOUT_L);
*AccX = (DataH << 8) | DataL;//该变量为16位,有指针指向该地址
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_L);
*GyroX = (DataH << 8) | DataL;
DataH = MPU6050_ReadReg(MPU6050_GYRO_YOUT_H);
DataL = MPU6050_ReadReg(MPU6050_GYRO_YOUT_L);
*GyroY = (DataH << 8) | DataL;
DataH = MPU6050_ReadReg(MPU6050_GYRO_ZOUT_H);
DataL = MPU6050_ReadReg(MPU6050_GYRO_ZOUT_L);
*GyroZ = (DataH << 8) | DataL;
}
/*
多返回值设计:
1. 在该文件中定义全局变量,extern暴露出去,在main文件中使用
2. 用指针
3. 使用结构体打包
*/
MPU6050.h
#ifndef __MPU6050_H
#define __MPU6050_H
void MPU6050_WriteReg(uint8_t RegAddress, uint8_t Data);
uint8_t MPU6050_ReadReg(uint8_t RegAddress);
void MPU6050_Init(void);
uint8_t MPU6050_GetID(void);
void MPU6050_GetData(int16_t *AccX, int16_t *AccY, int16_t *AccZ,
int16_t *GyroX, int16_t *GyroY, int16_t *GyroZ);
#endif
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
main.c
#include "stm32f10x.h" // Device header
#include "Delay.h"
#include "OLED.h"
#include "MPU6050.h"
uint8_t ID;
int16_t AX, AY, AZ, GX, GY, GZ;
int main(void)
{
OLED_Init();
MPU6050_Init();
OLED_ShowString(1, 1, "ID:");
ID = MPU6050_GetID();
OLED_ShowHexNum(1, 4, ID, 2);
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, GY, 5);
OLED_ShowSignedNum(4, 8, GZ, 5);
}
}
STM32——I2C外设总线
参考视频:江科大自化协