STM32的IO模拟I2C主机与硬件I2C从机实现
一、系统架构设计
-
通信模式
- 主机:通过GPIO模拟I2C时序(软件I2C)
- 从机:使用STM32硬件I2C外设
- 总线拓扑:单主机-单从机通信(支持扩展多从机)
-
关键参数
- 通信速率:标准模式100Kbps(软件模拟需精确延时)
- 总线配置:开漏输出模式 + 4.7kΩ上拉电阻
- 地址分配:从机7位地址(如0x50~0x57)
二、硬件设计
1. 电路连接
| 主机(STM32) | 从机(STM32) | 功能说明 |
|---|---|---|
| PA0(模拟SCL) | PB6(硬件SCL) | 开漏输出,需上拉电阻 |
| PA1(模拟SDA) | PB7(硬件SDA) | 开漏输出,需上拉电阻 |
| GND | GND | 共地 |
2. 硬件注意事项
- 上拉电阻:总线空闲时保持高电平(推荐4.7kΩ)
- 滤波电容:在SCL/SDA线并联0.1μF陶瓷电容抑制噪声
- 电平匹配:确保IO电平兼容(3.3V/5V系统需电平转换)
三、软件实现
1. 主机(软件I2C)
(1) 关键函数实现
// i2c_soft.c
#include "stm32f10x.h"
#define SDA_PIN PA1
#define SCL_PIN PA0
void I2C_Soft_Init(void) {
GPIO_InitTypeDef GPIO_InitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
GPIO_InitStructure.GPIO_Pin = SDA_PIN | SCL_PIN;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_OD; // 开漏输出
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure);
I2C_Soft_Start(); // 初始化总线为高电平
}
// 产生起始信号
void I2C_Soft_Start(void) {
SDA_OUT(); // 设置SDA为输出
SDA_H(); // SDA高
SCL_H(); // SCL高
delay_us(5);
SDA_L(); // SDA拉低
delay_us(5);
SCL_L(); // SCL拉低
}
// 发送一个字节
void I2C_Soft_SendByte(uint8_t data) {
for (int i=0; i<8; i++) {
SDA_OUT();
if (data & 0x80) SDA_H(); else SDA_L();
data <<= 1;
SCL_H(); delay_us(5);
SCL_L(); delay_us(5);
}
// 接收应答
SDA_IN();
SCL_H(); delay_us(5);
while (SDA_READ()); // 等待从机拉低SDA
SCL_L();
}
// 接收一个字节
uint8_t I2C_Soft_RecvByte(uint8_t ack) {
uint8_t data = 0;
SDA_IN();
for (int i=0; i<8; i++) {
SCL_H(); delay_us(5);
data <<= 1;
if (SDA_READ()) data |= 0x01;
SCL_L(); delay_us(5);
}
// 发送应答
SDA_OUT();
if (ack) SDA_L(); else SDA_H();
SCL_H(); delay_us(5);
SCL_L(); delay_us(5);
return data;
}
(2) 通信流程示例
// 向从机地址0x50写入数据0x12
void I2C_WriteData(uint8_t addr, uint8_t data) {
I2C_Soft_Start();
I2C_Soft_SendByte((addr << 1) | 0x00); // 写模式
while (!I2C_Soft_CheckAck()); // 等待应答
I2C_Soft_SendByte(data);
while (!I2C_Soft_CheckAck());
I2C_Soft_Stop();
}
2. 从机(硬件I2C)
(1) CubeMX配置
- 时钟配置:APB1时钟≥1MHz(硬件I2C最低要求)
- 地址设置:7位地址(如0x50)
- 中断优先级:设置为最高(避免通信阻塞)
(2) 关键代码
// i2c_slave.c
#include "stm32f10x.h"
void I2C_Slave_Init(void) {
I2C_InitTypeDef I2C_InitStructure;
RCC_APB1PeriphClockCmd(RCC_APB1Periph_I2C1, ENABLE);
I2C_InitStructure.I2C_Mode = I2C_Mode_Slave;
I2C_InitStructure.I2C_AddressingMode = I2C_AddressingMode_7bit;
I2C_InitStructure.I2C_OwnAddress1 = 0x50 << 1; // 7位地址左移
I2C_InitStructure.I2C_Ack = I2C_Ack_Enable;
I2C_Init(I2C1, &I2C_InitStructure);
NVIC_EnableIRQ(I2C1_EV_IRQn); // 使能事件中断
I2C_Cmd(I2C1, ENABLE);
}
// 中断服务函数
void I2C1_EV_IRQHandler(void) {
if (I2C_CheckEvent(I2C1, I2C_EVENT_SLAVE_RECEIVE)) {
uint8_t data = I2C_ReceiveData(I2C1); // 读取数据
// 处理接收到的数据
}
}
四、通信协议实现
1. 主从通信流程
主机操作 从机响应
1. 发送起始信号(S)
2. 发送从机地址+写位(0)
3. 等待从机应答(ACK)
4. 发送寄存器地址
5. 发送数据
6. 发送停止信号(P)
2. 关键时序参数
| 参数 | 软件模拟要求 | 硬件I2C要求 |
|---|---|---|
| 起始/停止时间 | >4.7μs | 自动满足 |
| 时钟低电平 | >4.7μs | 由硬件控制 |
| 时钟高电平 | >4.0μs | 由硬件控制 |
参考代码 STM32的IO模拟I2C主机_硬件I2C从机 www.youwenfan.com/contentcsr/102056.html
五、调试与问题排查
1. 常见问题
| 现象 | 可能原因 | 解决方案 |
|---|---|---|
| 无应答(ACK) | 从机地址错误 | 检查7位地址配置 |
| 数据错误 | 总线干扰/噪声 | 添加总线滤波电容 |
| 通信中断 | 硬件I2C中断未使能 | 在NVIC中启用中断 |
| SDA线持续低电平 | 总线锁定(死锁) | 复位I2C外设并重新初始化 |
2. 调试工具
- 逻辑分析仪:捕获SCL/SDA波形,验证时序(如中的示波器截图)
- 串口打印:输出通信状态(如ACK接收情况)
- I2C扫描工具:检测总线上的从机地址