STM32学习-20-[I2C]封装常用的功能

I2C.h

/**
  ******************************************************************************
  * @file    i2c.h
  * @author  lykf
  * @version V 1.0.0
  * @date    2026年9月3日
  * @brief   i2c驱动头文件
  ******************************************************************************
  */

#include "stm32f10x.h"

int My_I2C_SendBytes(I2C_TypeDef *I2Cx, uint8_t Addr, const uint8_t *pData, uint16_t Size);
int My_I2C_ReceiveBytes(I2C_TypeDef *I2Cx, uint8_t Addr, uint8_t *pBuffer, uint16_t Size);

I2C.c

/**
  ******************************************************************************
  * @file    i2c.c
  * @author  lykf
  * @version V 1.0.0
  * @date    2026年9月3日
  * @brief   i2c驱动源文件
  ******************************************************************************
  */
#include "i2c.h"

//
// @简介:通过I2C向从机写入多个字节
// 
// @参数 I2Cx:填写要操作的I2C的名称,可以是I2C1或I2C2
// @参数 Addr:填写从机的地址,左对齐 - A6 A5 A4 A3 A2 A1 A0 0
// @参数 pData:要发送的数据(数组)
// @参数 Size:要发送的数据的数量,以字节为单位
//
// @返回值:0 - 发送成功, -1 - 寻址失败, -2 - 数据被拒收
//
__weak int My_I2C_SendBytes(I2C_TypeDef *I2Cx, uint8_t Addr, const uint8_t *pData, uint16_t Size)
{
	// #1. 等待总线空闲
	while(I2C_GetFlagStatus(I2Cx, I2C_FLAG_BUSY) == SET);
	
	// #2. 发送起始位
	I2C_GenerateSTART(I2Cx, ENABLE);
	
	while(I2C_GetFlagStatus(I2Cx, I2C_FLAG_SB) == RESET);
	
	// #3. 寻址阶段
	I2C_ClearFlag(I2Cx, I2C_FLAG_AF);
	
	I2C_SendData(I2Cx, Addr & 0xfe);
	
	while(1)
	{
		if(I2C_GetFlagStatus(I2Cx, I2C_FLAG_ADDR) == SET)
		{
			break;
		}
		if(I2C_GetFlagStatus(I2Cx, I2C_FLAG_AF) == SET)
		{
			I2C_GenerateSTOP(I2Cx, ENABLE);
			return -1; // 寻址失败
		}
	}
	
	// 清除ADDR
	I2C_ReadRegister(I2Cx, I2C_Register_SR1);
	I2C_ReadRegister(I2Cx, I2C_Register_SR2);
	
	// #4. 发送数据
	for(uint16_t i=0; i<Size; i++)
	{
		while(1)
		{
			if(I2C_GetFlagStatus(I2Cx, I2C_FLAG_AF) == SET)
			{
				I2C_GenerateSTOP(I2Cx, ENABLE);
				return -2; // 数据被拒收
			}
			if(I2C_GetFlagStatus(I2Cx, I2C_FLAG_TXE) == SET)
			{
				break;
			}
		}
		
		I2C_SendData(I2Cx, pData[i]);
	}
	
	while(1)
	{
		if(I2C_GetFlagStatus(I2Cx, I2C_FLAG_AF) == SET)
		{
				I2C_GenerateSTOP(I2Cx, ENABLE);
				return -2; // 数据被拒收			
		}
		
		if(I2C_GetFlagStatus(I2Cx, I2C_FLAG_BTF) == SET)
		{
			break;
		}
	}
	
	// #5. 发送停止位
	I2C_GenerateSTOP(I2Cx, ENABLE);
	return 0; // 成功
}


