STM32与物联网01-ESP8266基本操作
ESP8266物联网简介
ESP8266简介
ESP8266 是上海乐鑫公司开发的一款具有 WiFi 功能的控制芯片,它带有完整的 TCP/IP 协议栈,因此可以用作物联网开发。
ESP8266 本身也是一个性能不错的 32 位微控制器,完全可以作为普通的 MCU 使用。然而,考虑到 ESP8266 作为 MCU 时需要一整套开发环境,且 ESP8266 的外设并不算丰富,因此这里仅将其作为一个普通外围器件使用,通过 STM32 等 MCU 控制它并接收 ESP8266 收到的网络数据。
在作为外围模块使用时,ESP8266 主要通过串口收发命令和数据,因此任意可以使用串口并设置波特率的 MCU 理论上都可以操作 ESP8266 实现物联网功能,包括但不限于 51 单片机、AVR 、STM32 和树莓派。
这里选用 ESP-01 作为 WiFi 模块,其外观为:
它具有的优点为:
- 价格非常低廉,仅需个位数
- 尺寸很小,大约为 25mm x 15mm
- 功能完善,它本身也是一个微型开发板,具有 8 个引脚,可以实现程序下载、串口收发等功能
- 市面上大多数 ESP-01 模块在售卖时已经内置了串口控制程序,上电后便可以正常工作。如果没有也不要紧,只需再花个位数价格就可以再买一个 ESP8266 固件下载器,结合商家给出的资料就可以重新烧入固件
在详细介绍 ESP8266 的使用方法之前,最好先了解以下背景知识:
ESP8266 所使用的 WiFi 是工作频率在 2.4GHz 波段的局域网无线通信。有些笔记本电脑或路由器默认使用的是 5GHz 的网络频段,如果不修改将会无法与 ESP8266 连接上。
ESP8266 支持两种 WiFi 通信模式:AP 和 Sta 。AP 表示接入点(access point),可以创建一个 WiFi 热点让其余设备连接,一般作为局域网服务器使用;Sta 表示连接设备,该模式下 ESP8266 可以主动连接其它 WiFi 信号,一般作为局域网客户端使用。不过 ESP8266 支持 Sta 和 AP 两模式共存,可以在连接 WiFi 的同时被其余设备连接。
在 ESP-01 模块中,具有 8 个引脚,各个引脚的作用为:
序号 | 名称 | 功能 |
---|---|---|
1 | GND | 接地 |
2 | GPIO 2 | 通用输入输出(内部已上拉) |
3 | GPIO 0 | 选择模式:低电平为下载模式,未连接或高电平为正常工作模式 |
4 | RXD | 串口 0 数据接收,也可用作普通 GPIO |
5 | VCC | 3.3V 供电 |
6 | RST | 复位线,若通过外部置为低电平则复位 |
7 | CH_PD | 高电平使能芯片,低电平失能芯片 |
8 | TXD | 串口 0 数据发送,也可用作普通 GPIO |
接下来的程序使用基于 STM32 的标准库编写,并可以比较容易地修改为 HAL 库的代码,或使用其余类似的单片机编写作用相似的代码。
串口接收不定长数据方法
在正式介绍 ESP8266 操作方法之前,首先介绍一个基本的要点:如何使用串口接收 ESP8266 可能发来的不定长数据并解析。
不定长数据的接收方法有很多,例如可以通过空字符确定结尾。这里使用串口的空闲中断实现该方法,空闲中断的的产生是由于在两次数据发送间隔,串口没有检测到数据输入而产生的,从而可以判断数据接收完毕,停止接收数据。
首先,为了保存接收数据,需要定义一个缓冲区。这里通过一个结构体的形式确定缓冲区所需成员:
#define USART_RX_BUF_SIZE 1024
typedef struct {
char Body[USART_RX_BUF_SIZE];
uint16_t Length :15;
uint16_t FinishFlag :1;
} USART_Buffer;
注意,由于不总是在中断函数内处理接收数据,因此需要一个比特的字段用于判断数据是否接收完毕。
为了接收串口空闲中断,需要先在初始化函数内使能它:
void USART_Config(void) {
// ...
USART_ITConfig(USART3, USART_IT_IDLE, ENABLE);
}
对应的串口中断函数的实现如下:
USART_Buffer ESP8266_Buffer;
void USART3_IRQHandler(void) {
if (USART_GetITStatus(USART3, USART_IT_RXNE) != RESET) {
if (ESP8266_Buffer.Length < (USART_RX_BUF_SIZE - 1))
ESP8266_Buffer.Body[ESP8266_Buffer.Length++] = (char)USART_ReceiveData(USART3);
}
if (USART_GetITStatus(USART3, USART_IT_IDLE) != RESET) {
ESP8266_Buffer.FinishFlag = 1;
ESP8266_Buffer.Body[ESP8266_Buffer.Length] = '\0';
volatile uint16_t temp;
temp = USART3->SR;
temp = USART3->DR;
ESP8266_FrameFinish_CallBack();
}
}
在串口中断函数中,对以下两个中断类型响应:USART_IT_RXNE
表示数据接收寄存器收到内容,那么将接收到的内容作为一个字符放入缓冲区中;USART_IT_IDLE
表示数据包接收完毕,在缓冲器结尾添加上一个空字符使其变为字符串,并将结束标志位置 1 。
注意在不接收中断时,串口空闲中断会一直产生,从而干扰程序运行;清除串口空闲中断标志位需要由软件完成,具体做法是通过程序先读取 USART_SR
寄存器,再读取 USART_DR
寄存器。
在程序的最后使用一个回调函数来处理本次接收的数据包,它可以根据当前项目的使用情况自行编写或替换为相应的语句。
ESP8266简单使用
设备连接与初始化
根据上文的介绍,单片机最少需要 4 个 I/O 口与 ESP8266 相连:这里选用 USART3 作为与 ESP8266 通信的串口,则 PB10 与 ESP8266 的 RXD 相连,PB11 与 TXD 相连;PA4 与 RST 相连,PA5 与 CH_PD 相连:
这里主要通过以下两个宏操作引脚:
#define ESP8266_RST(state) GPIO_WriteBit(GPIOA, GPIO_Pin_4, (BitAction)state)
#define ESP8266_CH_PD(state) GPIO_WriteBit(GPIOA, GPIO_Pin_5, (BitAction)state)
本节先介绍一个最简单的、手动操作 ESP8266 的方式演示操作的整个过程:通过计算机的串口调试工具将命令发送给 STM32 ,STM32 接收后转发给 ESP8266 ,并将接收到的数据再转发给串口调试工具:
因此,在初始化 ESP8266 时需要初始化相应的 GPIO 及两个 USART 外设,并将 RST 和 CH_PD 都置高电平:
void ESP8266_Init(void) {
ESP8266_GPIO_Config();
ESP8266_USART_Config();
ESP8266_RST(SET);
ESP8266_CH_PD(SET);
}
注意,初始化 STM32 连接到 ESP8266 的串口时,需要将波特率设置为 115200 ,否则数据无法被正常接收。当连接上 ESP8266 后,可以通过后续发送指令修改 ESP8266 的串口波特率。
除此之外,还有一些其它的外设如定时器、调试用串口等,其使用情况可以根据项目需要自行管理,对应的初始化过程不再介绍。
在串口 3 中断中,将接收到的 ESP8266 数据转发回串口调试工具:
static void ESP8266_FrameFinish_CallBack(void) {
printf("%s", ESP8266_Buffer.Body);
ESP8266_Buffer.FinishFlag = 0;
ESP8266_Buffer.Length = 0;
}
串口 1 的中断处理过程与以上类似,这里不再重复。
AT指令简介
既然是使用串口通信的方式操作 ESP8266 ,那么收、发数据都需要遵循一定格式。ESP8266 的固件内置了 AT 指令,可以通过串口发送 AT 指令控制 ESP8266 。
所谓 AT 指令,是一种字符串形式的数据,但开头都是 AT
两个字符,后续跟上具体的选项。AT 指令有以下 4 种主要的表现形式:
指令类型 | 指令格式 | 说明 |
---|---|---|
测试指令 | AT+<x>=? | 用于查询设置命令或内部程序设置的参数以及其取值范围 |
查询指令 | AT+<x>? | 用于查询参数当前设置的值 |
设置命令 | AT+<x>=<...> | 用于设置用户自定义的参数值 |
执行指令 | AT+<x> | 用于执行受模块内部程序控制的变参数不可变的功能 |
每一个 AT 指令以换行符 CRLF \r\n
作为结尾的标志,在串口调试工具中需要另起一行。
AT 指令很多,但是并不是每一个都会用得到。这里仅介绍需要的 AT 指令,完整的 AT 指令可以从文档中查看。
注意,某些厂商在生产开发板时,可能会对 AT 固件做一些裁剪,去除一些用处不大的指令,因此在使用时请阅读商家提供的说明文档。
最简单的 AT 指令就是单个 AT
,用于测试 AT 固件是否能用。如果能用,ESP8266 会返回 OK :
AT
AT
OK
(博客园的代码无法高亮出哪部分属于输入,如果分辨不够清楚的可以查看原文 )
上面发送了一个指令 AT
,而 ESP8266 则先回复了指令内容 AT ,再回复一个 OK ,这种先复述指令内容再发送有效数据的方式称为回显。回显会在一定程度上影响数据解析,并且在设计时 STM32 在接到串口调试工具发送的消息时已经执行了一次回显操作,因此可以使用 ATE0
指令关闭回显:
ATE0
ATE0
OK
AT
OK
这样后续发送指令时只会回复有效数据了。在后续的操作中全部关闭回显,命令都是通过 STM32 收到后立即转发回来的。
可以使用 AT+GMR
查看当前固件的版本信息:
AT+GMR
AT version:0.22.0.0(Mar 20 2015 10:04:26)
SDK version:1.0.0
compile time:Mar 20 2015 11:00:32
OK
如果固件版本过旧,可能也会缺少一些命令。可以使用专用的固件烧入模块通过 USB 为 ESP8266 更新固件。
上文曾经提到 ESP8266 有两种主要的工作模式:Sta 和 AP 。可以使用 AT+CWMODE=<mode>
设置 ESP8266 的通信模式:参数 <mode>
为 1 代表 ESP8266 设置为 Sta 模式;2 代表设置为 AP 模式;参数 3 则是 Sta 模式和 AP 模式共存。
这里将其设置为 Sta 模式,主动连接路由器或笔记本提供的 WiFi :
AT+CWMODE=1
OK
在 Sta 模式下,可以使用执行命令 AT+CWLAP
列出(List)当前环境下可用的 WiFi 接入点:
AT+CWLAP
+CWLAP:(4,"Laptop",-54,"ac:4e:aa:b2:1f:f2",1)
+CWLAP:(4,"TP-LINK",-28,"51:38:39:a8:d5:e0",1)
+CWLAP:(4,"Mobile",-86,"a8:79:4b:22:42:e6",11)
OK
返回的结果中,每项数据都占一行,有 5 个元素,第一个元素 <ecn>
列出了 WiFi 所使用的加密类型,值 4 代表加密类型为 WPA_WPA2_PSK ;第二个元素 <ssid>
代表 WiFi 名,第三个元素 <rssi>
代表 WiFi 强度,绝对值越小强度越高;第四个元素 <mac>
是设备的 MAC 地址;最后一个元素 <channel>
代表频道。
注意,ESP8266 返回的数据都是 UTF-8 编码的,需要将串口调试工具的编码也设置为 UTF-8 ,否则可能出现中文乱码。
连接(Join) WiFi 可以通过以下命令执行:
AT+CWJAP="TP-LINK","abc123456"
OK
WiFi 名和密码都要以字符串的形式放在双引号内,两者间使用逗号隔开。
连接到 WiFi 后,可以使用 AT+CIFSR
命令查看当前设备的 IP 地址:
AT+CIFSR
+CIFSR:STAIP,"192.168.137.129"
+CIFSR:STAMAC,"65:e8:db:a5:9b:84"
OK
更多的 AT 指令及其用法可以参考官方文档。接下来介绍 ESP8266 从连接 WiFi 到接收网络数据的一般过程。
WiFi连接与数据收发测试
以下测试也全部在串口调试工具中发送命令与接收数据。
首先提前设置好 WiFi 名和密码,然后让 ESP8266 主动连接 WiFi :
AT+CWMODE=1
OK
AT+CWJAP="TP-LINK","abc123456"
OK
这里将计算机和 ESP8266 都主动连接到路由器提供的 WiFi 中,两者处于同一个局域网内,这样便可以比较方便地互发数据。
连接后,需要在计算机中查看本机在局域网内的地址:(IPv4 Address)
C:\Users\Hello> ipconfig
Windows IP Configuration
Wireless LAN adapter WLAN:
Connection-specific DNS Suffix . :
IPv4 Address. . . . . . . . . . . : 192.168.1.105
Subnet Mask . . . . . . . . . . . : 255.255.255.0
Default Gateway . . . . . . . . . : 192.168.1.1
以下通过 ESP8266 主动向计算机发起连接,并发送查询当前时间的命令;计算机接到命令后,向 ESP8266 返回当前的时间。在计算机的客户端,使用 Python 编写如下套接字程序:
import socket, time
server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server.bind(('', 12000))
server.listen(1)
while True:
connect, address = server.accept()
print(address)
message = connect.recv(1024)
print(message)
if message.decode() == 'time':
connect.send(time.ctime().encode())
connect.close()
使用 Python 编写套接字程序的方法可以参考这篇文章。运行该程序后,在单片机端通过设置指令 AT+CIPSTART 向该局域网 IP 地址与端口号发起 TCP 连接:
AT+CIPSTART="TCP","192.168.1.105",12000
CONNECT
OK
连接完成以后,可以通过设置指令 AT+CIPSEND 发送数据,参数 <length>
为数据的长度:
AT+CIPSEND=4
OK
> time
SEND OK
+IPD,24:Mon Jul 11 14:58:48 2022CLOSED
当收到此命令后,会换行返回 >
符号,表示接下来可以继续接收待发送的数据;后续通过串口发送的数据可以不用以新行结尾,当数据长度达到 <length>
时,ESP8266 才会将数据发送出去并返回 OK 。
在收到网络数据时,ESP8266 会以 +IPD
的指令形式返回,第一个逗号后面代表数据的长度,冒号后面跟随的是实际的数据。最后的 CLOSE 代表连接中断,它和数据是是分两次接收的。通过解析数组 ESP8266_Buffer.Body
中保存的数据,单片机就可以通过网络获取当前的实时时间,并用于校正当前的 RTC 时钟等。
当然,在实际使用时不会通过串口转发这么麻烦的方式,可以在程序中直接操作串口按指定的形式收发数据,后续将会介绍相应程序的编写方法。
参考资料/延伸阅读
https://docs.espressif.com/projects/esp-at/en/release-v2.2.0.0_esp8266/Get_Started/index.html
ESP-AT 指令文档。不过很少有商家的固件会有这么新的版本。