STM32开发项目:软件模拟I2C功能

日期作者版本说明
2020.10.29TaoV1.11. 修复了端口配置输入输出方向函数的bug(没有设置端口速度)
2. STM32F103与F407平台的源码分开提供
3. 改进了等待从机应答的逻辑
2020.11.05TaoV1.21. 增加了I2C Device的数据结构封装

背景介绍

I2C协议介绍

本章节主要参考了这篇博文

I2C总线是由Philips公司开发的一种简单、双向二线制同步串行总线。它只需要两根线即可在连接于总线上的器件之间传送信息。

I2C 总线在物理连接上非常简单,分别由SDA(串行数据线)和SCL(串行时钟线)及上拉电阻组成。通信原理是通过对SCL和SDA线高低电平时序的控制,来 产生I2C总线协议所需要的信号进行数据的传递。在总线空闲状态时,这两根线一般被上面所接的上拉电阻拉高,保持着高电平。
I2C总线物理拓扑结构

I2C总线特征

I2C总线上的每一个设备都可以作为主设备或者从设备,而且每一个设备都会对应一个唯一的地址(可以从I2C器件的数据手册得知),主从设备之间就通过这 个地址来确定与哪个器件进行通信,在通常的应用中,我们把CPU带I2C总线接口的模块作为主设备,把挂接在总线上的其他设备都作为从设备。

I2C总线上可挂接的设备数量受总线的最大电容400pF 限制,如果所挂接的是相同型号的器件,则还受器件地址位的限制。

I2C总线数据传输速率在标准模式下可达100kbit/s,快速模式下可达400kbit/s,高速模式下可达3.4Mbit/s。一般通过I2C总线接口可编程时钟来实现传输速率的调整,同时也跟所接的上拉电阻的阻值有关。

I2C总线上的主设备与从设备之间以字节(8位)为单位进行双向的数据传输。

I2C总线协议

I2C协议规定,总线上数据的传输必须以一个起始信号作为开始条件,以一个结束信号作为传输的停止条件。起始和结束信号总是由主设备产生。总线在空闲状态 时,SCL和SDA都保持着高电平,当SCL为高电平而SDA由高到低的跳变,表示产生一个起始条件;当SCL为高而SDA由低到高的跳变,表示产生一个 停止条件。在起始条件产生后,总线处于忙状态,由本次数据传输的主从设备独占,其他I2C器件无法访问总线;而在停止条件产生后,本次数据传输的主从设备 将释放总线,总线再次处于空闲状态。如图所示:
在这里插入图片描述
在了解起始条件和停止条件后,我们再来看看在这个过程中数据的传输是如何进行的。前面我们已经提到过,数据传输以字节为单位。主设备在SCL线上产生每个 时钟脉冲的过程中将在SDA线上传输一个数据位,当一个字节按数据位从高位到低位的顺序传输完后,紧接着从设备将拉低SDA线,回传给主设备一个应答位, 此时才认为一个字节真正的被传输完成。当然,并不是所有的字节传输都必须有一个应答位,比如:当从设备不能再接收主设备发送的数据时,从设备将回传一个否 定应答位。数据传输的过程如图所示:
在这里插入图片描述在前面我们还提到过,I2C总线上的每一个设备都对应一个唯一的地址,主从设备之间的数据传输是建立在地址的基础上,也就是说,主设备在传输有效数据之前 要先指定从设备的地址,地址指定的过程和上面数据传输的过程一样,只不过大多数从设备的地址是7位的,然后协议规定再给地址添加一个最低位用来表示接下来 数据传输的方向,0表示主设备向从设备写数据,1表示主设备向从设备读数据。如图所示:
在这里插入图片描述

I2C总线操作

对I2C总线的操作实际就是主从设备之间的读写操作。大致可分为以下三种操作情况:
第一,主设备往从设备中写数据。数据传输格式如下:
在这里插入图片描述
第二,主设备从从设备中读数据。数据传输格式如下:
在这里插入图片描述
第三,主设备往从设备中写数据,然后重启起始条件,紧接着从从设备中读取数据;或者是主设备从从设备中读数据,然后重启起始条件,紧接着主设备往从设备中写数据。数据传输格式如下:
在这里插入图片描述
第三种操作在单个主设备系统中,重复的开启起始条件机制要比用STOP终止传输后又再次开启总线更有效率。

本套库的特点

