【STM32F103 外设】I2C 数据收发标准库实现(硬件 / 软件模拟)

I2C 硬件实现

I2C 初始化

#include "stm32f10x.h"
#define GPIO_Pin_SCL	GPIO_Pin_6    		//GPIOB_6 为 SCL
#define GPIO_Pin_SDA	GPIO_Pin_7			//GPIOB_7 为 SDA

/**
  * @brief I2C 初始化
  * @params None
  * @returns None
  */
 
void IIC_Init(void)
{
	/* 开启外设时钟 */
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_I2C1, ENABLE);
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);
	
	/* GPIO 配置 */
	GPIO_InitTypeDef GPIO_InitStructure;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_OD;  							//必须为复用开漏输出
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_SCL | GPIO_Pin_SDA;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOA, &GPIO_InitStructure);
	GPIO_SetBits(GPIOB, GPIO_Pin_SCL | GPIO_Pin_SDA);

	/* 串口参数配置 */
	I2C_InitTypeDef IIC_InitStructure;
	IIC_InitStructure.I2C_Mode = I2C_Mode_I2C;									//模式,选择为I2C模式
	IIC_InitStructure.I2C_ClockSpeed = 50000;									//时钟速度,选择为50KHz
	IIC_InitStructure.I2C_Ack = I2C_Ack_Enable;									//应答,选择使能
	IIC_InitStructure.I2C_DutyCycle = I2C_DutyCycle_2;							//时钟占空比,低电平:高电平 = 2:1
	IIC_InitStructure.I2C_AcknowledgedAddress = I2C_AcknowledgedAddress_7bit;	//自身地址位数,选择7位,从机模式下才有效
	IIC_InitStructure.I2C_OwnAddress1 = 0x00;									//自身地址,从机模式下才有效
	I2C_Init(I2C1, &IIC_InitStructure);
}

I2C 主模式

  • 当主设备处于发送模式时,它会先发送起始位,然后是7位从设备地址加上读/写方向位(0表示写,1表示读),接着发送要写入的数据,最后以停止位结束。
  • 当主设备处于接收模式时,主设备需要先发送起始位和从设备地址加写方向位,告诉从设备要读取其数据。然后,主设备需要切换到接收模式,等待从设备发送数据。为了实现这一切换,主设备需要再次发送一个起始位,但这次是从设备地址加读方向位,从而进入接收模式。

简单来说就是:主机想发就发,主机让从机发从机才能发

I2C 等待事件

I2C 标准库 Event

/**
  * @brief I2C 等待事件
  * @params I2C_EVENT 同 I2C_CheckEvent
  * @returns None
  */
void IIC_WaitEvent(I2C_TypeDef* I2Cx, uint32_t I2C_EVENT)
{
	uint32_t Timeout = 10000;							//给定超时计数时间
	while (I2C_CheckEvent(I2Cx, I2C_EVENT) != SUCCESS)	//等待指定事件
	{
		Timeout--;										
		if (Timeout == 0)								//判断是否等待超时
		{
			/*超时的错误处理代码,可以添加到此处*/
			break;										
		}
	}
}

主机向从机指定寄存器发送数据

主发送器传送序列图

/*  I2C 7 位主发送 */
/**
  * @brief I2C 向指定从设备的指定寄存器发送一个字节数据
  * @params 
  * 	@arg Address 七位从机地址
  * 	@arg RegAddress 从机寄存器地址
  * 	@arg Data 待发送数据
  * @returns None
  */
void IIC_Send_Byte(uint8_t Address, uint8_t RegAddress, uint8_t const Data)
{
	I2C_GenerateSTART(I2C1, ENABLE);									//产生起始条件
	IIC_WaitEvent(I2C1, I2C_EVENT_MASTER_MODE_SELECT);  				//等待EV5
	
	I2C_Send7bitAddress(I2C1, Address, I2C_Direction_Transmitter);		//发送从机地址
	IIC_WaitEvent(I2C1, I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED); 	//等待EV6
	
	I2C_SendData(I2C1, RegAddress);										//向从机发送寄存器地址
	IIC_WaitEvent(I2C1, I2C_EVENT_MASTER_BYTE_TRANSMITTING); 			//等待EV8
	
	I2C_SendData(I2C1, Data);											//向从机寄存器发送数据
	IIC_WaitEvent(I2C1, I2C_EVENT_MASTER_BYTE_TRANSMITTED); 			//等待EV8_2
	
	I2C_GenerateSTOP(I2C1, ENABLE);										//产生停止条件
}

