I2C模块内部结构
I2C(Inter-Integrated Circuit)模块是一种由Philips公司开发的二线式串行总线协议,用于短距离通信,允许多个设备共享相同的总线。
- 硬件连接简单:I2C通信仅需要两条总线,即SCL(时钟线)和SDA(数据线),大大简化了系统的硬件设计12。
- 支持多设备共享:在I2C总线中,可以挂载多个从设备,每个设备都有一个唯一的地址,主设备通过广播地址的方式与从设备进行通信25。
- 传输速率灵活:I2C总线传输模式具有向下兼容性,传输速率在标准模式下可达100kbps,快速模式下可达400kbps,高速模式下更是可达3.4Mbps34。
引脚初始化
引脚映射表
引脚实现代码
void My_I2C_Init(){
//对I2C进行重映射
RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO,ENABLE);
GPIO_PinRemapConfig(GPIO_Remap_I2C1,ENABLE);
//对PB8和PB9进行初始化
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE);
GPIO_InitTypeDef GPIO_InitStruct;
GPIO_InitStruct.GPIO_Pin = GPIO_Pin_8 | GPIO_Pin_9;
GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AF_OD;
GPIO_InitStruct.GPIO_Speed = GPIO_Speed_2MHz;
GPIO_Init(GPIOB,&GPIO_InitStruct)
}
连接电路
波特率
I2C的波特率指的是I2C总线上的数据传输速率,它可以根据不同的模式达到不同的速率。具体来说:
- 在标准模式下,I2C的波特率为100kHz12。
- 在快速模式下,I2C的波特率可以达到400kHz12。
- 还有一些更高速的模式,如**快速模式+**,波特率可以达到1MHz1。
I2C总线中的波特率由主机控制,主机通过产生SCL(时钟线)信号来分配给所有从机,因此主机可以通过控制时钟信号频率来调节波特率,即控制通信速度。这种灵活性使得I2C总线能够适应不同的通信需求和应用场景。
占空比
在I2C总线通信中,占空比是指数据线(SDA)上的高电平持续时间与整个时钟周期(由时钟线SCL控制)的比例。这个比例决定了数据传输的稳定性和可靠性12。
在没有明确的情况下我们选择2/1的占空比
初始化I2C模块代码
void My_I2C_Init(){
//对I2C进行重映射
RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO,ENABLE);
GPIO_PinRemapConfig(GPIO_Remap_I2C1,ENABLE);
//对PB8和PB9进行初始化
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE);
GPIO_InitTypeDef GPIO_InitStruct;
GPIO_InitStruct.GPIO_Pin = GPIO_Pin_8 | GPIO_Pin_9;
GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AF_OD;
GPIO_InitStruct.GPIO_Speed = GPIO_Speed_2MHz;
GPIO_Init(GPIOB,&GPIO_InitStruct);
RCC_APB1PeriphClockCmd(RCC_APB1Periph_CAN1,ENABLE);//开启I2C的时钟
RCC_APB1PeriphResetCmd(RCC_APB1Periph_CAN1,ENABLE);//施加复位信号
RCC_APB1PeriphResetCmd(RCC_APB1Periph_CAN1,DISABLE);//释放复位信号
I2C_InitTypeDef I2C_InitStruct;
I2C_InitStruct.I2C_ClockSpeed = 400000;//波特率400k
I2C_InitStruct.I2C_Mode = I2C_Mode_I2C;//标准的I2C
I2C_InitStruct.I2C_DutyCycle = I2C_DutyCycle_2;//占空比2:1
I2C_Init(I2C1,&I2C_InitStruct);
I2C_Cmd(I2C1,ENABLE);//闭合I2C1的总开关
}
写数据
数据发送的流程
主设备向从设备发送数据
- 发送起始信号:主设备在SCL(时钟线)为高电平时,将SDA(数据线)从高拉低,产生起始信号,通知所有从设备准备接收数据。
- 发送设备地址:主设备紧接着发送从设备的7位地址,以及一个写信号(通常是低电平),指示这是一个写操作。
- 等待从设备响应:从设备监测到自己的地址后,通过在下一个时钟周期拉低SDA线(发送ACK)来响应,确认它准备好了接收数据。
- 发送数据:主设备开始发送数据,每个字节数据后会跟着等待接收来自从设备的响应(ACK)。从设备在接收到每个字节后,都会发送一个ACK信号来确认。
- 发送停止信号:数据发送完毕后,主设备发送停止信号(SCL高时SDA从低变高),终止传输。
从设备向主设备发送数据
- 主设备初始化读取操作:主设备发送起始信号,然后发送从设备的地址以及一个读取位(通常是高电平),指示这是一个读取操作。
- 从设备响应:从设备监测到自己的地址后,通过发送ACK信号来响应。
- 主设备发送重复开始信号或停止信号:如果主设备计划在同一事务中连续读取多个从设备或进行连续读取,它可以发送重复开始信号来保持总线控制权。如果仅从当前从设备读取且读取操作即将结束,主设备在收到从设备的ACK后可直接发送停止信号。
- 从设备发送数据:在收到读取命令后,从设备开始发送数据。主设备在接收到每个字节后,都会发送一个ACK信号来确认。当接收到最后一个数据字节后,主设备可能会发送一个无效响应(NACK),然后发送停止信号来终止传输。
等待总线空闲
发送数据前要监控总线是否繁忙,从BUSY标志位来判断总线是否空闲,I2C_GetFlagStatus函数用来获取BUSY标志。I2C_GetFlagStatus
函数是一个在 STM32 微控制器的 I2C(Inter-Integrated Circuit)库函数中常用的函数,用于检查 I2C 接口的状态标志。这个函数通常用于轮询(polling)方式,以确定 I2C 总线上的特定事件或状态是否已经发生,例如数据传输完成、接收到起始信号、检测到错误等。
FlagStatus I2C_GetFlagStatus(I2C_TypeDef* I2Cx, uint32_t I2C_FLAG);
I2Cx
:指向要检查的 I2C 接口的指针。例如,对于 STM32F103 系列,可能是I2C1
或I2C2
。I2C_FLAG
:要检查的特定 I2C 状态标志。这些标志在 STM32 的 I2C 库头文件中定义,通常是以I2C_FLAG_
开头的宏。- 返回值是
FlagStatus
枚举类型,它通常有两个可能的值:SET
(标志已设置)和RESET
(标志未设置)。
发送起始位
发送起始位是向START寄存器内写数值1,使用函数I2C_GenerateStart完成。
I2C_GenerateStart
函数是用于生成 I2C 通信起始条件(START condition)的函数。在 I2C 通信中,起始条件是一个重要的信号,用于通知所有连接到总线的设备即将开始数据传输。当 NewState
参数为 ENABLE
时,I2C_GenerateStart
函数会设置相应的寄存器位,从而在 I2C 总线上生成一个起始条件。起始条件是一个在 SCL(时钟线)为高电平时,SDA(数据线)由高电平变为低电平的边沿。这个边沿会被所有连接到总线的 I2C 设备检测到,并通知它们即将开始数据传输。
void I2C_GenerateSTART(I2C_TypeDef* I2Cx, FunctionalState NewState);
I2Cx
:指向要操作的 I2C 接口的指针。在 STM32 微控制器中,这通常是I2C1
、I2C2
等。NewState
:这是一个FunctionalState
枚举类型的值,用于指定是否生成起始条件。它可以是ENABLE
(生成起始条件)或DISABLE
(不生成起始条件)。
在发送起止位后我们需要确定起止位是否发送完毕,我们通过SB标志来判断。
while(I2C_GetFlagStatus(I2Cx,I2C_FLAG_SB) == RESET);
发送地址
AF标志位是ACK应答标志位,当AF为1时ACK答应失败未收到答应,ADDR寻址成功标志位,当寻址成功值为1,失败值为0。在发送地址前我们需要清理AF标志位的值然后发送地址。
I2C_ClearFlag(I2Cx,I2C_FLAG_AF);//清除AF
I2C_SendData(I2Cx,Addr & 0xfe);//发送地址和RW
在发送的过程中需要持续判断AF和ADDR标识符的状态
while(1){
if(I2C_GetFlagStatus(I2Cx,I2C_FLAG_ADDR) == SET){
break;
}
if(I2C_GetFlagStatus(I2Cx,I2C_FLAG_AF) == SET){
I2C_GenerateSTOP(I2Cx,ENABLE);
return -1;
}
}
后续我们继续清除ADDR状态标示符
I2C_ReadRegister(I2Cx,I2C_Register_SR1);
I2C_ReadRegister(I2Cx,I2C_Register_SR2);
发送数据
发送数据过程中我们要持续监控ACK和发送数据寄存器的状态,AF为1标示为响应ACK,停止发送数据,BTF负责监控发送数据寄存器内是否有数据,保证在其空的情况下推送数据进入。
发送停止位
代码
int main(){
My_I2C_Init();
uint8_t commands[] = {0x00,0x8d,0x14,0xaf,0xa5};
My_I2C_SendBytes(I2C1,0x78,commands,5);
}
void My_I2C_Init(){
//对I2C进行重映射
RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO,ENABLE);
GPIO_PinRemapConfig(GPIO_Remap_I2C1,ENABLE);
//对PB8和PB9进行初始化
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE);
GPIO_InitTypeDef GPIO_InitStruct;
GPIO_InitStruct.GPIO_Pin = GPIO_Pin_8 | GPIO_Pin_9;
GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AF_OD;
GPIO_InitStruct.GPIO_Speed = GPIO_Speed_2MHz;
GPIO_Init(GPIOB,&GPIO_InitStruct);
RCC_APB1PeriphClockCmd(RCC_APB1Periph_I2C1,ENABLE);//开启I2C的时钟
RCC_APB1PeriphResetCmd(RCC_APB1Periph_I2C1,ENABLE);//施加复位信号
RCC_APB1PeriphResetCmd(RCC_APB1Periph_I2C1,DISABLE);//释放复位信号
I2C_InitTypeDef I2C_InitStruct;
I2C_InitStruct.I2C_ClockSpeed = 400000;//波特率400k
I2C_InitStruct.I2C_Mode = I2C_Mode_I2C;//标准的I2C
I2C_InitStruct.I2C_DutyCycle = I2C_DutyCycle_2;//占空比2:1
I2C_Init(I2C1,&I2C_InitStruct);
I2C_Cmd(I2C1,ENABLE);//闭合I2C1的总开关
}
int My_I2C_SendBytes(I2C_TypeDef *I2Cx,uint8_t Addr,uint8_t *pData,uint16_t Size){
//等待总线空闲
while(I2C_GetFlagStatus(I2Cx,I2C_FLAG_BUSY) == SET){}
//发送起止位
I2C_GenerateSTART(I2C1,ENABLE);
//确定起止位是否发送完毕
while(I2C_GetFlagStatus(I2Cx,I2C_FLAG_SB) == RESET){}
//发送地址
//清除AF
I2C_ClearFlag(I2Cx,I2C_FLAG_AF);
I2C_SendData(I2Cx,Addr & 0xfe);
while(1){
if(I2C_GetFlagStatus(I2Cx,I2C_FLAG_ADDR) == SET){
break;
}
if(I2C_GetFlagStatus(I2Cx,I2C_FLAG_AF) == SET){
I2C_GenerateSTOP(I2Cx,ENABLE);
return -1;//寻址失败
}
}
//清除ADDR
I2C_ReadRegister(I2Cx,I2C_Register_SR1);
I2C_ReadRegister(I2Cx,I2C_Register_SR2);
//发送数据
for(uint8_t i = 0;i<Size;i++){
while(1){
if(I2C_GetFlagStatus(I2Cx,I2C_FLAG_AF) == SET){
I2C_GenerateSTOP(I2Cx,ENABLE);
return -2;
}
if(I2C_GetFlagStatus(I2Cx,I2C_FLAG_TXE) == SET){
break;
}
}
I2C_SendData(I2Cx,pData[i]);
}
while(1){
if(I2C_GetFlagStatus(I2Cx,I2C_FLAG_AF) == SET){
I2C_GenerateSTOP(I2Cx,ENABLE);
return -2;
}
if(I2C_GetFlagStatus(I2Cx,I2C_FLAG_BTF) == SET){
break;
}
}
//发送停止位
I2C_GenerateSTOP(I2Cx,ENABLE);
return 0;
}