一、硬件连接与核心原理
1.1 硬件连接表(以STM32F407的USART1为例)
| STM32F407 引脚 | EC20 模块引脚 | 说明 |
|---|---|---|
| PA9 (USART1_TX) | RXD | STM32发送 AT指令 / 透传数据 |
| PA10 (USART1_RX) | TXD | STM32接收 AT回复 / 透传数据 |
| PA8 (GPIO推挽输出) | PWRKEY | 开机/关机控制(低电平持续1.2s开机) |
| 3.3V | VCC | EC20供电(瞬态电流可能达2A,需大电容储能) |
| GND | GND | 共地 |
1.2 EC20 TCP透传核心流程
要实现串口数据的无缝透传,需按顺序执行以下步骤(状态机逻辑):
- 开机:拉低PWRKEY引脚约1.2秒。
- 驻网 :发送
AT握手,等待返回OK。 - 激活网络 :配置APN(如中国移动
CMNET),并激活PDP上下文。 - 建立TCP :发送
AT+QIOPEN建立TCP连接,收到CONNECT后进入透传模式。 - 数据交互:此后串口收到的任何数据都会被EC20直接通过TCP发出;EC20收到的TCP数据也会直接通过串口吐出。
二、代码实现(基于STM32标准外设库)
2.1 主程序与全局变量(main.c)
c
#include "stm32f4xx.h"
#include "stm32f4xx_usart.h"
#include "misc.h"
#include <string.h>
#include <stdio.h>
// ==================== 宏定义 ====================
#define EC20_USART USART1
#define EC20_USART_CLK RCC_APB2Periph_USART1
#define EC20_USART_TX_PIN GPIO_Pin_9
#define EC20_USART_RX_PIN GPIO_Pin_10
#define EC20_USART_GPIO_PORT GPIOA
#define EC20_USART_GPIO_CLK RCC_AHB1Periph_GPIOA
#define EC20_USART_IRQn USART1_IRQn
// PWRKEY控制引脚
#define EC20_PWRKEY_PIN GPIO_Pin_8
#define EC20_PWRKEY_PORT GPIOA
// 缓冲区大小
#define RX_BUFFER_SIZE 512
#define CMD_BUFFER_SIZE 128
// ==================== 全局变量 ====================
volatile uint8_t ec20_rx_buffer[RX_BUFFER_SIZE];
volatile uint16_t ec20_rx_counter = 0;
volatile uint8_t ec20_data_ready = 0; // 数据接收完成标志
// 系统状态枚举
typedef enum {
STATE_INIT = 0,
STATE_POWER_ON, // 开机
STATE_WAIT_NETWORK, // 等待驻网
STATE_CONFIG_APN, // 配置APN并激活
STATE_CREATE_TCP, // 建立TCP连接
STATE_TRANSPARENT, // 透传模式
STATE_ERROR // 错误重试
} SystemState;
SystemState current_state = STATE_INIT;
// ==================== 延时函数 ====================
void delay_ms(uint32_t ms) {
uint32_t i;
while (ms--) {
i = 84000; // 168MHz主频下的粗略延时
while (i--);
}
}
// ==================== GPIO初始化 ====================
void GPIO_Configuration(void) {
GPIO_InitTypeDef GPIO_InitStructure;
// 1. 使能GPIOA时钟
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA, ENABLE);
// 2. 配置USART1引脚 (PA9-TX, PA10-RX)
GPIO_PinAFConfig(GPIOA, GPIO_PinSource9, GPIO_AF_USART1);
GPIO_PinAFConfig(GPIOA, GPIO_PinSource10, GPIO_AF_USART1);
GPIO_InitStructure.GPIO_Pin = EC20_USART_TX_PIN | EC20_USART_RX_PIN;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP;
GPIO_Init(EC20_USART_GPIO_PORT, &GPIO_InitStructure);
// 3. 配置PWRKEY引脚 (PA8) - 推挽输出
GPIO_InitStructure.GPIO_Pin = EC20_PWRKEY_PIN;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_OUT;
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL;
GPIO_Init(EC20_PWRKEY_PORT, &GPIO_InitStructure);
// 默认PWRKEY为高电平
GPIO_SetBits(EC20_PWRKEY_PORT, EC20_PWRKEY_PIN);
}
// ==================== USART1初始化 ====================
void USART1_Configuration(uint32_t baudrate) {
USART_InitTypeDef USART_InitStructure;
NVIC_InitTypeDef NVIC_InitStructure;
// 1. 使能USART1时钟
RCC_APB2PeriphClockCmd(EC20_USART_CLK, ENABLE);
// 2. USART参数配置
USART_InitStructure.USART_BaudRate = baudrate;
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(EC20_USART, &USART_InitStructure);
// 3. 配置NVIC(嵌套向量中断控制器)
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
NVIC_InitStructure.NVIC_IRQChannel = EC20_USART_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);
// 4. 使能接收中断
USART_ITConfig(EC20_USART, USART_IT_RXNE, ENABLE);
// 5. 使能USART
USART_Cmd(EC20_USART, ENABLE);
}
// ==================== 发送字符串 ====================
void USART_SendString(USART_TypeDef* USARTx, char* str) {
while (*str) {
USART_SendData(USARTx, *str++);
while (USART_GetFlagStatus(USARTx, USART_FLAG_TXE) == RESET);
}
}
// ==================== 清理接收缓冲区 ====================
void Clear_RxBuffer(void) {
memset((void*)ec20_rx_buffer, 0, RX_BUFFER_SIZE);
ec20_rx_counter = 0;
ec20_data_ready = 0;
}
// ==================== 检查关键字 ====================
uint8_t Check_Response(char* keyword) {
if (strstr((const char*)ec20_rx_buffer, keyword) != NULL) {
return 1;
}
return 0;
}
// ==================== EC20模块开机 ====================
void EC20_PowerOn(void) {
GPIO_ResetBits(EC20_PWRKEY_PORT, EC20_PWRKEY_PIN); // 拉低PWRKEY
delay_ms(1500); // 保持低电平1.5秒(规格书要求>1.2s)
GPIO_SetBits(EC20_PWRKEY_PORT, EC20_PWRKEY_PIN); // 释放PWRKEY
}
// ==================== 主函数 ====================
int main(void) {
uint32_t state_timeout = 0;
uint32_t retry_count = 0;
// 底层初始化
GPIO_Configuration();
USART1_Configuration(115200); // EC20默认波特率通常为115200
printf("System Start... Initializing EC20 DTU\r\n");
while (1) {
switch (current_state) {
case STATE_INIT:
EC20_PowerOn();
current_state = STATE_POWER_ON;
state_timeout = 0;
break;
case STATE_POWER_ON:
state_timeout++;
delay_ms(1000);
// 等待系统启动(通常需5-10秒)
if (state_timeout > 15) { // 15秒超时
printf("EC20 Power On Timeout, Rebooting...\r\n");
current_state = STATE_ERROR;
}
// 发送AT指令测试
Clear_RxBuffer();
USART_SendString(EC20_USART, "AT\r\n");
// 等待回复
for (int i = 0; i < 10; i++) {
if (ec20_data_ready) break;
delay_ms(500);
}
if (Check_Response("OK")) {
printf("EC20 Boot Success!\r\n");
current_state = STATE_WAIT_NETWORK;
state_timeout = 0;
} else {
printf("Waiting for EC20 response...\r\n");
}
Clear_RxBuffer();
break;
case STATE_WAIT_NETWORK:
// 检查网络注册状态 AT+CREG?
Clear_RxBuffer();
USART_SendString(EC20_USART, "AT+CREG?\r\n");
delay_ms(1000);
if (Check_Response(",1") || Check_Response(",5")) { // 1=本地注册, 5=漫游注册
printf("Network Registered!\r\n");
current_state = STATE_CONFIG_APN;
state_timeout = 0;
} else {
printf("Waiting for network...\r\n");
}
Clear_RxBuffer();
delay_ms(2000);
break;
case STATE_CONFIG_APN:
// 1. 配置APN (以中国移动为例)
Clear_RxBuffer();
USART_SendString(EC20_USART, "AT+QICSGP=1,1,\"CMNET\",\"\",\"\",1\r\n");
delay_ms(1000);
if (!Check_Response("OK")) {
printf("APN Config Failed\r\n");
break;
}
printf("APN Config OK\r\n");
// 2. 激活PDP上下文
Clear_RxBuffer();
USART_SendString(EC20_USART, "AT+QIACT=1\r\n");
delay_ms(3000); // 激活可能需要较长时间
if (Check_Response("OK")) {
printf("PDP Context Activated!\r\n");
current_state = STATE_CREATE_TCP;
state_timeout = 0;
} else {
printf("PDP Activation Failed, Retrying...\r\n");
}
Clear_RxBuffer();
break;
case STATE_CREATE_TCP:
// 建立TCP连接
// 格式: AT+QIOPEN=<contextID>,<connectID>,"TCP","<server_ip>",<port>,<access_mode>
// access_mode=2 表示透明传输模式
Clear_RxBuffer();
// 请在此处替换为您的服务器IP和端口
USART_SendString(EC20_USART, "AT+QIOPEN=1,0,\"TCP\",\"192.168.1.100\",8080,2\r\n");
printf("Connecting to Server...\r\n");
// 等待 CONNECT 回复(进入透传模式)
uint32_t connect_timeout = 0;
while (1) {
if (ec20_data_ready) {
if (Check_Response("CONNECT")) {
printf("TCP Connected! Entering Transparent Mode...\r\n");
current_state = STATE_TRANSPARENT;
retry_count = 0;
break;
}
if (Check_Response("+QIOPEN:")) {
printf("TCP Connection Failed, Retrying...\r\n");
delay_ms(5000);
current_state = STATE_CREATE_TCP;
break;
}
}
delay_ms(1000);
if (++connect_timeout > 15) { // 15秒连接超时
printf("TCP Connect Timeout\r\n");
current_state = STATE_ERROR;
break;
}
}
Clear_RxBuffer();
break;
case STATE_TRANSPARENT:
// 透传模式下,EC20会自动转发串口数据
// 此处仅做链路保活和断线重连检测
// 检测是否收到EC20主动上报的断连消息
if (ec20_data_ready) {
if (strstr((const char*)ec20_rx_buffer, "+QIURC: \"closed\"") != NULL) {
printf("TCP Link Closed by Remote!\r\n");
current_state = STATE_CREATE_TCP; // 尝试重连
Clear_RxBuffer();
break;
}
// 如果有其他数据(即服务器下发的数据),可以在这里处理
// 例如:通过另一个USART打印出来
// printf("Server Data: %s", ec20_rx_buffer);
Clear_RxBuffer();
}
delay_ms(1000); // 每秒检测一次
break;
case STATE_ERROR:
retry_count++;
printf("Error State, Retry Count: %lu\r\n", retry_count);
if (retry_count > 5) {
printf("Too many errors, Rebooting EC20...\r\n");
EC20_PowerOn(); // 强制重启模块
retry_count = 0;
}
delay_ms(5000);
current_state = STATE_INIT;
break;
}
}
}
// ==================== USART1中断服务函数 ====================
void USART1_IRQHandler(void) {
uint8_t temp;
if (USART_GetITStatus(EC20_USART, USART_IT_RXNE) != RESET) {
temp = USART_ReceiveData(EC20_USART);
if (ec20_rx_counter < RX_BUFFER_SIZE - 1) {
ec20_rx_buffer[ec20_rx_counter++] = temp;
// 检测到换行符或缓冲区即将满,认为一帧数据结束
if (temp == '\n' || ec20_rx_counter >= RX_BUFFER_SIZE - 2) {
ec20_rx_buffer[ec20_rx_counter] = '\0';
ec20_data_ready = 1;
}
} else {
ec20_rx_counter = 0; // 溢出保护
}
USART_ClearITPendingBit(EC20_USART, USART_IT_RXNE);
}
}
参考代码 STM32F407 开发EC20代码 www.youwenfan.com/contentcsu/56211.html
三、使用指南与避坑要点
3.1 关键代码修改点
- APN配置 :代码默认使用中国移动
CMNET。如果是联通卡,请修改为UNINET;电信卡修改为CTNET。 - 服务器地址 :在
STATE_CREATE_TCP分支中,将"192.168.1.100"和8080修改为您实际的服务端公网IP和端口。
3.2 避坑
- 供电是成败关键 :EC20在4G搜网和发射瞬间,电流会从几十毫安突增到 1.5A~2A 。务必保证电源能提供足够大的电流余量,建议在EC20的VCC和GND引脚间并联 1000uF以上的大电解电容 及 0.1uF陶瓷电容 进行退耦,否则极易导致模块反复重启。
- PWRKEY时序:代码严格遵循了拉低1.2秒以上的开机时序。如果自制底板,建议串联一个1k电阻以防短路。
3.3 透传模式说明
代码成功运行后会进入 STATE_TRANSPARENT(透传模式)。此时,您可以通过 USB转TTL工具 连接STM32的USART1(或代码指定的串口),直接向STM32发送任意字符串(如 Hello Server),STM32会将数据原封不动通过串口发给EC20,EC20再通过TCP链路发给服务器;反之亦然。