I2C编程
与GPIO和USART一样,I2C也是STM32F103C8T6 的一个片上外设。STM32F103C8T6 的 I2C 模块是一款兼容 I2C v2.1 规范的高性能通信接口,支持标准模式 (100kbps) 和快速模式 (400kbps),可配置为主机或从机模式,具备 7 位 / 10 位地址识别、PEC 错误检测、DMA 传输和中断驱动等功能。
I2C编程
I2C模块简介
与GPIO和USART一样,I2C也是STM32F103C8T6 的一个片上外设。STM32F103C8T6 的 I2C 模块是一款兼容 I2C v2.1 规范的高性能通信接口,支持标准模式 (100kbps) 和快速模式 (400kbps),可配置为主机或从机模式,具备 7 位 / 10 位地址识别、PEC 错误检测、DMA 传输和中断驱动等功能。该微控制器提供最多 2 个 I2C 接口,通过 PB6/PB7 或 PB8/PB9 (I2C1) 以及 PB10/PB11 (I2C2) 引脚实现,广泛用于连接传感器、显示器、存储器等外设;本节我们使用stm32作为主机,向从机OLED显示器发送数据
I2C模块框图

-
数据寄存器:包括发送数据寄存器和接收数据寄存器,作用是暂存要发送(接收)的数据
-
状态寄存器:I2C模块中共有两组状态寄存器,分别是SR1和SR2,图中状态寄存器的作用是:
-
SB : 起始位发送,指示主模式下起始条件已发送
-
ADDR: 地址发送 / 匹配,指示地址已发送 (主机) 或地址已匹配 (从机)
-
BTF: 字节传输完成,指示一个字节传输完成
-
RxNE: 接收数据寄存器非空,指示接收数据寄存器中有数据
-
TxE: 发送数据寄存器空, 指示发送数据寄存器为空
-
AF : 应答失败
-
BUSY:总线忙,指示 I2C 总线是否忙
-
-
控制寄存器:
-
ACK: 应答使能
-
STOP: 停止条件生成
-
START: 起始条件生成
-
在配置完I2C的占空比,波特率后,SCL控制器就会发送时间脉冲,确保 I2C 总线的数据传输时序正确和通信可靠
接线图

I2C初始化
引脚初始化
STM32F103C8T6有两个I2C资源,分别是I2C1、I2C2,其中I2C1的默认引脚是PB6(SCL),PB7(SDA),开启重映射后是PB8(SCL),PB9(SDA),I2C2的默认引脚是PB10(SCL),PB11(SDA),无重映射引脚,因为要实现逻辑线与的功能,所有引脚的模式都设置为复用开漏输出

//对I2C1重映射
RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO,ENABLE);
GPIO_PinRemapConfig(GPIO_Remap_I2C1,ENABLE);
//初始化GPIO引脚
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE);
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_8 | GPIO_Pin_9;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_OD;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_2MHz;
GPIO_Init(GPIOB,&GPIO_InitStructure);
I2C模块初始化
- 速度模式:stm32支持标准模式 (100kbps) 和快速模式 (400kbps)

- 占空比:只有在快速模式下才能设置占空比,我们使用默认占空比1/2

//使能I2C时钟
RCC_APB1PeriphClockCmd(RCC_APB1Periph_I2C1,ENABLE);
//重启I2C模块
RCC_APB1PeriphResetCmd(RCC_APB1Periph_I2C1,ENABLE);
RCC_APB1PeriphResetCmd(RCC_APB1Periph_I2C1,DISABLE);
//初始化I2C模块
I2C_InitTypeDef I2C_InitStructure;
I2C_InitStructure.I2C_ClockSpeed = 400000;
I2C_InitStructure.I2C_Mode = I2C_Mode_I2C;
I2C_InitStructure.I2C_DutyCycle = I2C_DutyCycle_2;
I2C_Init(I2C1,&I2C_InitStructure);
//闭合I2C1总开关
I2C_Cmd(I2C1,ENABLE);
将GPIO引脚初始化与I2C模块初始化封装成My_I2C_Init函数
void My_I2C_Init(){
//对I2C1重映射
RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO,ENABLE);
GPIO_PinRemapConfig(GPIO_Remap_I2C1,ENABLE);
//初始化GPIO引脚
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE);
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_8 | GPIO_Pin_9;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_OD;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_2MHz;
GPIO_Init(GPIOB,&GPIO_InitStructure);
//使能I2C时钟
RCC_APB1PeriphClockCmd(RCC_APB1Periph_I2C1,ENABLE);
//重启I2C模块
RCC_APB1PeriphResetCmd(RCC_APB1Periph_I2C1,ENABLE);
RCC_APB1PeriphResetCmd(RCC_APB1Periph_I2C1,DISABLE);
//初始化I2C模块
I2C_InitTypeDef I2C_InitStructure;
I2C_InitStructure.I2C_ClockSpeed = 400000;
I2C_InitStructure.I2C_Mode = I2C_Mode_I2C;
I2C_InitStructure.I2C_DutyCycle = I2C_DutyCycle_2;
I2C_Init(I2C1,&I2C_InitStructure);
//闭合I2C1总开关
I2C_Cmd(I2C1,ENABLE);
}
发送数据
发送数据的步骤如下:
- 等待总线空闲
- 发送起始位
- 发送地址
- 发送数据
- 发送停止位
int My_I2C_SendBytes(uint8_t* Addr,uint8_t* pData,uint16_t size){
//等待总线空闲
while(I2C_GetFlagStatus(I2C1,I2C_FLAG_BUSY) == SET);
//发送起始位
I2C_GenerateSTART(I2C1,ENABLE);
while(I2C_GetFlagStatus(I2C1,I2C_FLAG_SB) == RESET);
//寻址阶段
I2C_ClearFlag(I2C1,I2C_FLAG_AF);
I2C_SendData(I2C1,Addr&0XFE);
while(1)
{
if(I2C_GetFlagStatus(I2C1,I2C_FLAG_ADDR) == SET)
break;
if(I2C_GetFlagStatus(I2C1,I2C_FLAG_AF) == SET)
{
I2C_GenerateSTOP(I2C1,ENABLE);
return -1;
}
}
//清除ADDR(硬件规定)
I2C_ReadRegister(I2C1,I2C_Register_SR1);
I2C_ReadRegister(I2C1,I2C_Register_SR2);
//发送数据
for(uint16_t i=0;i<size;i++)
{
while (1)
{
if(I2C_GetFlagStatus(I2C1,I2C_FLAG_AF) == SET)
{
I2C_GenerateSTOP(I2C1,ENABLE);
return -2;
}
if(I2C_GetFlagStatus(I2C1,I2C_FLAG_TXE) == SET)
break;
}
I2C_SendData(I2C1,pData[i]);
}
while (1)
{
if(I2C_GetFlagStatus(I2C1,I2C_FLAG_AF) == SET)
{
I2C_GenerateSTOP(I2C1,ENABLE);
return -2;
}
if(I2C_GetFlagStatus(I2C1,I2C_FLAG_BTF) == SET)
break;
}
//发送停止位
I2C_GenerateSTOP(I2C1,ENABLE);
return 0;
}

