news 2026/9/9 5:01:53

STM32F103与PN5180的Keil工程实战:从SPI移植到稳定读取UID

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
STM32F103与PN5180的Keil工程实战:从SPI移植到稳定读取UID

简介:一套Keil工程文件整合了STM32F103C8T6与PN5180 NFC控制器的完整驱动与示例代码,面向嵌入式开发者,用于实现Mifare Classic及ICODE SLIX2非接触式卡片的读写,重点演示了在SPI2通信下不依赖BUSY信号、通过软件延时完成收发等待的时序处理思路。压缩包共1186个文件,约19.61MB,以.c/.h源码为主,同时包含工程配置、链接脚本、库文件及调试辅助文件,可直接在Keil环境中打开参考。目前已有1482人学习下载,代码中封装了NFC论坛库调用、命令发送与响应解析、卡操作API等关键模块,并提供了初始化、SPI配置、读写流程及调试建议,适合正在开发物联网设备、支付终端或门禁读卡器的工程师快速移植验证。 做 NFC 读卡器项目时,我翻遍了搜索引擎里关于“STM32F103、PN5180、keil 工程”的组合关键词,发现一个尴尬的事实:资料不少,但大多是零散的寄存器截图、Arduino 例程搬运,或者只讲 PN5180 本身、不讲怎么和 STM32F103 组织成一个完整可维护的 Keil 工程。这个组合几乎是做门禁、读卡器、NFC 标签读写最经典的嵌入式搭配,我自己也在踩完一堆坑后才算把流程理顺。这篇记录适合正准备把 PN5180 从 Arduino 或者 Linux 环境迁到 STM32F103 上、并且用 Keil 建工程的人,也适合已经开始动手但卡在 SPI 通信、启动文件和驱动移植上的朋友。

1. PN5180 和 STM32F103 是怎么看对眼的

1.1 为什么不是 RC522 或 PN532

很多人第一次做读卡器,脑子里第一个冒出来的是 RC522,毕竟十几块钱一片、资料满天飞、Arduino 一接就能读卡。但 RC522 只支持 ISO14443A 和 MIFARE 系列,协议覆盖面窄,射频前端的可配置性也一般,真做产品级的门禁、支付场景很容易碰壁。PN532 是另一个常见选择,接口丰富,做 NFC 交互很方便,但功率、灵敏度和协议覆盖和 PN5180 不是一个量级。

PN5180 是 NXP 的高性能 NFC 前端,支持 ISO/IEC 14443 Type A/B、ISO/IEC 15693、FeliCa 等常见卡片协议,内置收发器和协议处理固件,支持动态功率控制(DPC),还能通过 EEPROM 配置射频参数。换句话说,MCU 不需要去处理帧级的 CRC、奇偶校验这些脏活,只需要通过 SPI 给 PN5180 下发高层命令,再从 FIFO 里读结果。这正好让 STM32F103 这种“老而弥坚”的 MCU 发光发热:72MHz 主频跑起来绰绰有余,SPI 接口又是标配,成本低、资料多、开发环境成熟,市场上一抓一大把的核心板配合标准外设库,工程结构可以做到非常干净。

项目RC522PN532PN5180
支持协议ISO14443A / MIFARENFC 常见协议(14443A / FeliCa 等)ISO14443A/B、ISO15693、FeliCa
数据接口SPI/I2C/UARTUART/I2C/SPISPI 从机
射频能力一般一般强,支持 DPC、功率可调
上手成本极低中等,需要 SPI 时序和驱动移植
典型场景简单门禁NFC 交互、简单读卡支付、门禁、工业读卡设备

1.2 这组合适合做什么

基于 STM32F103 加 PN5180 的 Keil 工程,最常见的落地场景是门禁读卡器、访客终端、充电桩身份识别模块、工业设备的 RFID 身份绑定,以及各种需要读取非接触式 IC 卡 UID 或数据的设备。

这些项目的共同特点是:主控要跑的业务逻辑并不复杂,但需要稳定、可配置的射频前端,而且团队往往对 STM32 生态很熟,不想为了一个 NFC 前端引入 Linux 或者更复杂的主控。STM32F103 负责按键、屏幕、继电器、串口通信这些周边,PN5180 专心做射频,各干各的活。只要 SPI 底层驱动稳定,后续换天线、调功率、加协议都是一层配置的事,不污染主控代码。

2. 硬件连接:PN5180 的 SPI 从机逻辑和上电时序

2.1 引脚分配

