STM32 ESP8266 WiFi模块驱动
1. 简介
ESP8266是一款高度集成的WiFi芯片,可以为其他设备提供WiFi联网功能。本笔记记录了基于STM32 HAL库的ESP8266驱动实现,包括硬件连接、初始化配置、AT指令交互等关键部分。
项目源码仓库:STM32_Sensor_Drives
2. 硬件连接
2.1 引脚定义
ESP8266与STM32的连接引脚定义如下:
/******************************** ESP8266 连接引脚定义 ***********************************/
#define macESP8266_CH_PD_PORT GPIOA
#define macESP8266_CH_PD_PIN GPIO_PIN_11
#define macESP8266_RST_PORT GPIOA
#define macESP8266_RST_PIN GPIO_PIN_12
#define macESP8266_USART_BAUD_RATE 115200
#define macESP8266_USART_TX_PORT GPIOB
#define macESP8266_USART_TX_PIN GPIO_Pin_10
#define macESP8266_USART_RX_PORT GPIOB
#define macESP8266_USART_RX_PIN GPIO_Pin_11
#define macESP8266_USARTx huart3
2.2 GPIO初始化
在gpio.c
中,我们初始化了ESP8266所需的GPIO引脚:
void MX_GPIO_Init(void)
{
GPIO_InitTypeDef GPIO_InitStruct = {0};
/* GPIO Ports Clock Enable */
__HAL_RCC_GPIOC_CLK_ENABLE();
__HAL_RCC_GPIOD_CLK_ENABLE();
__HAL_RCC_GPIOA_CLK_ENABLE();
__HAL_RCC_GPIOB_CLK_ENABLE();
/*Configure GPIO pin Output Level */
HAL_GPIO_WritePin(GPIOA, GPIO_PIN_4|GPIO_PIN_11|GPIO_PIN_12, GPIO_PIN_SET);
/*Configure GPIO pins : PA4 PA11 PA12 */
GPIO_InitStruct.Pin = GPIO_PIN_4|GPIO_PIN_11|GPIO_PIN_12;
GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
GPIO_InitStruct.Pull = GPIO_NOPULL;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
// ... 其他GPIO配置 ...
}
3. 数据结构定义
3.1 ESP8266工作模式枚举
/******************************* ESP8266 数据类型定义 ***************************/
typedef enum{
STA, // 客户端模式
AP, // 热点模式
STA_AP // 客户端+热点模式
} ENUM_Net_ModeTypeDef;
typedef enum{
enumTCP, // TCP协议
enumUDP, // UDP协议
} ENUM_NetPro_TypeDef;
typedef enum{
Multiple_ID_0 = 0,
Multiple_ID_1 = 1,
Multiple_ID_2 = 2,
Multiple_ID_3 = 3,
Multiple_ID_4 = 4,
Single_ID_0 = 5,
} ENUM_ID_NO_TypeDef;
typedef enum{
OPEN = 0, // 开放模式
WEP = 1, // WEP加密
WPA_PSK = 2, // WPA-PSK加密
WPA2_PSK = 3, // WPA2-PSK加密
WPA_WPA2_PSK = 4, // WPA/WPA2混合加密
} ENUM_AP_PsdMode_TypeDef;
3.2 数据接收缓冲区结构
/******************************* ESP8266 外部全局变量声明 ***************************/
#define RX_BUF_MAX_LEN 1024 //最大接收缓存字节数
extern struct STRUCT_USARTx_Fram //串口数据帧的处理结构体
{
char Data_RX_BUF [ RX_BUF_MAX_LEN ]; // 数据接收缓冲区
union {
__IO uint16_t InfAll;
struct {
__IO uint16_t FramLength :15; // 数据帧长度
__IO uint16_t FramFinishFlag :1; // 数据帧接收完成标志
} InfBit;
}Inf;
} strEsp8266_Fram_Record;
4. 函数宏定义
/*********************************************** ESP8266 函数宏定义 *******************************************/
#define macESP8266_CH_ENABLE() HAL_GPIO_WritePin(macESP8266_CH_PD_PORT, macESP8266_CH_PD_PIN, GPIO_PIN_SET)
#define macESP8266_CH_DISABLE() HAL_GPIO_WritePin(macESP8266_CH_PD_PORT, macESP8266_CH_PD_PIN, GPIO_PIN_RESET)
#define macESP8266_RST_HIGH_LEVEL() HAL_GPIO_WritePin(macESP8266_RST_PORT, macESP8266_RST_PIN, GPIO_PIN_SET)
#define macESP8266_RST_LOW_LEVEL() HAL_GPIO_WritePin(macESP8266_RST_PORT, macESP8266_RST_PIN, GPIO_PIN_RESET)
5. 串口重定向
为了方便打印调试信息,重定向了printf
函数:
/* 串口重定向相关定义 */
extern UART_HandleTypeDef *current_huart;
#define printf_log(...) do { \
current_huart = &huart2; \
printf(__VA_ARGS__); \
} while(0)
#define printf_wifi(...) do { \
current_huart = &macESP8266_USARTx; \
printf(__VA_ARGS__); \
} while(0)
/* printf重定向实现 */
int fputc(int ch, FILE *f)
{
if (f == stdout) // 仅处理标准输出
{
HAL_UART_Transmit(current_huart, (uint8_t *)&ch, 1, 100); // 阻塞发送
if (ch == '\n') // 发送\n时自动补充\r
HAL_UART_Transmit(current_huart, (uint8_t *)"\r", 1, 100);
}
return ch;
}
6. ESP8266初始化与配置
6.1 ESP8266启动函数
/**
* @brief ESP8266 (Sta Tcp Client)透传
* @param 无
* @retval 无
*/
void ESP8266_start(void)
{
printf_log("\r\n正在配置 ESP8266 ......\r\n");
HAL_UART_Receive_IT(&macESP8266_USARTx, &UART_TEMP_CHAR, 1);
macESP8266_CH_ENABLE(); // 使能ESP8266
ESP8266_AT_Test(); // AT测试
ESP8266_Net_Mode_Choose(STA_AP); // 设置为STA+AP模式
// 连接到指定的WiFi热点
while (!ESP8266_JoinAP(macUser_ESP8266_ApSsid, macUser_ESP8266_ApPwd))
;
ESP8266_Cmd("AT+CIFSR", "OK", 0, 1000); // 查询IP地址
ESP8266_Cmd("AT+CIPMUX=1", "OK", 0, 1000); // 启用多连接
ESP8266_Cmd("AT+CIPSERVER=1,8288", "OK", 0, 1000); // 创建TCP服务器
// ESP8266_Cmd("AT+CIPSTART="TCP",192.168.1.1,8000","OK",0,1000);
printf_log("\r\n配置 ESP8266 完毕\r\n");
}
6.2 ESP8266复位函数
/*
* 函数名:ESP8266_Rst
* 描述 :重启WF-ESP8266模块
* 输入 :无
* 返回 : 无
* 调用 :被 ESP8266_AT_Test 调用
*/
void ESP8266_Rst(void)
{
#if 0
ESP8266_Cmd ( "AT+RST", "OK", "ready", 2500 );
#else
macESP8266_RST_LOW_LEVEL(); // 复位引脚拉低
HAL_Delay(500);
macESP8266_RST_HIGH_LEVEL(); // 复位引脚拉高
#endif
}
7. AT指令交互
7.1 AT指令发送与响应检查
/*
* 函数名:ESP8266_Cmd
* 描述 :对WF-ESP8266模块发送AT指令
* 输入 :cmd,待发送的指令
* reply1,reply2,期待的响应,为NULL表不需响应,两者为或逻辑关系
* waittime,等待响应的时间
* 返回 : 1,指令发送成功
* 0,指令发送失败
* 调用 :被外部调用
*/
uint8_t ESP8266_Cmd(char *cmd, char *reply1, char *reply2, uint32_t waittime)
{
strEsp8266_Fram_Record.Inf.InfBit.FramLength = 0; // 从新开始接收新的数据包
printf_wifi("%s\r\n", cmd); // 发送AT指令
if ((reply1 == NULL) && (reply2 == NULL)) // 不需要接收数据
return 1;
HAL_Delay(waittime); // 延时等待响应
strEsp8266_Fram_Record.Data_RX_BUF[strEsp8266_Fram_Record.Inf.InfBit.FramLength] = '\0';
printf_log("%s", strEsp8266_Fram_Record.Data_RX_BUF); // 打印响应数据
// 检查响应是否包含期望的字符串
if ((reply1 != NULL) && (reply2 != NULL))
{
if (strstr(strEsp8266_Fram_Record.Data_RX_BUF, reply1) != NULL ||
strstr(strEsp8266_Fram_Record.Data_RX_BUF, reply2) != NULL)
return 1;
return 0;
}
else if (reply1 != NULL)
{
if (strstr(strEsp8266_Fram_Record.Data_RX_BUF, reply1) != NULL)
return 1;
return 0;
}
else
{
if (strstr(strEsp8266_Fram_Record.Data_RX_BUF, reply2) != NULL)
return 1;
return 0;
}
}
7.2 AT测试函数
/*
* 函数名:ESP8266_AT_Test
* 描述 :对WF-ESP8266模块进行AT测试启动
* 输入 :无
* 返回 : 无
* 调用 :被外部调用
*/
void ESP8266_AT_Test(void)
{
char count = 0;
macESP8266_RST_HIGH_LEVEL();
HAL_Delay(1000);
while (count < 10)
{
if (ESP8266_Cmd("AT", "OK", NULL, 500)) // 发送AT测试指令
return;
ESP8266_Rst(); // 如果失败则复位ESP8266
++count;
}
}
8. WiFi连接功能
8.1 设置工作模式
/*
* 函数名:ESP8266_Net_Mode_Choose
* 描述 :选择WF-ESP8266模块的工作模式
* 输入 :enumMode,工作模式
* 返回 : 1,选择成功
* 0,选择失败
* 调用 :被外部调用
*/
uint8_t ESP8266_Net_Mode_Choose(ENUM_Net_ModeTypeDef enumMode)
{
switch (enumMode)
{
case STA: // 客户端模式
return ESP8266_Cmd("AT+CWMODE=1", "OK", "no change", 2500);
case AP: // 热点模式
return ESP8266_Cmd("AT+CWMODE=2", "OK", "no change", 2500);
case STA_AP: // 客户端+热点模式
return ESP8266_Cmd("AT+CWMODE=3", "OK", "no change", 2500);
default:
return 0;
}
}
8.2 连接WiFi热点
/*
* 函数名:ESP8266_JoinAP
* 描述 :WF-ESP8266模块连接外部WiFi
* 输入 :pSSID,WiFi名称字符串
* :pPassWord,WiFi密码字符串
* 返回 : 1,连接成功
* 0,连接失败
* 调用 :被外部调用
*/
uint8_t ESP8266_JoinAP(char *pSSID, char *pPassWord)
{
char cCmd[120];
sprintf(cCmd, "AT+CWJAP=\"%s\",\"%s\"", pSSID, pPassWord);
return ESP8266_Cmd(cCmd, "OK", NULL, 5000);
}
8.3 创建WiFi热点
/*
* 函数名:ESP8266_BuildAP
* 描述 :WF-ESP8266模块创建WiFi热点
* 输入 :pSSID,WiFi名称字符串
* :pPassWord,WiFi密码字符串
* :enunPsdMode,WiFi加密方式代号字符串
* 返回 : 1,创建成功
* 0,创建失败
* 调用 :被外部调用
*/
uint8_t ESP8266_BuildAP(char *pSSID, char *pPassWord, ENUM_AP_PsdMode_TypeDef enunPsdMode)
{
char cCmd[120];
sprintf(cCmd, "AT+CWSAP=\"%s\",\"%s\",1,%d", pSSID, pPassWord, enunPsdMode);
return ESP8266_Cmd(cCmd, "OK", 0, 1000);
}
9. TCP/IP通信功能
9.1 启用多连接
/*
* 函数名:ESP8266_Enable_MultipleId
* 描述 :WF-ESP8266模块启动多连接
* 输入 :enumEnUnvarnishTx,配置是否多连接
* 返回 : 1,配置成功
* 0,配置失败
* 调用 :被外部调用
*/
uint8_t ESP8266_Enable_MultipleId(FunctionalState enumEnUnvarnishTx)
{
return ESP8266_Cmd("AT+CIPMUX=%d", "OK", 0, 500);
}
9.2 连接服务器
/*
* 函数名:ESP8266_Link_Server
* 描述 :WF-ESP8266模块连接外部服务器
* 输入 :enumE,网络协议
* :ip,服务器IP字符串
* :ComNum,服务器端口字符串
* :id,模块连接服务器的ID
* 返回 : 1,连接成功
* 0,连接失败
* 调用 :被外部调用
*/
uint8_t ESP8266_Link_Server(ENUM_NetPro_TypeDef enumE, char *ip, char *ComNum, ENUM_ID_NO_TypeDef id)
{
char cStr[100] = {0}, cCmd[120];
switch (enumE)
{
case enumTCP:
sprintf(cStr, "\"%s\",\"%s\",%s", "TCP", ip, ComNum);
break;
case enumUDP:
sprintf(cStr, "\"%s\",\"%s\",%s", "UDP", ip, ComNum);
break;
default:
break;
}
if (id < 5)
sprintf(cCmd, "AT+CIPSTART=%d,%s", id, cStr);
else
sprintf(cCmd, "AT+CIPSTART=%s", cStr);
return ESP8266_Cmd(cCmd, "OK", "ALREAY CONNECT", 4000);
}
9.3 创建/关闭TCP服务器
/*
* 函数名:ESP8266_StartOrShutServer
* 描述 :WF-ESP8266模块开启或关闭服务器模式
* 输入 :enumMode,开启/关闭
* :pPortNum,服务器端口号字符串
* :pTimeOver,服务器超时时间字符串,单位:秒
* 返回 : 1,操作成功
* 0,操作失败
* 调用 :被外部调用
*/
uint8_t ESP8266_StartOrShutServer(FunctionalState enumMode, char *pPortNum, char *pTimeOver)
{
char cCmd1[120], cCmd2[120];
if (enumMode) // 开启服务器
{
sprintf(cCmd1, "AT+CIPSERVER=%d,%s", 1, pPortNum);
sprintf(cCmd2, "AT+CIPSTO=%s", pTimeOver);
return (ESP8266_Cmd(cCmd1, "OK", 0, 500) &&
ESP8266_Cmd(cCmd2, "OK", 0, 500));
}
else // 关闭服务器
{
sprintf(cCmd1, "AT+CIPSERVER=%d,%s", 0, pPortNum);
return ESP8266_Cmd(cCmd1, "OK", 0, 500);
}
}
10. 数据收发功能
10.1 发送数据
/*
* 函数名:ESP8266_SendString
* 描述 :WF-ESP8266模块发送字符串
* 输入 :enumEnUnvarnishTx,声明是否已使能了透传模式
* :pStr,要发送的字符串
* :ulStrLength,要发送的字符串的字节数
* :ucId,哪个ID发送的字符串
* 返回 : 1,发送成功
* 0,发送失败
* 调用 :被外部调用
*/
uint8_t ESP8266_SendString(FunctionalState enumEnUnvarnishTx, char *pStr, uint32_t ulStrLength, ENUM_ID_NO_TypeDef ucId)
{
char cStr[20];
uint8_t bRet = 0;
if (enumEnUnvarnishTx) // 透传模式
{
printf_wifi("%s", pStr);
bRet = 1;
}
else // 非透传模式
{
if (ucId < 5)
sprintf(cStr, "AT+CIPSEND=%d,%d", ucId, ulStrLength + 2);
else
sprintf(cStr, "AT+CIPSEND=%d", ulStrLength + 2);
ESP8266_Cmd(cStr, "> ", 0, 1000); // 等待发送指令的响应
bRet = ESP8266_Cmd(pStr, "SEND OK", 0, 1000); // 发送数据
}
return bRet;
}
10.2 接收数据
/*
* 函数名:ESP8266_ReceiveString
* 描述 :WF-ESP8266模块接收字符串
* 输入 :enumEnUnvarnishTx,声明是否已使能了透传模式
* 返回 : 接收到的字符串首地址
* 调用 :被外部调用
*/
char *ESP8266_ReceiveString(FunctionalState enumEnUnvarnishTx)
{
char *pRecStr = 0;
strEsp8266_Fram_Record.Inf.InfBit.FramLength = 0;
strEsp8266_Fram_Record.Inf.InfBit.FramFinishFlag = 0;
while (!strEsp8266_Fram_Record.Inf.InfBit.FramFinishFlag)
; // 等待接收完成
strEsp8266_Fram_Record.Data_RX_BUF[strEsp8266_Fram_Record.Inf.InfBit.FramLength] = '\0';
if (enumEnUnvarnishTx) // 透传模式
pRecStr = strEsp8266_Fram_Record.Data_RX_BUF;
else // 非透传模式
{
if (strstr(strEsp8266_Fram_Record.Data_RX_BUF, "+IPD"))
pRecStr = strEsp8266_Fram_Record.Data_RX_BUF;
}
return pRecStr;
}
10.3 UART接收中断回调
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
if (huart == &macESP8266_USARTx)
{
if (strEsp8266_Fram_Record.Inf.InfBit.FramLength < (RX_BUF_MAX_LEN - 1))
strEsp8266_Fram_Record.Data_RX_BUF[strEsp8266_Fram_Record.Inf.InfBit.FramLength++] = UART_TEMP_CHAR;
if (HAL_UART_GetState(&macESP8266_USARTx) == HAL_UART_STATE_READY)
{
strEsp8266_Fram_Record.Inf.InfBit.FramFinishFlag = 1;
ucTcpClosedFlag = strstr(strEsp8266_Fram_Record.Data_RX_BUF, "CLOSED\r\n") ? 1 : 0;
strEsp8266_Fram_Record.Data_RX_BUF[strEsp8266_Fram_Record.Inf.InfBit.FramLength] = '\0';
if (strEsp8266_Fram_Record.Data_RX_BUF[strEsp8266_Fram_Record.Inf.InfBit.FramLength-1] == '}'){
printf_log(strEsp8266_Fram_Record.Data_RX_BUF);
strEsp8266_Fram_Record.Inf.InfBit.FramLength = 0;
}
}
HAL_UART_Receive_IT(&macESP8266_USARTx, &UART_TEMP_CHAR, 1);
}
}
11. 主程序调用
在main.c
中,我们初始化外设并调用ESP8266启动函数:
int main(void)
{
/* 初始化HAL库 */
HAL_Init();
/* 配置系统时钟 */
SystemClock_Config();
/* 初始化外设 */
MX_GPIO_Init();
MX_USART2_UART_Init();
MX_USART3_UART_Init();
/* 启动ESP8266 WiFi模块 */
ESP8266_start();
/* 主循环 */
while (1)
{
HAL_Delay(200);
}
}
12. 总结
本驱动实现了基于STM32 HAL库的ESP8266 WiFi模块驱动,主要功能包括:
- ESP8266初始化与复位
- AT指令交互
- WiFi连接与热点创建
- TCP/IP服务器创建与客户端连接
- 数据收发功能
通过这些功能,可以实现STM32与ESP8266的通信,进而实现WiFi联网功能,为物联网应用提供基础。
使用时,只需修改macUser_ESP8266_ApSsid
和macUser_ESP8266_ApPwd
为实际的WiFi名称和密码即可。