????????I2C(Inter IC Bus)是由Philips公司开发的一种通用数据总线,它是两线式串行总线,它具有两根通信线:SCL(Serial Clock)、SDA(Serial Data),多用于主控制器和从器件间的主从通信,在小数据量场合使用,传输距离短,任意时刻只能有一个主机等特性。I2C是同步半双工的工作模式。
????????所有I2C设备的SCL连在一起,SDA连在一起,设备的SCL和SDA均要配置成开漏输出模式 SCL和SDA各添加一个上拉电阻,阻值一般为4.7KΩ左右
上图的CPU是该通信的主机
I2C硬件电路的特点:
(1)主机对SCL时钟总线具有绝对的控制权,并且再空闲状态下,主机可以发起对SDA的控制,只有在从机发送数据和从机应答的时候主机才会转交SDA的控制权给从机;
(2)所以设备都设置开漏输出模式(只会产生浮空和强下拉电路,为了防止电路处于浮空状态,所以加了上拉电阻,产生弱上拉),原因这样是为了防止两个设备同时处于输出状态且一个输出高电平一个输出低电平造成短路。这样会产生一个线与的状态,也就是只要有一个设备属于低电平,总线就处于低电平。
?(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)
对于指定设备(Slave Address),在指定地址(Reg Address)下,写入指定数据(Data)
起始条件——从机地址+读/写位(为0)——应答信号——要写入的寄存器地址——应答信号——写入的数据——终止条件
对于指定设备(Slave Address),在当前地址指针指示的地址下,读取从机数据(Data)
起始条件——从机地址+读/写位(为1)——应答信号——读取数据——应答信号——终止条件
对于指定设备(Slave Address),在指定地址(Reg Address)下,读取从机数据(Data),先进性写操作使寄存器指针指向要读的寄存器地址,在进行当前地址读操作
起始条件——从机地址+读/写位(为0)——应答信号——要写入的寄存器地址——应答信号——重复起始条件——从机地址+读/写位(为1)——应答信号——读取数据——应答信号——终止条件
#include "stm32f10x.h" // Device header
#include "Delay.h"
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_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_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));//取Byte各个位再发送,高位先行
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;
}
#include "stm32f10x.h" // Device header
#include "MyI2C.h"
#include "MPU6050Reg.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();//此时接收应答后,sda控制权就交给从机了,开始发送一个字节
Data = MyI2C_ReceiveByte();//读取从机发送的数据
MyI2C_SendAck(1);//返回給从机应答位
MyI2C_Stop();
return Data;
}
????????STM32内部集成了硬件I2C收发电路,可以由硬件自动执行时钟生成、起始终止条件生成、应答位收发、数据收发等功能,减轻CPU的负担,STM32F103C8T6 硬件I2C资源:I2C1、I2C2。
????????支持多主机模型,支持7位/10位地址模式,支持不同的通讯速度,标准速度(高达100 kHz),快速(高达400 kHz) ,支持DMA ,兼容SMBus协议
主机发送
主机接收
代码部分
#include "stm32f10x.h" // Device header
#include "MPU6050Reg.h"
#define MPU6050_ADDRESS 0xD0
void MPU6050_WriteReg(uint8_t RegAddress,uint8_t Data)//指定地址写一个字节
{
uint32_t Timeout;
Timeout = 10000;
I2C_GenerateSTART(I2C2,ENABLE);
while(I2C_CheckEvent(I2C2,I2C_EVENT_MASTER_MODE_SELECT) != SUCCESS) //超时退出
{
Timeout--;
if(Timeout)
{
break;
}
}
I2C_Send7bitAddress(I2C2,MPU6050_ADDRESS,I2C_Direction_Transmitter); //Transmitter地址最低位致0,为发送模式
while(I2C_CheckEvent(I2C2,I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED) != SUCCESS);
I2C_SendData(I2C2,RegAddress);
while(I2C_CheckEvent(I2C2,I2C_EVENT_MASTER_BYTE_TRANSMITTING) != SUCCESS);//指定从机地址
I2C_SendData(I2C2,Data);
while(I2C_CheckEvent(I2C2,I2C_EVENT_MASTER_BYTE_TRANSMITTED) != SUCCESS);//发送数据后不再继续发送
I2C_GenerateSTOP(I2C2,ENABLE);
}
uint8_t MPU6050_ReadReg(uint8_t RegAddress)//指定地址读一个字节
{
uint8_t Data;
I2C_GenerateSTART(I2C2,ENABLE);
while(I2C_CheckEvent(I2C2,I2C_EVENT_MASTER_MODE_SELECT) != SUCCESS);
I2C_Send7bitAddress(I2C2,MPU6050_ADDRESS,I2C_Direction_Transmitter); //Transmitter地址最低位致0,为发送模式
while(I2C_CheckEvent(I2C2,I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED) != SUCCESS);
I2C_SendData(I2C2,RegAddress);
while(I2C_CheckEvent(I2C2,I2C_EVENT_MASTER_BYTE_TRANSMITTED) != SUCCESS);//指定从机地址,注意标志位
I2C_GenerateSTART(I2C2,ENABLE);
while(I2C_CheckEvent(I2C2,I2C_EVENT_MASTER_MODE_SELECT) != SUCCESS);
I2C_Send7bitAddress(I2C2,MPU6050_ADDRESS,I2C_Direction_Receiver); //Receiver最低位致1,为接收模式
while(I2C_CheckEvent(I2C2,I2C_EVENT_MASTER_RECEIVER_MODE_SELECTED) != SUCCESS);
I2C_AcknowledgeConfig(I2C2,DISABLE);
I2C_GenerateSTOP(I2C2,ENABLE);//在接收单个字节时,我们要在EV6之后EV6_1之前将应答位置0,再设置停止位,在连续时就是EV7之后
while(I2C_CheckEvent(I2C2,I2C_EVENT_MASTER_BYTE_RECEIVED) != SUCCESS); //接受完一个字节后等待EV7
Data = I2C_ReceiveData(I2C2);
I2C_AcknowledgeConfig(I2C2,ENABLE);
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_InitStructure.I2C_Mode = I2C_Mode_I2C;
I2C_InitStructure.I2C_ClockSpeed = 50000; //设置不同的通讯速度最高400KHZ,100KHZ以下为标准速率
I2C_InitStructure.I2C_DutyCycle = I2C_DutyCycle_2; //配置时钟占空比,在标准模式下默认1:1,快速状态下低电平比高电平16:9
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);