当前位置: 首页 > news >正文

软件I2C

在这里插入图片描述

软件I2C说明

说明,有的单片机没有硬件I2C的功能,或者因为电路设计失误不得不使用软件I2C。
软件I2C虽然引脚设定灵活,但效果自然是比不上硬件I2C的……

具体来说:
虽然软件I2C(也称为Bit-banging I2C)提供了极大的灵活性,允许开发者在任意GPIO引脚上实现I2C通信,但它也有几个明显的缺点:

  1. CPU占用率高:由于所有的I2C协议处理(如时序控制、信号的高低电平转换等)都由CPU直接管理,而不是专用硬件模块完成,因此会占用大量的CPU资源。这意味着,在进行I2C通信时,CPU不能执行其他任务,除非通过中断或DMA等方式来部分缓解这个问题。

  2. 速度受限:由于需要依靠CPU指令来精确地控制时序,软件I2C的速度通常比硬件I2C慢得多。特别是在高速模式下(例如I2C Fast-mode Plus,速度可达1MHz),准确地生成和检测信号变得更加困难,可能无法达到标准要求的速度。

  3. 时序精度问题:不同CPU架构和运行频率下的指令周期时间不同,这使得编写可移植性好的软件I2C代码变得复杂。此外,中断或其他系统活动可能会干扰软件I2C的时序,导致通信失败。

  4. 可靠性较低:与硬件I2C相比,软件I2C更容易受到电磁干扰(EMI)和其他电气噪声的影响,因为其不具备硬件级别的过滤和错误纠正能力。同时,软件实现难以完全保证严格的I2C协议时序要求,尤其是在复杂的系统环境中。

  5. 不支持高级特性:许多现代微控制器的硬件I2C接口支持诸如多主控、时钟扩展(clock stretching)、中断驱动传输、DMA支持等高级特性。而这些特性通常很难或者不可能通过软件I2C来实现。

  6. 开发和维护成本:虽然对于简单的应用来说,软件I2C可以快速实现,但对于更复杂的应用场景,如需要支持多种速率、处理各种异常情况等,则需要更多的开发工作,并且调试起来也更加困难。

综上所述,尽管软件I2C在某些特定情况下(比如当硬件I2C引脚已被占用,或者需要在多个不同的GPIO引脚上实现I2C通信)非常有用,但考虑到性能、稳定性和功能完整性等方面,硬件I2C通常是更好的选择。如果条件允许,使用硬件I2C可以减少开发时间和提高系统的整体性能。

软件I2C代码

注意,根据自己使用的单片机修改IO口输入输出的相关实现!
实现软件微秒级延时的代码
soft_delay.h

#pragma once#ifdef __cplusplus
extern "C" {
#endif#include <stdint.h>//移植时请根据自身单片机修改/*** @brief 软件微秒级延时* * @param us 微秒数* * @note 需要根据系统频率简单修改* @note 不怎么准,但一般不需要很准* @note 准确的延时需要借助硬件定时器等*/
void softDelayMicro(uint8_t us);#ifdef __cplusplus
}
#endif

soft_delay.c

#include "soft_delay.h"// 定义每微秒需要执行的大约循环次数
// 注意:这个值可能需要根据实际情况进行调整
// 例如我的stm32f405频率168MHz
#define DELAY_MICROSECOND_LOOP_COUNT (168 / 4)#pragma GCC push_options    //禁止编译器优化这段代码
#pragma GCC optimize ("O0")void softDelayMicro(uint8_t us)
{while (us--) {volatile uint32_t counter = DELAY_MICROSECOND_LOOP_COUNT;while (counter--);}
}#pragma GCC pop_options

实现软件I2C的代码

soft_I2C.h

