PN532 NFC/RFID模块开发实战:SPI/I2C/UART接口选型与读写操作详解
1. 项目概述与核心需求解析PN532这颗芯片在NFC/RFID圈子里算是老面孔了从早期的门禁读卡器到现在的嵌入式项目到处都能看到它的身影。我最早接触它是在做一个智能柜锁的项目当时需要同时支持Mifare Classic卡和手机NFC模拟卡选来选去发现PN532几乎是唯一能在成本、功能和开发难度之间取得平衡的方案。它支持13.56MHz下的ISO14443A/B、ISO15693、FeliCa等多种协议读写距离能到5-7厘米通过SPI、I2C或UART都能跟主控通信灵活性很高。这个模块解决的核心问题其实就一个让嵌入式设备具备读取和写入NFC/RFID标签的能力。听起来简单但实际落地时会遇到一堆细节问题——通信接口怎么选、天线怎么调、密钥怎么处理、不同卡片的读写指令差异等等。我见过不少新手直接买模块回来照着示例代码跑一遍发现能读卡就以为搞定了结果一换卡片类型或者一改通信方式就卡住。这篇文章适合几类人看一是刚拿到PN532模块不知道怎么下手的嵌入式新手二是已经能跑通基础读写但想深入理解SPI/I2C通信细节的开发者三是需要在STM32等平台上做稳定NFC读写的工程师。我会从硬件连接到通信协议选择再到实际读写操作和常见问题排查把整个链路讲透。注意本文涉及的NFC读写操作仅用于合法的自有设备调试和学习研究请勿用于未经授权的门禁系统或其他侵犯他人权益的场景。2. 硬件连接方案与接口选型2.1 PN532模块的引脚定义与供电要求市面上常见的PN532模块一般有两种形态一种是红色小板带USB转串口芯片主要方便电脑端调试另一种是蓝色或黑色小板只引出排针适合嵌入到自己的电路板上。不管哪种核心引脚都差不多。先看供电。PN532的工作电压是3.3V这点必须注意。我见过有人直接接5V芯片当场发烫报废。模块上如果有AMS1117这类LDO那输入5V还能接受但如果没有稳压芯片就必须老老实实给3.3V。电流方面读卡瞬间峰值能到150mA左右所以电源要能提供至少200mA的余量否则会出现读卡不稳定、时好时坏的情况。引脚方面关键的有这几组VCC和GND供电3.3VSDA/SDA/RXI2C数据线或UART接收SCL/SCL/TXI2C时钟线或UART发送MOSI、MISO、SCK、SSSPI接口的四根线IRQ中断输出读卡成功或检测到卡片时拉低RSTPDN复位和低功耗控制模块上通常有一组拨码开关或跳线帽用来选择通信模式。这个一定要确认清楚我遇到过有人拨码开关没拨对代码怎么写都通信不上折腾了一下午才发现是硬件模式选错了。2.2 SPI、I2C、UART三种接口的取舍PN532支持三种通信方式选哪个取决于你的主控资源和项目需求。SPI接口的速度最快理论上能到几MHz适合需要快速连续读卡的场景。SPI是全双工PN532作为从设备主控主动发起通信。缺点是占用引脚多至少需要4根线SCK、MOSI、MISO、SS如果主控SPI资源紧张就要权衡。另外SPI的片选逻辑要处理好PN532对片选时序有一定要求。I2C接口只占两根线适合引脚资源紧张的项目。PN532的I2C地址是0x487位地址通信速率标准模式100kHz、快速模式400kHz都能跑。但I2C有个坑总线上必须接上拉电阻通常4.7kΩ到10kΩ。我试过用10kΩ在短距离下没问题但线拉长到20cm以上就出现通信失败换成4.7kΩ就稳了。上拉电阻太小会导致功耗增加和信号边沿变缓太大会导致上升沿不够陡峭100kHz以上就容易出错。UART接口最简单适合跟电脑或单片机串口直接对接。波特率一般用115200PN532的UART模式支持自动波特率检测但实际用下来固定波特率更可靠。UART的缺点是速度相对慢而且需要额外的TX/RX交叉连接。接口类型引脚数最高速率适用场景主要坑点SPI4数MHz高速连续读卡片选时序、模式配置I2C2400kHz引脚紧张的项目上拉电阻、总线电容UART2115200bps电脑调试、简单对接波特率匹配、电平兼容2.3 天线部分的设计与匹配要点PN532模块通常自带PCB天线但如果你要自己做板子或者调整读卡距离天线这块就得花点心思。13.56MHz的天线本质是一个LC谐振回路谐振频率必须精确落在13.56MHz附近偏移太多会导致读卡距离急剧下降。天线匹配一般通过调整串联和并联的电容来实现。模块上通常有可调电容或者预留的焊盘。我用网络分析仪测过几款模块出厂状态下的谐振频率有的偏到13.2MHz有的偏到13.9MHz虽然都能读卡但距离差了不少。如果你手头没有专业仪器可以用一个简单方法用示波器看天线两端的波形幅度调整电容让幅度最大基本就接近谐振了。天线线圈的电感值一般在1-3μH之间配合几十到一百多pF的电容谐振。圆形天线的直径越大读卡距离越远但超过一定尺寸后边缘磁场反而减弱。我实测下来直径4-6cm的圆形天线在5V供电下能稳定读到5cm左右再大提升就不明显了。提示天线附近不要铺铜或放置金属物体否则会严重吸收磁场能量读卡距离可能直接减半。如果必须靠近金属需要加铁氧体隔磁片。3. 通信协议深度解析与配置3.1 SPI通信时序与PN532的片选逻辑SPI通信的核心是时钟极性和相位也就是CPOL和CPHA。PN532的SPI模式要求CPOL0、CPHA0也就是时钟空闲为低电平数据在时钟上升沿采样。这个配置在STM32的CubeMX里对应SPI Mode 0。片选信号SS的处理有个细节PN532要求在每帧数据的第一个字节传输前拉低SS最后一个字节传输完成后拉高。如果SS在帧中间抖动PN532会认为通信异常。我见过有人用软件控制SS结果在中断里被打断导致SS提前拉高数据就丢了。建议用硬件SPI的NSS引脚或者确保软件控制SS的代码不会被中断打断。SPI的数据帧格式是第一个字节包含命令类型和通道号后续字节是数据。PN532的SPI协议规定第一个字节的bit7表示方向1为读0为写bit6表示是否最后一帧低6位是通道号。实际用的时候通常用通道0做命令传输。// STM32硬件SPI发送一个字节给PN532 uint8_t PN532_SPI_WriteByte(uint8_t data) { uint8_t rx_data; HAL_SPI_TransmitReceive(hspi1, data, rx_data, 1, 100); return rx_data; } // 写命令到PN532 void PN532_WriteCommand(uint8_t cmd) { PN532_CS_LOW(); PN532_SPI_WriteByte(PN532_SPI_DATAWRITE); PN532_SPI_WriteByte(cmd); PN532_CS_HIGH(); }上面这段代码里PN532_SPI_DATAWRITE通常是0x01表示写数据到PN532。读数据时用0x03。这些常量在Adafruit的PN532库里有定义可以直接参考。3.2 I2C地址、时序与上拉电阻计算PN532的I2C地址固定为0x487位地址格式。写操作时地址字节是0x481 0x90读操作是0x91。这个地址不能改所以同一条I2C总线上只能挂一个PN532。I2C的时序里起始条件和停止条件由主控产生。PN532作为从设备在SCL高电平期间SDA从高变低表示起始SDA从低变高表示停止。数据传输时SDA在SCL低电平期间变化在SCL高电平期间保持稳定。上拉电阻的计算是个实际问题。I2C总线的上升时间由RC时间常数决定R是上拉电阻C是总线电容。标准模式100kHz要求上升时间小于1000ns快速模式400kHz要求小于300ns。总线电容一般每根线10-20pF加上PCB走线和连接器总共可能到50-100pF。假设总线电容C100pF快速模式要求上升时间tr300ns那么R tr/(0.847C) ≈ 300ns/(0.847100pF) ≈ 3.5kΩ。所以快速模式下上拉电阻不能太大4.7kΩ是常见选择。标准模式下可以放宽到10kΩ。我实际用下来4.7kΩ在大多数场景下都能稳定工作。如果通信距离长或者总线上挂了多个设备可以适当减小到2.2kΩ但要注意功耗。上拉电阻太小会导致低电平时灌电流过大有些主控的I2C引脚驱动能力有限可能拉不到足够低的电平。3.3 UART模式下的波特率与帧格式UART模式最简单PN532的TX接主控的RXRX接主控的TX地线共连。波特率默认是1152008位数据位1位停止位无校验。PN532的UART协议有个特点它使用一种叫“帧”的格式每帧以0x00 0x00 0xFF开头然后是长度字节、长度校验字节、数据区和校验和。这个格式跟SPI和I2C的帧格式类似只是物理层不同。实际用的时候如果主控的串口引脚是3.3V电平可以直接连。如果是5V电平需要加电平转换否则可能损坏PN532。我见过有人用5V的Arduino直接连PN532的UART结果通信时好时坏后来加了电平转换模块就稳定了。注意PN532的UART模式在模块上通常需要把拨码开关拨到对应位置不同厂家的模块拨码定义可能不同使用前务必看模块的说明或丝印。4. 数据读写实操与核心环节实现4.1 唤醒与固件版本读取PN532上电后默认处于低功耗模式需要先发送唤醒命令。如果是SPI或I2C模式发送任意数据就能唤醒如果是UART模式需要发送0x55 0x55 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00这个唤醒序列。唤醒之后第一步通常是读取固件版本确认通信正常。发送GetFirmwareVersion命令0x02正常会返回类似0x32 0x01 0x06 0x07这样的数据表示IC是PN532版本1.6支持ISO14443A/B等。uint8_t PN532_GetFirmwareVersion(void) { uint8_t response[5]; PN532_WriteCommand(0x02); // 等待ACK if (PN532_WaitForAck() ! 0) return 0; // 读取响应 PN532_ReadData(response, 5); return response[0]; // 应该是0x32 }这段代码里WaitForAck是等待PN532返回ACK帧通常是0x00 0x00 0xFF 0x00 0xFF 0x00。如果等不到ACK说明通信有问题需要检查硬件连接和模式配置。4.2 Mifare Classic卡片的读写操作Mifare Classic 1K卡是最常见的RFID卡有16个扇区每个扇区4个块每块16字节。第0扇区的第0块是厂商数据只读每个扇区的第3块是密钥和访问控制位不能直接存数据。读写Mifare Classic需要先认证密钥。默认密钥通常是FFFFFFFFFFFF或A0A1A2A3A4A5。认证命令是0x40认证A密钥或0x41认证B密钥后面跟块号和6字节密钥。uint8_t PN532_MifareAuth(uint8_t block, uint8_t keyType, uint8_t *key) { uint8_t cmd[12]; cmd[0] 0x40 keyType; // 0x40 for KeyA, 0x41 for KeyB cmd[1] block; memcpy(cmd2, key, 6); // 发送InListPassiveTarget后获取的卡片UID需要在这里使用 // 实际认证命令需要包含UID return PN532_SendCommand(cmd, 12); }认证通过后就可以用0x30命令读块用0xA0命令写块。读块返回16字节数据写块需要提供16字节数据。我踩过的一个坑是写块之前必须先认证该块所在的扇区而且认证状态在切换扇区后会失效。比如你认证了扇区1写了块4然后想写块8必须重新认证扇区2。这个逻辑在代码里要处理好否则写操作会失败。4.3 NTAG系列标签的NDEF数据读写NTAG213/215/216这类标签是NFC论坛标准的Type 2 Tag数据结构跟Mifare Classic完全不同。它们用页Page来组织每页4字节。NTAG213有45页NTAG215有135页NTAG216有231页。NDEFNFC Data Exchange Format是NFC标签上存储数据的标准格式。一个NDEF消息可以包含多个记录每个记录有类型、ID和负载。比如一个URL的NDEF记录类型是“U”负载是网址字符串。读写NTAG标签用0x30命令读页0xA2命令写页。但要注意NTAG的写操作是按页写的每页4字节不能跨页写。而且有些页是配置页或锁定页不能随便写。// 读取NTAG213的第4页到第7页通常是NDEF数据区 uint8_t PN532_NTAG_ReadPages(uint8_t startPage, uint8_t *data, uint8_t pageCount) { uint8_t cmd[2]; cmd[0] 0x30; cmd[1] startPage; PN532_WriteCommand(cmd, 2); // 等待ACK后读取数据 PN532_ReadData(data, pageCount * 4); return 0; }写NDEF数据时需要先构造NDEF消息然后按页写入。比如写一个网址“https://example.com”NDEF记录大概是0x03NDEF消息开始、长度、0xD1记录头、0x01类型长度、负载长度、‘U’类型、0x04URL前缀https://、网址字符串、0xFE终止符。这些数据要按4字节对齐写入标签。4.4 STM32平台下的SPI DMA读取实现在STM32F103上用SPI DMA读取PN532数据可以大幅降低CPU占用。配置CubeMX时SPI1设为全双工主模式Mode 08位数据预分频到合适速率PN532的SPI最高支持几MHz但实际用1-2MHz比较稳。DMA通道配置为SPI1_RX和SPI1_TX优先级中等。// SPI DMA接收完成回调 void HAL_SPI_TxRxCpltCallback(SPI_HandleTypeDef *hspi) { if (hspi hspi1) { spi_dma_complete 1; } } // 用DMA读取PN532数据 uint8_t PN532_ReadData_DMA(uint8_t *buffer, uint16_t len) { uint8_t tx_data 0x03; // 读命令 PN532_CS_LOW(); HAL_SPI_Transmit(hspi1, tx_data, 1, 100); spi_dma_complete 0; HAL_SPI_Receive_DMA(hspi1, buffer, len); while (!spi_dma_complete); PN532_CS_HIGH(); return 0; }这里有个细节PN532的SPI读操作需要先发送读命令字节然后再接收数据。用DMA接收时要确保片选在DMA传输完成后再拉高否则数据会不完整。我试过在DMA完成回调里拉高片选但回调执行时DMA已经传输完了时序上没问题。另外FreeRTOS环境下要注意SPI中断的优先级设置。如果SPI中断优先级低于任务调度器的优先级DMA完成回调可能会被延迟导致片选拉高不及时。一般建议SPI中断优先级设为5左右数值越小优先级越高高于任务切换的优先级。5. 常见问题与排查技巧实录5.1 通信失败类问题速查通信失败是最常见的问题表现是读不到固件版本或者一直等不到ACK。排查思路可以按以下顺序来现象可能原因排查方法完全无响应供电不足或接反万用表测VCC和GND确认3.3V有ACK但数据错SPI模式不对确认CPOL0、CPHA0I2C时好时坏上拉电阻不合适换4.7kΩ缩短总线长度UART无响应波特率不匹配确认1152008N1读卡距离短天线失谐调整匹配电容远离金属我遇到过一次特别隐蔽的问题模块的拨码开关接触不良导致通信模式在SPI和I2C之间跳变。表现是偶尔能读到卡偶尔完全没反应。后来用万用表测拨码开关的通断发现有一路电阻忽大忽小换了开关就好了。所以如果问题很诡异不妨检查一下机械开关和连接器。5.2 读卡不稳定与天线调优经验读卡不稳定通常跟天线和电源有关。电源纹波大、天线失谐、附近有金属或强磁场干扰都会导致读卡距离波动。我的调优步骤是这样的先用示波器看天线两端的波形正常应该是13.56MHz的正弦波幅度在几十伏左右是的天线两端电压很高探头要选高压档。如果波形幅度小或者频率偏移就调整匹配电容。然后测电源纹波读卡瞬间如果VCC跌落超过0.2V就要加个大电容比如100μF在模块电源脚附近。还有一个经验PN532的读卡距离跟卡片的方向有关。卡片平行于天线平面时距离最远垂直时距离最短。如果应用场景里卡片方向不固定就要留足够的距离余量。5.3 密钥认证失败与卡片兼容性Mifare Classic的密钥认证失败有几个原因密钥不对、卡片不是Mifare Classic、认证前没有正确获取UID。PN532认证时需要提供卡片UID这个UID是在InListPassiveTarget命令的响应里返回的。如果UID没取对认证命令就会失败。另外有些卡片是Mifare Plus或Mifare DESFire它们虽然也响应ISO14443A的寻卡命令但认证方式完全不同。Mifare Plus在SL1模式下兼容Mifare ClassicSL2和SL3模式用AES认证。DESFire用ISO7816-4的命令集。如果你拿到的卡片认证不了先确认卡片类型。提示市面上有些“防复制卡”会返回伪造的UID或者拒绝认证这类卡片用普通读写器很难操作需要专门的工具和技巧。5.4 中断与轮询模式的选择建议PN532的IRQ引脚在检测到卡片或命令完成时会拉低可以用中断方式通知主控。但实际用下来轮询方式更简单可靠。因为PN532的IRQ信号在某些情况下会抖动中断方式容易误触发。如果一定要用中断建议在中断服务函数里只置一个标志位实际处理放到主循环里。中断里不要做SPI/I2C读写因为那些操作耗时较长会阻塞其他中断。轮询方式的话读卡命令的超时时间要设合理。太短了卡片还没响应就超时太长了程序响应慢。一般设100-200ms比较合适。我试过设50ms有些卡片响应慢就漏读了设500ms又觉得程序卡顿。最后折中到150ms兼顾了响应速度和可靠性。6. 进阶应用与扩展思路6.1 多卡片防冲突与UID管理PN532支持ISO14443A的防冲突机制可以同时检测多张卡片。InListPassiveTarget命令的MaxTg参数设为2时可以检测到两张卡片。但实际用下来同时读两张卡的成功率不高而且读到的UID顺序不确定。如果项目需要管理多张卡片建议还是一次只读一张通过轮询的方式依次读取。每张卡片的UID是唯一的可以用UID作为索引来管理权限或数据。UID通常是4字节或7字节存储时要注意字节序。6.2 低功耗设计与唤醒策略PN532支持低功耗模式通过RSTPDN引脚控制。拉低RSTPDN进入低功耗拉高唤醒。在电池供电的项目里可以让PN532大部分时间处于低功耗定期唤醒寻卡。唤醒后需要重新初始化包括发送唤醒序列和配置命令。这个过程大概需要几十毫秒如果寻卡间隔太短功耗反而降不下来。我一般设寻卡间隔1-2秒平均功耗能降到几毫安。6.3 与上位机工具的配合调试调试阶段可以用电脑端的NFC读写工具配合PN532模块快速验证卡片数据和命令响应。把模块通过UART或USB转串口连到电脑用工具发送命令观察返回数据。这样可以把硬件问题和软件问题分开排查。我习惯先用电脑端工具确认模块和卡片都正常然后再把模块接到自己的板子上调代码。这样如果出问题基本可以确定是代码或硬件连接的问题而不是模块本身的问题。6.4 数据加密与安全存储的考量如果项目涉及敏感数据比如门禁权限或支付信息不能明文存储在卡片里。Mifare Classic的Crypto1加密已经被破解不适合高安全场景。NTAG系列可以用密码保护或原厂签名但安全性也有限。更高安全性的方案是使用Mifare DESFire EV2/EV3或支持ISO14443-4的CPU卡它们支持AES加密和双向认证。但这类卡片的开发复杂度高不少需要处理APDU命令和密钥管理。如果只是内部使用的门禁系统Mifare Classic加自定义加密算法也能凑合但要知道它的安全边界在哪里。我在实际项目中用过一个折中方案卡片里只存一个随机ID真正的权限数据放在后台数据库读卡器读到ID后联网查询。这样即使卡片被复制没有后台授权也进不了门。这个方案对网络依赖高适合有稳定网络的环境。6.5 天线设计工具的辅助使用网上有一些在线的NFC圆形天线设计工具输入天线直径、线宽、圈数等参数可以估算电感值和匹配电容。这些工具的结果跟实际有偏差但作为起点参考还是有价值的。我一般用工具算出初始值然后实际调试时再微调。调试天线时如果没有网络分析仪可以用一个简单方法用一个已知感值的电感并联可调电容组成LC回路用信号发生器扫频找到谐振点。虽然精度不高但足够判断天线是否大致谐振在13.56MHz。最后分享一个我在多个项目中验证过的经验PN532的稳定性很大程度上取决于电源和地线的处理。模块的GND要跟主控的GND大面积连接电源脚附近要放0.1μF和10μF的电容。SPI或I2C的走线尽量短远离高频信号线。把这些基础工作做扎实后面调试会省很多事。