1. 为什么我们需要用GPIO模拟I2C?一个真实的硬件冲突故事
大家好,我是老张,在嵌入式这行摸爬滚打了十几年,从单片机玩到现在的各种智能硬件,踩过的坑比吃过的盐还多。今天想跟大家聊一个特别实际的问题:当你的硬件I2C引脚被占用了,或者干脆就和别的功能冲突了,你该怎么办? 别急着说换芯片或者改板子,很多时候我们根本没那个条件。这时候,用普通的GPIO口去软件模拟一个I2C协议,就成了救命的绝招。
我最近就栽在这个坑里。当时在做一个智能投影仪的项目,需要用到一颗三轴陀螺仪(G-sensor)来做自动梯形校正,这东西用的是I2C接口。一开始挺顺利,直接用芯片原厂提供的驱动就把数据读出来了。但噩梦从集成测试开始——只要一接上HDMI信号源,系统时不时就死机。排查过程那叫一个痛苦,从驱动到应用层查了个遍,最后用逻辑分析仪一抓波形才发现,好家伙,陀螺仪用的I2C引脚,和HDMI模块的I2C引脚,在芯片内部是复用的!它们打架了。
去找原厂支持,人家轻飘飘一句“升级到最新版SDK就解决了”。用过一些国产芯片的朋友可能都懂,它们的SDK更新速度堪比火箭,我们做产品的根本追不上,也不敢随便追,一升级可能一堆别的功能又出问题。硬件板子已经贴片生产了,也不可能为了改两个引脚去飞线或者重新投板。怎么办?硬件I2C的路被堵死了,但天无绝人之路,我们还有大把空闲的普通GPIO口。 对,就是用软件,让这两个GPIO口按照I2C的“交通规则”来收发数据,这就是GPIO模拟I2C的核心思想。
这招听起来有点“土法炼钢”,但在资源受限的嵌入式开发里,它可是个非常实用的技能。特别适合以下几种场景:一是像我的情况,硬件设计定型后才发现引脚冲突,改板成本太高;二是主控芯片的硬件I2C接口数量不够用,但你又需要接多个I2C设备;三是在一些超低成本的方案里,芯片可能连硬件I2C模块都没有。掌握了这个方法,你就多了一份解决问题的底气,不再被硬件限制轻易卡住脖子。
2. 磨刀不误砍柴工:彻底搞懂I2C协议的“交通规则”
在动手写代码之前,咱们必须把I2C协议这点事弄得门儿清。你可以把它想象成两个人(主设备和从设备)之间打电话,只有一根数据线(SDA)和一根时钟线(SCL),所有对话都按严格的时序进行,不能抢话。
首先,记住I2C通信的几个核心状态和信号,这是我们要用GPIO去模拟的关键:
- 起始信号(START):这是通话开始的标志。当SCL时钟线是高电平时,SDA数据线从高电平拉到低电平。就像一个主持人敲一下锤子,说“现在开会”。这个动作必须是主设备发起的。
- 停止信号(STOP):通话结束的标志。当SCL是高电平时,SDA从低电平拉回高电平。相当于主持人说“散会”。
- 数据传输:数据在SCL为高电平期间必须保持稳定(SDA不能变),只有在SCL为低电平时,SDA的电平状态才能改变。每一位数据(一个比特)的传输都伴随着一个时钟脉冲。数据是按字节(8位)传输的,而且是高位(MSB)在前,低位(LSB)在后。
- 应答信号(ACK/NACK):这是保证通信可靠的关键。每发送完一个字节(8位)数据,发送方(比如主设备)会释放SDA线(将其设为输入模式),并在第9个时钟脉冲期间,由接收方(比如从设备)将SDA线拉低,表示“我收到了”(ACK)。如果接收方没有拉低SDA,那就是无效应答(NACK),通常表示通信出错或从设备忙。
其次,一次完整的I2C通信流程是怎样的? 我们以主设备向从设备的一个寄存器写入一个字节数据为例:
- 主设备发出 起始信号。
- 主设备发送 7位从设备地址 + 1位写标志(0),共8位。
- 从设备回应 ACK。
- 主设备发送 8位寄存器地址。
- 从设备再次回应 ACK。
- 主设备发送 要写入的8位数据。
- 从设备最后回应 ACK。
- 主设备发出 停止信号。
读数据的过程稍微复杂一点,需要先发送寄存器地址(写操作),然后重新发起起始信号,再发送带读标志的地址去读取数据。这些具体的时序图,大家一定要找对应的传感器数据手册看清楚,上面会标明每个信号之间的时间要求,比如SCL高/低电平的最小保持时间、起始/停止信号建立时间等。我们模拟的时候,那些usleep(5)之类的延时,就是为了满足这些时序参数。
3. 从零开始:手把手搭建GPIO模拟I2C的代码骨架
理论懂了,咱们就来真刀真枪地干。模拟I2C的本质,就是用程序去精确控制两根GPIO线的电平变化和读取。首先,我们需要一套最基本的GPIO操作函数。这里我以Li

3109

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