PN5180 模块一般引出 SPI 接口、BUSY、RST 和电源引脚。它作为 SPI 从机,工作电压 3.3V,引脚不多,接线也不复杂。下面是我常用的一组 SPI1 引脚分配,如果你板子上的复用引脚和别处冲突,换到 SPI2 也可以,但注意 SPI2 挂在 APB1 总线上,时钟上限比 SPI1 低,对 PN5180 这种通信频率不高的从机来说影响不大,就是初始化时时钟源要写对,别照抄 SPI1 的 RCC 使能。

PN5180 引脚作用建议接 STM32F103
SCKSPI 时钟PA5(SPI1_SCK)
MOSI主机出,从机入PA7(SPI1_MOSI)
MISO主机入,从机出PA6(SPI1_MISO)
NSS片选PA4 或任意 GPIO
BUSY忙状态输出PA3(输入浮空/上拉)
RST复位输入PA2(推挽输出)
VCC / GND电源3.3V / GND

NSS 我从不使用 STM32 的硬件 NSS,而是用普通 GPIO 软件拉低拉高。原因很简单:PN5180 的 SPI 帧不是单纯发几字节,而是先发两字节 header 再发不定长数据,中间还可能要等 BUSY,软件片选在手,时序完全可控,排查问题也直观。BSUY 引脚一定要接,且要用输入模式轮询,不要省。很多移植跑不通的案例,根源就是没接 BUSY 或者接了没等。

2.2 复位时序和 BUSY 信号

PN5180 不是上电就能直接读寄存器,必须走一遍复位时序。我实测下来的稳定做法是:先保证 3.3V 电源稳定,RST 拉低至少 100 微秒以上,再拉高,之后等待 BUSY 引脚从高电平变成低电平,表示内部初始化完成。有些模块上电后要等 10 毫秒左右才就绪,这个时间不算长,但工程上要写成延时函数而不是靠人品。

BUSY 在 PN5180 处理内部命令时会被拉高,主机如果这时发 SPI 帧,数据会被忽略或者导致状态错乱。所以每次发起 SPI 事务前,先确认 BUSY 为低,或者事务完成后等它恢复。移植官方库时一般会有一个等待 BUSY 低电平的函数,但那个函数在异常状态下容易死循环,我的习惯是加超时,超时后打印错误并返回失败,否则项目烧进现场设备里,卡死一次就要断电重启,很难跟客户解释。

2.3 供电与天线

PN5180 的射频发射电流不小,虽然模块大多有稳压,但我不建议跟舵机、继电器、电磁锁共用同一个 3.3V 电源。如果项目里这些大电流外设必然存在,至少给 PN5180 单独加一颗 LDO,或者用双电源方案,否则读卡距离会忽远忽近,严重时直接复位重启。

天线是另一个容易被忽视的坑。如果你买的是成品模块,天线匹配电容电阻厂家已经调好,直接用没问题。如果自己画板子,天线走线宽度、线圈尺寸、谐振电容都要按 PN5180 数据手册和应用笔记(比如 NXP 关于天线设计的说明)来算,不是随便画个线圈就能有很好的读卡距离。这个主题展开又是另一篇长文,这里只提醒一句:读卡距离近,先怀疑天线,不要急着改 MCU 代码。

3. Keil 下从零建 STM32F103 标准库工程

3.1 标准库、Pack 和目录结构

虽然现在 ST 主推 HAL 库和 CubeMX,但 STM32F103 的标准外设库 V3.5.0 依然有大量存量项目在用,优势是轻、直观,适合像 PN5180 这种外设不多、逻辑清楚的工程。搜索“STM32F10x_StdPeriph_Lib”就能下载到那套经典库,解压后重点看两个目录:Libraries/CMSIS 和 Libraries/STM32F10x_StdPeriph_Driver。

MDK 版本建议用 5.x,安装完后用 Pack Installer 安装 STM32F1xx_DFP 器件支持包,否则 Device 列表里找不到 STM32F103 的具体型号。新建工程时,我习惯先把目录结构定好,不要让 Keil 自动把所有文件丢在一起:

project/ ├── User/ main.c、stm32f10x_it.c 等 ├── Libraries/ CMSIS 与标准库驱动 ├── Hardware/ PN5180 相关驱动文件 └── Objects/ 编译输出

然后往工程里分 Group:Startup 放启动文件,CMSIS 放 core_cm3.c 和 system_stm32f10x.c,StdPeriph 按需添加 rcc.c、gpio.c、spi.c、usart.c、misc.c,User 放 main.c,Hardware 放 PN5180 的驱动。标准库驱动不需要全集添加,用多少加多少,否则编译时间和目标文件体积都往上走,还容易出现外设库之间的一些隐藏依赖问题。

