1、I2C通信协议
1.1 概述
I2C(Inter IC Bus)是由Philips公司开发的一种通用数据总线。共包含两根通信线:SCL(Serial Clock)、SDA(Serial Data)。属于同步通信,支持半双工。带数据应答。支持总线挂载多设备可以支持一主多从和多住多从模式。本文主要介绍一主多从,读写一个字节的情况。
1.2 硬件电路
所有I2C设备的SCL连在一起,SDA连在一起。

设备的SCL和SDA均要配置成开漏输出模式。各添加一个上拉电阻,阻值一般为4.7KΩ左右。当设备输出低电平时,总线上的电位被拉低。输出高电平时,设备的IO口处于高组态,总线上的电位被外部电路上拉至高电位。
开漏输出只有驱动低电平的能力,无法驱动高电平。这能够有效避免总线上一个设备输出高电平,一个输出低电平时导致短路的情况。

1.3 I2C时序
主机对SCL有绝对的掌控权,可以决定是否释放SDA。谁发送数据,谁占用SDA。
1.3.1 I2C时序基本单元
空闲状态:在I2C总线没有数据收发的情况下,SCL和SDA都处于高电位(由外接的上拉电阻驱动,IO口输出1,在开漏模式下位高阻态)。
起始条件:在SCL高电平持续期间,SDA产生下降沿(高电位到低电位的跳变)
结束条件:在SCL高电平持续期间,SDA产生上升沿(低电位到高电位的跳变)

发送一个字节:SCL低电平,主机通过SDA发送一位数据,SCL高电平,从机读取一位数据。循环八次。高位先行。不允许在SCL高电平期间去改变SDA的电位。
接收一个字节:SCL低电平,从机通过SDA发送一位数据,SCL高电平,主机读取一位数据。循环八次。高位先行。不允许在SCL高电平期间去改变SDA的电位。(主机接收数据前,需要释放SDA的控制权)

应答的接收和发送:数据接收方需要在接收到每个字节数据后发送应答位。数据0表示应答,数据1表示非应答。谁接收数据,谁发送应答位。

1.3.1 I2C时序
(1)指定地址写
对于指定设备(Slave Address),在指定地址(Reg Address)下,写入指定数据(Data)

①主机发送起始信号
②主机发送指定设备的地址码(7位)+ 写(0),主机释放SDA
③从机发送应答位0,主机接收应答
④主机发送Slave的指定地址(Reg Address),主机释放SDA
⑤从机发送应答位0,主机接收应答
⑥主机发送数据(Data),主机释放SDA
⑦从机发送应答位0,主机接收应答
…(可以循环执行⑥⑦)
⑧结束通信,主机发送停止信号。
(2)当前地址读

①主机发送起始信号
②主机发送指定设备的地址码(7位)+ 读(1),主机释放SDA
③从机发送应答位0,主机接收应答,
④从机占用SDA,发送数据(Data),从机机释放SDA
⑤主机发送应答位1,发送停止信号
(3)指定地址读
将上面两个时序组合起来,先发送指定地址写,来将当前地址设置为指定位置。

