I2C通信:
1.使能:
使用stm32C8T6中的I2C2
第一步,时钟使能,GPIO使能
由图时钟APB1可知,I2C1和2均挂载在时钟1上,
而GPIO挂载在时钟APB2上,所以这里我们就需要对两个时钟相应的位进行配置,使得I2C和GPIO能够工作。首先我们来使能I2C2:由上述第一张图可知,要使得I2C2工作,我们需要配置APB1时钟的22位,具体代码如下:
RCC->APB1ENR |= RCC_APB1ENR_I2C2EN;
那么,这段代码在单片机中的运行逻辑是什么呢?请看下面这段话:
寄存器分组与指针
想象一下,单片机的内存就像一个巨大的数组,不同的功能(如GPIO控制、时钟配置等)被分配到了这个数组的不同部分。每个这样的“部分”可以看作是一个组,比如RCC组。RCC组有一个起始地址,即基地址0x40021000
,它就像是这个组的入口点或起点。
使用指针操作寄存器
当您想要操作RCC组内的某个特定寄存器时,您可以将其视为通过基地址加上偏移量来定位到具体的元素(寄存器)。这与C语言中使用指针访问数组元素非常相似。
假设我们想操作RCC_APB1ENR寄存器,其相对于RCC基地址的偏移量为0x1C
,如下图。
那么我们想要定位到该寄存器所在位置,只需要基地址 + 偏移地址即可。进一步,我们需要访问该寄存器中使能I2C2的具体的位,由上所述我们知道在22位,且知道默认地址位0x00000000.进一步阅读手册,如图:
该位置1表示I2C2时钟开启,所以便有了以下代码:(其中对于GPIOB的使能在APB2上挂载,方法亦同)
RCC->APB1ENR |= RCC_APB1ENR_I2C2EN;
/*其中RCC_APB1ENR_I2C2EN在寄存器库中定义为 ((uint32_t)0x00400000) 转为2进制数,正好位22为1,进行或运算,使得APB1 寄存器22位置1,实现I2C2使能*/
- 类比说明
- 基地址 (
0x40021000
) 就像是一本书的目录页,告诉你各个章节(功能模块)的开始位置。 - 偏移量 (
0x1C
) 是从该章节开始到你感兴趣的段落(特定寄存器)的距离。
——————————————————————————————————————————————————————
2.功能框图(各个寄存器之间是如何协同工作的)
————————————————————————————————————————————————————————
I²C功能框图的工作流程可分为以下几个关键步骤:
1. 通信基础:SDA与SCL控制
- **SDA(数据线)和SCL(时钟线)**是I²C总线的物理层信号。
- 时钟控制模块管理SCL的时钟频率(由时钟控制寄存器CCR配置),确保主机与从机的同步。
- 数据控制模块通过SDA线管理数据的发送与接收,包括起始条件、停止条件和数据位的时序。
2. 地址匹配与数据收发
- 自身地址寄存器和双地址寄存器存储从机设备的地址(可配置单/双地址模式)。
- 当主设备发送地址帧时,比较器将接收到的地址与寄存器中的地址匹配。若匹配成功,从机响应ACK并进入通信状态。
- 数据寄存器(DATA REGISTER)临时存储待发送或接收的完整字节数据,数据移位寄存器负责将数据逐位移至SDA线(发送)或从SDA线读取(接收)。
3. 错误校验与协议支持
- 帧错误校验(PEC)计算模块对传输数据生成校验值(如CRC),确保数据完整性。
- PEC寄存器存储校验值,供发送或接收时对比。若校验失败,会触发错误标志(通过状态寄存器SR1/SR2反映)。
4. 控制与状态管理
- **控制寄存器(CR1/CR2)**配置I²C模式(主机/从机)、时钟速度、中断使能等。
- **状态寄存器(SR1/SR2)**实时反映通信状态(如传输完成、地址匹配成功、总线错误等)。
- 控制逻辑电路根据寄存器的配置和状态标志协调各模块工作,例如触发中断或DMA请求。
5. 中断与DMA
- 中断模块在关键事件(如数据接收完成、地址匹配、校验错误)时触发中断,通知CPU处理。
- DMA请求与响应模块支持直接内存访问,用于高效传输连续数据块,减少CPU开销。
6. SMBus扩展功能
- SMBALERT信号(可选)用于SMBus的报警机制。若禁用SMBus模式,该功能不可用。
- SMBus模式下,还需遵循额外的协议规则(如超时检测、PEC强制校验等)。
完整工作流程总结
- 主机初始化:通过CR1/CR2配置为主机模式,设置时钟频率(CCR)。
- 发送起始条件:控制逻辑拉低SDA和SCL,启动通信。
- 地址匹配:主机发送目标地址,从机的比较器检查匹配,返回ACK。
- 数据传输:
- 发送时,数据从数据寄存器经移位寄存器逐位输出。
- 接收时,移位寄存器逐位读取SDA数据,存入数据寄存器。
- 错误校验:PEC模块计算校验值,与接收端对比,校验结果更新至状态寄存器。
- 结束通信:主机发送停止条件,状态寄存器更新传输完成标志,触发中断或DMA完成信号。
关键模块协作
- 时钟控制确保SCL与数据同步。
- 控制逻辑协调地址匹配、数据收发、校验和中断/DMA流程。
- 状态寄存器提供实时反馈,供软件查询或触发后续操作。
——————————————————————————————————————————————————————
3. I2C时序(图片取自江科大课件ppt)
————————————————————————————————————————————————————————
I²C成功通信的完整流程可分为以下几个关键步骤,需严格遵循时序和协议规则:
1. 总线初始化
- 硬件准备:确保SDA和SCL线均通过上拉电阻连接到电源(典型值:4.7kΩ),保持总线空闲时高电平。
- 主设备配置:
- 通过**控制寄存器(CR1/CR2)**设置为主机模式。
- 配置**时钟控制寄存器(CCR)**定义SCL频率(标准模式100kHz,快速模式400kHz)。
- 从设备配置:
- 将自身地址写入自身地址寄存器(支持7位或10位地址模式)。
- 使能中断或DMA(若需异步处理数据)。
2. 启动通信
- 起始条件:主设备拉低SDA线(保持SCL高电平),标志通信开始。
- 总线仲裁:多主机场景下,需检测总线冲突(SDA与预期输出不一致时放弃控制权)。
3. 发送地址帧
- 地址格式:发送7位/10位从机地址 + 1位读写方向(0:写,1:读)。
- 地址匹配:
- 从机通过比较器检测地址是否与自身地址匹配。
- 匹配成功后,从机拉低SDA(发送ACK信号)确认响应。
- 无ACK处理:若未收到ACK,主设备需重试或终止通信(超时机制)。
4. 数据传输
- 数据格式:每个字节(8位)后跟随1位ACK/NACK。
- 发送数据(主机→从机):
- 主机将数据写入数据寄存器。
- 数据移位寄存器逐位将数据移至SDA线,同步SCL时钟。
- 从机读取完8位后,发送ACK(拉低SDA)确认接收成功。
- 接收数据(主机←从机):
- 主机释放SDA线(设为高电平),切换为接收模式。
- 从机控制SDA线逐位发送数据,主机通过数据移位寄存器读取。
- 主机在第9个时钟周期发送ACK(继续接收)或NACK(终止接收)。
5. 错误校验与恢复
- PEC校验(可选):若启用帧错误校验,发送方在数据末尾附加PEC值(如CRC8),接收方验证校验值。
- 状态监控:通过**状态寄存器(SR1/SR2)**检测总线错误(如仲裁丢失、NACK、超时等)。
- 错误处理:复位总线、重发数据或触发中断报警。
6. 终止通信
- 停止条件:主设备在SCL高电平时拉高SDA线,标志通信结束。
- 总线释放:SDA和SCL恢复高电平,总线进入空闲状态。
关键成功条件
- 严格时序:起始/停止条件、数据位切换必须在SCL低电平时完成。
- ACK响应:每字节传输必须由接收方确认(ACK),否则视为失败。
- 地址唯一性:总线上每个从机地址必须唯一,避免冲突。
通信流程
- 主机写数据到从机
START → 发送从机地址(写模式) → ACK → 发送数据字节 → ACK → … → STOP - 主机从从机读取数据
START → 发送从机地址(读模式) → ACK → 接收数据字节 → 发送ACK/NACK → … → STOP
4. 详细代码
以下是代码配置的详细理由及每一步的作用分析:
1. 开启时钟
RCC->APB1ENR |= RCC_APB1ENR_I2C2EN; // 使能I2C2时钟
RCC->APB2ENR |= RCC_APB2ENR_IOPBEN; // 使能GPIOB时钟
- 理由:
- STM32外设需要时钟信号才能操作寄存器。I2C2挂载在APB1总线上,GPIOB挂载在APB2总线上。
- 若未开启时钟,无法配置I2C或GPIO的寄存器,外设将无法工作。
2. 配置GPIO引脚模式
// 清除CNF位后,配置为复用开漏
GPIOB->CRH &= ~(GPIO_CRH_CNF10 | GPIO_CRH_CNF11);
GPIOB->CRH |= (0b10 << 22) | (0b10 << 26); // CNF=10(复用开漏)
- 目标:将PB10(SCL)和PB11(SDA)设置为复用开漏模式。
- 理由:
- 复用功能:I2C需要专用硬件控制SDA和SCL,而非普通GPIO输出。
- 开漏输出(Open-Drain):
- 支持I²C总线的“线与”逻辑,避免多个设备同时驱动总线时电平冲突。
- 允许总线通过外部上拉电阻拉高,确保空闲时SDA和SCL为高电平。
3. 禁用SMBus模式
I2C2->CR1 &= ~I2C_CR1_SMBUS; // 选择标准I²C模式
- 理由:
- SMBus(System Management Bus)是I²C的子集,强制要求超时检测、特定电气特性等。
- 若无需SMBus功能,禁用后可使用更通用的I²C协议,减少协议限制。
4. 设置APB1时钟频率
I2C2->CR2 |= 36 << 0; // APB1时钟配置为36MHz
- 理由:
CR2[5:0]
(FREQ字段)需填入APB1总线实际频率(单位MHz)。- 后续计算CCR和TRISE值时依赖此参数,确保SCL频率和上升时间准确。
5. 选择标准模式(100kHz)
I2C2->CCR &= ~I2C_CCR_FS; // 标准模式(FS=0)
- 理由:
- 标准模式:SCL频率为100kHz,适用于大多数低速外设(如EEPROM、传感器)。
- **快速模式(400kHz)**需设置
FS=1
,但对总线电容和走线长度要求更高。
6. 配置SCL时钟频率
I2C2->CCR |= 180 << 0; // CCR=180(标准模式)
- 公式:
- 标准模式下,
CCR = APB1_CLK / (2 * SCL_FREQ)
。 - 计算:
36MHz / (2 * 100kHz) = 180
。
- 标准模式下,
- 作用:
- 定义SCL的占空比(高电平和低电平时间),确保时序符合I²C规范。
7. 设置SCL上升时间
I2C2->TRISE |= 37; // TRISE=37(标准模式)
- 公式:
TRISE = (Max_SCL_Rise_Time / APB1_Period) + 1
。- 计算:
- APB1周期 = 1/36MHz ≈ 27.78ns。
- 标准模式最大上升时间要求1μs(1000ns)。
1000ns / 27.78ns ≈ 36 → TRISE = 36 + 1 = 37
。
- 作用:
- 控制SCL信号的上升斜率,防止因信号边沿过缓导致时序错误。
8. 使能I2C外设
I2C2->CR1 |= I2C_CR1_PE; // 使能I2C2
- 理由:
- STM32外设需在配置完成后显式使能,否则无法工作。
- 使能后,I2C硬件开始响应总线事件(如起始条件、地址匹配)。
关键配置总结
配置项 | 目标 | 硬件要求 |
---|---|---|
时钟使能 | 激活I2C和GPIO功能 | 必须开启时钟才能操作寄存器 |
GPIO复用开漏 | 支持总线线与逻辑,避免冲突 | 外部需接上拉电阻(通常4.7kΩ) |
标准I²C模式 | 遵循I²C协议,避免SMBus限制 | 无需SMBus功能时适用 |
APB1时钟配置 | 确保CCR和TRISE计算准确 | 实际APB1频率需与代码一致 |
标准模式(100kHz) | 兼容低速设备,降低总线电容影响 | 从设备需支持100kHz |
CCR=180 | 生成100kHz的SCL时钟 | 公式依赖APB1频率和模式选择 |
TRISE=37 | 满足标准模式的最大上升时间要求(1μs) | 防止信号边沿过缓 |
使能I2C外设 | 启动I2C硬件功能 | 必须最后一步执行 |
总结
上述配置通过精确设置时钟、GPIO模式、通信速率和时序参数,确保I2C总线在标准模式下稳定工作。每一步均基于I²C协议规范及STM32硬件特性设计,最终实现可靠的主从设备通信。
完整代码如下:
void Dri_I2C2_Init(void) {
// 1. 开启时钟
RCC->APB1ENR |= RCC_APB1ENR_I2C2EN; // I2C2时钟
RCC->APB2ENR |= RCC_APB2ENR_IOPBEN; // GPIOB时钟
// 2. 配置GPIOB10(SCL)和PB11(SDA)为复用开漏
// 清除MODE和CNF位
GPIOB->CRH &= ~(GPIO_CRH_MODE10 | GPIO_CRH_MODE11 | GPIO_CRH_CNF10 | GPIO_CRH_CNF11);
// 设置MODE=11(50MHz输出速度),CNF=10(复用开漏)
GPIOB->CRH |= (0b11 << 20) | (0b11 << 24); // MODE10和MODE11
GPIOB->CRH |= (0b10 << 22) | (0b10 << 26); // CNF10和CNF11
// 3. 初始化I2C2配置
I2C2->CR1 &= ~I2C_CR1_SMBUS; // 禁用SMBus
I2C2->CR2 = 36; // APB1时钟36MHz
I2C2->CCR = 180; // 标准模式100kHz
I2C2->TRISE = 37; // 上升时间配置
I2C2->CR1 |= I2C_CR1_PE; // 使能I2C
}