搞硬件这几年,NFC 相关的模块我前前后后用过七八种,RC522、MFRC522、PN532,还有各种国产读卡芯片,最后用得最顺手的还是 PN532。如果你正准备给手头的小项目加上刷卡功能,或者想认真研究一下 13.56MHz RFID 卡片的读写流程,这篇 PN532 模块入门指南应该能帮你少踩不少坑。
PN532 是 NXP 推出的一颗 NFC 前端芯片,支持 ISO/IEC 14443A/B、Mifare Classic、FeliCa 等多种卡型,模块本身自带天线,外部只需要接几根线就能工作。和 RC522 相比,它最大的优势是同时引出 I2C、SPI、UART 三种接口,板上还有足够的资源去做卡模拟和点对点通信。这篇内容我会从选型、硬件连接、读写实操到问题排查,完整走一遍,用的是 Arduino UNO 作为主控,卡片用最常见的 Mifare Classic 1K(就是门禁卡、校园卡最常见的底层芯片那种)。
如果你之前只玩过 RC522,或者刚买到 PN532 模块不知道从哪里下手,看这一篇就够了。下面直接进正题。
1. 为什么选 PN532 做卡片读写实验
1.1 PN532 到底能做什么
PN532 不是一颗简单的读卡芯片,它内部固件直接实现了 NFC 协议栈的很多底层操作,你不需要自己去处理 14443A 的防碰撞、帧同步、状态机这些繁琐流程,只需要通过 I2C/SPI/UART 发一条命令,芯片就会把结果返回。这就是它比用射频芯片自己撸协议要省心得多的地方。
从功能上分,PN532 有三种工作模式:
- 读卡器模式(Reader/Writer Mode):最常用,可以读取卡片 UID、读写 Mifare Classic 数据块、读写 NDEF 文本/URL 标签。
- 卡模拟模式(Card Emulation Mode):让 PN532 像一张卡片一样被其他读卡器读取,适合做 NFC 模拟类实验。
- 点对点模式(P2P Mode):两个 NFC 设备之间交换数据,比如和手机之间的 Contactless 数据交换。
我日常用得最多的是读卡器模式,下面整个实操部分也围绕这个展开。如果你想做的项目只是"读出卡号,然后判断是否放行",那 PN532 属于杀鸡用牛刀,但它胜在身边资料多、接口全,踩坑了也好排查。
1.2 和 RC522 等模块的对比
很多人在选 NFC 模块时会在 RC522 和 PN532 之间犹豫。RC522 价格便宜,一块钱级别就能买到,但它的通信接口几乎只有 SPI 一种,I2C 和 UART 模式在国产模块上经常不完整,而且它不支持卡模拟。PN532 模块价格一般二三十到六七十,贵了一截,但接口全、功能完整。
| 对比项 | RC522 | PN532 |
|---|---|---|
| 通信接口 | 传统模块主打 SPI,部分需自接逻辑 | 模块级同时引出 I2C/SPI/UART |
| 读卡距离 | 通常 2-5cm | 模块天线设计正常的话 3-7cm |
| 卡模拟 | 不支持 | 支持,官方固件原生支持 |
| FeliCa 支持 | 不支持 | 支持 |
| 点对点通信 | 不支持 | 支持 |
| 数据处理能力 | 需要主机参与较多操作 | 固件处理大量底层流程,主机负担小 |
| 上手难度 | 入门简单,但上限低 | 接线稍复杂,功能上限高 |
这么说吧,如果你的需求就是"刷一块 M1 卡,读个 UID,或者简单读几个块",RC522 完全够用,成本也低。但如果你希望以后往卡模拟、NFC 标签写入、手机交互这个方向扩展,直接上 PN532 更划算,起码不用到时候换平台重新调一遍接线和代码。
1.3 模块选型避坑
PN532 模块在淘宝上有很多版本,颜色有红板、绿板、蓝板,天线形状也五花八门。颜色不是重点,重点看这几点:
- 芯片是不是正牌 PN532。市面上一部分模块用的是国产兼容芯片,表现是固件版本读不出来,命令响应不稳定,价格特别低。如果你实验老是莫名其妙失败,先怀疑这一点。
- 板上有没有把 IRQ 引脚引出来。卡模拟模式需要靠 IRQ 做中断唤醒,如果板子没引 IRQ,后面想开发卡模拟功能会很麻烦。做普通读卡读写则无所谓。
- 天线区域大小和走线。天线是 PCB 上的一圈铜箔,面积越大的模块读卡距离通常越好。天线下方的 PCB 区域不能有大面积覆铜和过孔干扰,买回来可以先贴卡试几厘米读卡距离。
- 模式配置开关。不同板子的 I2C/SPI/UART 切换方式不一样,有的是拨码开关,有的是跳线焊盘,有的靠 PSB 引脚电平决定。买之前看清楚详情页说明,到手先按丝印设置好模式。
选型这件事别图便宜,我见过有人买了九块九包邮的"PN532",收到货发现芯片丝印是打磨过的,通信时好时坏,排查三天最后换了个模块才正常,白白浪费时间。
2. 硬件连接:三种接口怎么接都不翻车
2.1 第一步先认识引脚
PN532 模块引脚不少,但实际用到的就那几组。以最常见的红板为例,板上通常有这些引脚:
- VCC、GND:电源和地,模块必须用 3.3V 供电,后面细说。
- SDA、SCL:I2C 数据线、时钟线,接主控的对应引脚。
- MOSI、MISO、SCK、NSS:SPI 四线,接主控的 SPI 引脚。
- RXD、TXD:UART 接收和发送,接主控的 TX、RX 时注意交叉。
- IRQ:中断请求输出引脚,卡模拟和低功耗唤醒常用。
很多模块还有 SEL 或 PSB 引脚,这个引脚决定通信接口模式。不同板子的逻辑不完全一样,但大致规律是:I2C 模式通常需要把 SEL/PSB 接到高电平,SPI 模式接到低电平,UART 模式则根据板子设计或悬空或接特定电平。千万不要拿到手就开始飞线,先把你的模块丝印看明白,再决定怎么接。
2.2 I2C 接口接线与模式配置
I2C 是我个人最推荐的入门接口,因为只需要两根数据线,代码也最简单。在 Arduino UNO 上接线如下:
| PN532 引脚 | Arduino UNO 引脚 |
|---|---|
| VCC | 3.3V |
| GND | GND |
| SDA | A4 |
| SCL | A5 |
接好线后,确认模块的 SEL/PSB 处于 I2C 模式。如果你的模块是跳线焊盘形式,把对应的 I2C 跳线焊上或者拨到 H 位置。
I2C 通信要注意地址问题。PN532 的默认地址是 0x24(7 位地址),有些 I2C 扫描程序会把地址显示成 0x48 或 0x49,其实这是把 7 位地址左移一位后的 8 位读写地址,不是设备挂了,别误会。如果总线上还有其他 I2C 设备,可以通过模块上的地址焊盘把地址改成 0x25,避免冲突。
I2C 模式还有一个容易被忽略的点:通信时 SDA 和 SCL 线上必须有上拉电阻。很多模块板载已经做了上拉,但也有部分模块偷工减料没焊上拉。如果扫描不到设备,先检查一下这两根线上有没有 4.7k 左右的上拉到 VCC,没有就得自己补。
2.3 SPI 和 UART 接口接线
SPI 模式如果主控是 Arduino UNO,接线是标准的硬件 SPI:
| PN532 引脚 | Arduino UNO 引脚 |
|---|---|
| VCC | 3.3V |
| GND | GND |
| SCK | D13 |
| MOSI | D11 |
| MISO | D12 |
| NSS | D10(可自定义) |
SPI 模式需要把 SEL/PSB 接到低电平。NSS 要注意,Adafruit 库允许你指定任意引脚作为片选,但不建议用 D10 以外的引脚和硬件 SPI 混着用,因为库底层用的是 SPI 接口,片选引脚随意指定容易踩坑。
UART 模式接线最直观,但很多人会在 TX/RX 交叉上栽跟头:PN532 的 TXD 要接主控的 RX,PN532 的 RXD 要接主控的 TX。接成同向就什么都收不到。
UART 模式的另一个坑是电平。Arduino UNO 的 RX/TX 是 5V 电平,PN532 是 3.3V 芯片,直接接存在烧引脚的风险。稳妥的做法是加一个电平转换模块,或者干脆用带 3.3V 串口的主控,比如 ESP32、STM32、树莓派。UNO 上用软件模拟串口接 PN532 我试过,115200 波特率下误码率偏高,不稳定,不推荐。
2.4 供电与电平匹配的几个硬性要求
PN532 芯片本身是 3.3V 的,模块板上虽然有稳压,但很多模块的稳压电路是给 5V 输入设计的,直接把 5V 接到 VCC 会烧稳压或者芯片。我见过的模块烧毁案例,十有八九是 VCC 接了 5V。收到模块第一件事就是确认电源电压,这是最基础的保命操作。
供电还有一个容易被忽视的细节:读卡瞬间电流峰值不低,可能到 100mA 以上。Arduino UNO 板载 3.3V 输出能力有限,如果模块再加上天线发射,3.3V 会被拉低,表现就是读卡成功率下降、偶尔超时。我建议用独立的 3.3V 稳压模块给 PN532 供电,主控和模块共地即可。
电平匹配方面,3.3V 的模块和 5V 主控通信时,除了 UART 电平问题,I2C 和 SPI 同样存在风险。I2C 在 UNO 上是 5V 上拉,直接连 PN532 的 SDA/SCL 也有隐患。最省心的方案是买一块 3.3V 主控(ESP32、STM32、树莓派都行),或者用一块 I2C/SPI 电平转换模块插在中间。
注意:共地是所有接线的前提。电源和主控之间、模块和主控之间,所有 GND 必须连在一起,信号才有参考电平。不要只接 VCC 和 SDA/SCL 忘了共地,这是新手最常见的错误。
3. 实操:从安装库到跑通读卡写卡
3.1 安装 Adafruit PN532 库
Arduino 环境下最常用的库是 Adafruit PN532 库。在 Arduino IDE 的库管理器里搜索 "Adafruit PN532",看到作者是 Adafruit 的那个就安装。这个库同时支持 I2C、SPI、UART 三种接口,API 也写得比较统一。
安装库的时候注意看依赖,Adafruit PN532 库依赖 Adafruit BusIO 和 Adafruit Unified Sensor 等基础库,Arduino IDE 通常会自动安装,如果编译报错找不到头文件,回头检查这几个依赖是不是都装上了。
库装好之后,不用急着写自己的程序,先把官方示例里的readMifare或iso14443a_uid打开,确认接口对应的宏定义开启正确。示例代码顶部一般会有类似这样的定义:
#define PN532_I2C_ADDRESS (0x24)如果你的模块是 SPI 方式,就注释掉 I2C 的定义,打开 SPI 相关的部分。示例程序开头还会让你填写接线引脚,对照上一步的接线改好就行。
3.2 读固件版本:验证连接最快路径
模块接好线、库装好之后,我建议第一件事不是急着刷卡,而是先读固件版本。这一步能一次性验证供电、接线、模式配置、库配置四件事是否全部正确。
I2C 模式下初始化代码是这样:
#include <Wire.h> #include <Adafruit_PN532.h> Adafruit_PN532 nfc(PN532_I2C_ADDRESS); void setup() { Serial.begin(115200); nfc.begin(); uint32_t versiondata = nfc.getFirmwareVersion(); if (!versiondata) { Serial.println("没有读到 PN532 固件版本,请检查接线和模式配置"); while (1); } Serial.print("固件版本: 0x"); Serial.println(versiondata, HEX); nfc.SAMConfig(); } void loop() { // 暂时留空 }如果你用的是 SPI,初始化部分改成:
#include <SPI.h> #include <Adafruit_PN532.h> #define PN532_SCK (13) #define PN532_MOSI (11) #define PN532_MISO (12) #define PN532_SS (10) Adafruit_PN532 nfc(PN532_SCK, PN532_MISO, PN532_MOSI, PN532_SS);注意构造函数参数顺序是 SCK、MISO、MOSI、SS,我在这个上面栽过跟头,抄完代码死活跑不通,后来对了一遍参数才发现 MISO 和 MOSI 写反了。
正常情况串口会输出类似固件版本: 0x0107xxxx的内容。不同批次芯片版本号不完全一样,但只要不是 0,就说明 PN532 已经在正常工作了。SAMConfig()这个调用不要漏,它的作用是初始化安全访问模块,不调用的话后面刷卡会一直超时。
3.3 读取卡片 UID
固件版本读出来了,下面就可以刷卡了。找一张 Mifare Classic 1K 卡,或者手机的 NFC 模拟卡,放在天线区域,运行下面的代码:
#include <Wire.h> #include <Adafruit_PN532.h> Adafruit_PN532 nfc(PN532_I2C_ADDRESS); void setup() { Serial.begin(115200); nfc.begin(); nfc.SAMConfig(); } void loop() { uint8_t uid[] = {0, 0, 0, 0, 0, 0, 0, 0}; uint8_t uidLength; boolean success = nfc.readPassiveTargetID( PN532_MIFARE_ISO14443A, uid, &uidLength, 1000 ); if (success) { Serial.print("读到 UID: "); for (uint8_t i = 0; i < uidLength; i++) { if (uid[i] < 0x10) Serial.print("0"); Serial.print(uid[i], HEX); Serial.print(" "); } Serial.println(); } else { Serial.println("等待卡片..."); } delay(500); }readPassiveTargetID里的PN532_MIFARE_ISO14443A表示查找 ISO 14443A 类型的卡片,常见的 Mifare Classic、Mifare Ultralight、大部分 NFC 标签都属于这一类。函数的最后一个参数是超时时间,单位毫秒,1000 表示一秒内没检测到卡就返回 false。如果刷的是银行卡这类 ISO 14443B 卡片,这里就要换成PN532_ISO14443B对应的查找方式,后面的认证读写也用不了同一套 API。
读 UID 是后面一切读写操作的前置步骤,因为对数据块做认证时,需要把 UID 一起传给芯片。跑通这一步,你已经完成了 PN532 入门的一半。
3.4 Mifare Classic 数据块读写:认证、读、写
读 UID 只是拿到卡的"身份证号",要想真正操作卡里的数据,就得进入 Mifare Classic 的块读写环节。Mifare Classic 1K 卡内部有 1024 字节存储空间,分成 16 个扇区,每个扇区 4 个块,每个块 16 字节。
扇区结构如下:每个扇区的 4 个块里,前 3 块是数据块,第 4 块是尾块,存放 KeyA、访问控制位和 KeyB。默认出厂情况下 KeyA 和 KeyB 都是FF FF FF FF FF FF,等你们自己改过密钥后,旧密钥就失效了。
块号的计算方式是:块号 = 扇区号 × 4 + 扇区内的块索引。扇区 0 的块 0 比较特殊,里面是厂商信息和 UID,出厂后一般不可写,别拿它当普通数据块存东西。
对数据块做写操作之前,必须先通过认证。认证就是向目标扇区的尾块提交密钥,验证通过后才能对该扇区的数据块做读写。Adafruit 库把认证和读写的流程封装好了,参考下面这串代码:
#include <Wire.h> #include <Adafruit_PN532.h> Adafruit_PN532 nfc(PN532_I2C_ADDRESS); uint8_t key[] = {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}; void setup() { Serial.begin(115200); nfc.begin(); nfc.SAMConfig(); uint8_t uid[] = {0, 0, 0, 0, 0, 0, 0, 0}; uint8_t uidLength; // 先等一张卡放上来 boolean success = nfc.readPassiveTargetID( PN532_MIFARE_ISO14443A, uid, &uidLength, 2000 ); if (!success) { Serial.println("超时,没有读到卡片"); while (1); } // 认证扇区 1 的尾块(块号 7) uint8_t blockNumber = 7; success = nfc.mifareclassic_AuthenticateBlock( uid, uidLength, blockNumber, 0, key ); if (!success) { Serial.println("认证失败"); while (1); } // 读块 4 uint8_t data[16]; success = nfc.mifareclassic_ReadDataBlock(4, data); if (success) { Serial.print("块 4 当前数据: "); for (int i = 0; i < 16; i++) { if (data[i] < 0x10) Serial.print("0"); Serial.print(data[i], HEX); Serial.print(" "); } Serial.println(); } // 写块 4 uint8_t newData[16] = { 'H', 'e', 'l', 'l', 'o', ' ', 'P', 'N', '5', '3', '2', '!', 0x00, 0x00, 0x00, 0x00 }; success = nfc.mifareclassic_WriteDataBlock(4, newData); if (success) { Serial.println("块 4 写入成功"); } else { Serial.println("块 4 写入失败"); } // 再读一次确认 success = nfc.mifareclassic_ReadDataBlock(4, data); if (success) { Serial.print("写入后块 4 数据: "); for (int i = 0; i < 16; i++) { if (data[i] < 0x10) Serial.print("0"); Serial.print(data[i], HEX); Serial.print(" "); } Serial.println(); } } void loop() {}注意认证函数的第 4 个参数,0 表示用 KeyA 认证,1 表示用 KeyB 认证。如果这张卡之前被改过密钥,默认的FF FF FF FF FF FF会认证失败,这时候只能找原密钥,或者换一张没改过密钥的卡测试。
写数据还有一个我之前踩过的大坑:写入的数据是 16 字节一整个块,不能只写几个字节。也就是说,哪怕你只想改其中一个字节,也得先把整块数据读出来,在内存里改动那个字节,然后把 16 字节整体写回去。否则其他字节会变成落空数据。
提示:写操作是不可逆的。如果块里有重要数据,写之前一定先把整张卡的所有扇区用读块的方式备份出来,存成文件。别问我为什么强调,烧过一张存着重要数据的 M1 卡你就懂了。
3.5 踩坑记录:写卡前先备份
写卡这个操作,看起来只是几行代码,实际上是对卡片底层数据的直接修改。Mifare Classic 1K 卡本身没有任何"回收站"和"撤销"功能,写入就是覆盖,覆盖了就没了。
我之前帮朋友写一个门禁卡串号备份的小工具,代码写好后直接拿他给的工作卡测试,结果认证逻辑写错,把扇区尾块的访问控制位给改掉了,那张卡的整个扇区直接变砖,读卡器再也认不出有效数据。后来我就养成一个习惯:任何写卡实验之前,先把目标卡的全扇区数据读一遍,保存到串口监视器或者文件里。至少要保存每个扇区前 3 个数据块的内容,尾块如果里面没有自定义数据,也要记录原始访问位的值。
实际测试卡片时,建议多备几张便宜的空白 M1 卡,写坏了不心疼。有些空白卡买回来就是可重复写的,但有些卡在出厂时设置了锁定,只允许写一次,这种卡写第二次就会失败,属于正常现象。
4. 常见问题与排查技巧
玩 PN532 的人基本都会遇到下面这些问题,我把常见的现象、原因和排查办法整理成了一张速查表。
4.1 问题速查表
| 现象 | 可能原因 | 排查方向 |
|---|---|---|
| 固件版本读不出来 | 接线错、模式配置错、供电不足、芯片是兼容品 | 检查 SEL/PSB 跳线、VCC 电压、重新插拔杜邦线 |
| 扫描 I2C 设备没有结果 | 没共地、SDA/SCL 上拉缺失、地址被占用 | 确认 GND 连接、补上拉电阻、换个地址 0x25 |
| 一直等待卡片超时 | 卡类型不对、天线位置不对、供电不足 | 换一张 M1 卡,把卡贴到天线正中央,独立供电 |
| 认证失败 | 密钥错误、卡被改过密钥、选错认证块 | 确认默认密钥是否FF FF FF FF FF FF,确认认证的是扇区尾块 |
| 写卡成功后读出来是空数据 | 写的块是扇区尾块或块 0 | 换扇区内前 3 个数据块测试 |
| UART 乱码 | TX/RX 接反、波特率不对、5V 电平问题 | 交叉接线、确认 115200、加电平转换 |
| 每次读到的 UID 都一样 | 这很正常,因为 UID 是卡出厂固定的 | 无需处理 |
| 读某张特定卡失败,其他卡正常 | 卡损坏、卡协议不兼容 | 换卡验证,确认卡是 ISO 14443A |
4.2 I2C 扫描不到设备怎么排查
这个问题占 PN532 故障的一半以上。第一次接线扫描 I2C 总线,却发现 0x24 地址上没有任何设备,先别急着怀疑模块坏了,按下面顺序排查:
先确认 SEL/PSB 跳线是不是真的切到了 I2C 模式。很多板子用焊盘选择模式,我见过有人把跳线帽插在旁边没用的排针上,看着像设置了,实际根本没接通。
再看共地。PN532 的 GND 和 Arduino 的 GND 必须用杜邦线直接连起来,有时候单独给模块供电就容易漏掉共地这一步。没共地时信号参考电平不一致,I2C 时钟和数据线上的波形完全对不上,设备自然扫描不到。
然后是上拉电阻。在 3.3V 和 SDA 之间、3.3V 和 SCL 之间,各接一个 4.7k 电阻。有些模块板上省了这两个电阻,如果扫描不到,大概率就是缺上拉。
最后怀疑地址。I2C 扫描脚本通常打印 7 位地址,PN532 的默认地址是 0x24。如果你的模块有地址选择焊盘,被改成 0x25 也不是不可能。实在找不到,就用一个最简单的 I2C 扫描程序把所有地址都扫一遍,看返回值落在哪个地址上。
4.3 读卡超时和认证失败
读卡超时,先检查卡是不是放在天线正上方了。PN532 的天线是 PCB 线圈,磁场范围比较小,卡片不能离太远,也不能偏太多。之前我做过一个嵌入项目,把模块装在亚克力外壳里,结果读卡距离从 5cm 直接掉到 1cm,就是因为外壳里有一层金属膜干扰了天线磁场。如果你把模块放在金属桌面上测试,大概率也会出现超时,把模块拿起来悬空试一下就好了。
认证失败大概率是密钥问题。出厂 M1 卡的默认密钥是FF FF FF FF FF FF,但很多门禁卡、校园卡在使用前就已经改过密钥,你拿默认密钥去认证,芯片直接拒绝。这不是 PN532 的问题,是卡本身被写过了。想测试读写功能,找一张新买的空白 M1 卡最省事。
认证的时候还有一个细节:认证函数传的块号必须是这个扇区的尾块,不能是数据块。比如你要操作扇区 1 的块 4,认证时传的块号必须是块 7。传成块 4,认证 API 也会失败,因为芯片内部逻辑只认尾块做认证入口。
4.4 写卡失败和数据被锁
写卡失败最常见的原因是没认证直接写。PN532 的固件层面就禁止了未认证的块写入,你跳过认证直接调WriteDataBlock,返回就是失败。我见过有人连续写十几次失败,最后发现是把认证代码注释掉了,纯属手滑。
还有一种写卡失败是目标块不可写。Mifare Classic 的访问控制位决定了一个块的读写权限,出厂默认通常是可读可写,但如果之前的程序把访问控制位改成了只读模式,后面再怎么认证也写不进去。这种块并不是坏了,是权限被锁住了。
我自己遇到过最头疼的是一次性锁卡。有的空白 M1 卡出厂时块 0 被锁定,只能写一次,写进去之后整张卡就固定了,再想擦除重写根本不行。这种卡一般价格便宜,但不适合做反复测试。买空白卡的时候看清楚商品描述,要买那种"可反复擦写"的,不要买"一次性写入"的。
4.5 固件版本都读不到的玄学原因
如果你按照上面的流程检查完接线、跳线、上拉、供电,固件版本还是读不出来,那就要考虑芯片本身了。市面上打着 PN532 旗号的模块,一部分用的是国产兼容芯片甚至翻新打磨芯片,这类芯片可能在部分命令上不兼容,表现为getFirmwareVersion返回 0。
这种问题没有太好的软件解决办法,只能换模块。我的建议是买模块时优先选销量高、评价多的正规店铺,别贪便宜。PN532 正品芯片价格摆在那里,异常便宜的模块大概率有猫腻。
另外,杜邦线质量也要怀疑。劣质杜邦线插针松旷、线芯氧化,会导致信号时通时断。我排查过一个问题:模块放在桌上能读到固件,手一碰排线就掉线,最后发现是杜邦线母座松了。把线全部换成镀金插针的排线,问题立刻消失。硬件调试到怀疑人生的时候,换个线材往往比换个模块更有效。
最后再说一个我自己的习惯:新买回来的 PN532 模块,我不会直接开始写应用逻辑,而是先做一个烧机测试。用三张不同的卡,一张 Mifare Classic、一张 Ultralight、一张手机模拟 NFC 卡,轮流刷半天,同时观察模块芯片温度和读卡稳定性。温度偏高、偶尔超时的板子,大概率是天线的匹配或者供电纹波有问题,趁早退换,别等写到一半才发现硬件靠不住。硬件这种东西,一开始稳了,后面才能安心写代码。