/*** @file soft_I2C.h* @author your name (you@domain.com)* @brief 软件I2C* @version 0.1* @date 2025-05-12* * @copyright Copyright (c) 2025* * @note //根据你的单片机修改IO口的输入输出的相关内容!*/#pragma once#ifdef __cplusplus
extern "C" {
#endif#include <stdbool.h>#include "gpio.h"//根据你的单片机修改IO口的输入输出的相关内容!
#include "soft_delay.h"/*** @brief 保存某个I2C总线使用的端口引脚信息* */
typedef struct
{GPIO_TypeDef* SCL_Port; //时钟线端口uint32_t SCL_Pin;       //时钟线引脚GPIO_TypeDef* SDA_Port; //数据线端口uint32_t SDA_Pin;       //数据线引脚
}Soft_I2C_Handle;/*** @brief 设置软件I2c使用的端口和引脚* * @param si2c 指向Soft_I2C_Handle结构体的指针,该结构体包含指定软件I2C的配置信息。* @param scl_port 时钟线端口* @param scl_pin 时钟线引脚* @param sda_port 数据线端口* @param sda_pin 数据线引脚* * @note 请先完成对应端口的初始化配置,然后再使用这个函数*/
void Soft_I2C_Config(Soft_I2C_Handle* si2c, GPIO_TypeDef* scl_port, uint32_t scl_pin, GPIO_TypeDef* sda_port, uint32_t sda_pin);/*** @brief 软件I2C以主模式发送一定数量的数据。* * @param si2c 指向Soft_I2C_Handle结构体的指针,该结构体包含指定软件I2C的配置信息。* @param DevAddress 目标设备地址:在调用接口之前,必须将数据表中的设备7位地址值左移1位* @param pData 指向数据缓冲区的指针* @param Size 要发送的数据量* @return true 成功* @return false 失败*/
bool Soft_I2C_Master_Transmit(Soft_I2C_Handle* si2c, uint16_t DevAddress, uint8_t *pData, uint16_t Size);/*** @brief 软件I2C以主模式接收一定数量的数据。* * @param si2c 指向Soft_I2C_Handle结构体的指针,该结构体包含指定软件I2C的配置信息。* @param DevAddress 目标设备地址:在调用接口之前,必须将数据表中的设备7位地址值左移1位* @param pData 指向数据缓冲区的指针* @param Size 要发送的数据量* @return true 成功* @return false 失败*/
bool Soft_I2C_Master_Receive(Soft_I2C_Handle* si2c, uint16_t DevAddress, uint8_t *pData, uint16_t Size);#ifdef __cplusplus
}
#endif

soft_I2C.c

