智能天气时钟项目(一):ESP32 AT指令集详解与STM32驱动开发

目录

前言

一、为什么选择ESP32作为WiFi模块?

[二、ESP32 AT指令集基础](#二、ESP32 AT指令集基础)

[2.1 什么是AT指令?](#2.1 什么是AT指令?)

[2.2 常用AT指令分类](#2.2 常用AT指令分类)

三、STM32驱动层设计

[3.1 硬件连接](#3.1 硬件连接)

[3.2 数据结构设计](#3.2 数据结构设计)

[3.3 串口初始化](#3.3 串口初始化)

[3.4 核心通信函数](#3.4 核心通信函数)

[3.5 发送命令的统一接口](#3.5 发送命令的统一接口)

四、功能模块实现

[4.1 模块初始化与Boot等待](#4.1 模块初始化与Boot等待)

[4.2 WiFi连接](#4.2 WiFi连接)

[4.3 WiFi状态查询](#4.3 WiFi状态查询)

[4.4 NTP时间获取](#4.4 NTP时间获取)

[4.5 HTTP请求](#4.5 HTTP请求)

五、使用示例

六、总结


前言

今天,我们将深入讲解ESP32 AT指令集,并实现STM32与ESP32的通信驱动层。这是整个项目中最关键的部分之一------让STM32通过AT指令控制WiFi模块联网。

一、为什么选择ESP32作为WiFi模块?

在嵌入式开发中,WiFi联网方案主要有以下几种:

方案 优点 缺点
自带WiFi的MCU(如ESP32) 集成度高、成本低 开发复杂、资源受限
外置WiFi模块(ESP8266/ESP32) 主控选择灵活、开发简单 增加BOM成本
4G模块 覆盖广、无需WiFi 成本高、功耗大

对于STM32开发者来说,使用ESP32作为AT指令驱动的WiFi模块是最佳选择:开发简单、稳定性好、成本可控

二、ESP32 AT指令集基础

2.1 什么是AT指令?

AT指令(Attention Commands)是一种串行通信命令集,最早用于控制调制解调器。ESP32固件通过解析串口接收的AT指令,执行相应操作并返回结果。

发送:AT

返回:OK

2.2 常用AT指令分类

类别 指令 功能
基础 AT 测试通信
基础 AT+RESTORE 恢复出厂设置
WiFi AT+CWMODE 设置WiFi模式
WiFi AT+CWJAP 连接AP
WiFi AT+CWSTATE 查询WiFi状态
网络 AT+CIPSNTPCFG 配置NTP时间
网络 AT+HTTPCLIENT HTTP请求

更详细更全面的内容参考以下网站:(ESP-AT用户指南)https://docs.espressif.com/projects/esp-at/zh_CN/latest/esp32/AT_Command_Set/HTTP_AT_Commands.html

三、STM32驱动层设计

3.1 硬件连接

STM32 ESP32 说明
PA2 (USART2_TX) RX 数据发送
PA3 (USART2_RX) TX 数据接收
GND GND 共地
3.3V 3.3V 电源

3.2 数据结构设计

cpp 复制代码
// AT指令响应类型
typedef enum
{
    AT_ACK_NONE,    // 无匹配
    AT_ACK_OK,      // 成功
    AT_ACK_ERROR,   // 失败
    AT_ACK_BUSY,    // 忙
    AT_ACK_READY,   // 就绪
} at_ack_t;

// 响应匹配表
static const at_ack_match_t at_ack_matches[] = 
{
    {AT_ACK_OK,     "OK\r\n"},
    {AT_ACK_ERROR,  "ERROR\r\n"},
    {AT_ACK_BUSY,   "busy p...\r\n"},
    {AT_ACK_READY,  "ready\r\n"},
};

// WiFi信息结构体
typedef struct
{
    bool connected;          // 连接状态
    char ssid[64];           // WiFi名称
    char bssid[18];          // MAC地址
    int channel;             // 信道
    int rssi;                // 信号强度
} esp_wifi_info_t;

// 时间日期结构体
typedef struct
{
    uint16_t year;           // 年份
    uint8_t month;           // 月份
    uint8_t day;             // 日期
    uint8_t weekday;         // 星期
    uint8_t hour;            // 小时
    uint8_t minute;          // 分钟
    uint8_t second;          // 秒钟
} esp_date_time_t;

3.3 串口初始化

cpp 复制代码
static void esp_at_usart_init(void)
{
    // 1. 配置USART2参数
    USART_InitTypeDef USART_InitStructure;
    USART_StructInit(&USART_InitStructure);
    USART_InitStructure.USART_BaudRate = 115200;      // ESP8266/32默认波特率
    USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;
    USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;
    USART_InitStructure.USART_Parity = USART_Parity_No;
    USART_InitStructure.USART_StopBits = USART_StopBits_1;
    USART_InitStructure.USART_WordLength = USART_WordLength_8b;
    
    // 2. 配置GPIO复用功能
    GPIO_PinAFConfig(GPIOA, GPIO_PinSource2, GPIO_AF_USART2);
    GPIO_PinAFConfig(GPIOA, GPIO_PinSource3, GPIO_AF_USART2);
    
    // 3. 初始化GPIO
    GPIO_InitTypeDef GPIO_InitStructure;
    GPIO_StructInit(&GPIO_InitStructure);
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;
    GPIO_InitStructure.GPIO_Speed = GPIO_High_Speed;
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_2 | GPIO_Pin_3;
    GPIO_Init(GPIOA, &GPIO_InitStructure);

    // 4. 使能USART2
    USART_Init(USART2, &USART_InitStructure);
    USART_Cmd(USART2, ENABLE);
}

3.4 核心通信函数

发送AT指令:

cpp 复制代码
static void esp_at_usart_write(const char *data)
{
    // 发送数据
    while (data && *data)
    {
        while (USART_GetFlagStatus(USART2, USART_FLAG_TXE) == RESET);
        USART_SendData(USART2, *data++);
    }
    // 发送换行符 \r\n
    while (USART_GetFlagStatus(USART2, USART_FLAG_TXE) == RESET);
    USART_SendData(USART2, '\r');
    while (USART_GetFlagStatus(USART2, USART_FLAG_TXE) == RESET);
    USART_SendData(USART2, '\n');
}

等待并解析响应:

cpp 复制代码
static at_ack_t esp_at_usart_wait_receive(uint32_t timeout)
{
    uint32_t rxlen = 0;
    const char *line = rxbuf;
    uint64_t start = cpu_get_ms();
    
    rxbuf[0] = '\0';
    while (rxlen < sizeof(rxbuf) - 1)
    {
        // 等待数据
        while (USART_GetFlagStatus(USART2, USART_FLAG_RXNE) == RESET)
        {
            if (cpu_get_ms() - start >= timeout)
                return AT_ACK_NONE;
        }
        
        // 接收一个字节
        rxbuf[rxlen++] = USART_ReceiveData(USART2);
        rxbuf[rxlen] = '\0';
        
        // 收到换行符,尝试匹配响应
        if (rxbuf[rxlen - 1] == '\n')
        {
            at_ack_t ack = match_internal_ack(line);
            if (ack != AT_ACK_NONE)
                return ack;
            line = rxbuf + rxlen;
        }
    }
    return AT_ACK_NONE;
}

3.5 发送命令的统一接口

cpp 复制代码
bool esp_at_write_command(const char *command, uint32_t timeout)
{
#if ESP_AT_DEBUG
    printf("[DEBUG] Send: %s\n", command);
#endif
    // 发送命令
    esp_at_usart_write(command);
    // 等待响应
    at_ack_t ack = esp_at_usart_wait_receive(timeout);
    
#if ESP_AT_DEBUG
    printf("[DEBUG] Response:\n%s\n", rxbuf);
#endif
    return ack == AT_ACK_OK;
}

四、功能模块实现

4.1 模块初始化与Boot等待

ESP32上电后需要一定时间启动,通过循环发送"AT"指令检测模块是否就绪:

cpp 复制代码
static bool esp_at_wait_boot(uint32_t timeout)
{
    for (int t = 0; t < timeout; t += 100)
    {
        if (esp_at_write_command("AT", 100))
            return true;
    }
    return false;
}

bool esp_at_init(void)
{
    esp_at_usart_init();
    
    // 等待模块启动
    if (!esp_at_wait_boot(3000))
        return false;
    // 恢复出厂设置
    if (!esp_at_write_command("AT+RESTORE", 2000))
        return false;
    // 等待模块就绪
    if (!esp_at_wait_ready(5000))
        return false;
    
    return true;
}

4.2 WiFi连接

cpp 复制代码
bool esp_at_wifi_init(void)
{
    // 设置为Station模式
    return esp_at_write_command("AT+CWMODE=1", 2000);
}

bool esp_at_connect_wifi(const char *ssid, const char *pwd, const char *mac)
{
    if (ssid == NULL || pwd == NULL)
        return false;
    
    char cmd[128];
    int len = snprintf(cmd, sizeof(cmd), "AT+CWJAP=\"%s\",\"%s\"", ssid, pwd);
    if (mac)
        snprintf(cmd + len, sizeof(cmd) - len, ",\"%s\"", mac);
    
    return esp_at_write_command(cmd, 5000);
}

4.3 WiFi状态查询

cpp 复制代码
static bool parse_cwstate_response(const char *response, esp_wifi_info_t *info)
{
    response = strstr(response, "+CWSTATE:");
    if (response == NULL)
        return false;
    
    int wifi_state;
    if (sscanf(response, "+CWSTATE:%d,\"%63[^\"]", &wifi_state, info->ssid) != 2)
        return false;
    
    info->connected = (wifi_state == 2);
    return true;
}

bool esp_at_get_wifi_info(esp_wifi_info_t *info)
{
    if (!esp_at_write_command("AT+CWSTATE?", 2000))
        return false;
    
    if (!parse_cwstate_response(esp_at_get_response(), info))
        return false;
    
    // 如果已连接,获取详细信息
    if (info->connected)
    {
        if (!esp_at_write_command("AT+CWJAP?", 2000))
            return false;
        
        sscanf(esp_at_get_response(), "+CWJAP:\"%63[^\"]\",\"%17[^\"]\",%d,%d", 
               info->ssid, info->bssid, &info->channel, &info->rssi);
    }
    return true;
}

4.4 NTP时间获取

cpp 复制代码
bool esp_at_sntp_init(void)
{
    // 配置NTP服务器,8表示自动更新
    return esp_at_write_command("AT+CIPSNTPCFG=1,8", 2000);
}

static bool parse_cipsntptime_response(const char *response, esp_date_time_t *date)
{
    char weekday_str[8], month_str[4];
    response = strstr(response, "+CIPSNTPTIME:");
    if (sscanf(response, "+CIPSNTPTIME:%3s %3s %hhu %hhu:%hhu:%hhu %hu", 
               weekday_str, month_str, 
               &date->day, &date->hour, &date->minute, &date->second, &date->year) != 7)
        return false;
    
    date->weekday = weekday_str_to_num(weekday_str);
    date->month = month_str_to_num(month_str);
    return true;
}

bool esp_at_sntp_get_time(esp_date_time_t *date)
{
    if (!esp_at_write_command("AT+CIPSNTPTIME?", 2000))
        return false;
    
    return parse_cipsntptime_response(esp_at_get_response(), date);
}

4.5 HTTP请求

cpp 复制代码
const char *esp_at_http_get(const char *url)
{
    char *txbuf = rxbuf;
    snprintf(txbuf, sizeof(rxbuf), "AT+HTTPCLIENT=2,1,\"%s\",,,2", url);
    bool ret = esp_at_write_command(txbuf, 5000);
    return ret ? esp_at_get_response() : NULL;
}

五、使用示例

cpp 复制代码
int main(void)
{
    esp_wifi_info_t wifi;
    esp_date_time_t time;
    
    // 1. 初始化ESP模块
    if (!esp_at_init())
    {
        printf("ESP module init failed!\r\n");
        while(1);
    }
    
    // 2. 配置WiFi模式
    esp_at_wifi_init();
    
    // 3. 连接WiFi
    if (esp_at_connect_wifi("MyWiFi", "12345678", NULL))
        printf("WiFi connected!\r\n");
    
    // 4. 获取WiFi信息
    esp_at_get_wifi_info(&wifi);
    printf("SSID: %s, RSSI: %d\r\n", wifi.ssid, wifi.rssi);
    
    // 5. 初始化NTP
    esp_at_sntp_init();
    
    // 6. 获取网络时间
    if (esp_at_sntp_get_time(&time))
        printf("Time: %04d-%02d-%02d %02d:%02d:%02d\r\n", 
               time.year, time.month, time.day,
               time.hour, time.minute, time.second);
    
    // 7. HTTP请求天气数据
    const char *weather = esp_at_http_get("http://api.weather.com/v1/current");
    if (weather)
        printf("Weather: %s\r\n", weather);
    
    while(1);
}

结果展示:

六、总结

本章我们系统讲解了ESP32 AT指令集的基础知识,并完整实现了STM32与ESP32的串口通信驱动层。我们从硬件连接开始,逐步完成了串口初始化、AT指令发送、响应解析等核心函数,并在此基础上封装了WiFi连接、状态查询、NTP时间获取和HTTP请求等实用功能模块。通过本章的学习,你已经掌握了使用AT指令控制ESP32进行网络通信的全部技术要点,后续章节我们将把这些功能与LCD显示结合起来,实现天气数据的实时获取和展示。

相关推荐
DA022110 小时前
系统移植-STM32MP1_TF-A移植
stm32·单片机·系统移植
俊俊谢12 小时前
LabVIEW如何排查和修复dll缺失问题
驱动开发·.net·labview·dll
我叫洋洋13 小时前
[Proteus 和 stm32f103c8t6]的使用控制OLED篇]
c语言·stm32·单片机·嵌入式硬件·蓝桥杯·proteus
yuan1999715 小时前
STM32F103C8T6 串口通信程序实例
stm32·单片机·嵌入式硬件
musicml16 小时前
从 Vibe Coding 到 SDD(规范驱动开发):AI 原生时代的软件工程化实践
人工智能·驱动开发·软件工程
IT方大同17 小时前
(实时操作系统)线程管理
c语言·开发语言·嵌入式硬件
意法半导体STM3217 小时前
【官方原创】STM32H7双核芯片通过 STlink连接失败问题分析 LAT1654
开发语言·前端·javascript·stm32·单片机·嵌入式硬件
夜星辰202317 小时前
MobaXterm会话窗口详解
嵌入式硬件·ssh·调试串口
BT-BOX18 小时前
第7章《Stm32CubeMX+Proteus仿真入门》--独立按键扫描
stm32·嵌入式硬件·proteus