①主机发送起始信号
②主机发送指定设备的地址码(7位)+ 写(0),主机释放SDA
③从机发送应答位0,主机接收应答
④主机发送Slave的指定地址(Reg Address),主机释放SDA
⑤从机发送应答位0,主机接收应答
⑥主机发送停止信号后,发送重新开始信号
⑦主机发送指定设备的地址码(7位)+ 读(1),主机释放SDA
⑧从机发送应答位0,主机接收应答,
⑩从机占用SDA,发送数据(Data),从机机释放SDA
⑪主机发送应答位1,发送停止信号
2、软件模拟
为了实现I2C通信,可以使用软件模拟的方式。这也是实际中常用的一种方式。
#include "MyI2C.h"
#include "Delay.h"
// SCL:PB6, SDA:PB7,软件模拟可以选择任意的GPIO端口
/*
功能:对SCL的写操作
参数:BitValue(uint8_t):发送的位数据,0/1
返回值:无
*/
void MyI2C_W_SCL(uint8_t BitValue)
{
GPIO_WriteBit(GPIOB, GPIO_Pin_6, (BitAction)BitValue);
Delay_us(10);
}
/*
功能:对SDA的写操作
参数:BitValue(uint8_t):发送的位数据,0/1
返回值:无
*/
void MyI2C_W_SDA(uint8_t BitValue)
{
GPIO_WriteBit(GPIOB, GPIO_Pin_7, (BitAction)BitValue);
Delay_us(10);
}
/*
功能:对SDA的读操作
参数:无
返回值:BitValue(uint8_t):接收的位数据,0/1
*/
// 主机对SDA的读操作
uint8_t MyI2C_R_SDA(void)
{
uint8_t BitValue;
BitValue = GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_7);
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_6 | GPIO_Pin_7;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOF, &GPIO_InitStructure);
GPIO_SetBits(GPIOB, GPIO_Pin_6 | GPIO_Pin_7);
}
// 发送起始信号
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);
}
/*
功能:发送一个字节的数据
参数:Byte(uint8_t):发送的字节
返回值:无
*/
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);
MyI2C_W_SCL(0);
}
}
/*
功能:接收一个字节的数据
参数:无
返回值:Byte(uint8_t):接收的字节
*/
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;
}
/*
功能:主机发送应答位
参数:应答位(uint8_t):0表示应答,1表示非应答
返回值:无
*/
void MyI2C_SendAck(uint8_t AckBit)
{
MyI2C_W_SDA(AckBit);
MyI2C_W_SCL(1);
MyI2C_W_SCL(0);
}
/*
功能:主机接收应答位
参数:无
返回值:应答位(uint8_t)
*/
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;
}
以MPU6050为例,使用I2C通信实现其驱动。
#include "MPU.h"
#define MPU_ADDRESS 0xD0
/*
功能:将数据写入到MPU指定地址
参数: RegAddress(uint8_t):寄存器地址;
Data(uint8_t):要写入的数据
返回值:无
*/
void MPU_WriteReg(uint8_t RegAddress, uint8_t Data)
{
MyI2C_Start();
MyI2C_SendByte(MPU_ADDRESS);
MyI2C_ReceiveAck();
MyI2C_SendByte(RegAddress);
MyI2C_ReceiveAck();
MyI2C_SendByte(Data);
MyI2C_ReceiveAck();
MyI2C_Stop();
}
/*
功能: 读取MPU指定地址的数据
参数: RegAddress(uint8_t):寄存器地址;
返回值:无
*/
uint8_t MPU_ReadReg(uint8_t RegAddress)
{
uint8_t Data;
MyI2C_Start();
MyI2C_SendByte(MPU_ADDRESS);
MyI2C_ReceiveAck();
MyI2C_SendByte(RegAddress);
MyI2C_ReceiveAck();
MyI2C_Start();
MyI2C_SendByte(MPU_ADDRESS | 0x01);
MyI2C_ReceiveAck();
Data = MyI2C_ReceiveByte();
MyI2C_ReceiveAck();
MyI2C_SendAck(1);
MyI2C_Stop();
return Data;
}
/*
功能: 初始化,配置MPU6050
参数: 无
返回值:无
*/
void MPU_Init(void)
{
MyI2C_Init();
MPU_WriteReg(MPU6050_PWR_MGMT_1, 0x01);
MPU_WriteReg(MPU6050_PWR_MGMT_2, 0x00);
MPU_WriteReg(MPU6050_SMPLRT_DIV, 0x00);
MPU_WriteReg(MPU6050_CONFIG, 0x06);
MPU_WriteReg(MPU6050_GYRO_CONFIG, 0x18);
MPU_WriteReg(MPU6050_ACCEL_CONFIG, 0x18);
}
/*
功能: 读取MPU6050的ID
参数: 无
返回值:MPU6050_WHO_AM_I(uint8_t)寄存器中的值
*/
uint8_t MPU_GetID(void)
{
return MPU_ReadReg(MPU6050_WHO_AM_I);
}
/*
功能: 读取MPU6050的X,Y,Z轴方向的加速度和角速度
参数: 用于接收数据的指针
返回值:
*/
void MPU_GetData(int16_t *AccX, int16_t *AccY, int16_t *AccZ,
int16_t *GyroX, int16_t *GyroY, int16_t *GyroZ)
{
uint16_t DataH, DataL;
DataH = MPU_ReadReg(MPU6050_ACCEL_XOUT_H);
DataL = MPU_ReadReg(MPU6050_ACCEL_XOUT_L);
*AccX = (DataH << 8) | DataL;
DataH = MPU_ReadReg(MPU6050_ACCEL_YOUT_H);
DataL = MPU_ReadReg(MPU6050_ACCEL_YOUT_L);
*AccY = (DataH << 8) | DataL;
DataH = MPU_ReadReg(MPU6050_ACCEL_ZOUT_H);
DataL = MPU_ReadReg(MPU6050_ACCEL_ZOUT_L);
*AccZ = (DataH << 8) | DataL;
DataH = MPU_ReadReg(MPU6050_GYRO_XOUT_H);
DataL = MPU_ReadReg(MPU6050_GYRO_XOUT_L);
*GyroX = (DataH << 8) | DataL;
DataH = MPU_ReadReg(MPU6050_GYRO_YOUT_H);
DataL = MPU_ReadReg(MPU6050_GYRO_YOUT_L);
*GyroY = (DataH << 8) | DataL;
DataH = MPU_ReadReg(MPU6050_GYRO_ZOUT_H);
DataL = MPU_ReadReg(MPU6050_GYRO_ZOUT_L);
*GyroZ = (DataH << 8) | DataL;
}
3、I2C外设
根据数据手册上的流程图,调用相应的库函数。


