GD32/STM32开发中I2C的软实现

前言

在消费电子、工业电子等领域,会使用各种类型的芯片,如微控制器、电源管理、显示驱动、传感器、存储器、转换器等,它们有着不同的功能。有时需要快速地进行数据交互。为了使用最简单的方式使这些芯片互联互通,I2C(Inter-Integrated Circuit)协议应运而生。

**I2C协议(或称IIC)**是由飞利浦(现在的恩智浦半导体)公司开发的一种通用的总线协议。它使用两根线(时钟线和数据线)来传输数据,支持多个设备共享同一条总线。 I2C协议通常用于连接微控制器、传感器、存储器和其他外围设备。

I2C通讯规则

I2C总线包括两根信号线:SDA(串行数据线)和SCL(串行时钟线)。这两根信号线共用一个总线,因此在总线上可以连接多个设备。在I2C总线上,每个设备都有一个唯一的地址,用于标识设备。SCL线是时钟线,用于控制数据传输的速度和时序;SDA线是数据线,用于传输实际的数据.

I2C写操作

流程如下:

  1. 开始。
  2. 发送设备地址,等待从设备响应
  3. 发送寄存器地址,等待从设备响应
  4. 发送一个字节,等待从设备响应。这个操作是循环执行,直到没有数据。
  5. 停止。

代码解析

主函数初始化

由数据手册我们可以进行i2c的引脚查询,我做了下面的整理:

同时,查阅原理图我们可以对其相应引脚进行初始化

cpp 复制代码
#define	SCL_RCU		RCU_GPIOB
#define SCL_PORT	GPIOB
#define SCL_PIN		GPIO_PIN_6

#define	SDA_RCU		RCU_GPIOB
#define SDA_PORT	GPIOB
#define SDA_PIN		GPIO_PIN_7

void I2C_soft_init(){
	// 初始化I2C的GPIO
	// SCL - PB6
	rcu_periph_clock_enable(SCL_RCU);
	gpio_mode_set(SCL_PORT, GPIO_MODE_OUTPUT, GPIO_PUPD_PULLUP, SCL_PIN);
	gpio_output_options_set(SCL_PORT, GPIO_OTYPE_OD, GPIO_OSPEED_MAX, SCL_PIN);
	
	// SDA - PB7
	rcu_periph_clock_enable(SDA_RCU);
	gpio_mode_set(SDA_PORT, GPIO_MODE_OUTPUT, GPIO_PUPD_PULLUP, SDA_PIN);
	gpio_output_options_set(SDA_PORT, GPIO_OTYPE_OD, GPIO_OSPEED_MAX, SDA_PIN);
}

写函数实现

我为大家提供的编程思路:根据I2C写操作流程,我们先将写操作的基本框架构建出来,具体包括开始、发送设备写地址、等待响应、发送寄存器地址、等待响应、循环发送所有字节、停止。然后我再根据各部分原理图去把对应的函数逐个实现出来。

大家先不要去看具体代码怎么编写,这里提供这张截图是为了让大家清楚我们构建的框架。

接下来,我们再去讲解里面的函数实现。

通讯信号------开始

由原理图我们看到第一步将SDA和SCL全都拉高,然后延时一段时间,将SDA拉低,延时后再将SCL拉低,当检测到有这种信号存在了,证明即将开始传输数据了。

部分代码展示:

cpp 复制代码
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);
}
通讯信号------结束

部分代码展示:

cpp 复制代码
static void stop() {
    SDA_OUT();

    SCL(0);
    SDA(0);

    SCL(1);
    delay_1us(5);
    SDA(1);
    delay_1us(5);
}
通讯信号------发送数据

从SCL上升沿到下降沿的这个阶段数据是有有效的,因为如果SDA在左边虚线靠右位置才拉高,那么这个信号就和结束信号一样了(图一),同理,如果SDA在右边虚线靠左的位置就拉低了,那么这个信号就和开始信号一样了(图二)。

所以,总结:不能在这个数据有效性阶段动数据,要是改数据,要在SCL低电平的时候修改。

通讯信号------等待响应

等待响应信号,首先SDA拉高,等待一会然后SCL拉高,此时SDA将控制权交给从设备,也就是说SDA由输出模式转化为输入模式,然后我们对SDA的电平进行检测,如果检测到是低电平,说明应答成功,代码返回0。如果检测到高点平则说明无人应答,此时代码应该返回1或者其他设定的数。应答成功后,SDA收回控制权,SCL拉低。

部分代码:

cpp 复制代码
#define SDA_STATE()		gpio_input_bit_get(SDA_PORT, SDA_PIN)