//
// @简介:通过I2C从从机读多个字节
// 
// @参数 I2Cx:填写要操作的I2C的名称,可以是I2C1或I2C2
// @参数 Addr:填写从机的地址,左对齐 - A6 A5 A4 A3 A2 A1 A0 0
// @参数 pBuffer:接收缓冲区(数组)
// @参数 Size:要读取的数据的数量,以字节为单位
//
// @返回值:0 - 发送成功, -1 - 寻址失败
//
__weak int My_I2C_ReceiveBytes(I2C_TypeDef *I2Cx, uint8_t Addr, uint8_t *pBuffer, uint16_t Size)
{
	if(Size == 0) return 0;
	
	// #1. 等待总线空闲
	while(I2C_GetFlagStatus(I2Cx, I2C_FLAG_BUSY) == SET);
	
	// #2. 发送起始位
	I2C_GenerateSTART(I2Cx, ENABLE);
	
	while(I2C_GetFlagStatus(I2Cx, I2C_FLAG_SB) == RESET);
	
	// #3. 寻址阶段
	I2C_ClearFlag(I2Cx, I2C_FLAG_AF);
	
	I2C_SendData(I2Cx, Addr | 0x01);
	
	while(1)
	{
		if(I2C_GetFlagStatus(I2Cx, I2C_FLAG_ADDR) == SET)
		{
			break;
		}
		if(I2C_GetFlagStatus(I2Cx, I2C_FLAG_AF) == SET)
		{
			I2C_GenerateSTOP(I2Cx, ENABLE);
			return -1; // 寻址失败
		}
	}
	
	// #4. 数据读取
	if(Size == 1)
	{
		// 向ACK写0
		I2C_AcknowledgeConfig(I2Cx, DISABLE);
		
		// 清除ADDR
		__disable_irq();
		I2C_ReadRegister(I2Cx, I2C_Register_SR1);
		I2C_ReadRegister(I2Cx, I2C_Register_SR2);
		
		// 发送停止位
		I2C_GenerateSTOP(I2Cx, ENABLE);
		__enable_irq();
		// 等待RxNE置位
		while(I2C_GetFlagStatus(I2Cx, I2C_FLAG_RXNE) == RESET);
		// 读取数据
		pBuffer[0] = I2C_ReceiveData(I2Cx);
	}
	else
	{
		// 向ACK写1
		I2C_AcknowledgeConfig(I2Cx, ENABLE);
		
		// 清除ADDR
		I2C_ReadRegister(I2Cx, I2C_Register_SR1);
		I2C_ReadRegister(I2Cx, I2C_Register_SR2);
		
		for(uint16_t i=0; i<Size-1; i++)
		{
			if(i==Size-2)
			{
				__disable_irq();
			}
			// 等待RxNE置位
			while(I2C_GetFlagStatus(I2Cx, I2C_FLAG_RXNE) == RESET);
			// 读取
			pBuffer[i] = I2C_ReceiveData(I2Cx);
		}
		
		// 向ACK写0
		I2C_AcknowledgeConfig(I2Cx, DISABLE);
		// 发送停止位
		I2C_GenerateSTOP(I2Cx, ENABLE);
		
		__enable_irq();
		
		// 等待RxNE置位
		while(I2C_GetFlagStatus(I2Cx, I2C_FLAG_RXNE) == RESET);
		pBuffer[Size-1] = I2C_ReceiveData(I2Cx);
	}
	
	return 0;
}

SI2C.h

/**
  ******************************************************************************
  * @file    si2c.h
  * @author  lykf
  * @version V 1.0.0
  * @date    2026年9月3日
  * @brief   软i2c驱动头文件
  ******************************************************************************
  */

#include "stm32f10x.h"

typedef struct
{
	GPIO_TypeDef *SCL_GPIOx; // SCL引脚的组编号
	uint16_t SCL_GPIO_Pin;   // SCL引脚的引脚编号
	
	GPIO_TypeDef *SDA_GPIOx; // SCL引脚的组编号
	uint16_t SDA_GPIO_Pin;   // SDA引脚的引脚编号
	
} SI2C_TypeDef;

void My_SI2C_Init(SI2C_TypeDef *SI2C);
int My_SI2C_SendBytes(SI2C_TypeDef *SI2C, uint8_t Addr, const uint8_t *pData, uint16_t Size);
int My_SI2C_ReceiveBytes(SI2C_TypeDef *SI2C, uint8_t Addr, uint8_t *pBuffer, uint16_t Size);

SI2C.c

/**
  ******************************************************************************
  * @file    si2c.c
  * @author  lykf
  * @version V 1.0.0
  * @date    2026年9月3日
  * @brief   软i2c驱动源文件
  ******************************************************************************
  */
