如何基于stm32实现IIC通信的IO口模拟

在 STM32 上实现 IIC 的 IO 口模拟(bit-banging),核心是通过 GPIO 口模拟 IIC 通信的时序逻辑(SCL 时钟线和 SDA 数据线的高低电平变化)。

以下是具体实现方案:

一、硬件基础

IIC 通信需要两根线:

  • SCL:时钟线(双向,由主机控制)
  • SDA:数据线(双向,主机 / 从机均可控制)

硬件要求

  1. 选择 STM32 的两个 GPIO 口(例如 PA6=SCL,PA7=SDA)
  1. 必须外接上拉电阻(通常 4.7KΩ),因为 IIC 总线通过线与逻辑工作(低电平有效,高电平由上拉维持)
  1. GPIO 配置为开漏输出模式(OD),允许外部设备拉低电平,同时支持输入模式切换(读取 SDA 状态)

二、软件实现核心

模拟 IIC 需要实现以下基础时序函数:

  • 起始信号(Start)
  • 停止信号(Stop)
  • 发送一个字节(SendByte)
  • 接收一个字节(ReadByte)
  • 发送应答(Ack)/ 非应答(Nack)
1. 引脚定义与初始化

首先定义 SCL 和 SDA 的引脚,并初始化 GPIO 为开漏输出模式。

头文件(iic.h)

#ifndef __IIC_H

#define __IIC_H

#include "stm32f1xx_hal.h" // 根据实际型号修改

// 引脚定义(可根据实际硬件修改)

#define IIC_SCL_PORT GPIOA

#define IIC_SDA_PORT GPIOA

#define IIC_SCL_PIN GPIO_PIN_6

#define IIC_SDA_PIN GPIO_PIN_7

// 宏定义:设置SCL/SDA高低电平

#define IIC_SCL_HIGH HAL_GPIO_WritePin(IIC_SCL_PORT, IIC_SCL_PIN, GPIO_PIN_SET)

#define IIC_SCL_LOW HAL_GPIO_WritePin(IIC_SCL_PORT, IIC_SCL_PIN, GPIO_PIN_RESET)

#define IIC_SDA_HIGH HAL_GPIO_WritePin(IIC_SDA_PORT, IIC_SDA_PIN, GPIO_PIN_SET)

#define IIC_SDA_LOW HAL_GPIO_WritePin(IIC_SDA_PORT, IIC_SDA_PIN, GPIO_PIN_RESET)

// 读取SDA电平(需先切换为输入模式)

#define IIC_SDA_READ HAL_GPIO_ReadPin(IIC_SDA_PORT, IIC_SDA_PIN)

// 函数声明

void IIC_Init(void); // 初始化IIC引脚

void IIC_Start(void); // 发送起始信号

void IIC_Stop(void); // 发送停止信号

uint8_t IIC_SendByte(uint8_t data); // 发送一个字节,返回应答状态

uint8_t IIC_ReadByte(uint8_t ack); // 接收一个字节,ack=1发送应答,0发送非应答

#endif

2. 核心时序实现

源文件(iic.c)#include "iic.h"

// 延时函数(关键!需根据IIC速率调整,标准模式100KHz需约5us延时)

static void IIC_Delay(void) {

uint8_t i;

// 循环次数根据MCU主频调整(示例:STM32F1 72MHz下约5us)

for (i = 0; i < 10; i++);

}

// 初始化IIC引脚(开漏输出,初始高电平)

void IIC_Init(void) {

GPIO_InitTypeDef GPIO_InitStruct = {0};

// 使能GPIO时钟(根据实际端口修改)

__HAL_RCC_GPIOA_CLK_ENABLE();

// 配置SCL和SDA为开漏输出

GPIO_InitStruct.Pin = IIC_SCL_PIN | IIC_SDA_PIN;

GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_OD; // 开漏输出

GPIO_InitStruct.Pull = GPIO_NOPULL; // 外部上拉,内部不使能

GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;

HAL_GPIO_Init(IIC_SCL_PORT, &GPIO_InitStruct);

// 初始化为高电平(空闲状态)

IIC_SCL_HIGH;

IIC_SDA_HIGH;

}

// 发送起始信号:SCL高电平时,SDA从高→低

void IIC_Start(void) {

IIC_SDA_HIGH; // 先确保SDA为高

IIC_SCL_HIGH;

IIC_Delay();

IIC_SDA_LOW; // SDA拉低,产生起始信号

IIC_Delay();

IIC_SCL_LOW; // 拉低SCL,准备发送数据

}

// 发送停止信号:SCL高电平时,SDA从低→高

