1. 项目概述
今天我想分享一个基于STM32F429和ESP8266的网页配网方案。这个项目源于我在实际开发中遇到的一个常见需求:如何让嵌入式设备在没有预先配置WiFi信息的情况下连接到网络。传统方式需要预先烧录WiFi账号密码或者通过串口输入,而网页配网方案则提供了更友好的用户体验。
我使用的是正点原子的ESP8266模块,通过串口与STM32通信。最初尝试了DMA+串口中断的方案,但在实际测试中发现存在数据丢失的问题,经过几小时的调试仍无法解决。最终决定采用更稳定的纯中断方式实现串口通信。
这个方案的核心思路是:
- 让ESP8266进入AP模式,创建一个配置热点
- 用户连接这个热点后,访问特定IP地址的网页
- 在网页表单中输入家庭WiFi的账号密码
- 设备获取这些信息后自动切换到STA模式连接指定网络
2. 硬件连接与初始化
2.1 硬件连接
ESP8266模块与STM32F429的连接非常简单,只需要四根线:
- VCC -> 3.3V
- GND -> GND
- TX -> PB11 (USART3_RX)
- RX -> PB10 (USART3_TX)
注意:ESP8266的工作电压是3.3V,切勿连接到5V电源,否则可能损坏模块。
2.2 USART3初始化
我选择直接通过寄存器配置USART3,而不是使用HAL库,这样可以获得更精确的控制和更小的代码体积。以下是关键配置代码:
c复制static void USART3_GPIO_Init(void)
{
RCC->AHB1ENR |= RCC_AHB1ENR_GPIOBEN;
// PB10 TX, PB11 RX
GPIOB->MODER &= ~((3U << 20) | (3U << 22));
GPIOB->MODER |= ((2U << 20) | (2U << 22));
GPIOB->AFR[1] &= ~((0xFU << 8) | (0xFU << 12));
GPIOB->AFR[1] |= ((7U << 8) | (7U << 12));
GPIOB->OSPEEDR |= (3U << 20) | (3U << 22);
GPIOB->PUPDR |= (1U << 22); // PB11 RX pull-up
}
static void USART3_Init(void)
{
RCC->APB1ENR |= RCC_APB1ENR_USART3EN;
ESP_USART->CR1 &= ~USART_CR1_UE;
// 115200 @ APB1=45MHz
ESP_USART->BRR = (24U << 4) | 6U;
// 使能收发 + RXNE 中断 + IDLE 中断
ESP_USART->CR1 |= USART_CR1_TE | USART_CR1_RE | USART_CR1_RXNEIE | USART_CR1_IDLEIE;
ESP_USART->CR1 |= USART_CR1_UE;
NVIC_SetPriority(USART3_IRQn, 5);
NVIC_EnableIRQ(USART3_IRQn);
}
波特率设置为115200,这是ESP8266的默认通信速率。BRR寄存器的值是根据APB1时钟频率45MHz计算得出的:
波特率 = APB1时钟 / (16 * USARTDIV)
USARTDIV = 45,000,000 / (16 * 115200) ≈ 24.414
整数部分24,小数部分0.414*16≈6
所以BRR寄存器设置为(24<<4)|6。
2.3 中断处理
串口中断处理函数负责接收ESP8266返回的数据。我们使用了RXNE(接收缓冲区非空)和IDLE(线路空闲)两种中断:
c复制void USART3_IRQHandler(void)
{
uint32_t sr = ESP_USART->SR;
// 接收一个字节(RXNE)
if (sr & USART_SR_RXNE)
{
uint8_t ch = ESP_USART->DR;
if (esp_rx_len < ESP_RX_BUF_SIZE - 1)
{
esp_rx_buf[esp_rx_len++] = ch;
}
}
// 空闲中断:一帧结束
if (sr & USART_SR_IDLE)
{
volatile uint32_t tmp;
tmp = ESP_USART->SR;
tmp = ESP_USART->DR;
(void)tmp;
esp_rx_buf[esp_rx_len] = 0; // C 字符串结束
esp_rx_done = 1;
}
}
这里有几个关键点:
- 每次RXNE中断只读取一个字节,确保不会丢失数据
- IDLE中断标志需要通过先读SR再读DR来清除
- 接收完成后设置esp_rx_done标志,主循环中检测这个标志来处理完整数据帧
3. ESP8266配置与网页配网实现
3.1 进入AP模式
首先需要将ESP8266配置为AP模式,创建一个配置用的WiFi热点:
c复制bool esp8266_enter_ap_web_config(void)
{
esp_enter_at_mode();
USART3_SendString("AT+RST\r\n");
vTaskDelay(pdMS_TO_TICKS(3000));
USART3_SendString("AT+CWMODE=2\r\n");
vTaskDelay(pdMS_TO_TICKS(1000));
USART3_SendString("AT+CWSAP=\"CLOCK_429\",\"12345678\",1,3\r\n");
vTaskDelay(pdMS_TO_TICKS(2000));
USART3_SendString("AT+CIPMUX=1\r\n");
vTaskDelay(pdMS_TO_TICKS(500));
USART3_SendString("AT+CIPSERVER=1,80\r\n");
vTaskDelay(pdMS_TO_TICKS(500));
esp8266_enter_data_mode();
printf("ESP8266 AP Ready: SSID=CLOCK_429 IP=192.168.4.1\r\n");
return true;
}
这个函数依次发送了以下AT指令:
- AT+RST:重启模块
- AT+CWMODE=2:设置为AP模式
- AT+CWSAP:配置AP的SSID和密码(这里设置为CLOCK_429/12345678)
- AT+CIPMUX=1:启用多连接
- AT+CIPSERVER=1,80:启动TCP服务器,监听80端口
实际产品中,建议使用设备唯一ID作为SSID的一部分,比如"DEVICE_1234",方便用户识别多个设备。
3.2 网页表单处理
当用户连接ESP8266的热点并访问192.168.4.1时,设备会返回一个简单的HTML表单:
c复制static const char *wifi_cfg_html =
"HTTP/1.1 200 OK\r\n"
"Content-Type: text/html; charset=UTF-8\r\n"
"Connection: close\r\n\r\n"
"<html><head><meta charset=\"UTF-8\"></head><body>"
"<h2>WiFi Setup</h2>"
"<form method=\"POST\">"
"SSID:<br><input name=\"s\"><br>"
"Password:<br><input type=\"password\" name=\"p\"><br><br>"
"<input type=\"submit\" value=\"Connect\">"
"</form></body></html>";
表单提交后,我们需要解析POST数据中的WiFi账号密码:
c复制static bool parse_post_wifi(const char *buf, char *ssid, char *pwd)
{
char raw_s[64] = {0}, raw_p[64] = {0};
char *s = strstr(buf, "s=");
char *p = strstr(buf, "p=");
if (!s || !p) return false;
sscanf(s + 2, "%[^&]", raw_s);
sscanf(p + 2, "%[^ \r\n]", raw_p);
url_decode(ssid, raw_s);
url_decode(pwd, raw_p);
return true;
}
这里需要注意几点:
- 表单数据是URL编码的,需要进行解码
- 密码字段可能包含特殊字符,必须正确处理
- 实际项目中应该对输入长度做限制,防止缓冲区溢出
3.3 连接目标WiFi
获取到WiFi账号密码后,就可以让ESP8266切换到STA模式并连接目标网络:
c复制static bool esp8266_connect_ap(const char *ssid, const char *pwd)
{
char cmd[128];
// 关闭自动连接
USART3_SendString("AT+CWAUTOCONN=0\r\n");
esp_wait_ok(1000);
// 断开当前连接
USART3_SendString("AT+CWQAP\r\n");
esp_wait_ok(2000);
// STA 模式
USART3_SendString("AT+CWMODE=1\r\n");
esp_wait_ok(1000);
// 连接新 AP
sprintf(cmd, "AT+CWJAP=\"%s\",\"%s\"\r\n", ssid, pwd);
USART3_SendString(cmd);
bool ok = esp_wait_ok(20000);
if (ok) {
esp8266_print_ip_once();
}
return ok;
}
连接过程可能会比较耗时(特别是密码错误时),所以等待超时设置为20秒。连接成功后,可以查询并打印获取到的IP地址:
c复制static bool esp8266_query_sta_ip(char *ip_out)
{
USART3_SendString("AT+CIFSR\r\n");
TickType_t start = xTaskGetTickCount();
while (xTaskGetTickCount() - start < pdMS_TO_TICKS(2000))
{
if (esp_rx_done)
{
esp_rx_done = 0;
char *p = strstr((char *)esp_rx_buf, "+CIFSR:STAIP,\"");
if (p)
{
p += strlen("+CIFSR:STAIP,\"");
sscanf(p, "%15[^\"]", ip_out);
return true;
}
esp_rx_len = 0;
memset(esp_rx_buf, 0, ESP_RX_BUF_SIZE);
}
vTaskDelay(pdMS_TO_TICKS(100));
}
return false;
}
4. 实际应��中的问题与解决方案
4.1 数据接收不完整
在实际测试中,我发现有时会接收不完整的HTTP请求。这通常是由于:
- 串口缓冲区大小不足
- 中断处理不及时导致数据丢失
解决方案:
- 增大接收缓冲区(我设置为1024字节)
- 确保中断优先级设置合理,不被其他高优先级中断打断
- 在IDLE中断时及时处理已接收的数据
4.2 网页重复提交问题
如原文提到的,浏览器有时会提示重新提交表单。这是因为:
- 页面刷新时会重新提交上次的POST请求
- 设备端没有正确处理HTTP连接关闭
解决方法:
- 在HTTP响应头中添加"Connection: close"
- 提示用户在配置完成后断开与配置热点的连接
- 设备在成功连接后自动关闭AP模式
4.3 安全性考虑
这个简易实现没有考虑安全性问题,实际产品中应该:
- 对配置热点设置更复杂的密码
- 限制配置热点的开启时间(如5分钟后自动关闭)
- 对收到的WiFi密码进行加密存储
- 实现HTTPS(需要ESP8266支持)
5. 项目扩展与优化方向
这个基础实现可以进一步扩展:
- 多网络配置:保存多个WiFi配置,当前网络不可用时自动切换
- 配置持久化:将WiFi配置保存到Flash,重启后自动连接
- OTA升级:通过网页界面实现固件升级
- 设备绑定:与手机APP或云平台绑定,实现远程管理
- 数据透传:在STA模式下实现与服务器的数据通信
我在实际项目中还添加了LED状态指示,通过不同闪烁模式显示当前网络状态,大大提高了用户体验。比如:
- 慢闪:等待配置
- 快闪:正在连接
- 常亮:连接成功
- 双闪:连接失败
这个方案已经成功应用在多个项目中,包括智能家居设备和工业传感器节点。它的优势在于:
- 无需预先配置WiFi信息
- 用户界面友好,无需安装专用APP
- 实现简单,成本低廉
- 兼容各种智能手机和平板电脑
希望这个分享对你有帮助。如果你在实现过程中遇到问题,或者有更好的改进建议,欢迎交流讨论。
