STM32F4+EC20 4G模块TCP透传实战:从AT指令到联网源码解析
发布时间:2026/10/20 1:41:59 锦皓数字建站

简介面向STM32F4嵌入式开发者这份资源演示EC20模块TCP透传模式通讯覆盖AT命令配置、串口交互、TCP连接建立与数据收发适合快速上手4G模块与单片机联调的工程师或学习者。压缩包共174个文件约4.06MB以C源文件48个和头文件46个为主另有uvproj工程、axf/hex编译产物及map/lst等辅助文件便于参考或移植到自己的工程。资源对PDP上下文配置、GPRS/4G激活、CIPSTART连接、数据收发、CIPCLOSE关闭等均有说明同时包含PWM、定时器、ADC、RCC、FLASH、CAN等驱动源码可作为STM32F4外设开发的综合示例。目前已有1358人学习下载适合正在调试EC20透传通信或了解AT命令交互细节的开发者。1. 拿到这份 EC20 透传工程STM32F4 用户最该先看的联网方案做远程设备数据采集的时候最常用的组合就是STM32F4系列 EC20 模块 TCP透传模式。很多第一次摸 4G 模块的开发者都会卡在同一个地方模块明明能收到 AT 指令可数据就是上不了服务器要不就是进了透传模式之后再也退不出来。这份带完整源码的工程包把 EC20 的 TCP 透传通讯流程整个串起来了从开机注网、TCP 建连到双向数据转发全部是可以在 STM32F4 上直接编译运行的代码。适合正在做联网网关、远程监控或者纯数据上报项目的开发者也适合还不太确定透传模式和指令模式怎么选的初学者。下面我会按工程的实际使用顺序从原理讲到代码再讲几个我实际踩过的坑。2. EC20 联网流程与透传模式选型先搞清模块在干什么2.1 从开机到 TCP 连接EC20 要过四道关很多人拿到 EC20 的第一反应是上电就能发数据实际上模块从冷启动到能够承载 TCP 数据要经过四个阶段开机、联网注册、PDP 激活、TCP 建连。前两个阶段是模块自身状态后两个阶段是数据通道状态。一个常见翻车场景是用测试板已经连通过换到自己的板子上死活建不了 TCP 连接最后发现是 SIM 卡槽接触不良、天线没接好导致注册不成功。开机阶段由硬件控制PWRKEY 拉低几百毫秒触发模块上电模块起来后串口会主动输出一段开机信息包括固件版本号。此时不要急着发 AT 指令先等模块自己把射频初始化完。之后依次检查 SIM 卡状态、信号强度、网络注册。网络注册分为 CS 域和 PS 域我们做 TCP 只看 PS 域的注册状态对应 AT 指令返回的第二个数字是 1 或 5而不是开机时看到的 1、2、3。PDP分组数据协议激活是容易被忽略的一步没有激活上下文就无法建立 IPO 连接。激活前要先通过 ATCGDCONT 设置 APN。国内多数物联网卡走的是 CMNET 这类公共 APN专用的 APN 需要向运营商申请。激活完成后模块才有公网侧的 IP 地址接下来才能发起 TCP 连接。2.2 透传模式 vs 指令模式为什么数据上行为主时选透传EC20 有两种数据通道用法。一种是指令模式即用 ATQISEND 发送数据、用 ATQIRD 接收数据模块收到网络数据时会主动上报提示。这种模式的优点是随时可以查信号、查网络状态、切换连接缺点也很明显每条数据都要组 AT 帧、等返回、做解析MCU 侧的处理压力集中在协议解析上吞吐量上不去。透传模式则不同模块建立 TCP 连接后进入透传状态单片机往串口写的每一个字节会直接变成网络数据发出去网络侧收到的 TCP 数据也会原样从串口透传回来。对 STM32F4 来说它的工作只剩下串口搬运不需要理解任何数据内容这非常适合大流量、长连接、数据上行为主的场景比如周期性上传传感器数据、固件 OTA 分包下发。但透传不是万能的如果你需要在连接过程中频繁查询信号强度 CSQ、或者动态切换服务器地址透传模式会非常难受因为要频繁退出和重进透传。遇到这种场景我一般会保留指令模式把业务数据封装成私有协议用缓冲模式处理。这个取舍在后面的进阶章节还会再展开。2.3 关键 AT 指令与返回参数速查表工程里把联网流程封装成了几个函数但底层调用的是这张指令序列。建议先把这张表看明白再去看工程代码。阶段作用指令返回判断串口探测确认模块能收到 AT 指令ATOK关闭回显减少串口干扰ATE0OKSIM 卡检测检查 SIM 是否识别ATCPIN?READY信号强度确认天线可用ATCSQ第一个值大于 10 较稳网络注册确认 PS 域注册ATCREG?返回中第二位为 1 或 5APN 配置设定数据网络ATCGDCONT1,IP,CMNETOKPDP 激活获取网络 IPATCGACT1,1OKTCP 建连连接服务器ATQIOPEN1,TCP,x.x.x.x,8080,20000,0QIOPEN: 1,0进入透传数据直通ATQIMODE1OK参数说明ATCSQ 返回的两个数字中第一个是信号强度等级范围 0~3110 以下基本属于能注册但容易掉线第二个数字是误码率等级一般不用太关注。ATCREG? 的返回里第二位 1 表示已注册本地网、5 表示已注册漫游网都可以建连。ATQIOPEN 里的三个数字依次是连接 ID、TCP 类型、远端 IP、远端端口、本地端口和连接模式最后一个 0 表示非缓冲模式1 表示缓冲模式。连接成果返回 QIOPEN: 1,0 中的第一个 1 是连接 ID第二个 0 表示成功若是负数代表失败。3. STM32F4 与 EC20 的硬件连接和驱动分层先稳住电气基础3.1 供电、电平与 SIM 卡硬件上最容易翻车的三个位置EC20 是 4G 模块不是 WiFi 模块。它的瞬态功耗比很多人想象的大得多在弱信号环境下射频发射功率拉满峰值电流能到 2A 左右这个电流如果用 STM32F4 板子上的 3.3V LDO 扛电压会被瞬间拉垮导致模块重启。我一般会在模块供电入口放一颗 DC-DC 降压芯片或者至少用能持续输出 1A 以上的降压电路并在模块 VBAT 引脚附近并联一个大容量的铝电容加若干 100nF 陶瓷电容。工程板子上如果空间紧张也要确保走线线宽不小于 1mm。电平转换是第二个容易踩坑的点。EC20 的 UART 电平是 1.8VSTM32F4 的 UART 电平是 3.3V直接连会有过压风险尤其模块的 TX 引脚在空闲时会被上拉到 1.8V这个电压识别不了 3.3V 为高电平反而可能损伤模块。更好的做法是用电平转换芯片把两边的 TXD/RXD 交叉转接或者选择已经带电平转换的底板。SIM 卡部分要注意卡座引脚是否和模块的 SIM 接口匹配EC20 使用的是 1.8V/3V 双电压 SIM 卡接口如果卡座加上过长走线容易信号衰减。做好这些事情再进入代码部分否则后边程序跑不通往往不是代码问题而是硬件电平不对这在调试时经常造成误导。3.2 驱动工程的文件结构把协议和业务分开这份工程值得称道的一个点是目录组织思路清楚。如果直接堆代码后续维护会很痛苦尤其是要换服务器 IP 或者改波特率的时候。打开工程包后你会看到这样一套结构project/ ├── core/ // 系统时钟、中断相关 ├── usart/ │ ├── usart.c // 串口底层DMA 发送接收 │ └── usart.h ├── ec20/ │ ├── ec20.c // AT 指令、联网状态机、透传控制 │ ├── ec20.h │ └── ec20_config.h // APN、服务器 IP、端口、心跳周期 ├── app/ │ ├── data_handler.c // 业务数据组装与解析 │ └── data_handler.h └── main.c // 主循环框架ec20_config.h 是我建议你第一个打开的文件。里面集中定义了所有需要按项目修改的参数比如模块串口句柄、APN 名称、服务器地址、端口、波特率等。这样做有一个实际好处换运营商 SIM 卡或者换服务器时不需要到整个工程里翻 AT 指令。app 层是业务代码比如温湿度数据怎么组帧、收到下行指令怎么处理它只被 ec20 层的接口调用不直接操作串口和 AT 指令。这样的分层对 STM32F4 用户的意义在于你想移植到其他 F4 型号时只需要改 usart 层里对应的串口号业务代码基本不用动。工程里用的是 HAL 库的串口在 usart.c 里能看到具体的串口初始化结构体可以通过修改句柄来切换 USART1、USART2 或 USART3。3.3 串口接收框架DMA 空闲中断怎么配合串口接收是 4G 模块通信最容易出 bug 的地方。很多人直接从 HAL_UART_Receive_IT 按字节收这样做的结果是有可能每收到一个字节就被中断打断一次在主循环里还要拼包工作量大还容易出现粘包。更好的做法是用 DMA 空闲中断HAL_UARTEx_ReceiveToIdle_DMA来接收这一套方法在这份工程里已经给了现成框架。static uint8_t ec20_rx_buf[EC20_RX_BUF_SIZE]; volatile uint16_t ec20_rx_len 0; void EC20_UartStartReceive(void) { HAL_UARTEx_ReceiveToIdle_DMA(huart3, ec20_rx_buf, EC20_RX_BUF_SIZE); } void HAL_UARTEx_RxEventCallback(UART_HandleTypeDef *huart, uint16_t Size) { if (huart-Instance USART3) { ec20_rx_len Size; // 本次 DMA 接收到的字节数 EC20_ProcessRxData(ec20_rx_buf, ec20_rx_len); memset(ec20_rx_buf, 0, EC20_RX_BUF_SIZE); HAL_UARTEx_ReceiveToIdle_DMA(huart3, ec20_rx_buf, EC20_RX_BUF_SIZE); } }逻辑说明HAL_UARTEx_ReceiveToIdle_DMA 打开一个 DMA 传输当串口在某个时间点一段时间没有新数据时硬件会产生空闲中断HAL 库会调用这个回调函数。Size 参数是当前这一轮 DMA 接收到的字节总数这样不需要自己拼包一次性把一整帧数据交给业务层。把收到的数据交给 EC20_ProcessRxData 后重新拉进 DMA 接收模式保证下一次数据到来时缓冲区一直在监听。参数说明EC20_RX_BUF_SIZE 的大小直接影响可接收的单帧数据长度。如果服务器下发的指令帧可能超过 512 字节就把它调大。这个参数的设置要和你的协议最大帧长度匹配否则模块返回的数据会被 DMA 截断造成解析失败。对于 K 线接收缓冲区设置到 1024 是合理的。另外注意 Buffer 是 uint8_t 数组不是指针这样 DMA 才能连续搬数据。一旦初始化后这个 buf 不能随意被局部变量覆盖所以它被定义成静态全局。回调里如果发现 ec20_rx_len 超过了缓冲长度说明出现了严重的溢出这种情况要在代码里做丢帧处理而不能直接把缓冲区后半段当成有效数据。4. 初始化、拨号、进入透传核心代码拆解4.1 开机与串口握手电源时序和 AT 探测打开 main 函数第一个用到的函数是 EC20_PowerOn。EC20 的 PWRKEY 引脚是低电平有效触发方式比较固定拉低再释放。电源时序上要先保证 VBAT 电压稳定后再拉 PWRKEY否则模块可能直接不启动。void EC20_PowerOn(void) { HAL_GPIO_WritePin(EC20_PWRKEY_GPIO_Port, EC20_PWRKEY_Pin, GPIO_PIN_RESET); HAL_Delay(600); // 拉低 600ms HAL_GPIO_WritePin(EC20_PWRKEY_GPIO_Port, EC20_PWRKEY_Pin, GPIO_PIN_SET); HAL_Delay(3000); // 等待开机 AT 信息 }逻辑说明PWRKEY 被拉低 600 毫秒后释放模块开始启动。等 3 秒不是因为 AT 必须等这么久而是为了在调试时不漏掉开机信息。质量好的习惯是后面加一个 AT 探测循环判断模块是否真正就绪。很多工程师在这里只延时一次然后就发 AT结果如果模块因为天线没接好重新启动AT 命令就发早了导致后面的初始化全部错位。uint8_t EC20_ATDetect(void) { uint8_t retry 0; while (retry 20) { if (EC20_SendCmd(AT\r\n, OK, 200) EC20_OK) { return EC20_OK; } HAL_Delay(500); } return EC20_ERR; }逻辑说明EC20_SendCmd 是工程里的核心 AT 发送函数参数包括指令、期望返回的字符串和超时时间。这个函数的基本工作是发送字符串然后在 DMA 接收缓冲区里查找有没有出现期望的返回关键字。循环 20 次是给模块留了 10 秒以上的时间来完成启动。对于实际联网超时要注意 EC20_SendCmd 的第三个参数不是整套协议的 timeout而是等待返回关键字的 timeout如果模块信号很差ATQIOPEN 的应答超过 2 秒才返回就需要把 timeout 加大。4.2 注网、激活 PDP 与建立 TCP 连接模块启动后就要依次走完注册、激活、建连这一步是联网的主流程。代码里一般会封装成一个 EC20_NetOpen 函数我把关键步骤拆开看uint8_t EC20_NetOpen(const char *apn, const char *ip, uint16_t port) { EC20_SendCmd(ATE0\r\n, OK, 1000); EC20_SendCmd(ATCPIN?\r\n, READY, 3000); EC20_SendCmd(ATCREG?\r\n, 0,1, 1000); // 期望第二位是 1 char cmd[128]; snprintf(cmd, sizeof(cmd), ATCGDCONT1,\IP\,\%s\\r\n, apn); EC20_SendCmd(cmd, OK, 2000); EC20_SendCmd(ATCGACT1,1\r\n, OK, 5000); snprintf(cmd, sizeof(cmd), ATQIOPEN1,\TCP\,\%s\,%d,20000,0\r\n, ip, port); EC20_SendCmd(cmd, QIOPEN: 1,0, 5000); return EC20_OK; }逻辑说明ATE0 把回显关掉减少串口上的干扰帧。ATCPIN? 等返回 READY 才继续。ATCREG? 里的期望返回值 0,1 表示已注册本地 4G 网络如果 SIM 卡是漫游状态这里要改成 0,5。ATCGDCONT 配置 APN 时1 表示上下文 ID后面建立连接也要用同一个 ID。ATCGACT1,1 激活上下文模块会获得 IP 地址。最后 ATQIOPEN 携带远端 IP 和端口发起 TCP 连接本地端口设置为 20000这个数值可以随意调整只要不和其他连接冲突即可。参数说明QIOPEN 最后一个参数 0 表示非缓冲模式1 表示缓冲模式。缓冲模式下网络数据不会立刻从串口出来而是先存在模块内部缓存等到缓存达到一定阈值才输出非缓冲模式是立刻透传。这里我们需要透传所以用 0。连接模式要和后面的 QIMODE 配合QIMODE1 本身也要求连接处于非缓冲模式下。实际调试时ATCREG? 这一句最容易耗时如果每次都等 3 秒初始化时间会拉得很长。更好的做法是轮询例如每 500ms 查一次最多等 20 秒。已注册/漫游的状态是在变化阶段的中间态模块可能先返回 0,2正在注册之后才是 0,1 或 0,5。所以不要以为一次返回 OK 就代表注册完成一定要等写进期望的 0,1 再往下走。4.3 进入透传与退出透传 的时序细节TCP 连接建立后调用 ATQIMODE1 进入透传模式。注意这个指令必须在 QIOPEN 成功之后发送。如果连接还没建好就发模块会直接返回 ERROR。uint8_t EC20_EnterTransparent(void) { return EC20_SendCmd(ATQIMODE1\r\n, OK, 2000); }进入透传模式后模块返回的 OK 是透传模式已开启的标记之后串口上再有数据就会被视为业务数据。这时候主循环里就不能再调用 EC20_SendCmd 去发 AT 指令了否则那些指令文本会被当成 TCP 数据发给服务器。退出透传用的是 序列这个退出序列有严格的时序要求进入透传后模块会监测串口数据中出现的连续三个加号字符但前后要有一段静默时间。如果直接发 模块可能会把它当成普通数据送出去。通常做法是保持 1 秒静默然后发送三个连续的加号字符再等待 1 秒静默。退回到 AT 模式后之前建立的 TCP 连接仍然存在不会因为退出透传而断链。这个设计给业务上带来了灵活性比如可以在运行中临时退出透传查询信号再重新进入透传继续传数据。void EC20_ExitTransparent(void) { HAL_Delay(1000); EC20_UartSendString(); // 注意不要带 \r\n HAL_Delay(1000); }逻辑说明这段代码中 EC20_UartSendString 是直接串口发送字节流不做 AT 返回区分因为此时模块处于透传模式不会有 OK 回应。发完 后等待 1 秒之后模块应该回到 AT 指令模式可以用 AT 或 ATE0 再次验证。那种在透传模式下误发 导致数据丢失的问题多半是发送时带了回车换行或者前后静默时间不够。谨记 三个字符是连发中间不能有停顿否则会触发模块每个加号的超时判定。4.4 主循环里的双向转发进入透传后STM32F4 的主要任务就是转发。上行方向把来自传感器的数据从 MCU 的串口写入 EC20 的串口下行方向把 EC20 网络侧收到的数据从模块串口转发到业务处理函数。这段逻辑在主循环里实现while (1) { if (sensor_new_data_ready) { EC20_UartSendString((char *)sensor_buf, sensor_len); sensor_new_data_ready 0; } // EC20 下行数据已经在 DMA 空闲中断回调里处理 EC20_ProcessRxData(ec20_rx_buf, ec20_rx_len); HAL_Delay(10); }逻辑说明主循环承担的是推的动作把业务层要发送的数据推给模块串口。下行数据则采用中断方式不需要在主循环里轮询。这里要注意 HAL_Delay(10) 会让主循环以 100Hz 的频率运行如果传感器数据产生频率很高需要放在中断里而不是靠主循环轮询传感器标志位否则会有数据延迟。双向转发代码还有一个关键点是缓冲区管理。如果 MCU 往模块串口写入的速度太快模块 UDP 缓冲区可能直接丢弃后续数据或者服务器侧收包出现乱序。最常见的做法是每次发送前判断模块串口是否还有数据未发送完成使用 HAL_UART_GetState 查询发送状态只有当状态为就绪时才写入下一包。咱们这份工程里 EC20_UartSendString 封装的底层就是带状态检查的。5. 避坑与排查五个实录问题5.1 现象模块不回 AT 指令的 OK或者返回乱码原因最典型的是串口波特率不匹配或者模块的开机时序没有走完就发指令。我自己遇到过一块底板上有跳帽控制电平结果默认接成 3.3V 的模块 TX读回来的数据完全是乱的。解决先看硬件用示波器抓模块串口 TX确认上电后确实有 1.8V 电平的信号。然后检查 STM32F4 的串口外设时钟和波特率是否与模块一致。工程里默认 115200如果你用的模块固件版本或者底板有不同波特率必须先在 AT 测试软件里确定再改代码里的初始化结构体。最后把 EC20_PowerOn 的延时从 3 秒加大到 5 秒避免模块缓慢开机的场合探测失败。5.2 现象ATCREG? 一直返回 0,2 或者 0,3原因0,2 表示正在注册网络中0,3 表示被拒绝。如果一直卡在 2 后面或者直接 3SIM 卡片和天线大概率有问题网络侧没有注册上。解决先用手机或者其他模块确认同一张 SIM 卡在同一地点能上网。如果是专用物联网卡APN 可能不是公共 CMNET这样模块注册会失败。我遇到过一次工厂里信号被屏蔽CREG 一直返回 0,2最后换到窗边就注册成功了。所以不要把代码里 ATCREG? 的期望值当成测试万能的信号差的时候该加天线延长线还是要加。5.3 现象ATQIOPEN 返回错误码常见错误码包括 QIOPEN: 1,3 和 1,4各自表示 DNS 解析失败和连接失败。这个返回值的第二项不是 0就代表建连不成功。多数情况下是因为 APN 没配好导致模块拿不到公网 IP。也可能是服务器端口写错或者远端服务器防火墙禁止了这个端口。解决先检查 ATCGACT1,1 是否返回 OK再发命令 ATQIOPEN 前可以用 ATQIACT? 查看当前 PDP 上下文的状态确认模块已经获得 IP 地址。如果服务器是公网 IP直接用 ping 的方式确认网络可达性。调试时优先选择不加密的 TCP 端口避免 TLS 握手引入新的变量。5.4 现象透传模式下数据不定时丢失或者数据粘包原因这类问题本质上是串口收发的时序问题而不是模块本身丢数据。EC20 的串口 FIFO 有限如果 MCU 大批量写入而模块来不及发出去就会产生溢出丢弃。粘包则是接收端把几个包合并成了一个尤其是在 DMA 空闲中断触发的判断里如果服务器连续下发两个小数据包中间间隔小于一个字节空闲中断就分不开了。解决上行尽量采用等长的数据包并且每次发送后用 HAL_UART_GetState 查询模块串口状态。下行粘包需要通过应用层的帧解析来处理比如定义帧头帧尾和长度字段。这份工程在 app 层的 data_handler.c 里预留了帧解析函数你可以把长度字段统一成两字节收到数据后先解析长度不足就等下一帧再拼接。5.5 现象 退出透传后模块不再响应 AT 指令原因最典型的是 是在模块收到网络数据后立即发送的此时模块内部还在透传缓冲区中把 当成了业务数据。另外如果用了回车换行结束那模块就会把命令生成业务数据发送到服务器自然没法进入 AT 模式。解决进入透传后如果要退出必须先把自己的发送端静默至少 1 秒再裸发三个加号注意不是 \r\n保持静默 1 秒以上。这个静默时间最好用 HAL_Delay 控制不能放在主循环里等待因为主循环可能被业务任务卡住。之后再用 AT 指令探测如果 OK 返回说明已经退回来了。6. 验证与进阶服务器回环、心跳保活与缓冲模式写完了代码、避了坑接下来就是验证。第一次拿到这种透传工程建议不要直接在真实业务服务器上测也不要先用业务数据。先用一个回环测试把链路能力验证清楚。你可以在 PC 上开一个 TCP 服务端监听某个端口然后用 STM32F4 连接这个服务端。服务器不回复任何业务逻辑直接把收到的数据原样返回给模块。这样就能判断整个透传链路是不是真的双向都通。回环测试的基本步骤PC 上运行 TCP Server 工具监听端口如 8080修改 ec20_config.h 里的 IP 和端口指向本机公网 IP如果是局域网测试要用同一网段 IP进入透传模式后STM32F4 每 2 秒发送一次 16 字节数据服务器回显后查看 F4 能否再次收到相同数据这个测试能一次性验证串口收、串口发、网络数据上行透传、网络数据下行透传、接收中断、缓冲区是否够用。我见过很多项目上来就用业务数据测一失败就怀疑模块实际上大部分问题都在自己的缓冲区或流程上。链路通了以后要考虑保活。TCP 长连接在运营商侧如果空闲时间太长可能被 NAT 老化掉服务器端几个月不重连的僵尸连接也经常出现。工程只在应用层常发了心跳这类 4G 模块本身还有一个低功耗机制如果长时间无数据部分固件会自动进入 PSM 模式这会导致用户感知断链。为此需要做应用层心跳代码里默认每 30 秒会往服务器发送一个 4 字节的通用心跳帧这个值可以根据服务器侧的超时策略调整。需注意服务器端的心跳超时时间要大于模块心跳周期我这里倾向于配置为 60 秒保持余量。还有一个进阶选择值得提把 TCP 连接配置为缓冲模式。也就是在 ATQIOPEN 时把最后一个参数改成 1以及 QIMODE1 的透传模式改成 ATQIMODE0这样模块接收网络数据后不会立刻从串口输出而是先暂存在模块缓冲区里达到一定条件再一次性输出。它的好处是减少 MCU 频繁被中断打扰坏处是实时性下降。如果你在做的是批量上传而非交互式控制缓冲模式比纯透传更适用能把 F4 的 CPU 利用率让出一部分。模块固件升级这个动作也被很多人忽略。收到了新版本固件或者某个功能有问题时换模块固件往往比改代码都快。但升级工具需要在 AT 模式下操作不要在透传模式下升级升级过程中不能掉电。升级完后再检查一遍串口返回的版本号确认是新固件。最后说点我的个人习惯。从那次在透传模式下误发 导致数据丢失、又排查了一整天才意识到是时序问题之后我每次给项目换 EC20 底板或者升级固件都会强制自己走一遍这套流程先看电源和电平再用回环测链路再测心跳最后才接业务代码。这套顺序看起来慢实际上比出一次问题再排查便宜得多。希望这份工程笔记能帮你把 EC20 的 TCP 透传链路一次跑通少走点弯路。本文还有配套的精品资源点击获取
锦
锦皓数字建站
深耕本土企业品牌数字化升级,专注原创端正雅致商务官网,从视觉设计到稳定运维全程保驾护航。