本套库的设计基于三个工程现实问题:

  1. 硬件I2C数量有限而且会影响代码的可移植性能;
  2. I2C通常不会有很高的数据速度和使用频率;
  3. 不同的从机器件一般不会挂载在同一个总线上;

第一个问题决定了必要性。设计一套软件模拟I2C库函数可以使得I2C的使用不受数量的限制且可移植行更强。第二个问题决定了可行性。使用I2C总线的低速低频特性决定了使用软件直接操作单片机的GPIO模拟I2C协议不至于占用过多的单片机资源或者无法满足性能的要求。第三个问题决定了通用性。软件模拟的I2C库函数必须有一套机制可以通过分时复用的方式驱动多条总线。笔者编写的库函数较好的符合了上述三个特征,以下给出源码以供大家参考。

源码实现

注意,笔者的源码是基于STM32F407平台运行的,延时函数也是从它处调用。在移植本代码的时候应当注意与平台硬件相关部分(例如void I2C_Virtual_SetSDA_Out())以及延时函数(delay_us())的实现。

头文件

#ifndef __I2C_H__
#define __I2C_H__

#include "stm32f4xx.h"
#include "stm32f4xx_conf.h"

#ifdef I2C_VIRTUAL

typedef struct
{
	char SclPort;
	uint8_t SclPin;
	char SdaPort;
	uint8_t SdaPin;
} I2CPort_Struct;

typedef struct
{
	//I2C从机设备的端口
	I2CPort_Struct port;
	/*
	 * I2C设备的写地址 = I2C设备地址 << 1 + 0
	 * I2C设备的读地址 = (I2C设备地址 << 1) + 1
	 */
	//I2C从机设备地址(器件地址占7位,D7~D1)
	uint8_t address;
	//I2C从机设备读写状态(0:写,1:读)
//	uint8_t rwStatus;
	//I2C从机设备的状态(0:错误或不存在,1:正常)
	uint8_t devStatus;
} I2CDevice_Struct;

extern uint8_t I2C_Virtual_ack;

void I2C_Virtual_ConfigPort(char SDA_Port, uint8_t SDA_Pin, char SCL_Port, uint8_t SCL_Pin);
void I2C_Virtual_SwitchBus(char SDA_Port, uint8_t SDA_Pin, char SCL_Port, uint8_t SCL_Pin);
void I2C_Virtual_SetSDA_Out(void);
void I2C_Virtual_SetSDA_In(void);
void I2C_Virtual_Init(void);
void I2C_Virtual_Start(void);
void I2C_Virtual_Stop(void);
uint8_t I2C_Virtual_SendByte(uint8_t c);
uint8_t I2C_Virtual_RcvByte(void);
void I2C_Virtual_Ack(void);
void I2C_Virtual_NoAck(void);
void I2C_Virtual_WaitAck(uint16_t time);

#endif
#endif

源文件

STM32F407平台

#include "i2c_virtual.h"

#ifdef I2C_VIRTUAL

static uint16_t I2C_Virtual_SDA_Pin;
static GPIO_TypeDef *I2C_Virtual_SDA_Port;
static unsigned long *I2C_Virtual_SDA_OUT_Addr;
static unsigned long *I2C_Virtual_SDA_IN_Addr;
static unsigned long *I2C_Virtual_SCL_OUT_Addr;

uint8_t I2C_Virtual_ack;	//应答标志位

/**
 * @brief 配置模拟I2C的引脚端口。
 * 		主要实现了MCU的GPIO端口配置,SDA需要配置为开漏输出,SCL需要配置为推挽输出。
 * 		此函数实现与硬件平台相关。
 * @param SDA_Port: 'A'~'G'
 * @param SDA_Pin: 0~15
 * @param SCL_Port: 'A'~'G'
 * @param SCL_Pin: 0~15
 */