#include "MPU.h"
#define MPU_ADDRESS 0xD0
void MPU_WaitEvent(I2C_TypeDef * I2Cx, uint32_t I2C_EVENT)
{
uint32_t Timeout;
Timeout = 10000;
while( I2C_CheckEvent(I2Cx, I2C_EVENT) != SUCCESS)
{
Timeout -- ;
if (Timeout == 0)
{
break;
}
}
}
void MPU_WriteReg(uint8_t RegAddress, uint8_t Data)
{
I2C_GenerateSTART(I2C1, ENABLE);
MPU_WaitEvent(I2C1, I2C_EVENT_MASTER_MODE_SELECT);
I2C_Send7bitAddress(I2C1, MPU_ADDRESS, I2C_Direction_Transmitter);
MPU_WaitEvent(I2C1, I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED);
I2C_SendData(I2C1, RegAddress);
MPU_WaitEvent(I2C1, I2C_EVENT_MASTER_BYTE_TRANSMITTING);
I2C_SendData(I2C1, Data);
MPU_WaitEvent(I2C1, I2C_EVENT_MASTER_BYTE_TRANSMITTED);
I2C_GenerateSTOP(I2C1, ENABLE);
}
uint8_t MPU_ReadReg(uint8_t RegAddress)
{
uint8_t Data;
I2C_GenerateSTART(I2C1, ENABLE);
MPU_WaitEvent(I2C1, I2C_EVENT_MASTER_MODE_SELECT);
I2C_Send7bitAddress(I2C1, MPU_ADDRESS, I2C_Direction_Transmitter);
MPU_WaitEvent(I2C1, I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED);
I2C_SendData(I2C1, RegAddress);
MPU_WaitEvent(I2C1, I2C_EVENT_MASTER_BYTE_TRANSMITTED);
I2C_GenerateSTART(I2C1, ENABLE);
MPU_WaitEvent(I2C1, I2C_EVENT_MASTER_MODE_SELECT);
I2C_Send7bitAddress(I2C1, MPU_ADDRESS, I2C_Direction_Receiver);
MPU_WaitEvent(I2C1, I2C_EVENT_MASTER_RECEIVER_MODE_SELECTED);
I2C_AcknowledgeConfig(I2C1, DISABLE);
I2C_GenerateSTOP(I2C1, ENABLE);
MPU_WaitEvent(I2C1, I2C_EVENT_MASTER_BYTE_RECEIVED);
Data = I2C_ReceiveData(I2C1);
I2C_AcknowledgeConfig(I2C1, ENABLE);
return Data;
}
void MPU_Init(void)
{
RCC_APB1PeriphClockCmd(RCC_APB1Periph_I2C1, 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_6 | GPIO_Pin_7;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOB, &GPIO_InitStructure);
I2C_InitTypeDef I2C_InitStructure;
I2C_InitStructure.I2C_Mode = I2C_Mode_I2C;
I2C_InitStructure.I2C_ClockSpeed = 50000;
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(I2C1, &I2C_InitStructure);
I2C_Cmd(I2C1, ENABLE);
MPU_WriteReg(MPU6050_PWR_MGMT_1, 0x01);
MPU_WriteReg(MPU6050_PWR_MGMT_2, 0x00);
MPU_WriteReg(MPU6050_SMPLRT_DIV, 0x00);
MPU_WriteReg(MPU6050_CONFIG, 0x06);
MPU_WriteReg(MPU6050_GYRO_CONFIG, 0x18);
MPU_WriteReg(MPU6050_ACCEL_CONFIG, 0x18);
}
uint8_t MPU_GetID(void)
{
return MPU_ReadReg(MPU6050_WHO_AM_I);
}
void MPU_GetData(int16_t *AccX, int16_t *AccY, int16_t *AccZ,
int16_t *GyroX, int16_t *GyroY, int16_t *GyroZ)
{
uint16_t DataH, DataL;
DataH = MPU_ReadReg(MPU6050_ACCEL_XOUT_H);
DataL = MPU_ReadReg(MPU6050_ACCEL_XOUT_L);
*AccX = (DataH << 8) | DataL;
DataH = MPU_ReadReg(MPU6050_ACCEL_YOUT_H);
DataL = MPU_ReadReg(MPU6050_ACCEL_YOUT_L);
*AccY = (DataH << 8) | DataL;
DataH = MPU_ReadReg(MPU6050_ACCEL_ZOUT_H);
DataL = MPU_ReadReg(MPU6050_ACCEL_ZOUT_L);
*AccZ = (DataH << 8) | DataL;
DataH = MPU_ReadReg(MPU6050_GYRO_XOUT_H);
DataL = MPU_ReadReg(MPU6050_GYRO_XOUT_L);
*GyroX = (DataH << 8) | DataL;
DataH = MPU_ReadReg(MPU6050_GYRO_YOUT_H);
DataL = MPU_ReadReg(MPU6050_GYRO_YOUT_L);
*GyroY = (DataH << 8) | DataL;
DataH = MPU_ReadReg(MPU6050_GYRO_ZOUT_H);
DataL = MPU_ReadReg(MPU6050_GYRO_ZOUT_L);
*GyroZ = (DataH << 8) | DataL;
}

被折叠的 条评论
为什么被折叠?



