STM32通过AT指令驱动A7680C 4G模块实现稳定TCP通信实战 简介本资源是一套面向嵌入式物联网开发者的STM32F103单片机实战例程聚焦于通过串口2驱动A7680C-4G模块完成TCP协议下的稳定数据收发适用于初学者快速入门及项目原型验证。压缩包共81个文件含35个头文件.h定义外设与通信接口、34个源码文件.c实现底层驱动、中断处理、AT指令解析与TCP连接逻辑另有启动文件.s、工程配置.uvprojx/.uvoptx、调试配置.dbgconf、Hex固件及辅助脚本.bat/.url总大小452KB结构清晰、模块化程度高。已有173人学习下载代码基于KEIL标准库开发注释详尽接线定义明确支持STM32F103全系列芯片适配仅需调整KEIL中芯片型号与Flash容量。读者可直接复用核心通信框架结合自身硬件微调串口参数与AT指令序列并参考同账号发布的传感器接入资料拓展应用。1. 项目背景与核心价值最近在做一个物联网终端设备核心需求是把STM32采集到的传感器数据稳定地发送到远端的云服务器。选型阶段Wi-Fi、NB-IoT和4G Cat.1模块都考虑过。Wi-Fi依赖热点部署不灵活NB-IoT虽然功耗低但带宽和实时性有限最终选择了4G Cat.1模块它在速率、功耗和网络覆盖上取得了不错的平衡。A7680C这款模块以其高性价比和稳定的TCP/IP协议栈成为了很多嵌入式开发者的首选。这个项目就是基于经典的STM32F103C8T6“蓝桥杯”核心板通过其串口2USART2与A7680C-4G模块通信实现一个完整的、可复用的TCP客户端数据收发例程。网上资料虽然多但要么过于简略只给AT指令要么代码耦合度高难以移植。我打算从硬件连接、AT指令交互逻辑、状态机设计到数据收发缓冲处理完整地走一遍把其中容易踩的坑和提升稳定性的技巧都分享出来。无论你是正在做毕业设计的学生还是需要快速为产品添加4G联网功能的工程师这份经过实测的代码和思路都能让你少走弯路。2. 硬件平台搭建与核心引脚分配动手写代码之前硬件连接是第一步也是最容易出错的一步。这里的选择直接影响后续软件驱动的编写和调试的便利性。2.1 MCU与4G模块的串口连接STM32F103C8T6有多个串口我们选择USART2因为它对应的引脚PA2(TX)和PA3(RX)在核心板上通常直接引出使用方便。A7680C模块则通过其主串口通常是UART1与MCU通信。注意务必确认你手上的A7680C模块的串口电平绝大多数A7680C模块的串口是3.3V TTL电平这与STM32F103的IO口电平完美兼容可以直接连接。如果你的模块是其他电平如1.8V则需要电平转换电路。连接关系非常简单但必须遵循“交叉互联”原则STM32的PA2 (USART2_TX)连接A7680C的RXD模块接收端。STM32的PA3 (USART2_RX)连接A7680C的TXD模块发送端。两边的GND必须连接在一起这是通信的基准。2.2 电源与关键控制引脚除了串口还有几个引脚对模块的稳定工作至关重要VCC供电A7680C模块的峰值电流可能达到2A因此必须为其提供独立、稳定、电流充足的5V电源。绝对禁止直接从STM32开发板的3.3V或5V引脚取电这会导致MCU复位甚至损坏。建议使用外部的DC-DC电源模块或性能优秀的LDO如AMS1117-5.0单独供电。PWR_KEY引脚模块的电源键。通常需要拉低至少1秒然后释放来开机拉低至少6秒然后释放来关机。我们可以用一个STM32的GPIO如PA4通过一个三极管或MOS管来控制实现软件开关机。RESET引脚模块复位引脚低电平有效。可以连接到STM32的另一个GPIO用于在模块死机时进行硬件复位。STATUS或NETLIGHT引脚模块状态指示灯引脚。连接到STM32的GPIO并配置为输入可以用于判断模块的工作状态如开机完成、网络注册成功等比单纯依赖AT指令查询更实时可靠。在我的实际连接中为了简化初期调试我暂时只连接了串口和电源PWR_KEY通过跳线帽手动控制。等通信逻辑调通后再把这些控制引脚接入程序。2.3 调试串口的必要性强烈建议保留STM32的USART1PA9/PA10作为调试打印串口连接一个USB转TTL工具到电脑。这样我们可以通过printf函数实时输出程序状态、AT指令交互过程、接收到的数据等这是排查问题的“眼睛”。没有调试信息定位问题如同盲人摸象。3. 软件架构设计与AT指令交互状态机直接在主循环里用HAL_UART_Receive和HAL_Delay轮询发送AT指令是最初级的做法代码会变得臃肿且难以维护无法处理超时、重试等复杂情况。一个健壮的驱动需要状态机Finite State Machine, FSM来管理。3.1 分层软件架构我将软件分为三层降低耦合度方便移植和测试硬件抽象层HAL基于STM32CubeMX/HAL库封装USART2的初始化、中断收发、DMA收发等底层操作。这一层只关心如何收/发字节流。AT指令驱动层这是核心。它维护一个AT指令状态机负责组帧添加\r\n、发送指令、等待响应、解析响应、处理超时和重试。它向上提供诸如AT_SendCommandAndWaitForResp、AT_TCP_Connect等接口。应用业务层调用驱动层接口实现具体的业务逻辑例如上电后自动初始化模块、连接指定服务器、定时发送数据、处理服务器下发的指令等。3.2 AT指令状态机实现细节状态机是驱动层的灵魂。我设计的状态机包含以下几个核心状态IDLE空闲状态等待应用层下发新命令。SENDING正在通过串口发送AT指令字符串。WAITING_RESP指令已发送正在等待模块回复。这里会启动一个硬件定时器作为超时计时器。RECEIVING正在接收模块的回复数据在串口中断中拼接字符串。RESP_OK成功收到OK或预期的成功响应。RESP_ERROR收到ERROR或CME ERROR等失败响应。TIMEOUT等待响应超时。状态机的运转依赖于两个核心机制串口空闲中断Idle Interrupt和超时定时器。串口空闲中断这是高效接收不定长数据的关键。配置USART2使能空闲中断。当模块返回数据后总线会持续保持高电平空闲状态超过一个字节的时间此时会触发空闲中断。在中断服务函数中我们可以认为“一帧”数据已经接收完毕然后将其拷贝到缓冲区并进行解析。这比判断\r\n或固定长度要可靠得多。超时定时器我使用STM32的一个基本定时器如TIM6在进入WAITING_RESP状态时启动设定一个超时时间例如5秒。如果超时前未完成响应解析定时器中断会强制将状态机拉回IDLE并标记超时错误防止程序永远卡死。一个典型的“ATCPIN?”查询SIM卡状态的指令交互流程在状态机中的体现如下应用层调用AT_CheckSIM()状态机从IDLE进入SENDING串口发送ATCPIN?\r\n。发送完毕状态进入WAITING_RESP启动5秒超时定时器。模块返回CPIN: READY\r\n\r\nOK\r\n。串口接收中断持续填充接收缓冲数组。最后一帧数据接收完毕总线空闲触发空闲中断。在中断中状态进入RECEIVING并关闭超时定时器。在RECEIVING状态的处理函数中解析缓冲区字符串发现包含READY和OK状态迁移至RESP_OK并调用应用层注册的回调函数通知SIM卡正常。应用层回调函数执行完毕状态机复位回IDLE等待下一个指令。4. TCP连接建立与数据收发实战当模块成功注册到4G网络ATCREG?返回1,1或5,1后就可以进行TCP通信了。4.1 单连接与多连接模式选择A7680C支持单连接和多连接模式。对于大多数物联网数据上报场景单连接ATCIPMUX0足够了逻辑简单。如果需要同时连接多个服务器则需使用多连接模式ATCIPMUX1并为每个连接分配一个ID。本例程我们采用单连接模式。建立TCP连接的AT指令序列是标准化的ATCIPSHUT// 关闭移动场景如果有残留连接ATCGATT1// 附着网络通常可以省略因为注册成功即已附着ATCIPSTARTTCP,server_ip,server_port// 发起TCP连接例如ATCIPSTARTTCP,183.230.40.40,1811等待返回CONNECT OK。这个过程可能需要几秒钟状态机需要耐心等待。连接成功后返回OK。4.2 数据发送的两种模式普通模式与透传模式这是最容易混淆的地方也直接决定了你代码的写法。普通模式非透传模式发送数据前需要先指定长度ATCIPSENDlength。模块回复提示符后再发送实际数据。发送完成后模块会返回SEND OK。优点可控性强每包数据独立适合发送不定时、不定长的数据包。缺点每次发送都有AT指令开销效率稍低。透传模式使用指令ATCIPMODE1进入透传模式然后使用ATCIPSEND触发进入。进入后串口发送的所有数据除了都会被直接作为TCP数据发送到服务器无需再输入ATCIPSEND指令。发送注意不要带回车且前后需要至少1秒的静默时间可以退出透传模式。优点效率极高延迟低适合流式数据或频繁发送小包的场景。缺点模块和MCU的串口被“占用”无法在此期间发送其他AT指令如查询信号强度。数据边界需要应用层自己定义如添加帧头帧尾。我的选择与理由对于物联网传感器数据上报通常是周期性的比如每10秒上报一次数据包长度固定或变化不大。我选择了普通模式。原因有三第一我的业务不需要极低的延迟第二普通模式下我可以在每次发送间隙插入ATCSQ查询信号质量便于设备监控第三逻辑更清晰数据包天然有边界不容易出现粘包问题虽然TCP本身是流式协议但模块以包为单位处理减轻了应用层压力。4.3 数据接收处理与缓冲区管理服务器下发的数据模块会通过串口主动上报格式为IPD,len:data。我们需要在串口接收中断或空闲中断的处理函数中实时解析这个前缀。这里有一个关键坑点IPD数据可能非常长超过一次串口空闲中断接收的缓冲区长度。因此我们的解析器必须具备“拼接”能力。我的做法是在串口空闲中断中将收到的原始字节流存入一个大的环形缓冲区Ring Buffer。在主循环中定期或在一个低优先级任务中检查环形缓冲区。实现一个解析函数从环形缓冲区头部开始查找IPD,字符串。找到后解析出长度len然后检查缓冲区中是否已有足够len字节的数据。如果数据足够则提取出完整的一包data交给应用层处理并从环形缓冲区中移除这些数据。如果数据不足分包了则等待下一次数据到来继续拼接。使用环形缓冲区可以有效防止数据覆盖是嵌入式网络通信中的标准做法。STM32的HAL库本身不提供环形缓冲区需要自己实现或使用成熟的库如circular_buffer。5. 代码实现详解与稳定性优化基于以上设计我们来看关键代码片段。完整工程基于STM32CubeIDE创建。5.1 USART2与DMA配置我使用DMA来发送AT指令使用“串口接收中断空闲中断”来接收数据。这样能最大程度释放CPU。在CubeMX中使能USART2的异步模式Asynchronous。使能USART2的全局中断。在NVIC Settings中使能USART2的中断。在DMA Settings中为USART2_TX添加一个DMA流Memory to Peripheral模式为Normal。在Parameter Settings中打开“串口空闲中断”在代码中手动调用__HAL_UART_ENABLE_IT(huart2, UART_IT_IDLE);。// usart.c 中USART2初始化部分补充 __HAL_UART_ENABLE_IT(huart2, UART_IT_IDLE); // 定义接收缓冲区和环形缓冲区 uint8_t uart2_rx_buf[256]; // 临时DMA接收缓冲区 RingBuffer_t at_rb; // 环形缓冲区结构体 uint8_t rb_buffer[1024]; // 环形缓冲区内存 // 启动串口DMA接收 HAL_UART_Receive_DMA(huart2, uart2_rx_buf, sizeof(uart2_rx_buf));5.2 串口空闲中断处理在stm32f1xx_it.c的USART2_IRQHandler函数中处理空闲中断。void USART2_IRQHandler(void) { /* USER CODE BEGIN USART2_IRQn 0 */ // 判断是否是空闲中断 if(__HAL_UART_GET_FLAG(huart2, UART_FLAG_IDLE) ! RESET) { __HAL_UART_CLEAR_IDLEFLAG(huart2); // 清除空闲中断标志 // 计算本次接收到的数据长度 uint16_t rx_len sizeof(uart2_rx_buf) - __HAL_DMA_GET_COUNTER(huart2.hdmarx); if(rx_len 0) { // 将数据存入环形缓冲区 RingBuffer_Write(at_rb, uart2_rx_buf, rx_len); // 可以设置一个标志通知主循环有数据需要处理 at_data_received_flag 1; } // 重新启动DMA接收防止溢出 HAL_UART_Receive_DMA(huart2, uart2_rx_buf, sizeof(uart2_rx_buf)); } /* USER CODE END USART2_IRQn 0 */ HAL_UART_IRQHandler(huart2); /* USER CODE BEGIN USART2_IRQn 1 */ /* USER CODE END USART2_IRQn 1 */ }5.3 AT指令状态机核心代码这是一个简化的状态机处理函数在主循环中被调用。typedef enum { AT_STATE_IDLE, AT_STATE_SENDING, AT_STATE_WAITING_RESP, AT_STATE_RECEIVING, AT_STATE_RESP_OK, AT_STATE_RESP_ERROR, AT_STATE_TIMEOUT } AT_State_t; AT_State_t at_state AT_STATE_IDLE; char at_resp_buffer[512]; uint32_t at_cmd_timeout 0; void AT_StateMachine_Process(void) { switch(at_state) { case AT_STATE_IDLE: // 等待新命令 break; case AT_STATE_SENDING: // 指令已通过DMA发送切换到等待状态 at_state AT_STATE_WAITING_RESP; at_cmd_timeout HAL_GetTick() 5000; // 设置5秒超时点 break; case AT_STATE_WAITING_RESP: // 检查是否有数据收到标志 if(at_data_received_flag) { at_data_received_flag 0; at_state AT_STATE_RECEIVING; } // 检查超时 if(HAL_GetTick() at_cmd_timeout) { at_state AT_STATE_TIMEOUT; printf([AT] Command timeout!\r\n); } break; case AT_STATE_RECEIVING: // 从环形缓冲区读取所有数据到at_resp_buffer uint16_t len RingBuffer_Read(at_rb, (uint8_t*)at_resp_buffer, sizeof(at_resp_buffer)-1); at_resp_buffer[len] \0; // 确保字符串结束 // 解析响应 if(strstr(at_resp_buffer, OK) ! NULL) { at_state AT_STATE_RESP_OK; // 进一步解析是否有“IPD”或“CIPSTART”等具体信息 if(strstr(at_resp_buffer, IPD) ! NULL) { // 提取TCP数据交给应用层 TCP_Data_Handler(at_resp_buffer); } } else if(strstr(at_resp_buffer, ERROR) ! NULL) { at_state AT_STATE_RESP_ERROR; } else { // 可能是中间响应如“”或者“CONNECT OK”继续等待 at_state AT_STATE_WAITING_RESP; } break; case AT_STATE_RESP_OK: case AT_STATE_RESP_ERROR: case AT_STATE_TIMEOUT: // 最终状态通知应用层命令执行结果然后复位状态机 AT_Command_Callback(at_state); // 应用层回调 at_state AT_STATE_IDLE; break; } } // 发送AT指令的接口函数 AT_Status_t AT_SendCommand(const char *cmd, uint32_t timeout_ms) { if(at_state ! AT_STATE_IDLE) { return AT_BUSY; } // 将指令拷贝到发送缓冲区添加\r\n sprintf(at_tx_buffer, %s\r\n, cmd); // 启动DMA发送 HAL_UART_Transmit_DMA(huart2, (uint8_t*)at_tx_buffer, strlen(at_tx_buffer)); at_state AT_STATE_SENDING; return AT_OK; }5.4 TCP数据发送函数示例普通模式// 发送一包TCP数据 int32_t TCP_Send_Data(uint8_t *data, uint16_t len) { char cmd[64]; // 1. 发送长度指令 sprintf(cmd, ATCIPSEND%d, len); if(AT_SendCommandAndWaitForResp(cmd, , 2000) ! AT_RESP_OK) { return -1; // 发送失败 } // 2. 发送实际数据 HAL_UART_Transmit(huart2, data, len, 1000); // 这里用阻塞发送也可用DMA // 3. 等待SEND OK if(AT_WaitForResp(SEND OK, 5000) ! AT_RESP_OK) { return -2; // 发送确认失败 } return 0; // 成功 }6. 实测中的典型问题与深度排查即使代码逻辑正确在实际环境中仍会遇到各种问题。以下是几个我踩过的坑及解决方案。6.1 模块上电后无响应或响应异常现象连接好硬件发送AT指令模块不回OK。排查步骤查电源这是首要怀疑对象。用万用表测量模块VCC引脚电压在模块发射数据时观察电压是否被拉低低于4.0V就要警惕。务必确保电源能提供2A以上的峰值电流。查串口线确认TX/RX是否接反。可以用USB转TTL工具直接连接模块用串口助手发送AT测试排除MCU端问题。查波特率A7680C默认波特率通常是115200。但有些模块或固件可能不同。尝试9600, 19200, 38400, 57600, 115200等常用波特率。查启动时序有些模块上电后需要几秒钟才能稳定。发送AT指令前先延时2-3秒。查硬件流控如果模块和MCU的RTS/CTS引脚悬空或接错可能导致通信失败。如果不使用硬件流控确保在AT指令中用ATIFC0,0关闭它。6.2 TCP连接频繁断开或连接失败现象ATCIPSTART经常返回ERROR或连接成功后不久就断开。排查步骤查网络状态先执行ATCSQ查询信号强度。CSQ值应在10以上99表示未知或不可用值越大信号越好。ATCREG?应返回1,1已注册本地网络或5,1已注册漫游网络。查APN设置不同的SIM卡移动、联通、电信需要不同的APN。使用ATCGDCONT1,IP,你的APN进行设置。例如中国移动物联网卡通常是CMNET。查服务器与端口确认服务器IP和端口是否正确且服务器端的防火墙是否放行了该端口。可以在电脑上用网络调试助手创建一个TCP服务器进行本地测试。查SIM卡状态ATCPIN?返回READY吗SIM卡是否欠费是否开通了数据业务降低连接超时时间ATCIPSTART指令本身可以带一个连接超时参数但A7680C可能不支持。更可靠的做法是在状态机中设置一个较长的等待时间如30秒因为从拨号到获取IP再到建立TCP连接整个过程在信号弱时可能很长。6.3 数据发送成功但服务器收不到或收不全现象ATCIPSEND返回SEND OK但服务器端没有显示数据。排查步骤确认连接状态发送数据前先用ATCIPSTATUS查询连接状态是否为CONNECT OK。检查发送模式确认你是否在透传模式下错误地使用了普通模式指令或者反之。模式混乱是常见错误。检查数据长度在普通模式下ATCIPSEND指定的长度必须和后续发送的实际数据长度严格一致。如果指定长度大于实际发送长度模块会等待直到超时如果小于多余的数据会被丢弃或导致错乱。启用模块回显使用ATE1打开回显。这样你通过串口发送给模块的每一条指令和数据模块都会原样发回方便你在调试工具上确认MCU到底发送了什么。在服务器端抓包在服务器端使用Wireshark等工具抓包看TCP连接是否真的建立数据包是否到达服务器网卡。这是最终极的排查手段。6.4 内存泄漏与长时间运行稳定性嵌入式设备要求7x24小时稳定运行。一些不经意的代码会导致内存逐渐耗尽虽然STM32没有操作系统但缓冲区溢出同样致命。环形缓冲区溢出这是最大的风险点。必须确保RingBuffer_Write函数在缓冲区满时有正确的处理策略例如丢弃最旧数据或返回错误。在USART2_IRQHandler中写入环形缓冲区前可以检查剩余空间。AT指令响应缓冲区不足at_resp_buffer要足够大以容纳可能的长响应例如查询基站信息的响应可能很长。我遇到过ATCOPS?指令返回超长字符串导致缓冲区溢出程序跑飞的情况。状态机死锁确保每个状态在超时或错误时都能安全地回到IDLE状态。为状态机添加一个“看门狗”定时器如果在一个状态如WAITING_RESP停留时间过长比如30秒则强制复位状态机并尝试重启模块连接流程。定期复位模块即使程序无误4G模块本身也可能因网络波动或内部软件问题而死机。一个简单的提升稳定性的策略是在应用层监控TCP连接状态或定期Ping服务器。如果长时间例如10分钟无数据交互或Ping不通则通过PWR_KEY引脚执行模块的硬重启。虽然粗暴但有效。7. 进阶优化与项目扩展思路当基础功能跑通后可以考虑以下优化来提升项目的产品化程度。7.1 引入RTOS实现多任务管理目前的状态机是在主循环中轮询的。当业务复杂时比如需要同时处理多个传感器、用户按键、显示界面轮询会显得力不从心。引入FreeRTOS这样的实时操作系统可以将AT指令处理、TCP数据收发、业务逻辑分别放在不同的任务中。创建AT通信任务优先级较高专门处理与模块的串口通信和状态机。创建网络心跳任务优先级较低定期如每30秒发送心跳包或查询模块状态。创建数据上报任务由传感器数据就绪事件触发负责组包并调用TCP发送接口。创建命令处理任务解析服务器下发的指令并执行。任务间通过队列Queue传递数据如收到的TCP数据包和事件如“网络已连接”。这样结构更清晰响应更及时。7.2 实现掉线自动重连与断点续传物联网设备网络环境复杂断线重连是必备功能。心跳检测除了模块自带的TCP保活可设置ATCIPKEEP应用层也应定期如每60秒向服务器发送一个简单的心跳包。如果连续多次发送失败则认为连接已断。分级重连策略检测到断线后不要立即进行全套重连流程从重启模块开始。应先尝试ATCIPCLOSE关闭当前连接然后ATCIPSTART重新连接。如果多次失败再尝试ATCFUN1,1飞行模式开关刷新网络。最后一步才是硬件重启模块。这种“软重启”到“硬重启”的渐进策略能减少对模块硬件的损耗。数据缓存与续传在发送重要数据时可以先将其存入外部Flash如W25Q64或FRAM。发送成功后标记为已发送。如果发送过程中断线重连后可以从Flash中读取未成功的数据重新发送。这需要设计一个简单的轻量级队列管理系统。7.3 集成MQTT协议提升云平台兼容性直接使用TCP裸套接字与私有服务器通信是灵活的但与主流物联网云平台如阿里云、腾讯云、AWS IoT对接时使用MQTT协议是更标准、更高效的选择。A7680C的后期固件通常支持内置的MQTT客户端功能可以通过AT指令ATMQTTCONN,ATMQTTPUB等直接操作。这省去了在STM32上移植MQTT客户端库如Paho MQTT的麻烦节省了宝贵的ROM和RAM资源。迁移到MQTT的主要工作是理解MQTT的连接参数ClientID, Username, Password, Clean Session。将数据封装成MQTT的PUBLISH报文发布到特定的Topic。订阅Subscribe服务器下发的指令Topic。使用内置MQTT功能后网络断线重连、QoS质量等级等复杂问题都由模块内部处理了稳定性会进一步提升。整个项目从硬件选型、连接到软件状态机设计、坑点排查再到进阶优化是一个典型的嵌入式网络产品开发流程。代码的模块化设计使得它很容易移植到其他STM32系列甚至其他品牌的MCU上。最关键的是理解AT指令交互的本质和状态机思维这是驾驭各类通信模块的通用技能。在实际部署中别忘了做好静电防护和电源滤波这些硬件上的细节往往是野外设备长期稳定运行的最终保障。本文还有配套的精品资源点击获取