3.2 启动文件与宏定义要匹配

这是 Keil 建 STM32F103 工程最常见的翻车点。选错启动文件或者宏定义不匹配,症状千奇百怪,最典型的是程序一上电就跑进 HardFault。

芯片类型Flash 容量启动文件C/C++ 宏定义
STM32F103C8T6 等64KB~128KBstartup_stm32f10x_md.sSTM32F10X_MD, USE_STDPERIPH_DRIVER
STM32F103ZET6 等256KB~512KBstartup_stm32f10x_hd.sSTM32F10X_HD, USE_STDPERIPH_DRIVER

在 Options for Target 的 C/C++ 选项卡里填 Define 内容,同时在 Include Paths 里把 Libraries 相关的头文件目录加全。很多“找不到头文件”的编译错误,不是文件不存在,而是路径没加,或者中括号写多写少。另外,工程绝对路径里不要有中文和空格,这是 Keil 的老毛病,能避免就避免。

3.3 编译配置、授权和生成 bin

MDK 5 的授权问题一直没有消失过。我的态度比较直接:能拉正版授权或评估版就用,别花时间研究注册机。评估版有代码体积限制,PN5180 工程稍微加些协议栈就容易撞墙,破解环境里出现的调试器抽风、Pack 更新失败这类问题,排查起来特别浪费时间。这点投入真不该省。

生成 bin 文件在量产阶段很有用,配置方法是在 Options for Target 的 User 页 After Build/Rebuild 里加一行:

fromelf --bin --output=Objects/f103_pn5180.bin Objects/f103_pn5180.axf

注意路径是相对 uvprojx 所在目录的,如果你把输出目录改成 Output 或者 Build,路径要对应改。烧录用 ST-Link 时,在 Debug 页选择 ST-Link Debugger,然后进入 Settings 确认能识别到芯片,否则下载时会报找不到目标。

3.4 验证工程:先点灯后串口

工程建好先别急着烧 PN5180 驱动,先在 main.c 里点亮一颗 LED,或者用串口打印一个“hello”,确认最小系统、时钟、调试器和下载链路全通。STM32F103 的外部晶振通常用 8MHz,标准库默认 HSE_VALUE 就是 8MHz,系统时钟最终通过 PLL 倍频到 72MHz。如果你的板子晶振不是 8MHz,要在 stm32f10x.h 里改 HSE_VALUE,并在 system_stm32f10x.c 里对 RCC_PLLConfig 做对应调整,否则串口波特率怎么配都是乱的。

4. PN5180 官方驱动移植:真正要改的只有三个地方

4.1 PN5180 的 SPI 帧协议

PN5180 虽然是 SPI 从机,但它的 SPI 帧和普通外设不太一样。每次事务开始,主机要把片选拉低,先发送两字节 header,再根据命令类型发送或接收 payload。第一字节是命令类型,常见的包括写寄存器、读寄存器、写 EEPROM、读 EEPROM、发送数据、读取数据等。第二字节的含义取决于命令:读写寄存器时它是寄存器地址,读写 FIFO 数据时它是数据长度。

这个设计初看有点绕,想清楚后就一句话:PN5180 通过这两字节告诉内部固件“你接下来要干什么”,然后主机按约定把数据补齐。如果第二字节的含义搞错,比如把长度当成了地址,寄存器读写会全部错位,读回来的数据就是一堆 0xFF 或者 0x00。

4.2 在 Keil 里实现 SPI 底层适配

从 NXP 的开源仓库获取 PN5180 驱动后,你会发现核心代码已经写好了,真正需要自己实现的底层函数并不多,主要是三个:SPI 数据收发、GPIO 控制片选与复位、延时。下面是一个基于标准外设库的最小适配伪代码,可以放到 Hardware 目录里:

