STM32实战:基于STM32F103的4G模块(EC20)HTTP通信

文章目录

    • 一、前言
      • [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

  1. 访问ST官网下载STM32CubeIDE
  2. 安装并配置工作空间
  3. 安装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服务器

httpbin.org


五、测试验证

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 模块无法启动

**现象:**模块无响应

排查步骤:

  1. 检查电源:EC20峰值电流2A,确保电源足够
  2. 检查PWRKEY:低电平触发至少500ms
  3. 检查串口连接:TX/RX是否接反
  4. 等待时间:开机需要10秒左右

6.2 SIM卡错误

现象: +CPIN: SIM NOT INSERTED

解决方案:

c 复制代码
// 检查SIM卡插入
// 尝试重新插拔SIM卡
// 检查SIM卡是否欠费

6.3 网络注册失败

现象: +CREG: 0,2(搜索中)或+CREG: 0,3(注册拒绝)

解决方案:

  1. 检查天线连接
  2. 检查信号强度:AT+CSQ
  3. 检查APN配置是否正确
  4. 确认SIM卡已开通数据业务

七、总结

7.1 核心知识点回顾

  1. 4G通信原理:LTE网络架构、PDP上下文、APN配置
  2. EC20模块:AT指令集、HTTP协议支持、网络状态管理
  3. STM32驱动开发:UART通信、中断处理、状态机设计

7.2 扩展方向

  • MQTT协议:实现物联网消息发布订阅
  • HTTPS安全通信:添加SSL/TLS加密
  • GPS定位:EC20内置GPS功能
  • 低功耗优化:睡眠唤醒机制

7.3 学习资源

官方文档:


💡 提示:使用4G模块时请注意SIM卡流量消耗。

相关推荐
送外卖的CV工程师3 小时前
STM32+Makefile编译+OpenOCD 烧录调试
stm32·单片机·嵌入式硬件·makefile·调试·烧录·openocd
豆包公子3 小时前
程序流监控:AUTOSAR CP 功能安全在裸机 MCU 上的实现(理论篇)
运维·单片机·嵌入式硬件·安全·车载系统·autosar
编程之升级打怪4 小时前
单片机SPI硬件接口的要点
嵌入式硬件
Aaron_dw5 小时前
PHY Eye Monitor 全栈技术说明书
嵌入式硬件
饺子不吃醋5 小时前
深入理解HTTP:请求/响应、缓存机制、登录态与跨域
http
NQBJT8 小时前
嵌入式从零开始(第十二篇):调试与工具链 —— 从 IDE 到逻辑分析仪
ide·stm32·单片机·嵌入式硬件·c#
广州灵眸科技有限公司8 小时前
瑞芯微(EASY EAI)RV1126B 网络摄像头方案
开发语言·网络·科技·嵌入式硬件·物联网
豆包公子8 小时前
程序流监控 —— AUTOSAR CP 功能安全在裸机 MCU 上的实现:实践篇
单片机·嵌入式硬件·学习
iCxhust9 小时前
51单片机定时器PWM发生
stm32·单片机·51单片机