STM32+ESP8266+RFID:MFRC522读卡数据上报OneNet物联网平台实战
简介一套基于STM32 F103C8T6微控制器的完整工程源码包利用ESP8266无线模块将MFRC522射频模块读取到的卡号信息上传至中国移动ONENET物联网平台。工程中主控负责读卡和串口转发ESP8266负责Wi-Fi联网与云端上报覆盖射频识别、网络接入、数据交互三条核心链路面向物联网与嵌入式学习者适用于门禁考勤、智能终端等场景。压缩包约5.09MB包含211个文件以STM32标准外设库的.c/.h源码为主同时附有Keil工程配置.uvprojx、编译产物.hex/.axf/.map及过程文件.crf/.o/.d目录结构清晰可直接定位到初始化、外设驱动和上传相关代码。已有1770人浏览学习。工程从MFRC522读卡、ESP8266初始化到ONENET平台数据上传的完整流程均有对应实现可对照学习射频识别读写、串口数据解析、Wi-Fi入网和物联网平台接口对接细节便于按模块查阅和二次开发。初学者可按“读卡—串口—Wi-Fi—云端”的路径逐步调试有经验者可以直接复用其中的驱动与通信框架整个工程将硬件采集、无线传输与云端存储串联起来作为课程设计、毕业设计或物联网入门项目的参考都非常合适也可作为实际产品原型的底层基础。1. 把 MFRC522 读到的卡号送上 OneNet这套链路最容易被低估的不是读卡而是联网把一张 IC 卡的卡号从 MFRC522 读出来经过 STM32 整理再借 ESP8266 送到 OneNet 平台——听起来只是三颗芯片之间的几条线加两个串口实际调起来绝大多数时间会耗在 ESP8266 的回包时序和 OneNet 的 HTTP 拼接上。你会同时接触 SPI 设备驱动、串口 AT 指令状态机、云端数据点上报正好是一条能延伸到门禁、考勤、共享设备管理的小而完整的物联网数据链路。这个方向适合两类人一类是在校生做课设或电子竞赛一类是硬件工程师想用最低成本验证云上业务。核心难点只有一个——让三颗芯片和一个平台在时序上互相迁就。先说结论读卡部分跑通只需要一个下午真正让你反复烧录的是联网环节。工程文件拿到手先别急着编译先找 readme 和引脚定义把硬件接线对完再点烧录按钮。2. 硬件链路与时序选择SPI 留给读卡串口留给联网供电单独走整条链路上STM32 的引脚是唯一的信息会合点MFRC522 通过 SPI 把卡号送进来ESP8266 通过串口把卡号送出去。这两条总线的选择不是随意的接口类型直接决定了驱动代码的复杂度和联调时你手里的调试工具是否顺手上。2.1 为什么 MFRC522 走 SPI、ESP8266 走串口三个选型依据第一个依据是器件本身的控制方式。MFRC522 是一个寄存器映射的射频控制芯片驱动里最频繁的操作就是读写一组内部寄存器发命令、查 FIFO、读版本号、查中断标志。SPI 一次读写就能完成一个寄存器操作全双工且带硬件片选时序非常干净。I2C 当然也能用但多了器件地址和内部寄存器地址两段开销速率上限只有 400Kbps在连续寻卡场景下延迟明显偏高。更现实的原因是网上现成的 MFRC522 驱动绝大部分都是 SPI 版本直接移植的工作量最小。第二个依据是 ESP8266 的出厂形态。绝大多数 ESP8266 模块出厂烧录的是 AT 固件你主控只需要通过串口发文本指令不需要去搭乐鑫 SDK 的交叉编译工具链。AT 协议是文本化的意味着你可以用一根 USB-TTL 把模块单独接到电脑上用串口助手验证模块本身是否正常再回过来查自己的代码——这一步在联调时极为关键因为它把“模块坏了”和“代码错了”这两类问题隔离开。只有当你对功耗、启动时间或数据透传吞吐有硬指标时才需要考虑用 SDK 重新写 ESP8266 内部程序那是另一个层面的工作主控侧的外围协议反而不用动。第三个依据是外设占用与数据流特性。SPI1 加 USART2 在 STM32F103 上是两套独立外设引脚不冲突GPIO 压力很小而卡号是低频小数据一次上报也就一两百字节115200 波特率绰绰有余。这条链路的瓶颈永远不在串口速率而在云平台的 API 鉴权和请求格式上所以选型时把“可调试性”放在和“速率”同等的权重上是值得的。2.2 接线表与供电分配一次画对避免乱飞线三颗芯片的逻辑电平都是 3.3VSTM32F103 的 GPIO 输出也是 3.3V所以直连没有电平转换问题。真正需要小心的是 ESP8266 的供电它在上电联网瞬间的峰值电流能到 300mA 以上从 STM32 的 3.3V 引脚直接取电启动瞬间会把系统电压拉低表现为反复复位上电这是新手第一个会踩的坑。下面是我常用的接线方式以 STM32F103C8 最小系统板为例角色模块引脚STM32 引脚备注SPI 片选MFRC522 SDA/CSPA4GPIO 推挽输出SPI 时钟MFRC522 SCKPA5SPI1_SCKSPI 主入从出MFRC522 MISOPA6SPI1_MISO浮空输入SPI 主出从入MFRC522 MOSIPA7SPI1_MOSI复位MFRC522 RST任意 GPIO如 PB0低电平复位模块中断可选MFRC522 IRQ不接轮询方式读卡时不接串口发送ESP8266 RXDPA2USART2_TX交叉连接串口接收ESP8266 TXDPA3USART2_RX供电分配我一般这样做5V 进板后接一路独立稳压常见的是 AMS1117-3.3专门给 ESP8266 供电STM32 和 MFRC522 共用另一路 3.3V。如果用电脑 USB 供电整机电流约 200-400mAUSB 口能扛住但不要再用同一个 USB 口拖其他大电流外设。MFRC522 的天线是 PCB 印制天线天线区周围不要铺铜、不要贴金属外壳读卡距离会明显缩水。提示ST-LINK 自带的 3V3 引脚能点灯但带不动 ESP8266 连 Wi-Fi 时的峰值电流。调试时建议把 ESP8266 的串口和 STM32 的调试串口分开接 USB-TTL这样 AT 回包和业务日志互不干扰。接线完成后SPI 初始化参数先对齐模式 0CPOL0、CPHA0、8 位数据、MSB 优先、主模式、软件片选。这些参数写错任何一个MFRC522 的版本寄存器都读不回来。下一章就从读卡驱动的裁剪说起。3. MFRC522 读卡部分驱动裁剪与 UID 的字节陷阱读卡的过程本质上是一次 ISO14443A 的射频交互MFRC522 唤醒天线场卡响应然后防碰撞、选卡最后把卡的 UID 序列号放进 FIFO。驱动框架是现成的但移植到 STM32 不是复制粘贴那么简单需要做三处裁剪同时理解 UID 的字节序和长度差异。3.1 驱动移植的三个裁剪点中断、延时和天线控制第一处裁剪是把驱动和串口解耦。很多流传的 MFRC522 驱动最初面向 Arduino代码里布满了 Serial.print 和 delay()。STM32 工程里这些必须替换成自己的调试宏或日志接口否则直接编译会报一堆串口调用错误。替换的时候顺手把调试输出级别做一个开关联调阶段打开稳定后关掉这样读卡流程里的时间片不受打印拖累。第二处裁剪是延时方式。Arduino 的 delay(ms) 直接换成 HAL_Delay(ms) 没有错但驱动里那些“等 FIFO 数据就绪”“等命令执行完”的循环不能靠固定延时硬等要用短循环反复读状态寄存器。原因是 MFRC522 的射频交互对时序敏感固定长延时会吞掉防碰撞窗口表现就是卡贴上去偶尔读得出、偶尔读不出。正确做法是给轮询加一个最大重试次数读到状态位立刻继续。第三处裁剪是天线控制。部分驱动在每次寻卡前会把天线关掉再打开代码里表现为往 TxControlReg 写 0x00 再写 0x83。这种做法在切换卡片类型时有用但对连续读卡场景反而制造了不必要的射频建立时间。我一般只在初始化时开一次天线之后保持常开连续读卡的稳定性会好很多。射频增益寄存器RFCfgReg 附近在多数场景不需要动如果你发现卡片必须贴得很近才读得出第一嫌疑是电源纹波而不是增益配置。// 以 STM32CubeMX 生成的 HAL 初始化为例重点看速度和极性三行 hspi1.Instance SPI1; hspi1.Init.Mode SPI_MODE_MASTER; hspi1.Init.Direction SPI_DIRECTION_2LINES; hspi1.Init.DataSize SPI_DATASIZE_8BIT; hspi1.Init.CLKPolarity SPI_POLARITY_LOW; // CPOL0 hspi1.Init.CLKPhase SPI_PHASE_1EDGE; // CPHA0 hspi1.Init.NSS SPI_NSS_SOFT; // 软件片选不用硬件 NSS hspi1.Init.BaudRatePrescaler SPI_BAUDRATEPRESCALER_8; // 72MHz/89MHz hspi1.Init.FirstBit SPI_FIRSTBIT_MSB;逻辑说明MFRC522 要求 SPI 模式 0速率上限 10MHz预分频 8 在 72MHz 主频下输出 9MHz刚好在规格之内。NSS 必须用软件 GPIO因为驱动的每个命令阶段都要独立拉低 SDA 再拉高硬件 NSS 的自动片选时序和命令边界对不齐很容易出现第一字节丢失。标准外设库版本在 SPI_Cmd(SPI1, ENABLE) 前后同样适用这几行参数只是寄存器封装形式不同。移植完成后先做一个最小验证读版本寄存器地址 0x37正常返回 0x91 或 0x92。如果返回 0x00 或 0xFF不要急着查驱动先检查 SPI 极性、接线顺序和供电这三项占读卡失败原因的九成。版本号确认没问题再进入 UID 读取逻辑。3.2 UID 的字节序与长度决定云端卡号是不是“看起来对”MFRC522 完成寻卡、防碰撞、选卡后拿到的是 UID 字节数组。最常见的是 4 字节的 MIFARE Classic 卡也就是门禁里大量使用的白色 IC 卡也有 7 字节的标签卡。问题通常出现在两个地方一是直接把字节当成 ASCII 字符发送0x12 被 printf 成控制字符云端收到乱码二是不知道字节序规则同一张卡在不同设备上报出两个卡号数据库里出现重复卡。统一做法是每个字节转成两位大写十六进制按读取顺序拼接成一个字符串。UID 是 0xAB 0xCD 0x12 0x34就转成 ABCD1234。这个字符串在云端就是设备的唯一身份标识后续不管是门禁白名单还是考勤记录都以它为准。7 字节 UID 的卡如果驱动只做了一次防碰撞只返回前 4 字节那两张不同 7 字节卡的前 4 字节可能完全相同导致撞号。如果你的工程要面向通用市场需要确认驱动是否实现了级联防碰撞也就是 ISO14443A 里的 Cascade Level 2 流程。uint8_t uid[8]; // 驱动返回的 UID 缓冲具体长度按驱动定义 uint8_t uidLen; // 实际 UID 字节数可能是 4 或 7 char cardNo[16]; // 十六进制字符串最大 14 字符 \0 uidLen MFRC522_GetUID(uid); // 函数名以你的工程驱动为准 if (uidLen 4 || uidLen 7) { for (int i 0; i uidLen; i) { sprintf(cardNo[i * 2], %02X, uid[i]); } } printf([CARD] %s\r\n, cardNo); // 例UID {AB,CD,12,34} - ABCD1234逻辑说明MFRC522_GetUID 内部通常包含 Request、Anticollision、Select 三步不同驱动的函数名可能叫 PcdRequest、PcdAnticoll但返回的 UID 字节顺序是统一的。sprintf 里的 %02X 会补齐前导零所以 0x0A 会输出 A0 前面的 0这一点比手动移位拼接更不容易出错。拼好的字符串不要再加空格或换行后续拼 HTTP body 时这会变成多余的转义负担。拿到卡号字符串后建议先串口打印确认与读卡器工具读出的卡号一致再继续下一步。这个验证半小时内能完成但它能帮你把读卡问题和联网问题彻底分开——后面 Any 一眼看不出原因的云平台异常只要确认卡号字符串是对的排查方向就不会跑偏。4. 卡号上云ESP8266 的 AT 指令串与 OneNet 数据点写入ESP8266 本身不带业务逻辑主控的角色就是通过串口敲 AT 指令把模块带上网再让它发一次 HTTP 请求。OneNet 平台的接入有老平台和新版平台两套差异本章按老平台最常见的 HTTP 数据点上报来写这是多数存量工程和网盘工程文件采用的路径。4.1 把 ESP8266 带上网AT 指令的最小序列与超时判断AT 指令在固件版本之间有少量差异但核心序列是固定的关回显、设 Station 模式、连路由器、建 TCP 连接、发数据。容易翻车的不是指令拼写而是每条指令的“预期回包”和“等待时间”。ATAT 会回复 OKATCWJAP 连上后分步返回 WIFI CONNECTED 和 WIFI GOT IPATCIPSEND 则要等模块先回一个 提示符然后才能发数据发完还要等 SEND OK。这些回包都可能被串口拆成多段到达所以接收侧不要用“收满固定长度”的方式而要用环形缓冲加关键字匹配收到 OK 或 ERROR 就认为指令执行完毕。// 伪代码体现状态机每条 AT 都确认收到预期回包后再进入下一步 ESP_WaitReady(2000); // 等模块上电后打印 ready ESP_SendCmd(ATE0\r\n, OK, 500); // 关闭回显回包更干净 ESP_SendCmd(ATCWMODE1\r\n, OK, 500); // 如果 WiFi 密码含引号或逗号请在字符串里转义 ESP_SendCmd(ATCWJAP\MY_SSID\,\MY_PASS\\r\n, WIFI GOT IP, 15000); ESP_SendCmd(ATCIPSTART\TCP\,\api.heclouds.com\,80\r\n, CONNECT, 5000);逻辑说明ESP_SendCmd 内部要做回包匹配而不是简单地等够时间就读缓冲区。ATCWJAP 的超时给到 15 秒因为路由器关联、DHCP 获取 IP 是两个阶段CIPSTART 用域名时模块需要先解析 DNS首次解析可能 3 到 5 秒超时给 5 秒以上。如果经常 DNF FAIL大概率是固件版本太老或路由器开了 AP 隔离可以先用 ATCIPDOMAIN 手工解析域名拿到 IP再用 IP 建 TCP。参数说明115200 是 ESP8266 出厂常见波特率但二手模块可能被改过上电无响应时用 ATUART_DEF115200,8,1,0,0 重置一次。注意 USART2 的波特率、数据位、停止位要和模块保持一致STM32 这边初始化对不上AT 指令会变成乱码。建好 TCP 连接后HTTP 请求的发送就交给下一节的拼接函数。这里有一个经验不要每次上报都重新执行一遍完整的 AT 序列模块联网和 TCP 连接在持续供电状态下一直是保持的把联网状态做成一个全局标志位只有掉线时才重新执行。这样既省时间也减少了路由器侧频繁掉线的概率。4.2 组装一次 HTTP 上报URL、鉴权头与 Content-LengthOneNet 老平台的数据点写入接口是 POST /devices/{设备ID}/datapoints头部需要四个字段Host、api-key、Content-Type、Content-Length。鉴权用的是 api-key 头不是 Authorization这是新手最容易看错的地方。设备 ID 和 APIKey 是两串不同的数字从平台控制台的设备详情和产品信息里分别复制别混用。请求体是一个 JSON 数组里面的数据流 ID 要和控制台创建的数据流一致卡号字符串作为 value 传入。最关键的细节是 Content-Length 必须和 body 的实际字节数完全一致多一位少一位都会表现为“云端收不到数据”而 ESP8266 这个层面看 TCP 是发送成功的。我习惯先拼 body再用 strlen 算长度最后把它填进头部顺序上不要反过来。char body[96]; char req[256]; int body_len, req_len; // 先拼 bodybody_len 就是实际字节数 body_len sprintf(body, {\datastreams\:[{\id\:\%s\,\datapoints\:[{\value\:\%s\}]}]}, card_no, cardNo); // card_no 是控制台里的数据流 ID // 再把 body 长度填进头部最后整体发送 req_len sprintf(req, POST /devices/%s/datapoints HTTP/1.1\r\n Host: api.heclouds.com\r\n api-key: %s\r\n Content-Type: application/json\r\n Content-Length: %d\r\n\r\n %s, YOUR_DEVICE_ID, YOUR_API_KEY, body_len, body); ESP8266_TCPSend(req, req_len); // 通过 ATCIPSEND 发送这段请求内容逻辑说明sprintf 的返回值是实际写入的字符数用它当 Content-Length 不会算错。注意代码里 YOUR_DEVICE_ID 和 YOUR_API_KEY 是占位符要替换成你控制台的真实值数据流 ID “card_no” 也可以换成你自己的数据流名。ESP8266_TCPSend 内部做三件事发 ATCIPSEND长度等 提示符然后发送数据最后等 SEND OK。长度参数以 req_len 为准因为这个值来自已经拼好的完整请求不会出现“头部声明长度和实际发送长度不一致”的问题。这段请求发出后OneNet 会返回 HTTP 响应正常是 200你可以把响应内容打印到调试串口。如果返回 401基本是 api-key 错误返回 404基本是设备 ID 或 URL 路径不对返回 400 且响应体提示 JSON 格式问题优先检查双引号是否被转义或者数据流 ID 里有没有多余空格。提示如果你在平台控制台看到的是新版的 OneNET Studio接入方式和本章的 api-key 请求不同它改成了签名鉴权。对应做法是去平台文档里找 HTTP 数据上报接口把请求头换成文档给出的 token 格式上报逻辑和 TCP 链路本身是不变的。老平台与新平台的差异会在后面的进阶章节里给出一个工程化的兼容写法。5. 联调高频问题排查五个翻车点与对应的解决路径这一章把联调阶段最常见的五个问题按“现象、原因、解决”整理出来。这些问题我在搭建同类型链路时基本都遇到过也是帮别人排查时出现频率最高的。建议按顺序逐一排除不要跳着试。现象一MFRC522 读卡无反应版本寄存器读回 0x00 或 0xFF。原因是 SPI 极性相位不对、接线顺序错误或模块没有正常上电复位。解决先不跑业务代码只读版本寄存器地址 0x37命令 0x02 类别下正常返回 0x91 或 0x92。用逻辑分析仪看 SCK 空闲电平MFRC522 要求空闲低电平、第一个边沿采样和代码里 CPOL0、CPHA0 对应。RST 引脚在高电平下运行确认没有误接低电平IRQ 引脚在轮询模式下悬空即可。现象二ESP8266 发 AT 无任何回应。原因是模块没有供电、RXD/TXD 接反、波特率不匹配或 CH_PD 没有上拉。解决把模块单独接到 USB-TTL直接和电脑串口助手通信先确认模块本身能回 OK。RXD 和 TXD 是交叉连接ST32 的 TX 接模块的 RXST32 的 RX 接模块的 TX。3.3V 供电要用独立稳压不要用 ST-LINK 的 3V3 引脚硬带。CH_PDEN引脚接 3.3V 上拉这是模块能启动的前提。现象三ATCWJAP 返回 ERROR 或一直在 CONNECT FAIL。原因是路由器开了 5G 与 2.4G 同名的双频合一、WiFi 密码含特殊字符、或者信号强度不够导致重连超时。解决先用手机开一个纯 2.4G 热点热点名改成简单英文字母密码用纯数字测试。热点能连上就说明模块没问题问题在路由器环境这时候去路由器后台把双频合一关掉或者把模块固定到 2.4G 频段。中文 SSID 在部分 AT 固件下会解析失败工程里尽量避免。现象四ATCIPSEND 发出了数据也收到了 SEND OK但 OneNet 平台没有数据。原因是 Content-Length 和 body 实际字节数不一致、HTTP 头部缺少 \r\n\r\n 分隔、或者 APIKey 头拼写有误。解决把整段请求原样打印到串口用网络调试助手在本机开一个 TCP Server把请求发到本机肉眼看头部空行是否存在再用 strlen 计算 body 长度核对 Content-Length。这里有一个细节HTTP 头部和 body 之间那个空行必须是 \r\n\r\n少一个 \r 平台解析就会失败。建议直接复制本章代码里的字符串格式不要手动敲。现象五云端有数据但卡号看起来乱码或者两张不同的卡上报了相同的卡号。原因是 UID 字节被当成 ASCII 字符发送、大小端顺序拼反、或者 7 字节卡只做了单级防碰撞。解决统一用 sprintf 的 %02X 转十六进制字符串卡号里的字符只允许 0-9 和 A-F。用读卡器工具和 STM32 打印的卡号对比确认顺序一致。7 字节的卡如果驱动不支持级联防碰撞建议先 4 字节卡跑通整个链路后续再补驱动不要一开始就混用卡型。这一章的排查思路是“由底向上”先把单模块验证过再做链路验证最后才怀疑云平台。我见过很多工程文件里的代码本身没有大问题最终排查出来是 USB 供电不稳导致 ESP8266 反复重启这个问题在一开始按第 2 章单独供电就可以完全避免。6. 从能通到能用缓存重传、平台接口差异与低功耗的三个习惯链路跑通只是起点真实环境下还要处理网络抖动、平台版本更迭和功耗问题。我在这套架构上沉淀了三个值得优先做的习惯都改动量不大但对稳定性的提升立竿见影。第一个习惯是本地缓存与重传。ESP8266 在弱 Wi-Fi 环境下可能建连失败这时候卡号如果直接丢弃云端就永久丢了一条数据。我会在 STM32 内部开辟一个环形缓冲上报失败时把卡号字符串存进去等模块恢复联网后下次上报前先把缓冲里的数据补发再发新数据。缓存的容量不用大几十条就够因为刷卡本身是低频事件。第二个习惯是用宏把平台差异隔离出来。老平台的 api-key 鉴权和新版平台的签名鉴权接入路径不同但业务层都只关心“上报一个卡号字符串”。我会在代码里定义几个宏HTTP_HOST、API_KEY_HEADER、DEVICE_URL把平台相关的差异全部收敛到一处切平台时只改宏定义主流程代码不动。后续如果要对接其他物联网平台也是同样思路业务逻辑和上报协议之间隔一层抽象这层抽象用宏实现比用函数回调更省代码。第三个习惯是低功耗与读卡唤醒。设备待机时不需要让 MFRC522 的天线一直开着也不需要 ESP8266 一直保持 TCP 连接。我一般让主控进入 STOP 模式MFRC522 的 IRQ 引脚配置为外部中断源卡片进入天线场时会产生中断唤醒主控主控再开天线、联网、上报然后再次休眠。这样待机电流可以从几十毫安降到微安级别电池供电的门禁模块才具备实用性。我现在的习惯是先把串口回环打通再谈上云。所有“平台不吐数据”的玄学最后基本都能归因到链路里某一段没确认而不是云端的问题。先把读卡打印、AT 回包、HTTP 响应这三段各自验证明白再合并起来跑整个过程会顺畅很多。希望帮到你。本文还有配套的精品资源点击获取