现在我们向OLED显示屏发送命令把OLED点亮,OLED地址为0X78
int main()
{
My_I2C_Init();
uint8_t commands[] = {0X00,0X8D,0X14,0XAF,0XA5};
My_I2C_SendBytes(0X78,commands,5);
while(1)
{
}
}

接收数据
- 发送起始位和地址
- 发送ACK或NAK
- 发送停止位
对于读取数据个数的不同,我们做出以下分类:
需要注意的是,必须在等待RxNE之前就发送ACK和STOP,否则从机会来不及接收应答
int My_I2C_ReceiveBytes(uint8_t Addr,uint8_t* pBuffer,uint16_t size){
//发送起始位
I2C_GenerateSTART(I2C1,ENABLE);
while(I2C_GetFlagStatus(I2C1,I2C_FLAG_SB) == RESET);
//寻址
I2C_ClearFlag(I2C1,I2C_FLAG_AF);
I2C_SendData(I2C1,Addr|0x01);
while (1)
{
if(I2C_GetFlagStatus(I2C1,I2C_FLAG_AF) == SET)
{
//寻址失败
I2C_GenerateSTOP(I2C1,ENABLE);
return -1;
}
if(I2C_GetFlagStatus(I2C1,I2C_FLAG_ADDR) == SET)
break;
}
//清除ADDR(硬件规定)
I2C_ReadRegister(I2C1,I2C_Register_SR1);
I2C_ReadRegister(I2C1,I2C_Register_SR2);
if(size == 1)
{
//向ACK写0
I2C_AcknowledgeConfig(I2C1,DISABLE);
//写停止位
I2C_GenerateSTOP(I2C1,ENABLE);
//等待RxNE置位
while(I2C_GetFlagStatus(I2C1,I2C_FLAG_RXNE) == RESET);
*pBuffer[0] = I2C_ReceiveData(I2C1);
}
else if(size == 2)
{
//向ACK写1
I2C_AcknowledgeConfig(I2C1,ENABLE);
//等待RxNE置位
while(I2C_GetFlagStatus(I2C1,I2C_FLAG_RXNE) == RESET);
*pBuffer[0] = I2C_ReceiveData(I2C1);
//向ACK写0
I2C_AcknowledgeConfig(I2C1,DISABLE);
//写停止位
I2C_GenerateSTOP(I2C1,ENABLE);
//等待RxNE置位
while(I2C_GetFlagStatus(I2C1,I2C_FLAG_RXNE) == RESET);
*pBuffer[1] = I2C_ReceiveData(I2C1);
}
else
{
//向ACK写1
I2C_AcknowledgeConfig(I2C1,ENABLE);
//循环size-1次
for(uint16_t i=0;i<size-1;i++)
{
while(I2C_GetFlagStatus(I2C1,I2C_FLAG_RXNE) == RESET);
*pBuffer[i] = I2C_ReceiveData(I2C1);
}
//向ACK写0
I2C_AcknowledgeConfig(I2C1,DISABLE);
//写停止位
I2C_GenerateSTOP(I2C1,ENABLE);
//等待RxNE置位
while(I2C_GetFlagStatus(I2C1,I2C_FLAG_RXNE) == RESET);
*pBuffer[size-1] = I2C_ReceiveData(I2C1);
}
return 0;
}
更多推荐



所有评论(0)