#include "si2c.h"

#define scl_w(v) GPIO_WriteBit(SI2C->SCL_GPIOx, SI2C->SCL_GPIO_Pin, ((v)?Bit_SET:Bit_RESET))
#define sda_w(v) GPIO_WriteBit(SI2C->SDA_GPIOx, SI2C->SDA_GPIO_Pin, ((v)?Bit_SET:Bit_RESET))
#define scl_r ((GPIO_ReadInputDataBit(SI2C->SCL_GPIOx, SI2C->SCL_GPIO_Pin) == Bit_SET) ? 1 : 0)
#define sda_r ((GPIO_ReadInputDataBit(SI2C->SDA_GPIOx, SI2C->SDA_GPIO_Pin) == Bit_SET) ? 1 : 0)
void delay(uint32_t us) { for(uint32_t i = 0; i<8*us; i++); }

static uint8_t SendByte(SI2C_TypeDef *SI2C, uint8_t Byte);
static uint8_t ReceiveByte(SI2C_TypeDef *SI2C, uint8_t Ack);
static void SendStop(SI2C_TypeDef *SI2C);


//
// @简介:对软件I2C进行初始化
//
__weak void My_SI2C_Init(SI2C_TypeDef *SI2C)
{
	// #1. 使能SCL引脚的时钟
	if(SI2C->SCL_GPIOx == GPIOA)
	{
		RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
	}
	else if(SI2C->SCL_GPIOx == GPIOB)
	{
		RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);
	}
	else if(SI2C->SCL_GPIOx == GPIOC)
	{
		RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC, ENABLE);
	}
	else if(SI2C->SCL_GPIOx == GPIOD)
	{
		RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOD, ENABLE);
	}
	
	// #2. 对SCL和SDA写1
	GPIO_WriteBit(SI2C->SDA_GPIOx, SI2C->SDA_GPIO_Pin, Bit_SET);
	GPIO_WriteBit(SI2C->SCL_GPIOx, SI2C->SCL_GPIO_Pin, Bit_SET);
	
	// #2. 使能SDA引脚的时钟
	if(SI2C->SDA_GPIOx == GPIOA)
	{
		RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
	}
	else if(SI2C->SDA_GPIOx == GPIOB)
	{
		RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);
	}
	else if(SI2C->SDA_GPIOx == GPIOC)
	{
		RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC, ENABLE);
	}
	else if(SI2C->SDA_GPIOx == GPIOD)
	{
		RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOD, ENABLE);
	}
	
	// #3. 初始化SCL引脚为输出开漏
	GPIO_InitTypeDef GPIO_InitStruct;
	
	GPIO_InitStruct.GPIO_Pin = SI2C->SCL_GPIO_Pin;
	GPIO_InitStruct.GPIO_Mode = GPIO_Mode_Out_OD;
	GPIO_InitStruct.GPIO_Speed = GPIO_Speed_2MHz;
	GPIO_Init(SI2C->SCL_GPIOx, &GPIO_InitStruct);
	
	// #4. 初始化SDA引脚为输出开漏
	
	GPIO_InitStruct.GPIO_Pin = SI2C->SDA_GPIO_Pin;
	GPIO_InitStruct.GPIO_Mode = GPIO_Mode_Out_OD;
	GPIO_InitStruct.GPIO_Speed = GPIO_Speed_2MHz;
	GPIO_Init(SI2C->SDA_GPIOx, &GPIO_InitStruct);
}

//
// @简介:通过软件I2C向从机写入多个字节
// 
// @参数 I2Cx:填写要操作的I2C的名称,可以是I2C1或I2C2
// @参数 Addr:填写从机的地址,左对齐 - A6 A5 A4 A3 A2 A1 A0 0
// @参数 pData:要发送的数据(数组)
// @参数 Size:要发送的数据的数量,以字节为单位
//
// @返回值:0 - 发送成功, -1 - 寻址失败, -2 - 数据被拒收
//
__weak int My_SI2C_SendBytes(SI2C_TypeDef *SI2C, uint8_t Addr, const uint8_t *pData, uint16_t Size)
{
	sda_w(1);
	scl_w(1);
	
	// #1. 发送起始位
	sda_w(0);
	delay(1);
	
	// #2. 发送从机地址+RW
	if(SendByte(SI2C, Addr & 0xfe) != 0)
	{
		SendStop(SI2C);
		return -1; // 寻址失败
	}
	
	// #3. 发送数据
	for(uint16_t i=0; i<Size; i++)
	{
		if(SendByte(SI2C, pData[i]) != 0)
		{
			SendStop(SI2C);
			return -2; // 数据被拒收
		}
	}
	
	// #4. 发送停止位
	SendStop(SI2C);
	
	return 0;
}

