CH579软件I2C模拟扩展接口资源
在物联网设备越来越“小而智能”的今天,我们常常会遇到这样一个尴尬的局面:主控芯片功能强大、蓝牙连得稳、算力也够用,可一到接几个传感器——温度、气压、RTC、OLED屏……好家伙,I²C总线直接“堵车”了!😱
尤其是像 CH579 这种集成了 RISC-V 内核和 BLE 无线通信的高性能 MCU,虽然片上外设丰富,但原生硬件 I²C 接口通常只有1~2路。当你想同时挂载 EEPROM、环境传感器、实时时钟和显示屏时,立马就捉襟见肘。
怎么办?加个I/O扩展芯片?成本上去了,PCB也复杂了。不如换个思路—— 用GPIO“手搓”一个I²C总线出来 !
没错,这就是传说中的 软件模拟I2C(Software Bit-Banging I2C) ,一种“以时间换空间”的经典嵌入式技巧。它不需要额外硬件,只要两个普通IO口,就能让你的CH579轻松多出一条甚至多条I²C通道,简直是“穷开发者的福音”✨。
🧠 软件I2C到底是什么?
简单来说,就是不用芯片内置的I²C控制器,而是靠代码手动控制SCL(时钟)和SDA(数据)引脚的电平变化,一点一点“捏”出符合I²C协议的波形。
听起来有点“土”,但它真的很实用!
I²C本身是开漏结构,通过上拉电阻实现“线与”逻辑。所以在CH579上实现时,关键在于:
- 输出低电平 → 强制拉低(推挽输出)
- 输出高电平 → 不驱动,靠外部上拉“浮”上去(输入态 + 关闭内部上拉)
⚠️ 注意:CH579默认GPIO为推挽输出,不能直接设为高阻态或开漏,必须通过切换输入/输出模式来模拟开漏行为。
举个例子,你想让SDA变“高”,不能直接
GPIO_SetHigh()
,而应该:
GPIOB->DIR &= ~(1 << SDA_PIN); // 设为输入
GPIOB->PU &= ~(1 << SDA_PIN); // 关闭内部弱上拉(避免干扰)
这样引脚相当于“释放”,由外部4.7kΩ上拉电阻拉高,完美复现I²C物理层特性。
⏱️ 时序怎么控?延时函数很关键!
I²C协议对时序有严格要求,比如标准模式下SCL周期至少10μs,快速模式下也要≥2.5μs。由于没有硬件定时器自动打拍子,全靠CPU轮询+延时函数来卡节奏。
在CH579运行于48MHz主频的情况下,一个简单的空循环就可以实现微秒级延时:
static void i2c_delay_us(uint32_t us) {
volatile uint32_t n = us * 6; // 经验值,每μs约6个循环
while (n--);
}
当然啦,这方法依赖编译优化等级和指令执行速度,不够精确。如果你追求更高稳定性,可以用内联汇编或者SysTick定时器做更精准的延迟 💡。
不过对于大多数传感器(如BMP280、DS1307等),通信频率不高、数据量小,这种“软延时”完全够用,而且代码简洁易调试!
🔧 核心操作函数怎么写?
下面这几个基础函数,是你搭建软件I2C的“四大金刚”👇
✅ 起始信号(Start Condition)
SCL为高时,SDA从高→低跳变
void i2c_start(void) {
set_sda(1); i2c_delay_us(2);
set_scl(1); i2c_delay_us(2);
set_sda(0); i2c_delay_us(2); // SDA下降沿
set_scl(0); i2c_delay_us(2); // 拉低SCL准备发送数据
}
✅ 停止信号(Stop Condition)
SCL为高时,SDA从低→高跳变
void i2c_stop(void) {
set_sda(0); i2c_delay_us(2);
set_scl(1); i2c_delay_us(2); // 先释放SCL
set_sda(1); i2c_delay_us(2); // SDA上升沿
}
✅ 发送一个字节 + 读ACK
每次发完8位后,主机要释放SDA,读取从机是否回应ACK(拉低表示确认)
uint8_t i2c_write_byte(uint8_t byte) {
for (int i = 0; i < 8; i++) {
set_scl(0); i2c_delay_us(1);
(byte & 0x80) ? set_sda(1) : set_sda(0);
i2c_delay_us(1);
set_scl(1); i2c_delay_us(2); // 上升沿采样
set_scl(0);
byte <<= 1;
}
// 读ACK
set_sda(1); i2c_delay_us(1); // 主机释放SDA
set_scl(1); i2c_delay_us(2);
uint8_t ack = (GPIOB->PIN & (1 << SDA_PIN)) ? 1 : 0; // 0=收到ACK
set_scl(0);
return ack;
}
✅ 读取一个字节(支持NACK结尾)
接收完最后一个字节时,主机应返回NACK以通知结束
uint8_t i2c_read_byte(uint8_t nack) {
uint8_t byte = 0;
set_sda(1); // 释放总线,允许从机驱动
for (int i = 0; i < 8; i++) {
i2c_delay_us(1);
set_scl(1); i2c_delay_us(1);
byte = (byte << 1) | ((GPIOB->PIN >> SDA_PIN) & 0x01);
set_scl(0);
}
// 发送ACK/NACK
set_scl(0); i2c_delay_us(1);
nack ? set_sda(1) : set_sda(0);
i2c_delay_us(1);
set_scl(1); i2c_delay_us(2); // 完成应答
set_scl(0);
set_sda(1); // 再次释放SDA
return byte;
}
这些函数组合起来,就可以实现任意I²C设备的读写操作啦!
🛠️ 实战案例:读取AT24C02 EEPROM
假设你要从地址
0x50
的EEPROM中读取某个寄存器的数据,典型的“随机读”流程如下:
uint8_t eeprom_read_byte(uint8_t dev_addr, uint8_t reg_addr) {
i2c_start();
i2c_write_byte(dev_addr << 1); // 写命令
i2c_write_byte(reg_addr); // 指定地址
i2c_start(); // 重复起始条件
i2c_write_byte((dev_addr << 1) | 1); // 读命令
uint8_t data = i2c_read_byte(1); // 最后一次读取发NACK
i2c_stop();
return data;
}
整个过程清晰明了,逻辑严密,配合逻辑分析仪一看,波形标准得很!🎯
🔄 和硬件I2C比,到底值不值得用?
| 对比项 | 软件I2C | 硬件I2C |
|---|---|---|
| 引脚自由度 | ✅ 任意GPIO都行 | ❌ 固定引脚 |
| CPU占用 | ⚠️ 占用较多,阻塞式 | ✅ DMA/中断处理,几乎不打扰CPU |
| 最高速率 | ~200kbps(实际建议≤100kbps) | 支持400kbps以上 |
| 多任务影响 | ⚠️ 长时间占用可能导致调度延迟 | ✅ 异步非阻塞 |
| 开发难度 | ✅ 几十个函数搞定,适合新手 | ❌ 寄存器配置繁琐,需懂中断机制 |
| 扩展能力 | ✅ 可并行开多组模拟总线 | ❌ 一般最多2路 |
📌 所以结论很明确:
如果你接的是低速、偶尔访问的传感器(比如温湿度、RTC、配置存储),那软件I2C香爆了!
但如果要跑高速设备(如音频Codec、高速ADC),还是老老实用硬件I2C吧~
🏗️ 典型系统架构设计
来看一个真实场景:基于CH579的智能家居节点,需要连接四个I²C设备:
+------------+
| |
Hardware I2C|---> BMP280 | ← 气压/温度传感器
(PB2/SCL, PB3/SDA) |
| |
+------------+
+------------+
| |
Software I2C (PB0/1)|---> AT24C02 | ← 存配置
|---> DS1307 | ← 实时时钟
|---> SSD1306 | ← OLED显示
+------------+
👉 把高频使用的传感器放在硬件I2C上保证响应速度;其余低频设备统统扔进软件I2C,既节省资源又不影响性能,美滋滋~ 😎
⚠️ 设计避坑指南
别以为软件I2C就是“随便搞搞”,踩过的坑多了也是血泪史:
-
千万别开强上拉输出!
否则多个设备同时拉低时可能造成短路电流过大,烧毁IO! -
必须加外部上拉电阻!
推荐4.7kΩ上拉至VCC,太小功耗大,太大上升沿拖尾严重。 -
注意引脚复用冲突
某些GPIO可能被调试接口(SWD)占用,务必查手册确认可用性。 -
RTOS环境下记得加锁!
多任务中调用i2c函数前禁用调度器或使用互斥量,防止被打断导致时序错乱。 -
低功耗模式下记得关设备电源
软件I2C虽省资源,但挂载的外设仍可能漏电,记得进入Sleep前关闭不用的模块。
🚀 未来还能怎么优化?
虽然现在这套方案已经够用了,但我们还可以让它更“聪明”:
- 用定时器中断生成SCL时钟 :提升时序精度,接近硬件级表现
-
封装成标准驱动库
:统一API,支持
i2c_master_write()这类通用接口 - 结合FreeRTOS队列+任务 :实现异步非阻塞调用,提升系统实时性
- 动态速率调节 :根据设备支持自动切换100k/400k模式
甚至可以考虑做个“虚拟I2C总线管理器”,统一调度硬件与软件通道,自动负载均衡,想想都有点激动呢~ 😏
💡 总结一句话
CH579虽然硬件I2C不多,但靠着丰富的GPIO和灵活的软件模拟技术,完全可以“无中生有”地扩展出多路I²C总线 。
这项技术门槛低、成本为零、移植性强,特别适合原型验证、小批量生产和资源紧张的嵌入式项目。只要你愿意动手,“缺接口”再也不是借口!
与其花钱买I/O扩展芯片,不如花十分钟写个bit-bang函数——这才是工程师的浪漫啊 ❤️🔧
小贴士:下次遇到“接口不够用”,先别急着换主控,试试用代码“造”一个?说不定就有惊喜!🎉
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
1265

被折叠的 条评论
为什么被折叠?



