第4章 I2C/SPI通信协议:与NFC芯片的“对话”艺术
各位同学,大家好。今天我们来聊聊嵌入式POS机里,主控芯片和NFC读卡芯片之间是怎么“说话”的。说白了,就是I2C和SPI这两种总线协议。我在做第一代POS机的时候,就因为在协议细节上栽过跟头,所以这一章,我会把那些容易踩的坑,一个一个指给你们看。
4.1 I2C协议详解:两根线的艺术
I2C,全称是Inter-Integrated Circuit。它只用两根线:SCL(时钟线)和SDA(数据线)。你想想看,就靠这两根线,能挂几十个设备,是不是很神奇?
4.1.1 起始条件与停止条件
I2C总线上,什么时候开始传输,什么时候结束,有严格的规定。我刚开始学的时候,总觉得这玩意儿有点“矫情”,但后来发现,正是这种严谨,保证了通信的可靠性。
- 起始条件(S):SCL为高电平时,SDA从高电平跳变到低电平。记住这个“高到低”的下降沿。
- 停止条件(P):SCL为高电平时,SDA从低电平跳变到高电平。这是“低到高”的上升沿。
核心要点:起始和停止条件,都是由主机(我们的MCU)产生的。从机(NFC芯片)只能被动响应。
嗯,这里要注意:总线上如果没有起始条件,任何数据都是无效的。我曾经见过一个同事,调试了两天,最后发现是起始条件没发对,SCL和SDA的时序差了那么一点点。
4.1.2 数据帧格式
I2C的数据帧,结构很清晰。一个完整的传输过程,通常包含以下几个部分:
- 起始条件:主机发出S信号。
- 从机地址:7位或10位地址,后面跟着一个读写位(0表示写,1表示读)。
- 应答位(ACK):从机收到地址后,拉低SDA表示应答。如果没应答(NACK),说明从机不在线或忙。
- 数据字节:每个字节8位,后面紧跟一个应答位。
- 停止条件:主机发出P信号,结束传输。
举个例子,我们读NFC芯片的某个寄存器,流程是这样的:
// 伪代码:I2C读NFC寄存器
// 1. 发送起始条件
I2C_Start();
// 2. 发送从机地址 + 写位 (0xAA, 假设NFC地址为0x55)
I2C_SendByte(0xAA); // 0x55 << 1 | 0
// 3. 等待从机应答
if (I2C_WaitAck() == ERROR) { /* 处理错误 */ }
// 4. 发送寄存器地址
I2C_SendByte(REG_ADDR);
I2C_WaitAck();
// 5. 发送重复起始条件 (Restart)
I2C_Start();
// 6. 发送从机地址 + 读位 (0xAB)
I2C_SendByte(0xAB); // 0x55 << 1 | 1
I2C_WaitAck();
// 7. 读取数据 (主机发送NACK表示读取结束)
uint8_t data = I2C_ReadByte();
I2C_SendNack();
// 8. 发送停止条件
I2C_Stop();
个人经验:我习惯在每次发送地址后,都加一个超时判断。有些NFC芯片在初始化阶段,应答会慢一些。不加超时,程序容易卡死。
4.2 SPI协议详解:四根线的速度与激情
SPI,全称是Serial Peripheral Interface。它比I2C快得多,但用的线也多:SCK(时钟)、MOSI(主出从入)、MISO(主入从出)、CS(片选)。
4.2.1 模式选择
SPI有四种模式,由时钟极性(CPOL)和时钟相位(CPHA)决定。说白了,就是决定数据在时钟的哪个边沿采样。
| 模式 | CPOL | CPHA | 采样边沿 | 空闲时钟电平 |
|---|---|---|---|---|
| 模式0 | 0 | 0 | 上升沿 | 低电平 |
| 模式1 | 0 | 1 | 下降沿 | 低电平 |
| 模式2 | 1 | 0 | 上升沿 | 高电平 |
| 模式3 | 1 | 1 | 下降沿 | 高电平 |
为什么会有这么多模式?因为不同的芯片,设计思路不一样。我遇到过一款NFC芯片,它只支持模式0和模式3。如果你用模式1去通信,数据全是乱的。
4.2.2 时序图解读
看时序图,是硬件工程师的基本功。我教你们一个笨办法:先找到CS片选信号,它拉低表示一次传输开始。然后看SCK时钟,再根据模式,找到数据采样点。
举个例子,模式0的时序:
- CS拉低,从机被选中。
- SCK空闲为低电平。
- 数据在SCK的上升沿被采样,在下降沿变化。
- 传输结束,CS拉高。
避坑指南:我曾经在调试一款国产NFC芯片时,发现它的数据手册上写的模式0,但实际工作时,必须用模式3才能正常通信。后来发现是手册写错了。所以,拿到新芯片,先用逻辑分析仪抓一下波形,确认实际时序,别全信手册。
4.3 与NFC芯片的通信调试
理论说完了,咱们来点实战。怎么调试MCU和NFC芯片之间的通信?
4.3.1 调试工具的选择
我个人习惯,必备三样东西:
- 逻辑分析仪:便宜又好用,能抓I2C和SPI的波形。我推荐24MHz采样率以上的,够用。
- 示波器:看信号质量,比如上升沿是否过缓,有没有毛刺。
- 串口打印:在MCU代码里,把关键数据打印出来,辅助分析。
4.3.2 常见问题与排查
调试NFC芯片,最常遇到的就是“读不到卡”或者“读卡数据错误”。我总结了几点:
- 地址不对:I2C的7位地址和8位地址,很多人搞混。NFC芯片的地址,数据手册上通常写的是7位,但发送时要左移一位。
- 速率不匹配:NFC芯片的I2C/SPI接口,有最大速率限制。比如有些芯片I2C只支持400kHz,你非要用1MHz,它就不干活。
- 上电时序:NFC芯片需要先上电,然后等它稳定(通常几毫秒),MCU才能去访问。我见过有人把MCU和NFC同时上电,结果初始化失败。
- 中断处理:很多NFC芯片有中断引脚(IRQ)。MCU要正确配置外部中断,否则收不到芯片的“数据准备好了”信号。
调试口诀:先看波形,再查代码;先通I2C/SPI,再谈NFC协议。基础通信不通,上层应用全是白搭。
4.3.3 实战:用逻辑分析仪抓I2C波形
假设你写了一段代码,去读NFC芯片的ID,但读回来的全是0xFF。怎么办?
第一步,用逻辑分析仪抓SCL和SDA的波形。你会看到:
- 起始条件有没有发出来?
- 地址字节对不对?
- 从机有没有应答(ACK)?
- 数据字节是不是预期的值?
如果从机没有应答,那问题大概率出在硬件连接上:比如SDA上拉电阻没焊,或者地址引脚电平配置错了。
如果应答了,但数据不对,那可能是寄存器地址写错了,或者NFC芯片处于某种错误状态。
一个小技巧:我习惯在代码里,先写一个“读芯片ID”的函数。如果ID能读对,说明I2C/SPI通信是通的。然后再去搞复杂的NFC协议。这样能快速定位问题。
好了,这一章的内容就到这里。I2C和SPI,是嵌入式开发的基础。你把这些搞透了,后面学NFC协议,就会轻松很多。下一章,我们开始真正接触NFC的射频层,那才是更有意思的部分。