void I2C_Virtual_ConfigPort(char SDA_Port, uint8_t SDA_Pin, char SCL_Port, uint8_t SCL_Pin)
{
	uint16_t I2C_Virtual_SDA_Pin;
	uint16_t I2C_Virtual_SCL_Pin;
	GPIO_TypeDef *I2C_Virtual_SDA_Port;
	GPIO_TypeDef *I2C_Virtual_SCL_Port;
	GPIO_InitTypeDef GPIO_InitStructure;

	switch (SDA_Port)
	{
	case 'A':
		I2C_Virtual_SDA_Port = GPIOA;
		RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA,ENABLE);
		break;
	case 'B':
		I2C_Virtual_SDA_Port = GPIOB;
		RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOB,ENABLE);
		break;
	case 'C':
		I2C_Virtual_SDA_Port = GPIOC;
		RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOC,ENABLE);
		break;
	case 'D':
		I2C_Virtual_SDA_Port = GPIOD;
		RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOD,ENABLE);
		break;
	case 'E':
		I2C_Virtual_SDA_Port = GPIOE;
		RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOE,ENABLE);
		break;
	case 'F':
		I2C_Virtual_SDA_Port = GPIOF;
		RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOF,ENABLE);
		break;
	case 'G':
		I2C_Virtual_SDA_Port = GPIOG;
		RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOG,ENABLE);
		break;
	default:
		break;
	}

	I2C_Virtual_SDA_Pin = (uint16_t) (0x0001 << SDA_Pin);

	switch (SCL_Port)
	{
	case 'A':
			I2C_Virtual_SCL_Port = GPIOA;
			RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA,ENABLE);
			break;
		case 'B':
			I2C_Virtual_SCL_Port = GPIOB;
			RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOB,ENABLE);
			break;
		case 'C':
			I2C_Virtual_SCL_Port = GPIOC;
			RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOC,ENABLE);
			break;
		case 'D':
			I2C_Virtual_SCL_Port = GPIOD;
			RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOD,ENABLE);
			break;
		case 'E':
			I2C_Virtual_SCL_Port = GPIOE;
			RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOE,ENABLE);
			break;
		case 'F':
			I2C_Virtual_SCL_Port = GPIOF;
			RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOF,ENABLE);
			break;
		case 'G':
			I2C_Virtual_SCL_Port = GPIOG;
			RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOG,ENABLE);
			break;
		default:
			break;
	}

	I2C_Virtual_SCL_Pin = (uint16_t) (0x0001 << SCL_Pin);

	GPIO_InitStructure.GPIO_Pin = I2C_Virtual_SDA_Pin;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN;
	GPIO_InitStructure.GPIO_OType = GPIO_OType_OD;
	GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL;
	GPIO_Init(I2C_Virtual_SDA_Port, &GPIO_InitStructure);

	GPIO_InitStructure.GPIO_Pin = I2C_Virtual_SCL_Pin;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_OUT;
	GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
	GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP;
	GPIO_Init(I2C_Virtual_SCL_Port, &GPIO_InitStructure);
}

/**
 * @brief 使本套库函数可以支持多个独立的软件模拟I2C总线。
 * 		通过切换I2C总线引脚,以分别控制不同的芯片。
 * 		一般在配置模拟I2C的引脚端口之后,需要调用此函数设置总线。
 * @param SDA_Port: 'A'~'G'
 * @param SDA_Pin: 0~15
 * @param SCL_Port: 'A'~'G'
 * @param SCL_Pin: 0~15
 */
