IIC(Inter—Integrated Circuit)总线是一种由PHILIPS公司开发的两线式串行总线,用于连接微控制器及其外围设备。在CPU与被控IC之间、IC与IC之间进行双向数据传送,高速IIC总线一般可达400kbps以上。
I2C通讯设备之间的常用连接方式:
I2C具有如下特点:
STM32的 I2C 外设可用作通讯的主机及从机,支持 7位、10位设备地址,支持 DMA数据传输,并具有数据校验功能。它的 I2C外设还支持 SMBus2.0协议,SMBus 协议与 I2C类似,主要应用于笔记本电脑的电池管理中,本手册不展开,感兴趣的读者可参《SMBus20》文档了解。
STM32F030的I2C架构剖析:
①I2C通讯引脚
I2C 的所有硬件架构都是根据图中右侧 SCL 线和 SDA 线展开的。STM32 芯片有多个 I2C 外设,它们的 I2C 通讯信号引出到不同的 GPIO 引脚上,使用时必须配置到这些指定的引脚。
②数据控制逻辑
I2C 的 SDA信号主要连接到数据移位寄存器上。当向外发送数据的时候,数据移位寄存器以“数据寄存器”为数据源,把数据一位一位地通过 SDA 信号线发送出去;当从外部接收数据的时候,数据移位寄存器把 SDA信号线采样到的数据一位一位地存储到“数据寄存器”中。若使能了数据校验,接收到的数据会经过 PCE计算器运算,运算结果存储在“PEC寄存器”中。当 STM32 的 I2C工作在从机模式的时候,接收到设备地址信号时,数据移位寄存器会把接收到的地址与 STM32的自身的“I2C 地址寄存器”的值作比较,以便响应主机的寻址。STM32 的自身 I2C地址可通过修改“自身地址寄存器”修改,支持同时使用两个 I2C设备地址,两个地址分别存储在 OAR1和 OAR2中。
③时钟控制逻辑
I2C由一个独立的时钟源进行计时,该时钟源允许I2C独立于PCLK频率工作。这个独立的时钟可以选为SYSCLK系统时钟。I2C的IO口支持20毫安输出电流驱动器快速模式操作,这是通过SCL和SDA引脚的驱动来实现的。在启用外设之前,必须在I2C_TIMINGR寄存器中配置SCLH和SCLL的I2C主时钟。
SDA和SCL时钟配置公式如下:
I2C分频后的时钟周期:tPRESC = (PRESC+1) * tI2CCLK
传输模式下,当SDA数据准备好,SCL上升沿到来的延时时间:tSCLDEL = (SCLDEL+1) * tPRESC
传输模式下,SCL下降沿到来之后,SDA数据发生改变的延时时间:tSDADEL = SDADEL x tPRESC
SCL高电平持续时间:tSCLH = (SCLH+1) x tPRESC
SCL低电平持续时间:tSCLL = (SCLL+1) x tPRESC
tI2CCLK为I2C的时钟周期,PRESC、SCLDEL、SDADEL、SCLH、SCLL可以通过I2C_TIMINGR寄存器进行配置。
通讯过程:
使用 I2C外设通讯时,在通讯的不同阶段它会对“状态寄存器(ISR)”的不同数据位写入参数,我们通过读取这些寄存器标志来了解通讯状态。
I2C主机发送数据流程图如下(以发送两个字节数据为例):
I2C主机发送数据流程:
I2C主机接收数据流程图如下(以接收两个字节数据为例):
I2C主机接收数据步骤:
bsp_hard_i2c.c程序如下:
#include "bsp_hard_i2c.h"
__IO uint32_t I2C2Timeout = I2C_LONG_TIMEOUT;
// 初始化IIC2
void I2C2_Hard_Init(void)
{
GPIO_InitTypeDef GPIO_InitStructure; // 定义GPIO结构体
I2C_InitTypeDef I2C_InitStruct;
RCC_AHBPeriphClockCmd(RCC_AHBPeriph_GPIOB, ENABLE); // 打开GPIOB口时钟
RCC_APB1PeriphClockCmd(RCC_APB1Periph_I2C2, ENABLE); // 使能I2C2时钟
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF; // 复用功能
GPIO_InitStructure.GPIO_OType = GPIO_OType_OD; // 开漏
GPIO_InitStructure.GPIO_Pin = Pin_SCL | Pin_SDA; // IIC对应IO口
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP; // 上拉
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_Level_3; // 50MHZ
GPIO_Init(GPIOB, &GPIO_InitStructure); // 初始化GPIO
GPIO_PinAFConfig(GPIOB, GPIO_PinSource10, GPIO_AF_1); // 将 PB10 映射为 I2C_SCL
GPIO_PinAFConfig(GPIOB, GPIO_PinSource11, GPIO_AF_1); // 将 PB11 映射为 I2C_SDA
SYSCFG_I2CFastModePlusConfig(SYSCFG_I2CFastModePlus_I2C1, ENABLE); // 配置I2C1快速模式(1MHz)和驱动能力
I2C_InitStruct.I2C_Ack = I2C_Ack_Enable; // I2C2应答使能
I2C_InitStruct.I2C_AcknowledgedAddress = I2C_AcknowledgedAddress_7bit; // 7位应答地址
I2C_InitStruct.I2C_AnalogFilter = I2C_AnalogFilter_Enable; // I2C模拟滤波使能
I2C_InitStruct.I2C_DigitalFilter = 0x00; // 数字滤波系数
I2C_InitStruct.I2C_Mode = I2C_Mode_I2C; // 工作模式:选择模式为I2C
I2C_InitStruct.I2C_OwnAddress1 = 0x00; // 设置I2C自身器件地址
I2C_InitStruct.I2C_Timing = 0x00700818; // 设置I2C时间寄存器的值 1M:0x00700818 400k:0x00901850;
I2C_Init(I2C2,&I2C_InitStruct);
I2C_Cmd(I2C2, ENABLE);
}
// 时间溢出返回函数
uint8_t I2C2_TIMEOUT_UserCallback()
{
return 1;
}
// 写多字节函数
uint8_t I2C2_Write_NByte(const uint8_t Slave_Address, const uint16_t REG_Address, const uint8_t* REG_data, const uint8_t len)
{
I2C2Timeout = I2C_LONG_TIMEOUT;
while(I2C_GetFlagStatus(I2C2,I2C_FLAG_BUSY) != RESET) // 设备忙等待
{
if((--I2C2Timeout) == 0) return I2C2_TIMEOUT_UserCallback();
}
// 配置从机设备地址 发送数据长度 发送模式 发送开始写信号
I2C_TransferHandling(I2C2, Slave_Address, RegAddressBitWide, I2C_Reload_Mode, I2C_Generate_Start_Write);
I2C2Timeout = I2C_LONG_TIMEOUT;
while(I2C_GetFlagStatus(I2C2,I2C_FLAG_TXIS) == RESET) // 等待发送状态复位
{
if((--I2C2Timeout) == 0) return I2C2_TIMEOUT_UserCallback();
}
// 如果寄存器为16位 发送寄存器高8位地址
if(RegAddressBitWide == Bit16)
{
I2C_SendData(I2C2, REG_Address >> 8);
I2C2Timeout = I2C_LONG_TIMEOUT;
while(I2C_GetFlagStatus(I2C2, I2C_FLAG_TXIS) == RESET) // 等待发送完成
{
if((--I2C2Timeout) == 0) return I2C2_TIMEOUT_UserCallback();
}
}
I2C_SendData(I2C2, REG_Address & 0xff); // 发送寄存器地址
I2C2Timeout = I2C_LONG_TIMEOUT;
while(I2C_GetFlagStatus(I2C2, I2C_FLAG_TCR) == RESET) // 等待Reload模式下,数据发送完成
{
if((--I2C2Timeout) == 0) return I2C2_TIMEOUT_UserCallback();
}
// 配置从机设备地址 发送数据长度 发送模式 发送开始写信号
I2C_TransferHandling(I2C2, Slave_Address, len, I2C_AutoEnd_Mode, I2C_No_StartStop);
uint8_t length = 0;
while(length != len)
{
I2C2Timeout = I2C_LONG_TIMEOUT;
while(I2C_GetFlagStatus(I2C2, I2C_ISR_TXIS) == RESET) // 等待发送完成
{
if((--I2C2Timeout) == 0) return I2C2_TIMEOUT_UserCallback();
}
I2C_SendData(I2C2, (uint8_t)(REG_data[length]));
length++;
}
I2C2Timeout = I2C_LONG_TIMEOUT;
while(I2C_GetFlagStatus(I2C2, I2C_ISR_STOPF) == RESET) // 等待AutoEnd模式下停止信号
{
if((--I2C2Timeout) == 0) return I2C2_TIMEOUT_UserCallback();
}
I2C_ClearFlag(I2C2, I2C_ICR_STOPCF);
return 0;
}
// 读多字节函数
uint8_t I2C2_Read_NByte(const uint8_t Slave_Address, const uint16_t REG_Address, uint8_t *REG_data, const uint8_t len)
{
I2C2Timeout = I2C_LONG_TIMEOUT;
while(I2C_GetFlagStatus(I2C2,I2C_FLAG_BUSY) != RESET) // 设备忙等待
{
if((--I2C2Timeout) == 0) return I2C2_TIMEOUT_UserCallback();
}
// 配置从机设备地址 发送数据长度 发送模式 发送开始写信号
I2C_TransferHandling(I2C2, Slave_Address, RegAddressBitWide, I2C_SoftEnd_Mode, I2C_Generate_Start_Write);
I2C2Timeout = I2C_LONG_TIMEOUT;
while(I2C_GetFlagStatus(I2C2,I2C_FLAG_TXIS) == RESET) // 等待发送状态复位
{
if((--I2C2Timeout) == 0) return I2C2_TIMEOUT_UserCallback();
}
// 如果寄存器为16位 发送寄存器高8位地址
if(RegAddressBitWide == Bit16)
{
I2C_SendData(I2C2, REG_Address >> 8);
I2C2Timeout = I2C_LONG_TIMEOUT;
while(I2C_GetFlagStatus(I2C2,I2C_FLAG_TXIS) == RESET) // 等待发送完成
{
if((--I2C2Timeout) == 0) return I2C2_TIMEOUT_UserCallback();
}
}
I2C_SendData(I2C2, REG_Address & 0xff); // 发送寄存器地址
I2C2Timeout = I2C_LONG_TIMEOUT;
while(I2C_GetFlagStatus(I2C2,I2C_ISR_TC) == RESET) // 等待软件停止模式(I2C_SoftEnd_Mode)下,数据发送完成
{
if((--I2C2Timeout) == 0) return I2C2_TIMEOUT_UserCallback();
}
// 配置从机设备地址 接收数据长度 发送模式 发送开始读信号
I2C_TransferHandling(I2C2, Slave_Address, len, I2C_AutoEnd_Mode, I2C_Generate_Start_Read);
uint8_t length = 0;
while( length != len)
{
I2C2Timeout = I2C_LONG_TIMEOUT;
while(I2C_GetFlagStatus(I2C2,I2C_ISR_RXNE) == RESET) // 等待接收寄存器非空
{
if((--I2C2Timeout) == 0) return I2C2_TIMEOUT_UserCallback();
}
REG_data[length] = I2C_ReceiveData(I2C2);
length++;
}
I2C2Timeout = I2C_LONG_TIMEOUT;
while(I2C_GetFlagStatus(I2C2, I2C_ISR_STOPF) == RESET) // 等待AutoEnd模式下停止信号
{
if((--I2C2Timeout) == 0) return I2C2_TIMEOUT_UserCallback();
}
I2C_ClearFlag(I2C2, I2C_ICR_STOPCF);
return 0;
}
bsp_hard_i2c.h程序如下:
#ifndef _BSP_HARD_I2C_H_
#define _BSP_HARD_I2C_H_
#include "stm32f0xx.h"
#include
// IO口设置
#define IIC_GPIOx GPIOB
#define Pin_SCL GPIO_Pin_10
#define Pin_SDA GPIO_Pin_11
#define I2C_TIMEOUT ((uint32_t)0x1000)
#define I2C_LONG_TIMEOUT ((uint32_t)(10 * I2C_TIMEOUT))
#define Bit16 2
#define Bit8 1
#define RegAddressBitWide Bit8 // 寄存器位宽 1:8位 2:16位
void I2C2_Hard_Init(void);
uint8_t I2C2_TIMEOUT_UserCallback(void);
uint8_t I2C2_Write_NByte(const uint8_t Slave_Address, const uint16_t REG_Address, const uint8_t* REG_data, const uint8_t len);
uint8_t I2C2_Read_NByte(const uint8_t Slave_Address, const uint16_t REG_Address, uint8_t *REG_data, const uint8_t len);
#endif