STM32+ESP8266+MFRC522卡号上报OneNET实战解析

发布时间:2026/10/14 2:53:07
STM32+ESP8266+MFRC522卡号上报OneNET实战解析 简介面向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 走 SPI1ESP8266 走 USART2。RC522 是 3.3V 器件ESP8266 模块也是 3.3V 供电这两者都不建议直接接 5V尤其是 RC522 的 VCC 如果接了 5VSPI 引脚电平会和 STM32 不匹配轻则读卡不稳定重则烧模块。RC522 引脚与 STM32 的标准分配如下RC522 引脚STM32 引脚说明VCC3.3V必须 3.3V不要接 5VRSTPB1普通 GPIO 输出初始化时给复位脉冲GNDGND与 STM32 共地MISOPA6SPI1_MISO数据输入到 MCUMOSIPA7SPI1_MOSI数据输出到 RC522SCKPA5SPI1_SCK时钟由 MCU 产生NSSPA4软件片选拉低选中 RC522IRQ悬空本工程未用悬空即可这里有一个常见反人类设计有人把 IRQ 也接上某个引脚试图做中断读卡结果中断频繁触发导致主循环被卡死。MFRC522 的 IRQ 不是必须的轮询方式完全够用读卡频率本来就不高没必要用中断。ESP8266 这边推荐接 USART2ESP8266 引脚STM32 引脚说明RXDPA2USART2_TXMCU 发送给模块TXDPA3USART2_RX模块发送给 MCUCH_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(ATCWMODE1\r\n); // 设置为 Station 模式 } break; case WIFI_CONNECTING: // 连接 WiFiSSID 和密码在工程开头宏定义里改 esp8266_send_string(ATCWJAP\MyWiFi\,\12345678\\r\n); if (esp8266_has_received(WIFI GOT IP)) { wifi_state WIFI_TCP_CONNECT; } break; case WIFI_TCP_CONNECT: esp8266_send_string(ATCIPSTART\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; }状态机的好处是不会因为某一步阻塞把整个系统卡死刷卡和上报两条逻辑可以并行。这段代码里有几个参数必须按实际环境改ATCWJAP里的 WiFi 名和密码如果路由器是 5G 频段ESP8266 模块连不上必须用 2.4G 频段ATCIPSTART里的 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?type3 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服务器会认为报文没结束一直等你的后续数据表现为连接超时而不是立即报错。第二type3表示 JSON 格式这个参数在旧版 OneNET HTTP 接口里是必带的部分新接口路径改成/datapoints直接 POST JSON路径前缀不同但报文结构一致。第三如果卡号字符串里包含中文或其他非 ASCII 字符Content-Length的字节数就不再等于字符串长度容易算错。所以我在上面特意把卡号限定为纯 ASCII 的十六进制字符串这能省掉一半编码坑。发送过程也不是一次printf就完事。AT 指令的发送模式是“先发长度、再发数据”char send_cmd[32]; sprintf(send_cmd, ATCIPSEND%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 的 RXRXD 接 STM32 的 TX反接是最常见失误其次是 STM32 板子供电和模块供电不在同一个电源域导致逻辑电平没有参考最后是波特率老的 AT 固件默认 115200有些模块出厂刷了 9600 波特率的固件两边对不上发什么都不返回。解决方法是分三步走。先把 ESP8266 模块单独接到 USB 转串口工具上用电脑串口助手直接发AT确认模块本身是好的再用一根杜邦线把模块地和 STM32 地接在一起最后确认串口配置的波特率、数据位、停止位和模块固件一致。如果模块本身能返回OK再接回 STM32问题通常在接线和用户代码的串口初始化上。这条路径能过滤掉七成“模块不工作”的假故障。现象二AT有返回但ATCWJAP一直返回FAIL。原因大概率是路由器频段或加密方式问题。ESP8266 只支持 2.4G WiFi如果路由器开了 5G 优先或者双频合一模块可能搜到信号也连不上。另外SSID 或密码里含有特殊字符时AT 指令里的转义处理很容易出问题。解决方法是先在串口助手用ATCWLAP扫描附近网络确认模块能看到目标 SSID然后把手机热点临时开成 2.4G 频段做测试能连上说明模块没问题问题出在路由器配置最后在代码里把 SSID 和密码转义成十六进制的 ASCII避免特殊字符干扰。这里有个小技巧我测试时常用手机热点而不是公司或学校的大型 WiFi因为大型无线网络常做 MAC 白名单和认证跳转模块连不上不代表代码有错。现象三WiFi 连上了ATCIPSTART返回ERROR。这个现象的原因通常是域名解析失败。部分 AT 固件对域名解析支持不好尤其是一些精简固件你填的是 OneNET 的接入域名但固件内部 DNS 解析失败TCP 连接建立不起来。另一个原因是目标端口没有开放填的端口号不对。解决方法是把域名换成 IP。先用电脑工具确认真实接入 IP 和端口然后把ATCIPSTART里的域名替换成 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 字节以上并在每个状态切换前先清空缓冲区的历史数据。我调试时还发现一个规律ATCIPCLOSE执行后模块会返回CLOSED这个字符串也可能出现在下一轮的AT响应之前所以判断“OK”时要排除掉缓冲区里已有的残留。从代码结构上讲这类问题最好的规避方式是在每次发送新指令前先调用一次清空函数。6. 验证与进阶串口日志分级外加断网缓存重发6.1 用一个日志开关和缓存重发机制把工程变成可维护的状态先说验证。工程刷进去后我习惯按下面这个顺序把整个链路切开来测而不是直接刷卡看平台。第一步只跑读卡模块串口能打印出 UID第二步跑 ESP8266 的ATCIPSTART模块能建立 TCP 连接第三步用ATCIPSEND手动发一段固定的报文平台能收到测试数据第四步把读卡和上报合到一起。每一步都有明确的期望输出出了故障能直接锁定到哪一段。这个验证方式比一次性整体跑快很多因为整体跑的时候你根本不知道是卡没读到还是 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)里“读卡→上报→延时”的写法可靠得多。从那以后我每次做这类联网采集工程都会强制把“采集”和“上传”拆成两个模块先确认本地逻辑完整再处理网络逻辑用日志开关把两段流程分别验证一遍。这套思路也延续到了现在做的每一个嵌入式联网项目上希望帮到你。本文还有配套的精品资源点击获取

关于本文作者

来自尧图内容编辑团队

尧图内容编辑团队 内容团队

尧图内容编辑团队

本文由尧图网络内容编辑团队执笔。团队由资深项目经理、前端工程师与设计师组成,所有内容均来自亲手交付的真实项目,先讲清问题、再给出可落地的解法。尧图深耕北京网站建设十年,服务过京华建材集团、智造科技等各行业客户,把一线经验沉淀为可复用的行业观察。

  • 十年建站经验,覆盖建材、制造、服务、文创等
  • 项目经理把关选题与事实准确性
  • 工程师与设计师联合撰写专业细节
  • 统一编辑规范,保证文风与排版一致
  • 每月复盘转化数据,迭代选题方向

延伸阅读

相关资讯与近期热门内容

深度阅读推荐

建站决策前值得细读的三篇

网站改版的5个关键决策
2024-08-12

网站改版的5个关键决策

什么时候该改版、改到什么程度、如何避免流量掉光,京华建材集团改版复盘给出答案。

获取专属建站方案

看完文章,把您的行业与预算告诉我们,免费获取一份量身定制的官网建设方案与报价。

立即免费咨询