☰
STM32+ESP8266+MFRC522卡号上报OneNET实战解析
2026/10/14 2:52:58 网站建设 项目流程

简介:面向STM32物联网开发者与课程设计、毕业设计学生,这套rar压缩包提供完整的“读卡-联网-上云”工程源码。工程以STM32F103C8T6为主控,通过SPI接口驱动MFRC522射频模块,读取符合ISO/IEC 14443A标准的13.56MHz IC卡卡号,再由ESP8266模块以AT指令方式建立WiFi连接,按照ONENET平台MQTT协议将卡号信息上报,数据链路清晰,可直接用Keil MDK打开工程编译并烧录验证。压缩包共211个文件,约5.09MB,主要包含38个.h头文件、36个.c源文件,以及.o/.d/.crf等编译中间文件,同时保留.uvprojx工程配置、.axf/.hex可执行文件、map/lst链接列表和.sct分散加载文件,便于重新编译与问题定位。已有1770人学习下载,适合作为物联网入门、单片机综合实验或项目二次开发的参考。内容覆盖RFID底层驱动、SPI通信、UART串口、ESP8266 AT指令控制、MQTT消息发布等关键代码,并保留Keil工程备份和编译产物,方便读者对照说明逐步复现从刷卡到云端显示卡号的完整流程。

1. 这套基于 STM32、ESP8266、MFRC522 与 OneNET 的联网上报工程,卡点不在读卡,而在“怎么把卡号稳定地送到平台”

我拿到这份工程文件时的第一反应是:RC522 读卡部分几乎每个教程都有,真正值钱的是后面那两百多行——ESP8266 的 AT 指令状态机、HTTP POST 报文拼接、以及串口缓冲区的处理。卡号从 MFRC522 的 SPI 接口读出来只需要几步,但要让卡号变成 OneNET 数据流里的一个数据点,中间那段“搬运”才是新手最容易翻车的地方。这套工程适合已经点亮过 LED、想碰联网但没写过 TCP/IP 协议栈的开发者,也适合门禁、考勤、实验室设备借用这类需要把刷卡信息远程记录的场景。拿到手别急着烧录,先花十分钟把引脚分配和串口规划看清楚,能省下后面一整天的排查时间。

2. 硬件接线与工程骨架:先把引脚分配和文件结构这两处看清楚

2.1 接线:RC522 与 ESP8266 怎么分配引脚

这套工程里 MCU 按常见的 STM32F103C8T6 小系统板来算,MFRC522 走 SPI1,ESP8266 走 USART2。RC522 是 3.3V 器件,ESP8266 模块也是 3.3V 供电,这两者都不建议直接接 5V,尤其是 RC522 的 VCC 如果接了 5V,SPI 引脚电平会和 STM32 不匹配,轻则读卡不稳定,重则烧模块。

RC522 引脚与 STM32 的标准分配如下:

RC522 引脚STM32 引脚说明
VCC3.3V必须 3.3V,不要接 5V
RSTPB1普通 GPIO 输出,初始化时给复位脉冲
GNDGND与 STM32 共地
MISOPA6SPI1_MISO,数据输入到 MCU
MOSIPA7SPI1_MOSI,数据输出到 RC522
SCKPA5SPI1_SCK,时钟,由 MCU 产生
NSSPA4软件片选,拉低选中 RC522
IRQ悬空本工程未用,悬空即可

这里有一个常见反人类设计:有人把 IRQ 也接上某个引脚试图做中断读卡,结果中断频繁触发,导致主循环被卡死。MFRC522 的 IRQ 不是必须的,轮询方式完全够用,读卡频率本来就不高,没必要用中断。

ESP8266 这边,推荐接 USART2:

ESP8266 引脚STM32 引脚说明
RXDPA2USART2_TX,MCU 发送给模块
TXDPA3USART2_RX,模块发送给 MCU
CH_PD3.3V使能脚,必须拉高
VCC3.3V模块供电,注意电流峰值
GNDGND共地

为什么不用 USART1?因为 USART1 的 PA9/PA10 通常被调试下载器占用,如果 ESP8266 接在 PA9/PA10 上,每次烧录时串口数据会和下载握手冲突,容易导致下载失败或烧完后模块乱发数据。我一般会把 USART1 留给调试打印,USART2 专门给 ESP8266,这样上位机串口看到的是干净的日志,不会和模块数据混在一起。ESP8266 的 RXD 接 STM32 的 TXD,这是最容易接反的地方——模块上标的是自己的管脚名,不是对端管脚名。