void I2C_Virtual_SwitchBus(char SDA_Port, uint8_t SDA_Pin, char SCL_Port, uint8_t SCL_Pin)
{

	switch (SDA_Port)
	{
	case 'A':
		I2C_Virtual_SDA_Port = GPIOA;
		I2C_Virtual_SDA_OUT_Addr = GPIOA_OUT_ADDR(SDA_Pin);
		I2C_Virtual_SDA_IN_Addr = GPIOA_IN_ADDR(SDA_Pin);
		break;
	case 'B':
		I2C_Virtual_SDA_Port = GPIOB;
		I2C_Virtual_SDA_OUT_Addr = GPIOB_OUT_ADDR(SDA_Pin);
		I2C_Virtual_SDA_IN_Addr = GPIOB_IN_ADDR(SDA_Pin);
		break;
	case 'C':
		I2C_Virtual_SDA_Port = GPIOC;
		I2C_Virtual_SDA_OUT_Addr = GPIOC_OUT_ADDR(SDA_Pin);
		I2C_Virtual_SDA_IN_Addr = GPIOC_IN_ADDR(SDA_Pin);
		break;
	case 'D':
		I2C_Virtual_SDA_Port = GPIOD;
		I2C_Virtual_SDA_OUT_Addr = GPIOD_OUT_ADDR(SDA_Pin);
		I2C_Virtual_SDA_IN_Addr = GPIOD_IN_ADDR(SDA_Pin);
		break;
	case 'E':
		I2C_Virtual_SDA_Port = GPIOE;
		I2C_Virtual_SDA_OUT_Addr = GPIOE_OUT_ADDR(SDA_Pin);
		I2C_Virtual_SDA_IN_Addr = GPIOE_IN_ADDR(SDA_Pin);
		break;
	case 'F':
		I2C_Virtual_SDA_Port = GPIOF;
		I2C_Virtual_SDA_OUT_Addr = GPIOF_OUT_ADDR(SDA_Pin);
		I2C_Virtual_SDA_IN_Addr = GPIOF_IN_ADDR(SDA_Pin);
		break;
	case 'G':
		I2C_Virtual_SDA_Port = GPIOG;
		I2C_Virtual_SDA_OUT_Addr = GPIOG_OUT_ADDR(SDA_Pin);
		I2C_Virtual_SDA_IN_Addr = GPIOG_IN_ADDR(SDA_Pin);
		break;
	default:
		break;
	}

	I2C_Virtual_SDA_Pin = (uint16_t) (0x0001 << SDA_Pin);

	switch (SCL_Port)
	{
	case 'A':
		I2C_Virtual_SCL_OUT_Addr = GPIOA_OUT_ADDR(SCL_Pin);
		break;
	case 'B':
		I2C_Virtual_SCL_OUT_Addr = GPIOB_OUT_ADDR(SCL_Pin);
		break;
	case 'C':
		I2C_Virtual_SCL_OUT_Addr = GPIOC_OUT_ADDR(SCL_Pin);
		break;
	case 'D':
		I2C_Virtual_SCL_OUT_Addr = GPIOD_OUT_ADDR(SCL_Pin);
		break;
	case 'E':
		I2C_Virtual_SCL_OUT_Addr = GPIOE_OUT_ADDR(SCL_Pin);
		break;
	case 'F':
		I2C_Virtual_SCL_OUT_Addr = GPIOF_OUT_ADDR(SCL_Pin);
		break;
	case 'G':
		I2C_Virtual_SCL_OUT_Addr = GPIOG_OUT_ADDR(SCL_Pin);
		break;
	default:
		break;
	}
}



/**
 * @brief Switch data bus input/output state.
 */
void I2C_Virtual_SetSDA_Out(void)
{
	GPIO_InitTypeDef GPIO_InitStructure;

	GPIO_InitStructure.GPIO_Pin = I2C_Virtual_SDA_Pin;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_OUT;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_InitStructure.GPIO_OType = GPIO_OType_OD;
	GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL;

	GPIO_Init(I2C_Virtual_SDA_Port, &GPIO_InitStructure);
}



/**
 * @brief Switch data bus input/output state.
 */
void I2C_Virtual_SetSDA_In(void)
{
	GPIO_InitTypeDef GPIO_InitStructure;

	GPIO_InitStructure.GPIO_Pin = I2C_Virtual_SDA_Pin;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_InitStructure.GPIO_OType = GPIO_OType_OD;
	GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL;

	GPIO_Init(I2C_Virtual_SDA_Port, &GPIO_InitStructure);
}


/**
 * @brief Initialization of the virtual I2C.
 */
void I2C_Virtual_Init(void)
{
	*I2C_Virtual_SCL_OUT_Addr = 1;
	
	I2C_Virtual_SetSDA_Out();
	*I2C_Virtual_SDA_OUT_Addr = 1;
}



/**
 * @brief Start I2C.
 */
void I2C_Virtual_Start()
{
	*I2C_Virtual_SDA_OUT_Addr = 1;			//发送起始条件的数据信号
	delay_us(1);

	*I2C_Virtual_SCL_OUT_Addr = 1;
	delay_us(10);										//起始条件建立时间大于4.7us,延时

	*I2C_Virtual_SDA_OUT_Addr = 0;		//发送起始信号
	delay_us(10);										//起始条件锁定时间大于4μ

	*I2C_Virtual_SCL_OUT_Addr = 0;			//钳住I2C总线,准备发送或接收数据
	delay_us(2);
}



/**
 * @brief Stop I2C.
 */
