EC20 4G模块TCP透传实战:从AT指令到STM32驱动开发
简介EC20 TCP透传模式通信的STM32F4实现工程面向使用Quectel EC20模块与STM32F4系列做4G/3G/2G联网的嵌入式开发者重点解决AT指令配置、Socket建立与透传数据收发的完整流程。工程代码覆盖从初始化、PDP上下文配置、TCP连接建立、数据传输到异常监测与连接关闭的完整链路并对串口收发和错误处理做了示例。压缩包共174个文件以C/H源码为主附带Keil工程文件、hex烧录文件、编译中间文件o、d、crf及清理脚本等整体约4.06MB可直接用Keil打开或提取关键代码移植。已有1347人浏览学习可作为开发参考。适合需要快速上手EC20透传模式的嵌入式开发者也可作为物联网项目的基础框架节省从零调试的时间。1. 为什么折腾EC20的TCP透传模式做物联网设备联调这段时间我最常被问到的一个问题就是“我的单片机怎么把数据发到服务器上”——这个问题看起来简单但真正落地的时候牵扯到模块选型、网络协议、串口驱动、服务器端配置一大堆事。如果你手里正好有一颗移远的EC20或者正打算用4G模块做数据上云那EC20的TCP透传模式就是一个绕不开的实用功能。EC20是移远Quectel推出的一款LTE Cat 1模组支持4G全网通在工业路由、车联网、充电桩、远程抄表这些场景里都能见到它的身影。它的工作模式分两种一种是传统的AT命令模式MCU通过串口给模块发AT指令来拨号、建连、收发数据每一步都要等模块回显才能继续另一种就是标题里提到的“透传模式”Transparent Mode模块负责把串口上收到的所有数据原封不动地通过已建立的TCP连接发给远端服务器反过来服务器下发的数据也会原样从串口吐给MCU。对开发者来说透传模式最大的好处就是——你不需要跟TCP/IP协议栈打交道也不需要关心数据包怎么封装的串口进去什么远端就收到什么整个链路对MCU来说就像一根透明的管子。这期的资料包名字叫“EC20 使用TCP透传模式通讯.rar”里面是完整可跑的工程和配置文档。本文就基于这个包把从接线、配AT指令、写MCU驱动到验证服务器收发这一整套流程掰开揉碎讲一遍同时把我实测过程中遇到的那些坑一并记录下来。适合正在做4G联网项目、用STM32或K210这类MCU接EC20的工程师也适合刚接触Cat 1模块、想快速把设备数据送到云平台的朋友。2. 透传模式的原理与整体选型思路2.1 透传到底“透”的是什么先看一张最简链路图一条数据从设备到服务器的完整路径是这样的传感器或业务逻辑把数据交给MCU比如STM32的串口USART1MCU通过串口把数据帧发给EC20模块模块的USB口或UART口EC20内部完成数据打包经4G网络走TCP协议发给远端服务器服务器收到数据后回响应EC20把响应从串口原样吐回给MCU。在这个过程中TCP连接建立、IP地址解析、报文重传、流量控制这些事情全部由EC20内部完成。MCU侧唯一需要做的就是“把数据塞进串口发送缓冲区”。相反如果你用AT命令模式MCU每次发数据之前都要发一条类似ATQISENDlength的指令然后等模块返回提示符再发送数据内容结束后还得等SEND OK回显。一次两次没问题但如果每秒都上报一次数据这种一问一答的方式会让代码变得极其冗长而且非常容易出状态机Bug。透传模式直接从机制上消灭了这些问题。EC20进入透传模式有两条路一条是在TCP连接建立后发ATQISWTMD1切换进去另一条是在建立连接时直接指定ATQIOPEN...的第二个参数同时保证模块固件支持连接成功后自动进入透传。两种方式最终效果一样区别只是代码里多一步少一步的问题。2.2 为什么选EC20而不是其他方案做4G透传市面上可选方案其实不少有人用DTU数据传输单元成品买回来配置一下就能用也有人用ESP8266/ESP32走WiFi成本低但依赖路由器还有人直接用华为ME909、移远EC200等模组。我在这个项目里选择EC20主要基于以下几个理由全网通支持EC20同时支持移动/联通/电信的4G不用针对运营商做区分全国跑项目时不用换硬件管脚和封装兼容性好EC20采用LGA封装和移远同系列模块的硬件设计基本兼容后续升级或替换比较方便生态成熟移远的AT指令集文档、参考设计、驱动例程都很齐全出了问题百度/论坛上能找到大量案例Cat 1的速率对绝大多数传感器数据上报场景足够用下行最高10Mbps、上行5Mbps比较稳发热和功耗也远低于Cat 4模块。如果项目对成本极度敏感、或者只需要2G网络也可以选EC200或MC20但如果你希望“一套代码、多项目复用”EC20的性价比和稳定性会更合适。2.3 整体方案框图我这次测试用的主控是STM32F103ZET6接线方式如下EC20_TXD-STM32_USART1_RXPA10EC20_RXD-STM32_USART1_TXPA9EC20_PWRKEY-STM32_PE1控制开机EC20_GND-STM32_GND模块开机方面EC20的PWRKEY引脚需要拉低至少500ms才能触发开机这个在之后代码部分会详细讲。如果手头是K210或者其他单片机逻辑完全一样无非是串口号和GPIO号不同所以这篇文章的驱动代码部分可以无障碍地移植过去。3. 串口之外的TCP链路基础为什么先聊三次握手3.1 从“连接建立”到“数据可靠”EC20要进入透传模式前提是模块内部已经建好了一条TCP连接。TCP是一个“面向连接”的可靠传输协议所谓面向连接就意味着通信双方在传数据之前得先通过三次握手协商好彼此的收发窗口和初始序列号。这个机制的原理可以这样理解A给B打电话A说“喂能听到吗”B听到后回一句“听到了你能听到我吗”A再回“我也听到了”确认双方都具备收发能力才开始正式讲话。具体到TCP协议上就是第一次握手客户端向服务器发送SYN报文并带上一个初始序列号seqx第二次握手服务器收到SYN后回复SYNACK报文确认号ackx1同时带上自己的初始序列号seqy第三次握手客户端再回复ACK报文确认号acky1。这三次握手完成后连接状态变为ESTABLISHED数据才能开始传输。为什么要三次而不是两次因为TCP要避免一种情况客户端第一次发的SYN因为网络延迟在连接关闭后才到达服务器。如果只有两次握手服务器会误以为客户端想要建立新连接于是分配资源、进入ESTABLISHED状态结果客户端根本不理它白白浪费服务器资源。而三次握手让客户端可以确认“服务器确实收到了我的SYN且愿意建立连接”从而避免这种历史连接导致的资源浪费。对于EC20的透传应用来说MCU开发者一般不需要关心这些握手细节但理解它有一个实际意义无线网络环境复杂TCP连接的建立和维持并不总是那么顺利排查问题时你得知道服务器上的SYN_RECV、ESTABLISHED这些状态是什么意思才能判断问题到底出在模块侧还是服务器侧后面我还会细讲。3.2 TCP和UDP透传场景如何选在配置EC20时绝大多数人都会面临一个选择ATQIOPEN里协议参数填“TCP”还是“UDP”TCP是面向连接的保证数据不丢、不乱序、不重复适合对数据完整性要求高的场景比如远程升级、指令下发、传感器关键数据上报UDP是无连接的只管发不管到效率高但可能丢包适合视频流、语音这种允许少量丢失但对实时性要求高的场景也适合一些只上报不做双向交互的业务。我个人的建议是如果你的设备需要服务器下发指令比如远程开关、参数配置或者丢失某一条数据会造成严重后果选TCP。如果纯粹是传感器周期性上报丢一条下个周期还能补上UDP也可以考虑但EC20这类Cat 1模块跑TCP的开销本来就很小所以项目中我默认都走TCP。4. AT指令配置TCP连接的完整流程4.1 串口参数与模块开机检测把EC20模块和MCU的串口接好后第一件事是先确认模块能正常工作。我习惯在PC上通过USB转TTL直接连EC20的调试串口用串口助手发AT指令波特率设115200数据位8停止位1无校验。这是EC20默认的串口参数。模块上电后串口会输出开机URC上报一般是一串以RDY或CFUN: 1结尾的信息。如果没看到任何输出大概率是接线问题或者模块没正常开机。EC20的PWRKEY引脚是个开集电极控制脚正常状态下默认上拉为高电平要让模块开机需要把该引脚拉低至少500ms再释放。如果你用的是开发板一般已经把PWRKEY接好了直接上电就自动开机如果是自己画板子这一步务必检查。模块开机并且串口有响应之后可以先发一条AT如果返回OK说明模块核心已就绪。接着依次检查SIM卡状态和网络注册情况ATCPIN? # 返回 CPIN: READY 说明SIM卡正常识别 ATCREG? # 返回 CREG: 0,1 或 0,5 说明已注册上网络 ATCGATT? # 返回 CGATT: 1 说明已附着4G网络可以开始建立TCP连接4.2 建立TCP连接与进入透传确认网络没问题后打开一个TCP服务器。我这边用网络调试助手开在电脑上监听一个端口比如8080。这里有个注意点如果你把EC20放到4G网卡模式下模块拿到的是运营商分配的内网IP电脑上的TCP服务器不能直接被公网访问。为了测试方便我通常让电脑和模块在同一个网段——即用开发板/模块的USB网卡模式RNDIS或者让电脑插一张同运营商流量卡使得两端网络可达更省事的做法是买一台有公网IP的云服务器轻量应用服务器就行在上面搭一个TCP服务。下面以云服务器123.45.67.89:8080为例展示指令。ATQICLOSE0 # 先确保之前没有遗留的连接占用通道0返回 OK 即可 ATQIOPEN1,0,TCP,123.45.67.89,8080,0,0 # 参数依次是 # 1 —— 使用TCP/IP协议栈这个参数固定为1 # 0 —— 连接ID0~11可分配这里用0号连接 # TCP —— 协议类型 # IP地址—— 远端服务器地址 # 8080 —— 远端端口 # 0 —— 本地端口0表示由模块自动分配 # 0 —— 访问模式0表示缓存模式1表示直接推送模式连接请求发出去后模块会先返回OK等一段时间取决于网络状况会异步返回QIOPEN: 0,0表示0号连接已成功建立。如果返回的是QIOPEN: 0, -1则说明连接失败大概率是服务器地址/端口不对或者服务器根本没在监听。连接建立后下一步就进入透传模式ATQISWTMD0这条命令的作用是把0号连接切换成透传模式。成功后会返回OK之后再发数据模块就不会再回显或SEND OK这些提示而是直接把串口数据原样发到远端TCP服务器。4.3 退出透传的两种方式进入透传模式之后一个比较棘手的问题就是“怎么退出”。因为此时所有的串口数据都会被当成业务数据发出去你没法再发AT指令来查询模块状态。EC20提供了两种退出机制硬件方式把DTR引脚拉低模块会自动退出透传模式返回AT命令模式。前提是你在进入透传前执行过ATD1把DTR功能设置对软件方式连续发送不需要回车换行模块会识别出“这不是业务数据”并把控制权交回AT模式。发送之前和之后需要保持一定的静默时间默认1秒否则模块会把它当成业务数据发出去。实测下来我建议用DTR引脚控制退出因为定时器和静默时间的处理对MCU来说比较繁琐而且稍有差错就会把发到服务器端造成数据污染。如果你用的模块没引出DTR那就只能用注意把串口发送之间的间隔控制在合适范围内即可。5. MCU端代码怎么实现透传5.1 初始化串口与开机控制下面给出一份精简的STM32驱动代码实现了“开机—查询网络—TCP建连—透传—收发数据”的完整流程。先看串口初始化和模块开机部分// 串口1初始化用于和EC20通信 void EC20_UART_Init(void) { GPIO_InitTypeDef GPIO_InitStructure {0}; USART_InitTypeDef USART_InitStructure {0}; RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_USART1, ENABLE); // PA9 - USART1_TXPA10 - USART1_RX GPIO_InitStructure.GPIO_Pin GPIO_Pin_9; GPIO_InitStructure.GPIO_Mode GPIO_Mode_AF_PP; GPIO_InitStructure.GPIO_Speed GPIO_Speed_50MHz; GPIO_Init(GPIOA, GPIO_InitStructure); GPIO_InitStructure.GPIO_Pin GPIO_Pin_10; GPIO_InitStructure.GPIO_Mode GPIO_Mode_IN_FLOATING; GPIO_Init(GPIOA, GPIO_InitStructure); USART_InitStructure.USART_BaudRate 115200; USART_InitStructure.USART_WordLength USART_WordLength_8b; USART_InitStructure.USART_StopBits USART_StopBits_1; USART_InitStructure.USART_Parity USART_Parity_No; USART_InitStructure.USART_HardwareFlowControl USART_HardwareFlowControl_None; USART_InitStructure.USART_Mode USART_Mode_RX | USART_Mode_TX; USART_Init(USART1, USART_InitStructure); USART_ITConfig(USART1, USART_IT_RXNE, ENABLE); // 开启接收中断 USART_Cmd(USART1, ENABLE); } // EC20开机PWRKEY拉低至少800ms void EC20_PowerOn(void) { GPIO_ResetBits(GPIOE, GPIO_Pin_1); delay_ms(800); GPIO_SetBits(GPIOE, GPIO_Pin_1); delay_ms(3000); // 等模块完成开机流程 }这段代码有几个细节值得说。第一PWRKEY拉低时间一定要足够我见过有人只延时200ms导致模块开机不稳定的情况最后通过示波器抓波形才发现是这个问题。第二模块开机后不能立刻发AT指令模块内部要初始化射频、SIM卡、网络栈经验值至少等3秒再开始发指令否则前几条指令会被丢弃。5.2 建连指令发得稳因为AT指令是“一问一答”的交互模式代码里不能用裸的printf直接怼最好封装一个“发指令等回应”的函数。参考下面的写法uint8_t EC20_SendATCmd(const char *cmd, const char *expect, uint32_t timeout_ms) { uint8_t buf[128] {0}; uint32_t len 0; uint32_t tick HAL_GetTick(); uint8_t status 0; // 清空串口接收缓存 memset(buf, 0, sizeof(buf)); len 0; // 发送命令 EC20_UART_SendString(cmd); // 等待期望的回应 while ((HAL_GetTick() - tick) timeout_ms) { if (EC20_UART_ReceiveByte(buf[len])) { // 每收到一个字节就检查一次缓冲区是否包含期望子串 if (strstr((char*)buf, expect) ! NULL) { status 1; break; } len; if (len sizeof(buf) - 1) break; } } return status; }这个函数的思路很简单发一条指令然后在一定时间内等待串口回显一旦在缓冲区里匹配到目标字符串比如OK或QIOPEN: 0, 0就返回成功。实际使用时要注意EC20回显可能比较长比如查询信号质量时带一堆参数缓冲区记得开大点另外每发完一条指令要清空接收缓冲防止上一次的残留数据干扰匹配。建立连接并进入透传的调用顺序如下// 等待模块就绪 EC20_SendATCmd(AT\r\n, OK, 1000); // 查SIM卡状态这里最多重试3次 for (int i 0; i 3; i) { if (EC20_SendATCmd(ATCPIN?\r\n, READY, 2000)) break; delay_ms(500); } // 等网络注册 while (!EC20_SendATCmd(ATCREG?\r\n, CREG: 0,1, 3000)) { delay_ms(2000); } // 查网络附着状态 while (!EC20_SendATCmd(ATCGATT?\r\n, CGATT: 1, 3000)) { delay_ms(2000); } // 建连 EC20_SendATCmd(ATQICLOSE0\r\n, OK, 1000); EC20_SendATCmd(ATQIOPEN1,0,\TCP\,\123.45.67.89\,8080,0,0\r\n, QIOPEN: 0, 0, 15000); // 进入透传模式 EC20_SendATCmd(ATQISWTMD0\r\n, OK, 1000);5.3 透传模式下的数据收发进入透传后代码就简化成一个串口数据中转器。MCU收到传感器的数据直接往EC20的串口发EC20串口反馈的数据来自服务器MCU用接收中断取出来交给业务逻辑处理。// 在串口中断里收EC20的数据 void USART1_IRQHandler(void) { uint8_t ch; if (USART_GetITStatus(USART1, USART_IT_RXNE) ! RESET) { ch USART_ReceiveData(USART1); // 把数据放入环形缓冲区或直接送到上层处理 data_from_EC20_buffer[write_index] ch; } } // 发送数据到服务器 void SendDataToServer(uint8_t *data, uint16_t len) { for (uint16_t i 0; i len; i) { while (USART_GetFlagStatus(USART1, USART_FLAG_TXE) RESET); USART_SendData(USART1, data[i]); } }如果业务数据量大建议在发送端用DMA接收端用空闲中断DMA的方式能大幅降低MCU的负载。EC20串口的接收缓冲区不要开太小因为模块从网络层收到突发数据时会一口气全部吐出如果MCU来不及读取串口硬件FIFO一旦溢出就会丢数据。我在项目里把DMA接收缓冲设为1KB实测没有出现丢失。6. 完整数据链路的验证与排查6.1 用网络调试助手验证收发写完全部代码之后先不要着急接服务器业务我习惯分三步做链路验证第一步先让模块用AT命令模式建连然后手动发一条数据观察网络调试助手能否收到。这一步能确认从模块到服务器的网络链路是通的。如果这一步都收不到问题大概率出在网络侧不用急着查MCU代码。第二步AT模式下发完数据后主动接收服务器下发的数据确认下行链路也没问题。网络调试助手支持手动发送服务器这边发一串明文看看模块串口会不会回显这串内容。第三步执行ATQISWTMD0进入透传模式用MCU自动周期上报数据跑个半小时观察是否有丢包、断连、乱序的情况。我自己实测的结果是在信号正常的环境下连续跑了3万条数据每包约200字节服务器全部收到一条没丢。透传的稳定性比预想中好不过这是在网络质量较好时的结论。在信号弱、基站切换频繁的场景下TCP的可靠性机制会自动兜底最明显的表现就是偶尔会卡顿一下随后数据还能续上不会乱序。6.2 服务器端怎么看连接状态如果建连或者传数据过程中出了问题建议先在服务器端确认TCP连接状态。Linux服务器上用netstat或ss命令就能看ss -tunap | grep 8080如果看到ESTABLISHED说明TCP连接是正常的如果连接数很多但状态全是SYN_RECV大概率是防火墙或NAT问题导致服务器发出的SYNACK回不到模块侧。如果出现ECONNRESET连接被重置或者Connection timed out主要排查这几项EC20的APN设置是否正确。国内SIM卡一般自动获取APN如cmnet、ctnet但有些物联网卡需要手动配置ATCGDCONT1,IP,APN_NAME服务器防火墙是否放行对应端口。云服务器默认安全组只放行22/80等端口8080或自定义端口需要在控制台手动添加规则模块的信号质量。可以用ATCSQ查看信号强度返回值范围0~31低于10基本属于很差的信号容易掉线。6.3 高频踩坑点整理整个调测过程中值得记录并分享的坑大概有下面几类做成速查表方便大家定位问题现象可能原因解决办法发AT指令无任何回显接线错误或模块未开机检查TXD/RXD是否交叉连接PWRKEY拉低时间是否足够ATCPIN?返回ERRORSIM卡未识别或卡没插好检查SIM卡触点确认卡座方向用万用表测SIM_VCC电压是否正常ATCREG?一直是0,0模块没搜到网络天线接了吗检查天线座和馈线移到窗口附近测试信号建连返回QIOPEN: 0, -1服务器不可达或地址写错确认IP/端口正确服务器用ss -lntp确认在监听云服务器检查安全组透传后数据丢帧串口波特率太高或MCU读取不及时把串口数据间隔拉大一点测试改用DMA空闲中断接收适当降低波特率偶发断连又自动重连网络信号弱或基站切换加TCP心跳包机制重连后自动恢复业务考虑启用MQTT等应用层协议做断线续传6.4 心跳包与断线重连经验最后想多说一句关于“长连接保活”的话题这也是EC20 TCP透传实际产品化时必须处理的问题。模块建立TCP连接后如果长时间没有数据交互运营商NAT设备的映射表可能会过期导致连接被静默断开——从模块侧看连接还在但实际上数据已经发不过去了。我这边采用的做法是MCU每60秒向服务器发送一帧心跳数据比如#HEART#服务器收到后可以不必回复如果连续3个心跳周期都没有收到服务器的任何数据或者模块上报了QIURC: 0的URC消息表示连接异常断开就主动执行ATQICLOSE0关闭旧连接重新走一遍建连流程。这样即使网络抖动、模块重拨业务也能在几秒内恢复。对于EC20这类模块芯片内部其实也有TCP Keep-Alive参数可以调但AT指令配置相对复杂实际项目中用应用层心跳更省心。这个方案目前已经在我手头的几个4G联网项目里稳定跑了两个多月累计在线时长超过99%没出现过无法恢复的死连接问题。如果你也在用EC20做透传强烈建议把心跳和重连这两个机制提前设计进代码里——别等到现场设备掉线了才想起来补那时候改代码的成本会翻好几倍。本文还有配套的精品资源点击获取