【每周分享】I2C原理代码结合(干货)看图易懂
本帖最后由 虚幻的是灵魂 于 2024-10-15 15:19 编辑#申请原创#
@21小跑堂
I2C通讯规则
I2C总线包括两根信号线:SDA(串行数据线)和SCL(串行时钟线)。这两根信号线共用一个总线,因此在总线上可以连接多个设备。在I2C总线上,每个设备都有一个唯一的地址,用于标识设备。SCL线是时钟线,用于控制数据传输的速度和时序;SDA线是数据线,用于传输实际的数据。I2C写操作流程如下:
[*]开始。
[*]发送设备地址,等待从设备响应
[*]发送寄存器地址,等待从设备响应
[*]发送一个字节,等待从设备响应。这个操作是循环执行,直到没有数据。
[*]停止。
I2C读流程流程如下:
[*]开始。
[*]发送设备地址(写地址),等待从设备响应
[*]发送寄存器地址,等待从设备响应。
[*]开始
[*]发送设备地址(读地址),等待从设备响应
[*]接收一个字节,发送响应给从设备。这个操作是循环执行,直到没有数据。当是最后一个数据时,发送空响应。
[*]停止。
通讯信号开始
Start代码表示
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);
}结束
Stop代码表示
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位变化为高低电平,发送出去。发送byte表示 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:Acknowledge 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接收
逻辑分析仪测的:
说明: 在上升沿写入数据时,会获取从设备控制权, 上升沿写入数据; SCL在高电平时,会释放从设备控制权, 直到从设备被拉低后,主设备才响应成功。
接收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;
}
发送响应
发送ACK
static void send_ack() {
SDA_OUT();
SCL(0);
SDA(0);
delay_1us(5);
SDA(0);
SCL(1);
delay_1us(5);
SCL(0);
SDA(1);
}发送NACK
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开发流程
[*]引脚初始化
[*]引脚功能定义
[*]实现读操作
[*]实现写操作
GD32软件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引脚高低电平
GD32软件I2C引脚功能#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
/************** 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输入模式状态读取。
写操作
代码:
uint8_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(); // 发送 NACK
stop();
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);
// 使能i2c
i2c_mode_addr_config(i2cx, I2C_I2CMODE_ENABLE, I2C_ADDFORMAT_7BITS, 0x00);
i2c_enable(i2cx);
// i2c ack enable
i2c_ack_config(i2cx, I2C_ACK_ENABLE);
[*]哪个I2C
[*]SCL是哪个引脚
[*]SDA是哪个引脚
[*]速度是多快
写操作流程开始/************* 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; 发送设备地址/************* 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;
// 等待发送数据缓冲区为空
if(I2C_wait(i2cx, I2C_FLAG_TBE)) return 6;
// 发送数据
i2c_data_transmit(i2cx, d);
// 等待数据发送完成
if(I2C_wait(i2cx, I2C_FLAG_BTC)) return 7;
}停止/***************** stop ********************/
// stop
i2c_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);
//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 23;数据读取/************* data **************/
//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;
// 读取数据
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 = i2c_data_receive(i2cx);
if (i == len - 1) {
// 在读取最后一个字节之前,禁用ACK,并发送停止信号
// 配置自动NACK
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;完整代码I2C0.H#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();
#endifI2C.C#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);
// 使能i2c
i2c_mode_addr_config(i2cx, I2C_I2CMODE_ENABLE, I2C_ADDFORMAT_7BITS, 0x00);
i2c_enable(i2cx);
// i2c ack enable
i2c_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;
// start
i2c_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;
// 等待发送数据缓冲区为空
if(I2C_wait(i2cx, I2C_FLAG_TBE)) return 6;
// 发送数据
i2c_data_transmit(i2cx, d);
// 等待数据发送完成
if(I2C_wait(i2cx, I2C_FLAG_BTC)) return 7;
}
/***************** stop ********************/
// stop
i2c_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;
// start
i2c_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 ********************/
// stop
i2c_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 **************/
//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 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 = 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;
}
请教一下,这个时序图和流程图是用什么软件做的 话说,I2C的挂载数量跟什么有关系?巡检的时候也是点名的方式吗?
页:
[1]