//
// @简介:通过软件I2C从从机读多个字节
// 
// @参数 I2Cx:填写要操作的I2C的名称,可以是I2C1或I2C2
// @参数 Addr:填写从机的地址,左对齐 - A6 A5 A4 A3 A2 A1 A0 0
// @参数 pBuffer:接收缓冲区(数组)
// @参数 Size:要读取的数据的数量,以字节为单位
//
// @返回值:0 - 发送成功, -1 - 寻址失败
//
__weak int My_SI2C_ReceiveBytes(SI2C_TypeDef *SI2C, uint8_t Addr, uint8_t *pBuffer, uint16_t Size)
{
	sda_w(1);
	scl_w(1);
	
	// #1. 发送起始位
	sda_w(0);
	delay(1);
	
	// #2. 发送从机地址+RW
	if(SendByte(SI2C, Addr | 0x01) != 0)
	{
		SendStop(SI2C);
		return -1; // 寻址失败
	}
	
	// #3. 接收
	for(uint16_t i=0; i<Size; i++)
	{
		pBuffer[i] = ReceiveByte(SI2C, (i==Size-1) ? 1 : 0);
	}
	
	// #4. 发送停止位
	SendStop(SI2C);
	
	return 0;
}

//
// @简介:发送一个字节
//
// @返回值:0-ACK,其它-NAK
//
static uint8_t SendByte(SI2C_TypeDef *SI2C, uint8_t Byte)
{
	for(int8_t i=7; i>=0; i--)
	{
		scl_w(0); // 将SCL拉低
		sda_w((Byte & (0x01<<i)) ? 1 : 0); // 变SDA的电压
		delay(2); // 延迟1/2周期
		
		scl_w(1); // 将SCL拉高
		delay(2); // 延迟1/2周期
	}
	
	// 读取ACK
	scl_w(0); // 将SCL拉低
	sda_w(1); // 将SDA释放
	delay(2); // 延迟1/4周期
	
	scl_w(1); // 将SCL拉高
	delay(2); // 延迟1/4周期
	
	return sda_r;
}

//
// @简介:发送停止位
//
static void SendStop(SI2C_TypeDef *SI2C)
{
	scl_w(0); // scl拉低
	delay(1); // 延迟1/4周期
	sda_w(0); // sda拉低
	delay(1); // 延迟1/4周期
	scl_w(1); // scl拉高
	delay(1); // 延迟1/4周期
	sda_w(1); // sda拉高
	delay(1); // 延迟1/4周期
}


//
// @简介:从从机读取一个字节的数据
// @参数 Ack:0 - 回NAK,1 - 回ACK
// @返回值:读取到的数据
//
static uint8_t ReceiveByte(SI2C_TypeDef *SI2C, uint8_t Ack)
{
	uint8_t ret = 0;
	
	for(int8_t i=7; i>=0; i--)
	{
		scl_w(0); // scl拉低
		sda_w(1); // 释放SDA
		delay(2); // 延迟1/2周期
		scl_w(1); // scl拉高
		delay(2); // 延迟1/2周期
		
		if(sda_r) // 如果读到的比特位为1
		{
			ret |= 0x01 << i; // 写入比特位
		}
		else // 如果读到的比特位为0
		{
			// 什么也不干
		}
	}
	
	// 回复ACK或NAK
	
	scl_w(0); // scl拉低
	
	if(Ack)
	{
		sda_w(0); // sda拉低
	}
	else
	{
		sda_w(1); // sda拉高
	}
	
	delay(2); // 延迟1/2周期
	
	return ret;
}

posted @ 2026-09-01 10:41  灵垚克府  阅读(12)  评论(0)    收藏  举报