概述:Inter Integrated Circuit,一组多从 多组多从 有应答
是一种同步(具有时钟线需要同步时钟SCL)、串行(一位一位的往一个方向发送)、半双工(发送接收存在一种)通信总线。
(1)硬件电路 所有I2C设备的SCL连接在一起,SDA连在一起
设备的SCL和SDA均要配置成开漏输出模式
SCL跟SDA各添加一个上拉电阻,阻值一般为4.7K欧姆左右
(2)I2C时序基本单元
起始条件;SCL高电平期间,SDA从高电平切换到低电平
终止条件:SCL高电平期间,SDA从低电平切换到高电平
发送一个字节:SCL低电平期间,主机将数据位依次放到SDA线上(高位先行),然后释放SCL,从机将在SCL高电平期间读取数据位,所以SCL高电平期间SDA不允许有数据变化,依次循环上述过程8次,即可发送一个字节
接收一个字节:SCL低电平期间,从机将数据位依次放到SDA线上(高位先行),然后释放SCL,主机将在SCL高电平期间读取数据位,所以SCL高电平期间SDA不允许有数据变化,依次循环上述过程8次,即可接收一个字节(主机在接收之前,需要释放SDA)
发送应答:主机在接收完一个字节之后,在下一个时钟发送一位数据,数据0表示应答,数据1表示非应答
接收应答:主机在发送完一个字节之后,在下一个时钟接收一位数据,判断从机是否应答,数据0表示应答,数据1表示非应答(主机在接收之前,需要释放SDA)
(3)IIC 的基本的读写通讯过程
主机首先在 IIC 总线上发送起始信号,那么这时总线上的从机都会等待接收由主机发出的 数据。主机接着发送从机地址+0(写操作)组成的 8bit 数据,所有从机接收到该 8bit 数据后,行检验是否是自己的设备的地址,假如是自己的设备地址,那么从机就会发出应答信号。主机 在总线上接收到有应答信号后,才能继续向从机发送数据。注意:IIC 总线上传送的数据信号是广义的,既包括地址信号,又包括真正的数据信号
主机向从机读取数据的操作,一开始的操作与写操作有点相似,观察两个图也可以发现,
都是由主机发出起始信号,接着发送从机地址+1(读操作)组成的 8bit 数据,从机接收到数据验证是否是自身的地址。那么在验证是自己的设备地址后,从机就会发出应答信号,并向主机返回 8bit 数据,发送完之后从机就会等待主机的应答信号。假如主机一直返回应答信号,那么从机可以一直发送数据,也就是图中的(n byte + 应答信号)情况,直到主机发出非应答信号,从机才会停止发送数据。
(4)软件读取I2C 的代码程序部分(主要是如何写时序复现时序的代码)
void MyI2C_W_SCL(uint8_t BitValue)//写SCL时钟电平
{
GPIO_WriteBit(GPIOB, GPIO_Pin_10, (BitAction)BitValue);
Delay_us(10);
}
void MyI2C_W_SDA(uint8_t BitValue)//写SDA数据电平
{
GPIO_WriteBit(GPIOB, GPIO_Pin_11, (BitAction)BitValue);
Delay_us(10);
}
uint8_t MyI2C_R_SDA(void)//读SDA数据电平
{
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);一定要先拉高电平
}
//在SCL为低电平时 SDA可以改变电平状态 也就是 SCL拉低时 可写
//在SCL为高电平时 SDA可以不可以改变电平状态 也就是 SCL拉高时 可读
void MyI2C_Start(void)//时序开始;SDA要由高到低,SCL拉高 拉低为了可以改变数据SDA
{
MyI2C_W_SDA(1);
MyI2C_W_SCL(1);
MyI2C_W_SDA(0);
MyI2C_W_SCL(0);
}
void MyI2C_Stop(void)//时序结束;SDA由低到高,SCL拉高
{
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));//由于开始的时候把SCL拉低了 所以可以直接写
MyI2C_W_SCL(1);//由于写完了一位 SCL立马拉高电平保证数据稳定写入
MyI2C_W_SCL(0);//写入后 SCL变回低电平 利于循环遍历 方便下次改变SDA
}
}
uint8_t MyI2C_ReceiveByte(void)
{
uint8_t i, Byte = 0x00;
MyI2C_W_SDA(1);//设置I2C总线上的SDA线为高电平,准备接收数据。
for (i = 0; i < 8; i ++)
{
MyI2C_W_SCL(1);//SCL线为高电平,表示开始读取一个位的数据
if (MyI2C_R_SDA() == 1){Byte |= (0x80 >> i);}
//条件语句判断SDA线的状态,如果为高电平,则将对应的位(根据循环次数)置为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);//准备接收数据
MyI2C_W_SCL(1);//开始接收数据
AckBit = MyI2C_R_SDA();
MyI2C_W_SCL(0);//结束数据接收
return AckBit;
}
以下为I2C读取MPU6050
#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为读 0为写
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);
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);//用来开启工作状态
}
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;
}
以下为硬件读取I2C部分
配置步骤 (主要是使用引脚本身的IIC相关功能 而不像软件模拟通过IO方式)
1.开启RCC外设时钟 开启GPIO以及I2C外设
2.初始化GPIO 配置为复用开漏
3.配置I2C初始化结构体
4.开启I2C