主机从从机指定寄存器读取数据

主接收器传送序列图

/*  I2C 7 位主接收 */
/**
  * @brief I2C 从指定从设备的指定寄存器读取一个字节数据
  * @params 
  * 	@arg Address 七位从机地址
  * 	@arg RegAddress 从机寄存器地址
  * @returns 接收数据
  */
uint8_t IIC_Receive_Byte(uint8_t Address, uint8_t RegAddress)
{
	I2C_GenerateSTART(I2C1, ENABLE);									//产生起始条件
	IIC_WaitEvent(I2C1, I2C_EVENT_MASTER_MODE_SELECT);  				//等待EV5
	
	I2C_Send7bitAddress(I2C1, Address, I2C_Direction_Transmitter);		//发送从机地址
	IIC_WaitEvent(I2C1, I2C_EVENT_MASTER_RECEIVER_MODE_SELECTED); 		//等待EV6
	
	I2C_SendData(I2C1, RegAddress);										//向从机发送寄存器地址
	IIC_WaitEvent(I2C1, I2C_EVENT_MASTER_BYTE_TRANSMITTED); 			//等待EV8_2
	
	I2C_GenerateSTART(I2C1, ENABLE);									//产生重复起始条件
	IIC_WaitEvent(I2C1, I2C_EVENT_MASTER_MODE_SELECT);  				//等待EV5
	
	I2C_Send7bitAddress(I2C1, Address, I2C_Direction_Receiver);			//发送从机地址
	IIC_WaitEvent(I2C1, I2C_EVENT_MASTER_RECEIVER_MODE_SELECTED); 		//等待EV6
	
	I2C_AcknowledgeConfig(I2C1, DISABLE);								//提前关闭应答位
	I2C_GenerateSTOP(I2C1, ENABLE);										//提前产生停止条件
	
	IIC_WaitEvent(I2C1, I2C_EVENT_MASTER_BYTE_RECEIVED); 				//等待EV7
	uint8_t Data = I2C_ReceiveData(I2C1);								//接收数据
	
	I2C_AcknowledgeConfig(I2C2, ENABLE);								//将应答恢复使能,不影响读取多字节操作

	return Data;
}

I2C 软件实现

GPIO 初始化

#include "stm32f10x.h"
/**
  * @brief GPIO初始化
  * @params None
  * @returns None
  */
void MyIIC_Init(void)
{
	/* 开启外设时钟 */
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);
	
	/* GPIO 配置 */
	GPIO_InitTypeDef GPIO_InitStructure;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_OD;  			//必须为开漏输出
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6 | GPIO_Pin_7;		//任意两个 GPIO_Pin 口
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOA, &GPIO_InitStructure);
}

SCL、SDA 引脚配置

/**
  * @brief 写 SCL 电平
  * @params Value 写入 SCL 电平值(0 表示低电平,1 表示高电平)
  * @returns None
  */
void MyIIC_W_SCL(uint8_t Value)
{
	GPIO_WriteBit(GPIOB, GPIO_Pin_6, (BitAction)Value);
	/* 添加一定的延时时间,防止时序频率超过设备要求。
	   不同的 I2C 设备其最大支持的时钟频率是有限的。
	   如果使用的 I2C 时钟频率超过了所连接设备的最大支持频率,
	   那么这些设备可能无法正常工作,导致数据传输错误或失败。 */
}

/**
  * @brief 写 SDA 电平
  * @params Value 写入 SDA 电平值(0 表示低电平,1 表示高电平)
  * @returns None
  */
void MyIIC_W_SDA(uint8_t Value)
{
	GPIO_WriteBit(GPIOB, GPIO_Pin_7, (BitAction)Value);
	/* 添加一定的延时时间,防止时序频率超过设备要求。*/
}

/**
  * @brief 读 SDA 电平
  * @params None
  * @returns SDA 电平值
  */
uint8_t MyIIC_R_SDA(void)
{
	uint8_t Value = GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_7);
	/* 添加一定的延时时间,防止时序频率超过设备要求。*/
	return Value;
}

I2C 协议层

  • 起始位:SCL高电平、SDA下降沿
    I2C 起始条件