/*** @file soft_I2C.c* @author your name (you@domain.com)* @brief 软件I2C* @version 0.1* @date 2025-05-12* * @copyright Copyright (c) 2025* */#include "soft_I2C.h"/****************************** 1. 配置函数 *****************************************///移植时请根据自身单片机修改void Soft_I2C_Config(Soft_I2C_Handle* si2c, GPIO_TypeDef* scl_port, uint32_t scl_pin, GPIO_TypeDef* sda_port, uint32_t sda_pin)
{si2c->SCL_Port = scl_port;si2c->SCL_Pin = scl_pin;si2c->SDA_Port = sda_port;si2c->SDA_Pin = sda_pin;
}/****************************** 2. 辅助宏/函数定义 *****************************************/#define I2C_PIN_HIGH GPIO_PIN_SET
#define I2C_PIN_LOW  GPIO_PIN_RESET//移植时请根据自身单片机修改static inline void SCL_Low(Soft_I2C_Handle* si2c) {HAL_GPIO_WritePin(si2c->SCL_Port, si2c->SCL_Pin, GPIO_PIN_RESET);
}static inline void SCL_High(Soft_I2C_Handle* si2c) {HAL_GPIO_WritePin(si2c->SCL_Port, si2c->SCL_Pin, GPIO_PIN_SET);
}static inline void SDA_Low(Soft_I2C_Handle* si2c) {HAL_GPIO_WritePin(si2c->SDA_Port, si2c->SDA_Pin, GPIO_PIN_RESET);
}static inline void SDA_High(Soft_I2C_Handle* si2c) {HAL_GPIO_WritePin(si2c->SDA_Port, si2c->SDA_Pin, GPIO_PIN_SET);
}static inline GPIO_PinState SDA_Read(Soft_I2C_Handle* si2c) {return HAL_GPIO_ReadPin(si2c->SDA_Port, si2c->SDA_Pin);
}/*** @brief 设置SDA引脚为输入* * @param si2c */
static void SDA_SetInput(Soft_I2C_Handle* si2c)
{GPIO_InitTypeDef GPIO_InitStruct = {0};GPIO_InitStruct.Pin = si2c->SDA_Pin;GPIO_InitStruct.Mode = GPIO_MODE_INPUT;GPIO_InitStruct.Pull = GPIO_NOPULL;HAL_GPIO_Init(si2c->SDA_Port, &GPIO_InitStruct);
}/*** @brief 设置SDA引脚为输出* * @param si2c */
static void SDA_SetOutput(Soft_I2C_Handle* si2c)
{GPIO_InitTypeDef GPIO_InitStruct = {0};GPIO_InitStruct.Pin = si2c->SDA_Pin;GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_OD;GPIO_InitStruct.Pull = GPIO_NOPULL;GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;HAL_GPIO_Init(si2c->SDA_Port, &GPIO_InitStruct);
}/****************************** 3. I2C 协议辅助函数 *****************************************//*** @brief 产生IIC起始信号* * @param si2c */
static void I2C_Start(Soft_I2C_Handle* si2c) {SDA_SetOutput(si2c);  SDA_High(si2c);SCL_High(si2c);softDelayMicro(4);SDA_Low(si2c);//START:当CLK高时,DATA由高变低softDelayMicro(4);SCL_Low(si2c);//钳住I2C总线,准备发送或接收数据
}/*** @brief 产生IIC停止信号* * @param si2c */
static void I2C_Stop(Soft_I2C_Handle* si2c) {SDA_SetOutput(si2c);SCL_Low(si2c);SDA_Low(si2c);//STOP:当CLK高时,数据由低变高softDelayMicro(4);SCL_High(si2c);SDA_High(si2c);//发送I2C总线结束信号softDelayMicro(4);
}static void I2C_SendByte(Soft_I2C_Handle* si2c, uint8_t byte) {SDA_SetOutput(si2c);for (int i = 0; i < 8; i++) {SCL_Low(si2c);if (byte & 0x80)SDA_High(si2c);elseSDA_Low(si2c);softDelayMicro(1);SCL_High(si2c);softDelayMicro(1);byte <<= 1;}
}/*** @brief 等待应答信号到来* * @param si2c * @return true 接收应答成功* @return false 接收应答失败* */
static bool I2C_Wait_Ack(Soft_I2C_Handle* si2c) {SCL_Low(si2c);SDA_SetInput(si2c);SCL_High(si2c);softDelayMicro(1);uint8_t cycleTimes = 0;while(SDA_Read(si2c) != I2C_PIN_LOW)// 从机拉低表示 ACK{if(cycleTimes > 250){I2C_Stop(si2c);return false;}cycleTimes++;softDelayMicro(1);}SCL_Low(si2c);return true;
}static uint8_t I2C_ReceiveByte(Soft_I2C_Handle* si2c) {uint8_t byte = 0;SDA_SetInput(si2c);for (int i = 0; i < 8; i++) {SCL_Low(si2c);softDelayMicro(1);SCL_High(si2c);byte <<= 1;if (SDA_Read(si2c) == I2C_PIN_HIGH)byte |= 0x01;softDelayMicro(1);}return byte;
}/*** @brief 产生ACK应答* * @param si2c */
static void I2C_Ack(Soft_I2C_Handle* si2c) {SCL_Low(si2c);SDA_SetOutput(si2c);SDA_Low(si2c);softDelayMicro(2);SCL_High(si2c);softDelayMicro(2);SCL_Low(si2c);
}/*** @brief 不产生ACK应答* * @param si2c */
static void I2C_Nack(Soft_I2C_Handle* si2c) {SCL_Low(si2c);SDA_SetOutput(si2c);SDA_High(si2c);softDelayMicro(2);SCL_High(si2c);softDelayMicro(2);SCL_Low(si2c);
}/****************** 4. 软件I2C读写函数实现 ***************/bool Soft_I2C_Master_Transmit(Soft_I2C_Handle* si2c, uint16_t DevAddress, uint8_t* pData, uint16_t Size) {I2C_Start(si2c);I2C_SendByte(si2c, DevAddress | 0x00);if (!I2C_Wait_Ack(si2c)) return false;for (uint16_t i = 0; i < Size; i++) {I2C_SendByte(si2c, pData[i]);if (!I2C_Wait_Ack(si2c)) return false;}I2C_Stop(si2c);return true;
}bool Soft_I2C_Master_Receive(Soft_I2C_Handle* si2c, uint16_t DevAddress, uint8_t* pData, uint16_t Size) {I2C_Start(si2c);I2C_SendByte(si2c, DevAddress | 0x01);if (!I2C_Wait_Ack(si2c)) return false;for (uint16_t i = 0; i < Size; i++) {pData[i] = I2C_ReceiveByte(si2c);if (i < Size - 1)I2C_Ack(si2c);elseI2C_Nack(si2c);}I2C_Stop(si2c);return true;
}