void I2C_Virtual_Stop()
{
	*I2C_Virtual_SCL_OUT_Addr = 0;
	delay_us(2);

	*I2C_Virtual_SDA_OUT_Addr = 0;	//发送结束条件的数据信号
	delay_us(1);

	*I2C_Virtual_SCL_OUT_Addr = 1;		//发送结束条件的时钟信号
	delay_us(10);									//结束条件建立时间大于4μ

	*I2C_Virtual_SDA_OUT_Addr = 1;		//发送I2C总线结束信号
	delay_us(10);
}



/**
 * @brief 主机字节数据发送函数
 * 		将数据c发送出去,可以是地址,也可以是数据,发完后等待应答,
 * 		并对此状态位进行操作.(不应答或非应答都使ack=0 假)
 * 		发送数据正常,ack=1; ack=0表示被控器无应答或损坏。
 * @retval 应答位
 * 		@arg 0:有应答
 * 		@arg 1:无应答
 */
uint8_t I2C_Virtual_SendByte(uint8_t c)
{
	for (uint8_t BitCnt = 0; BitCnt < 8; BitCnt++)	//要传送的数据长度为8位
	{
		if ((c << BitCnt) & 0x80)	//判断发送位
			*I2C_Virtual_SDA_OUT_Addr = 1;
		else
			*I2C_Virtual_SDA_OUT_Addr = 0;

		delay_us(1);

		*I2C_Virtual_SCL_OUT_Addr = 1;	//置时钟线为高,通知被控器开始接收数据位
		delay_us(10);	//保证时钟高电平周期大于4μ

		*I2C_Virtual_SCL_OUT_Addr = 0;
	}

	I2C_Virtual_WaitAck(1000);
	
	return I2C_Virtual_ack;
}



/**
 * @brief 主机字节数据接收函数
 * 		用来接收从器件传来的数据,并判断总线错误(不发应答信号),
 * 		发完后请用应答函数。
 */
uint8_t I2C_Virtual_RcvByte()
{
	uint8_t retc;
	uint8_t BitCnt;

	retc = 0;

	*I2C_Virtual_SDA_OUT_Addr = 1;	//置数据线为输入方式

	for (BitCnt = 0; BitCnt < 8; BitCnt++)
	{
		delay_us(1);
		*I2C_Virtual_SCL_OUT_Addr = 0;	//置时钟线为低,准备接收数据位
		delay_us(10);	//时钟低电平周期大于4.7us
		*I2C_Virtual_SCL_OUT_Addr = 1;	//置时钟线为高使数据线上数据有效
		//delay1us();
		retc = retc << 1;
		I2C_Virtual_SetSDA_In();

		if (*I2C_Virtual_SDA_IN_Addr == 1)
			retc = retc + 1;	//读数据位,接收的数据位放入retc中

		I2C_Virtual_SetSDA_Out();
		delay_us(1);
	}

	*I2C_Virtual_SCL_OUT_Addr = 0;
	delay_us(2);

	return (retc);
}



/**
 * @brief 主机应答函数
 */
void I2C_Virtual_Ack(void)
{
	*I2C_Virtual_SDA_OUT_Addr = 0;
	delay_us(2);

	*I2C_Virtual_SCL_OUT_Addr = 1;
	delay_us(10);	//时钟低电平周期大于4μ

	*I2C_Virtual_SCL_OUT_Addr = 0;  	//清时钟线,钳住I2C总线以便继续接收
	delay_us(2);
}



/**
 * @brief 主机不应答函数
 */
void I2C_Virtual_NoAck(void)
{
	*I2C_Virtual_SDA_OUT_Addr = 1;
	delay_us(1);

	*I2C_Virtual_SCL_OUT_Addr = 1;
	delay_us(10);	//时钟低电平周期大于4μ

	*I2C_Virtual_SCL_OUT_Addr = 0;	    //清时钟线,钳住I2C总线以便继续接收
	delay_us(2);
}


/**
 * @brief 主机等待应答函数
 * @param time: wait for timeout
 */
void I2C_Virtual_WaitAck(uint16_t time)
{
	uint16_t timeout = 0;

	*I2C_Virtual_SDA_OUT_Addr = 1;	//8位发送完后释放数据线,准备接收应答位
	I2C_Virtual_SetSDA_In();

	*I2C_Virtual_SCL_OUT_Addr = 1;

	delay_us(1);

	while(*I2C_Virtual_SDA_IN_Addr != 0)
	{
		if(timeout++>time)
		{
			I2C_Virtual_ack = 0;
			
			I2C_Virtual_SetSDA_Out();
			I2C_Virtual_Stop();

			return;
		}
	}

	I2C_Virtual_ack = 1;

	I2C_Virtual_SetSDA_Out();
	*I2C_Virtual_SCL_OUT_Addr = 0;

	delay_us(2);
}

