STM32与HAL库开发实战:深入探索ESP8266的多种工作模式
一、硬件平台简介
STM32F103C8T6(BluePill核心板)作为主控芯片,通过 HAL库 直接驱动 ESP8266 WiFi模块,实现以下核心功能:
- STA模式:连接外部WiFi路由器
- AP模式:自建WiFi热点
- TCP客户端/服务器模式:实现网络数据传输
- 混合模式:STA+AP共存
- 低功耗模式:深度睡眠控制
二、硬件连接
在ESP8266中,我们所买到的模块经常的有两种,一种是ESP-01S和ESP-01;这两种有什么区别呢。
外观区别
- ESP01模块上电和信号传输在天线区域右下角有2个LED灯,红色LED上电的时候闪烁,串口通讯的时候蓝色LED闪烁;ESP01s整个模块上只有一个蓝色LED,上电和串口通讯都是蓝色LED闪烁。如图左边是ESP01s,右边是ESP01(图片来源)
- ESP01s模块背面有引脚说明,ESP01背面没有。
电路区别
ESP01s内部电路相较于ESP01做了优化,
- LED灯的管脚发生变化,由ESP01的TXD0变成ESP01s的GPIO2引脚;
- ESP01s模块的IO0、RST、EN引脚上加了上拉电阻,也就是说在连接了3v3引脚后这三个引脚也自动连接上高电平,无需再EN引脚上外接高电平。
本文我们使用的模块是ESP-01s,驱动这款WIFI模块只需要四根线。以下是硬件连接。
1. 物理接线
STM32引脚 | ESP8266引脚 | 功能说明 |
---|---|---|
PA2 | RX | STM32的UART2_TX |
PA3 | TX | STM32的UART2_RX |
3.3V | VCC | 电源输入 |
GND | GND | 共地 |
2. 电路注意事项
电平匹配:ESP8266为3.3V电平,STM32需使用3.3V供电
电源滤波:在VCC与GND间并联100μF+0.1μF电容
抗干扰设计:串口线长超过15cm时需加120Ω终端电阻
三、HAL库驱动开发
1. 手动初始化UART
UART_HandleTypeDef esp8266_handle = {0};
void esp8266_uart_init(uint32_t baudrate)
{
esp8266_handle.Instance = USART2;
esp8266_handle.Init.BaudRate = baudrate;
esp8266_handle.Init.WordLength = UART_WORDLENGTH_8B;
esp8266_handle.Init.StopBits = UART_STOPBITS_1;
esp8266_handle.Init.Parity = UART_PARITY_NONE;
esp8266_handle.Init.HwFlowCtl = UART_HWCONTROL_NONE;
esp8266_handle.Init.Mode = UART_MODE_TX_RX;
HAL_UART_Init(&esp8266_handle);
}
void HAL_UART_MspInit(UART_HandleTypeDef *huart)
{
GPIO_InitTypeDef gpio_init_struct;
if (huart->Instance == USART1) /* 如果是串口1,进行串口1 MSP初始化 */
{
__HAL_RCC_GPIOA_CLK_ENABLE(); /* 使能串口TX脚时钟 */
__HAL_RCC_USART1_CLK_ENABLE(); /* 使能串口时钟 */
gpio_init_struct.Pin = GPIO_PIN_9; /* 串口发送引脚号 */
gpio_init_struct.Mode = GPIO_MODE_AF_PP; /* 复用推挽输出 */
gpio_init_struct.Pull = GPIO_PULLUP; /* 上拉 */
gpio_init_struct.Speed = GPIO_SPEED_FREQ_HIGH; /* IO速度设置为高速 */
HAL_GPIO_Init(GPIOA, &gpio_init_struct);
gpio_init_struct.Pin = GPIO_PIN_10; /* 串口RX脚 模式设置 */
gpio_init_struct.Mode = GPIO_MODE_AF_INPUT;
HAL_GPIO_Init(GPIOA, &gpio_init_struct); /* 串口RX脚 必须设置成输入模式 */
HAL_NVIC_EnableIRQ(USART1_IRQn); /* 使能USART1中断通道 */
HAL_NVIC_SetPriority(USART1_IRQn, 3, 3); /* 组2,最低优先级:抢占优先级3,子优先级3 */
__HAL_UART_ENABLE_IT(huart, UART_IT_RXNE); /* 使能UART1接收中断 */
__HAL_UART_ENABLE_IT(huart, UART_IT_IDLE); /* 使能UART1总线空闲中断 */
}
else if (huart->Instance == USART2) /* 如果是串口2,进行串口2 MSP初始化 */
{
__HAL_RCC_GPIOA_CLK_ENABLE(); /* 使能串口TX脚时钟 */
__HAL_RCC_USART2_CLK_ENABLE(); /* 使能串口时钟 */
gpio_init_struct.Pin = GPIO_PIN_2; /* 串口发送引脚号 */
gpio_init_struct.Mode = GPIO_MODE_AF_PP; /* 复用推挽输出 */
gpio_init_struct.Pull = GPIO_PULLUP; /* 上拉 */
gpio_init_struct.Speed = GPIO_SPEED_FREQ_HIGH; /* IO速度设置为高速 */
HAL_GPIO_Init(GPIOA, &gpio_init_struct);
gpio_init_struct.Pin = GPIO_PIN_3; /* 串口RX脚 模式设置 */
gpio_init_struct.Mode = GPIO_MODE_AF_INPUT;
HAL_GPIO_Init(GPIOA, &gpio_init_struct); /* 串口RX脚 必须设置成输入模式 */
HAL_NVIC_EnableIRQ(USART2_IRQn); /* 使能USART2中断通道 */
HAL_NVIC_SetPriority(USART2_IRQn, 3, 3); /* 组2,最低优先级:抢占优先级3,子优先级3 */
__HAL_UART_ENABLE_IT(huart, UART_IT_RXNE); /* 使能UART2接收中断 */
//__HAL_UART_ENABLE_IT(huart, UART_IT_IDLE); /* 使能UART2总线空闲中断 */
}
}
2. AT指令处理框架
#define ESP8266_RX_BUF_SIZE 128
#define ESP8266_TX_BUF_SIZE 64
#define ESP8266_EOK 0
#define ESP8266_ERROR 1
#define ESP8266_ETIMEOUT 2
#define ESP8266_EINVAL 3
#define ESP8266_STA_MODE 1
#define ESP8266_AP_MODE 2
#define ESP8266_STA_AP_MODE 3
#define ESP8266_SINGLE_CONNECTION 0
#define ESP8266_MULTI_CONNECTION 1
#define WIFI_SSID "kkk"
#define WIFI_PWD "88888888"
uint8_t esp8266_wait_receive(void)
{
if(esp8266_cnt == 0)
return ESP8266_ERROR;
if(esp8266_cnt == esp8266_cntPre)
{
esp8266_cnt = 0;
return ESP8266_EOK;
}
esp8266_cntPre = esp8266_cnt;
return ESP8266_ERROR;
}
void esp8266_rx_clear(void)
{
memset(esp8266_rx_buf, 0, sizeof(esp8266_rx_buf));
esp8266_cnt = 0;
}
void esp8266_receive_data(void)
{
if(esp8266_wait_receive() == ESP8266_EOK)
{
printf("esp8266 recv: %s\r\n", esp8266_rx_buf);
esp8266_rx_clear();
}
}
uint8_t esp8266_send_command(char *cmd, char *res)
{
uint8_t time_out = 250;
esp8266_rx_clear();
HAL_UART_Transmit(&esp8266_handle, (uint8_t *)cmd, strlen(cmd), 100);
while(time_out--)
{
if(esp8266_wait_receive() == ESP8266_EOK)
{
if(strstr((const char*)esp8266_rx_buf, res) != NULL)
return ESP8266_EOK;
}
delay_ms(10);
}
return ESP8266_ERROR;
}
uint8_t esp8266_at_test(void)
{
return esp8266_send_command("AT\r\n", "OK");
}
uint8_t esp8266_set_mode(uint8_t mode)
{
switch(mode)
{
case ESP8266_STA_MODE:
return esp8266_send_command("AT+CWMODE=1\r\n", "OK");
case ESP8266_AP_MODE:
return esp8266_send_command("AT+CWMODE=2\r\n", "OK");
case ESP8266_STA_AP_MODE:
return esp8266_send_command("AT+CWMODE=3\r\n", "OK");
default:
return ESP8266_EINVAL;
}
}
四、核心工作模式实现
1. STA模式(连接路由器)
uint8_t esp8266_join_ap(char *ssid, char *pwd)
{
char cmd[64];
sprintf(cmd, "AT+CWJAP=\"%s\",\"%s\"\r\n", ssid, pwd);
return esp8266_send_command(cmd, "WIFI GOT IP");
}
uint8_t esp8266_connection_mode(uint8_t mode)
{
char cmd[64];
sprintf(cmd, "AT+CIPMUX=%d\r\n", mode);
return esp8266_send_command(cmd, "OK");
}
void esp8266_init(uint32_t baudrate)
{
printf("esp8266初始化开始...\r\n");
esp8266_uart_init(baudrate);
//esp8266的其它初始化
printf("1. 测试esp8266是否存在...\r\n");
while(esp8266_at_test())
delay_ms(500);
printf("2. 设置工作模式为STA...\r\n");
while(esp8266_set_mode(ESP8266_STA_MODE))
delay_ms(500);
printf("3. 设置单路链接模式...\r\n");
while(esp8266_connection_mode(ESP8266_SINGLE_CONNECTION))
delay_ms(500);
printf("4. 连接wifi,SSID: %s, PWD: %s\r\n", WIFI_SSID, WIFI_PWD);
while(esp8266_join_ap(WIFI_SSID, WIFI_PWD))
delay_ms(1500);
printf("ESP8266初始化完成!\r\n");
}
2. AP模式(自建热点)
uint8_t esp8266_build_tcp_server(void)
{
return esp8266_send_command("AT+CIPSERVER=1\r\n", "OK");
}
void esp8266_init(uint32_t baudrate)
{
printf("esp8266初始化开始...\r\n");
esp8266_uart_init(baudrate);
//esp8266的其它初始化
printf("1. 测试esp8266是否存在...\r\n");
while(esp8266_at_test())
delay_ms(500);
printf("2. 设置工作模式为AP...\r\n");
while(esp8266_set_mode(ESP8266_AP_MODE))
delay_ms(500);
printf("3. 设置多路链接模式...\r\n");
while(esp8266_connection_mode(ESP8266_MULTI_CONNECTION))
delay_ms(500);
printf("4. 建立TCP服务器...\r\n");
while(esp8266_build_tcp_server())
delay_ms(500);
printf("ESP8266初始化完成!\r\n");
}
3. TCP客户端模式
uint8_t esp8266_enter_unvarnished(void)
{
uint8_t ret;
ret = esp8266_send_command("AT+CIPMODE=1\r\n", "OK");
ret += esp8266_send_command("AT+CIPSEND\r\n", ">");
if (ret == ESP8266_EOK)
return ESP8266_EOK;
else
return ESP8266_ERROR;
}
void esp8266_init(uint32_t baudrate)
{
printf("esp8266初始化开始...\r\n");
esp8266_uart_init(baudrate);
//esp8266的其它初始化
printf("1. 测试esp8266是否存在...\r\n");
while(esp8266_at_test())
delay_ms(500);
printf("2. 设置工作模式为STA...\r\n");
while(esp8266_set_mode(ESP8266_STA_MODE))
delay_ms(500);
printf("3. 设置单路链接模式...\r\n");
while(esp8266_connection_mode(ESP8266_SINGLE_CONNECTION))
delay_ms(500);
printf("4. 连接wifi,SSID: %s, PWD: %s\r\n", WIFI_SSID, WIFI_PWD);
while(esp8266_join_ap(WIFI_SSID, WIFI_PWD))
delay_ms(1500);
printf("5. 连接TCP服务器,server_ip:%s, server_port:%s\r\n", TCP_SERVER_IP, TCP_SERVER_PORT);
while(esp8266_connect_tcp_server(TCP_SERVER_IP, TCP_SERVER_PORT))
delay_ms(500);
printf("6. 进入到透传模式...\r\n");
while(esp8266_enter_unvarnished())
delay_ms(500);
printf("ESP8266已连接上TCP服务器并进入透传模式\r\n");
printf("ESP8266初始化完成!\r\n");
}
4. 混合模式(STA+AP)
void ESP8266_Hybrid_Mode(void) {
// 设置混合模式
esp8266_send_command("AT+CWMODE=3\r\n", "OK");
// 同时连接路由器和开启AP
esp8266_send_command("AT+CWJAP=\"router_ssid\",\"router_pwd\"\r\n", "OK");
esp8266_send_command("AT+CWSAP=\"Hybrid_AP\",\"87654321\",6,4\r\n", "OK");
}
五、高级功能实现
这种模式需要连接EN引脚,和RST引脚
STM32引脚 | ESP8266引脚 | 功能说明 |
---|---|---|
PA0 | EN | 模块使能(高电平有效) |
PA1 | RST | 硬件复位 |
1. 低功耗控制
// 进入Modem-Sleep模式
void ESP8266_Enter_LightSleep(void) {
esp8266_send_command("AT+SLEEP=1\r\n", "OK");
}
// 唤醒模块
void ESP8266_WakeUp(void) {
HAL_GPIO_WritePin(EN_GPIO_Port, EN_Pin, GPIO_PIN_SET);
HAL_Delay(100);
}
2. 数据透传模式
uint8_t esp8266_enter_unvarnished(void)
{
uint8_t ret;
ret = esp8266_send_command("AT+CIPMODE=1\r\n", "OK");
ret += esp8266_send_command("AT+CIPSEND\r\n", ">");
if (ret == ESP8266_EOK)
return ESP8266_EOK;
else
return ESP8266_ERROR;
}
六、调试技巧与常见问题
1. AT指令调试方法
指令格式化:每条指令必须以 \r\n 结尾
响应超时:根据网络状况调整超时时间(建议500ms~5000ms)
错误码解析:
- ERROR:指令格式错误
- FAIL:网络连接失败
- SEND OK:数据发送成功
2. 常见问题解决
问题现象 | 排查方向 | 解决方案 |
---|---|---|
模块无响应 | 电源/接线/波特率 | 检查3.3V供电是否稳定 |
AT指令返回乱码 | 波特率不匹配 | 尝试115200或9600波特率 |
无法连接WiFi | SSID/密码错误/信号强度 | 使用AT+CWLAP扫描周围热点 |
TCP连接频繁断开 | 路由器NAT超时设置 | 发送心跳包保持连接 |
七、性能优化建议
环形缓冲区:使用circular buffer管理接收数据
DMA传输:对大数据量传输启用UART DMA模式
指令压缩:合并多条AT指令(如AT+CWJAP_CUR=“ssid”,“pwd”;+CIPMUX=1)
状态机设计:用有限状态机(FSM)管理模块工作流程
八、完整工程示例
void esp8266_init(uint32_t baudrate)
{
printf("esp8266初始化开始...\r\n");
esp8266_uart_init(baudrate);
//esp8266的其它初始化
// 退出透传
printf("退出透传\r\n");
esp8266_send_command("+++", "ready");
delay_ms(500);
printf("0. 重启esp8266...\r\n");
printf("status = %d\r\n",esp8266_send_command("AT+RST\r\n", "ready"));
delay_ms(500);
printf("1. 测试esp8266是否存在...\r\n");
while(esp8266_at_test())
delay_ms(500);
printf("2. 设置工作模式为STA...\r\n");
while(esp8266_set_mode(ESP8266_STA_MODE))
delay_ms(500);
printf("3. 设置单路链接模式...\r\n");
while(esp8266_connection_mode(ESP8266_SINGLE_CONNECTION))
delay_ms(500);
printf("4. 连接wifi,SSID: %s, PWD: %s\r\n", WIFI_SSID, WIFI_PWD);
while(esp8266_join_ap(WIFI_SSID, WIFI_PWD))
delay_ms(1500);
oled_fill(0x00); // 清空
oled_show_string(0,0,WIFI_SSID,16);
printf("5. 连接TCP服务器,server_ip:%s, server_port:%s\r\n", TCP_SERVER_IP, TCP_SERVER_PORT);
while(esp8266_connect_tcp_server(TCP_SERVER_IP, TCP_SERVER_PORT))
delay_ms(500);
oled_show_string(0,2,"suscess",16);
oled_show_string(0,4,TCP_SERVER_IP,16);
printf("6. 进入到透传模式...\r\n");
while(esp8266_enter_unvarnished())
delay_ms(500);
printf("ESP8266已连接上TCP服务器并进入透传模式\r\n");
printf("ESP8266初始化完成!\r\n");
}
// main.c
int main(void)
{
HAL_Init(); /* 初始化HAL库 */
stm32_clock_init(RCC_PLL_MUL9); /* 设置时钟, 72Mhz */
uart1_init(115200);
printf("hello world!\r\n");
adc_dma_init((uint32_t *)&adc_result);
esp8266_init(115200);// 初始化esp8266,连接socket
while(1)
{
// do something
}
}
九、总结
通过HAL库直接操作STM32F103C8T6的UART外设,结合ESP8266的AT指令集,开发者可以灵活实现WiFi模块的多种工作模式。本方案避免了CubeMX的依赖,强调底层寄存器操作与协议解析能力,为物联网终端设备开发提供了高可靠性的通信基础。