一、I2C通信
1、I2C(Inter IC Bus)是由Philips(飞利浦)公司开发的一种通用数据总线
2、两根通信线:SCL(Serial Clock)、SDA(Serial Data)
3、同步,半双工
4、带数据应答
5、支持总线挂载多设备(一主多从、多主多从)
6、硬件电路
(1)所有I2C设备的SCL连在一起,SDA连在一起
(2)设备的SCL和SDA均要配置成开漏输出模式
(3)SCL和SDA各添加一个上拉电阻,阻值一般为4.7KΩ左右
7、I2C时序基本单元
(1)起始条件:SCL高电平期间,SDA从高电平切换到低电平
(2)终止条件:SCL高电平期间,SDA从低电平切换到高电平
(3)发送一个字节:SCL低电平期间,主机将数据位依次放到SDA线上(高位先行),然后释放SCL,从机将在SCL高电平期间读取数据位,所以SCL高电平期间SDA不允许有数据变化,依次循环上述过程8次,即可发送一个字节
(4)接收一个字节:SCL低电平期间,从机将数据位依次放到SDA线上(高位先行),然后释放SCL,主机将在SCL高电平期间读取数据位,所以SCL高电平期间SDA不允许有数据变化,依次循环上述过程8次,即可接收一个字节(主机在接收之前,需要释放SDA)
(5)发送应答:主机在接收完一个字节之后,在下一个时钟发送一位数据,数据0表示应答,数据1表示非应答
(6)接收应答:主机在发送完一个字节之后,在下一个时钟接收一位数据,判断从机是否应答,数据0表示应答,数据1表示非应答(主机在接收之前,需要释放SDA)
8、I2C时序
(1)指定地址写
对于指定设备(Slave Address),在指定地址(Reg Address)下,写入指定数据(Data)
(2)当前地址读
对于指定设备(Slave Address),在当前地址指针指示的地址下,读取从机数据(Data)
(3)指定地址读
对于指定设备(Slave Address),在指定地址(Reg Address)下,读取从机数据(Data)
二、MPU6050陀螺仪加速度计
1、MPU6050是一个6轴姿态传感器,可以测量芯片自身X、Y、Z轴的加速度、角速度参数,通过数据融合,可进一步得到姿态角,常应用于平衡车、飞行器等需要检测自身姿态的场景
(1)3轴加速度计(Accelerometer):测量X、Y、Z轴的加速度
(2)3轴陀螺仪传感器(Gyroscope):测量X、Y、Z轴的角速度
2、MPU6050参数
(1)16位ADC采集传感器的模拟信号,量化范围:-32768~32767
(2)加速度计满量程选择:±2、±4、±8、±16(g)
(3)陀螺仪满量程选择: ±250、±500、±1000、±2000(°/sec)
(4)可配置的数字低通滤波器
(5)可配置的时钟源
(6)可配置的采样分频
(7)I2C从机地址:1101000(AD0=0)
1101001(AD0=1)
3、硬件电路
4、MPU6050框图
三、软件I2C读写MPU6050
1、按照以下接线方式连接,并将STLINK插到电脑上
2、多层模块架构
(1)最底层:I2C协议层
MyI2C.c
#include "stm32f10x.h" // Device header
#include "Delay.h"
#define SCL_PORT GPIOB
#define SCL_PIN GPIO_Pin_10
/*
写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)
{
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);
MyI2C_W_SCL(1);
MyI2C_W_SDA(0);
MyI2C_W_SCL(0);
}
/*
停止条件
*/
void MyI2C_Stop(void)
{
MyI2C_W_SDA(0);
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));//右移i位
MyI2C_W_SCL(1);
MyI2C_W_SCL(0);
}
}
/*
接收一个字节
*/
uint8_t MyI2C_Receivebyte(void)
{
uint8_t i, Byte = 0x00;
MyI2C_W_SDA(1);
for(i=0;i<8;i++)
{
MyI2C_W_SCL(1);
if(MyI2C_R_SDA() == 1){Byte |= (0x80>>i);}
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);
MyI2C_W_SCL(1);
AckBit = MyI2C_R_SDA();
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
(2)协议层之上:MPU6050的驱动层
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);
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);//配置电源管理寄存器1,解除睡眠,选择陀螺仪时钟
MPU6050_WriteReg(MPU6050_PWR_MGMT_2,0x00);//配置电源管理寄存器2,6个轴均不待机
MPU6050_WriteReg(MPU6050_SMPLRT_DIV,0x09);//配置采样率分频,10分频
MPU6050_WriteReg(MPU6050_CONFIG,0x06);//配置寄存器,滤波参数给最大
MPU6050_WriteReg(MPU6050_GYRO_CONFIG,0x18);//陀螺仪配置寄存器,选择最大量程
MPU6050_WriteReg(MPU6050_ACCEL_CONFIG,0x18);//加速度计配置寄存器,选择最大量程
}
/*
获取芯片ID号
*/
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;
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;
}
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);
void MPU6050_GetData(int16_t *AccX,int16_t *AccY,int16_t *AccZ,
int16_t *GyroX,int16_t *GyroY,int16_t *GyroZ);
uint8_t MPU6050_GetID(void);
#endif
(3)应用层:主函数
mian.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);
}
}
3、实现效果
软件I2C读写MPU6050
四、I2C外设简介
1、I2C外设简介
(1)STM32内部集成了硬件I2C收发电路,可以由硬件自动执行时钟生成、起始终止条件生成、应答位收发、数据收发等功能,减轻CPU的负担
(2)支持多主机模型
(3)支持7位/10位地址模式
(4)支持不同的通讯速度,标准速度(高达100 kHz),快速(高达400 kHz)
(5)支持DMA
(6)兼容SMBus(系统管理总线)协议
(7)STM32F103C8T6 硬件I2C资源:I2C1、I2C2
2、I2C外设框图
3、I2C基本结构
4、主机发送
5、主机接收
6、软件/硬件波形对比
五、硬件I2C读写MPU6050
1、I2C引脚
2、把MyI2C模块移出工程
(1)在选项卡对应的文件这里右键,关闭.c和.h文件
(2)在工程树对应的文件这里右键,把.c和.h文件移除
(3)把工程目录对应的两个文件也删掉,保证工程树和工程目录里的文件一致,有利于工程管理
3、I2C函数驱动模块
(1)I2C库函数的功能
(2)状态监控函数
(3)MPU6050.c
#include "stm32f10x.h" // Device header
#include "MPU6050_Reg.h"
#define MPU6050_ADDRESS 0XD0
/*
将CheckEvent封装成带有超时退出机制的WaitEvent函数
*/
void MPU6050_WaitEvent(I2C_TypeDef* I2Cx, uint32_t I2C_EVENT)
{
uint32_t Timeout;
Timeout = 10000;
while(I2C_CheckEvent(I2Cx,I2C_EVENT) != SUCCESS)//EV5:主机模式选择
{ //计次计时超时退出机制
Timeout--;
if(Timeout ==0)
{
break;
}
}
}
/*
指定地址写
*/
void MPU6050_WriteReg(uint8_t RegAddress, uint8_t Data)
{
I2C_GenerateSTART(I2C2,ENABLE);//起始条件
MPU6050_WaitEvent(I2C2,I2C_EVENT_MASTER_MODE_SELECT);//EV5:主机模式选择
I2C_Send7bitAddress(I2C2,MPU6050_ADDRESS,I2C_Direction_Transmitter);//发送:地址最低位清0,接收:地址最低位置1,发送数据自带接收应答过程
MPU6050_WaitEvent(I2C2,I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED);//EV6:主机发送模式已选择
I2C_SendData(I2C2,RegAddress);
MPU6050_WaitEvent(I2C2,I2C_EVENT_MASTER_BYTE_TRANSMITTING);//EV8: 主机字节正在发送
I2C_SendData(I2C2,Data);
MPU6050_WaitEvent(I2C2,I2C_EVENT_MASTER_BYTE_TRANSMITTED) ;//EV8_2: 主机字节已经发送完毕
I2C_GenerateSTOP(I2C2,ENABLE);//终止条件
}
/*
指定地址读
*/
uint8_t MPU6050_ReadReg(uint8_t RegAddress)
{
uint8_t Data;
I2C_GenerateSTART(I2C2,ENABLE);//起始条件
MPU6050_WaitEvent(I2C2,I2C_EVENT_MASTER_MODE_SELECT);//EV5:主机模式选择
I2C_Send7bitAddress(I2C2,MPU6050_ADDRESS,I2C_Direction_Transmitter);//发送:地址最低位清0,接收:地址最低位置1,发送数据自带接收应答过程
MPU6050_WaitEvent(I2C2,I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED);//EV6:主机发送模式已选择
I2C_SendData(I2C2,RegAddress);
MPU6050_WaitEvent(I2C2,I2C_EVENT_MASTER_BYTE_TRANSMITTED);//EV8_2: 主机字节已经发送完毕,等待字节发送完毕再重新开始
I2C_GenerateSTART(I2C2,ENABLE);//重新起始条件
MPU6050_WaitEvent(I2C2,I2C_EVENT_MASTER_MODE_SELECT);//EV5:主机模式选择
I2C_Send7bitAddress(I2C2,MPU6050_ADDRESS,I2C_Direction_Receiver);//接收:地址最低位置1,读
MPU6050_WaitEvent(I2C2,I2C_EVENT_MASTER_RECEIVER_MODE_SELECTED);//EV6:主机接收模式已选择
I2C_AcknowledgeConfig(I2C2,DISABLE);//不给应答
I2C_GenerateSTOP(I2C2,ENABLE);//申请产生终止条件
MPU6050_WaitEvent(I2C2,I2C_EVENT_MASTER_BYTE_RECEIVED);//EV7:主机字节已经接收完毕
Data = I2C_ReceiveData(I2C2);//读取DR的值放在Data里
I2C_AcknowledgeConfig(I2C2,ENABLE);//ACK置1,默认状态下ACK就是1,给丛机应答
return Data;
}
void MPU6050_Init(void)
{
RCC_APB1PeriphClockCmd(RCC_APB1Periph_I2C2,ENABLE); //开启时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE);
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);
I2C_InitTypeDef I2C_InitStructure; //I2C初始化
I2C_InitStructure.I2C_Mode=I2C_Mode_I2C;
I2C_InitStructure.I2C_ClockSpeed=50000;//50MHz 标准速度
I2C_InitStructure.I2C_DutyCycle=I2C_DutyCycle_2;//快速模式下的占空比,标准速度下占空比没有用
I2C_InitStructure.I2C_Ack=I2C_Ack_Enable;
I2C_InitStructure.I2C_AcknowledgedAddress=I2C_AcknowledgedAddress_7bit;
I2C_InitStructure.I2C_OwnAddress1=0x00;
I2C_Init(I2C2,&I2C_InitStructure);
I2C_Cmd(I2C2,ENABLE); //使能I2C2
MPU6050_WriteReg(MPU6050_PWR_MGMT_1,0x01);//配置电源管理寄存器1,解除睡眠,选择陀螺仪时钟
MPU6050_WriteReg(MPU6050_PWR_MGMT_2,0x00);//配置电源管理寄存器2,6个轴均不待机
MPU6050_WriteReg(MPU6050_SMPLRT_DIV,0x09);//配置采样率分频,10分频
MPU6050_WriteReg(MPU6050_CONFIG,0x06);//配置寄存器,滤波参数给最大
MPU6050_WriteReg(MPU6050_GYRO_CONFIG,0x18);//陀螺仪配置寄存器,选择最大量程
MPU6050_WriteReg(MPU6050_ACCEL_CONFIG,0x18);//加速度计配置寄存器,选择最大量程
}
/*
获取芯片ID号
*/
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;
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;
}
(4)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);
void MPU6050_GetData(int16_t *AccX,int16_t *AccY,int16_t *AccZ,
int16_t *GyroX,int16_t *GyroY,int16_t *GyroZ);
uint8_t MPU6050_GetID(void);
#endif
4、编写main.c代码和实现效果
硬件I2C读写MPU6050和软件I2C读写MPU6050的main.c代码和实现效果是相同的,不再重复展示,软件的I2C初始化是用MyI2C协议层进行编写,硬件不需要这一步,可以直接用库函数的结构体进行初始化,但是在读写数据时要注意while的死循环情况,要设置超时退出机制。