一、引言
在嵌入式系统设计中,I2C总线因其简单的两线制结构和多主从架构而广受欢迎。GD32E230作为兆易创新推出的Cortex-M23内核微控制器,其I2C外设提供了强大而灵活的从机功能支持。本文将深入剖析GD32E230的I2C从机工作机制,并提供完整的软件实现方案。
IIC通讯协议详解:https://blog.csdn.net/weixin_43386810/article/details/127176416
二、GD32E230 I2C从机特性详解
2.1 硬件架构概述
GD32E230的I2C控制器完全兼容I2C总线标准协议,支持:
标准模式(100kbps)和快速模式(400kbps)
7位和10位地址寻址
时钟同步与延长功能
双地址匹配检测
丰富的可配置中断源
2.2 从机工作模式关键特性
作为从机设备时,GD32E230的I2C模块具备以下重要特性:
地址匹配机制:
支持7位和10位地址格式
cppi2c_mode_addr_config(I2C0, I2C_I2CMODE_ENABLE, I2C_ADDFORMAT_7BITS, 0x22);
使用函数i2c_mode_addr_config即可完成,当主设备发送的地址与预设的从机地址匹配时,硬件自动产生应答并置位地址匹配标志。**数据缓冲区管理 :**I2C模块包含独立的发送和接收缓冲区,支持中断或DMA方式的数据传输,有效减轻CPU负担。
**时钟同步控制 :**从机设备能够通过保持SCL线低电平来延长传输周期,为数据处理提供充足时间。
三、硬件设计要点
3.1 GPIO配置规范
正确的GPIO配置是I2C通信稳定的基础:
引脚复用配置
SCL和SDA必须配置为开漏输出模式
使能内部上拉电阻或外接上拉电阻
选择合适的GPIO复用功能映射
电气特性考虑
上拉电阻值通常选择4.7kΩ(标准模式)或2.2kΩ(快速模式)
总线电容控制在400pF以内以保证信号完整性
3.2 电源与接地设计
确保I2C总线设备共地
避免电源噪声干扰通信质量
在高速模式下考虑电源去耦设计
四、软件架构设计
4.1 初始化流程设计
完整的I2C从机初始化包含以下步骤:
时钟使能
使能GPIO端口时钟
使能I2C外设时钟
GPIO配置
设置引脚为复用开漏模式
配置合适的输出速度
映射到正确的复用功能
I2C参数配置
设置从机地址和地址格式
配置时钟参数(即使作为从机也需配置)
使能ACK应答
4.2 中断驱动架构
采用中断驱动的设计能够提高系统响应效率:
事件中断处理
地址匹配中断:处理主设备的寻址
接收缓冲区非空中断:读取接收数据
发送缓冲区空中断:准备发送数据
错误中断处理
总线错误检测与恢复
仲裁丢失处理
超时错误管理
五、核心代码实现解析
5.1 基础配置模块
头文件
cpp
#ifndef _GD32_I2C_SLAVE_H
#define _GD32_I2C_SLAVE_H
/* Includes ------------------------------------------------------------------*/
#include "gd32e230.h"
#define MSG_RECV_BYTE_SUM 6
#define MSG_SEND_BYTE_SUM 5
#define I2C0_OWN_ADDRESS7 0x44 //w:0x44 r:0x45 7bit:0x22
typedef union
{
struct
{
uint8_t DirState : 1; // 数据方向状态(0:接收,1:发送)
uint8_t RecSuccess : 1; // 接收成功标志
uint8_t SendSuccess : 1; // 发送成功标志
} Bits;
uint8_t Byte;
} I2C_FlagTypeDef;
typedef struct
{
uint8_t RecBuff[I2C_SLAVE_REC_MAX_SIZE]; // 接收缓冲区
uint8_t RecCount; // 接收计数
uint8_t SendAddr; // 发送地址
uint8_t SendSize; // 发送数据大小
uint8_t SendCount; // 发送计数
I2C_FlagTypeDef uFlag; // I2C标志
} I2C_SlaveTypeDef;
void iic_slave_config(void);
I2C_SlaveTypeDef tI2cSlave;
uint8_t iic_internal_recv_buffer[3];
uint8_t iic_internal_send_buffer[3];
#endif //_GD32_I2C_SLAVE_H
GPIO初始化实现
cpp
void iic_slave_gpio_config(void)
{
/* enable GPIOB clock */
rcu_periph_clock_enable(RCU_GPIOB);
/* connect PB6 to I2C0_SCL */
gpio_af_set(GPIOB, GPIO_AF_1, GPIO_PIN_6);
/* connect PB7 to I2C0_SDA */
gpio_af_set(GPIOB, GPIO_AF_1, GPIO_PIN_7);
/* configure GPIO pins of I2C0 */
gpio_mode_set(GPIOB, GPIO_MODE_AF, GPIO_PUPD_PULLUP, GPIO_PIN_6);
gpio_output_options_set(GPIOB, GPIO_OTYPE_OD, GPIO_OSPEED_50MHZ, GPIO_PIN_6);
gpio_mode_set(GPIOB, GPIO_MODE_AF, GPIO_PUPD_PULLUP, GPIO_PIN_7);
gpio_output_options_set(GPIOB, GPIO_OTYPE_OD, GPIO_OSPEED_50MHZ, GPIO_PIN_7);
}
I2C从机配置实现
cpp
void iic_slave_config(void)
{
/* enable I2C0 clock */
rcu_periph_clock_enable(RCU_I2C0);
/* I2C clock configure */
i2c_clock_config(I2C0, 400000, I2C_DTCY_2);
/* I2C address configure */
i2c_mode_addr_config(I2C0, I2C_I2CMODE_ENABLE, I2C_ADDFORMAT_7BITS, I2C0_OWN_ADDRESS7);
/* enable I2C0 */
i2c_enable(I2C0);
/* enable acknowledge */
i2c_ack_config(I2C0, I2C_ACK_ENABLE);
nvic_irq_enable(I2C0_EV_IRQn, 23);
nvic_irq_enable(I2C0_ER_IRQn, 32);
/* enable the I2C0 interrupt */
i2c_interrupt_enable(I2C0, I2C_INT_ERR);
i2c_interrupt_enable(I2C0, I2C_INT_EV);
i2c_interrupt_enable(I2C0, I2C_INT_BUF);
}
5.2 中断服务例程设计
事件中断处理函数
cpp
void I2C0_EV_IRQHandler(void)
{
if (i2c_interrupt_flag_get(I2C0, I2C_INT_FLAG_ADDSEND)) // 地址
{
// RESET:接收端
// SET: 发送端
if (i2c_flag_get(I2C0, I2C_FLAG_TR) == RESET)
{
// 清空接收buf
for (uint8_t i = 0 ; i < RECV_BYTE_SIZE ; i++)
{
i2c_rxbuffer[i] = 0;
}
}
else
{
/* clear I2C_TDATA register */
i2c_flag_clear(I2C0, I2C_FLAG_TBE);
// 给发送buf进行赋值
i2c_txbuffer[0] = 0xFF;
}
iic_recv_buf_count = 0;
msg_iic_send_buf_count = 0;
/* clear the ADDSEND bit */
i2c_interrupt_flag_clear(I2C0, I2C_INT_FLAG_ADDSEND);
}
else if (i2c_interrupt_flag_get(I2C0, I2C_INT_FLAG_RBNE)) // 接收
{
if (iic_recv_buf_count < RECV_BYTE_SIZE)
{
i2c_rxbuffer[iic_recv_buf_count] = i2c_data_receive(I2C0);
if (iic_recv_buf_count == (RECV_BYTE_SIZE - 1))
{
//接收完毕 这里可以进行标志位置位后通过其他函数进行处理
}
++iic_recv_buf_count;
}
else
{
i2c_data_receive(I2C0);
}
}
else if ((i2c_interrupt_flag_get(I2C0, I2C_INT_FLAG_TBE)) && (!i2c_interrupt_flag_get(I2C0, I2C_INT_FLAG_AERR))) // 发送
{
/* send a data byte */
if (iic_send_buf_count < MSG_SEND_BYTE_SUM)
{
/* if reception data register is not empty, I2C0 will read a data from I2C_RDATA */
i2c_data_transmit(I2C0, i2c_txbuffer[iic_send_buf_count]);
iic_send_buf_count++;
}
else
{
i2c_data_transmit(I2C0, 0xFF);
}
}
else if (i2c_interrupt_flag_get(I2C0, I2C_INT_FLAG_STPDET)) //停止
{
if (tI2cSlave.uFlag.Bits.DirState == RESET)
{
tI2cSlave.uFlag.Bits.RecSuccess = SUCCESS;
iic_recv_buf_count = 0;
}
else
{
tI2cSlave.uFlag.Bits.SendSuccess = SUCCESS;
tI2cSlave.SendSize = 0 ; // 避免没有接收到命令重新发数据
iic_send_buf_count = 0;
}
i2c_enable(I2C0);
}
else if (i2c_interrupt_flag_get(I2C0, I2C_INT_FLAG_AERR))
{
/* clear STPDET interrupt flag */
i2c_interrupt_flag_clear(I2C0, I2C_INT_FLAG_AERR);
tI2cSlave.uFlag.Bits.SendSuccess = SUCCESS;
}
}
错误中断处理函数
cpp
void I2C0_ER_IRQHandler(void)
{
/* no acknowledge received */
if (i2c_interrupt_flag_get(I2C0, I2C_INT_FLAG_AERR))
{
i2c_interrupt_flag_clear(I2C0, I2C_INT_FLAG_AERR);
}
/* SMBus alert */
if (i2c_interrupt_flag_get(I2C0, I2C_INT_FLAG_SMBALT))
{
i2c_interrupt_flag_clear(I2C0, I2C_INT_FLAG_SMBALT);
}
/* bus timeout in SMBus mode */
if (i2c_interrupt_flag_get(I2C0, I2C_INT_FLAG_SMBTO))
{
i2c_interrupt_flag_clear(I2C0, I2C_INT_FLAG_SMBTO);
}
/* over-run or under-run when SCL stretch is disabled */
if (i2c_interrupt_flag_get(I2C0, I2C_INT_FLAG_OUERR))
{
i2c_interrupt_flag_clear(I2C0, I2C_INT_FLAG_OUERR);
}
/* arbitration lost */
if (i2c_interrupt_flag_get(I2C0, I2C_INT_FLAG_LOSTARB))
{
i2c_interrupt_flag_clear(I2C0, I2C_INT_FLAG_LOSTARB);
}
/* bus error */
if (i2c_interrupt_flag_get(I2C0, I2C_INT_FLAG_BERR))
{
i2c_interrupt_flag_clear(I2C0, I2C_INT_FLAG_BERR);
}
/* CRC value doesn't match */
if (i2c_interrupt_flag_get(I2C0, I2C_INT_FLAG_PECERR))
{
i2c_interrupt_flag_clear(I2C0, I2C_INT_FLAG_PECERR);
}
tI2cSlave.RecCount = 0; // 计数值清0 从头开始接收数据
tI2cSlave.uFlag.Bits.RecSuccess = ERROR;
tI2cSlave.SendSize = 0; // 避免没有接收到命令重新发数据
tI2cSlave.SendCount = 0; // 计数值清零
tI2cSlave.uFlag.Bits.SendSuccess = ERROR;
iic_send_buf_count = 0;
iic_recv_buf_count = 0;
}
六、高级功能与优化策略
6.1 双地址匹配功能
GD32E230支持同时响应两个不同的从机地址,极大提高了系统设计的灵活性:
cpp// 配置双地址匹配 i2c_dual_address_enable(I2C0, I2C_DUADEN_ENABLE); i2c_address_config(I2C0, I2C_SLAVE_ADDRESS1, I2C_SLAVE_ADDRESS2);6.2 DMA集成方案
对于大数据量传输场景,建议使用DMA与I2C集成:
cpp// 配置I2C DMA i2c_dma_config(I2C0, I2C_DMA_ON); i2c_dma_last_transfer_config(I2C0, I2C_DMALST_ON);6.3 低功耗优化
在电池供电应用中,可采用以下功耗优化策略:
合理配置I2C时钟分频
在空闲时段进入睡眠模式
使用时钟延长功能配合低功耗设计