2.2 下载后的工程骨架:从文件结构判断能不能跑

下载的 rar 压缩包解压后,我期望看到下面这种典型的 STM32 标准外设库工程结构:

Stm32_MFRC522_OneNET/ ├─ HARDWARE/ │ ├─ RC522/ │ │ ├─ rc522.c │ │ └─ rc522.h │ └─ ESP8266/ │ ├─ esp8266.c │ └─ esp8266.h ├─ SYSTEM/ │ ├─ delay/ │ ├─ sys/ │ └─ usart/ ├─ USER/ │ ├─ main.c │ ├─ stm32f10x_it.c │ └─ usart.c └─ PROJECT/ ├─ STM32F103C8Tx.uvprojx └─ ...

拿到工程后先做三件事。第一,打开rc522.h看 SPI 引脚宏定义是否和 2.1 节的表格一致,不同板子的引脚宏可能被改过,最常见的是 NSS 从 PA4 被换到 PB12,这种改动会导致读卡失败且没有任何报错。第二,打开esp8266.h看串口波特率,老款 ESP8266 AT 固件常用 115200,新款有些默认 9600,波特率不匹配时发AT没有任何返回,这是第一个排查点。第三,在main.c里搜while(1),看主循环里是不是一个delay接着一个delay。如果整个读卡上报流程全是阻塞式延时,说明这套工程跑起来简单,但稳定性有限,后面我会讲怎么改状态机。

工程文件不是拿来直接烧的,而是要先把板子上的跳线、电源、串口都核对完再上电。常规的编译烧录流程不用多说,但我特别提醒:第一次烧录前,先把 ESP8266 的串口线从板上拔掉,烧录完成后再插回去。否则部分烧录器在连接 STM32 时会因为外围串口设备的干扰导致握手失败,报出“cannot access target”之类的问题,这不是工程本身的问题。

3. 读卡与数据拼装:把 MFRC522 的 UID 抠出来,再按字节序格式化

3.1 RC522 初始化与寻卡流程

RC522 驱动本身不复杂,核心步骤是 SPI 初始化、RC522 软复位、发送天线开启命令、以及寻卡与反碰撞。初始化代码通常长这样:

void rc522_init(void) { // 拉低片选、复位引脚做一次高低脉冲 RC522_RST_H(); delay_short(); RC522_RST_L(); rc522_write(RC522_REG_COMMAND, PCD_RESETPHASE); // 软复位命令 rc522_write(RC522_REG_MODE, 0x3D); // 设置 MIFARE 兼容模式 rc522_write(RC522_REG_TX_MODE, 0x00); rc522_write(RC522_REG_RX_MODE, 0x00); // 开启天线 rc522_write(RC522_REG_TX_CONTROL, 0x40); rc522_write(RC522_REG_TX_CONTROL, 0x80); }

这段代码背后的逻辑有两个关键点。PCD_RESETPHASE是让 RC522 进入复位状态,之后写入的寄存器参数才有效;TX_CONTROL先写 0x40 再写 0x80 是先把发射关闭再开启,完成天线上电。如果天线没开,后续寻卡永远返回超时,而且代码里看不出任何异常,因为状态寄存器里只反映“无卡”。我排查过不少案例,最后一查发现是天线开启命令没执行到位。

寻卡和反碰撞是两件事。寻卡是发请求指令给射频场里的卡片,反碰撞是当多张卡在场时选出其中一张并读回 UID。工程里常见的流程是:

uint8_t tag_type[2] = {0}; uint8_t uid_buf[5] = {0}; // 前 4 字节是卡号,第 5 字节是校验位 if (rc522_request(PICC_REQIDL, tag_type) != MI_OK) { return 0; // 没有卡在场 } if (rc522_anticoll(uid_buf) != MI_OK) { return 0; // 防冲突失败 }

PICC_REQIDL是寻卡请求命令,意思是让所有处于空闲状态的卡响应;MI_OK是驱动里定义的返回码,通常为 0。rc522_anticoll拿到的uid_buf[0]到uid_buf[3]是卡号本体,uid_buf[4]是 CRC 校验字节。新手常犯的错误是定义一个长度为 4 的数组去接返回值,导致第五个校验字节越界写入,后面卡号变量被覆盖,排查起来非常罪。

读卡间隔也要注意。RC522 寻卡成功后,卡片不会立刻再次响应,需要让卡离开射频场或者等一段时间后才能再次寻卡。主循环里如果每 50ms 就寻卡一次,有些卡会出现间歇性读不到的现象,这不是驱动坏了,而是卡片类型和天线功率的配合问题。我一般把两次寻卡间隔放到 300ms 以上,既有实时感又稳定。

3.2 卡号字节序与格式化:直接决定 OneNET 里看到的数据

这是最容易踩坑的环节。RC522 反碰撞拿到的四个字节,顺序不是统一的,不同厂家的读卡器、不同型号的卡片,输出字节序可能不同。常见做法是把收到的四个字节按“先读到的就是高位字节”来拼成整数,也就是大端序:

uint32_t card_id_dec = ((uint32_t)uid_buf[0] << 24) | ((uint32_t)uid_buf[1] << 16) | ((uint32_t)uid_buf[2] << 8) | ((uint32_t)uid_buf[3]); char card_str[12]; sprintf(card_str, "%lu", (unsigned long)card_id_dec);

这段代码把四字节 UID 拼成一个十进制整数,方便在 OneNET 数据点里存成一个数值。但这里有个边界:如果卡号四字节的最高位不为 0,拼出来的数可能接近 43 亿,而很多平台上默认的整数字段是 int32 范围,直接存会溢出。所以我现在更推荐输出固定长度的十六进制字符串:

char card_hex[9] = {0}; sprintf(card_hex, "%02X%02X%02X%02X", uid_buf[0], uid_buf[1], uid_buf[2], uid_buf[3]);

十六进制字符串的好处有三个。第一,长度固定为 8 个字符,OneNET 的数据点显示出来整齐,不会出现“4051829736”这种一眼看不出规律的十进制串;第二,不受 int32 溢出限制,后续换 7 字节 UID 的卡片也能扩展;第三,十六进制串本身就是 ASCII 码,在 HTTP 报文里传输不需要考虑中文编码问题,Content-Length 怎么算都不会出错。

字节序怎么验证?把读卡器放在模块天线旁边,串口打印出card_hex,再用读卡器软件或者手机上读一下同一张卡的 UID,看两个十六进制串是否一致。如果不一致,常见情况是低位在前,把数组下标顺序反过来再测试即可。这个坑和硬件无关,纯粹是数据格式约定,属于典型的“看起来读出来了,其实顺序反了”的问题。我一般在工程的rc522.c里把字节序调好后就固定下来,不做成运行时可切换的配置,因为运行时切换只会在调试时增加混乱。

4. ESP8266 串口状态机与 OneNET 上报:AT 指令循环与 HTTP 报文组装是两座山

4.1 用 AT 指令建立 TCP 连接:状态机拆解

ESP8266 模块工作在 AT 指令固件模式下,Stm32 通过串口发字符串控制它连 WiFi、建 TCP 连接、发数据。最直观的写法是阻塞式,发一条指令、延时等返回、再发下一条,这种写法在演示工程里没毛病,但实际跑起来会遇到一个很现实的问题:AT 固件的返回时间不固定。连 WiFi 时有时几百毫秒就返回,有时三五秒才返回,用固定延时去等,要么超时误判,要么数据在缓冲区里积压。

我更推荐把这些步骤拆成一个状态机。先定义好状态枚举:

typedef enum { WIFI_AT_CHECK, // 发送 AT 测试模块是否在线 WIFI_CONNECTING, // 连接路由器 WIFI_TCP_CONNECT, // 建立到 OneNET 的 TCP 连接 WIFI_SENDING, // 发送 HTTP POST 数据 WIFI_DONE // 本次上报完成 } wifi_state_t;

然后在主循环里驱动它:

wifi_state_t wifi_state = WIFI_AT_CHECK; switch (wifi_state) { case WIFI_AT_CHECK: esp8266_send_string("AT\r\n"); // 如果收到 "OK",则认为模块在线,跳到下一步 if (esp8266_has_received("OK")) { wifi_state = WIFI_CONNECTING; esp8266_send_string("AT+CWMODE=1\r\n"); // 设置为 Station 模式 } break; case WIFI_CONNECTING: // 连接 WiFi,SSID 和密码在工程开头宏定义里改 esp8266_send_string("AT+CWJAP=\"MyWiFi\",\"12345678\"\r\n"); if (esp8266_has_received("WIFI GOT IP")) { wifi_state = WIFI_TCP_CONNECT; } break; case WIFI_TCP_CONNECT: esp8266_send_string("AT+CIPSTART=\"TCP\",\"183.230.40.39\",80\r\n"); if (esp8266_has_received("CONNECT OK")) { wifi_state = WIFI_SENDING; } break; case WIFI_SENDING: // 组装 HTTP 报文并发送 send_http_post(); wifi_state = WIFI_DONE; break; }

状态机的好处是不会因为某一步阻塞把整个系统卡死,刷卡和上报两条逻辑可以并行。这段代码里有几个参数必须按实际环境改:AT+CWJAP里的 WiFi 名和密码,如果路由器是 5G 频段,ESP8266 模块连不上,必须用 2.4G 频段;AT+CIPSTART里的 IP 和端口是 OneNET 平台 HTTP 接入地址,这个地址以平台当前开出来的为准,不同区域可能不同。我一般会把平台接入地址定义成宏,方便统一替换。

串口接收处理是个容易被忽视的细节。ESP8266 返回的数据是一串一坨的,可能 “OK\r\n” 和 “CONNECT” 混在一次 DMA 传输里到达,所以判断返回结果不能等固定长度,要判断缓冲区里是否“包含”目标关键字。上面代码里的esp8266_has_received("OK")就是典型的包含判断。这里记住一个原则:永远不要用strcmp去和整个接收缓冲区比较,因为你不知道何时会多收到一个空行或附加状态码。

4.2 组 HTTP POST 报文:Content-Length 是多数事故现场

OneNET 平台接入方式常用 HTTP POST,报文格式看起来简单,但细节非常多。先看拼报文的代码:

char body[64]; sprintf(body, "{\"datastreams\":[{\"id\":\"card\",\"datapoints\":[{\"value\":\"%s\"}]}]}", card_hex); int body_len = strlen(body); char http_buf[128]; sprintf(http_buf, "POST /devices/%s/datapoints?type=3 HTTP/1.1\r\n" "Host: %s\r\n" "api-key: %s\r\n" "Content-Length: %d\r\n\r\n" "%s", DEVICE_ID, ONENET_HOST, API_KEY, body_len, body);

这里最关键的一行是Content-Length,它必须等于 body 的实际字节数。很多工程里直接写sizeof(body),但body是字符数组,sizeof返回的是数组容量,而不是字符串长度,一旦 body 里存的是十六进制串和固定 JSON 外壳,实际长度和数组容量对不上,OneNET 返回 400 Bad Request。正确做法是先用strlen(body)算出长度,再拼进 header。

报文结构里还有三个细节容易出错。第一,HTTP 头每行结尾必须是\r\n,不是单个\n,最后 header 和 body 之间还要有一个空行,也就是那两个\r\n\r\n。少一个\r\n,服务器会认为报文没结束,一直等你的后续数据,表现为连接超时而不是立即报错。第二,type=3表示 JSON 格式,这个参数在旧版 OneNET HTTP 接口里是必带的,部分新接口路径改成/datapoints直接 POST JSON,路径前缀不同但报文结构一致。第三,如果卡号字符串里包含中文或其他非 ASCII 字符,Content-Length的字节数就不再等于字符串长度,容易算错。所以我在上面特意把卡号限定为纯 ASCII 的十六进制字符串,这能省掉一半编码坑。

发送过程也不是一次printf就完事。AT 指令的发送模式是“先发长度、再发数据”:

char send_cmd[32]; sprintf(send_cmd, "AT+CIPSEND=%d\r\n", body_len + strlen(http_buf) - body_len); esp8266_send_string(send_cmd); // 等待模块返回 ">" 之后,再发送完整报文 if (esp8266_has_received(">")) { esp8266_send_string(http_buf); }

这里有个坑:CIPSEND后面跟的长度是整个 HTTP 报文的总长度,不是 body 的长度。上面代码里html_len = strlen(http_buf)比较清晰。而等待“>”这一步不能省略,发送完CIPSEND指令后,模块可能立刻返回“>”,也可能等几十毫秒才返回,直接延时 200ms 再发数据的写法,在模块反应慢时会把报文头部丢掉,服务器端收到的就是残缺请求。我在调试时习惯把这一段的等待做成超时循环,500ms 内等不到“>”就重发整条链路。

5. 避坑记录:从 AT 没反应到平台查无此数据

5.1 启动阶段的三个高频问题

现象一:给 ESP8266 上电后,串口发送AT,模块没有任何返回。这是最普遍的问题。

原因就三种:串口接反、没有共地、波特率不对。ESP8266 的 TXD 接 STM32 的 RX,RXD 接 STM32 的 TX,反接是最常见失误;其次是 STM32 板子供电和模块供电不在同一个电源域,导致逻辑电平没有参考;最后是波特率,老的 AT 固件默认 115200,有些模块出厂刷了 9600 波特率的固件,两边对不上,发什么都不返回。

解决方法是分三步走。先把 ESP8266 模块单独接到 USB 转串口工具上,用电脑串口助手直接发AT,确认模块本身是好的;再用一根杜邦线把模块地和 STM32 地接在一起;最后确认串口配置的波特率、数据位、停止位和模块固件一致。如果模块本身能返回OK,再接回 STM32,问题通常在接线和用户代码的串口初始化上。这条路径能过滤掉七成“模块不工作”的假故障。

现象二:AT有返回,但AT+CWJAP一直返回FAIL。

原因大概率是路由器频段或加密方式问题。ESP8266 只支持 2.4G WiFi,如果路由器开了 5G 优先或者双频合一,模块可能搜到信号也连不上。另外,SSID 或密码里含有特殊字符时,AT 指令里的转义处理很容易出问题。

解决方法是先在串口助手用AT+CWLAP扫描附近网络,确认模块能看到目标 SSID;然后把手机热点临时开成 2.4G 频段做测试,能连上说明模块没问题,问题出在路由器配置;最后在代码里把 SSID 和密码转义成十六进制的 ASCII,避免特殊字符干扰。这里有个小技巧,我测试时常用手机热点而不是公司或学校的大型 WiFi,因为大型无线网络常做 MAC 白名单和认证跳转,模块连不上不代表代码有错。

现象三:WiFi 连上了,AT+CIPSTART返回ERROR。

这个现象的原因通常是域名解析失败。部分 AT 固件对域名解析支持不好,尤其是一些精简固件,你填的是 OneNET 的接入域名,但固件内部 DNS 解析失败,TCP 连接建立不起来。另一个原因是目标端口没有开放,填的端口号不对。

解决方法是把域名换成 IP。先用电脑工具确认真实接入 IP 和端口,然后把AT+CIPSTART里的域名替换成 IP,报错立刻消失。需要注意,平台的接入 IP 是分开的,HTTP 和 MQTT 接入地址不同,别拿 MQTT 地址去做 HTTP POST,端口完全对不上。这类问题排查速度最快的方法,是先在电脑上用网络调试工具直接对目标地址发一个测试请求,能通再回来查模块配置。

5.2 上报阶段的几个隐蔽问题

现象四:HTTP POST 发出去了,OneNET 返回 400。

这个问题的“现象”非常明确,因为模块返回的串口日志里能看到 HTTP 状态码。400 的根因几乎都是报文格式错误。占大头的是Content-Length算错,其次是我在 4.2 节说到的\r\n缺失。还有一个容易被忽略的细节:OneNET 对api-key的校验是在 header 里完成的,key 值前后不能有空格,不能有引号,很多代码里为了排版在拼接时多加了空格,直接 403。

解决办法是打开串口调试助手,把http_buf的原始内容完整打印出来,逐字节检查。我一般会在工程里加一个调试宏,上报前把strlen(http_buf)和body_len都打印出来,对着报文数一下是不是一致。习惯上我还会把http_buf存成一个固定的全局数组而不在函数栈里拼,避免栈空间不够导致字符串被截断。STM32F103C8T6 的栈默认不大,大数组放局部变量容易溢出,表现是程序跑着跑着突然 HardFault,和报文本身没有关系。

现象五:OneNET 平台上有数据,但卡号和读卡器显示的 UID 不一致,或者上报时间是混乱的。

卡号不一致的原因就是第 3.2 节说的字节序问题。上报时间混乱的原因则是数据点里没有带时间戳,平台入库时用的是服务器当前时间,如果网络延迟大,时间看起来就会“飘”。解决方法是让数据点自己带datetime字段:

{"datastreams":[{"id":"card","datapoints":[{"value":"A2B3C4D5","datetime":"2024-01-01 12:00:00"}]}]}

但要注意,OneNET 平台对自定义时间戳的要求比较严格,格式必须是YYYY-MM-DD HH:MM:SS,且要使用东八区时间。如果你不上传datetime字段,平台默认取服务器当前时间,对于考勤这类应用来说其实够用,不必强行加时间戳。卡号不一致的排查方法则很简单:把uid_buf的原始字节打印出来,和读卡器读到的字节直接对比,确定是高字节序还是低字节序,然后统一调整格式化代码。

现象六:上报偶尔成功偶尔失败,没有任何规律。

这属于最难查的“玄学问题”。原因通常是 ESP8266 的串口接收缓冲区太小,平台返回的响应包头还没读完,下一轮的 AT 指令已经发出去了,导致模块状态错乱。另一个原因是在 TCP 连接保持期间,ESP8266 固件会主动发一些链路保活数据,这些数据混进接收缓冲区里,干扰了关键字判断。

解决办法是给串口接收缓冲区加大到 256 字节以上,并在每个状态切换前先清空缓冲区的历史数据。我调试时还发现一个规律:AT+CIPCLOSE执行后,模块会返回CLOSED,这个字符串也可能出现在下一轮的AT响应之前,所以判断“OK”时要排除掉缓冲区里已有的残留。从代码结构上讲,这类问题最好的规避方式,是在每次发送新指令前先调用一次清空函数。

6. 验证与进阶:串口日志分级,外加断网缓存重发

6.1 用一个日志开关和缓存重发机制,把工程变成可维护的状态

先说验证。工程刷进去后,我习惯按下面这个顺序把整个链路切开来测,而不是直接刷卡看平台。第一步,只跑读卡模块,串口能打印出 UID;第二步,跑 ESP8266 的AT+CIPSTART,模块能建立 TCP 连接;第三步,用AT+CIPSEND手动发一段固定的报文,平台能收到测试数据;第四步,把读卡和上报合到一起。每一步都有明确的期望输出,出了故障能直接锁定到哪一段。这个验证方式比一次性整体跑快很多,因为整体跑的时候,你根本不知道是卡没读到,还是 TCP 断了,还是报文格式错了。

进阶一点,可以在工程里加一个简单的日志分级宏:

#define LOG_LEVEL 2 #define LOG_ERROR 1 #define LOG_INFO 2 #define LOG_DEBUG 3 #define log_info(fmt, ...) \ do { if (LOG_LEVEL >= LOG_INFO) printf("[I] " fmt "\r\n", ##__VA_ARGS__); } while(0)

这个宏的好处是调试阶段把LOG_LEVEL调高,看全量细节;上线阶段调低,只保留错误日志,不会因为大量串口输出干扰 ESP8266 的实时响应。用这个思路重新组织工程代码,会让整个状态机的行为从“黑匣子”变成“每一步都可观察”。

断网重发是个很实用的进阶功能。很多场景下 ESP8266 掉线后,程序还在正常读卡,但上报数据永远失败,用户刷了卡以为成功了,实际考勤记录没上传。我一般会在刷卡记录里加一个待发送标志:

static uint8_t report_pending = 0; static uint8_t retry_count = 0; // 读卡成功后只置标志,不立即上报 report_pending = 1; sprintf(cache_card, "%s", card_hex); // 主循环里网络就绪时再补发 if (report_pending && wifi_state == WIFI_DONE) { if (send_http_post(cache_card) == 0) { report_pending = 0; // 上报成功,清除待发标志 } else if (++retry_count > 5) { report_pending = 0; // 连续失败五次,丢卡,避免死循环 retry_count = 0; } }

这段逻辑把读卡和上报彻底解耦,网络不在线时读卡功能不受影响,网络恢复后自动补发,实际效果比在while(1)里“读卡→上报→延时”的写法可靠得多。从那以后我每次做这类联网采集工程,都会强制把“采集”和“上传”拆成两个模块,先确认本地逻辑完整,再处理网络逻辑,用日志开关把两段流程分别验证一遍。这套思路也延续到了现在做的每一个嵌入式联网项目上,希望帮到你。

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

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询