尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

PN532三种通信接口详解:I2C、SPI、HSU的选型与实战

PN532三种通信接口详解:I2C、SPI、HSU的选型与实战 PN532 这个芯片玩物联网和嵌入式开发的应该都不陌生。不管你是做 NFC 门禁、校园卡模拟、公交卡读取还是想给 Arduino、STM32、树莓派加上刷卡功能基本绕不开它。我最早接触 PN532 是在做一个小型读卡器项目当时手里同时有 I2C、SPI 和 HSU 三种接口的模块本来以为换个接口只是改几根线的事结果踩了一堆坑从时序到配置挨个折腾了一遍。这篇文章就把我实际试过的经验整理出来重点讲清楚 I2C、SPI、HSU 三种协议在 PN532 上的差异、初始化细节、代码实现和排查方法适合刚接触 NFC 的开发者也适合准备把 PN532 集成到自己项目里、但不确定选哪种接口的人。先给还没上车的朋友补一句背景PN532 是 NXP 推出的一颗 NFC 芯片支持读卡、写卡、卡模拟、点对点通信。它最有意思的地方是主机接口很灵活既可以接 I2C也可以接 SPI还可以接 HSU其实就是一个高速 UART。这意味着同一个芯片能适配不同主控的资源——有的主控硬件 I2C 不够用有的 SPI 引脚被占满了还有的老单片机只有串口PN532 三种接口都能接就很香。不过接口多了也有烦恼。很多芯片的接口在出厂后就固定了PN532 却是上电时通过引脚电平来判断当前走哪种协议。如果引脚下拉或者上拉没处理好时序模块初始化得再对也没用。这篇文章我会从硬件选型、引脚配置、通信帧格式到实际代码一步步拆开最后再附上我调试时经常遇到的几个诡异问题。1. 三种通信接口的硬件设计与选型思路1.1 I2C、SPI、HSU 的本质区别先明确一个概念这三种接口都是 PN532 和主控之间通信的“通道”不是 NFC 无线通信本身。PN532 通过天线和外面的卡片通信走的是 13.56MHz 射频而主控发指令给 PN532 则是走 I2C、SPI 或者 HSU。所以上层代码逻辑基本一样变的只是传输层。I2C 是两根线SCL、SDA半双工速度一般跑 100kHz 或 400kHz。因为 I2C 是开漏输出加外部上拉电阻所以它天然支持多设备挂一条总线适合主控引脚紧张、总线上还有其他传感器的场景。但速度不算快如果你要频繁读取大块数据比如从 NFC 标签里连续读几百字节I2C 会比其他两种慢不少。SPI 是四根线SCLK、MOSI、MISO、CS全双工速率可以跑到几 MHz是三种接口里速度最快的。它适合对吞吐量有要求、需要大量数据交换的场景。缺点是多占引脚而且一个 SPI 总线上一般用片选引脚来区分设备占用的 CS 引脚一多优势就没那么明显了。HSU 就是 UART只是 PN532 给它起名叫 High Speed UART默认波特率是 1152008 数据位、无校验、1 停止位。串口的好处是几乎所有单片机都有调试也方便用 USB 转串口模块就能直接跟电脑通信。缺点也是半双工速率受波特率限制跟 SPI 比起来传输大块数据时会慢一些。下面这张表是我整理的三者对比接口引脚数速率工作方式典型场景I2C2100k/400kHz半双工总线式引脚紧缺总线已有多设备SPI4最高数MHz全双工主从式大数据量交互对速度敏感HSU2默认115200bps半双工点对点主控仅有串口调试方便选哪种接口不是拍脑袋定的我一般按这个顺序来考虑先看主控还有什么外设可用再看通信数据量大小最后考虑 PCB 布线和模块兼容性。1.2 PN532 上电接口检测原理PN532 有一个很容易忽略的机制它在复位上电的时候会去采样两个引脚的电平用这两个引脚来决定后续主机接口走 I2C、SPI 还是 HSU。这两个引脚在不同模块上可能标注不太一样但原理是一样的。以常用的模块为例I0 和 I1 的电平组合决定了接口模式I0I1通信接口00I2C10SPI01HSU11串行调试模式一般不用于正常通信电平 0 表示接地电平 1 表示接高电平或通过上拉电阻接 VCC。这个配置必须在芯片上电之前就稳定下来因为芯片是在复位释放后的很短时间内采样这两个引脚。我看到不少新手在调试 SPI 的时候模块明明标着 SPI 接口但代码怎么调都没反应。最后用万用表一量发现 I0 引脚是被模块内部下拉到地的等于芯片一直工作在 I2C 模式主控却往 SPI 引脚上发数据自然石沉大海。所以拿到一个模块第一件事不是写代码而是确认板上 I0/I1 的默认电平或者看模块原理图确认出厂模式。如果你的模块上这两个引脚引出到排针那就好办可以通过跳线帽或者直接焊接选择模式。如果没有引出那基本是出厂固定了只能按模块标注的接口来用。我自己手头有几个红色 PCB 的 PN532 模块它们默认是 HSU 模式但也把 I2C 和 SPI 引脚都引出来了要切换到别的模式就得自己改跳线。1.3 硬件接线和电平匹配选好接口之后接线看起来很简单但有几个隐藏细节特别容易出问题。第一是电源。PN532 数字部分和天线驱动部分需要 3.3V 供电注意尽量用稳压芯片不要直接从主控的 3.3V LDO 上取电因为天线发射时电流波动比较大有可能把主控电压拉低导致复位或者不稳定。我在做第一版电路时直接把 PN532 的 VCC 接到了 STM32 的 3.3V 上结果读卡的时候屏幕偶尔闪一下后来查到就是电源瞬态干扰。第二是电平匹配。如果你的主控是 5V 单片机比如 Arduino Uno 这类直接跟 3.3V 的 PN532 通信I2C 和 UART 电平不匹配长期使用或者某些引脚上电瞬间有概率把芯片搞坏。最稳妥的做法是加一个电平转换模块。如果是 I2C还要注意两边上拉电阻都挂在 3.3V 上不能挂到 5V。第三是地线。所有通信说到底都是共地问题主控和 PN532 一定要共地否则波形就是漂浮的特别是 I2C 和 UART 这种靠电平判断信号的协议地电位差会让信号乱成一团。2. 接口初始化与关键参数配置2.1 模式切换与引脚配置不同接口模式下PN532 的引脚复用不一样不是说切换成 I2C 之后 SPI 引脚就完全没事了而是要把不用的引脚处理掉避免浮动电平干扰芯片内部逻辑。I2C 模式下SCL 和 SDA 需要外部上拉电阻典型值 4.7kΩ如果总线速率跑 400kHz也可以用 2.2kΩ 或更低。I2C 地址默认是 7 位地址 0x24换算成 8 位写地址是 0x48读地址是 0x49。在很多单片机库里你看到的地址参数如果是 0x24通常库内部会自动左移一位如果是 0x48那就是直接用了 8 位地址。这个换算容易搞混最好先查一下自己用的库是怎么处理的。SPI 模式下PN532 要求 SPI 模式 0也就是 CPOL0、CPHA0。这意味着时钟空闲时是低电平数据在时钟上升沿采样。片选引脚 CS 低有效每次传输开始前下拉结束后释放。如果主控 SPI 硬件不支持模式 0可以用软件模拟 SPI但速度会慢很多。另外 PN532 的 SPI 是只支持从机模式的主控作为主机来控制通信节奏。HSU 模式下波特率默认 115200这个参数不能随意改除非你在程序里主动发送波特率切换指令。我自己就干过一件傻事以为自己用的是通用串口模块把波特率设成了 9600结果 PN532 一个字节都不回。后来看数据手册才发现HSU 默认就是 115200接收命令前不会自动检测波特率。2.2 命令帧格式与校验计算PN532 的通信协议本身和接口无关不管 I2C、SPI 还是 HSU传输的数据都是同样格式的帧。理解这个帧格式排查问题会快很多。PN532 的命令帧结构是Preamble固定 0x00Start Code固定 0x00 0xFFLEN数据长度指 TFI PD0 PD1... 这个字段的长度LCSLEN 的校验值计算方法是对 LEN 取反后加 1使得 LEN LCS 0TFI方向标识主机发给 PN532 是 0xD4PN532 返回给主机是 0xD5PD0命令码比如获取固件版本是 0x02PD1...PDn命令参数DCS数据校验等于 TFI PD0 PD1... 所有数据字节取反加 1Postamble固定 0x00初次接触时感觉计算校验很麻烦其实写代码就三行。我一般在发送前这样算static uint8_t PN532_Checksum(uint8_t *data, uint8_t len) { uint8_t sum 0; while (len--) { sum *data; } return (uint8_t)(0x00 - sum); }这个方法能算 LCS也能算 DCS因为核心思想就是“所有字节求和后低字节为 0”。对应到校验值就是将所有需要参与校验的字节相加再取反加 1。验证的时候把参与校验的字节和校验值加起来低字节应该是 0x00。还有一个细节是PN532 收到主机命令后会先回一个 ACK 帧固定是00 00 FF 00 FF 00。这个 ACK 不代表命令执行完毕只是告诉主机它收到数据了。真正的响应数据要等芯片内部处理完才会返回这个间隔有时长有时短取决于命令复杂度。如果你发送完命令立刻去读响应很可能读到的是 ACK 甚至什么都没读到所以驱动代码里一定要有等待机制。2.3 时序与通信流程之前我提到接口选择引脚这里展开讲讲不同接口的时序差异在调试时到底意味着什么。I2C 通信的时序简单说就是主机发送起始条件然后发设备地址和写标志接着发送帧数据最后发停止条件。PN532 在 I2C 模式下有点特殊它处理完命令后会把数据放到内部的发送缓冲区主机需要以读方式再发一次设备地址才能把响应读出来。也就是说I2C 模式下你通常要分两步先写命令再读响应。SPI 的时序相对清爽主机拉低 CS然后按字节发送命令帧PN532 会在 MISO 上回 ACK。注意读完 ACK 后不能马上拉高 CS要等芯片把响应帧准备好再继续读。如果 CS 控制得太急PN532 可能根本来不及组装响应。HSU 最简单就像普通串口一样往 TX 发帧从 RX 收 ACK 和响应。它是最容易忍住想摸逻辑分析仪的接口但也是最容易犯低级错误的接口比如波特率配置错、接线接反。我实际测试下来三种接口在“获取固件版本”这种简单命令上速度差距很小真正拉开差距的是大量读写 NFC 数据时。如果你做的是门禁这种“刷卡-比对-开门”的轻量操作I2C 完全够了如果你要快速读写高频标签里的数据SPI 会明显更跟手。3. 实战STM32F103 通过 I2C 读取 PN532 版本信息3.1 环境准备与接线理论讲了那么多还是得上手操作。我选 STM32F103 作为主控因为它太经典了网上资料也多很多人手里都有现成开发板。软件环境用 STM32CubeMX 生成初始化代码HAL 库操作。硬件连接I2C 模式是这样的PN532 引脚STM32 引脚VCC3.3VGNDGNDSDAPB7I2C1_SDASCLPB6I2C1_SCLIRQ不接可选我建议接上 IRQ 引脚后面讲轮询还是中断的问题时会用到。如果模块默认不是 I2C 模式需要把 I0/I1 都拉低后再上电。CubeMX 里把 I2C1 打开速度设 100kHz其他保持默认。生成代码后在 main.c 里加上 PN532 驱动。3.2 I2C 模式核心代码拆解先写一个最底层的 I2C 写帧函数。这个函数的作用是把前面说的命令帧打包好然后通过 I2C 发出去。我一般这样做void PN532_I2C_WriteCommand(uint8_t *cmd, uint8_t len) { uint8_t frame[64]; uint8_t i 0; uint8_t checksum; frame[i] 0x00; // Preamble frame[i] 0x00; // Start Code frame[i] 0xFF; // Start Code frame[i] len 1; // LEN: TFI 命令数据 checksum 0x00 - (uint8_t)(len 1); // LCS frame[i] checksum; frame[i] 0xD4; // TFI主机到 PN532 for (uint8_t j 0; j len; j) { frame[i] cmd[j]; } checksum 0x00 - 0xD4; for (uint8_t j 0; j len; j) { checksum (uint8_t)(checksum - cmd[j]); } frame[i] checksum; // DCS frame[i] 0x00; // Postamble HAL_I2C_Master_Transmit(hi2c1, 0x48, frame, i, 100); }然后写读取 ACK 和响应帧的函数。PN532 收到命令后I2C 从机地址 0x48 是写方向0x49 是读方向。HAL 库里面 HAL_I2C_Master_Receive 传入的地址是 8 位地址0x49 就代表读方向。uint8_t PN532_I2C_ReadAck(void) { uint8_t ack[6]; HAL_I2C_Master_Receive(hi2c1, 0x49, ack, 6, 100); if (ack[0] 0x00 ack[1] 0x00 ack[2] 0xFF ack[3] 0x00 ack[4] 0xFF ack[5] 0x00) { return 1; } return 0; }获取固件版本命令是 PD0 0x02无参数所以 cmd 数组只需要一个字节。完整调用流程是uint8_t cmd 0x02; PN532_I2C_WriteCommand(cmd, 1); // 稍作延时等 ACK HAL_Delay(10); if (PN532_I2C_ReadAck()) { // 等待响应就绪 HAL_Delay(10); uint8_t buf[16]; HAL_I2C_Master_Receive(hi2c1, 0x49, buf, sizeof(buf), 100); // buf 里就是响应帧 }响应帧的格式和命令帧类似开头也是00 00 FF然后是长度、TFI0xD5、PD00x03响应标识、后面跟 4 个版本信息字节。打印出来就能看到类似 IC 型号、固件版本号这些信息。提示HAL_Delay 等待 ACK 和响应是一种简单粗暴但有效的方式实际项目中我更建议用 IRQ 引脚做中断触发读取。PN532 在没有准备好数据时IRQ 保持高电平数据准备完成后 IRQ 拉低通知主机可以读取。这样既省 CPU也不会因为延时不够导致读不到数据。3.3 SPI 与 HSU 模式的移植要点如果要用 SPI 模式CubeMX 里把 SPI1 打开配置为全双工主机、模式 0速率先设 1MHz。代码框架跟 I2C 基本一样差别只在底层收发函数。SPI 写帧时先拉低 CS然后 HAL_SPI_Transmit发完后等待芯片回 ACK。这里有个细节PN532 的 SPI 是全双工主机发送一个字节的同时会收到一个字节所以读 ACK 时要用 HAL_SPI_Receive 而不是只读 MISO。很多主控在 SPI 通信时是边发边收接收的字节可能都是 0xFF 或者乱值需要根据时序在正确的时间点采样。如果你想让 SPI 跑得更快可以开 DMAHAL_SPI_Transmit_DMA 和 HAL_SPI_Receive_DMA。但要注意 DMA 的缓冲区长度和数据长度要匹配还有片选信号要自己控制好。我在一个项目里用 STM32F103 的 SPI1 DMA 读取 PN532速率提到 2MHz数据刷新率明显比 I2C 高。HSU 模式更直接就是普通的串口收发。CubeMX 里把 USART1 配成 115200、8N1然后用 HAL_UART_Transmit 发送帧HAL_UART_Receive 接收数据。代码和 I2C 的差异只在于底层接口函数不一样帧结构完全复用。比较方便的是HSU 模式不需要关心 ACK 和响应是否在同一条总线上因为只要往串口发数据串口那边就会把芯片返回的内容原样送回来。有一点要注意PN532 的 HSU 默认波特率是 115200但它的 UART 设备在长时间不用时可能会进入低功耗模式这时候你需要先唤醒它。最简单的方法是在发送正式命令之前先拉低或拉高某个唤醒引脚看具体模块定义或者连续发送几个 0x55 之类的唤醒字节。不同模块实现不太一样我买过一个模块说明书里专门提到要先发 0x55 唤醒另一个则不用直接发指令就行。3.4 逻辑分析仪怎么看时序如果你在调试过程中一头雾水代码怎么改都不通最有效的手段就是用逻辑分析仪抓波形。一个几十块钱的 8 通道逻辑分析仪配合电脑软件可以清晰看到 I2C 的起始条件、地址、数据帧也能看到 SPI 的时钟和数据是否对齐。抓 I2C 时要注意逻辑分析仪探头并联在 SDA 和 SCL 上本身会给总线增加一点电容如果上拉电阻比较大波形边缘可能会变缓。这时候可以先确认软件波形是不是符合预期再判断是硬件问题还是软件问题。抓 SPI 时重点看 CS 拉低后到第一个时钟上升沿之间数据是否已经稳定再看 MISO 上芯片返回的数据是不是在正确的时钟沿被采样。大多数 SPI 排查到最后发现都是模式配错了或者速率太高导致信号失真。实际板子上的波形未必像教科书那样完美但只要关键转换点正确逻辑分析仪能解码出正确的数据通信就是通的。我见过有人过度纠结波形上升沿不够陡其实 400kHz 的 I2C 在 10cm 杜邦线下有一点缓边沿完全能正常工作。4. 常见问题与排查技巧实录4.1 I2C 不通信八成是上拉和地址问题I2C 模式最常见的问题就是完全不响应或者读回来的数据全是 0xFF。这类问题我总结下来原因集中在三个地方。第一个是上拉电阻。I2C 是开漏输出必须有外部上拉电阻才会产生高电平。如果你的开发板上没有上拉或者上拉电阻太大比如 10kΩ 以上再叠加杜邦线和逻辑分析仪的寄生电容波形高电平可能抬不上去通信直接失败。我可以负责任地说 4.7kΩ 是 I2C 最稳妥的起点如果总线线长超过 20cm 或者挂载多个设备可以适当减小到 2.2kΩ。第二个是地址。PN532 默认 I2C 地址是 0x247 位地址但实际代码里写地址时不同库和平台要求不一样。HAL 库的 HAL_I2C_Master_Transmit 需要传 8 位地址所以写 0x48Adafruit 库内部用 7 位地址写 0x24。我见过有人把两个地址混用对着 0x48 的库函数写 0x24结果收发全部错位。第三个是 SCL 和 SDA 接反了。这种错误在排线比较杂乱时特别容易发生。最好用万用表量一下确认主控 SCL 对应模块 SCL主控 SDA 对应模块 SDA不要想当然。4.2 SPI 通信不生效先查模式和片选SPI 的坑主要在两个方面。一个是时钟极性 CPOL 和相位 CPHA。PN532 要求模式 0如果主控配置成模式 3通信不会完全失败但数据位会整体偏一位或者半位解析出来全是乱码。这个很难通过代码层面看出来只能用逻辑分析仪对比。另一个是片选信号的处理。我调试 SPI 时发现有些库会在发送完整个命令帧后立刻释放 CS但如果 PN532 处理命令需要时间主机再想读响应时 CS 已经被释放了。正确做法是先发送命令帧保持 CS 拉低等待芯片返回完 ACK 和响应帧再拉高 CS。如果使用硬件 SPI 的自动片选功能反而容易因为时序不可控而失败我宁可用普通 GPIO 软件控制 CS。还有一点是 SPI 速率。PN532 数据手册给出的最高 SPI 频率有限制实际应用从 1MHz 起步比较稳妥。如果你的主控在 18MHz SPI 下通信正常但 PN532 不稳定那大概率是分频配置不合理降到 4MHz 以内再试。4.3 HSU 乱码和唤醒问题HSU 的典型故障是收到大量乱码或者一条指令发出去芯片理都不理。乱码第一怀疑对象是波特率。确认代码里是 115200而不是 9600 或者 38400。有些模块板载了电平转换或者固定了别的波特率看模块说明书里有没有标注。如果模块是通过 USB 转串口和电脑连接还要确认串口工具本身没有开额外的流控。第二是接线。TX 要和 RX 交叉连接这是串口最容易犯的错。模块 TX 接主控 RX模块 RX 接主控 TX两边 GND 共地。第三是唤醒问题前面说过PN532 在 HSU 模式下可能会进入休眠需要先发唤醒字节。另外还有一个容易被忽略的点HSU 模式下PN532 的 IRQ 引脚同样会指示数据是否就绪但有些模块没有把 IRQ 引出来。如果你发现发完命令后直接读串口经常读不到完整响应可以试试在发送命令后加一个 10ms 到 20ms 的延时等芯片处理完再读比一直死等要靠谱。4.4 排查问题速查表现象可能原因对应手段I2C 发送后无 ACK上拉电阻太大/太小、地址错误、接线反检查上拉确认地址 0x48/0x49核对接线I2C 读到全 0xFF设备未响应、总线被占用逻辑分析仪看波形确认 SCL/SDASPI 通信数据错位CPOL/CPHA 配置错误检查是否为模式 0SPI 读不到响应CS 时序不对、速率过高CS 全程保持拉低降低速率HSU 乱码波特率不匹配确认 115200 8N1HSU 无响应TX/RX 接反、芯片休眠交叉接线发送唤醒字节所有接口都不通供电不足或电平不匹配独立稳压供电加电平转换我在实际测试中还有一个小经验如果没有逻辑分析仪可以用一个简单方法验证硬件通路是否正常。对于 I2C扫描总线上所有地址看能不能扫到 0x48或者 0x24对于 SPI发一个获取固件版本命令看 MISO 上有没有非 0xFF 的数据对于 HSU直接把模块 TX 和 RX 短接发什么回什么就说明串口通路没问题。这个方法不复杂但能快速缩小问题范围。调完这些PN532 的读取基本就稳定了。如果你准备做更复杂的功能比如卡模拟注意 PN532 不是所有型号都支持完整的卡模拟功能和具体固件版本有关系先确认固件版本号会少走很多弯路。我自己后来做产品时优先用 SPI 接口拿数据再用 HSU 做调试输出这样开发阶段和生产阶段互不干扰。PN532 这套多协议通信思路其实很值得借鉴一颗芯片用多种方式接入主机既照顾了资源紧张的小主控也满足了大吞吐量的需求。你在自己项目里选型时不用太纠结说哪种接口最好先把你手上主控的资源盘点清楚再对照我前面的对比表基本就能确定方案了。后面不管是做门禁、读卡器还是 NFC 开发板通信层的框架都是同一套熟悉了一种接口再切到另一种也只是改底层收发函数的事。
返回列表