void MyIIC_START(void)
{	
	/* 空闲状态下 SCL 和 SDA 均为高电平 */
	MyIIC_W_SCL(SET);
	MyIIC_W_SDA(SET);
	MyIIC_W_SDA(RESET);  		//起始条件:SCL 高电平时 SDA 下降沿
	MyIIC_W_SDA(RESET);			//占用总线
}

  • 终止位:SCL高电平、SDA上升沿
    I2C 终止条件
void MyIIC_STOP(void)
{	
	MyIIC_W_SDA(RESET);
	MyIIC_W_SCL(SET);
	MyIIC_W_SDA(SET);			//终止条件:SCL 高电平时 SDA 上升沿
}
  • 主机发送应答位
    主机接收完一个字节后,在下一个时钟发送一位数据(0 表示应答,1 表示非应答)
    I2C 发送应答位
/**
  * @brief 主机向从机发送应答位(Ack 由主机控制 SDA 线发出)
  * @params Ack 应答位 0 表示应答,1 表示非应答
  * @returns None
  */
void Send_Ack(uint8_t Ack)
{ 
	MyIIC_W_SDA(Ack);			//主机把应答位数据放到 SDA 线
	MyIIC_W_SCL(SET);			//释放 SCL,从机在 SCL 高电平期间,读取应答位
	MyIIC_W_SCL(RESET);			//拉低 SCL,开始下一个数据传输
}
  • 主机接收应答位
    主机发送完一个字节后,在下一个时钟接收一位数据,判断从机是否应答(0 表示应答,1 表示非应答)
    I2C 接收应答位
/**
  * @brief 主机读取从机应答位(Ack 由从机控制 SDA 线发出)
  * @params None
  * @returns 应答位 0 表示应答,1 表示非应答
  */
uint8_t Receive_Ack(void)
{ 
	uint8_t Ack;				//定义应答位变量
	MyIIC_W_SDA(SET);			//接收前,主机先确保释放 SDA,避免干扰从机的数据发送
	MyIIC_W_SCL(SET);			//释放 SCL,主机将在 SCL 高电平期间读取 SDA
	Ack = MyIIC_R_SDA();		//将应答位存储到变量里
	MyIIC_W_SCL(RESET);			//拉低 SCL,开始下一个数据传输
	return Ack;					//返回应答位
}
  • 主机发送一个字节
/**
  * @brief I2C 发送一个字节
  * @params Byte 要发送的一个字节数据,范围:0x00~0xFF
  * @returns None
  */
void MyI2C_SendByte(uint8_t Byte)
{
	uint8_t i;
	for (i = 0; i < 8; i ++)					//循环 8 次,主机依次发送数据的每一位
	{
		/* I2C协议规定先传输高位(MSB,即 Most Significant Bit),然后传输低位(LSB,即 Least Significant Bit)*/
		MyIIC_W_SDA(Byte & (0x80 >> i));		//使用掩码的方式取出 Byte 的指定一位数据并写入到 SDA 线 (1000 0000b)
		MyIIC_W_SCL(SET);						//释放 SCL,从机在SCL高电平期间读取 SDA
		MyIIC_W_SCL(RESET);						//拉低 SCL,主机开始发送下一位数据
	}
}
  • 主机接收一个字节
/**
  * @brief I2C 接收一个字节
  * @params None
  * @returns 接收到的一个字节数据,范围:0x00~0xFF
  */
uint8_t MyI2C_ReceiveByte(void)
{
	uint8_t i, Byte = 0x00;									//定义接收的数据,并赋初值 0x00
	MyIIC_W_SDA(SET);										//接收前,主机先确保释放 SDA,避免干扰从机的数据发送
	for (i = 0; i < 8; i ++)								//循环 8 次,主机依次接收数据的每一位
	{
		MyIIC_W_SCL(SET);									//释放 SCL,主机机在 SCL 高电平期间读取 SDA
		if (MyIIC_R_SDA() == 1){Byte |= (0x80 >> i);}		//读取 SDA 数据,并存储到 Byte 变量
															//当 SDA 为 1 时,置变量指定位为 1,当 SDA 为 0 时,不做处理,指定位为默认的初值 0
		MyIIC_W_SCL(RESET);									//拉低 SCL,从机在SCL低电平期间写入 SDA
	}
	return Byte;											
}

欢迎批评指正!!!

参考文献:STM32F10xxx参考手册
协议层图片来源:STM32入门教程-2023版 细致讲解 中文字幕

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值