void PN5180_SPI_Init(void) { SPI_InitTypeDef spi; GPIO_InitTypeDef gpio; RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_SPI1, ENABLE); /* SCK: PA5, MOSI: PA7, MISO: PA6 */ gpio.GPIO_Pin = GPIO_Pin_5 | GPIO_Pin_7; gpio.GPIO_Mode = GPIO_Mode_AF_PP; gpio.GPIO_Speed = GPIO_Speed_50MHz; GPIO_Init(GPIOA, &gpio); gpio.GPIO_Pin = GPIO_Pin_6; gpio.GPIO_Mode = GPIO_Mode_IN_FLOATING; GPIO_Init(GPIOA, &gpio); /* CS: PA4, RST: PA2, BUSY: PA3 */ gpio.GPIO_Pin = GPIO_Pin_4 | GPIO_Pin_2; gpio.GPIO_Mode = GPIO_Mode_Out_PP; gpio.GPIO_Speed = GPIO_Speed_50MHz; GPIO_Init(GPIOA, &gpio); GPIO_WriteBit(GPIOA, GPIO_Pin_4, Bit_SET); /* CS 默认高 */ GPIO_WriteBit(GPIOA, GPIO_Pin_2, Bit_SET); /* RST 默认高 */ SPI_InitTypeDef spiInit; spiInit.SPI_Direction = SPI_Direction_2Lines_FullDuplex; spiInit.SPI_Mode = SPI_Mode_Master; spiInit.SPI_DataSize = SPI_DataSize_8b; spiInit.SPI_CPOL = SPI_CPOL_Low; spiInit.SPI_CPHA = SPI_CPHA_1Edge; spiInit.SPI_NSS = SPI_NSS_Soft; spiInit.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_8; spiInit.SPI_FirstBit = SPI_FirstBit_MSB; SPI_Init(SPI1, &spiInit); SPI_Cmd(SPI1, ENABLE); }

SPI 模式这里值得多说一句。PN5180 的手册和不同家的例程给出的模式不完全一致,我在实际项目里用 CPOL=0、CPHA=0 能正常工作,也见过有人用 CPOL=1、CPHA=1 跑起来的。遇到 MISO 读回全是 0xFF 或者 0x00 时,第一时间把 SPI 模式换一组极性和相位试,往往比反复检查寄存器快得多。速度上,建议一开始分频系数设大一点,比如 8 分频,稳定后再往 2 分频、4 分频调。

片选和字节收发实现:

void PN5180_CS_Low(void) { GPIO_WriteBit(GPIOA, GPIO_Pin_4, Bit_RESET); } void PN5180_CS_High(void) { GPIO_WriteBit(GPIOA, GPIO_Pin_4, Bit_SET); } uint8_t PN5180_SPI_TransferByte(uint8_t byte) { while (SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_TXE) == RESET); SPI_I2S_SendByte(SPI1, byte); while (SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_RXNE) == RESET); return SPI_I2S_ReceiveByte(SPI1); }

有了这两个基础函数,就能实现 NXP 库要求的底层收发入口。关键点是发送 header 时和普通数据一样,不要额外插入延时,但每次事务结束要等 BUSY 释放后再开始下一次。

4.3 卡片轮询流程:REQA 到 UID

PN5180 的固件帮主机挡掉了大部分 ISO14443A 的底层细节。主机要做的事其实就是:开启射频场,通过 FIFO 发送 REQA,读回 ATQA,然后做防碰撞、选卡,最后拿到 UID。整体流程用伪代码看非常直观:

void PN5180_PollCard(void) { uint8_t cmd_reqa = 0x26; uint8_t atqa[2] = {0}; PN5180_RF_FieldOn(); PN5180_SendData(&cmd_reqa, 1); PN5180_ReadData(atqa, 2); if (atqa[0] != 0x00) { /* 有卡片响应,继续防碰撞和选卡 */ } }

如果只是想快速读取卡片 UID,可以直接调用官方库提供的高层轮询函数,把 UID 缓冲区传进去就行。真正需要自己写状态机的场景一般是特殊协议、低功耗轮询或者多卡防碰撞策略,那时候再去啃数据手册也不迟。先让射频链路转起来,比什么都重要。

5. 实测链路:从编译不过到稳定读到 UID

5.1 排错顺序很重要

移植完成后的调试顺序是有讲究的,我见过太多人一上来就插卡测射频,结果代码跑飞了还以为是天线问题。我的固定流程是:

  1. 确认 Keil 工程编译通过,烧录后程序不跑飞,LED 或串口有响应。
  2. 用 PA8 的 MCO 功能把系统时钟引出来,或者直接看串口波特率是否准确。MCO 配置代码很简单,开启 GPIOA 和 AFIO 时钟后,把 PA8 配成复用推挽输出,再调用 RCC_MCOConfig 选择输出 PLL 二分频后的 36MHz。这个信号用示波器或者逻辑分析仪一看,时钟对不对立刻见分晓。
  3. 确认 SPI 通信:直接读 PN5180 的芯片版本寄存器并打印。这一步如果没通过,后面所有射频操作都没有意义。
  4. 确认复位流程没被跳过,BUSY 等待超时机制正常。
  5. 最后再把卡片放到天线上测 REQA 和 UID。