#endif

STM32F103平台

平台的不同影响了void I2C_Virtual_ConfigPort(char SDA_Port, uint8_t SDA_Pin, char SCL_Port, uint8_t SCL_Pin), void I2C_Virtual_SetSDA_Out(void),void I2C_Virtual_SetSDA_In(void)这三个与底层硬件相关的函数。

/**
 * @brief 配置模拟I2C的引脚端口。
 * 		主要实现了MCU的GPIO端口配置,SDA需要配置为开漏输出,SCL需要配置为推挽输出。
 * 		此函数实现与硬件平台相关。
 * @param SDA_Port: 'A'~'G'
 * @param SDA_Pin: 0~15
 * @param SCL_Port: 'A'~'G'
 * @param SCL_Pin: 0~15
 */
void I2C_Virtual_ConfigPort(char SDA_Port, uint8_t SDA_Pin, char SCL_Port, uint8_t SCL_Pin)
{
	uint16_t I2C_Virtual_SDA_Pin;
	uint16_t I2C_Virtual_SCL_Pin;
	GPIO_TypeDef *I2C_Virtual_SDA_Port;
	GPIO_TypeDef *I2C_Virtual_SCL_Port;
	GPIO_InitTypeDef GPIO_InitStructure;

	switch (SDA_Port)
	{
	case 'A':
		I2C_Virtual_SDA_Port = GPIOA;
		RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
		break;
	case 'B':
		I2C_Virtual_SDA_Port = GPIOB;
		RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE);
		break;
	case 'C':
		I2C_Virtual_SDA_Port = GPIOC;
		RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC,ENABLE);
		break;
	case 'D':
		I2C_Virtual_SDA_Port = GPIOD;
		RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOD,ENABLE);
		break;
	case 'E':
		I2C_Virtual_SDA_Port = GPIOE;
		RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOE,ENABLE);
		break;
	case 'F':
		I2C_Virtual_SDA_Port = GPIOF;
		RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOF,ENABLE);
		break;
	case 'G':
		I2C_Virtual_SDA_Port = GPIOG;
		RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOG,ENABLE);
		break;
	default:
		break;
	}

	I2C_Virtual_SDA_Pin = (uint16_t) (0x0001 << SDA_Pin);

	switch (SCL_Port)
	{
	case 'A':
		I2C_Virtual_SCL_Port = GPIOA;
		RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
		break;
	case 'B':
		I2C_Virtual_SCL_Port = GPIOB;
		RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE);
		break;
	case 'C':
		I2C_Virtual_SCL_Port = GPIOC;
		RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC,ENABLE);
		break;
	case 'D':
		I2C_Virtual_SCL_Port = GPIOD;
		RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOD,ENABLE);
		break;
	case 'E':
		I2C_Virtual_SCL_Port = GPIOE;
		RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOE,ENABLE);
		break;
	case 'F':
		I2C_Virtual_SCL_Port = GPIOF;
		RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOF,ENABLE);
		break;
	case 'G':
		I2C_Virtual_SCL_Port = GPIOG;
		RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOG,ENABLE);
		break;
	default:
		break;
	}

	I2C_Virtual_SCL_Pin = (uint16_t) (0x0001 << SCL_Pin);

	GPIO_InitStructure.GPIO_Pin = I2C_Virtual_SDA_Pin;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_OD;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(I2C_Virtual_SDA_Port, &GPIO_InitStructure);

	GPIO_InitStructure.GPIO_Pin = I2C_Virtual_SCL_Pin;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(I2C_Virtual_SCL_Port, &GPIO_InitStructure);
}



/**
 * @brief Switch data bus input/output state.
 */
void I2C_Virtual_SetSDA_Out(void)
{
	GPIO_InitTypeDef GPIO_InitStructure;

	GPIO_InitStructure.GPIO_Pin = I2C_Virtual_SDA_Pin;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_OD;

	GPIO_Init(I2C_Virtual_SDA_Port, &GPIO_InitStructure);
}



/**
 * @brief Switch data bus input/output state.
 */
