STM32F4与EC20的TCP透传实战:从AT指令到串口DMA实现4G数据链路
发布时间:2026/10/20 1:26:58 锦皓数字建站

简介面向STM32F4嵌入式开发者的EC20模块TCP透传通信工程包解决了在4G无线模块上建立TCP客户端并透明传输数据的关键难题。压缩包内含174个文件约4.06MB以stm32f4xx系列的C源码与头文件为主体涵盖RCC、TIM、ADC、CAN等标准外设驱动同时包含Keil工程文件uvproj、编译生成的axf与hex目标文件、启动与链接脚本以及用于清理工程的批处理工具可直接打开工程对照学习。代码覆盖时钟与串口初始化、AT指令配置如CGDCONT、CGATT、CIPSTART、TCP连接建立与数据收发、错误处理与连接关闭等完整流程并对PDP上下文、GPRS/4G附着、SOCKET发送等关键操作给出可移植的实现。适合具有一定STM32基础、希望快速为物联网设备加入4G联网能力的开发者参考亦可作为调试TCP透传模式的排错模板。目前已有1358人学习下载参考价值获得广泛认可。1. EC20 的 TCP 透传模式STM32F4 接上就发数据的省心方案用 EC20 做网络通讯第一反应是走 AT 指令一条条问应答ATQIOPEN 建连、ATQISEND 发数、ATQIRD 收数写起来不复杂可真上了产线就发现维护一个 TCP 连接的状态机远比写指令麻烦。TCP 透传模式把这件事压成了「串口进、网络出网络进、串口出」的两根管道STM32F4 侧不用碰任何 TCP/IP 协议栈串口收到的一字节也会原样推给服务器服务器推回来的数据会自动从串口吐出来。这份资源就是围绕这个模式展开的含 AT 指令配置流程、STM32F4 串口收发代码和透传模式进出的关键时序。适合正在用手头 F4 板子做 DTU、数据采集器或远程网关想把 4G 模块尽快跑通的开发者也适合刚接触 EC20、在「指令模式还是透传模式」之间摇摆的人。2. 透传模式的底层逻辑AT 状态机与数据通路2.1 为什么是透传而不是指令模式EC20 支持两种通讯方式AT 指令模式和透传模式。指令模式下每次收发数据都要走「ATQISEND长度 → 等待 → 输入数据 → 等待 SEND OK」这串流程一来一回全是串口 IO。问题在于MCU 在等待应答期间必须阻塞否则状态机就乱了。你要在 STM32F4 上边采集传感器边维持 TCP 长连接最怕的就是「正在等 SEND OK 的时候服务器数据到了」这时候要么丢弃数据要么搞个缓存把状态机撑起来代码复杂度直线上升。透传模式QIMODE1把这一切收敛了模块自己管理 TCP 连接、重传和分包MCU 只要把串口数据丢给模块就行。串口收到的所有字节都会被模块当成 TCP payload 发给服务器服务器发来的 TCP 数据也自动出现在模块串口 TX 上。对 STM32F4 来说这等效于一根虚拟串口线——穿到对面的服务器上中间的网络细节全是黑匣子。代价是你失去了对 TCP 连接的显式控制断开、重连、发送超时都要靠模块的回调提示或者心跳去判断这是后面调试最容易出问题的地方。2.2 数据通路的三个环节串口收、模块转、服务器收整个透传链路可以拆成三段STM32F4 串口 → EC20 模块串口 → TCP 连接对端。第一段是 F4 的 USART 往 EC20 的串口发数据。这里直接决定了吞吐上限F4 的串口波特率跑 115200 时每秒最多约 11.5KB而 EC20 的 4G 上行带宽远高于此所以瓶颈几乎总在串口不要指望一条串口线跑满 LTE 带宽。实际工程里我一般把 MCU 到模块的串口波特率拉到 460800 或 921600这样能尽量减少串口对吞吐的限制。第二段是 EC20 模块内部的 TCP/IP 协议栈处理。模块收到串口数据后按当前 TCP 连接的发送窗口分包发送发送失败会重传。这段不用你管但它的存在会影响时序你在透传模式下向模块发数据模块并不会立刻 ACK而是在网络栈里缓冲后异步发送。所以「串口发完 服务器收到」这个等式不成立对实时性要求高的指令要在应用层做确认。第三段是服务器侧。EC20 作为 TCP Client连接的是你指定的服务器 IP 和端口服务器侧必须有一个 TCP Server 监听。常见的坑是服务器把模块当成 HTTP 客户端去解析数据——透传模式下模块只做透传不会帮你封装 HTTP 报文。2.3 透传模式的三个配置前置条件进入透传模式之前必须先把模块的网络状态确认好模块已注册上运营商网络。ATCREG? 返回CREG: 0,1才是已注册。TCP 连接已经建立。ATQIOPEN 建连成功后模块会返回CONNECT OK。连接类型必须是 TCP 或 UDP 的 client 端透传不支持 server 端。这三者缺一个ATQIMODE1 都会返回 ERROR。很多新手上来就对着手册敲 ATQIMODE1结果进不去就是因为连接没建立。正确顺序是先配 APN → 查网络注册 → 解析域名 → 建连 → 再切透传。常见做法是让 MCU 启动后把这套流程走一遍。模块回 CONNECT OK 之后你心里要有一个状态标记当前是 AT 模式还是透传模式。这个标记在后面决定「串口收到的字节往哪送」——是送去解析 AT 响应还是直接透传出去。推荐用枚举变量维护别用布尔值因为中间还有「正在建连」「正在退出透传」两个过渡态。3. STM32F4 侧的实现串口接收、DMA 缓冲与透传转发3.1 硬件连接与电平匹配EC20 模组的串口逻辑电平是 1.8V而 STM32F4 的 GPIO 是 3.3V不能直接互连。虽然有些模块的 VDDIO 引脚可以接 3.3V 把串口电平拉高但多数方案里模块的串口 TX/RX 是 1.8V 电平直接接 F4 的 3.3V 引脚轻则电平不识别重则烧模块 IO。最稳的做法是中间加一颗电平转换芯片TXS0102 或 TXS0108 都行只跑串口的话 TXS0102 绰绰有余。接线关系STM32F4 引脚电平转换EC20 模块USART6_TX (PC6)转 1.8VUART_RXUSART6_RX (PC7)转 3.3VUART_TXGND共地GNDPD7复位控制直连或三极管RESET我用的是 USART6 接 EC20波特率 4608008N1。模块上电后需要等 1.5 秒左右完成开机自检然后才能接收 AT 指令。复位引脚建议由 F4 控制——调试时按一下按键复位模块比反复拔电方便得多。PD7 输出低电平保持 100ms 以上即可触发模块复位。3.2 串口接收空闲中断 DMA 双缓冲透传模式下服务器下发的数据是异步且不定长的。最常见的做法是串口 DMA 接收 空闲中断IDLE定帧。STM32F4 的 HAL 库提供HAL_UART_Receive_DMA配合空闲中断可以在「串口线上没有新数据了」这个时刻被触发然后从 DMA 缓冲区把刚收到的数据取出来。以下是核心接收代码HAL 库风格#define RX_BUF_SIZE 1024 uint8_t dma_rx_buf[RX_BUF_SIZE]; // DMA 接收缓冲区 uint8_t ec20_rx_data[RX_BUF_SIZE]; // 应用层数据缓冲区 volatile uint16_t ec20_rx_len 0; void EC20_UART_Init(void) { // 使能 DMA 接收数据到达后自动写入 dma_rx_buf HAL_UART_Receive_DMA(huart6, dma_rx_buf, RX_BUF_SIZE); __HAL_UART_ENABLE_IT(huart6, UART_IT_IDLE); // 使能空闲中断 } void HAL_UART_IRQHandler(UART_HandleTypeDef *huart) { if (huart-Instance USART6) { if (__HAL_UART_GET_FLAG(huart6, UART_FLAG_IDLE)) { __HAL_UART_CLEAR_IDLEFLAG(huart6); // 先清标志 HAL_UART_DMAStop(huart6); // 停 DMA 防止数据错位 uint16_t len RX_BUF_SIZE - __HAL_DMA_GET_COUNTER(huart6); memcpy(ec20_rx_data, dma_rx_buf, len); // 拷贝到应用缓冲 ec20_rx_len len; // 重新启动 DMA并清空缓冲区 memset(dma_rx_buf, 0, RX_BUF_SIZE); HAL_UART_Receive_DMA(huart6, dma_rx_buf, RX_BUF_SIZE); } } }这段代码的关键点有两处。第一空闲中断触发时先清标志再停 DMA顺序反了会漏数据——__HAL_UART_CLEAR_IDLEFLAG必须在DMAStop之前否则可能把第二次空闲事件误吞。第二通过__HAL_DMA_GET_COUNTER计算实际接收长度DMA 配置的 BufferSize 是 1024当前计数器剩多少就说明 DMA 已经搬了多少字节两者相减就是本次接收的数据长度。这个方案对不定长数据最省心你不需要知道一帧数据有多长、也不需要帧头帧尾只要串口线上一段时间内安静下来就默认一帧接收完毕。但要注意数据流量非常大、持续不断时空闲中断可能一直不触发DMA 缓冲被写满后会发生覆盖这种情况要么加大缓冲区要么在 DMA 半满中断里把数据搬走。半满中断的做法稍复杂一点但工业级 DTU 里是必需项。3.3 数据转发AT 模式与透传模式的双向切换F4 的串口收到数据后要先判断当前 EC20 处于什么模式再决定数据去向。判断逻辑typedef enum { EC20_MODE_AT 0, // AT 指令模式 EC20_MODE_TRANSPARENT, // TCP 透传模式 } EC20_Mode_t; EC20_Mode_t ec20_mode EC20_MODE_AT; void EC20_UART_RxIndicate(uint16_t len) { if (ec20_mode EC20_MODE_AT) { // AT 模式下收到的数据可能是模块回显或主动上报例如 URC EC20_ParseAT(ec20_rx_data[0], len); } else { // 透传模式下收到的数据直接是服务器下发内容 EC20_ForwardToApp(ec20_rx_data[0], len); } }这里的EC20_ParseAT负责解析CONNECT OK、QIURC: 1这类异步上报。EC20_ForwardToApp把数据交给上层协议处理——你是做 Modbus 网关就交给 Modbus 解析做 JSON 透传就直接送给业务逻辑。反过来F4 要发数据给服务器时直接走串口把数据写到 USART 的 TX 即可发送代码相对简单阻塞式或 DMA 都行。整个设计中真正要紧的是退出透传透传模式下你没法发 AT 指令必须先用串口发送模块收到后才会退出透传回到 AT 模式。手册要求前后各保持 1 秒空闲不能连着数据发否则模块会把当成业务数据直接发给服务器。void EC20_ExitTransparentMode(void) { // 先等待串口发送 FIFO 清零 while (__HAL_UART_GET_FLAG(huart6, UART_FLAG_TC) RESET); HAL_Delay(1200); // 确保模块已进入稳定空闲状态 // 发送 不附带任何其他字符 uint8_t esc_seq[] ; HAL_UART_Transmit(huart6, esc_seq, 3, 100); HAL_Delay(1200); // 等模块完成退出响应 // 此时模块会返回 OK ec20_mode EC20_MODE_AT; }透传退出失败最常见的现象是模块没有返回 OK而是把原样发给了服务器。原因就是时序没满足前面必须至少 1 秒没有任何串口数据后面也要至少 1 秒才能发下一条指令。我把延时放在最前面而不是最末尾是为了兜住「刚发完数据就立刻切透传」的情况——如果你在业务里刚发完一包数据就调EC20_ExitTransparentMode执行到发送时前面刚好留出了 1.2 秒空闲窗口这样更稳妥。3.4 代码包中的其他关键模块这份资源里除了上面这段透传核心逻辑还带了 APN 配置、DNS 解析、TCP 建连的封装。APN 这东西对新手是第一个黑洞不配置 APN 时模块用的默认 APN 可能不是你当地运营商可用的直接结果是 ATCSOCONF 显示 DNS 解析失败、或者CONNECT OK永远等不到。代码里的配置顺序参考// 1. 关闭回显 ATE0 // 2. 配置 APN ATCGDCONT1,IP,ctnet // 3. 激活 PDP 上下文 ATCGACT1,1 // 4. 等待网络注册 ATCREG? // 5. 建连透传模式下用 ATQIOPEN ATQIOPEN1,0,TCP,120.24.78.90,8080,0,1其中ATQIOPEN七个参数的含义按顺序是上下文 ID、连接 ID、协议类型、服务器地址、端口、本地端口0 表示由模块自动分配、访问模式最后一个参数 1 表示透传模式0 是指令模式。这里有个容易混的点你在QIOPEN里即便指定了 1透传建连成功后也不会立刻进透传模块只是把这个连接标记为透传属性真正进入透传要等ATQIMODE1或者建连后模块直接切换。不同固件版本表现不一样老版本固件建连后要手动ATQIMODE1新版本在QIOPEN最后一个参数置 1 时会直接进透传。怎么确认看建连后模块是否回CONNECT且不再回OK就知道。4. 避坑指南EC20 透传实战中五个高频翻车点4.1 现象透传模式串口偶发第一个字节丢失刚切进透传模式立刻往串口发数据服务器收到的第一包数据少一个字节。原因模块从 AT 模式切到透传的瞬间内部串口驱动还在做状态切换你在这个窗口内发出的数据被当成模式切换握手被吞了。解决进透传成功后先发一个空包或等 200ms再正式发业务数据。我一般会在ATQIMODE1之后加一个HAL_Delay(200)实测丢字节概率从 30% 降到 0%。4.2 现象 发出去模块没反应数据到了服务器透传模式下想要退出串口发结果服务器收到三个加号。原因前后的 1 秒空闲窗口没满足。模块判定是转义符还是业务数据完全依赖前后是否有数据——它要求检测到时串口已经空闲至少 1 秒且之后也必须保持空闲让模块处理。解决把EC20_ExitTransparentMode里的延时从 1 秒加到 1.5 秒并且发送前检查__HAL_UART_GET_FLAG(huart6, UART_FLAG_TC) RESET确认发送缓冲区完全空。4.3 现象模块上电后 AT 指令偶发无响应EC20 上电后立刻发AT经常等不到OK。原因模块开机需要时间完成协议栈初始化和网络搜索不同固件版本开机时间从 800ms 到 2s 不等你发包太早模块串口还没就绪。解决上电后延时 2 秒再做第一次 AT 探测如果 AT 无响应再等 500ms 重试。我习惯在循环里做三次探测仍无响应就认为模块死机拉低复位引脚重启模块。4.4 现象服务器间歇性收不到数据但发送端 F4 的串口确实发出去了现象最隐蔽逻辑分析仪看 STM32F4 的 TX 引脚数据确实在发但服务器没收到。原因EC20 透传模式下模块内部有流控机制模块串口 RX 忙时不会通知 MCU数据直接丢弃尤其在服务器 TCP 接收窗口满、模块重传堵住时模块串口 RX 的 FIFO 会溢出。解决硬件上必须接 CTS/RTS 流控脚代码里使能硬件流控不要只用 TX/RX 三根线。HAL_UART_Init里设置HAL_UART_AUTOFLOWCTRL_CTS_RTS同时模块侧ATQICFGflowcontrol,1,1打开流控。4.5 现象模块长期运行后自动退出透传设备跑了一天后服务器发现连接断了退回 AT 模式会看到模块在透传和 AT 之间反复横跳。原因服务器没用 TCP Keep-Alive运营商侧 NAT 表把不活跃的连接删了。解决应用层心跳每 30 秒发一个心跳包服务器收到后回一个短包双方都保活模块侧也开启 TCP Keep-AliveATQICFGtcp/keepalive,1。心跳包设计成固定含义的短指令比如 0xAA 0x55别用真实业务数据当心跳否则服务器侧不好区分。5. 验证链路与最后一公里的调试习惯5.1 本地模拟服务器用 PC 做 TCP Server 先跑通不连真实服务器先本地验证。在 PC 上开一个 TCP Server 工具监听 8080 端口模块作为 Client 连 PC 的局域网 IP 或公网 IP。EC20 在没插 SIM 卡时也能用 ATQICFG 配 TCP 参数但建连必须依赖运营商网络所以本地验证也要插卡、用流量。流程模块上电 → 查询信号 ATCSQ返回值大于 15 才建议继续→ 配置 APN → ATQIOPEN 建连 → PC 工具看连接建立 → 切透传 → 串口助手发数据 → PC 工具看数据到达。这个环节我每次都会做一个「回环测试」从串口助手发一串固定报文看 PC 端原样收到再从 PC 端往回推一串报文看串口助手收到。两边都通说明链路是好的。只测单向通常会漏掉半双工方向的问题。5.2 抓日志串口抓手 VS 模块内部日志调试透传问题最痛苦的是不知道数据卡在哪个环节。我常用的手段是双路串口监控把 F4 的 TX发往模块的数据和模块的 TX发往 F4 的数据各引出一根线到 USB-TTL 调试助手分别看两路数据。如果 F4-TX 有数据、模块-TX 没有问题在模块侧——查网络、查 APN如果两边都有数据但服务器没收到问题在服务器侧——查防火墙、查端口。另一种更省事的做法是利用 EC20 的ATQIFGCNT0关闭回显后在 AT 模式下用ATQISEND发数据时模块会回提示符和SEND OK这能帮你确认模块本身收发能力正常。但进了透传模式这些诊断指令全失效所以你只能在 AT 模式阶段把问题排查完再切透传。5.3 我至今保留的一个强制步骤无论项目多赶我每次在透传代码里动任何参数——波特率、DMA 缓冲大小、流控开关——都会在改完后的第一件事就是跑一遍「上电 → 建连 → 透传 → 双向下发 100 包 1024 字节随机数据 → 核对丢包率」。这个流程在脚本里自动跑数据比对也在脚本里做好。用着很死板但救了我好几次上次以为波特率只是改了速度结果忘了同步改模块侧配置是这 100 包测试把 460800 和 115200 之间的错位揪了出来。从那以后透传代码的每次改动都强制走一遍这个回归流程修改完模块配置也会同步核对一遍 F4 侧的设置两边不一致的状况几乎绝迹。希望这份拆解能帮你把 EC20 透传这块硬骨头啃下来少走几趟我当年走过的弯路。本文还有配套的精品资源点击获取
锦
锦皓数字建站
深耕本土企业品牌数字化升级,专注原创端正雅致商务官网,从视觉设计到稳定运维全程保驾护航。