- 学习目标
- 学习内容
- define SCL_RCU RCU_GPIOB
- define SCL_PORT GPIOB
- define SCL_PIN GPIO_PIN_6
- define SCL_AF GPIO_AF_4
- define SDA_RCU RCU_GPIOB
- define SDA_PORT GPIOB
- define SDA_PIN GPIO_PIN_7
- define SDA_AF GPIO_AF_4
- define SCL(BIT) gpio_bit_write(SCL_PORT, SCL_PIN, BIT?SET:RESET)
- define SDA(BIT) gpio_bit_write(SDA_PORT, SDA_PIN, BIT?SET:RESET)
- define SDA_STATE() gpio_input_bit_get(SDA_PORT, SDA_PIN)
- define SDA_IN() gpio_mode_set(SDA_PORT, GPIO_MODE_INPUT, GPIO_PUPD_NONE, SDA_PIN)
- define SDA_OUT() gpio_mode_set(SDA_PORT, GPIO_MODE_OUTPUT, GPIO_PUPD_NONE, SDA_PIN)
- 硬件I2C-GD32F4系列
学习目标
- 理解I2C通讯原理
- 理解I2C通讯过程中的信号
- 理解软件I2C实现过程
-
学习内容
I2C通讯规则

I2C总线包括两根信号线:SDA(串行数据线)和SCL(串行时钟线)。这两根信号线共用一个总线,因此在总线上可以连接多个设备。在I2C总线上,每个设备都有一个唯一的地址,用于标识设备。
SCL线是时钟线,用于控制数据传输的速度和时序;SDA线是数据线,用于传输实际的数据.I2C写操作
流程如下:
开始。
- 发送设备地址,等待从设备响应
- 发送寄存器地址,等待从设备响应
- 发送一个字节,等待从设备响应。这个操作是循环执行,直到没有数据。
- 停止。
I2C读流程
流程如下:
- 开始。
- 发送设备地址(写地址),等待从设备响应
- 发送寄存器地址,等待从设备响应。
- 开始
- 发送设备地址(读地址),等待从设备响应
- 接收一个字节,发送响应给从设备。这个操作是循环执行,直到没有数据。当是最后一个数据时,发送空响应。
- 停止。
通讯信号
开始

static void start() {SDA_OUT();SDA(1);delay_1us(5);SCL(1);delay_1us(5);SDA(0);delay_1us(5);SCL(0);delay_1us(5);}
结束

static void stop() {SDA_OUT();SCL(0);SDA(0);SCL(1);delay_1us(5);SDA(1);delay_1us(5);}
发送数据
bit发送

数据有效性:
- SCL上升沿到下降沿这个阶段,SDA电平的高低,表示数据bit的1和0
如果SDA电平在这个阶段发生变化,则无效,参考start和stop信号。
Byte发送
基于数据有效性,将byte按bit位变化为高低电平,发送出去。

static void send(uint8_t data) {uint8_t i;SDA_OUT();for(i = 0; i < 8; i++) {if(data & 0x80) {SDA(1);} else {SDA(0);}SCL(1);delay_1us(5);SCL(0);delay_1us(5);data <<= 1;}}
等待响应
wait ack:_Ack_nowledge character。表示等待响应,每发送一个数据,需要确认对方是否收到,就需要等待对方响应。
static uint8_t wait_ack() {int8_t retry = 10;SCL(0);SDA(1);SDA_IN();delay_1us(5);SCL(1);delay_1us(5);while(SDA_STATE() == 1 && retry > 0) {retry --;delay_1us(5);}if(retry <= 0) {stop();return 1;} else {SCL(0);SDA_OUT();}return 0;}
接收数据
bit接收
Byte接收

static uint8_t recv() {uint8_t i, data;SDA_IN();data = 0;for(i = 0; i < 8; i++) {SCL(0);delay_1us(5);SCL(1);delay_1us(5);data <<= 1;data |= SDA_STATE();delay_1us(5);}SCL(0);return data;}
发送响应

