2026/9/20 2:59:10

基于STM32与RFID的智能停车收费系统设计与实现

基于STM32与RFID的智能停车收费系统设计与实现 1. 项目缘起与整体设计思路1.1 为什么选择STM32RFID做停车收费系统这个项目的出发点其实很朴素传统停车场要么靠人工发卡收钱要么用一套动辄上万的商业道闸系统前者效率低、跑冒滴漏严重后者对小型停车场来说成本太高。我当初接这个活儿是帮一个朋友改造他那个只有六十多个车位的小型露天停车场预算卡得很死要求能自动识别月租车和临时车、能计费、能控制道闸抬杆最好还能把进出记录存下来方便对账。选STM32作为主控理由很直接。STM32F103C8T6这颗芯片在圈子里被叫做“国民MCU”价格便宜量又足72MHz主频、64KB Flash、20KB SRAM跑一个停车收费系统的逻辑绰绰有余。它的外设资源也刚好够用多个USART口可以分别接RFID读卡器和上位机通信SPI口驱动RC522射频模块GPIO直接控制道闸继电器和地感检测定时器用来做计费的时间基准。更关键的是这颗芯片的资料铺天盖地从寄存器手册到标准外设库再到HAL库遇到问题几乎都能搜到答案对个人开发者和小团队来说试错成本极低。RFID这块我选的是MFRC522方案工作在13.56MHz频段支持ISO14443A协议配S50白卡或者钥匙扣卡都行。有人会问为什么不直接用NFC其实RFID和NFC的区别在这里很关键NFC本质上是RFID的一个子集工作频率相同但协议栈更复杂支持点对点通信和卡模拟而停车收费只需要读卡号这一个功能用RC522这种纯读卡方案成本更低、开发更简单。一张M1卡几毛钱读卡模块十几块整套下来比商业系统便宜一个数量级。整个系统的设计思路是“主控射频读卡道闸控制计费管理”四块拼图。STM32作为大脑通过SPI总线轮询RC522寻找卡片读到卡号后先查本地存储的白名单判断是月租车还是临时车月租车直接放行临时车记录入场时间出场时计算停车时长和费用最后通过GPIO驱动继电器控制道闸电机抬杆。数据存储用STM32内部的Flash模拟EEPROM存几百条记录没问题如果不够还可以外挂AT24C02或者W25Q64。1.2 硬件方案选型与原理图设计考量原理图设计这块我踩过不少坑这里把关键决策点摊开讲。电源部分是最容易被忽视但最要命的地方。整个系统需要5V和3.3V两路电源5V给继电器和RC522模块供电3.3V给STM32和OLED显示屏。我一开始图省事用AMS1117-3.3从5V直接降压结果发现RC522在读卡瞬间电流波动很大导致3.3V轨上有明显的纹波STM32偶尔会复位。后来改成两级滤波5V输入端加一个470μF的电解电容和一个0.1μF的陶瓷电容AMS1117输出端再加一个220μF的钽电容和0.1μF陶瓷电容问题才解决。如果你也要做这个项目电源部分的电容千万别省这是血泪教训。STM32最小系统部分晶振用8MHz无源晶振配两个22pF电容复位电路用10K上拉电阻加100nF电容BOOT0和BOOT1各接一个10K下拉电阻到地保证从主Flash启动。SWD调试接口引出四根线3.3V、GND、SWDIO、SWCLK方便用ST-Link下载和调试。这里有个细节SWDIO和SWCLK线上最好各串一个100Ω电阻能有效抑制调试时的信号反射尤其是杜邦线连接的时候。RC522模块接口是SPI我把它接到STM32的SPI1上SCK接PA5MISO接PA6MOSI接PA7NSS片选接PA4复位引脚接PA3。注意RC522的工作电压是3.3V千万别接5V否则模块直接烧掉。另外RC522的天线部分对周围环境很敏感PCB上要保证天线区域下方没有覆铜否则读卡距离会大幅缩短。道闸控制部分我用的是继电器方案。STM32的GPIO输出电流有限驱动不了继电器线圈所以中间加了一级ULN2003达林顿管阵列。PA0输出高电平时ULN2003导通继电器吸合常开触点闭合给道闸电机供电。继电器线圈两端要反向并联一个1N4007续流二极管否则断电瞬间线圈产生的反向电动势会把ULN2003打穿。这个二极管我见过太多人漏掉然后莫名其妙烧驱动芯片。地感检测用的是环形线圈加振荡电路输出一个开关信号给STM32的PA1。有车压在线圈上时PA1为低电平车离开后变高电平。这个信号用来判断车辆是否已经完全通过道闸防止杆子砸车。如果没有地感也可以用红外对射传感器替代原理类似。显示部分用0.96寸OLEDI2C接口接PB6和PB7。显示内容很简单待机时显示“请刷卡”读到月租车显示“月租车 欢迎”读到临时车显示入场时间出场时显示停车时长和费用。OLED比LCD1602贵不了几块钱但显示效果好太多而且I2C只占两个引脚。PCB设计方面我画的是双层板尺寸10cm×8cm。布局上把电源部分放在板子左侧STM32和RC522接口放中间继电器和道闸接口放右侧这样强弱电自然隔离。RC522的天线区域在板子边缘下方挖空所有铜层。走线方面SPI的四根线尽量等长且短时钟线SCK包地处理。电源线宽用40mil信号线用10mil这个线宽电流对照关系要心里有数1oz铜厚下10mil线宽大约能过0.5A40mil能过2A左右。继电器那一路的走线要加粗到60mil以上因为道闸电机启动电流可能到1A以上。1.3 软件架构与核心流程设计软件部分我用的是Keil MDK开发环境标准外设库版本。整个程序分成四个层次硬件驱动层、业务逻辑层、数据存储层和用户接口层。硬件驱动层包括RC522驱动、OLED驱动、Flash读写驱动和GPIO控制。RC522驱动是核心需要实现寻卡、防冲突、选卡、读卡号这几个基本操作。寻卡用REQA指令0x26防冲突用防冲突指令0x93选卡用SELECT指令0x93最后读卡号。这一套流程走下来大概几十毫秒对于停车收费场景完全够用。业务逻辑层是主状态机我设计了五个状态待机状态、入场处理状态、出场处理状态、月租车处理状态和异常处理状态。系统上电后进入待机状态OLED显示“请刷卡”RC522持续寻卡。读到卡后根据当前状态跳转如果是入场模式且卡在白名单里直接抬杆放行并记录入场时间如果是临时卡记录卡号和入场时间后抬杆如果是出场模式计算停车时长和费用显示在OLED上收费后抬杆。数据存储层用STM32内部Flash的最后一页Page 63地址0x0801FC00模拟EEPROM。存储结构分三块月租车白名单最多100个卡号每个4字节、临时车入场记录最多200条每条包含卡号4字节入场时间4字节、系统参数计费费率、免费时长等。每次写入前先擦除整页然后按顺序写入。这里要注意Flash的擦写寿命是1万次左右所以不能频繁写我的策略是入场记录先存在RAM里出场时再一起写入Flash减少擦写次数。用户接口层就是OLED显示和可能的按键输入。按键我用三个模式切换键、确认键和取消键接在PB0、PB1、PB2上内部上拉按下为低电平。模式切换键用来在入场模式和出场模式之间切换确认键用于确认收费取消键用于异常情况下的手动放行。整个程序的运行流程是这样的上电初始化时钟、GPIO、SPI、I2C、定时器和Flash然后从Flash读取白名单和参数到RAMOLED显示待机界面。主循环里先扫描按键然后RC522寻卡如果有卡就进入业务处理流程。定时器每1ms中断一次用来做按键消抖和计费的时间基准。计费规则我设的是前15分钟免费之后每小时5元不足一小时按一小时算24小时内最高30元。这个规则可以根据实际需求在代码里改。2. 核心细节解析与实操要点2.1 RC522射频模块的驱动细节与调试技巧RC522这颗芯片说起来简单但真正调通还是需要一些耐心的。它通过SPI和STM32通信寄存器地址是7位的读写的时候要拼成8位读操作是地址左移一位再或0x80写操作是地址左移一位再与0x7E。这个细节如果搞错读出来的全是0xFF或者0x00。初始化流程我总结为五步第一步软复位写CommandReg寄存器的0x0F位第二步设置定时器写TModeReg和TPrescalerReg这两个寄存器决定了RC522内部定时器的周期影响寻卡的超时时间第三步设置调制解调方式写TxControlReg和RxControlReg打开天线驱动第四步设置通信速率写ModWidthReg第五步打开天线写TxControlReg的0x03位。这五步走完RC522才能正常工作。寻卡函数我写了一个循环每200ms发一次REQA指令。如果读到卡返回卡的类型M1卡返回0x0400。防冲突环节是关键因为读卡范围内可能有多张卡同时响应防冲突算法会选出一张卡并返回它的UID。UID是4字节的我把它拼成一个32位整数作为卡号。这里有个坑有些卡的UID不是标准的4字节可能是7字节或10字节但停车收费场景用4字节的M1卡就够了遇到其他卡型直接忽略。调试RC522的时候我建议先用逻辑分析仪抓SPI波形。正常的SPI通信SCK空闲时为低电平数据在上升沿采样。如果波形不对先检查SPI的时钟极性和相位设置STM32的SPI1我配的是CPOL0、CPHA0。另外RC522的NSS片选信号要手动控制不能用硬件NSS因为RC522要求片选在每次通信前后有明确的拉低和拉高。读卡距离是另一个常见问题。标准RC522模块配印刷天线读卡距离大概3-5cm。如果想增加距离可以换更大的天线或者调整天线匹配电路。但停车收费场景其实不需要太远3cm足够了太远反而容易误读旁边车道的卡。我在天线周围贴了一圈铜箔胶带做屏蔽效果不错能有效抑制侧面读卡。还有一个容易被忽略的点RC522的复位引脚。我一开始没接复位靠软复位也能工作但偶尔会出现死机。后来把复位引脚接到STM32的PA3上电时先拉低100ms再拉高稳定性大幅提升。所以复位引脚一定要接别省这一根线。2.2 道闸控制与地感检测的联动逻辑道闸控制看起来简单就是GPIO输出高低电平控制继电器但实际做起来要考虑的东西不少。首先是继电器的选型。我用的是5V驱动的SRD-05VDC-SL-C触点容量10A 250VAC对于道闸电机这种感性负载余量足够。但要注意继电器触点断开感性负载时会产生电弧长期使用会导致触点氧化。我在触点两端并联了一个RC吸收电路100Ω电阻串0.1μF电容能有效延长继电器寿命。控制逻辑上STM32的PA0输出高电平ULN2003导通继电器吸合道闸电机得电抬杆。抬杆到位后限位开关断开电机电源同时给STM32一个信号接PA2STM32检测到后拉低PA0继电器释放。落杆逻辑类似只是电机转向相反用另一个继电器控制。这里要注意抬杆和落杆不能同时进行代码里要加互锁逻辑否则两个继电器同时吸合会短路电源。地感检测的环形线圈我用的是100匝、周长约6米的漆包线埋在地下切割的线槽里。线圈两端接一个LC振荡电路有车压在线圈上时线圈电感量变化振荡频率偏移检测电路输出电平翻转。这个信号接到STM32的PA1我用外部中断的方式检测上升沿和下降沿都触发记录车辆进入和离开的时刻。联动逻辑是这样的读到有效卡后STM32先检查地感状态。如果地感检测到有车PA1为低说明车辆已经在道闸前此时抬杆如果没车OLED提示“请将车辆停至感应区”。抬杆后STM32持续监测地感信号当检测到车辆离开PA1从低变高后延时2秒落杆。这个延时很重要防止车辆还没完全通过就落杆砸到车尾。防砸逻辑是必须的。除了地感我还加了一个红外对射传感器作为冗余接在PA4上。如果地感失效但红外检测到有障碍物道闸也不会落杆。两个传感器同时失效的概率极低这样基本能保证安全。手动控制方面我留了一个按钮接在PB3上按下后强制抬杆松开后延时落杆。这个按钮是给保安用的遇到系统故障或者特殊车辆时手动放行。按钮要加硬件消抖电路10K上拉电阻加100nF电容到地否则STM32会检测到多次触发。2.3 Flash模拟EEPROM的数据存储策略STM32F103C8T6没有真正的EEPROM但可以用内部Flash模拟。Flash的擦除单位是页一页1KB写入单位是半字16位。擦除后所有位都是1写入就是把对应的位清零。这个特性决定了Flash不能像EEPROM那样随意读写必须先擦后写。我的存储方案是这样的用最后一页Flash地址0x0801FC00到0x0801FFFF存所有数据。数据结构分三个区域前400字节存月租车白名单每个卡号4字节最多100个中间800字节存临时车入场记录每条8字节卡号4字节入场时间4字节最多100条最后24字节存系统参数包括计费费率、免费时长、当前模式等。写入流程是先把整页数据读到RAM里修改RAM中对应的数据然后擦除整页再把RAM里的数据写回去。这个流程听起来简单但有个致命问题如果在擦除后、写回前断电所有数据就丢了。为了解决这个问题我在Flash里维护了两个副本交替写入。具体做法是把最后一页分成两半每半512字节存一份完整的数据。写入时先写第二半写完后更新一个标志位再擦除第一半并写入新数据。这样即使写入过程中断电至少还有一份完整的数据可用。时间戳我用的是Unix时间戳格式从2000年1月1日0点开始计秒。STM32内部有一个RTC外设配一个32.768kHz的晶振就能走时。如果没有晶振也可以用定时器计数模拟但精度差一些。我建议还是加一个晶振成本几毛钱走时准确得多。RTC的备份寄存器可以在主电源断电时由纽扣电池供电保持时间不丢失。数据校验方面我在每条记录后面加了一个字节的校验和把所有字节异或的结果存进去。读取时重新计算校验和如果不匹配就认为这条记录无效。这个简单的校验能发现大部分数据损坏的情况。Flash的擦写寿命是1万次按每天擦写10次算能用将近3年。如果担心寿命问题可以外挂一个AT24C02 EEPROMI2C接口擦写寿命100万次价格也就一块多钱。我的建议是如果项目要长期运行直接上外挂EEPROM省心。2.4 计费算法的实现与时间处理计费算法是这个系统的核心业务逻辑看起来简单但细节很多。我的计费规则是入场后前15分钟免费超过15分钟后每小时5元不足一小时按一小时算24小时内最高收费30元。这个规则用代码实现的时候关键是要处理好时间边界。时间获取用RTC的Unix时间戳入场时记录entry_time出场时读取current_time停车时长duration current_time - entry_time单位是秒。然后判断如果duration 90015分钟费用为0否则计算计费小时数hours (duration - 900 3599) / 3600这里加3599是为了向上取整费用fee hours * 5如果fee 30则fee 30。这个算法有个细节如果停车时间刚好是15分钟整应该免费。如果超过15分钟哪怕1秒也要按1小时收费。这个边界条件在实际运营中经常引发纠纷所以代码里要严格按规则执行同时OLED上要显示清楚“已停时长”和“计费时长”让车主心里有数。跨天停车的情况也要考虑。如果停车超过24小时我的规则是每24小时重新计费但最高不超过30元每天。实现方式是先算天数days duration / 86400剩余秒数remain duration % 86400然后分别计算每天的費用再累加。这个逻辑稍微复杂一点但用几个if-else就能搞定。时间同步方面RTC的初始时间我设的是编译时的系统时间用__DATE__和__TIME__宏获取。第一次上电后可以通过串口发送指令来校准时间。串口协议我定义得很简单发送“TIME 2025-01-01 12:00:00”这样的字符串STM32解析后更新RTC。这个功能在实际部署时很有用因为RTC晶振可能有偏差运行几个月后需要校准一次。OLED显示的时间格式我用的是“HH:MM:SS”日期用“YYYY-MM-DD”。显示刷新频率不用太高每秒刷一次就行否则OLED容易老化。我在主循环里用了一个计数器每1000次循环刷新一次显示这样既保证实时性又延长屏幕寿命。3. 实操过程与核心环节实现3.1 开发环境搭建与工程配置开发环境我用的是Keil MDK 5.38配合STM32F1的器件支持包。安装过程这里不赘述重点讲几个容易出问题的配置项。首先是芯片包的选择。STM32F103C8T6属于STM32F1系列需要安装Keil.STM32F1xx_DFP这个包。安装完成后在Keil的Pack Installer里能看到STM32F1的器件列表。新建工程时选STM32F103C8然后在弹出的Run-Time Environment里勾选CMSIS的CORE和Device的Startup标准外设库需要手动添加。标准外设库我用的是V3.5.0版本这是ST官方最后一个标准库版本虽然现在推HAL库但标准库代码更精简、执行效率更高适合这种资源有限的芯片。把库文件复制到工程目录下然后在Keil的Options for Target里添加头文件路径Inc目录、CMSIS目录、STM32F10x_StdPeriph_Driver/inc目录。源文件方面只需要添加stm32f10x_gpio.c、stm32f10x_rcc.c、stm32f10x_spi.c、stm32f10x_i2c.c、stm32f10x_tim.c、stm32f10x_flash.c、stm32f10x_rtc.c、stm32f10x_pwr.c、stm32f10x_bkp.c和misc.c这几个就够了其他的不用加减少编译时间。时钟配置是关键。STM32F103C8T6最高主频72MHz外部8MHz晶振经过PLL 9倍频得到72MHz。在system_stm32f10x.c里修改SYSCLK_FREQ_72MHz的定义然后在main函数开头调用SystemInit()。APB1总线最高36MHzAPB2最高72MHzSPI1挂在APB2上所以SPI时钟是72MHz分频。我设的分频系数是8SPI时钟9MHzRC522最高支持10MHz这个速度刚好。调试配置方面在Options for Target的Debug选项卡里选ST-Link Debugger然后点Settings在Flash Download里勾选Reset and Run这样下载完程序自动运行。SWD接口的速度设成4MHz比较稳定太快了容易连接失败。编译优化等级我建议用Level 1-O1不要用Level 3因为Level 3会做一些激进的优化可能导致延时函数被优化掉。如果非要用Level 3延时函数里要加volatile关键字修饰循环变量。3.2 原理图与PCB设计实操记录原理图我用的是Altium Designer 20画的这里把关键部分的绘制要点说一下。STM32最小系统部分晶振电路要靠近芯片放置走线尽量短且等长。复位电路我用了IMP811T这个专用复位芯片比RC复位可靠得多价格也就一块多钱。BOOT0和BOOT1各接10K下拉电阻保证从主Flash启动。每个电源引脚旁边都要放一个100nF的去耦电容VDD和VDDA之间还要加一个1μF的钽电容。RC522接口部分我画了一个8pin的排针引脚定义是SDA片选、SCK、MOSI、MISO、IRQ、GND、RST、3.3V。注意RC522模块的引脚顺序可能和我的定义不一样画板子之前一定要确认清楚。天线部分在PCB上要特殊处理天线走线宽度和间距要严格按照RC522模块的参考设计来一般是0.5mm线宽、0.5mm间距。天线区域下方所有层都要挖空不能有铜皮。继电器驱动部分ULN2003的输入端串1K电阻接STM32的GPIO输出端接继电器线圈。每个继电器线圈两端反向并联1N4007。继电器的常开触点引出到接线端子接道闸电机。这里要注意继电器触点和STM32电路之间要有足够的爬电距离至少2mm以上防止高压串入低压部分。PCB布局方面板子尺寸10cm×8cm双层板顶层走信号线底层走电源线和地线。电源部分放在板子左侧用大面积覆铜做散热。STM32放在中间偏左RC522接口放在板子右上角天线伸出板子边缘。继电器放在右下角靠近接线端子。OLED接口放在板子底部中间方便安装。走线规则信号线宽10mil电源线宽40mil继电器驱动线宽60mil。SPI的四根线尽量平行走长度差不超过5mm。晶振走线要包地两边各加一条地线。RC522的天线走线要做50Ω阻抗匹配这个在Altium里可以用阻抗计算工具算出来。过孔方面每个电源引脚旁边至少放一个过孔连接到地平面。信号线的过孔尽量少尤其是SPI和晶振这种高速信号。板子边缘放一圈地过孔间距5mm能有效抑制边缘辐射。丝印方面每个接口都要标清楚引脚定义每个按键和指示灯都要有标注。这些细节在调试和安装的时候能省很多事。3.3 程序下载与联调过程实录程序下载用ST-Link V2接线是ST-Link的SWDIO接STM32的PA13SWCLK接PA14GND接GND3.3V接3.3V。注意不要接5VST-Link的3.3V输出电流有限最好用外部电源给板子供电。第一次下载程序后先不接RC522和继电器只测试STM32最小系统。用万用表测3.3V和5V电压是否正常晶振是否起振用示波器看PA8引脚有没有8MHz波形。然后写一个最简单的LED闪烁程序确认GPIO能正常控制。这一步没问题了再逐步添加外设。RC522调试的时候我先用逻辑分析仪抓SPI波形。正常的通信波形是NSS拉低SCK输出8个时钟脉冲MOSI上依次是地址字节和数据字节MISO上返回数据。如果MISO一直是高电平说明RC522没有响应检查电源和复位引脚。如果MISO有数据但不对检查SPI的极性和相位设置。读卡测试我用的是S50白卡放在RC522天线上方1cm处。串口打印卡号正常的卡号应该是4字节的十六进制数。如果读不到卡先检查天线是否连接好然后调整天线的匹配电容。RC522模块上一般有两个可调电容用无感螺丝刀慢慢调直到读卡距离最远。继电器测试的时候我先不接道闸电机用万用表测继电器触点是否正常通断。STM32输出高电平继电器吸合万用表蜂鸣器响输出低电平继电器释放蜂鸣器不响。确认无误后再接电机。电机我用的是12V直流减速电机正反转分别用两个继电器控制。测试时注意电机电源和STM32电源要隔离否则电机启动时的电流波动会导致STM32复位。地感检测的调试比较麻烦因为线圈要埋在地下。我是在实验室先用一个替代线圈测试确认检测电路能正常工作后再去现场安装。替代线圈用同样的漆包线绕100匝直径约1米放在地上上面放一块铁板模拟车辆。检测电路的输出接STM32的PA1用串口打印状态确认有铁板时PA1变低拿走铁板后变高。OLED显示调试就简单了I2C地址一般是0x78或0x7A用扫描程序确认地址后初始化屏幕显示几个字符测试。如果显示乱码检查I2C的时钟频率OLED一般支持400kHz我设的是100kHz稳定为主。3.4 系统联调与现场安装要点所有模块单独测试通过后进行系统联调。联调的顺序是先上电初始化OLED显示待机界面然后刷卡OLED显示卡号和卡类型接着模拟入场记录入场时间再刷卡模拟出场计算费用并显示最后测试道闸控制确认抬杆落杆正常。联调过程中我发现一个问题RC522寻卡和OLED刷新会互相干扰。原因是OLED的I2C通信和RC522的SPI通信都在主循环里如果OLED刷新时间过长会错过RC522的寻卡窗口。解决办法是把OLED刷新放在定时器中断里或者降低OLED刷新频率。我最后是把OLED刷新放在主循环里但每次只刷一行分多次刷完这样每次占用时间很短不影响寻卡。现场安装的时候有几个要点。RC522模块要固定在道闸旁边的立柱上高度约1.2米方便车主伸手刷卡。天线面要朝向车道倾斜15度左右这样读卡效果最好。模块要加防水盒户外使用必须防水。道闸控制线用屏蔽线屏蔽层单端接地防止干扰。地感线圈的线槽要切割整齐线圈要完全埋入地下上面用沥青或水泥封好。电源方面现场取电用12V直流电源经过LM2596降压到5V给系统供电。LM2596的输入输出都要加电容输入100μF输出220μF。如果现场电压不稳定还要加一个TVS管做浪涌保护。系统上电后先进入自检模式OLED显示“系统自检中”依次检测RC522、继电器、地感和RTC。如果某个模块检测失败OLED显示对应的错误代码方便排查。自检通过后进入待机模式开始正常收费。4. 常见问题与排查技巧实录4.1 硬件类问题速查与解决硬件问题占了调试问题的七成以上我把遇到的典型问题整理成表格方便对照排查。问题现象可能原因排查方法解决方案上电后STM32不运行电源电压不对万用表测3.3V和5V检查AMS1117输入输出电容确保焊接良好程序下载失败SWD接线错误检查PA13、PA14、GND、3.3V确认ST-Link驱动安装降低SWD速度到1MHzRC522读不到卡天线未连接或损坏示波器测天线端波形重新焊接天线调整匹配电容读卡距离短天线周围有金属观察天线附近是否有铜柱或螺丝移开金属物天线下方挖空铜层继电器不吸合ULN2003损坏测ULN2003输入端电压更换ULN2003检查续流二极管继电器吸合但电机不转继电器触点接触不良万用表测触点通断更换继电器触点并联RC吸收电路OLED不显示I2C地址错误用I2C扫描程序找地址修改代码中的I2C地址OLED显示乱码I2C速率太快降低I2C时钟频率从400kHz降到100kHz地感检测不灵敏线圈匝数不够增加线圈匝数到150匝调整振荡电路的可调电阻系统偶尔复位电源纹波大示波器测3.3V纹波增加滤波电容电机电源隔离RTC走时不准晶振负载电容不对测晶振频率调整负载电容到12pF或22pFFlash数据丢失擦写过程中断电检查写入流程采用双副本交替写入策略这里重点说几个我踩过的坑。第一个坑是RC522的电源。我一开始用STM32的3.3V输出给RC522供电结果读卡时好时坏。后来用示波器一看读卡瞬间3.3V轨上有200mV的纹波。原因是RC522读卡时电流从几十mA跳到100mA以上STM32的LDO响应不过来。解决办法是给RC522单独用一个LDO或者至少在RC522的电源引脚旁边加一个100μF的钽电容。第二个坑是继电器的续流二极管。我一开始觉得ULN2003内部已经有续流二极管了就没外接。结果用了不到一个月ULN2003就烧了。后来查手册才发现ULN2003内部的二极管是给逻辑电路用的驱动继电器这种感性负载必须外接1N4007。这个二极管方向不能接反否则一上电就短路。第三个坑是地感线圈的线槽。我第一次安装的时候线槽切得太浅线圈有一部分露在地面上结果被车压断了。后来重新切割深度至少5cm线圈完全埋入上面用沥青封好再也没出过问题。4.2 软件类问题排查思路软件问题相对隐蔽但排查思路清晰了也不难。程序跑飞是最常见的问题。STM32的HardFault异常会导致程序卡死我一般在HardFault_Handler里加一个死循环然后点灯或者串口打印错误信息。更专业的做法是读取LR和PC寄存器的值定位到出错的具体指令地址。在Keil的Watch窗口里添加__return_address变量能看到调用栈。SPI通信失败是RC522调试的拦路虎。我的排查步骤是第一步用示波器确认SCK有波形如果没有检查SPI初始化代码第二步确认NSS在通信时拉低通信后拉高第三步确认MOSI上的数据正确对照RC522手册的寄存器地址第四步确认MISO上有返回数据如果没有检查RC522的电源和复位。这四步走完基本能定位问题。I2C通信失败通常是地址不对或者上拉电阻没接。OLED的I2C需要4.7K上拉电阻有些模块自带有些不带。如果不带要在STM32的PB6和PB7上各接一个4.7K电阻到3.3V。地址方面0.96寸OLED一般是0x78但有些是0x7A用扫描程序确认一下最保险。Flash读写错误多半是地址算错了。STM32F103C8T6的Flash起始地址是0x08000000每页1KB最后一页的起始地址是0x0801FC00。擦除的时候要调用FLASH_ErasePage函数写入用FLASH_ProgramHalfWord函数。注意写入前要先解锁Flash写完后再上锁。如果忘了上锁下次擦除会失败。RTC不走时通常是晶振没起振。32.768kHz晶振的负载电容一般是6pF或12.5pF要配对应的电容。如果晶振不起振先检查焊接然后换晶振最后检查RTC初始化代码。RTC初始化后要等待RSF标志位置位表示寄存器同步完成才能读取时间。计费算错的情况我也遇到过。有一次测试时发现停车16分钟收了5元但规则是15分钟内免费。查代码发现是边界条件写错了应该是duration 900才收费我写成了。这种问题只能靠仔细测试把边界值都测一遍14分59秒、15分整、15分01秒、59分59秒、1小时整、1小时01秒确保每个边界都正确。4.3 现场部署的独家避坑经验现场部署和实验室调试完全是两码事环境复杂得多我总结了几条独家经验。第一条电源一定要加防雷击和浪涌保护。户外设备最怕雷击我在12V输入端加了一个SMBJ15A的TVS管能吸收几百瓦的浪涌功率。地线要单独打一根接地棒接地电阻小于4Ω。这个钱不能省否则一场雷雨可能烧掉整个系统。第二条RC522模块要做防水处理。我用的是IP65的防水盒但防水盒的出线孔要用防水接头否则雨水会顺着线缆流进去。模块本身也要涂一层三防漆防止潮气腐蚀电路板。天线部分不能涂漆否则会影响读卡距离。第三条道闸控制线要用双绞屏蔽线。道闸电机是感性负载启停时会产生很强的电磁干扰。我用的是RVSP 2×1.0的双绞屏蔽线屏蔽层在STM32端单端接地。如果干扰还是很强可以在继电器触点两端并联一个RC吸收电路或者用光耦隔离STM32和继电器。第四条地感线圈的引线要双绞。从线圈到检测电路的引线每米至少绞合10次能有效抑制共模干扰。引线不能和电源线平行走交叉时要垂直交叉。如果引线较长中间要加一个接线端子方便日后维护。第五条系统要加看门狗。STM32内部有独立看门狗和窗口看门狗我用的是独立看门狗超时时间设2秒。主循环里每500ms喂一次狗如果程序跑飞看门狗会复位系统。这个功能在无人值守的停车场特别重要能避免系统死机导致道闸不抬杆。第六条数据要定期备份。我在系统里加了一个串口输出功能每天凌晨3点自动把当天的收费记录通过串口打印出来接一个串口转USB模块存到电脑上。这样即使Flash数据丢失也有备份可查。如果嫌麻烦可以加一个SD卡模块用SPI接口成本也就几块钱。第七条OLED屏幕要加保护。户外阳光直射下OLED的对比度会下降看不清显示内容。我在OLED前面加了一块偏光片能改善可视角度。另外OLED长期显示固定内容会烧屏我设置了屏幕保护待机5分钟后自动熄屏有卡时再亮。4.4 系统优化与功能扩展建议系统跑通之后还可以做一些优化和扩展提升实用性和可靠性。优化方面首先是降低功耗。如果停车场有太阳能供电的需求可以把STM32的主频降到24MHz关闭不用的外设时钟RC522设置成低功耗寻卡模式整体功耗能从100mA降到30mA左右。其次是提高读卡速度把SPI时钟从9MHz提高到18MHz寻卡间隔从200ms缩短到100ms刷卡体验会更流畅。最后是增加存储容量把Flash的临时车记录从100条增加到200条或者外挂一个W25Q64能存几万条记录。功能扩展方面可以加一个ESP8266 WiFi模块把收费记录上传到云端实现远程监控和报表统计。ESP8266通过串口和STM32通信协议用AT指令开发难度不大。还可以加一个语音播报模块刷卡时播报“月租车欢迎”或“停车费5元”提升用户体验。语音模块用WT588D成本十几块钱通过串口发送指令就能播放。如果停车场有多个出入口可以做成联网系统。每个出入口一个STM32控制器通过RS485总线连接到中央管理电脑。RS485用MAX485芯片传输距离可达1200米足够覆盖大部分停车场。中央电脑上跑一个管理软件实时显示各出入口的状态和收费记录。安全性方面可以加一个防拆开关如果有人试图拆开RC522模块开关触发后系统报警并锁定。还可以加一个摄像头模块刷卡时拍照留存防止盗刷。摄像头用OV7670通过DCMI接口和STM32连接但STM32F103的处理能力有限只能拍低分辨率的照片存到SD卡里。最后再分享一个小技巧RC522的卡号可以加密存储。不要直接把卡号明文存在Flash里而是存卡号的哈希值。这样即使Flash数据被读取也无法还原出原始卡号。哈希算法用简单的CRC32就行计算速度快占用资源少。验证时把读到的卡号算一遍CRC32和存储的值比对一致就放行。这个改动不大但能提升系统的安全性。