void I2C_Virtual_SetSDA_In(void)
{
	GPIO_InitTypeDef GPIO_InitStructure;

	GPIO_InitStructure.GPIO_Pin = I2C_Virtual_SDA_Pin;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;

	GPIO_Init(I2C_Virtual_SDA_Port, &GPIO_InitStructure);
}

应用指南

软件模拟I2C属于底层驱动类型的库,一般情况下不会直接使用,而是为各种芯片的库函数提供底层接口。本章节从框架结构上对软件模拟I2C库驱动多个不同总线上的芯片做一个简要说明。

  • 首先应完成GPIO的初始化配置,SDA功能的GPIO配置为开漏输出(总线要上拉)、SCL功能的GPIO配置为推挽或者开漏输出。此处以STM32F103单片机为例:
/**
* ADS1115 - SDA: PB0, SCL: PB1
*/
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_Init(GPIOE, &GPIO_InitStructure);

GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_OD;
GPIO_Init(GPIOE, &GPIO_InitStructure);

/**
* ADS1115 - SDA: PB2, SCL: PB3
*/
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_Init(GPIOE, &GPIO_InitStructure);

GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_OD;
GPIO_Init(GPIOE, &GPIO_InitStructure);

本库函数中内置了void I2C_Virtual_ConfigPort(char SDA_Port, uint8_t SDA_Pin, char SCL_Port, uint8_t SCL_Pin)函数,可以直接完成GPIO端口的初始化工作。应当注意的是,此函数的实现与硬件平台密切相关,因此在移植的时候需要注意。

  • 宏定义挂载设备的总线引脚,字符A, B, …,G表示端口号,数字0~15表示引脚号。
#define ADS1115_SCL_PORT	'A'
#define ADS1115_SCL_PIN		6

#define ADS1115_SDA_PORT	'A'
#define ADS1115_SDA_PIN		5
  • 设置Device1的I2C总线。
I2C_Virtual_SwitchBus(ADS1115_SDA_PORT, ADS1115_SDA_PIN, ADS1115_SCL_PORT, ADS1115_SCL_PIN);
  • 执行Device1的相关操作。
    在Device1的库函数的I2C通讯部分,应该调用本库的各种I2C通讯接口,例如void I2C_Virtual_Start(),void I2C_Virtual_Stop(),uint8_t I2C_Virtual_SendByte(uint8_t c)
ADS1115_Config();
ADS1115_ReadData();
  • 切换Device2的I2C总线
I2C_Virtual_SwitchBus(sts31Dev.SdaPort, sts31Dev.SdaPin, sts31Dev.SclPort, sts31Dev.SclPin);
  • 执行Device2的相关操作
STS31_Config();
STS31_ReadData();
  • 22
    点赞
  • 80
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 10
    评论
STM32是一种广泛应用的嵌入式单片机,具有强大的功能和灵活的软件开发能力。在STM32中,I2C(Inter-Integrated Circuit)是一种通信协议,用于连接各个外设并实现数据传输。 软件模拟I2C程序是指在STM32中使用软件模拟的方式实现I2C协议的功能。这种方法可以在硬件不支持I2C功能或者I2C总线被其他外设占用的情况下,使用GPIO引脚模拟I2C信号线,通过软件控制来实现I2C通信。 实现软件模拟I2C程序的关键步骤如下: 1. 设置GPIO引脚模拟I2C的SDA(数据线)和SCL(时钟线)。 2. 初始化GPIO引脚,并配置为输出模式。 3. 实现I2C的起始信号,将SDA线由高电平变为低电平时,同时将SCL线保持为高电平。 4. 实现I2C的停止信号,将SDA线由低电平变为高电平时,同时将SCL线保持为高电平。 5. 实现I2C的数据传输,包括发送和接收。 6. 在发送数据时,先将数据写入SDA线,再将SCL线由高电平变为低电平,完成一次数据传输。 7. 在接收数据时,先将SCL线由高电平变为低电平,再读取SDA线上的数据。 8. 根据I2C协议的需要,可能还需要设置ACK信号等功能。 需要注意的是,软件模拟I2C程序在速度上可能无法达到硬件I2C的要求,因此在使用时需根据具体应用场景进行性能上的优化调整。 总之,通过软件模拟I2C程序,我们可以在STM32上实现I2C通信的功能,为嵌入式开发提供了更多的灵活性和可扩展性。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 10
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

全能骑士涛锅锅

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值