static void send_ack() {SDA_OUT();SCL(0);SDA(0);delay_1us(5);SDA(0);SCL(1);delay_1us(5);SCL(0);SDA(1);}
static void send_nack() {SDA_OUT();SCL(0);SDA(0);delay_1us(5);SDA(1);SCL(1);delay_1us(5);SCL(0);SDA(1);}
软件I2C
开发流程
- 引脚初始化
- 引脚功能定义
- 实现读操作
-
GD32F4软件I2C初始化
void SoftI2C_init() {// 时钟配置rcu_periph_clock_enable(SCL_RCU);// 设置输出模式gpio_mode_set(SCL_PORT, GPIO_MODE_OUTPUT, GPIO_PUPD_NONE, SCL_PIN);gpio_output_options_set(SCL_PORT, GPIO_OTYPE_OD, GPIO_OSPEED_50MHZ, SCL_PIN);// 时钟配置rcu_periph_clock_enable(SDA_RCU);// 设置输出模式gpio_mode_set(SDA_PORT, GPIO_MODE_OUTPUT, GPIO_PUPD_NONE, SDA_PIN);gpio_output_options_set(SDA_PORT, GPIO_OTYPE_OD, GPIO_OSPEED_50MHZ, SDA_PIN);}
- I2C引脚高低电平
GD32F4软件I2C引脚功能
```cdefine SCL_RCU RCU_GPIOB
define SCL_PORT GPIOB
define SCL_PIN GPIO_PIN_6
define SCL_AF GPIO_AF_4
define SDA_RCU RCU_GPIOB
define SDA_PORT GPIOB
define SDA_PIN GPIO_PIN_7
define SDA_AF GPIO_AF_4
/** io */
define SCL(BIT) gpio_bit_write(SCL_PORT, SCL_PIN, BIT?SET:RESET)
define SDA(BIT) gpio_bit_write(SDA_PORT, SDA_PIN, BIT?SET:RESET)
define SDA_STATE() gpio_input_bit_get(SDA_PORT, SDA_PIN)
define SDA_IN() gpio_mode_set(SDA_PORT, GPIO_MODE_INPUT, GPIO_PUPD_NONE, SDA_PIN)
define SDA_OUT() gpio_mode_set(SDA_PORT, GPIO_MODE_OUTPUT, GPIO_PUPD_NONE, SDA_PIN)
- IO引脚定义- 引脚输出模式高低电平输出:SCL高和低,SDA高和低- SDA模式配置:SDA输出模式,SDA输入模式- SDA输入模式状态读取。<a name="tWatG"></a>#### 写操作```cuint8_t SoftI2C_write(uint8_t addr, uint8_t reg, uint8_t* data, uint32_t len) {start();send(addr << 1); //发送设备写地址if(wait_ack()) return 1; //等待响应send(reg); //发送寄存器地址if(wait_ack()) return 2; //等待响应do {send(*data++);if(wait_ack()) return 3;} while(--len);stop();return 0;}
读操作

uint8_t SoftI2C_read(uint8_t addr, uint8_t reg, uint8_t* data, uint32_t len) {start();send(addr << 1); //发送设备写地址if(wait_ack()) return 1; //等待响应send(reg); //发送寄存器地址if(wait_ack()) return 2; //等待响应start();send((addr << 1) | 0x01); //发送设备读地址if(wait_ack()) return 3; //等待响应do {*data = recv();data++;if(len != 1) send_ack(); // 发送 NACK} while(--len);send_nack(); // 发送 NACKstop();return 0;}
硬件I2C-GD32F4系列
初始化操作
uint32_t i2cx_scl_port_rcu = RCU_GPIOB;uint32_t i2cx_scl_port = GPIOB;uint32_t i2cx_scl_pin = GPIO_PIN_6;uint32_t i2cx_scl_af = GPIO_AF_4;uint32_t i2cx_sda_port_rcu = RCU_GPIOB;uint32_t i2cx_sda_port = GPIOB;uint32_t i2cx_sda_pin = GPIO_PIN_7;uint32_t i2cx_sda_af = GPIO_AF_4;uint32_t i2cx = I2C0;uint32_t i2cx_rcu = RCU_I2C0;uint32_t i2cx_speed = 400000;/****************** GPIO config **********************/// 时钟配置rcu_periph_clock_enable(i2cx_scl_port_rcu);// 设置复用功能gpio_af_set(i2cx_scl_port, i2cx_scl_af, i2cx_scl_pin);// 设置输出模式gpio_mode_set(i2cx_scl_port, GPIO_MODE_AF, GPIO_PUPD_NONE, i2cx_scl_pin);gpio_output_options_set(i2cx_scl_port, GPIO_OTYPE_OD, GPIO_OSPEED_50MHZ, i2cx_scl_pin);// 时钟配置rcu_periph_clock_enable(i2cx_sda_port_rcu);// 设置复用功能gpio_af_set(i2cx_sda_port, i2cx_sda_af, i2cx_sda_pin);// 设置输出模式gpio_mode_set(i2cx_sda_port, GPIO_MODE_AF, GPIO_PUPD_NONE, i2cx_sda_pin);gpio_output_options_set(i2cx_sda_port, GPIO_OTYPE_OD, GPIO_OSPEED_50MHZ, i2cx_sda_pin);/****************** I2C config **********************/i2c_deinit(i2cx);// 时钟配置rcu_periph_clock_enable(i2cx_rcu);// I2C速率配置i2c_clock_config(i2cx, i2cx_speed, I2C_DTCY_2);// 使能i2ci2c_mode_addr_config(i2cx, I2C_I2CMODE_ENABLE, I2C_ADDFORMAT_7BITS, 0x00);i2c_enable(i2cx);// i2c ack enablei2c_ack_config(i2cx, I2C_ACK_ENABLE);
- 哪个I2C
- SCL是哪个引脚
- SDA是哪个引脚
- 速度是多快
写操作流程
开始
```c /* start */ // 等待I2C闲置 if(I2C_waitn(i2cx, I2C_FLAG_I2CBSY)) return 1;
// start i2c_start_on_bus(i2cx);
// 等待I2C主设备成功发送起始信号 if(I2C_wait(i2cx, I2C_FLAG_SBSEND)) return 2;
<a name="WtaJX"></a>##### 发送设备地址```c/************* device address **************/// 发送设备地址i2c_master_addressing(i2cx, address, I2C_TRANSMITTER);// 等待地址发送完成if(I2C_wait(i2cx, I2C_FLAG_ADDSEND)) return 3;i2c_flag_clear(i2cx, I2C_FLAG_ADDSEND);
发送寄存器地址
/************ register address ************/// 寄存器地址// 等待发送数据缓冲区为空if(I2C_wait(i2cx, I2C_FLAG_TBE)) return 4;// 发送数据i2c_data_transmit(i2cx, reg);// 等待数据发送完成if(I2C_wait(i2cx, I2C_FLAG_BTC)) return 5;
数据发送
/***************** data ******************/// 发送数据uint32_t i;for(i = 0; i < data_len; i++) {uint32_t d = data[i];// 等待发送数据缓冲区为空if(I2C_wait(i2cx, I2C_FLAG_TBE)) return 6;// 发送数据i2c_data_transmit(i2cx, d);// 等待数据发送完成if(I2C_wait(i2cx, I2C_FLAG_BTC)) return 7;}
停止
/***************** stop ********************/// stopi2c_stop_on_bus(i2cx);if(I2C_waitn(i2cx, I2C_CTL0(I2C0)&I2C_CTL0_STOP)) return 8;
读操作流程
开始
/************* start ***********************/// 等待I2C空闲if(I2C_waitn(i2cx, I2C_FLAG_I2CBSY)) return 1;// 发送启动信号i2c_start_on_bus(i2cx);if(I2C_wait(i2cx, I2C_FLAG_SBSEND)) return 2;
发送设备地址(写)
/************* device address **************/// 发送从设备地址i2c_master_addressing(i2cx, address, I2C_TRANSMITTER);if(I2C_wait(i2cx, I2C_FLAG_ADDSEND)) return 3;i2c_flag_clear(i2cx, I2C_FLAG_ADDSEND);
发送寄存器地址
/********** register address **************/// 等待发送缓冲区if(I2C_wait(i2cx, I2C_FLAG_TBE)) return 4;// 发送寄存器地址i2c_data_transmit(i2cx, reg);// 等待发送数据完成if(I2C_wait(i2cx, I2C_FLAG_BTC)) return 5;if(I2C_wait(i2cx, I2C_FLAG_TBE)) return 6;
开始
/************* start ***********************/// 发送再启动信号i2c_start_on_bus(i2cx);if(I2C_wait(i2cx, I2C_FLAG_SBSEND)) return 7;
发送设备地址(读)
/************* device address **************/// 发送从设备地址i2c_master_addressing(i2cx, address, I2C_RECEIVER);if(I2C_wait(i2cx, I2C_FLAG_ADDSEND)) return 8;i2c_flag_clear(i2cx, I2C_FLAG_ADDSEND);//acki2c_ack_config(i2cx, I2C_ACK_ENABLE);i2c_ackpos_config(i2cx, I2C_ACKPOS_CURRENT);if(I2C_wait(i2cx, (I2C_CTL0(i2cx) & I2C_CTL0_ACKEN))) return 23;
数据读取
/************* data **************///acki2c_ack_config(i2cx, I2C_ACK_ENABLE);i2c_ackpos_config(i2cx, I2C_ACKPOS_CURRENT);if(I2C_wait(i2cx, (I2C_CTL0(i2cx) & I2C_CTL0_ACKEN))) return 11;// 读取数据uint8_t i;for (i = 0; i < len; i++) {if(i != len - 1) {// 等待接收缓冲区if(I2C_wait(i2cx, I2C_FLAG_BTC)) return 9;}// 等待ACK数据发送完成// 等待接收缓冲区if(I2C_wait(i2cx, I2C_FLAG_RBNE)) return 10;data[i] = i2c_data_receive(i2cx);if (i == len - 1) {// 在读取最后一个字节之前,禁用ACK,并发送停止信号// 配置自动NACKi2c_ack_config(i2cx, I2C_ACK_DISABLE);}}
结束
/***************** stop ********************/i2c_stop_on_bus(i2cx);if(I2C_waitn(i2cx, I2C_CTL0(I2C0) & I2C_CTL0_STOP)) return 11;
GD32F4寄存器
| 流程 | 功能 | 标记 | 描述 |
|---|---|---|---|
| START | I2C_FLAG_I2CBSY |
busy标记。I2C是否占用,没有占用才可以使用。 | |
I2C_FLAG_SBSEND |
起始信号发送状态标记。START成功或失败。 | ||
| 数据 | 设备地址 | I2C_FLAG_ADDSEND |
地址发送状态标记。成功或失败。 |
| 发送 | I2C_FLAG_TBE |
发送数据寄存器是否为空的标记。为空才可以继续发送。 | |
I2C_FLAG_BTC |
发送数据寄存器中数据是否发送完成。 | ||
| 接收 | I2C_FLAG_RBNE |
接收缓冲区寄存器是否为空的标记。为空才可以继续接收。 | |
| STOP | I2C_CTL0_STOP |
停止标记位。 |
完整代码
#ifndef __I2C0_H__#define __I2C0_H__#include "systick.h"#include "gd32f4xx.h"void I2C0_init();uint8_t I2C0_read(uint8_t addr, uint8_t reg, uint8_t* data, uint32_t len);uint8_t I2C0_write(uint8_t addr, uint8_t reg, uint8_t* data, uint32_t len);uint8_t I2C0_write2(uint8_t addr, uint8_t reg, uint8_t* data, uint32_t offset, uint32_t len);void I2C0_deinit();#endif
#include "I2C0.h"void I2C0_init() {uint32_t i2cx_scl_port_rcu = RCU_GPIOB;uint32_t i2cx_scl_port = GPIOB;uint32_t i2cx_scl_pin = GPIO_PIN_6;uint32_t i2cx_scl_af = GPIO_AF_4;uint32_t i2cx_sda_port_rcu = RCU_GPIOB;uint32_t i2cx_sda_port = GPIOB;uint32_t i2cx_sda_pin = GPIO_PIN_7;uint32_t i2cx_sda_af = GPIO_AF_4;uint32_t i2cx = I2C0;uint32_t i2cx_rcu = RCU_I2C0;uint32_t i2cx_speed = 400000;/****************** GPIO config **********************/// 时钟配置rcu_periph_clock_enable(i2cx_scl_port_rcu);// 设置复用功能gpio_af_set(i2cx_scl_port, i2cx_scl_af, i2cx_scl_pin);// 设置输出模式gpio_mode_set(i2cx_scl_port, GPIO_MODE_AF, GPIO_PUPD_NONE, i2cx_scl_pin);gpio_output_options_set(i2cx_scl_port, GPIO_OTYPE_OD, GPIO_OSPEED_50MHZ, i2cx_scl_pin);// 时钟配置rcu_periph_clock_enable(i2cx_sda_port_rcu);// 设置复用功能gpio_af_set(i2cx_sda_port, i2cx_sda_af, i2cx_sda_pin);// 设置输出模式gpio_mode_set(i2cx_sda_port, GPIO_MODE_AF, GPIO_PUPD_NONE, i2cx_sda_pin);gpio_output_options_set(i2cx_sda_port, GPIO_OTYPE_OD, GPIO_OSPEED_50MHZ, i2cx_sda_pin);/****************** I2C config **********************/i2c_deinit(i2cx);// 时钟配置rcu_periph_clock_enable(i2cx_rcu);// I2C速率配置i2c_clock_config(i2cx, i2cx_speed, I2C_DTCY_2);// 使能i2ci2c_mode_addr_config(i2cx, I2C_I2CMODE_ENABLE, I2C_ADDFORMAT_7BITS, 0x00);i2c_enable(i2cx);// i2c ack enablei2c_ack_config(i2cx, I2C_ACK_ENABLE);//i2c_ackpos_config(i2cx, I2C_ACKPOS_CURRENT);}static uint8_t I2C_wait(uint32_t i2cx, uint32_t flag) {uint16_t TIMEOUT = 50000;uint16_t cnt = 0;while(!i2c_flag_get(i2cx, flag)) {cnt++;if(cnt > TIMEOUT) return 1;}return 0;}static uint8_t I2C_waitn(uint32_t i2cx, uint32_t flag) {uint16_t TIMEOUT = 50000;uint16_t cnt = 0;while(i2c_flag_get(i2cx, flag)) {cnt++;if(cnt > TIMEOUT) return 1;}return 0;}uint8_t I2C0_write(uint8_t addr, uint8_t reg, uint8_t* data, uint32_t data_len) {uint32_t i2cx = I2C0;uint8_t address = addr << 1;/************* start ***********************/// 等待I2C闲置if(I2C_waitn(i2cx, I2C_FLAG_I2CBSY)) return 1;// starti2c_start_on_bus(i2cx);// 等待I2C主设备成功发送起始信号if(I2C_wait(i2cx, I2C_FLAG_SBSEND)) return 2;/************* device address **************/// 发送设备地址i2c_master_addressing(i2cx, address, I2C_TRANSMITTER);// 等待地址发送完成if(I2C_wait(i2cx, I2C_FLAG_ADDSEND)) return 3;i2c_flag_clear(i2cx, I2C_FLAG_ADDSEND);/************ register address ************/// 寄存器地址// 等待发送数据缓冲区为空if(I2C_wait(i2cx, I2C_FLAG_TBE)) return 4;// 发送数据i2c_data_transmit(i2cx, reg);// 等待数据发送完成if(I2C_wait(i2cx, I2C_FLAG_BTC)) return 5;/***************** data ******************/// 发送数据uint32_t i;for(i = 0; i < data_len; i++) {uint32_t d = data[i];// 等待发送数据缓冲区为空if(I2C_wait(i2cx, I2C_FLAG_TBE)) return 6;// 发送数据i2c_data_transmit(i2cx, d);// 等待数据发送完成if(I2C_wait(i2cx, I2C_FLAG_BTC)) return 7;}/***************** stop ********************/// stopi2c_stop_on_bus(i2cx);if(I2C_waitn(i2cx, I2C_CTL0(I2C0)&I2C_CTL0_STOP)) return 8;return 0;}uint8_t I2C0_write2(uint8_t addr, uint8_t reg, uint8_t* data, uint32_t offset, uint32_t len) {uint32_t i2cx = I2C0;uint8_t address = addr << 1;/************* start ***********************/// 等待I2C闲置if(I2C_waitn(i2cx, I2C_FLAG_I2CBSY)) return 1;// starti2c_start_on_bus(i2cx);// 等待I2C主设备成功发送起始信号if(I2C_wait(i2cx, I2C_FLAG_SBSEND)) return 2;/************* device address **************/// 发送设备地址i2c_master_addressing(i2cx, address, I2C_TRANSMITTER);// 等待地址发送完成if(I2C_wait(i2cx, I2C_FLAG_ADDSEND)) return 3;i2c_flag_clear(i2cx, I2C_FLAG_ADDSEND);/************ register address ************/// 寄存器地址// 等待发送数据缓冲区为空if(I2C_wait(i2cx, I2C_FLAG_TBE)) return 4;// 发送数据i2c_data_transmit(i2cx, reg);// 等待数据发送完成if(I2C_wait(i2cx, I2C_FLAG_BTC)) return 5;/***************** data ******************/// 发送数据do {// 等待发送数据缓冲区为空if(I2C_wait(i2cx, I2C_FLAG_TBE)) return 6;// 发送数据i2c_data_transmit(i2cx, *data);data += offset;// 等待数据发送完成if(I2C_wait(i2cx, I2C_FLAG_BTC)) return 7;} while(--len);/***************** stop ********************/// stopi2c_stop_on_bus(i2cx);if(I2C_waitn(i2cx, I2C_CTL0(I2C0)&I2C_CTL0_STOP)) return 8;return 0;}void I2C0_deinit() {}uint8_t I2C0_read(uint8_t addr, uint8_t reg, uint8_t* data, uint32_t len) {uint32_t i2cx = I2C0;uint8_t address = addr << 1;/************* start ***********************/// 等待I2C空闲if(I2C_waitn(i2cx, I2C_FLAG_I2CBSY)) return 1;// 发送启动信号i2c_start_on_bus(i2cx);if(I2C_wait(i2cx, I2C_FLAG_SBSEND)) return 2;/************* device address **************/// 发送从设备地址i2c_master_addressing(i2cx, address, I2C_TRANSMITTER);// //ack// i2c_ack_config(i2cx, I2C_ACK_ENABLE);// i2c_ackpos_config(i2cx, I2C_ACKPOS_CURRENT);// if(I2C_wait(i2cx, (I2C_CTL0(i2cx) & I2C_CTL0_ACKEN))) return 11;// // i2c_ack_config(i2cx, I2C_ACK_DISABLE);if(I2C_wait(i2cx, I2C_FLAG_ADDSEND)) return 3;i2c_flag_clear(i2cx, I2C_FLAG_ADDSEND);/********** register address **************/// 等待发送缓冲区if(I2C_wait(i2cx, I2C_FLAG_TBE)) return 4;// 发送寄存器地址i2c_data_transmit(i2cx, reg);// 等待发送数据完成if(I2C_wait(i2cx, I2C_FLAG_BTC)) return 5;if(I2C_wait(i2cx, I2C_FLAG_TBE)) return 6;/************* start ***********************/// 发送再启动信号i2c_start_on_bus(i2cx);if(I2C_wait(i2cx, I2C_FLAG_SBSEND)) return 7;/************* device address **************/// 发送从设备地址i2c_master_addressing(i2cx, address, I2C_RECEIVER);if(I2C_wait(i2cx, I2C_FLAG_ADDSEND)) return 8;i2c_flag_clear(i2cx, I2C_FLAG_ADDSEND);/************* data **************///acki2c_ack_config(i2cx, I2C_ACK_ENABLE);i2c_ackpos_config(i2cx, I2C_ACKPOS_CURRENT);if(I2C_wait(i2cx, (I2C_CTL0(i2cx) & I2C_CTL0_ACKEN))) return 23;// 读取数据uint8_t i;for (i = 0; i < len; i++) {if(i != len - 1) {// 等待ACK发送完成if(I2C_wait(i2cx, I2C_FLAG_BTC)) return 9;}// 等待ACK数据发送完成// 等待接收缓冲区if(I2C_wait(i2cx, I2C_FLAG_RBNE)) return 10;data[i] = i2c_data_receive(i2cx);if (i == len - 1) {// 在读取最后一个字节之前,禁用ACK,并发送停止信号// 配置自动NACK//i2c_ackpos_config(i2cx, I2C_ACKPOS_NEXT);//if(I2C_wait(i2cx, (I2C_CTL0(i2cx) & I2C_CTL0_ACKEN))) return 9;i2c_ack_config(i2cx, I2C_ACK_DISABLE);}}/***************** stop ********************/i2c_stop_on_bus(i2cx);if(I2C_waitn(i2cx, I2C_CTL0(I2C0) & I2C_CTL0_STOP)) return 11;return 0;}
