前言
上一节我们学习了串口的简单使用,本节我们增加难度,做一个demo通过AT指令控制ESP8266,使用DMA方式接收ESP8266发来的数据,后续我们便开始通过ESP8266连接物联网云平台,敬请关注。
一、准备
1. ESP8266硬件准备
准备ESP8266模块,本实验使用便宜好用的ESP-01s模块,某宝只需要5RMB左右,很适合新手测试使用,ESP-01S默认支持AT指令,所谓AT指令是说WIFI厂商把复杂的TCP/IP协议坐在了ESP8266芯片内部了,单片机只需要通过串口发送AT+CMD形式的指令即可达到调用TCP/IP协议栈的功能,极大的降低了学习和开发成本。
2. ESP8266固件准备
更新AT MQTT固件,进入安信可官网,下载MQTT透传固件(固件号:1471),参考之前文章即可轻松完成烧录。
3. 硬件连接
注意串口2连接ESP01S模块,串口1连接串口工具
二、实例
1. 建立工程
将上节串口实验代码复制并修改名字为:7.WiFi,通过CubeMx关闭Usart2,打开Usart3,并设置DMA方法方式接收数据。
取消勾选USART3 global interrupt中断回调,打开DMA1 channel3 global interrupt。
Keil打开工程,查看stm32f1xx_it.c中相关配置如下:
/**
* @brief This function handles DMA1 channel3 global interrupt.
*/
void DMA1_Channel3_IRQHandler(void)
{
/* USER CODE BEGIN DMA1_Channel3_IRQn 0 */
/* USER CODE END DMA1_Channel3_IRQn 0 */
HAL_DMA_IRQHandler(&hdma_usart3_rx);
/* USER CODE BEGIN DMA1_Channel3_IRQn 1 */
/* USER CODE END DMA1_Channel3_IRQn 1 */
}
按照下图顺序,创建Application/User/Driver文件夹。
同时在本地根目录中创建User文件夹,文件夹中创建esp8266.c和esp8266.h两个文件。
在Keil中Application/User/Driver处右击选择Add Existing Files to Group ‘Application/user/Driver’…选择上述创建的esp8266.c文件,将文件加入到工程。
将/User文件路径按照下图方式添加到工程,以便能访问到esp8266.h文件,
编辑esp8266.h内容如下,增加#ifndef #define #endif格式文件,防止因为重复包含头文件导致错误;重点关注Uart_Frame_Record_t结构体,该结构体内部访问共用体,希望结构体直接访问共用体内元素,需要增加#pragma anon_unions。
#ifndef __ESP8266_H
#define __ESP8266_H
#include "main.h"
#include <stdio.h>
#include <string.h>
#include <stdbool.h>
#pragma anon_unions
// ESP8266 printf
#define ESP8266_USART(fmt, ...) USART_printf (fmt, ##__VA_ARGS__)
#define PC_USART(fmt, ...) printf(fmt, ##__VA_ARGS__) //这是串口打印函数,串口1,执行printf后会自动执行fput函数,重定向了printf。
#define RX_BUF_MAX_LEN 1024 //最大字节数
//ESP8266模式选择
typedef enum
{
STA,
AP,
STA_AP
}ENUM_Net_ModeTypeDef;
//网络传输层协议,枚举类型
typedef enum{
enumTCP,
enumUDP,
} 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{
WIFI_DISCONNECT= 0,
WIFI_CONNECTED = 1,
WIFI_GOT_IP = 2,
WIFI_CLOUD_CONNECTED = 3,
WIFI_CLOUD_FAILED = 4,
} ENUM_WIFI_STATUS_TypeDef;
typedef struct _Uart_Frame_Record_t //数据帧结构体
{
char Data_RX_BUF[RX_BUF_MAX_LEN];
union
{
__IO uint16_t InfAll;
__IO uint16_t FramLength;
};
}Uart_Frame_Record_t;
extern Uart_Frame_Record_t Uart_Frame_Record;
extern volatile int wifi_connect_status;
//初始化和TCP功能函数
void ESP8266_Init(uint32_t bound);
void ESP8266_ATE0(void);
bool ESP8266_Send_AT_Cmd(char *cmd,char *ack1,char *ack2,uint32_t time);
bool ESP8266_Net_Mode_Choose(ENUM_Net_ModeTypeDef enumMode);
bool ESP8266_JoinAP( char * pSSID, char * pPassWord );
bool ESP8266_Enable_MultipleId ( FunctionalState enumEnUnvarnishTx );
bool ESP8266_Link_Server(ENUM_NetPro_TypeDef enumE, char * ip, char * ComNum, ENUM_ID_NO_TypeDef id);
bool ESP8266_UnvarnishSend ( void );
void ESP8266_ExitUnvarnishSend ( void );
uint8_t ESP8266_Get_LinkStatus ( void );
uint8_t AT_Printf(char* fmt,...);
uint8_t ESP8266_SendCMD(uint8_t *cmd, uint8_t *ack, uint16_t waittime);
uint8_t ESP8266_SendData(uint8_t *tbuf, uint16_t len);
uint8_t ESP8266_APInit(char *name, char *password);
uint8_t ESP8266_STAInit(void);
uint8_t ESP8266_STAConnect(char *name, char *password);
#endif
esp8266.c中内容如下所示:
#include "esp8266.h"
#include "usart.h"
volatile int wifi_connect_status = 0;
Uart_Frame_Record_t Uart_Frame_Record= { 0 }; //定义了一个数据帧结构体
//对ESP8266模块发送AT指令
// cmd 待发送的指令
// ack1,ack2;期待的响应,为NULL表不需响应,两者为或逻辑关系
// time 等待响应时间
//返回1发送成功, 0失败
bool ESP8266_Send_AT_Cmd(char *cmd,char *ack1,char *ack2,uint32_t time)
{
Uart_Frame_Record.FramLength = 0; //重新接收新的数据包
AT_Printf("%s\r\n", cmd);
if(ack1==0&&ack2==0) //不需要接收数据
{
return true;
}
delay_ms(time); //延时
Uart_Frame_Record.Data_RX_BUF[Uart_Frame_Record.FramLength ] = '\0';
printf("------ %s", Uart_Frame_Record.Data_RX_BUF);
if(ack1!=0&&ack2!=0)
{
return ( ( bool ) strstr ( Uart_Frame_Record.Data_RX_BUF, ack1 ) ||
( bool ) strstr ( Uart_Frame_Record.Data_RX_BUF, ack2 ) );
}
else if( ack1 != 0 ) //strstr(s1,s2);检测s2是否为s1的一部分,是返回该位置,否则返回false,它强制转换为bool类型了
return ( ( bool ) strstr ( Uart_Frame_Record.Data_RX_BUF, ack1 ) );
else
return ( ( bool ) strstr ( Uart_Frame_Record.Data_RX_BUF, ack2 ) );
}
//发送恢复出厂默认设置指令将模块恢复成出厂设置
void ESP8266_ATE0(void)
{
char count=0;
delay_ms(1000);
while(count < 10)
{
if(ESP8266_Send_AT_Cmd("ATE0","OK",NULL,500))
{
printf("OK\r\n");
return;
}
++ count;
}
}
//发送恢复出厂默认设置指令将模块恢复成出厂设置
void ESP8266_AT_Restore(void)
{
char count=0;
delay_ms(1000);
while(count < 10)
{
if(ESP8266_Send_AT_Cmd("AT+RESTORE","OK",NULL,500))
{
printf("OK\r\n");
return;
}
++ count;
}
}
//选择ESP8266的工作模式
// enumMode 模式类型
//成功返回true,失败返回false
bool ESP8266_Net_Mode_Choose(ENUM_Net_ModeTypeDef enumMode)
{
switch ( enumMode )
{
case STA:
return ESP8266_Send_AT_Cmd ( "AT+CWMODE=1", "OK", "no change", 2500 );
case AP:
return ESP8266_Send_AT_Cmd ( "AT+CWMODE=2", "OK", "no change", 2500 );
case STA_AP:
return ESP8266_Send_AT_Cmd ( "AT+CWMODE=3", "OK", "no change", 2500 );
default:
return false;
}
}
//ESP8266连接外部的WIFI
//pSSID WiFi帐号
//pPassWord WiFi密码
//设置成功返回true 反之false
bool ESP8266_JoinAP( char * pSSID, char * pPassWord)
{
char cCmd [120];
sprintf ( cCmd, "AT+CWJAP=\"%s\",\"%s\"", pSSID, pPassWord );
return ESP8266_Send_AT_Cmd( cCmd, "OK", NULL, 5000 );
}
//ESP8266 透传使能
//enumEnUnvarnishTx 是否多连接,bool类型
//设置成功返回true,反之false
bool ESP8266_Enable_MultipleId (FunctionalState enumEnUnvarnishTx )
{
char cStr [20];
sprintf ( cStr, "AT+CIPMUX=%d", ( enumEnUnvarnishTx ? 1 : 0 ) );
return ESP8266_Send_AT_Cmd ( cStr, "OK", 0, 500 );
}
//ESP8266 连接服务器
//enumE 网络类型
//ip ,服务器IP
//ComNum 服务器端口
//id,连接号,确保通信不受外界干扰
//设置成功返回true,反之fasle
bool 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_Send_AT_Cmd ( cCmd, "OK", "ALREAY CONNECT", 4000 );
}
//透传使能
//设置成功返回true, 反之false
bool ESP8266_UnvarnishSend ( void )
{
if (!ESP8266_Send_AT_Cmd ( "AT+CIPMODE=1", "OK", 0, 500 ))
return false;
return
ESP8266_Send_AT_Cmd( "AT+CIPSEND", "OK", ">", 500 );
}
//ESP8266 检测连接状态
//返回0:获取状态失败
//返回2:获得ip
//返回3:建立连接
//返回4:失去连接
uint8_t ESP8266_Get_LinkStatus ( void )
{
if (ESP8266_Send_AT_Cmd( "AT+CIPSTATUS", "OK", 0, 500 ) )
{
if ( strstr ( Uart_Frame_Record.Data_RX_BUF, "STATUS:2\r\n" ) )
return 2;
else if ( strstr ( Uart_Frame_Record.Data_RX_BUF, "STATUS:3\r\n" ) )
return 3;
else if ( strstr ( Uart_Frame_Record.Data_RX_BUF, "STATUS:4\r\n" ) )
return 4;
}
return 0;
}
//检测应答命令
static uint8_t* ESP8266_CheckCMD(uint8_t *str)
{
// char *strx = 0;
// if(Uart_Frame_Record.FramLength&0x8000)
// {
// Uart_Frame_Record.Data_RX_BUF[Uart_Frame_Record.FramLength&0x7FFF] = 0;//添加结束符
// strx = strstr((const char*)Uart_Frame_Record.Data_RX_BUF,(const char*)str);
// }
// return (uint8_t*)strx;
return (uint8_t *)strstr((const char*)Uart_Frame_Record.Data_RX_BUF,(const char*)str);
}
// 串口2 PA2 TX PA3 RX
void Dev_UART3SendStr(uint8_t* tbuf, uint16_t tlen, uint8_t tByte){
uint16_t i = 0,j = 0;
if(tlen > 0)
j = tlen;
else
j = strlen((const char*)tbuf);
for( i = 0; i < j; i++)
{
if((tByte>0)&&(i==2))
{
HAL_UART_Transmit(&huart3, &tByte, 1, 10);
}
HAL_UART_Transmit(&huart3, &tbuf[i], 1, 10);
}
}
//cmd:发送的命令字符串
//ack:期待的应答结果,如果为空,则表示不需要等待应答
//返回值:0,发送成功 1,发送失败
uint8_t ESP8266_SendCMD(uint8_t *cmd, uint8_t *ack,uint16_t waittime)
{
Uart_Frame_Record.FramLength = 0;
memset((void *)Uart_Frame_Record.Data_RX_BUF, 0, sizeof(Uart_Frame_Record.Data_RX_BUF));
Dev_UART3SendStr(cmd, 0, 0);
if(ack&&waittime)
{
while(--waittime)
{
delay_ms(10);
if(Uart_Frame_Record.FramLength&0x8000)
{
if(ESP8266_CheckCMD(ack))
{
printf("AT: %s, ack: %s\r\n", cmd, Uart_Frame_Record.Data_RX_BUF);
Uart_Frame_Record.FramLength = 0;
return 0;
}
else
{
Uart_Frame_Record.FramLength = 0;
return 1;
}
}
}
if(waittime==0)
{
return 1;
}
}
return 1;
}
uint8_t AT_Printf(char* fmt,...)
{
uint8_t tbuf[300] = {0};
uint16_t j = 0;
va_list ap;
va_start(ap, fmt);
vsprintf((char*)tbuf, fmt, ap);
va_end(ap);
j = strlen((const char*)tbuf);
HAL_UART_Transmit(&huart3, tbuf, j, 10);
return 0;
}
uint8_t ESP8266_SendData(uint8_t *tbuf, uint16_t len)
{
HAL_UART_Transmit(&huart3, tbuf, len, 10);
return 0;
}
// STA模式下获取本地IP
void ESP8266_GetLocalIP(uint8_t* ipbuf)
{
uint8_t *p,*p1;
if(ESP8266_SendCMD( (uint8_t *)"AT+CIFSR\r\n", (uint8_t *)"OK", 50))
{
ipbuf[0] = 0;
return;
}
p = ESP8266_CheckCMD((uint8_t *)"\"");
p1 = (uint8_t *)strstr((const char*)(p+1),"\"");
*p1=0;
sprintf((char*)ipbuf,"%s",p+1);
}
//退出透传模式 0,退出成功; 1,退出失败
uint8_t ESP8266_QuitTrans(void)
{
ESP8266_SendData((uint8_t *)"+++", 3);
delay_ms(500); //等待500ms
return ESP8266_SendCMD((uint8_t *)"AT\r\n",(uint8_t *)"OK",20);//退出透传判断
}
//获取连接状态 0,未连接;1,连接成功.
uint8_t ESP8266_ConstaCheck(void)
{
while(ESP8266_SendCMD((uint8_t *)"AT+CIPSTATUS\r\n",(uint8_t *)"OK",50));
return 0;
}
uint8_t ESP8266_APInit(char *name, char *password)
{
uint8_t Sbuf[60] ={0};
while(ESP8266_SendCMD((uint8_t *)"AT\r\n",(uint8_t *)"OK",200))
{
//退出透传
ESP8266_QuitTrans();
//关闭透传模式
ESP8266_SendCMD((uint8_t *)"AT+CIPMODE=0\r\n",(uint8_t *)"OK",200);
delay_ms(1000);
}
// 关闭回显
while(ESP8266_SendCMD((uint8_t *)"ATE0\r\n",(uint8_t *)"OK",200));
// 设置波特率
while(ESP8266_SendCMD((uint8_t *)"AT+UART=115200,8,1,0,0\r\n",(uint8_t *)"OK",200));
delay_ms(10);
// 设置WIFI AP模式
while(ESP8266_SendCMD((uint8_t *)"AT+CWMODE=2\r\n",(uint8_t *)"OK",200));
while(ESP8266_SendCMD((uint8_t *)"AT+RST\r\n",(uint8_t *)"OK",200));
// 延时4秒等待重启成功
delay_ms(1000);
delay_ms(1000);
delay_ms(1000);
delay_ms(1000);
memset(Sbuf, 0 , sizeof(Sbuf));
sprintf((char*)Sbuf, "AT+CWSAP=\"%s\",\"%s\",1,4\r\n", name, password);
while(ESP8266_SendCMD(Sbuf, (uint8_t *)"OK", 1000));
#ifdef UDP_Mode
while(ESP8266_SendCMD((uint8_t *)"AT+CIPMUX=0\r\n",(uint8_t *)"OK",200));
while(ESP8266_SendCMD((uint8_t *)"AT+CIPSTART=\"UDP\",\"255.255.255.255\",60156,42254,0\r\n",(uint8_t *)"OK",500));
while(ESP8266_SendCMD((uint8_t *)"AT+CIPMODE=1\r\n",(uint8_t *)"OK",300));
while(ESP8266_SendCMD((uint8_t *)"AT+CIPSEND\r\n",(uint8_t *)"OK",200));
#endif
Uart_Frame_Record.FramLength = 0;
memset((void *)Uart_Frame_Record.Data_RX_BUF, 0, sizeof(Uart_Frame_Record.Data_RX_BUF));
return 0;
}
uint8_t ESP8266_STAConnect(char *name, char *password)
{
uint8_t Sbuf[60] ={0};
memset(Sbuf, 0 , sizeof(Sbuf));
sprintf((char*)Sbuf,"AT+CWJAP=\"%s\",\"%s\"\r\n", name, password);
if(ESP8266_SendCMD( Sbuf, (uint8_t *)"WIFI GOT IP", 3000)){
return 1;
}
return 0;
}
uint8_t ESP8266_STAInit(void)
{
// 延时2秒等待串口初始化完成
while(ESP8266_SendCMD((uint8_t *)"AT\r\n",(uint8_t *)"OK",200))
{
//退出透传
ESP8266_QuitTrans();
//关闭透传模式
ESP8266_SendCMD((uint8_t *)"AT+CIPMODE=0\r\n",(uint8_t *)"OK",200);
delay_ms(800);
}
// 关闭回显
while(ESP8266_SendCMD((uint8_t *)"ATE0\r\n",(uint8_t *)"OK",200));
// // 设置波特率
// while(ESP8266_SendCMD((uint8_t *)"AT+UART=115200,8,1,0,0\r\n",(uint8_t *)"OK",200));
delay_ms(10);
// 设置WIFI STA模式
while(ESP8266_SendCMD((uint8_t *)"AT+CWMODE=1\r\n",(uint8_t *)"OK",200));
// while(ESP8266_SendCMD((uint8_t *)"AT+RST\r\n",(uint8_t *)"OK",200));
delay_ms(1000);
// delay_ms(1000);
// delay_ms(1000);
return 1;
}
2. 核心函数
main函数内容如下,调用__HAL_UART_ENABLE_IT(&huart3, UART_IT_IDLE)启动串口空闲中断,使用HAL_UART_Receive_DMA函数将Uart_Frame_Record.Data_RX_BUF设置为DMA接收存储区。
/* USER CODE BEGIN PV */
uint8_t Wssid[20] = "ioter";
uint8_t Wpassword[20] = "1234567980";
/* USER CODE END PV */
int main(void)
{
/* USER CODE BEGIN 1 /int ret = 0;/ USER CODE END 1 */
/* MCU Configuration--------------------------------------------------------*/
/* Reset of all peripherals, Initializes the Flash interface and the Systick. */
HAL_Init();
/* USER CODE BEGIN Init */
/* USER CODE END Init */
/* Configure the system clock */
SystemClock_Config();
/* USER CODE BEGIN SysInit */
/* USER CODE END SysInit */
/* Initialize all configured peripherals /MX_GPIO_Init();MX_DMA_Init();MX_USART1_UART_Init();MX_USART3_UART_Init();/ USER CODE BEGIN 2 */
printf("start application\r\n");
// 使能空闲中断
__HAL_UART_ENABLE_IT(&huart3, UART_IT_IDLE);
HAL_UART_Receive_DMA(&huart3, (uint8_t *)Uart_Frame_Record.Data_RX_BUF, RX_BUF_MAX_LEN); // 启动DMA接收
// 连接路由器
ESP8266_STAInit();
delay_ms(1000);
if (wifi_connect_status != WIFI_GOT_IP)
{
while(ESP8266_STAConnect((char *)Wssid, (char *)Wpassword));
}
/* USER CODE END 2 */
/* Infinite loop */
/* USER CODE BEGIN WHILE */
while (1)
{
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
HAL_GPIO_WritePin(GPIOC, GPIO_PIN_13, GPIO_PIN_RESET);
printf("reset gpio!\r\n");
HAL_Delay(1000);
HAL_GPIO_WritePin(GPIOC, GPIO_PIN_13, GPIO_PIN_SET);
printf("set gpio!\r\n");
HAL_Delay(1000);
}
return ret;
/* USER CODE END 3 */
}
USART3_IRQHandler函数如下, 当串口接收完一帧数据后,会触发空闲中断,中断回调函数检测到空闲中断被触发,停止DMA接收数据,置位Uart_Frame_Record.FramLength,然后对接收的数据处理,通过wifi_event_cb函数将WIFI连接状态传送出去。
void USART3_IRQHandler(void)
{
if(__HAL_UART_GET_FLAG(&huart3, UART_FLAG_IDLE) != RESET) // 空闲中断标记被置位
{
__HAL_UART_CLEAR_IDLEFLAG(&huart3); // 清除中断标记
HAL_UART_DMAStop(&huart3); // 停止DMA接收
Uart_Frame_Record.FramLength = RX_BUF_MAX_LEN - __HAL_DMA_GET_COUNTER(huart3.hdmarx); // 总数据量减去未接收到的数据量为已经接收到的数据量
Uart_Frame_Record.Data_RX_BUF[Uart_Frame_Record.FramLength] = 0; // 添加结束符
Uart_Frame_Record.FramLength |= 1 << 15;
if (strstr(Uart_Frame_Record.Data_RX_BUF, "WIFI CONNECTED"))
{
wifi_event_cb(WIFI_CONNECTED);
}else if(strstr(Uart_Frame_Record.Data_RX_BUF, "WIFI DISCONNECT"))
{
wifi_event_cb(WIFI_DISCONNECT);
}else if(strstr(Uart_Frame_Record.Data_RX_BUF, "WIFI GOT IP"))
{
wifi_event_cb(WIFI_GOT_IP);
}
HAL_UART_Receive_DMA(&huart3, (uint8_t *)Uart_Frame_Record.Data_RX_BUF, RX_BUF_MAX_LEN); // 重新启动DMA接收
}
}
接着我们看下ESP8266_SendCMD函数,该函数实现发送自定义AT指令,并根据结果判断AT指令是否执行成功,该函数为本节核心内容,函数首先清空缓存,然后调用串口发送函数将AT指令发送出去,然后进入while(–waittime)等待超时,如果正常收到数据Uart_Frame_Record.FramLength最高位会被置位,判断是否接收到ack数据。
//cmd:发送的命令字符串
//ack:期待的应答结果,如果为空,则表示不需要等待应答
//返回值:0,发送成功 1,发送失败
uint8_t ESP8266_SendCMD(uint8_t *cmd, uint8_t *ack,uint16_t waittime)
{
Uart_Frame_Record.FramLength = 0;
memset((void *)Uart_Frame_Record.Data_RX_BUF, 0, sizeof(Uart_Frame_Record.Data_RX_BUF));
Dev_UART3SendStr(cmd, 0, 0);
if(ack&&waittime)
{
while(--waittime)
{
delay_ms(10);
if(Uart_Frame_Record.FramLength&0x8000)
{
if(ESP8266_CheckCMD(ack))
{
printf("AT: %s, ack: %s\r\n", cmd, Uart_Frame_Record.Data_RX_BUF);
Uart_Frame_Record.FramLength = 0;
return 0;
}
else
{
Uart_Frame_Record.FramLength = 0;
return 1;
}
}
}
if(waittime==0)
{
return 1;
}
}
return 1;
}
3. 程序执行
编译后下载程序,打开路由器或者设置WIFI热点,ioter:1234567980, 注意不要选择5G,ESP8266仅2.4G,串口1打印数据如下所示,看到WIFI GOT IP说明设备联网成功,并且得到了DHCP分配的IP地址。
三、小结
如您在使用过程中有任何问题,请加QQ群进一步交流。
QQ交流群:573122190 (备注:物联网项目交流)
小叶老师出品:种一棵树最好的时间是十年前,其次是现在!
完整代码获取:
https://download.csdn.net/download/weixin_45006076/89472819