一、前言
最近需要用到GD32的I2C通信,虽然是第一次做I2C通信,但是GD32完整的标准库有现存的I2C通信示例,虽然示例是EEPROM的通信,但是调用的函数应该是大差不差,所以上手比较简单,这里简单记录一下笔记,方便下次使用。
二、GD32与SD2068 的连接
SDA:GD32的I2C数据线(我选用的PB10)。
SCL:GD32的I2C时钟线(我选用的PB11)。
三、I2C初始化
宏定义和头文件:
#include "gd32e23x.h" //已经包含了gd32e23x_i2c.h
#define I2C_PERIPH I2C1
#define I2C_SPEED 100000
#define I2C_BUS_ADDRESS 0x32
#define I2C_RCUX RCU_I2C1
#define I2C_RCU_GPIOX RCU_GPIOB
#define I2C_SCL_PIN GPIO_PIN_10
#define I2C_SDA_PIN GPIO_PIN_11
1、GPIO初始化
void i2c_gpio_config(void)
{
/* 时钟初始化 */
rcu_periph_clock_enable(I2C_RCU_GPIOX);
/* I2C_SCL 引脚复用 */
gpio_af_set(GPIOB, GPIO_AF_1, I2C_SCL_PIN);
/* I2C_SDA 引脚复用 */
gpio_af_set(GPIOB, GPIO_AF_1, I2C_SDA_PIN);
/* 初始化GPIO复用功能模式 */
gpio_mode_set(GPIOB, GPIO_MODE_AF, GPIO_PUPD_PULLUP, I2C_SCL_PIN);
gpio_output_options_set(GPIOB, GPIO_OTYPE_OD, GPIO_OSPEED_50MHZ, I2C_SCL_PIN);
gpio_mode_set(GPIOB, GPIO_MODE_AF, GPIO_PUPD_PULLUP, I2C_SDA_PIN);
gpio_output_options_set(GPIOB, GPIO_OTYPE_OD, GPIO_OSPEED_50MHZ, I2C_SDA_PIN);
}
2、I2C功能初始化
void i2c_config(void)
{
// 启用 I2C 外设的时钟
rcu_periph_clock_enable(I2C_RCUX);
// 配置 I2C 的时钟参数:
// I2C_PERIPH:这里使用的I2C1
// I2C_SPEED:通信速率(单位为 Hz,常用为 100000 或 400000),这里使用100000
i2c_clock_config(I2C_PERIPH, I2C_SPEED, I2C_DTCY_2);
// 配置 I2C 工作模式和地址:
// I2C_ADDFORMAT_7BITS:使用 7 位地址模式
// I2C_BUS_ADDRESS:SD2068器件代码0110010 = 0x32
i2c_mode_addr_config(I2C_PERIPH, I2C_I2CMODE_ENABLE, I2C_ADDFORMAT_7BITS, I2C_BUS_ADDRESS);
// 启用 I2C 外设
i2c_enable(I2C_PERIPH);
// 使能 ACK 应答功能,确保在接收数据后自动发送 ACK
i2c_ack_config(I2C_PERIPH, I2C_ACK_ENABLE);
}
四、I2C写实现
主要过程就是:空闲->发送START 信号->发送设备、寄存器地址和方向->发送数据。封装函数如下:
void i2c_write_multi(uint8_t reg_addr, uint8_t *data, uint8_t dataSize)
{
// 等待 I2C 总线空闲,防止冲突
while(i2c_flag_get(I2C_PERIPH, I2C_FLAG_I2CBSY));
// 发送 START 信号,起始 I2C 通信
i2c_start_on_bus(I2C_PERIPH);
while(!i2c_flag_get(I2C_PERIPH, I2C_FLAG_SBSEND));
// 发送设备地址和写入方向(最低位0)
i2c_master_addressing(I2C_PERIPH, I2C_BUS_ADDRESS << 1, I2C_TRANSMITTER);
//i2c_data_transmit(I2C_PERIPH, I2C_BUS_ADDRESS << 1);
while(!i2c_flag_get(I2C_PERIPH, I2C_FLAG_ADDSEND));
// 清除地址发送标志位
i2c_flag_clear(I2C_PERIPH, I2C_FLAG_ADDSEND);
// 发送寄存器地址(子地址)
i2c_data_transmit(I2C_PERIPH, reg_addr);
while(!i2c_flag_get(I2C_PERIPH, I2C_FLAG_TBE));
// 开始循环写入多个数据字节
while(dataSize--) {
delay_1ms(5);
i2c_data_transmit(I2C_PERIPH, *data++);
while(!i2c_flag_get(I2C_PERIPH, I2C_FLAG_TBE));
}
// 发送 STOP 信号,结束通信
i2c_stop_on_bus(I2C_PERIPH);
while(I2C_CTL0(I2C_PERIPH) & I2C_CTL0_STOP);
}
五、I2C读实现
通信过程与写类似,直接上实现代码:
void i2c_read_multi(uint8_t reg_addr, uint8_t *data, uint8_t dataSize)
{
// 等待 I2C 总线空闲
while(i2c_flag_get(I2C_PERIPH, I2C_FLAG_I2CBSY));
// 发送起始信号
i2c_start_on_bus(I2C_PERIPH);
while(!i2c_flag_get(I2C_PERIPH, I2C_FLAG_SBSEND));
// 发送设备地址
i2c_master_addressing(I2C_PERIPH, I2C_BUS_ADDRESS << 1, I2C_TRANSMITTER);
while(!i2c_flag_get(I2C_PERIPH, I2C_FLAG_ADDSEND));
//清地址标志
i2c_flag_clear(I2C_PERIPH, I2C_FLAG_ADDSEND);
// 写入要读取的寄存器地址(子地址)
i2c_data_transmit(I2C_PERIPH, reg_addr);
while(!i2c_flag_get(I2C_PERIPH, I2C_FLAG_TBE));
while(!i2c_flag_get(I2C_PERIPH, I2C_FLAG_BTC));
// 第二次起始信号,重新启动为“读模式”
i2c_start_on_bus(I2C_PERIPH);
while(!i2c_flag_get(I2C_PERIPH, I2C_FLAG_SBSEND));
// 发送设备地址,读方向(1)
i2c_master_addressing(I2C_PERIPH, I2C_BUS_ADDRESS << 1, I2C_RECEIVER);
while(!i2c_flag_get(I2C_PERIPH, I2C_FLAG_ADDSEND));
i2c_flag_clear(I2C_PERIPH, I2C_FLAG_ADDSEND);
// 如果只读取1个字节,提前关闭ACK,准备发送STOP
if (dataSize == 1) {
i2c_ack_config(I2C_PERIPH, I2C_ACK_DISABLE);
i2c_stop_on_bus(I2C_PERIPH);
}
// 读取多个字节循环
for (uint8_t i = 0; i < dataSize; i++) {
delay_1ms(5);
if (i == dataSize - 2) {// 读取即将完成,关闭ACK,准备发送STOP
i2c_ack_config(I2C_PERIPH, I2C_ACK_DISABLE);
i2c_stop_on_bus(I2C_PERIPH);
}
while(!i2c_flag_get(I2C_PERIPH, I2C_FLAG_RBNE));
data[i] = i2c_data_receive(I2C_PERIPH);
}
// 等待 STOP 传输完毕
while(I2C_CTL0(I2C_PERIPH) & I2C_CTL0_STOP);
// 恢复 ACK 设置(便于下次通信)
i2c_ack_config(I2C_PERIPH, I2C_ACK_ENABLE);
i2c_ackpos_config(I2C_PERIPH, I2C_ACKPOS_CURRENT);
}