void IIC_Stop(void) {

IIC_SDA_LOW; // 先确保SDA为低

IIC_SCL_HIGH;

IIC_Delay();

IIC_SDA_HIGH; // SDA拉高,产生停止信号

IIC_Delay();

}

// 发送一个字节,返回0表示接收应答,1表示无应答

uint8_t IIC_SendByte(uint8_t data) {

uint8_t i, ack;

for (i = 0; i < 8; i++) { // 逐位发送(高位先送)

// SCL低电平时,准备数据

IIC_SCL_LOW;

IIC_Delay();

// 设置SDA电平(发送当前位)

if (data & 0x80) IIC_SDA_HIGH;

else IIC_SDA_LOW;

data <<= 1; // 移位准备下一位

IIC_Delay();

// SCL拉高,从机读取数据

IIC_SCL_HIGH;

IIC_Delay();

}

// 接收应答位(第9个时钟)

IIC_SCL_LOW; // 拉低SCL,准备读取应答

IIC_SDA_HIGH; // 释放SDA(由从机控制)

IIC_Delay();

IIC_SCL_HIGH; // SCL拉高,读取应答

ack = IIC_SDA_READ; // 若ack=0表示从机应答,1表示非应答

IIC_Delay();

IIC_SCL_LOW; // 拉低SCL,结束应答阶段

return ack;

}

// 接收一个字节,ack=1发送应答,0发送非应答

uint8_t IIC_ReadByte(uint8_t ack) {

uint8_t i, data = 0;

IIC_SDA_HIGH; // 释放SDA,准备接收数据

for (i = 0; i < 8; i++) { // 逐位接收(高位先收)

IIC_SCL_LOW; // 拉低SCL,从机准备数据

IIC_Delay();

IIC_SCL_HIGH; // SCL拉高,读取数据

data <<= 1; // 移位

if (IIC_SDA_READ) data |= 0x01; // 读取当前位

IIC_Delay();

}

// 发送应答/非应答(第9个时钟)

IIC_SCL_LOW; // 拉低SCL,准备发送应答

if (ack) IIC_SDA_LOW; // 应答:拉低SDA

else IIC_SDA_HIGH; // 非应答:拉高SDA

IIC_Delay();

IIC_SCL_HIGH; // SCL拉高,从机读取应答

IIC_Delay();

IIC_SCL_LOW; // 拉低SCL,结束应答阶段

return data;

}

三、关键注意事项

  1. 延时精度:IIC_Delay()的时长直接影响通信速率(标准模式 100KHz 需时钟周期约 10us,快速模式 400KHz 需约 2.5us),需根据实际 MCU 主频调试。
  1. 引脚模式切换:若需频繁切换 SDA 为输入(如读取数据时),可在读取前将 GPIO 模式改为输入,读完后改回开漏输出(上述代码通过开漏输出 + 释放 SDA(拉高)实现输入功能,简化了模式切换)。
  1. 上拉电阻:必须外接,否则总线无法维持高电平,通信会失败。
  1. 应答处理:从机接收数据后需发送应答(ACK),主机接收完最后一个字节后通常发送非应答(NACK),然后发送停止信号。

四、应用示例(读取从机数据)

以读取某个 IIC 从机(地址 0xA0)的 0x00 寄存器为例:

// 读取从机数据

uint8_t IIC_ReadFromSlave(uint8_t slave_addr, uint8_t reg_addr, uint8_t *data, uint8_t len) {

IIC_Start();

// 发送从机写地址(bit0=0表示写)

if (IIC_SendByte(slave_addr & 0xFE)) {

IIC_Stop();

return 1; // 从机无应答

}

// 发送寄存器地址

if (IIC_SendByte(reg_addr)) {

IIC_Stop();

return 1;

}

// 重新发送起始信号,切换为读模式

IIC_Start();

// 发送从机读地址(bit0=1表示读)

if (IIC_SendByte(slave_addr | 0x01)) {

IIC_Stop();

return 1;

}

// 读取数据

for (uint8_t i = 0; i < len; i++) {

// 最后一个字节发送非应答,其余发送应答

data[i] = IIC_ReadByte((i != len - 1) ? 1 : 0);

}

IIC_Stop();

return 0; // 成功

}

通过上述实现,即可在 STM32 上通过 GPIO 模拟 IIC 通信,兼容绝大多数 IIC 设备(如传感器、EEPROM 等)。

核心是保证时序的准确性,需根据实际设备的时序要求调整延时函数。

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

start_up_go

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

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

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

打赏作者

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

抵扣说明:

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

余额充值