5.2 常见编译错误速查

错误或现象原因处理方式
L6218E: Undefined symbol SystemInit启动文件或 system_stm32f10x.c 没加进工程添加正确的 startup 文件,并加入 CMSIS 源文件
cannot open source input file "stm32f10x.h"Include Paths 配置不全把 CMSIS 各层目录加进 Include Paths
上电进入 HardFault宏定义与芯片容量不匹配,或启动文件加错核对 STM32F10X_MD / STM32F10X_HD 与启动文件
串口打印乱码系统时钟频率和 HSE_VALUE 不一致检查晶振值,修改 PLL 配置
SPI 读回全是 0xFFSPI 模式不匹配、速度太快、CS 极性错误换 SPI 模式,降低分频,用逻辑分析仪看波形

5.3 调不出来时,先确认 SPI 通没通

看似“射频没反应”的问题,最终常常落在 SPI 底层。我的血泪经验是:刚开始读 PN5180 芯片 ID 时,读回来一直是 0xFF,排查了很久寄存器配置,最后发现只是 SPI 模式没对。还有一次是 CS 引脚被复用成了别的外设,导致片选永远拉不低,整个 SPI 事务根本没被 PN5180 收到。

判断 SPI 通没通最直接的方法,是先用逻辑分析仪抓一次读寄存器事务。CS 拉低,MOSI 上能看到两字节 header,MISO 上能看到芯片回的数据。如果波形里 header 都不完整,问题一定在 GPIO 复用或 SPI 初始化;如果 header 完整但 MISO 没数据,再怀疑模式、速度和复位状态。这个 debug 思路比盲改代码高效得多。

5.4 一个小技巧:打开 SPI 日志

最后分享一个第一次移植时特别有用的技巧:在 SPI 底层收发函数里留一个日志开关。定义类似 PN5180_SPI_DEBUG 的宏,打开后每次事务都把 header 和收发数据通过串口打印出来。射频轮询时数据量不大,串口完全跟得上,你就能实时看到 REQA 发出去、ATQA 有没有回来、卡片的 UID 字节是什么顺序。等整个流程跑通了,再把宏关掉,不影响性能,也不影响代码结构。

这个技巧帮我抓到的最大一个坑是防碰撞循环里 UID 长度判断错误。PN5180 返回的 UID 可能是 4 字节、7 字节或者 10 字节,如果代码里只按 4 字节处理,卡片处理顺序一错,选卡阶段就失败。日志里把每次读到的数据打出来,配合数据手册一对照,问题当场就清楚了。

本文还有配套的精品资源,点击获取

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/9 5:01:39

uC/OS-II移植到STM32F407完整指南:MDK工程搭建与排错

简介:面向嵌入式开发者的实时操作系统移植代码包,目标为意法半导体公司的STM32F407微控制器,基于Cortex-M4内核并带硬件浮点运算单元。资源包提供完整MDK工程,可在Keil下直接编译,涵盖启动文件、核心源码、外设驱动、库…

作者头像 李华
网站建设 2026/9/9 5:01:04

Swift开发IDE怎么选?Xcode与VS Code实战对比与配置指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/9 5:00:19

SEO优化完整流程实操指南:从关键词分析到效果监测

做SEO这行久了,你会发现一个挺残酷的现实——网上的教程、课程、工具推荐满天飞,但真正能让你完整跑完一遍“从分析到落地到复盘”的内容,少得可怜。多数人今天抄个标题写法,明天学个内链技巧,折腾一两个月&#xff0c…

作者头像 李华
网站建设 2026/9/9 4:59:53

Web自动化测试实战:Selenium从环境到CI全攻略

Selenium学了几年,从Selenium 1时代一路用到现在Selenium 4,很多朋友问我要入门路径,干脆把我自己从0到1落地一套Web自动化测试的经验完整写出来。这篇文章会从环境准备、脚本编写、框架设计一路讲到你真正能交付一套能上线跑的自动化用例&am…

作者头像 李华
网站建设 2026/9/9 4:55:02

KT106双麦ANC模块:DAC与I2S输出选型指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/9 4:53:26

信创环境下JAVA分块上传的兼容性适配与实战解析

前两天刚把公司内部一个文件管理系统迁移到信创环境,最让我头疼的不是数据迁移,也不是定时任务,反而是这个看起来不起眼的JAVA分块上传功能。原来在老环境跑得好好的代码,一换到麒麟V10、鲲鹏ARM芯片、达梦数据库、东方通中间件这…

作者头像 李华