static uint8_t wait_ack(){
	// SDA拉高, 等待从设备拉低
	SDA(1);
	DELAY();
	
	// SCL拉高, 同时释放SDA权限(变成输入模式)
	SCL(1);
	SDA_IN();
	DELAY();
	
	if(SDA_STATE() == RESET){
		// 从设备拉低了SDA,应答成功
		SCL(0);
		SDA_OUT();
	}else {
		// 无人应答,应答失败, 直接结束
		stop();
		return 1;
	}
	
	return 0;
}

这样上面所有通讯信号开始时,都要将SDA控制权收回SDA_OUT(),保证能够正常输出。

补充

设备地址实际发送的是左移一位后的数据,最右边一位是0就是write,1就是read。

I2C读操作

流程如下

  1. 开始。
  2. 发送设备地址(写地址),等待从设备响应
  3. 发送寄存器地址,等待从设备响应。
  4. 开始
  5. 发送设备地址(读地址),等待从设备响应
  6. 接收一个字节,发送响应给从设备。这个操作是循环执行,直到没有数据。当是最后一个数据时,发送空响应。
  7. 停止。

代码解析

I2C读流程前半部分和写流程一样,我们只需在读流程的基础上进行修改和增加即可,这里注意的是发送的设备地址左移后最后一位要置为1,代表读数据。

通讯信号------接收数据

cpp 复制代码
static uint8_t recv(){
	// 释放SDA控制权,进入输入模式
	SDA_IN();
	uint8_t cnt = 8; 			//1个字节 8bit
	uint8_t data = 0x00;  // 空容器接收数据
	while(cnt--){ 				// 接收一个bit (先收高位)
		// SCL拉低
		SCL(0);	// 等待从设备准备数据
		DELAY();
		
		SCL(1); // 设置数据有效性
		// 0000 0000 -> 1111 1011
		// 0000 0001	8
		// 0000 0011	7
		// 0000 0111	6
		// 0000 1111	5
		// 0001 1111	4
		// 0011 1110	3
		// 0111 1101	2
		// 1111 1011	1
		data <<= 1;
		
		if(SDA_STATE()) data++;
		
		// SCL在高电平等待一会儿
		DELAY();
	}
	
	// 最后一次低电平,不能忘
	SCL(0);
	
	return data;
}

先释放SDA的控制权,进入输入模式,也就是由从设备控制,然后进行循环从高位接收数据,将SCL拉低,等待从设备准备数据,延时一会后将SCL拉高,保证数据的有效性,准备一个空容器放数据,每次都左移一位然后存放数据,当检测到SDA是高电平1时,空容器左移后最后一位加1,即data++,如果检测到是低电平0时,则不进行操作,延时一会,最后别忘了把SCL拉低,保证后面的数据传输。

通讯信号------发送响应

cpp 复制代码
static void send_ack(){
	// 主机发送ACK响应
	
	// 主机获取SDA控制权,进入输出模式
	SDA_OUT();
	// 拉低SDA
	SDA(0);
	DELAY();
	
	// 拉高SCL
	SCL(1);
	DELAY();
	
	// 拉低SCL
	SCL(0);
	DELAY();	
}
cpp 复制代码
static void send_nack(){
	// 主机发送NACK响应
	// 主机获取SDA控制权,进入输出模式
	SDA_OUT();
	// 拉高SDA
	SDA(1);
	DELAY();
	
	// 拉高SCL
	SCL(1);
	DELAY();
	
	// 拉低SCL
	SCL(0);
	DELAY();
}

补充

收到数据就要ACK反馈,收到最后一个数据的时候是NACK反馈。

总结

以上就是GD32的I2C的软实现过程,需要大家掌握,因为不同平台的硬实现可能会不同,但软实现每个平台都是一样的,所以大家重点掌握软实现,我们下期再见!

相关推荐
紫阡星影11 分钟前
【模块系列】STM32&1.69TFT屏幕
stm32·单片机·嵌入式硬件
彭某。16 分钟前
STM32低功耗模式结合看门狗
stm32·单片机·嵌入式硬件
憧憬一下29 分钟前
PCI/PCIe设备INTx中断机制和MSI中断机制
arm开发·嵌入式硬件·嵌入式·linux驱动开发·pci/pcie
不能只会打代码1 小时前
32单片机综合案例——智能环境监控系统
单片机·嵌入式硬件
小菜鸟学代码··9 小时前
STM32文件详解
stm32·单片机·嵌入式硬件
马浩同学10 小时前
【GD32】从零开始学GD32单片机 | DAC数模转换器 + 三角波输出例程
c语言·单片机·嵌入式硬件·mcu
最后一个bug13 小时前
STM32MP1linux根文件系统目录作用
linux·c语言·arm开发·单片机·嵌入式硬件
wenchm14 小时前
细说STM32F407单片机IIC总线基础知识
stm32·单片机·嵌入式硬件
嵌入式lover14 小时前
STM32项目之环境空气质量检测系统软件设计
stm32·单片机·嵌入式硬件
kenwblack15 小时前
STM32 SPI读取SD卡
stm32·单片机