公司动态

正点原子Mini STM32F103RCT6驱动RC522读卡程序详解

📅 2026/9/1 7:34:55
正点原子Mini STM32F103RCT6驱动RC522读卡程序详解
简介这套RC522读卡程序专为正点原子Mini开发板设计主控芯片是STM32F103RCT6代码改写自野火Mini工程。程序通过SPI2接口与MFRC522模块通信利用串口1把读卡结果和调试信息发送到终端适合正在学习STM32、SPI总线、串口通信以及RFID应用的开发者参考稍作修改也可适配其他SPI接口的RFID读卡芯片。压缩包共153个文件约2.91MB包含完整工程源码、编译中间文件、Keil工程配置、可烧录的HEX文件以及链接映射文件等目录结构清晰整个工程采用标准外设库编写代码结构整齐可以直接编译下载。目前已有1782人学习。借助这套程序读者可以快速搭建RC522读写环境理解SPI时序和串口输出方法还能参考其中定时器、LCD、Flash等外设驱动方便把RC522模块移植到其他STM32型号上为门禁、电子支付等非接触场景提供实用基础同时适合入门学习与二次开发。 做嵌入式这些年开发板换了不少但正点原子MiniSTM32F103RCT6这块小板子一直留在我工作台角落。原因很简单芯片资源够用例程齐全跑个RC522这种13.56MHz射频读卡模块非常合适。项目标题说的“正点原子Mini(STM32F103RCT6)Rc522程序”本质上就是一套让MCU通过SPI总线驱动RC522实现IC卡读卡、防冲突、选卡、读写扇区数据的裸机程序。这篇文章我把硬件接线、底层驱动、核心代码、调试心得全部拆开讲适合正在学STM32外设驱动、或者准备做门禁/考勤/校园卡读卡项目的朋友直接抄作业。1. 这个项目的本质一块板子加一个射频模块的通路搭建1.1 STM32F103RCT6和RC522分别在干什么很多新手拿到“RC522程序”第一反应是能不能直接复制到自己的板子上这就得先搞明白这套系统里两个主角的分工。STM32F103RCT6是意法半导体旗下的Cortex-M3内核芯片主频72MHz片上Flash 256KBSRAM 48KB。在正点原子Mini板上它负责运行逻辑初始化SPI外设、控制GPIO拉高拉低、解析RC522返回的数据、把卡号打印到串口或者显示在屏幕上。RC522则是恩智浦的MFRC522射频读写芯片工作频率13.56MHz支持ISO14443A协议常见的Mifare S50、S70卡都能读。它负责“无线电”这一层发射射频场、调制解调卡片返回的信号、完成CRC校验等底层活儿。简单说MCU是大脑RC522是耳朵和嘴巴。程序里最核心的工程问题就是让这两个芯片用同一种语言SPI协议说话。1.2 这套程序能做什么谁适合参考完成这个项目后你可以实现以下功能读出一张Mifare卡的4字节序列号UID通过串口打印出来选卡后对指定扇区进行密钥验证验证通过后读写块数据把卡号显示在OLED屏或者LCD屏上扩展成简易门禁系统把读到的卡号与上位机交互做考勤记录、会员管理等应用适合参考的人群很明确正在学STM32 SPI通信的初学者做过基础流水灯想进阶的新手以及需要快速在主控里集成RFID读卡功能的开发者。RC522模块价格便宜几块钱一个比买整套RFID开发板划算得多。2. 硬件准备与接线动手前先答对三道题2.1 物料清单和模块选型建议我的建议配置如下正点原子Mini开发板STM32F103RCT6核心板RC522模块带天线一体板最常见那种蓝色PCB一张Mifare S50卡或者普通IC卡、门禁卡杜邦线若干母对母、母对公都备一点USB转TTL串口模块如果板载没有USB串口选RC522模块时注意看天线大小我试过几种那种矩形三圈天线的模块读卡距离大概能到3~5cm圆形小天线模块只有2cm左右差别不小。如果要做门禁项目建议选大面积天线的模块读卡体验会好很多。2.2 RC522与正点原子Mini的引脚对照表正点原子Mini板上的SPI2接口在PB13、PB14、PB15这三个引脚这个是硬件固定的不能随便改。RC522的SDA是SPI片选信号我用软件方式控制接到PB12上。RST复位引脚接到PB0。IRQ中断引脚不接程序里使用轮询方式读取数据这样省一个GPIO。RC522引脚正点原子Mini引脚说明SDAPB12SPI片选软件控制低电平有效SCKPB13SPI2时钟线MOSIPB15SPI2主机发送数据线MISOPB14SPI2主机接收数据线IRQ不接中断引脚轮询模式无需连接RSTPB0复位引脚低电平复位GNDGND共地VCC3.3V模块供电注意不能接5V2.3 供电、电平、线序的三个坑三个坑每一个都可能让你白折腾两小时。第一供电电压。RC522模块标注是3.3V供电但很多模块板上已经集成稳压电路接5V也能工作。问题是正点原子Mini板上的IO电平是3.3V如果模块用5V供电SPI总线上的高电平就会变成5VMCU引脚虽然一般有容忍能力但长期使用不推荐。稳妥做法是统一用3.3V给模块供电。第二共地。很多人只接VCC、SCK、MOSI、MISO、SDA不接GND导致SPI通信电平没有参考点时好时坏。这个问题排查起来非常隐蔽串口偶尔能打印出卡号偶尔又全零十有八九就是没共地。第三SDA的片选极性。RC522的SDA低电平有效但某些资料图里画的是高电平选中照抄就完蛋。在初始化流程里必须确保片选在没有数据通信时保持高电平。3. RC522的读卡原理与SPI的调参逻辑3.1 MFRC522芯片在RFID链路里的位置不用把射频规则背得滚瓜烂熟你只需要理解这层关系MFRC522负责和物理世界打交道它内部有发送器、接收器、模拟前端、数字基带、寄存器组。MCU通过SPI往MFRC522的寄存器里写命令和参数MFRC522再把命令调制到13.56MHz载波上发送给无源IC卡。卡本身没有电池它靠线圈感应取电所以读卡距离近是物理特性决定的一般不超过10cm。卡返回数据时通过负载调制方式改变天线端阻抗MFRC522检测到这些微小变化后解码把数据放到FIFO缓冲区里MCU再读取出来。一句话总结MCU发的不是射频命令而是寄存器操作MFRC522才是真正和卡对话的人。3.2 SPI四参数怎么定模式、速率、位序、片选SPI通信有四件事必须确认缺一不可时钟极性和相位CPOL、CPHA决定数据在时钟上升沿还是下降沿采样数据位宽一般8位时钟速率决定SPI总线频率片选信号决定从机何时被激活MFRC522默认支持SPI模式0CPOL0CPHA0数据在时钟上升沿采样下降沿改变。标准外设库初始化SPI2时把SPI_CPOL_Low、SPI_CPHA_1Edge填进去这就是模式0。位序必须是MSB先行即最高位先发。速率这块要特别说明一下。STM32F103的SPI2挂在APB1总线上频率36MHz。RC522最高支持10MHz的SPI时钟理论上可以配到9MHz分频4。但我实测下来用杜邦线连接时超过4MHz很容易出现偶发读取错误特别是线长超过10cm后。程序里我使用8分频SPI时钟大概4.5MHz实际操作稳定可靠。3.3 寻卡到读写扇区的完整时序完整读一段扇区数据的时序大概是这样的PcdRequest寻卡发送PICC_REQALL命令寻找视野内的卡片卡返回ATQA类型码PcdAnticoll防冲突如果多张卡同时在场读出其中一张的4字节序列号UIDPcdSelect选卡用UID选中这张卡PcdAuthState验证密钥使用A/B密钥验证目标扇区PcdRead/PcdWrite读写对指定块进行数据读写每步都要检查返回值是否为MI_OK任何一个环节失败都不能继续往下走。这个顺序是MFRC522固件规定的不能跳步比如你不选卡直接读扇区返回值必然是错误。4. 程序框架与关键代码解读4.1 工程结构在正点原子库函数模板里加文件正点原子Mini的例程模板默认带了sys.c、delay.c、usart.c这些基础文件我建议直接基于“库函数版本”的工程模板来做不要从零写启动文件。我的文件组织方式是根目录下新建rc522文件夹放入rc522.c和rc522.h头文件路径加入工程确保能够#include rc522.h在usart.c里初始化好串口1波特率115200方便printf输出卡号rc522.c里的函数分成两层底层函数负责SPI读写寄存器和GPIO控制上层命令函数负责按照协议向MFRC522发送命令并解析结果。4.2 SPI2和GPIO初始化引脚映射别抄错void SPI2_Init(void) { GPIO_InitTypeDef GPIO_InitStructure; SPI_InitTypeDef SPI_InitStructure; RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE); RCC_APB1PeriphClockCmd(RCC_APB1Periph_SPI2, ENABLE); GPIO_InitStructure.GPIO_Pin GPIO_Pin_13 | GPIO_Pin_15; GPIO_InitStructure.GPIO_Mode GPIO_Mode_AF_PP; GPIO_InitStructure.GPIO_Speed GPIO_Speed_50MHz; GPIO_Init(GPIOB, GPIO_InitStructure); GPIO_InitStructure.GPIO_Pin GPIO_Pin_14; GPIO_InitStructure.GPIO_Mode GPIO_Mode_IN_FLOATING; GPIO_Init(GPIOB, GPIO_InitStructure); GPIO_SetBits(GPIOB, GPIO_Pin_12 | GPIO_Pin_13 | GPIO_Pin_14 | GPIO_Pin_15); SPI_InitStructure.SPI_Direction SPI_Direction_2Lines_FullDuplex; SPI_InitStructure.SPI_Mode SPI_Mode_Master; SPI_InitStructure.SPI_DataSize SPI_DataSize_8b; SPI_InitStructure.SPI_CPOL SPI_CPOL_Low; SPI_InitStructure.SPI_CPHA SPI_CPHA_1Edge; SPI_InitStructure.SPI_NSS SPI_NSS_Soft; SPI_InitStructure.SPI_BaudRatePrescaler SPI_BaudRatePrescaler_8; SPI_InitStructure.SPI_FirstBit SPI_FirstBit_MSB; SPI_Init(SPI2, SPI_InitStructure); SPI_Cmd(SPI2, ENABLE); }这段代码有几个容易踩的细节。PB13和PB15配置为复用推挽输出PB14配置为浮空输入。PB12是软件片选必须配置为通用推挽输出并且默认输出高电平。我见过有人把PB12也配成复用功能结果SPI通信时片选被SPI硬件控制时序完全乱套。时钟使能上SPI2是APB1外设GPIOB是APB2外设两者要分别使能。4.3 RC522底层函数复位、写寄存器、读寄存器RC522的通信协议很简单先拉低SDA然后发送一个字节的地址加命令组合再读写数据。写寄存器函数void RC522_WriteReg(unsigned char addr, unsigned char val) { RC522_CS_LOW(); SPI2_ReadWriteByte(((addr 1) 0x7E)); SPI2_ReadWriteByte(val); RC522_CS_HIGH(); }读寄存器函数unsigned char RC522_ReadReg(unsigned char addr) { unsigned char val; RC522_CS_LOW(); SPI2_ReadWriteByte((((addr 1) 0x7E) | 0x80)); val SPI2_ReadWriteByte(0x00); RC522_CS_HIGH(); return val; }注意地址移位和位7置位的细节。写命令时地址左移1位最高位为0读命令时最高位为1。这个约定看数据手册才能确定很多人直接抄别人代码没注意这一层。复位函数PcdReset固定三步先把RST引脚拉低至少10微秒然后拉高复位后延时10ms左右让芯片启动稳定再调用软件复位命令写寄存器。千万别省略复位后的延时否则可能出现第一次寻卡失败、第二次就正常的诡异现象。4.4 上层命令寻卡、防冲突、选卡、验证、读写上层函数直接调RC522的寄存器完成协议交互。寻卡函数核心逻辑char PcdRequest(unsigned char req_code, unsigned char *pTagType) { unsigned char status; unsigned char TagType[2]; unsigned char buf[2]; RC522_WriteReg(RC522_REG_BIT_FRAMING, 0x07); buf[0] req_code; status RC522_Command(PCD_TRANSCEIVE, buf, 1, TagType, 2); if (status MI_OK) { pTagType[0] TagType[0]; pTagType[1] TagType[1]; } return status; }防冲突函数返回卡的4字节UIDchar PcdAnticoll(unsigned char *pSnr) { unsigned char status; unsigned char buf[5]; unsigned char i; RC522_WriteReg(RC522_REG_BIT_FRAMING, 0x00); buf[0] PICC_ANTICOLL; buf[1] 0x20; status RC522_Command(PCD_TRANSCEIVE, buf, 2, buf, 5); if (status MI_OK) { for (i 0; i 4; i) { pSnr[i] buf[i]; } } return status; }这里有个经典坑PcdAnticoll执行完后bit framing寄存器里的值不是0是0x80因为有尾字节需要发送。如果后续要做Mifare密钥验证必须先把RC522_REG_BIT_FRAMING写回0x00否则验证永远失败。正点原子老版例程里在PcdAuthState开头写了这行。主循环里读卡号的逻辑while (1) { status PcdRequest(PICC_REQALL, tagType); if (status MI_OK) { status PcdAnticoll(snr); if (status MI_OK) { printf(Card UID: %02X %02X %02X %02X\r\n, snr[0], snr[1], snr[2], snr[3]); } delay_ms(500); } delay_ms(200); }如果只想读卡号到这一步就已经完成。需要读写扇区数据的继续调PcdSelect和PcdAuthState就行。5. 把程序跑起来实际烧录、调试和验证5.1 首次编译烧录要改哪几个地方拿到工程后我一般按下面的顺序检查确认芯片型号选择的是STM32F103RC正点原子Mini的HSE晶振是8MHz不要刻意改宏定义里检查是否有STM32F10X_HDRCT6属于高密度器件漏了这个宏可能编译初始化代码选错确认串口1波特率我习惯用1152008位数据1位停止位用ST-Link或者FlyMcu烧录正点原子Mini板上预留了SWD接口建议直接用ST-Link烧录速度稳定首次上电后如果串口不断打印乱码先查波特率和串口接线是否交叉不用先怀疑SPI。5.2 串口打印卡号的实测记录我实际跑起来的效果是这样把IC卡贴近天线时串口立刻输出一行Card UID: 0B 92 84 15移开后程序继续循环寻卡再次放卡又能打印。整个过程没有初始化失败的消息没有乱码。第一次调通这个程序的时候我一度怀疑是不是假的读取因为手机NFC功能读出来是同一个UID对比完全一致。后来做按键开门实验时我拿这张卡去刷宿舍的门禁居然也能开说明RC522读出来的卡号和真实门禁系统是一致的协议层没有走偏。5.3 顺带扩展加一个OLED显示卡号串口打印只能接电脑看稍微扩展一下把卡号显示到0.96寸OLED上整个项目就完整多了。OLED用I2C接口接PB6、PB7正点原子Mini的I2C1复用引入OLED驱动文件把卡号格式化后显示char buf[20]; sprintf(buf, UID: %02X%02X%02X%02X, snr[0], snr[1], snr[2], snr[3]); OLED_ShowString(0, 2, buf, 12);加上OLED显示后就可以脱离电脑独立演示适合拿去给别人展示项目成果。这部分代码不复杂但要注意OLED初始化功耗较低和RC522一起用3.3V供电没问题。6. 读卡不成功的排查顺序与避坑清单6.1 按优先级排查硬件→电源→时序→程序遇到读不到卡别急着改代码按这个顺序排查现象可能原因解决办法全无反应模块不发热供电没接对用万用表量VCC和GND之间电压是否为3.3VSDA拉低后电流异常供电电压过高检查是否误接5V确认模块引脚顺序单片机能跑但读卡失败SPI引脚映射错误重新核对PB13/14/15与RC522的SCK/MOSI/MISO对应关系偶尔成功偶尔失败杜邦线接触不良换短杜邦线或将模块直接插面包板寻卡返回MI_NOTAGERR卡片类型不支持确认是ISO14443A的Mifare卡别用身份证我用表格整理出来后基本上排查速度能快一倍。尤其是“模块没反应”这种八成是供电没接好或者模块本身是坏的没必要在代码层面深挖。6.2 让我印象最深的三个问题第一个问题是换了一张卡后读到的UID全是FF FF FF FF。排查发现那张卡是UID可重写卡出厂UID区域全0xFF需要先写入新的UID才能正常使用。这不是程序问题。第二个问题是RC522读卡距离短到只有1cm怎么调都不行。后来发现是天线区域粘了一层双面胶胶贴边缘正好覆盖了天线线圈的一部分导致射频能量被干扰。撕掉之后读卡距离立刻恢复到3cm以上。这个细节在小批量打样时很容易忽略。第三个问题比较隐蔽。我用库函数版本例程时加了delay_ms(500)后寻卡偶尔会卡死。查了汇编发现是SysTick中断优先级设置问题RC522命令执行期间被打断导致SPI时序超时。解决办法是把滴答定时器优先级调为最低或者在寻卡期间关闭中断任务完成后重新开启。6.3 其他移植场景的提醒如果把程序从正点原子Mini板移植到自己的自制板子上有几个位置容易出错SPI外设编号STM32F103C8T6也有SPI1和SPI2但引脚位置不同SPI1在PA5/PA6/PA7移植时如果改了SPI编号必须同步改GPIO配置片选引脚RC522的SDA可以用任意GPIO控制只要在宏定义里改对应的引脚号即可时钟频率如果板子的外部晶振不是8MHzHSE_VALUE宏要改否则串口波特率会偏移天线匹配自制PCB时天线的线圈参数直接决定读卡距离这个没有捷径只能按数据手册推荐值画板身边有朋友在F103C8T6上直接复刻这套程序改完SPI1后一切正常。核心逻辑没变引脚映射改明白就行。最后再分享一点个人经验RC522程序调通确实不算难但真正把它做成一个稳定的产品级功能比想象中琐碎得多。建议在项目初期就把每次读卡的成功失败次数用串口打出来挂机跑几个小时观察是否出现偶发失败再配合一个简单的看门狗就能大幅度提高稳定性。如果后续要接多个RC522模块可以考虑用单片机自带的SPI硬件NSS配合软件切换不过目前这个方案对于大多数应用场景已经完全够用。本文还有配套的精品资源点击获取