STM32之WIFI通信(ESP8266 ESP-01S)
前言
本文:使用的是 STM32F103C8T6 + ESP-01S模块 通过采用串口的通信方式 来实现 WIFI功能
ESP8266的介绍
ESP8266指的是乐鑫推出的ESP8266EX芯片,用ESP8266EX芯片和FLASH芯片等封装在PCB上、金属屏蔽罩下便得到ESP8266模块。
ESP8266EX 由乐鑫公司开发,提供了⼀套⾼度集成的 Wi-Fi SoC 解决⽅案,其低功耗、 紧凑设计和⾼稳定性可以满⾜⽤户的需求。
ESP8266EX 拥有完整的且⾃成体系的 Wi-Fi 网络功能,既能够独立应用,也可以作为从机 搭载于其他主机 MCU 运⾏。当 ESP8266EX 独⽴应⽤时,能够直接从外接 Flash 中启动。 内置的⾼速缓冲存储器有利于提⾼系统性能,并且优化存储系统。此外 ESP8266EX 只需通过 SPI/SDIO 接⼝或 I2C/UART ⼝即可作为 Wi-Fi 适配器,应⽤到基于任何微控制器的 设计中。 ESP8266EX 集成了天线开关、射频 balun、功耗放⼤器、低噪放⼤器、过滤器和电源管理 模块。这样紧凑的设计仅需极少的外部电路并且将 PCB 的尺⼨降到最⼩
ESP-01S的介绍
ESP-01S 是由安信可科技开发的Wi-Fi模块,该模块核心处理器 ESP8266 在较小尺寸封装中集成了业界领先的 Tensilica L106 超低功耗 32 位微型 MCU,带有 16 位精简模式,主频支持 80 MHz 和 160 MHz,支持 RTOS,集成 Wi-Fi MAC/ BB/RF/PA/LN
ESP-01S Wi-Fi 模块支持标准的 IEEE802.11 b/g/n 协议,完整的 TCP/IP 协议栈。用户可以使用该模块为现有的设备添加联网功能,也可以构建独立的网络控制器。
脚序 |
名称 |
功能说明 |
1 |
GND |
接地 |
2 |
IO2 |
GPIO2/UART1_TXD |
3 |
IO0 |
GPIO0;下载模式:外部拉低;运行模式:悬空或者外部拉高 |
4 |
RXD |
UART0_RXD/GPIO3 |
5 |
TXD |
UART0_TXD/GPIO1 |
6 |
EN |
芯片使能端,高电平有效 |
7 |
RST |
复位 |
8 |
VCC |
3.3V 供电(VDD);外部供电电源输出电流建议在500mA以上 |
官方说明: EN 脚和 RST 脚必须上拉到 VCC
通过我的实践经验:
- 遇到最头疼问题(当时),就是感觉模块时好时不好,偶然发现 拔掉重新插就好的现象,后来再到将 ESP-01S的VCC重新插拔就好了,知道了: 官方 推荐 3.3V 电压,峰值 500mA 以上电 说明我们的板子供电可能不足 所以建议使用外部电源
- 有个中断优先级的问题 就是最开始给USART1的优先级低于USART2的优先级 导致程序不能正常执行 可能是因为WIFI初始化时候有乱码发送 这时候串口1发送指令 而导致错乱 可能是因为这个
- 我们STM32单片机 和 ESP-01S模块通过串口通信时连接可以只用使用 VCC GND TXD RXD 这四个引脚
- 但是我自己感觉会有点问题的存在: 就是 在复位时 并不会 出现上电时候 那些乱码
- 所以我想到了 是不是因为 我当时RST复位引脚 在测试时没有上拉到VCC也可以正常使用 那我板子复位了 但WIFI模块不复位 重新跑我的程序 我觉得也是可以的 但是呢 这时发现 EN 使能位 出现了问题 不插可以 插就出现问题
- 我加上EN使能位时,单片机重新复位程序时 容易卡死 而不使用EN使能位时 程序非常快的执行了一遍 但是不会出现上电时的那些乱码了 这个问题还需加深考虑
- ==最终:==我还是将 VCC GND TXD RXD EN RST 引脚,都接上了 并且 在STM32复位时 将 RST复位引脚/VCC引脚 插拔一下 会更好的 运行 感觉是硬件问题 因为我使用的是最小开发板+模块
STM32与ESP8266通信
好了 上面是关于 如何对ESP-01S的模块硬件上的连接 以及一些AT指令 那么我接下来讲一下如何使STM32和ESP-01S进行通信 最后WIFI连接到我们的电脑上
在单片机内部
- 串口助手发送给USART1的数据 USART1通过USART2再传给 ESP8266
- ESP8266发送给USART2的数据 USART2通过USART1再传给串口助手
可以看出以上就是我们STM32在串口助手和ESP01S之间的通信方式了,所以只要学会串口通信,在学习一下发送给ESP8266的指令就可以使用WIFI连接到我们的网路了,下面介绍一下 AT指令
AT指令
AT指令,其实就是一种通信协议,你可以理解为 串口是马路,而AT指令就是交通规则,只有正确的驾驶才可以通过,即我们通过串口向ESP8266发送数据帧 它会检测 你发送的是不是AT指令,从而做出相应的动作 指令集主要分为:基础 AT 指令、Wi-Fi 功能 AT 指令、TCP/IP 相关的 AT 指令
AT指令集非常多,我在这里只列举几个最常用的AT指令:
指令 |
含义 |
响应 |
AT |
测试 AT 启动 |
OK |
AT+RST |
重启模块 |
OK |
AT+CWMODE=1/2/3 |
设置 Wi-Fi 模式 (Station/SoftAP/Station+SoftAP) |
OK |
AT+CWJAP="SSID","PWD" |
设置 ESP8266 Station 需连接的 AP -- 连接WIFI |
OK |
AT+CIPSTART="TCP","122.114.122.174",37590 |
建⽴ TCP 连接,UDP 传输或 SSL 连接 "安可信网址""端口号" |
OK |
AT+CIPMODE=1 |
设置传输模式(0:普通传输模式 ‣ 1:透传模式,仅⽀持 TCP 单连接和 UDP 固定通信对端的情况) 透传模式传输时,如果连接断开,ESP8266 会不停尝试重连,此时单独输⼊ +++ 退出透传,则停⽌重 连;普通传输模式则不会重连,提示连接断开。 |
OK |
AT+CIPSEND |
发送数据 收到此命令后先换⾏返回 > ,注意:在不退出透传时AT指令将不起作用 |
> |
+++ |
退出透传模式(不能加换行) |
OK |
AT+CIPCLOSE |
关闭 TCP/UDP/SSL 传输 |
OK |
AT+CIPMUX=1 |
设置多连接 |
OK |
AT+CWSAP="ESP8266","1234567890",5,3 |
配置 ESP8266 SoftAP 参数 -- ESP8266为WIFI源 |
OK |
AT+CIPSERVER=1,5050 |
建立TCP服务器( 0:关闭服务器 ‣ 1:建⽴服务器),端口号 |
OK |
AT+CIFSR |
查询本地 IP 地址 |
OK |
AT指令的使用时,需要自动加上换行符号
想要练习AT指令 可以使用 安信可透传云 V1.0进行测试 使用多条发送的数据并不会打印到串口上,我们平常发送AT会有数据回显是因为厂商设置的我们也可以是使用ATE0指令来关闭回显
STM32实战演习
通过以上学习,我们就可以与ESP8266进行通信 并连接自己的热点 注意:WIFI热点要开2.4GHZ的
本次实现连接淘宝API然后获取当前时间,获取时间的格式为 JSON数据 我们可以对JSON数据格式进行解析 然后把时间发送给RTC这样我们就可以永久保持这个时间了 对了当长时间不与TCP进行连接时服务器会主动断开与我们的连接
USART1.h
| #ifndef __USART_H__//如果没有定义了则参加以下编译
#define __USART_H__//一旦定义就有了定义 所以 其目的就是防止模块重复编译
#include "stm32f10x.h"
#include "stdio.h"
#include <stdarg.h>
void Usart_Init(void);
void Usart1_SendByte(uint8_t Byte);
extern char RxPacket[];
extern uint8_t RxFlag;
#endif //结束编译
|
USART1.c
| #include "usart.h"
uint8_t RxFlag;
/*
*PA9 -- TX
*PA10 -- RX
*/
void Usart_Init(void)
{
//使能时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_USART1, ENABLE);//使能时钟A和USART1
//为初始化函数做准备
GPIO_InitTypeDef GPIO_InitStructure;//定义结构体
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9;//设置PA9引脚
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP ;//设置输出模式为复用推挽输出
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz ;//设置输出速度为50MHZ
//初始化函数PIN9↓
GPIO_Init(GPIOA,&GPIO_InitStructure);//初始化
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10;//设置PA10引脚
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU ;//设置输出模式为上拉输入
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz ;//设置输出速度为50MHZ
//初始化函数PIN10↓
GPIO_Init(GPIOA,&GPIO_InitStructure);//初始化
USART_InitTypeDef USART_InitStructure; //定义串口结构体
USART_InitStructure.USART_BaudRate = 115200; //波特率
USART_InitStructure.USART_WordLength = USART_WordLength_8b;//数字帧长度
USART_InitStructure.USART_StopBits = USART_StopBits_1; //停止位
USART_InitStructure.USART_Parity = USART_Parity_No; //不使用校验位
USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;//不使用硬件流控制
USART_InitStructure.USART_Mode = USART_Mode_Tx|USART_Mode_Rx; //模式为发送+接收
//初始化串口1
USART_Init(USART1, &USART_InitStructure);
USART_ITConfig(USART1, USART_IT_RXNE, ENABLE);
NVIC_InitTypeDef NVIC_InitStructure; //定义结构体
NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn; //根据上面的我们所选取的USART1
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;//这里选择的是抢占 1
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1; //这里选择的是响应1
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //使能指定的中断通道
//初始化函数↓
NVIC_Init(&NVIC_InitStructure);
//使能串口1
USART_Cmd(USART1, ENABLE);
}
/**
* @brief 串口1发送字节 -- 发送的最基本的函数 -->其它发送函数都是基于它
* @param
* @retval
*/
void Usart1_SendByte(uint8_t Byte)
{
USART_SendData(USART1,Byte);
while(USART_GetFlagStatus(USART1,USART_FLAG_TXE) == RESET );
}
/**
* @brief 串口中断函数
中断函数里面可以放你想要实现的功能函数
* @param
* @retval
*/
void USART1_IRQHandler(void)
{
uint8_t data = 0;
if (USART_GetITStatus(USART1, USART_IT_RXNE) == SET)
{
USART_ClearITPendingBit(USART1, USART_IT_RXNE);
data = USART_ReceiveData(USART1);//获取串口助手发送给单片机的数据
USART_SendData(USART2,data);//通过串口2发送 --> ESP8266
}
}
/**
* @brief 函数重定义--封装printf函数
* @param
* @retval
*/
int fputc(int ch, FILE *f)
{
Usart1_SendByte(ch);
return ch;
}
|
ESP8266.h
| #ifndef __ESP8266_H__//如果没有定义了则参加以下编译
#define __ESP8266_H__//一旦定义就有了定义 所以 其目的就是防止模块重复编译
#include "stm32f10x.h"
#include "delay.h"
#include "stdlib.h"
#include "string.h"
void ESP8266_Init(void);
void Usart2_SendByte(uint8_t Byte);
void Usart2_SendString(uint8_t *string);
void Esp8266_SendString(uint8_t *string,uint16_t len);//串口2 发送字符串
uint8_t EspSendCmdAndCheckRecvData(uint8_t *cmd,uint8_t *Rcmd,uint16_t outtime);
void WIFI_ConnectTaoBao(void);
typedef struct{
uint8_t rxbuff[1024];
uint16_t rxcount;
uint8_t rxover;
uint8_t txbuff[1024];
uint16_t txcount;
}WIFI;
extern WIFI wifi;
#endif //结束编译
|
ESP8266.c
| #include "ESP8266.h"
/**
* @brief ESP8266初始化
PA2--TX
PA3--RX
PA5--ESP8266使能引脚
*/
WIFI wifi = {0};
void ESP8266_Init(void)
{
//使能时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);//使能时钟A
RCC_APB1PeriphClockCmd(RCC_APB1Periph_USART2,ENABLE);//使能时钟USART2
//为初始化函数做准备
GPIO_InitTypeDef GPIO_InitStructure;//定义结构体
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_2;//设置PA2引脚
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP ;//设置输出模式为复用推挽输出
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz ;//设置输出速度为50MHZ
//初始化函数PIN2
GPIO_Init(GPIOA,&GPIO_InitStructure);//初始化
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_3;//设置PA3引脚
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU ;//设置输出模式为上拉输入
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz ;//设置输出速度为50MHZ
//初始化函数PIN3↓
GPIO_Init(GPIOA,&GPIO_InitStructure);//初始化
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5;//设置PA5引脚 -- //ESP8266使能引脚
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP ;//设置输出模式为推挽输出
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz ;//设置输出速度为50MHZ
//初始化函数PIN5↓
GPIO_Init(GPIOA,&GPIO_InitStructure);//初始化
USART_InitTypeDef USART_InitStructure; //定义串口结构体
USART_InitStructure.USART_BaudRate = 115200; //波特率
USART_InitStructure.USART_WordLength = USART_WordLength_8b;//数字帧长度
USART_InitStructure.USART_StopBits = USART_StopBits_1; //停止位
USART_InitStructure.USART_Parity = USART_Parity_No; //不使用校验位
USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;//不使用硬件流控制
USART_InitStructure.USART_Mode = USART_Mode_Tx|USART_Mode_Rx; //模式为发送+接收
//初始化串口2
USART_Init(USART2, &USART_InitStructure);
USART_ITConfig(USART2, USART_IT_RXNE, ENABLE);//使能接收中断
USART_ITConfig(USART2, USART_IT_IDLE, ENABLE);//使能空闲中断
NVIC_InitTypeDef NVIC_InitStructure; //定义结构体
NVIC_InitStructure.NVIC_IRQChannel = USART2_IRQn; //根据上面的我们所选取的USART2
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 2;//这里选择的是抢占 1
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1; //这里选择的是响应2
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //使能指定的中断通道
//初始化函数↓
NVIC_Init(&NVIC_InitStructure);
//使能串口2
USART_Cmd(USART2, ENABLE);
GPIO_SetBits(GPIOA,GPIO_Pin_5);//GPIOA 高电平有效 -- 使能ESP8266
Delay_ms(2000);//延时 等待WiFi模块稳定
}
/**
* @brief 串口2发送字节 -- 发送的最基本的函数 -->其它发送函数都是基于它
* @param
* @retval
*/
void Usart2_SendByte(uint8_t Byte)
{
USART_SendData(USART2,Byte);
while(USART_GetFlagStatus(USART2,USART_FLAG_TXE) == RESET );
}
/**
* @brief 串口中断函数
中断函数里面可以放你想要实现的功能函数
* @param
* @retval
*/
void USART2_IRQHandler(void)
{
uint8_t data = 0;
if (USART_GetITStatus(USART2, USART_IT_RXNE) == SET)
{
USART_ClearITPendingBit(USART2, USART_IT_RXNE);
data = USART_ReceiveData(USART2);//ESP8266 发送给 STM32的数据
wifi.rxbuff[wifi.rxcount++] = data;//将 ESP8266 发送给单片机的数据 转存到rxbuff里面
USART_SendData(USART1,data); //可通过 串口助手在电脑屏幕上 显示
}
if(USART_GetITStatus(USART2, USART_IT_IDLE) == 1) //串口空闲中断
{
data = USART2->SR;
data = USART2->DR;
wifi.rxover = 1;
}
}
/**
* @brief 发送字符串
* @param string 字符串
* @retval
*/
void Usart2_SendString(uint8_t *string)
{
for(uint8_t i = 0;string[i] != '\0';i++)
{
Usart2_SendByte(string[i]);
}
}
/**
* @brief 发送字符串 + 带长度
* @param
* @retval
*/
void Esp8266_SendString(uint8_t *string,uint16_t len)//串口3 发送字符串
{
for(uint16_t i=0;i<len;i++)
Usart2_SendByte(string[i]);
}
/**
* @brief 发送AT指令,检查AT指令的返回,判断AT指令是否发送成功
* @param cmd:发送的AT指令
* @param Rcmd:AT指令对应的返回值
* @param outtime:ESP8266超时回复判断
* @retval
*/
uint8_t EspSendCmdAndCheckRecvData(uint8_t *cmd,uint8_t *Rcmd,uint16_t outtime)
{
uint8_t data = 0;
//发送AT指令之前要清空
memset(wifi.rxbuff,0,1024);
wifi.rxover = 0;
wifi.rxcount = 0;
Usart2_SendString(cmd);//发送AT指令 -- > 才会进入串口2中断服务
while(outtime)
{
if(wifi.rxover == 1)
{
if(strstr((char *)wifi.rxbuff,(char *)Rcmd) != NULL)
{
data = 1;
break;
}
}
outtime--;
Delay_ms(1);
}
return data;
}
/**
* @brief 连接淘宝API 获取当前时间
* @param
* @retval
*/
void WIFI_ConnectTaoBao(void)
{
if(EspSendCmdAndCheckRecvData("AT\r\n","OK",1000) == 1)
{
if(EspSendCmdAndCheckRecvData("AT+CWMODE=1\r\n","OK",1000) == 1)
{
if(EspSendCmdAndCheckRecvData("AT+CWJAP=\"你的WIFI\",\"你的WIFI密码\"\r\n","OK",100000) == 1)
{
if(EspSendCmdAndCheckRecvData("AT+CIPSTART=\"TCP\",\"api.m.taobao.com\",80\r\n","OK",10000)==1)
{
EspSendCmdAndCheckRecvData("AT+CIPMODE=1\r\n","OK",1000);//开启透传
EspSendCmdAndCheckRecvData("AT+CIPSEND\r\n",">",1000);//发送数据
//清空回收的buff
memset(wifi.rxbuff,0,1024);
wifi.rxover = 0;
wifi.rxcount = 0;
Delay_ms(1000);
Usart2_SendString("GET http://api.m.taobao.com/rest/api3.do?api=mtop.common.getTimestamp\r\n");
Delay_ms(1000);
Usart2_SendString("+++");//退出透传模式
}
}
}
}
}
/**
* @brief 解析JSON数据
* @param
* @retval
*/
void GetTime_RecvData(void)
{
char Val[20] = {0};
char tep[20] = {0};
uint8_t i = 0;
uint32_t timeget = 0;
// long long timeget = 0;
time_t time = 0;
if(wifi.rxover==1)
{
wifi.rxover = 0;
if(wifi.rxcount>10)//判断是否是 下发指令的回传的指令 而不是上传时系统默认下发的 //防止有影响
{
char *addr = strstr((char *)(wifi.rxbuff+6),"\"t\"");
addr+=5;
while(*(addr + i) != '"')
{
Val[i] = *( addr+i);
i++;
}
printf("%s\r\n",Val);
// sscanf(Val, "%lld", &timeget); // 存储类型为long long int
// printf("%lld\r\n",timeget);
for(i = 0;i<10;i++) //这种要去掉3位 才能使用atoi转换
{
tep[i] = Val[i];
}
printf("%s\r\n",tep);
timeget = atoi(tep);
memset(wifi.rxbuff,0,1024);
wifi.rxcount = 0;
RTC_Init(timeget);
}
}
}
|
main.c
| #include "stm32f10x.h" // Device header
#include "Delay.h"
#include "OLED.h"
#include "usart.h"
#include "ESP8266.h"
//#include "RTC.h"
int main(void)
{
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
OLED_Init();
Usart_Init();
ESP8266_Init();
WIFI_ConnectTaoBao();
// GetTime_RecvData();
while(1)
{
}
}
|
最后更新:
November 4, 2023
创建日期:
October 7, 2023