目录
[二、ESP32 AT指令集基础](#二、ESP32 AT指令集基础)
[2.1 什么是AT指令?](#2.1 什么是AT指令?)
[2.2 常用AT指令分类](#2.2 常用AT指令分类)
[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显示结合起来,实现天气数据的实时获取和展示。