代码使用说明

1、根据自己的单片机修改相关内容
2、创造句柄并配置引脚(和硬件I2C类似)
3、进行读写操作(和硬件I2C类似)

以温湿度传感器AHT20为例
aht20.h

#ifndef __DHT20_H__
#define __DHT20_H__#ifdef __cplusplus
extern "C" {
#endif#include "main.h"
#include "soft_I2C.h"// 初始化AHT20
void AHT20_Init();// 获取温度和湿度
void AHT20_Read(float *Temperature, float *Humidity);#ifdef __cplusplus
}
#endif#endif

aht20.c

#include "aht20.h"#define AHT20_ADDRESS 0x70uint8_t readBuffer[6] = {0};Soft_I2C_Handle AHT20_I2C;/*** @brief  初始化AHT20*/
void AHT20_Init()
{Soft_I2C_Config(&AHT20_I2C, AHT20_SCL_GPIO_Port, AHT20_SCL_Pin, AHT20_SDA_GPIO_Port, AHT20_SDA_Pin);uint8_t readBuffer;HAL_Delay(40);Soft_I2C_Master_Receive(&AHT20_I2C, AHT20_ADDRESS, &readBuffer, 1);if ((readBuffer & 0x08) == 0x00){uint8_t sendBuffer[3] = {0xBE, 0x08, 0x00};Soft_I2C_Master_Transmit(&AHT20_I2C, AHT20_ADDRESS, sendBuffer, 3);}
}/*** @brief  获取温度和湿度* @param  Temperature: 存储获取到的温度* @param  Humidity: 存储获取到的湿度*/
void AHT20_Read(float *Temperature, float *Humidity)
{uint8_t sendBuffer[3] = {0xAC, 0x33, 0x00};uint8_t readBuffer[6] = {0};Soft_I2C_Master_Transmit(&AHT20_I2C, AHT20_ADDRESS, sendBuffer, 3);HAL_Delay(75);Soft_I2C_Master_Receive(&AHT20_I2C, AHT20_ADDRESS, readBuffer, 6);if ((readBuffer[0] & 0x80) == 0x00){uint32_t data = 0;data = ((uint32_t)readBuffer[3] >> 4) + ((uint32_t)readBuffer[2] << 4) + ((uint32_t)readBuffer[1] << 12);*Humidity = data * 100.0f / (1 << 20);data = (((uint32_t)readBuffer[3] & 0x0F) << 16) + ((uint32_t)readBuffer[4] << 8) + (uint32_t)readBuffer[5];*Temperature = data * 200.0f / (1 << 20) - 50;}
}

相关文章:

  • MCP-RAG 服务器:完整设置和使用指南
  • 图片的require问题
  • 前端工程化:从 Webpack 到 Vite
  • React+Webpack 脚手架、前端组件库搭建
  • 华为鸿蒙电脑能否作为开发机?开发非鸿蒙应用?
  • 力扣第156场双周赛
  • Angular | 利用 `ChangeDetectorRef` 解决 Angular 动态显示输入框的聚焦问题
  • linux入门学习(介绍、常用命令、vim、shell)
  • Leetcode 3543. Maximum Weighted K-Edge Path
  • linux系统如何将采集的串口数据存储到txt
  • aardio - 虚表 —— CheckBox列使用方法
  • 【HBase整合Hive】HBase-1.4.8整合Hive-2.3.3过程
  • 重构门店网络:从“打补丁“到“造地基“的跨越
  • 测试集群的功能-执行wordcount程序
  • 什么是 SSM 框架?
  • Nature图形复现—两种快速绘制热图的方法
  • c# UTC 时间赋值注意事项
  • 为什么GOOSE通讯需要MAC地址?
  • 游戏资源传输服务器
  • Android中RelativeLayout相对布局使用详解
  • 成都警方通报:8岁男孩落水父母下水施救,父亲遇难
  • 人才争夺战,二三线城市和一线城市拼什么?洛阳官方调研剖析
  • 熊出没!我驻日本札幌总领馆提示中国公民注意人身安全
  • 这些网红果蔬正在收割你的钱包,营养师:吃了个寂寞
  • 专访|日本驻华大使金杉宪治:对美、对华外交必须在保持平衡的基础上稳步推进
  • 种罂粟喂鸡防病?四川广元一村民非法种植毒品原植物被罚​