文章目录
-
- 一、前言
-
- [1.1 技术背景](#1.1 技术背景)
- [1.2 本文目标](#1.2 本文目标)
- [1.3 适合读者](#1.3 适合读者)
- 二、环境准备
-
- [2.1 硬件清单](#2.1 硬件清单)
- [2.2 EC20模块引脚说明](#2.2 EC20模块引脚说明)
- [2.3 开发环境搭建](#2.3 开发环境搭建)
- 三、核心实现
-
- [3.1 项目结构规划](#3.1 项目结构规划)
- [3.2 环形缓冲区实现](#3.2 环形缓冲区实现)
- [3.3 EC20驱动实现](#3.3 EC20驱动实现)
- [3.4 HTTP客户端实现](#3.4 HTTP客户端实现)
- [3.5 主程序](#3.5 主程序)
- 四、系统架构
- 五、测试验证
-
- [5.1 硬件连接](#5.1 硬件连接)
- [5.2 测试输出示例](#5.2 测试输出示例)
- 六、故障排查
-
- [6.1 模块无法启动](#6.1 模块无法启动)
- [6.2 SIM卡错误](#6.2 SIM卡错误)
- [6.3 网络注册失败](#6.3 网络注册失败)
- 七、总结
-
- [7.1 核心知识点回顾](#7.1 核心知识点回顾)
- [7.2 扩展方向](#7.2 扩展方向)
- [7.3 学习资源](#7.3 学习资源)
一、前言
1.1 技术背景
随着物联网技术的快速发展,越来越多的设备需要接入互联网进行数据交互。传统的WiFi和以太网虽然成熟,但在移动场景和偏远地区存在覆盖限制。4G LTE技术凭借其广覆盖 、高带宽 、低延迟的优势,成为物联网设备远程通信的首选方案。
Quectel EC20是一款高性能的LTE Cat-4模块,支持全球主流频段,提供高达150Mbps的下载速度和50Mbps的上传速度。它内置TCP/IP协议栈,支持HTTP/HTTPS、MQTT、FTP等多种协议,广泛应用于车载、安防、工业控制等领域。
1.2 本文目标
通过本教程,你将学会:
- 理解4G通信原理和EC20模块架构
- 掌握STM32与EC20的UART通信配置
- 使用AT指令控制EC20模块
- 实现HTTP GET/POST请求与云端交互
- 构建稳定的远程数据传输系统
技术栈:
- 硬件平台:STM32F103C8T6(Blue Pill)
- 4G模块:Quectel EC20 R2.1
- 开发环境:STM32CubeIDE
- 通信接口:UART + AT指令
- 网络协议:HTTP/HTTPS
- 调试工具:ST-Link V2、串口助手
1.3 适合读者
本教程适合初级实践级读者,假设你已具备:
- 基础C语言编程能力
- STM32 UART外设的基本了解
- 简单的HTTP协议概念
二、环境准备
2.1 硬件清单
| 组件 | 型号/规格 | 数量 | 说明 |
|---|---|---|---|
| 主控芯片 | STM32F103C8T6 | 1片 | 主控制器 |
| 4G模块 | Quectel EC20 R2.1 | 1个 | LTE Cat-4模块 |
| SIM卡 | 物联网卡/普通SIM | 1张 | 需开通数据流量 |
| USB转TTL | CH340/CP2102 | 1个 | 串口调试 |
| ST-Link | V2 | 1个 | 程序下载调试 |
| 杜邦线 | 母对母/公对母 | 若干 | 连接使用 |
| 天线 | 4G全频段天线 | 1根 | 模块配套 |
| 电源 | 5V/2A | 1个 | EC20峰值电流2A |
2.2 EC20模块引脚说明
EC20 Mini PCIe接口常用引脚:
┌─────────────────────────────┐
│ EC20 R2.1 │
│ ┌─────────────────────┐ │
│ │ VCC (3.3-4.2V) │───┼─── 电源输入(需2A+)
│ │ GND │───┼─── 地线
│ │ UART_TX │───┼─── UART发送(接MCU RX)
│ │ UART_RX │───┼─── UART接收(接MCU TX)
│ │ PWRKEY │───┼─── 开机键(低电平触发)
│ │ RESET_N │───┼─── 复位(低电平有效)
│ │ USB_DM/USB_DP │───┼─── USB调试(可选)
│ │ STATUS │───┼─── 状态指示
│ └─────────────────────┘ │
└─────────────────────────────┘
2.3 开发环境搭建
步骤1:安装STM32CubeIDE
- 访问ST官网下载STM32CubeIDE
- 安装并配置工作空间
- 安装STM32F1系列支持包
步骤2:创建新项目
File → New → STM32 Project
→ 选择MCU: STM32F103C8Tx
→ 输入项目名称: EC20_HTTP_Client
步骤3:配置时钟和引脚
在CubeMX中配置:
- 系统时钟:72MHz(HSE 8MHz × 9)
- USART1:异步模式,115200波特率(调试串口)
- USART2:异步模式,115200波特率(连接EC20)
- GPIO:配置PWRKEY为输出
三、核心实现
3.1 项目结构规划
本教程将创建以下代码文件:
EC20_HTTP_Project/
├── Core/
│ ├── Inc/
│ │ ├── main.h
│ │ ├── ec20.h # EC20驱动头文件
│ │ ├── http_client.h # HTTP客户端头文件
│ │ └── ring_buffer.h # 环形缓冲区
│ └── Src/
│ ├── main.c
│ ├── ec20.c # EC20驱动实现
│ ├── http_client.c # HTTP客户端实现
│ ├── ring_buffer.c # 环形缓冲区实现
│ └── stm32f1xx_it.c
└── README.md
3.2 环形缓冲区实现
📄 创建文件:
Core/Inc/ring_buffer.h
c
/**
* @file ring_buffer.h
* @brief 环形缓冲区头文件
* @version 1.0
*/
#ifndef __RING_BUFFER_H
#define __RING_BUFFER_H
#include <stdint.h>
#include <stdbool.h>
#include <stddef.h>
#define RING_BUFFER_SIZE 2048 // 缓冲区大小
typedef struct {
uint8_t buffer[RING_BUFFER_SIZE];
volatile uint16_t head; // 写指针
volatile uint16_t tail; // 读指针
volatile uint16_t count; // 当前数据量
} RingBuffer_t;
void RingBuffer_Init(RingBuffer_t *rb);
bool RingBuffer_Write(RingBuffer_t *rb, uint8_t data);
bool RingBuffer_WriteString(RingBuffer_t *rb, const char *str);
uint16_t RingBuffer_Read(RingBuffer_t *rb, uint8_t *data, uint16_t len);
uint16_t RingBuffer_ReadLine(RingBuffer_t *rb, char *line, uint16_t maxLen);
bool RingBuffer_IsEmpty(RingBuffer_t *rb);
bool RingBuffer_IsFull(RingBuffer_t *rb);
uint16_t RingBuffer_Count(RingBuffer_t *rb);
void RingBuffer_Clear(RingBuffer_t *rb);
#endif /* __RING_BUFFER_H */
📄 创建文件:
Core/Src/ring_buffer.c
c
/**
* @file ring_buffer.c
* @brief 环形缓冲区实现
* @version 1.0
*/
#include "ring_buffer.h"
#include <string.h>
/**
* @brief 初始化环形缓冲区
* @param rb 环形缓冲区句柄
*/
void RingBuffer_Init(RingBuffer_t *rb)
{
memset(rb->buffer, 0, RING_BUFFER_SIZE);
rb->head = 0;
rb->tail = 0;
rb->count = 0;
}
/**
* @brief 写入单个字节
* @param rb 环形缓冲区句柄
* @param data 要写入的数据
* @return true=成功, false=缓冲区满
*/
bool RingBuffer_Write(RingBuffer_t *rb, uint8_t data)
{
if (RingBuffer_IsFull(rb)) {
return false;
}
rb->buffer[rb->head] = data;
rb->head = (rb->head + 1) % RING_BUFFER_SIZE;
rb->count++;
return true;
}
/**
* @brief 写入字符串
* @param rb 环形缓冲区句柄
* @param str 要写入的字符串
* @return true=成功, false=缓冲区满
*/
bool RingBuffer_WriteString(RingBuffer_t *rb, const char *str)
{
while (*str) {
if (!RingBuffer_Write(rb, (uint8_t)*str)) {
return false;
}
str++;
}
return true;
}
/**
* @brief 读取多个字节
* @param rb 环形缓冲区句柄
* @param data 数据缓冲区
* @param len 要读取的长度
* @return 实际读取的字节数
*/
uint16_t RingBuffer_Read(RingBuffer_t *rb, uint8_t *data, uint16_t len)
{
uint16_t i;
uint16_t readLen = (len < rb->count) ? len : rb->count;
for (i = 0; i < readLen; i++) {
data[i] = rb->buffer[rb->tail];
rb->tail = (rb->tail + 1) % RING_BUFFER_SIZE;
}
rb->count -= readLen;
return readLen;
}
/**
* @brief 读取一行(以\r\n结尾)
* @param rb 环形缓冲区句柄
* @param line 行缓冲区
* @param maxLen 最大长度
* @return 实际读取的长度
*/
uint16_t RingBuffer_ReadLine(RingBuffer_t *rb, char *line, uint16_t maxLen)
{
uint16_t i = 0;
uint16_t tempTail = rb->tail;
uint16_t tempCount = rb->count;
// 查找\r\n
while (tempCount > 0 && i < maxLen - 1) {
char c = rb->buffer[tempTail];
line[i] = c;
if (i > 0 && line[i-1] == '\r' && c == '\n') {
// 找到行尾
line[i+1] = '\0';
// 更新实际指针
rb->tail = (tempTail + 1) % RING_BUFFER_SIZE;
rb->count = tempCount - 1;
return i + 1;
}
tempTail = (tempTail + 1) % RING_BUFFER_SIZE;
tempCount--;
i++;
}
return 0; // 未找到完整行
}
bool RingBuffer_IsEmpty(RingBuffer_t *rb)
{
return (rb->count == 0);
}
bool RingBuffer_IsFull(RingBuffer_t *rb)
{
return (rb->count >= RING_BUFFER_SIZE - 1);
}
uint16_t RingBuffer_Count(RingBuffer_t *rb)
{
return rb->count;
}
void RingBuffer_Clear(RingBuffer_t *rb)
{
rb->head = 0;
rb->tail = 0;
rb->count = 0;
}
3.3 EC20驱动实现
📄 创建文件:
Core/Inc/ec20.h
c
/**
* @file ec20.h
* @brief EC20 4G模块驱动头文件
* @version 1.0
*/
#ifndef __EC20_H
#define __EC20_H
#include "main.h"
#include "ring_buffer.h"
#include <stdint.h>
#include <stdbool.h>
/* ==================== 配置参数 ==================== */
#define EC20_UART_TIMEOUT 1000 // UART超时时间(ms)
#define EC20_CMD_TIMEOUT 5000 // AT指令超时时间(ms)
#define EC20_RESP_BUFFER_SIZE 512 // 响应缓冲区大小
/* ==================== 状态定义 ==================== */
typedef enum {
EC20_OK = 0,
EC20_ERROR_UART,
EC20_ERROR_TIMEOUT,
EC20_ERROR_RESPONSE,
EC20_ERROR_NO_SIM,
EC20_ERROR_NO_NETWORK,
EC20_ERROR_GPRS_FAILED
} EC20_StatusTypeDef;
typedef enum {
EC20_STATE_POWER_OFF = 0,
EC20_STATE_POWER_ON,
EC20_STATE_INITIALIZING,
EC20_STATE_READY,
EC20_STATE_CONNECTING,
EC20_STATE_CONNECTED,
EC20_STATE_ERROR
} EC20_StateTypeDef;
typedef struct {
UART_HandleTypeDef *huart; // UART句柄
GPIO_TypeDef *pwrkey_port; // PWRKEY端口
uint16_t pwrkey_pin; // PWRKEY引脚
RingBuffer_t rxBuffer; // 接收环形缓冲区
EC20_StateTypeDef state; // 当前状态
char imei[16]; // IMEI号
char iccid[21]; // ICCID号
char operator[32]; // 运营商名称
int8_t signalLevel; // 信号强度(0-31, 99=未知)
char ipAddress[16]; // IP地址
} EC20_HandleTypeDef;
/* ==================== 函数声明 ==================== */
// 初始化和控制
EC20_StatusTypeDef EC20_Init(EC20_HandleTypeDef *dev);
void EC20_PowerOn(EC20_HandleTypeDef *dev);
void EC20_PowerOff(EC20_HandleTypeDef *dev);
void EC20_Reset(EC20_HandleTypeDef *dev);
// AT指令
EC20_StatusTypeDef EC20_SendCommand(EC20_HandleTypeDef *dev,
const char *cmd,
char *response,
uint16_t respLen,
uint32_t timeout);
EC20_StatusTypeDef EC20_SendData(EC20_HandleTypeDef *dev,
const uint8_t *data,
uint16_t len);
// 状态查询
bool EC20_IsReady(EC20_HandleTypeDef *dev);
EC20_StatusTypeDef EC20_CheckSIM(EC20_HandleTypeDef *dev);
EC20_StatusTypeDef EC20_CheckNetwork(EC20_HandleTypeDef *dev);
EC20_StatusTypeDef EC20_GetSignalQuality(EC20_HandleTypeDef *dev, int8_t *level);
EC20_StatusTypeDef EC20_GetIMEI(EC20_HandleTypeDef *dev);
EC20_StatusTypeDef EC20_GetICCID(EC20_HandleTypeDef *dev);
EC20_StatusTypeDef EC20_GetOperator(EC20_HandleTypeDef *dev);
// PDP和IP
EC20_StatusTypeDef EC20_ActivatePDP(EC20_HandleTypeDef *dev,
const char *apn);
EC20_StatusTypeDef EC20_DeactivatePDP(EC20_HandleTypeDef *dev);
EC20_StatusTypeDef EC20_GetIPAddress(EC20_HandleTypeDef *dev);
// 中断回调
void EC20_UART_RxCallback(EC20_HandleTypeDef *dev);
#endif /* __EC20_H */
📄 创建文件:
Core/Src/ec20.c
c
/**
* @file ec20.c
* @brief EC20 4G模块驱动实现
* @version 1.0
*/
#include "ec20.h"
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
/* ==================== 私有函数 ==================== */
static bool EC20_WaitForResponse(EC20_HandleTypeDef *dev,
const char *expected,
uint32_t timeout);
static void EC20_Delay(uint32_t ms);
static void EC20_ParseResponse(char *response, const char *prefix, char *value, uint16_t valueLen);
/**
* @brief 延时函数
*/
static void EC20_Delay(uint32_t ms)
{
HAL_Delay(ms);
}
/**
* @brief 初始化EC20模块
* @param dev EC20设备句柄
* @return 初始化状态
*/
EC20_StatusTypeDef EC20_Init(EC20_HandleTypeDef *dev)
{
// 初始化环形缓冲区
RingBuffer_Init(&dev->rxBuffer);
dev->state = EC20_STATE_POWER_OFF;
// 清空信息字段
memset(dev->imei, 0, sizeof(dev->imei));
memset(dev->iccid, 0, sizeof(dev->iccid));
memset(dev->operator, 0, sizeof(dev->operator));
memset(dev->ipAddress, 0, sizeof(dev->ipAddress));
dev->signalLevel = 99;
return EC20_OK;
}
/**
* @brief 开机
* @param dev EC20设备句柄
*/
void EC20_PowerOn(EC20_HandleTypeDef *dev)
{
// PWRKEY拉低至少500ms开机
HAL_GPIO_WritePin(dev->pwrkey_port, dev->pwrkey_pin, GPIO_PIN_RESET);
EC20_Delay(800);
HAL_GPIO_WritePin(dev->pwrkey_port, dev->pwrkey_pin, GPIO_PIN_SET);
dev->state = EC20_STATE_POWER_ON;
// 等待模块启动(约10秒)
EC20_Delay(10000);
}
/**
* @brief 关机
* @param dev EC20设备句柄
*/
void EC20_PowerOff(EC20_HandleTypeDef *dev)
{
// 发送关机AT指令
char response[64];
EC20_SendCommand(dev, "AT+QPOWD=1", response, sizeof(response), 5000);
dev->state = EC20_STATE_POWER_OFF;
// 等待关机完成
EC20_Delay(3000);
}
/**
* @brief 复位模块
* @param dev EC20设备句柄
*/
void EC20_Reset(EC20_HandleTypeDef *dev)
{
EC20_PowerOff(dev);
EC20_Delay(2000);
EC20_PowerOn(dev);
}
/**
* @brief 发送AT指令
* @param dev EC20设备句柄
* @param cmd AT指令
* @param response 响应缓冲区
* @param respLen 缓冲区长度
* @param timeout 超时时间
* @return 执行状态
*/
EC20_StatusTypeDef EC20_SendCommand(EC20_HandleTypeDef *dev,
const char *cmd,
char *response,
uint16_t respLen,
uint32_t timeout)
{
char cmdBuffer[256];
uint32_t startTick;
uint16_t respIndex = 0;
// 清空接收缓冲区
RingBuffer_Clear(&dev->rxBuffer);
// 构造AT指令(添加\r\n)
snprintf(cmdBuffer, sizeof(cmdBuffer), "%s\r\n", cmd);
// 发送指令
HAL_UART_Transmit(dev->huart, (uint8_t *)cmdBuffer, strlen(cmdBuffer), EC20_UART_TIMEOUT);
// 等待响应
startTick = HAL_GetTick();
while ((HAL_GetTick() - startTick) < timeout) {
// 读取环形缓冲区数据
while (!RingBuffer_IsEmpty(&dev->rxBuffer) && respIndex < respLen - 1) {
uint8_t data;
RingBuffer_Read(&dev->rxBuffer, &data, 1);
response[respIndex++] = (char)data;
}
response[respIndex] = '\0';
// 检查是否收到OK或ERROR
if (strstr(response, "OK\r\n") || strstr(response, "ERROR\r\n")) {
break;
}
EC20_Delay(10);
}
// 检查响应结果
if (respIndex == 0) {
return EC20_ERROR_TIMEOUT;
}
if (strstr(response, "OK\r\n")) {
return EC20_OK;
} else if (strstr(response, "ERROR\r\n")) {
return EC20_ERROR_RESPONSE;
}
return EC20_ERROR_TIMEOUT;
}
/**
* @brief 发送原始数据
* @param dev EC20设备句柄
* @param data 数据缓冲区
* @param len 数据长度
* @return 执行状态
*/
EC20_StatusTypeDef EC20_SendData(EC20_HandleTypeDef *dev,
const uint8_t *data,
uint16_t len)
{
HAL_StatusTypeDef status = HAL_UART_Transmit(dev->huart, data, len, EC20_UART_TIMEOUT);
return (status == HAL_OK) ? EC20_OK : EC20_ERROR_UART;
}
/**
* @brief 检查模块是否就绪
* @param dev EC20设备句柄
* @return true=就绪
*/
bool EC20_IsReady(EC20_HandleTypeDef *dev)
{
char response[64];
EC20_StatusTypeDef status = EC20_SendCommand(dev, "AT", response, sizeof(response), 1000);
return (status == EC20_OK);
}
/**
* @brief 检查SIM卡状态
* @param dev EC20设备句柄
* @return 执行状态
*/
EC20_StatusTypeDef EC20_CheckSIM(EC20_HandleTypeDef *dev)
{
char response[128];
EC20_StatusTypeDef status;
// 查询SIM卡状态
status = EC20_SendCommand(dev, "AT+CPIN?", response, sizeof(response), 5000);
if (status != EC20_OK) {
return EC20_ERROR_NO_SIM;
}
// 检查响应
if (strstr(response, "+CPIN: READY")) {
return EC20_OK;
} else if (strstr(response, "+CPIN: SIM PIN")) {
// 需要输入PIN码
return EC20_ERROR_NO_SIM;
}
return EC20_ERROR_NO_SIM;
}
/**
* @brief 检查网络注册状态
* @param dev EC20设备句柄
* @return 执行状态
*/
EC20_StatusTypeDef EC20_CheckNetwork(EC20_HandleTypeDef *dev)
{
char response[128];
EC20_StatusTypeDef status;
// 查询网络注册状态
status = EC20_SendCommand(dev, "AT+CREG?", response, sizeof(response), 5000);
if (status != EC20_OK) {
return EC20_ERROR_NO_NETWORK;
}
// 解析响应 +CREG: 0,1 或 +CREG: 0,5
char *ptr = strstr(response, "+CREG:");
if (ptr) {
int n, stat;
sscanf(ptr, "+CREG: %d,%d", &n, &stat);
// stat: 1=本地网络已注册, 5=漫游网络已注册
if (stat == 1 || stat == 5) {
dev->state = EC20_STATE_READY;
return EC20_OK;
}
}
return EC20_ERROR_NO_NETWORK;
}
/**
* @brief 获取信号强度
* @param dev EC20设备句柄
* @param level 信号强度输出(0-31, 99=未知)
* @return 执行状态
*/
EC20_StatusTypeDef EC20_GetSignalQuality(EC20_HandleTypeDef *dev, int8_t *level)
{
char response[64];
EC20_StatusTypeDef status;
status = EC20_SendCommand(dev, "AT+CSQ", response, sizeof(response), 5000);
if (status != EC20_OK) {
return status;
}
// 解析响应 +CSQ: <rssi>,<ber>
char *ptr = strstr(response, "+CSQ:");
if (ptr) {
int rssi, ber;
sscanf(ptr, "+CSQ: %d,%d", &rssi, &ber);
*level = (int8_t)rssi;
dev->signalLevel = *level;
return EC20_OK;
}
return EC20_ERROR_RESPONSE;
}
/**
* @brief 获取IMEI号
* @param dev EC20设备句柄
* @return 执行状态
*/
EC20_StatusTypeDef EC20_GetIMEI(EC20_HandleTypeDef *dev)
{
char response[64];
EC20_StatusTypeDef status;
status = EC20_SendCommand(dev, "AT+GSN", response, sizeof(response), 5000);
if (status != EC20_OK) {
return status;
}
// 解析IMEI(第一行数字)
char *start = response;
while (*start == '\r' || *start == '\n') start++;
char *end = strchr(start, '\r');
if (end) {
uint16_t len = (end - start < sizeof(dev->imei) - 1) ? (end - start) : (sizeof(dev->imei) - 1);
strncpy(dev->imei, start, len);
dev->imei[len] = '\0';
}
return EC20_OK;
}
/**
* @brief 获取ICCID号
* @param dev EC20设备句柄
* @return 执行状态
*/
EC20_StatusTypeDef EC20_GetICCID(EC20_HandleTypeDef *dev)
{
char response[64];
EC20_StatusTypeDef status;
status = EC20_SendCommand(dev, "AT+QCCID", response, sizeof(response), 5000);
if (status != EC20_OK) {
return status;
}
// 解析ICCID +QCCID: <iccid>
char *ptr = strstr(response, "+QCCID:");
if (ptr) {
ptr += 7;
while (*ptr == ' ' || *ptr == '"') ptr++;
char *end = strchr(ptr, '"');
if (!end) end = strchr(ptr, '\r');
if (end) {
uint16_t len = (end - ptr < sizeof(dev->iccid) - 1) ? (end - ptr) : (sizeof(dev->iccid) - 1);
strncpy(dev->iccid, ptr, len);
dev->iccid[len] = '\0';
}
}
return EC20_OK;
}
/**
* @brief 获取运营商名称
* @param dev EC20设备句柄
* @return 执行状态
*/
EC20_StatusTypeDef EC20_GetOperator(EC20_HandleTypeDef *dev)
{
char response[128];
EC20_StatusTypeDef status;
status = EC20_SendCommand(dev, "AT+COPS?", response, sizeof(response), 5000);
if (status != EC20_OK) {
return status;
}
// 解析响应 +COPS: <mode>,<format>,<oper>
char *ptr = strstr(response, "+COPS:");
if (ptr) {
int mode, format;
char oper[32];
if (sscanf(ptr, "+COPS: %d,%d,\"%31[^\"]\"", &mode, &format, oper) == 3) {
strncpy(dev->operator, oper, sizeof(dev->operator) - 1);
dev->operator[sizeof(dev->operator) - 1] = '\0';
}
}
return EC20_OK;
}
/**
* @brief 激活PDP上下文
* @param dev EC20设备句柄
* @param apn APN接入点名称
* @return 执行状态
*/
EC20_StatusTypeDef EC20_ActivatePDP(EC20_HandleTypeDef *dev, const char *apn)
{
char cmd[128];
char response[128];
EC20_StatusTypeDef status;
// 设置APN
snprintf(cmd, sizeof(cmd), "AT+CGDCONT=1,\"IP\",\"%s\"", apn);
status = EC20_SendCommand(dev, cmd, response, sizeof(response), 5000);
if (status != EC20_OK) {
return EC20_ERROR_GPRS_FAILED;
}
// 激活PDP上下文
status = EC20_SendCommand(dev, "AT+CGACT=1,1", response, sizeof(response), 10000);
if (status != EC20_OK) {
return EC20_ERROR_GPRS_FAILED;
}
dev->state = EC20_STATE_CONNECTED;
return EC20_OK;
}
/**
* @brief 去激活PDP上下文
* @param dev EC20设备句柄
* @return 执行状态
*/
EC20_StatusTypeDef EC20_DeactivatePDP(EC20_HandleTypeDef *dev)
{
char response[64];
EC20_SendCommand(dev, "AT+CGACT=0,1", response, sizeof(response), 5000);
dev->state = EC20_STATE_READY;
return EC20_OK;
}
/**
* @brief 获取IP地址
* @param dev EC20设备句柄
* @return 执行状态
*/
EC20_StatusTypeDef EC20_GetIPAddress(EC20_HandleTypeDef *dev)
{
char response[128];
EC20_StatusTypeDef status;
status = EC20_SendCommand(dev, "AT+CGPADDR=1", response, sizeof(response), 5000);
if (status != EC20_OK) {
return status;
}
// 解析响应 +CGPADDR: 1,<ip_address>
char *ptr = strstr(response, "+CGPADDR:");
if (ptr) {
int cid;
char ip[16];
if (sscanf(ptr, "+CGPADDR: %d,\"%15[^\"]\"", &cid, ip) == 2) {
strncpy(dev->ipAddress, ip, sizeof(dev->ipAddress) - 1);
dev->ipAddress[sizeof(dev->ipAddress) - 1] = '\0';
}
}
return EC20_OK;
}
/**
* @brief UART接收中断回调
* @param dev EC20设备句柄
*/
void EC20_UART_RxCallback(EC20_HandleTypeDef *dev)
{
uint8_t data;
// 读取接收到的数据并放入环形缓冲区
if (HAL_UART_Receive(dev->huart, &data, 1, 0) == HAL_OK) {
RingBuffer_Write(&dev->rxBuffer, data);
}
}
3.4 HTTP客户端实现
📄 创建文件:
Core/Inc/http_client.h
c
/**
* @file http_client.h
* @brief HTTP客户端头文件
* @version 1.0
*/
#ifndef __HTTP_CLIENT_H
#define __HTTP_CLIENT_H
#include "ec20.h"
#include <stdint.h>
/* ==================== 配置参数 ==================== */
#define HTTP_DEFAULT_TIMEOUT 30000 // HTTP默认超时时间(ms)
#define HTTP_BUFFER_SIZE 4096 // HTTP缓冲区大小
/* ==================== 类型定义 ==================== */
typedef enum {
HTTP_METHOD_GET = 0,
HTTP_METHOD_POST,
HTTP_METHOD_PUT,
HTTP_METHOD_DELETE
} HTTP_MethodTypeDef;
typedef struct {
uint16_t statusCode; // HTTP状态码
char contentType[64]; // Content-Type
uint32_t contentLength; // Content-Length
char *body; // 响应体
uint32_t bodyLen; // 响应体长度
} HTTP_ResponseTypeDef;
typedef struct {
EC20_HandleTypeDef *ec20; // EC20设备句柄
HTTP_ResponseTypeDef response; // HTTP响应
char buffer[HTTP_BUFFER_SIZE]; // 数据缓冲区
} HTTP_ClientTypeDef;
/* ==================== 函数声明 ==================== */
// 初始化和配置
EC20_StatusTypeDef HTTP_ClientInit(HTTP_ClientTypeDef *client, EC20_HandleTypeDef *ec20);
void HTTP_ClientDeInit(HTTP_ClientTypeDef *client);
// HTTP请求
EC20_StatusTypeDef HTTP_Get(HTTP_ClientTypeDef *client,
const char *url,
const char *headers,
uint32_t timeout);
EC20_StatusTypeDef HTTP_Post(HTTP_ClientTypeDef *client,
const char *url,
const char *headers,
const char *body,
uint32_t timeout);
// URL解析辅助函数
void HTTP_ParseURL(const char *url, char *host, uint16_t *port, char *path);
#endif /* __HTTP_CLIENT_H */
📄 创建文件:
Core/Src/http_client.c
c
/**
* @file http_client.c
* @brief HTTP客户端实现
* @version 1.0
*/
#include "http_client.h"
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
/**
* @brief 初始化HTTP客户端
* @param client HTTP客户端句柄
* @param ec20 EC20设备句柄
* @return 初始化状态
*/
EC20_StatusTypeDef HTTP_ClientInit(HTTP_ClientTypeDef *client, EC20_HandleTypeDef *ec20)
{
client->ec20 = ec20;
memset(&client->response, 0, sizeof(client->response));
memset(client->buffer, 0, sizeof(client->buffer));
return EC20_OK;
}
/**
* @brief 反初始化HTTP客户端
* @param client HTTP客户端句柄
*/
void HTTP_ClientDeInit(HTTP_ClientTypeDef *client)
{
if (client->response.body != NULL) {
free(client->response.body);
client->response.body = NULL;
}
}
/**
* @brief 解析URL
* @param url 完整URL
* @param host 主机名输出
* @param port 端口输出
* @param path 路径输出
*/
void HTTP_ParseURL(const char *url, char *host, uint16_t *port, char *path)
{
const char *ptr = url;
// 跳过协议头
if (strncmp(ptr, "http://", 7) == 0) {
ptr += 7;
*port = 80;
} else if (strncmp(ptr, "https://", 8) == 0) {
ptr += 8;
*port = 443;
}
// 提取主机名
const char *hostEnd = strchr(ptr, ':');
const char *pathStart = strchr(ptr, '/');
if (hostEnd && (!pathStart || hostEnd < pathStart)) {
// 有端口号
strncpy(host, ptr, hostEnd - ptr);
host[hostEnd - ptr] = '\0';
*port = atoi(hostEnd + 1);
} else if (pathStart) {
// 有路径
strncpy(host, ptr, pathStart - ptr);
host[pathStart - ptr] = '\0';
} else {
// 只有主机名
strcpy(host, ptr);
}
// 提取路径
if (pathStart) {
strcpy(path, pathStart);
} else {
strcpy(path, "/");
}
}
/**
* @brief 发送HTTP GET请求
* @param client HTTP客户端句柄
* @param url 请求URL
* @param headers 自定义请求头(可选,NULL表示无)
* @param timeout 超时时间
* @return 执行状态
*/
EC20_StatusTypeDef HTTP_Get(HTTP_ClientTypeDef *client,
const char *url,
const char *headers,
uint32_t timeout)
{
char cmd[512];
char response[EC20_RESP_BUFFER_SIZE];
EC20_StatusTypeDef status;
// 解析URL
char host[128];
uint16_t port;
char path[256];
HTTP_ParseURL(url, host, &port, path);
// 设置HTTP URL
snprintf(cmd, sizeof(cmd), "AT+QHTTPURL=%d,%d", strlen(url), 80);
status = EC20_SendCommand(client->ec20, cmd, response, sizeof(response), 5000);
if (status != EC20_OK) {
return status;
}
// 等待CONNECT提示
if (!strstr(response, "CONNECT")) {
return EC20_ERROR_RESPONSE;
}
// 发送URL
status = EC20_SendData(client->ec20, (uint8_t *)url, strlen(url));
if (status != EC20_OK) {
return status;
}
// 等待OK
status = EC20_SendCommand(client->ec20, "", response, sizeof(response), 5000);
if (status != EC20_OK) {
return status;
}
// 发送GET请求
status = EC20_SendCommand(client->ec20, "AT+QHTTPGET=80", response, sizeof(response), timeout);
if (status != EC20_OK) {
return status;
}
// 检查响应
if (strstr(response, "+QHTTPGET:")) {
int result, statusCode;
sscanf(strstr(response, "+QHTTPGET:"), "+QHTTPGET: %d,%d", &result, &statusCode);
client->response.statusCode = (uint16_t)statusCode;
if (result != 0) {
return EC20_ERROR_RESPONSE;
}
}
// 读取响应内容
status = EC20_SendCommand(client->ec20, "AT+QHTTPREAD=80", response, sizeof(response), timeout);
if (status == EC20_OK) {
// 提取响应体
char *bodyStart = strstr(response, "CONNECT");
if (bodyStart) {
bodyStart += 7;
while (*bodyStart == '\r' || *bodyStart == '\n') bodyStart++;
char *okPtr = strstr(bodyStart, "OK\r\n");
if (okPtr) {
client->response.bodyLen = okPtr - bodyStart;
if (client->response.body != NULL) {
free(client->response.body);
}
client->response.body = (char *)malloc(client->response.bodyLen + 1);
if (client->response.body) {
memcpy(client->response.body, bodyStart, client->response.bodyLen);
client->response.body[client->response.bodyLen] = '\0';
}
}
}
}
return EC20_OK;
}
/**
* @brief 发送HTTP POST请求
* @param client HTTP客户端句柄
* @param url 请求URL
* @param headers 自定义请求头(可选,NULL表示无)
* @param body 请求体
* @param timeout 超时时间
* @return 执行状态
*/
EC20_StatusTypeDef HTTP_Post(HTTP_ClientTypeDef *client,
const char *url,
const char *headers,
const char *body,
uint32_t timeout)
{
char cmd[512];
char response[EC20_RESP_BUFFER_SIZE];
EC20_StatusTypeDef status;
// 解析URL
char host[128];
uint16_t port;
char path[256];
HTTP_ParseURL(url, host, &port, path);
// 设置HTTP URL
snprintf(cmd, sizeof(cmd), "AT+QHTTPURL=%d,%d", strlen(url), 80);
status = EC20_SendCommand(client->ec20, cmd, response, sizeof(response), 5000);
if (status != EC20_OK) {
return status;
}
// 等待CONNECT提示
if (!strstr(response, "CONNECT")) {
return EC20_ERROR_RESPONSE;
}
// 发送URL
status = EC20_SendData(client->ec20, (uint8_t *)url, strlen(url));
if (status != EC20_OK) {
return status;
}
// 等待OK
status = EC20_SendCommand(client->ec20, "", response, sizeof(response), 5000);
if (status != EC20_OK) {
return status;
}
// 设置POST数据
uint16_t bodyLen = strlen(body);
snprintf(cmd, sizeof(cmd), "AT+QHTTPPOST=%d,%d,%d", bodyLen, 80, 80);
status = EC20_SendCommand(client->ec20, cmd, response, sizeof(response), 5000);
if (status != EC20_OK) {
return status;
}
// 等待CONNECT提示
if (!strstr(response, "CONNECT")) {
return EC20_ERROR_RESPONSE;
}
// 发送POST数据
status = EC20_SendData(client->ec20, (uint8_t *)body, bodyLen);
if (status != EC20_OK) {
return status;
}
// 等待响应
status = EC20_SendCommand(client->ec20, "", response, sizeof(response), timeout);
if (status != EC20_OK) {
return status;
}
// 检查响应
if (strstr(response, "+QHTTPPOST:")) {
int result, statusCode;
sscanf(strstr(response, "+QHTTPPOST:"), "+QHTTPPOST: %d,%d", &result, &statusCode);
client->response.statusCode = (uint16_t)statusCode;
if (result != 0) {
return EC20_ERROR_RESPONSE;
}
}
// 读取响应内容
status = EC20_SendCommand(client->ec20, "AT+QHTTPREAD=80", response, sizeof(response), timeout);
if (status == EC20_OK) {
// 提取响应体
char *bodyStart = strstr(response, "CONNECT");
if (bodyStart) {
bodyStart += 7;
while (*bodyStart == '\r' || *bodyStart == '\n') bodyStart++;
char *okPtr = strstr(bodyStart, "OK\r\n");
if (okPtr) {
client->response.bodyLen = okPtr - bodyStart;
if (client->response.body != NULL) {
free(client->response.body);
}
client->response.body = (char *)malloc(client->response.bodyLen + 1);
if (client->response.body) {
memcpy(client->response.body, bodyStart, client->response.bodyLen);
client->response.body[client->response.bodyLen] = '\0';
}
}
}
}
return EC20_OK;
}
3.5 主程序
📄 创建文件:
Core/Src/main.c
c
/**
* @file main.c
* @brief EC20 HTTP通信主程序
* @version 1.0
*/
#include "main.h"
#include "ec20.h"
#include "http_client.h"
#include <stdio.h>
#include <string.h>
// EC20模块配置
EC20_HandleTypeDef ec20Dev = {
.huart = &huart2,
.pwrkey_port = GPIOB,
.pwrkey_pin = GPIO_PIN_5
};
// HTTP客户端
HTTP_ClientTypeDef httpClient;
// 调试串口
extern UART_HandleTypeDef huart1;
// APN配置(根据运营商修改)
#define APN_CMNET "cmnet" // 中国移动
#define APN_CTWAP "ctnet" // 中国电信
#define APN_CUNET "3gnet" // 中国联通
/**
* @brief 重定向printf到串口
*/
int _write(int file, char *ptr, int len)
{
HAL_UART_Transmit(&huart1, (uint8_t *)ptr, len, HAL_MAX_DELAY);
return len;
}
/**
* @brief 打印EC20模块信息
*/
void PrintModuleInfo(EC20_HandleTypeDef *dev)
{
printf("\r\n========================================\r\n");
printf(" 4G Module Information\r\n");
printf("========================================\r\n");
printf("IMEI: %s\r\n", dev->imei);
printf("ICCID: %s\r\n", dev->iccid);
printf("Operator: %s\r\n", dev->operator);
printf("Signal: %d/31\r\n", dev->signalLevel);
printf("IP: %s\r\n", dev->ipAddress);
printf("========================================\r\n\r\n");
}
/**
* @brief 系统入口
*/
int main(void)
{
// HAL初始化
HAL_Init();
// 配置系统时钟
SystemClock_Config();
// 初始化外设
MX_GPIO_Init();
MX_USART1_UART_Init();
MX_USART2_UART_Init();
printf("\r\n========================================\r\n");
printf(" STM32 EC20 HTTP Client\r\n");
printf(" 4G Module Test\r\n");
printf("========================================\r\n\r\n");
// 初始化EC20
printf("[INFO] Initializing EC20 module...\r\n");
EC20_Init(&ec20Dev);
// 开机
printf("[INFO] Powering on EC20...\r\n");
EC20_PowerOn(&ec20Dev);
// 检查模块就绪
printf("[INFO] Waiting for module ready...\r\n");
uint32_t startTick = HAL_GetTick();
while (!EC20_IsReady(&ec20Dev)) {
if (HAL_GetTick() - startTick > 30000) {
printf("[ERROR] Module not responding!\r\n");
Error_Handler();
}
HAL_Delay(1000);
}
printf("[INFO] Module is ready!\r\n\r\n");
// 获取模块信息
printf("[INFO] Getting module info...\r\n");
EC20_GetIMEI(&ec20Dev);
EC20_GetICCID(&ec20Dev);
// 检查SIM卡
printf("[INFO] Checking SIM card...\r\n");
if (EC20_CheckSIM(&ec20Dev) != EC20_OK) {
printf("[ERROR] SIM card error!\r\n");
Error_Handler();
}
printf("[INFO] SIM card is ready!\r\n");
// 等待网络注册
printf("[INFO] Waiting for network registration...\r\n");
startTick = HAL_GetTick();
while (EC20_CheckNetwork(&ec20Dev) != EC20_OK) {
if (HAL_GetTick() - startTick > 60000) {
printf("[ERROR] Network registration failed!\r\n");
Error_Handler();
}
HAL_Delay(2000);
}
printf("[INFO] Network registered!\r\n");
// 获取运营商和信号强度
EC20_GetOperator(&ec20Dev);
EC20_GetSignalQuality(&ec20Dev, &ec20Dev.signalLevel);
// 激活PDP上下文
printf("[INFO] Activating PDP context...\r\n");
if (EC20_ActivatePDP(&ec20Dev, APN_CMNET) != EC20_OK) {
printf("[ERROR] PDP activation failed!\r\n");
Error_Handler();
}
printf("[INFO] PDP activated!\r\n");
// 获取IP地址
EC20_GetIPAddress(&ec20Dev);
// 打印模块信息
PrintModuleInfo(&ec20Dev);
// 初始化HTTP客户端
HTTP_ClientInit(&httpClient, &ec20Dev);
// HTTP测试循环
uint32_t requestCount = 0;
while (1) {
requestCount++;
printf("\r\n========================================\r\n");
printf(" HTTP Request #%lu\r\n", requestCount);
printf("========================================\r\n");
// GET请求示例
printf("[HTTP] Sending GET request...\r\n");
EC20_StatusTypeDef status = HTTP_Get(&httpClient,
"http://httpbin.org/get",
NULL,
30000);
if (status == EC20_OK) {
printf("[HTTP] GET Success! Status: %d\r\n", httpClient.response.statusCode);
if (httpClient.response.body) {
printf("[HTTP] Response:\r\n%s\r\n", httpClient.response.body);
}
} else {
printf("[ERROR] GET failed! Status: %d\r\n", status);
}
HAL_Delay(5000);
// POST请求示例
printf("[HTTP] Sending POST request...\r\n");
const char *postData = "{\"device\":\"STM32\",\"value\":123}";
status = HTTP_Post(&httpClient,
"http://httpbin.org/post",
"Content-Type: application/json\r\n",
postData,
30000);
if (status == EC20_OK) {
printf("[HTTP] POST Success! Status: %d\r\n", httpClient.response.statusCode);
if (httpClient.response.body) {
printf("[HTTP] Response:\r\n%s\r\n", httpClient.response.body);
}
} else {
printf("[ERROR] POST failed! Status: %d\r\n", status);
}
// 每30秒请求一次
printf("[INFO] Waiting 30 seconds...\r\n");
HAL_Delay(30000);
}
}
/**
* @brief USART2初始化(连接EC20)
*/
void MX_USART2_UART_Init(void)
{
huart2.Instance = USART2;
huart2.Init.BaudRate = 115200;
huart2.Init.WordLength = UART_WORDLENGTH_8B;
huart2.Init.StopBits = UART_STOPBITS_1;
huart2.Init.Parity = UART_PARITY_NONE;
huart2.Init.Mode = UART_MODE_TX_RX;
huart2.Init.HwFlowCtl = UART_HWCONTROL_NONE;
huart2.Init.OverSampling = UART_OVERSAMPLING_16;
if (HAL_UART_Init(&huart2) != HAL_OK) {
Error_Handler();
}
// 启动接收中断
HAL_UART_Receive_IT(&huart2, (uint8_t *)&ec20Dev.rxBuffer, 1);
}
/**
* @brief UART中断回调
*/
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
if (huart->Instance == USART2) {
// 重新启动接收
HAL_UART_Receive_IT(&huart2, (uint8_t *)&ec20Dev.rxBuffer, 1);
}
}
// 其他初始化函数...
// MX_USART1_UART_Init(), MX_GPIO_Init(), SystemClock_Config(), Error_Handler()
四、系统架构
🌐 互联网
📶 EC20 4G模块
🖥️ STM32F103
AT指令
LTE
主控程序
main.c
HTTP客户端
http_client.c
EC20驱动
ec20.c
环形缓冲区
ring_buffer.c
UART2接口
EC20 R2.1
4G天线
基站
Internet
HTTP服务器
五、测试验证
5.1 硬件连接
STM32F103 EC20模块
───────── ─────────
PA2 (USART2_TX) ──> UART_RX
PA3 (USART2_RX) <── UART_TX
PB5 ──> PWRKEY
3.3V ──> VCC(需2A+电源)
GND ──> GND
STM32F103 USB-TTL
───────── ───────
PA9 (USART1_TX) ──> RX
PA10(USART1_RX) <── TX
GND ──> GND
5.2 测试输出示例
========================================
STM32 EC20 HTTP Client
4G Module Test
========================================
[INFO] Initializing EC20 module...
[INFO] Powering on EC20...
[INFO] Waiting for module ready...
[INFO] Module is ready!
[INFO] Getting module info...
[INFO] Checking SIM card...
[INFO] SIM card is ready!
[INFO] Waiting for network registration...
[INFO] Network registered!
[INFO] Activating PDP context...
[INFO] PDP activated!
========================================
4G Module Information
========================================
IMEI: 866123456789012
ICCID: 89860012345678901234
Operator: CHINA MOBILE
Signal: 23/31
IP: 10.45.123.45
========================================
========================================
HTTP Request #1
========================================
[HTTP] Sending GET request...
[HTTP] GET Success! Status: 200
[HTTP] Response:
{
"args": {},
"headers": {
"Host": "httpbin.org"
},
"origin": "10.45.123.45",
"url": "http://httpbin.org/get"
}
[HTTP] Sending POST request...
[HTTP] POST Success! Status: 200
[HTTP] Response:
{
"args": {},
"data": "{\"device\":\"STM32\",\"value\":123}",
"headers": {
"Content-Type": "application/json"
},
"json": {
"device": "STM32",
"value": 123
}
}
六、故障排查
6.1 模块无法启动
**现象:**模块无响应
排查步骤:
- 检查电源:EC20峰值电流2A,确保电源足够
- 检查PWRKEY:低电平触发至少500ms
- 检查串口连接:TX/RX是否接反
- 等待时间:开机需要10秒左右
6.2 SIM卡错误
现象: +CPIN: SIM NOT INSERTED
解决方案:
c
// 检查SIM卡插入
// 尝试重新插拔SIM卡
// 检查SIM卡是否欠费
6.3 网络注册失败
现象: +CREG: 0,2(搜索中)或+CREG: 0,3(注册拒绝)
解决方案:
- 检查天线连接
- 检查信号强度:
AT+CSQ - 检查APN配置是否正确
- 确认SIM卡已开通数据业务
七、总结
7.1 核心知识点回顾
- 4G通信原理:LTE网络架构、PDP上下文、APN配置
- EC20模块:AT指令集、HTTP协议支持、网络状态管理
- STM32驱动开发:UART通信、中断处理、状态机设计
7.2 扩展方向
- MQTT协议:实现物联网消息发布订阅
- HTTPS安全通信:添加SSL/TLS加密
- GPS定位:EC20内置GPS功能
- 低功耗优化:睡眠唤醒机制
7.3 学习资源
官方文档:
💡 提示:使用4G模块时请注意SIM卡流量消耗。