STM32F407 + EC20 串口透传 TCP DTU 实现方案

一、硬件连接与核心原理

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透传核心流程

要实现串口数据的无缝透传,需按顺序执行以下步骤(状态机逻辑):

  1. 开机:拉低PWRKEY引脚约1.2秒。
  2. 驻网 :发送 AT 握手,等待返回 OK
  3. 激活网络 :配置APN(如中国移动 CMNET),并激活PDP上下文。
  4. 建立TCP :发送 AT+QIOPEN 建立TCP连接,收到 CONNECT 后进入透传模式。
  5. 数据交互:此后串口收到的任何数据都会被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 关键代码修改点

  1. APN配置 :代码默认使用中国移动 CMNET。如果是联通卡,请修改为 UNINET;电信卡修改为 CTNET
  2. 服务器地址 :在 STATE_CREATE_TCP 分支中,将 "192.168.1.100"8080 修改为您实际的服务端公网IP和端口。

3.2 避坑

  1. 供电是成败关键 :EC20在4G搜网和发射瞬间,电流会从几十毫安突增到 1.5A~2A 。务必保证电源能提供足够大的电流余量,建议在EC20的VCC和GND引脚间并联 1000uF以上的大电解电容0.1uF陶瓷电容 进行退耦,否则极易导致模块反复重启。
  2. PWRKEY时序:代码严格遵循了拉低1.2秒以上的开机时序。如果自制底板,建议串联一个1k电阻以防短路。

3.3 透传模式说明

代码成功运行后会进入 STATE_TRANSPARENT(透传模式)。此时,您可以通过 USB转TTL工具 连接STM32的USART1(或代码指定的串口),直接向STM32发送任意字符串(如 Hello Server),STM32会将数据原封不动通过串口发给EC20,EC20再通过TCP链路发给服务器;反之亦然。

相关推荐
pengyi8710154 小时前
共享 IP 池冲突根源与基础分配原则
网络·爬虫·网络协议·tcp/ip·智能路由器
Championship.23.244 小时前
2026年AI辅助STM32 IoT实战:从串口到云平台全指南
人工智能·stm32·物联网
Deitymoon4 小时前
STM32——按键控制led灯
stm32·单片机·嵌入式硬件
胡图图不糊涂^_^4 小时前
网络原理笔记
java·网络·笔记·学习·tcp/ip·http·https
三品吉他手会点灯4 小时前
STM32 VSCode 开发-与Keil MDK协同开发环境搭建
笔记·vscode·stm32·单片机·嵌入式硬件
三佛科技-187366133974 小时前
FT32F103VEAT7兼容STM32F103VETx/APM32F103VET6,单片机替代分析
单片机·嵌入式硬件
程序员黄老师4 小时前
一分钟了解ARM发展史与全系列产品
arm开发·嵌入式硬件·arm
想唱rap4 小时前
TCP套接字编程
java·linux·网络·c++·tcp/ip·mysql·ubuntu
風清掦5 小时前
【江科大STM32学习笔记-11】SPI通信协议 - 11.2 硬件SPI读写W25Q64
笔记·stm32·单片机·嵌入式硬件·学习