ARTICLE DETAIL

资讯详情

深耕网站建设、视觉设计与SEO优化的一线实战洞察。

STM32+PN532+I2C读取NFC卡UID完整指南

STM32+PN532+I2C读取NFC卡UID完整指南 简介面向嵌入式开发者的STM32与PN532 NFC/RFID模块通信资料包围绕“通过I²C读取UID”这一核心场景展开同时附带UART驱动与FreeRTOS集成示例适合需要快速掌握NFC标签读取、或希望将PN532移植到实际STM32项目中的开发者。压缩包共272个文件包含大量C源文件与头文件、Keil工程文件uvprojx/uvoptx、PN532库文件、PDF说明文档以及编译过程生成的中间文件o、d、crf、s、lst、map、axf、hex等其中STM32F103RB的FreeRTOS示例和新的UART驱动能帮助理解RTOS环境下NFC任务调度与多接口通信的实现方式。整个包体约14.74MB目录结构清晰完整。目前已有631人学习下载适合课程设计、门禁系统、物联网身份识别等应用开发对照工程源码可快速梳理I²C协议、PN532命令集与硬件接线逻辑显著减少排错时间。 最近在帮朋友做一个基于STM32的NFC卡签到装置需求很朴素刷卡、读UID、亮灯上报。方案对比了一圈最后敲定PN532通信走I2C。整个项目源码精简下来就是标题里那串词——stm32、PN532、i2c、read、uid。本来以为是个十分钟就能跑通的活结果从硬件接线到命令帧解析再到I2C总线上的各种玄学问题前前后后踩了不少坑。这篇文章就把整个流程从头到尾捋一遍包括模块选型逻辑、硬件接法、PN532命令交互细节、UID解析代码、以及我在实测中遇到的那些“看似不可能”的故障希望能帮正在折腾同样组合的人少走点弯路。1. 为什么选PN532I2C这个组合三个接口方案的实际差异1.1 先搞清楚PN532支持哪三种通信方式PN532这块NFC芯片几乎成了非接触式读卡方案的入门标配它内部集成了13.56MHz的射频模拟前端支持ISO/IEC 14443 A/B和Felica市面上能买到的绝大多数IC卡、门禁卡、饭卡只要是高频卡它基本都能读。不过真正让开发者省心的是它同时提供了三种主机接口SPI、UART、I2C而且通过引脚电平配置切换不需要重新烧录固件。三种方式的取舍在项目里其实很现实SPI速度最快理论吞吐量高适合频繁读写大块数据的场景但占用引脚多需要4根线而且不少国产STM32板子上的SPI引脚和板载器件有冲突需要抢资源。UART接线最简单只需要TX/RX两根线调试时还能直接用USB转TTL接电脑跟模块对话非常直观。但UART模式下PN532的波特率配置、唤醒时序、以及部分命令的流控处理都要额外注意尤其是模块和主机之间的电平匹配。I2C只需要SDA/SCL两根线还能挂多个I2C设备到同一条总线上而且PN532有一个非常方便的IRQ引脚能主动通知主机“数据准备好了”不用主机反复轮询等待。这在低功耗项目里优势很明显。我最终选了I2C核心原因有三一是手头这块STM32的I2C外设刚好空闲不跟其他传感器抢SPI和串口二是项目还要并一个IO扩展芯片同一条I2C总线上挂多个设备省事三是PN532的IRQ引脚配合中断方式读取代码写起来比UART的“盲等”要稳得多。1.2 这个组合最适合解决什么场景“stm32-PN532-i2c-read-uid”这个标题背后对应的是一个很典型的应用需求读卡片UID。所谓UID就是每张NFC卡出厂时烧录的唯一标识号长度通常为4字节或7字节。很多场景其实只需要这个UID就够了比如门禁/考勤预先登记一批卡片的UID刷到就开门不需要读写卡内数据。签到打卡读取UID后通过串口或WiFi上报服务器。设备认证把UID当成硬件身份标识做防伪或绑定。我做的是签到装置所以只需要读到UID并校验合法性不涉及MIFARE Classic扇区读写。如果你后续要读卡内扇区数据或者要往卡里写数据流程会再多几步但底层的命令交互机制是完全相通的理解了本文这套帧结构扩展起来不难。2. 硬件连接和上电顺序先把模块点亮再说2.1 引脚分配与上拉电阻的坑PN532模块的I2C接口引出一般是SDA、SCL、IRQ、RSTPD四个关键引脚。RSTPD是复位/掉电引脚根据PN532数据手册在I2C模式下它必须被拉高才能进入正常工作状态IRQ是中断请求输出默认高电平当PN532有数据可读时会拉低。我用的是STM32F103C8T6硬件I2C1对应PB6SCL和PB7SDA连接如下PN532引脚STM32引脚说明SDAPB7I2C数据线开漏输出SCLPB6I2C时钟线开漏输出IRQPB0输入模式检测PN532中断RSTPDPB1输出模式控制PN532复位这里第一个容易踩的坑就是上拉电阻。I2C协议要求SDA和SCL必须有上拉电阻才能正常工作但很多市面上的PN532模块已经板载了上拉而有些精简版模块没有。如果模块没有上拉你必须自己在外部加两个4.7kΩ电阻到3.3V。更隐蔽的问题是如果模块板载上拉而你外接的STM32开发板内部也启用了上拉两者并联后等效电阻会变小某些情况下I2C信号边沿会变差导致通信不稳定。我一开始直接开了STM32内部的GPIO上拉结果发现偶尔读数据会错位改成外部4.7kΩ上拉、关闭内部上拉后问题消失。2.2 I2C地址和模式配置不是0x48就是0x49的误区PN532在I2C模式下有一个7位从机地址0x24左移一位后变成8位写地址0x48读地址0x49。这个地址默认是固定的但也有个别模块通过板上的地址选择电阻做了改动所以最好先用I2C扫描程序扫一遍总线确认模块实际地址而不是直接写死0x48。很多人在这个环节犯的错误是把PN532的“I2C模式”和“模块上电默认模式”搞混。PN532在I2C模式下SEL0和SEL1引脚必须保持特定电平一般是悬空或拉高具体看模块否则模块可能默认进入UART模式此时你发I2C地址永远得不到ACK。我就碰到过一块模块卖家说支持I2C结果出厂时SEL引脚被默认接到了GND导致一直无法通信后来强行把SEL1拉高才解决。2.3 上电时序先VCC后RSTPD顺序反了会进不了I2C模式PN532的上电复位时序比普通传感器要讲究。正确的顺序是先给模块供上3.3V电源然后延迟至少10ms再把RSTPD从低拉高让芯片完成内部复位并进入I2C模式。如果供电和RSTPD同时拉高或者RSTPD先拉高芯片可能无法正确识别SEL引脚电平状态导致进入错误的通信模式。代码里我封装了一个PN532_Reset函数void PN532_Reset(void) { HAL_GPIO_WritePin(GPIOB, GPIO_PIN_1, GPIO_PIN_RESET); // RSTPD低 HAL_Delay(10); HAL_GPIO_WritePin(GPIOB, GPIO_PIN_1, GPIO_PIN_SET); // RSTPD高 HAL_Delay(10); }别小看这个10ms延迟很多人I2C扫描不到设备不是线接错了而是复位时序不对。PN532数据手册里明确写了电源稳定到RSTPD释放之间需要等待t_rst复位时间实测下来10ms是比较稳妥的值。2.4 热搜词里那个“pn532卡应该怎么放置”看到热搜词里有“pn532卡应该怎么放置”我其实一点不意外。PN532的射频天线不是均匀分布在整个PCB板面的而是环绕在PCB边缘的线圈。很多人的操作习惯是把卡贴在模块正中央结果读不到或者要来回挪半天。正确做法是让卡片尽量贴近模块PCB的四周边缘区域尤其是模块上印有天线轮廓的那一圈。如果你的模块是带线圈延长板的天线板那就把卡片贴在天线板的中空线圈区域而不是实心的电路板位置。另外PN532对电源质量很敏感读取瞬间的电流波动可能导致射频场不稳定。建议模块供电脚旁边加一个10μF电解电容和一个100nF陶瓷电容靠近VCC引脚放置这对读卡成功率的提升非常明显。3. 核心读取流程从I2C裸读到PN532命令帧解析3.1 I2C通信的底层细节为什么读命令前要发一个0x01PN532在I2C模式下主机和它之间的数据交换不是像普通EEPROM那样“写寄存器地址然后读写寄存器”而是有一种比较特殊的握手方式。主机在读取数据之前需要先发送一个0x01字节作为读请求标志然后再启动一次I2C读事务PN532才会把数据放上总线。这个0x01不是什么寄存器地址而是PN532自定义的I2C读触发码很多第一次接触的人会在这里卡住。我用的是STM32 HAL库对应的读取逻辑如下uint8_t PN532_ReadData(uint8_t *buf, uint16_t len) { uint8_t cmd 0x01; // 先发读请求标志 HAL_I2C_Master_Transmit(hi2c1, PN532_I2C_ADDR, cmd, 1, 100); // 再启动读事务 HAL_I2C_Master_Receive(hi2c1, PN532_I2C_ADDR | 0x01, buf, len, 100); return 0; }不过实际工程中不能一上来就读PN532可能还没准备好数据。这就是IRQ引脚存在的意义PN532在收到命令并处理完之后会把IRQ拉低表示有数据等待主机读取。所以标准的时序应该是发完命令后等待IRQ变低然后再发起I2C读。3.2 PN532命令帧格式Preamble、Start Code、DCS校验PN532的命令帧和响应帧有一套固定的封装格式理解这个才是整个项目的核心。一帧数据从主机发往PN532时的格式为Preamble1字节固定0x00Start Code2字节固定0x00 0xFFTFI1字节主机到PN532时为0xD4PN532到主机时为0xD5PD0长度1字节表示后面数据字段PD1到校验和之前的长度PD1长度校验1字节值为(PD0 1)按位取反用于长度校验数据字段实际命令DCS校验和1字节使得“TFI PD0 数据字段所有字节 DCS”之和的低8位为0Postamble1字节固定0x00举个例子发送“SAMConfig”命令让PN532进入普通NFC模式数据字段是0x14 0x01 0x00完整帧就是00 00 FF 04 D4 14 01 00 17 00这里TFI0xD4PD00x04表示后面有4个字节D4 14 01 00长度校验取反为0xFB等一下我拆一下Preamble: 0x00Start Code: 0x00 0xFFTFI: 0xD4PD0: 0x04PD1: 0xFB因为0x04 0x01 0x05取反0xFA再算PD1 ~(PD0 1) ~(0x041)~0x050xFA数据: 0x14 0x01 0x00DCS: 使 TFIPD0数据DCS 0。0xD40x040x140x010x00 0xEDDCS 0x00 - 0xED 0x13Postamble: 0x00所以实际发送的是00 00 FF 04 D4 14 01 00 13 00。很多网上贴的代码里SAMConfig命令写的是00 00 FF 04 D4 14 01 00 17 00那是UART模式下的帧格式I2C模式下也是一样的帧只不过校验值不同。我建议你在写代码时不要手算校验和而是封装一个通用的发送函数自动计算PD1和DCSvoid PN532_SendCommand(uint8_t *cmd, uint8_t len) { uint8_t frame[64]; uint8_t checksum 0; frame[0] 0x00; // Preamble frame[1] 0x00; // Start Code frame[2] 0xFF; // Start Code frame[3] 0xD4; // TFI frame[4] len 1; // PD0包含TFI在内的数据长度 frame[5] ~(len 1) 1; // PD1取反加1等价于按位取反 // 或者 frame[5] (uint8_t)~(len 1); checksum 0xD4 frame[4]; for (uint8_t i 0; i len; i) { frame[6 i] cmd[i]; checksum cmd[i]; } frame[6 len] (uint8_t)(0x00 - checksum); // DCS frame[7 len] 0x00; // Postamble HAL_I2C_Master_Transmit(hi2c1, PN532_I2C_ADDR, frame, 8 len, 100); }注意PD1的计算取反加1或者直接按位取反都可以因为按位取反和补码在这里只差一个加1的细节PN532手册用的是负值表示实际上以“~(PD01)”为准。3.3 读UID的完整命令流程SAMConfig InListPassiveTarget读UID的完整流程分两步。第一步是发送SAMConfig命令配置PN532的SAM安全访问模块寄存器让它进入正常模式uint8_t sam_config[] {0x14, 0x01, 0x00}; PN532_SendCommand(sam_config, 3);发送后等待IRQ变低然后读出响应帧确认响应数据是00 00 FF 00 FF 00 00 00 00之类的正常回复TFI0xD5D00x01实际是00 00 FF 03 D5 14 01 00 15 00之类关键是TFI变为0xD5命令号回显0x14。第二步是核心发送InListPassiveTarget命令让PN532暂停射频场、搜索一张目标卡并返回卡片的ATQA、SAK和UIDuint8_t list_passive_target[] {0x4A, 0x01, 0x00}; PN532_SendCommand(list_passive_target, 3);这个命令的参数含义是0x4A是InListPassiveTarget命令号0x01表示最多检测1张卡0x00表示使用106kbps Type A的通信速率。如果你要同时检测多张卡可以把0x01改成0x02但同一个射频场上同时出现多张卡时会出现防冲突处理返回的数据会更复杂一般读UID场景固定用1张就够了。等待IRQ变低后读出响应帧。PN532返回的响应帧中去掉帧头Preamble、Start Code、TFI、长度、校验后真正的数据内容从第0个字节开始依次为偏移内容说明00xD5响应TFI10x4B命令号回显2NbTg检测到的卡片数量正常为13Tg卡片逻辑编号第一张卡为0x014-5ATQA卡片应答信息Type A卡通常为0x0400或0x44006SAK卡片确认字节MIFARE Classic 1K通常为0x087NFCID长度一般为4或78~NFCID/UID真正的UID数据我封装了解析函数直接返回UID长度和UID内容uint8_t PN532_ReadUID(uint8_t *uid, uint8_t *uid_len) { uint8_t buf[64]; uint16_t len 0; // 发送SAMConfig并等待响应省略细节 PN532_SAMConfig(); // 发送InListPassiveTarget uint8_t cmd[] {0x4A, 0x01, 0x00}; PN532_SendCommand(cmd, 3); PN532_WaitIRQ(1000); // 等待IRQ拉低超时1秒 len PN532_ReadResponse(buf, sizeof(buf)); // 检查响应帧起始是否正确这里省略帧校验 if (buf[0] ! 0x00 || buf[1] ! 0x00 || buf[2] ! 0xFF) { return 1; // 帧头错误 } uint8_t *data buf 6; // 跳过帧头定位到数据区起始 // 此时data[0]0xD5, data[1]0x4B if (data[0] ! 0xD5 || data[1] ! 0x4B) { return 2; // 命令响应错误 } if (data[2] 0) { return 3; // 没有检测到卡片 } *uid_len data[7]; // NFCID长度 for (uint8_t i 0; i *uid_len; i) { uid[i] data[8 i]; } return 0; }注意我这里跳转帧头时用的是buf 6但实际上帧头包含了Preamble、Start Code、TFI、PD0、PD1、DCS这些字节响应帧里“数据区”的起点位置取决于你读响应时是从哪个字节开始存入buf的。我建议在读取响应时就把完整的原帧读回来然后在代码里定位不要假设固定位置因为PN532的响应长度会根据检测到的卡片类型变化。4. 实测中掉过的坑从I2C卡死到UID解析错误4.1 经典故障I2C总线卡死HAL_I2C_Master_Transmit一直超时这是我折腾这个项目时耗时最长的一个问题而且它在STM32圈子里特别经典。现象是程序跑起来后第一次发送命令成功但第二次调用HAL_I2C_Master_Transmit时函数返回HAL_BUSY或者一直等到超时。网上搜“stm32f407模拟i2c”有一堆人问其实根因不外乎三个第一GPIO没有配置成开漏输出。I2C的SDA和SCL必须是开漏加上拉电阻如果配置成了推挽输出总线电平会被强制拉高或拉低出现总线冲突。检查你的GPIO初始化代码确保是GPIO_MODE_AF_OD复用开漏而不是GPIO_MODE_AF_PP。第二I2C从设备在通信过程中拉低了SCL线时钟延展但主机没有正确处理。PN532的I2C接口支持时钟延展但HAL库和一些低版本的STM32固件库对时钟延展的支持并不好。解决办法是调低I2C时钟频率把I2C_InitStruct.ClockSpeed从400000降到100000试试。PN532的I2C最高支持400kHz但实际使用中100kHz稳定得多读UID这种小数据量的场景根本不需要那么快。第三也是最隐蔽的STM32F1系列硬件I2C外设有一个著名的“Busy Flag Bug”。当I2C通信中途被意外打断比如超时、从设备NACKI2C外设的BUSY标志位可能一直无法清除导致后续所有传输都直接返回HAL_BUSY。这个问题的万金油解法是给I2C外设加一个软件复位流程void I2C_Recovery(void) { // 复位I2C外设 __HAL_I2C_DISABLE(hi2c1); // 将SCL和SDA配置为GPIO输出手动产生9个时钟脉冲 GPIO_InitTypeDef GPIO_InitStruct {0}; GPIO_InitStruct.Pin GPIO_PIN_6 | GPIO_PIN_7; GPIO_InitStruct.Mode GPIO_MODE_OUTPUT_OD; GPIO_InitStruct.Pull GPIO_PULLUP; GPIO_InitStruct.Speed GPIO_SPEED_FREQ_HIGH; HAL_GPIO_Init(GPIOB, GPIO_InitStruct); for (int i 0; i 9; i) { HAL_GPIO_WritePin(GPIOB, GPIO_PIN_6, GPIO_PIN_SET); HAL_Delay(1); HAL_GPIO_WritePin(GPIOB, GPIO_PIN_6, GPIO_PIN_RESET); HAL_Delay(1); } // 重新初始化I2C外设 MX_I2C1_Init(); }话说回来如果项目里不是必须用硬件I2C直接用GPIO模拟I2C反而更省心。我后来在实际项目中用软件模拟I2C彻底摆脱了这些烦人的外设问题只需要保证GPIO的翻转速度够快。STM32F407跑48MHz翻转GPIO模拟100kHz的I2C完全没压力。4.2 读回来的UID不稳定有时前面多了0xFF有时顺序反了另一个我花了不少时间排查的问题是UID数据不稳定。有时候读到4个字节有时候读到5个字节而且偶尔第一个字节是0xFF或0x00看起来毫无规律。这个问题的根因其实不在PN532而在我自己的I2C读取逻辑。PN532的I2C读流程要求先发0x01然后立刻读。但如果你在发完0x01之后、读取之前没有正确等待IRQ变低PN532可能还没有把响应帧完整地放到I2C发送缓冲区里导致读回来的数据不完整。更麻烦的是PN532的I2C缓冲区在读取后会自动清空如果你多读了几个字节那些多出来的字节就是垃圾数据会把后续的帧数据冲掉。解决方法是严格遵循“发命令→等IRQ→读完整帧”的顺序并且每次读取多少字节要以IRQ来电时PN532实际可用的数据量为准不能拍脑袋定一个固定长度。我封装了一个读取函数先读第一个字节判断数据是否准备好然后根据帧头中的PD0字段计算总长度再一次性读完uint16_t PN532_ReadResponse(uint8_t *buf, uint16_t max_len) { uint8_t header[6]; // 等待IRQ拉低 while (HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_0) ! GPIO_PIN_RESET); // 读前6个字节Preamble Start Code TFI PD0 PD1 DCS PN532_ReadData(header, 6); if (header[0] ! 0x00 || header[1] ! 0x00 || header[2] ! 0xFF) { return 0; // 帧头错误 } uint8_t length header[4]; // PD0 // 总帧长度 前导6字节 length个数据字节 1个DCS 1个Postamble uint16_t total_len 6 length 2; if (total_len max_len) { total_len max_len; } memcpy(buf, header, 6); PN532_ReadData(buf 6, total_len - 6); return total_len; }4.3 卡片靠近时IRQ不触发先查射频场再查电源还有一次很诡异的故障模块刚上电时一切正常卡片贴上也能读到UID但连续读写一段时间后再贴卡就完全没反应了IRQ始终不拉低。测了一下模块VCC电压发现模块供电电压被拉低到2.8V左右而PN532的最低工作电压是2.7V虽然勉强在工作但射频场强度已经大幅衰减。排查到最后发现是杜邦线压降问题。模块和STM32开发板之间的供电线用的是一根15厘米的细杜邦线瞬间电流一大线上压降就超过0.5V。换了一根粗短线之后问题彻底消失。所以建议模块供电不要走面包板跳线尽量用短而粗的线缆直接连到3.3V电源或者干脆用模块自带的排针焊接到主板上。4.4 关于“stm32 target not found”的提醒热搜词里出现“error: no stm32 target found! if your product embeds debug authentication”这个报错很多人在做类似项目时会遇到。这里说明一下这个报错是ST-Link调试器连接STM32芯片失败时出现的跟PN532模块本身没有任何关系。常见原因是调试引脚被程序复用了比如我把PB6/PB7用作I2C但调试口SWD是PA13/PA14一般不会冲突、芯片进入低功耗模式、或者ST-Link和芯片之间的SWD线接触不良。如果你跟我一样在代码里把PB6/PB7配置成了I2C功能并且在调试时发现ST-Link连不上先检查是不是把芯片搞进了HardFault或者低功耗状态。另外如果是全新芯片第一次烧写记得用ST-Link Utility的“Connect under reset”模式。5. 稳定运行后的工程化思考代码分层、低功耗与后续扩展5.1 代码别全堆在main函数里简单分层能救命当PN532读UID跑通之后紧接着就是把它真正变成一个能长期运行的产品/装置。这个阶段最值得做的不是继续加功能而是把代码重新组织一下。我习惯把和PN532相关的代码拆成两个层级底层是platform层负责和硬件打交道包括I2C收发、IRQ等待、RSTPD控制、以及CRC校验等。上层是command层负责拼装和解析PN532的协议帧比如SAMConfig、InListPassiveTarget、MIFARERead等。这样如果后续换了一个主控、或者从硬件I2C换成软件模拟I2C只需要改底层那几个函数上层的读卡命令就不用动了。别小看这个分层习惯。我在调试过程中经常需要在上层加日志、加状态统计如果所有代码都堆在main里每改一次都要重新翻一遍几百行I2C时序非常痛苦。5.2 低功耗场景让PN532休眠用IRQ唤醒如果你的装置是电池供电PN532的功耗是个不能忽视的问题。PN532正常工作时电流大约几十毫安一直开着会很快耗尽电池。PN532本身支持Hibernate模式进入该模式后电流可以降到微安级别。进入Hibernate最简单的方式是发送0x02 0x01 0x00PDAnalyze命令不是这个命令号不准确——实际上PN532数据手册里有一个SetParameter或者直接通过命令让其进入低功耗的流程但我更常用的做法是直接用硬件复位引脚控制平时把RSTPD拉低让模块完全掉电需要读卡时再重新执行上电复位流程。这个方案的缺点是需要重新初始化一遍PN532每次读卡大约多花50ms但对于大多数签到/门禁应用来说完全够用。如果你不想重新初始化可以让PN532进入休眠模式然后用IRQ引脚的下降沿来唤醒STM32的停机模式。不过需要注意PN532在Hibernate模式下IRQ引脚的行为和正常模式不一样需要仔细看数据手册的时序图这里就不展开了。5.3 后续还能怎么扩展从读UID到读写MIFARE扇区读UID只是NFC应用的敲门砖。同样的PN532I2C链路改几个命令参数就能实现更多功能读取MIFARE Classic卡片数据使用MfRead命令0x30需要先通过MfClassicAuth0x40命令进行扇区认证认证需要知道扇区密钥。默认的密钥通常是FFFFFFFFFFFF或者A/B两组厂商默认密钥。写入数据使用MfWrite命令0xA0可以往卡片扇区写入自定义数据比如把用户ID、余额等写到卡里做成离线钱包或身份令牌。读取NTAG系列标签NTAG213/215/216这类NFC标签存储容量更大使用READ命令0x30可以直接读取其数据块不需要认证常用于防伪溯源场景。我实测过在同一块STM32上用同样的I2C链路读MIFARE Classic 1K卡的扇区数据流程基本就是“SAMConfig→InListPassiveTarget→MfClassicAuth→MfRead”每一步的帧结构都和本文讲的完全一致只是命令号和数据参数不同。所以一旦吃透了PN532的帧协议后面扩展功能就是查数据手册、拼字段的体力活了。说实话这个项目本身技术难度不算高PN532和STM32的官方文档也都写得足够详细但真正做起来还是会遇到各种文档里不会写的问题——I2C总线时序、模块引脚配置、卡片放置位置、电源稳定性每一个都能让人折腾半天。我在最终稳定运行后最大的体会是像这种“模块主控”的经典组合项目不要一上来就埋头写代码先把硬件连接、复位时序、I2C地址这些基础项逐一确认清楚再动手写协议栈会省下很多反复排查的时间。如果你现在也卡在读UID的某个环节上建议先用逻辑分析仪抓一遍I2C波形看看PN532到底有没有正确回复ACK这比盲调代码要高效得多。希望这篇记录能帮到你。本文还有配套的精品资源点击获取
返回列表