ESP-01S模块
一、核心处理器与功能
- 核心处理器:ESP-01的核心处理器是ESP8266,该处理器集成了Tensilica L106超低功耗32位微型MCU,带有16位精简模式,主频支持80MHz和160MHz。
- Wi-Fi功能:ESP8266集成了Wi-Fi MAC/BB/RF/PA/LNA,支持标准的IEEE802.11 b/g/n协议和完整的TCP/IP协议栈,可以方便地与其他设备或网络进行通信。
二、模块特点
- 设计紧凑:ESP-01尺寸小巧,设计紧凑,适用于空间有限的应用。
- 多种工作模式:ESP-01可以通过路由器连接到互联网(STA模式),使手机或电脑能够实现对设备的远程控制;也可以作为热点(AP模式),使其他设备能够连接到它。
- 可编程GPIO引脚:ESP-01提供了多个可编程的GPIO引脚,可以用于控制外部设备,如传感器和执行器等。
- 灵活的IP地址配置:用户可以通过串口输入更改控制器在局域网的IP地址。
用途
由于我们的arduino uno是没有WIFI联网功能的,所以一些好玩的项目,或者一些常用的网络通信协议tcp、mqtt等也是用不了的
那么为了拓展arduino uno的可玩性,方便开发更多项目,我们可以外接一个ESP-01S模块,让ESP-01S联网接收信息,然后将信息传入arduino uno
但是由于arduino uno本身内存和性能就比较低,运行联网的项目可能会占用大量的资源,可能导致项目运行不了
课程后续会更新ESP8266/ESP32这种高内存高性能的芯片,自带WIFI和蓝牙,做项目非常好,而且也可以用arduino IDE进行编程
(二)ESP-01S
ESP-01S的通信模式为串口通信(TX/RX)
所以我们可以先用串口调试工具,来看看这个模块是怎么工作的
将串口通信工具的RX和模块的TX链接,工具的TX和模块的RX链接,GND和VCC连接
ESP-01S访问天气API获取信息
文章末尾带AT指令大全
①AT(测试指令)
返回"OK"代表模块运行正常
②AT+CWMODE=3(工作模式--AP兼Station模式)
返回"OK"代表设置成功
③AT+CWJAP="12345678","88888888"(连接WIFI,账号、密码)
连接过程会陆续返回3条信息,如果连接成功,最后返回"OK"
④AT+CIPSTART="TCP","api.xemowo.top",80(使用TCP协议,接入API服务器,端口为80)
连接成功则返回"OK",若长时间没动静,则返回"closed"关闭连接
⑤AT+CIPMODE=1(设置透传模式)
设置成功,返回"OK"
⑥AT+CIPSEND(发送数据)
此时会返回">"符号,代表可以访问API了
⑦GET http://api.xemowo.top/api/tqyb.php?city=442000(GET请求,访问API)
返回天气API的JSON信息
⑧自此,ESP-01S整套访问API的流程结束,方法就是往串口发送指令,然后返回对应的指令,还是比较简单的
(三)程序
①接线
首先要接线,注意tx和rx的位置
arduino uno | esp-01s |
TX | RX |
RX | TX |
GND | GND |
5V | VCC |
②创建变量
创建一个字符串变量用于储存我们接收到的数据
创建两个整数型变量,用于存储温湿度数据
创建一个char变量,储存接收过程的数据
- String rcvData="";//保存数据
- int temperature = 0;//温度数据
- int humidity = 0;//湿度数据
- char SerialRcvBuf[500];
③发送AT指令函数
while循环,send_cmd函数可以发送一条串口数据,然后在返回的数据中含有需要的数据就退出while循环
例:while (!send_cmd("AT", "OK"));
发送指令,while (!Serial.available());一直判断是否有接收到信息
如果接收到,则进入if判断
if (Serial.find(keyword)) //返回关键词判断,比如ok
{
returntrue;
}
else
{
returnfalse;
}
Serial.find函数:如果接收的信息中,带有所需的信息,如"OK"
则返回true,否则false
while (Serial.available()) Serial.read(); //清空串口缓存
注意,每次执行完,要情况缓存,用于下次执行
- //boolean 成功返回true,失败返回false
- boolean send_cmd(String data, char*keyword)
- {
- boolean result = false;
- if (data != "") //对于tcp连接命令,直接等待第二次回复
- {
- Serial.println(data); //发送AT指令
- }
- if (data == "AT") //寻找esp8266是否正常工作
- delay(1000);
- else
- while (!Serial.available()); // 等待wifi模块应答
- delay(200);
- if (Serial.find(keyword)) //返回关键词判断,比如ok
- {
- returntrue;
- }
- else
- {
- returnfalse;
- }
- while (Serial.available()) Serial.read(); //清空串口缓存
- delay(500);
- }
④初始化函数SETUP
利用AT指令函数,我们可以在setup的时候,初始化ESP-01S
设置波特率为115200(注意,要和ESP-01S的波特率一致)
然后逐条执行AT程序,下列AT指令在上文中有讲解
然后执行完最后一条AT指令后,发送一条信息,提示初始化成功Serial.println("connect success");
- void setup() {
- Serial.begin(115200);
- while (!send_cmd("AT", "OK"));
- while (!send_cmd("AT+CWMODE=3", "OK")); //工作模式
- while (!send_cmd("AT+CWJAP="XEMOWO","1SUNjiajun"", "OK")); //接入AP
- while (!send_cmd("AT+CIPSTART="TCP","api.xemowo.top",80", "OK")); //接入服务器
- while (!send_cmd("AT+CIPMODE=1", "OK")); //透传模式
- while (!send_cmd("AT+CIPSEND", ">")); //开始发送
- Serial.println("connect success"); //发送报文信息
- }
⑤循环函数LOOP
直接串口发送"GET http://api.xemowo.top/api/tqyb.php?city=442000"GET访问API地址
如果Serial.available()串口缓存区不为0时
先将保存数据的变量清空
然后进入while循环,直到缓存区没内容为止
读取每个字节的数据,存入到字符串变量内
SerialRcvBuf[rcvMark]= char(Serial.read());
rcvMark++;然后字节个数,自加
这里你可以直接将char转换成字符串类型,也可以不用
- rcvData = String(SerialRcvBuf); // 执行转换与保存
- Serial.println(SerialRcvBuf);
然后执行两条函数(下文解析),用于读取串口数据内的温度和湿度信息
- temperatureJson(SerialRcvBuf);
- humidityJson(SerialRcvBuf);
最后,打印温度和湿度数据
- Serial.println(humidity);
- Serial.println(temperature);
- Serial.println("GET http://api.xemowo.top/api/tqyb.php?city=442000");
- if (Serial.available() > 0) {
- memset(SerialRcvBuf, 0, sizeof(SerialRcvBuf));// 先清空buffer
- byte rcvMark=0;
- while(Serial.available() > 0){
-
- SerialRcvBuf[rcvMark]= char(Serial.read());
- delay(2);
- rcvMark++;
- }
- // Serial.println(SerialRcvBuf);
- rcvData = String(SerialRcvBuf); // 执行转换与保存
- Serial.println(SerialRcvBuf);
- temperatureJson(SerialRcvBuf);
- humidityJson(SerialRcvBuf);
- Serial.println(humidity);
- Serial.println(temperature);
- }
- delay(5000);
- }
⑥temperatureJson和humidityJson读取JSON信息内所需的数据,使用截取的方式
(两段函数大同小异,只讲一段)
首先,我们得到的数据是这样的,我们需要获取其中的temperature和humidity
很显然可以用arduinojson来解析,但是不行(下文讲解)
所以可以采用截取字符串的方法,因为json的变量名是不变的
- {"province":"广东","city":"中山市","adcode":"442000","weather":"晴","temperature":"18","winddirection":"北","windpower":"≤3","humidity":"59","reporttime":"2024-11-26 19:00:46","temperature_float":"18.0","humidity_float":"59.0"}
利用strstr函数,截取数据中temperature的位置
- const char* cityFieldStart = strstr(json, "temperature");
从temperature的位置开始算,跳过14位,相当于跳过temperature":",就只剩下18这个数据量
- cityFieldStart += 14; // "temperature":" 共有14个字符
寻找cityFieldStart值的下一个"分号,就是 18"的这个分号,为结束的位置
- const char* cityValueEnd = strchr(cityFieldStart, '"');
末尾的位置 减去 寻找值temperature的位置的位置,可以得到数据量 18 的位置
- size_t cityValueLength = cityValueEnd - cityFieldStart;
创建变量,用于储存数据量 18
- char cityValueBuffer[cityValueLength + 1];
将数据量移动到变量内
- strncpy(cityValueBuffer, cityFieldStart, cityValueLength);
使用atoi函数,将字符串转换成整数,并存入变量内
- temperature = atoi(cityValueBuffer);//atoi字符串转换为整数
- void temperatureJson(const char* json) {
- // 查找"city":"的起始位置
- const char* cityFieldStart = strstr(json, "temperature");
- if (!cityFieldStart) {
- Serial.println("City field not found in JSON.");
- return;
- }
-
- // 跳过"temperature":"部分,指向值的起始位置(即第二个引号之后)
- cityFieldStart += 14; // "temperature":" 共有14个字符
-
- // 查找值的结束位置(即下一个引号)
- const char* cityValueEnd = strchr(cityFieldStart, '"');
- if (!cityValueEnd) {
- Serial.println("Error extracting city value from JSON.");
- return;
- }
-
- // 计算temperature值的长度(不包括起始和结束引号)
- size_t cityValueLength = cityValueEnd - cityFieldStart;
-
- // 创建一个字符数组来存储temperature值(加1用于null终止符)
- char cityValueBuffer[cityValueLength + 1];
-
- // 复制temperature值到字符数组中
- strncpy(cityValueBuffer, cityFieldStart, cityValueLength);
- cityValueBuffer[cityValueLength] = '\0'; // 确保字符串以null终止
-
- // 打印temperature值
- Serial.print("temperatureJson: ");
- Serial.println(cityValueBuffer);
- temperature = atoi(cityValueBuffer);//atoi字符串转换为整数
- Serial.println(temperature);
- }
⑦代码现象
(四)问题
在学习ESP-01S与arduino uno通信的过程中,遇到几个问题,大家有什么解决方法也可以留言
①串口缓存区,内存不足
现象:接收到的串口的JSON数据不全,只有一半
原因:arduino uno的串口库定义的缓存区内存太小,不足以显示完整所有的数据
解决方法:
文件夹:C:\Users\XEMOWO\AppData\Local\Arduino15\packages\arduino\hardware\avr\1.8.6\cores\arduino
找到arduino uno的内置库,找到串口通信的定义库HardwareSerial.h
将SERIAL_TX_BUFFER_SIZE和SERIAL_RX_BUFFER_SIZE都改成300,300足够显示很长的数据
②为什么不使用Arduino json库
正常来说,使用Arduino json可以解析JSON信息,但是不知道为什么我在使用的过程中,解析不了
提示:deserializeJson() failed: NoMemory
我将函数的储存改大,依旧报错
- StaticJsonDocument<384> doc;
(在ESP32中可以正常使用,感觉是uno的闪存跟不上)
希望有懂的朋友可以解答一下
(五)完整程序
- String rcvData="";//保存数据
- int temperature = 0;//温度数据
- int humidity = 0;//湿度数据
- char SerialRcvBuf[500];
- void setup() {
- Serial.begin(115200);
- while (!send_cmd("AT", "OK"));
- while (!send_cmd("AT+CWMODE=3", "OK")); //工作模式
- while (!send_cmd("AT+CWJAP="XEMOWO","1SUNjiajun"", "OK")); //接入AP
- while (!send_cmd("AT+CIPSTART="TCP","api.xemowo.top",80", "OK")); //接入服务器
- while (!send_cmd("AT+CIPMODE=1", "OK")); //透传模式
- while (!send_cmd("AT+CIPSEND", ">")); //开始发送
- Serial.println("connect success"); //发送报文信息
- }
- void loop() {
- Serial.println("GET http://api.xemowo.top/api/tqyb.php?city=442000");
- if (Serial.available() > 0) {
- memset(SerialRcvBuf, 0, sizeof(SerialRcvBuf));// 先清空buffer
- byte rcvMark=0;
- while(Serial.available() > 0){
-
- SerialRcvBuf[rcvMark]= char(Serial.read());
- delay(2);
- rcvMark++;
- }
- // Serial.println(SerialRcvBuf);
- rcvData = String(SerialRcvBuf); // 执行转换与保存
- Serial.println(SerialRcvBuf);
- temperatureJson(SerialRcvBuf);
- humidityJson(SerialRcvBuf);
- Serial.println(humidity);
- Serial.println(temperature);
- }
- delay(5000);
- }
- //boolean 成功返回true,失败返回false
- boolean send_cmd(String data, char *keyword)
- {
- boolean result = false;
- if (data != "") //对于tcp连接命令,直接等待第二次回复
- {
- Serial.println(data); //发送AT指令
- }
- if (data == "AT") //寻找esp8266是否正常工作
- delay(1000);
- else
- while (!Serial.available()); // 等待wifi模块应答
- delay(200);
- if (Serial.find(keyword)) //返回关键词判断,比如ok
- {
- return true;
- }
- else
- {
- return false;
- }
- while (Serial.available()) Serial.read(); //清空串口缓存
- delay(500);
- }
- // 提取并打印JSON中temperature字段的值的函数
- void temperatureJson(const char* json) {
- // 查找"city":"的起始位置
- const char* cityFieldStart = strstr(json, "temperature");
- if (!cityFieldStart) {
- Serial.println("City field not found in JSON.");
- return;
- }
-
- // 跳过"city":"部分,指向值的起始位置(即第二个引号之后)
- cityFieldStart += 14; // "city":" 共有7个字符
-
- // 查找值的结束位置(即下一个引号)
- const char* cityValueEnd = strchr(cityFieldStart, '"');
- if (!cityValueEnd) {
- Serial.println("Error extracting city value from JSON.");
- return;
- }
-
- // 计算city值的长度(不包括起始和结束引号)
- size_t cityValueLength = cityValueEnd - cityFieldStart;
-
- // 创建一个字符数组来存储city值(加1用于null终止符)
- char cityValueBuffer[cityValueLength + 1];
-
- // 复制city值到字符数组中
- strncpy(cityValueBuffer, cityFieldStart, cityValueLength);
- cityValueBuffer[cityValueLength] = '\0'; // 确保字符串以null终止
-
- // 打印city值
- Serial.print("temperatureJson: ");
- Serial.println(cityValueBuffer);
- temperature = atoi(cityValueBuffer);//atoi字符串转换为整数
- Serial.println(temperature);
- }
- void humidityJson(const char* json) {
- // 查找"city":"的起始位置
- const char* cityFieldStart2 = strstr(json, "humidity");
- if (!cityFieldStart2) {
- Serial.println("City field not found in JSON.");
- return;
- }
-
- // 跳过"city":"部分,指向值的起始位置(即第二个引号之后)
- cityFieldStart2 += 11; // "city":" 共有7个字符
-
- // 查找值的结束位置(即下一个引号)
- const char* cityValueEnd2 = strchr(cityFieldStart2, '"');
- if (!cityValueEnd2) {
- Serial.println("Error extracting city value from JSON.");
- return;
- }
-
- // 计算city值的长度(不包括起始和结束引号)
- size_t cityValueLength2 = cityValueEnd2 - cityFieldStart2;
-
- // 创建一个字符数组来存储city值(加1用于null终止符)
- char cityValueBuffer2[cityValueLength2 + 1];
-
- // 复制city值到字符数组中
- strncpy(cityValueBuffer2, cityFieldStart2, cityValueLength2);
- cityValueBuffer2[cityValueLength2] = '\0'; // 确保字符串以null终止
-
- // 打印city值
- Serial.print("humidity: ");
- Serial.println(cityValueBuffer2);
- humidity = atoi(cityValueBuffer2);//atoi字符串转换为整数
- }
(六)AT指令大全
指令表出处:https://blog.csdn.net/michaelchain/article/details/119627777
基本命令
AT: 测试, 模块正常应当返回OK
AT+RST: 重启模块
AT+GMR: 检查固件版本信息
ATE: 配置 AT 命令的回显.
ATE0: 关闭回显
ATE1: 打开回显
AT+UART_CUR? 查看当前的UART配置
AT+UART_DEF? 查看默认的UART配置
WIFI命令
AT+CWMODE?: 查看当前的WIFI模式(Station/SoftAP/Station+SoftAP), 用=号可以设置
AT+CWMODE=3
AT+CWLAP: 列出周围的WIFI AP, 需要先设置为station模式, AT+CWMODE=1
AT+CWJAP: 连接到WIFI AP, 命令格式 AT+CWJAP="DXQ","aa123456"
掉电重启后, 模块会自动连接之前连接的WIFI
AT+CWQAP: 断开与WIFI AP的连接
AT+CIPSTAMAC: 查看客户端模式时的MAC地址
AT+CIPSTA?: 查看客户端模式的IP地址及掩码
AT+CIPAPMAC?: Query/Set the MAC address of an ESP SoftAP.
AT+CIPAP?: Query/Set the IP address of an ESP SoftAP.
TCP/IP命令
AT+CIPSTATUS: 获取TCP/UDP/SSL连接状态和信息, 先会显示一个状态, 然后会列出各个连接
0: The ESP station is not initialized.
1: The ESP station is initialized, but not started a Wi-Fi connection yet.
2: The ESP station is connected to an AP and its IP address is obtained.
3: The ESP station has created a TCP/SSL transmission.
4: All of the TCP/UDP/SSL connections of the ESP device station are disconnected.
5: The ESP station started a Wi-Fi connection, but was not connected to an AP or disconnected from an AP.
AT+PING: ping指定的地址, 返回平均响应时间
AT+PING="192.168.110.1"
AT+CIPSTART: 建立TCP/UDP/SSL连接
AT+CIPSTART="TCP","192.168.110.1",22
AT+CIPSTART="TCPv6","test-ipv6.com",80
AT+CIPSTART="UDP","192.168.101.110",1000,1002,2 第4个参数是本地端口, 第5个是模式, UDP unicast
AT+CIPSTART="UDPv6","FF02::FC",1000,1002,0 UDP multicast based on IPv6 network
AT+CIPSTART="SSL","iot.espressif.cn",8443
AT+CIPSEND: 透传模式发送数据
AT+CIPSENDEX: Send data in the normal transmission mode in expanded ways.
AT+CIPCLOSE: 关闭TCP/UDP/SSL连接
AT+CIFSR: 获取本地IP地址和MAC地址
AT+CIPSNTPTIME: 查询SNTP时间
AT+CIPMUX?: 查询连接类型, 0单连接, 1多连接
AT+CIPMUX=1 设置为多连接
AT+CIPSERVER: 创建或删除一个 TCP/SSL 服务, 创建前需要执行AT+CIPMUX=1
AT+CIPSERVER=1,80 创建一个TCP服务
AT+CIPSERVER=1,443,"SSL",1 创建一个SSL服务
AT+CIPSERVER=0,1 删除一个服务并关闭所有连接
AT+CIPSERVERMAXCONN?: 查询允许的服务最大连接数量
AT+CIPSTO?: 查询本地TCP服务超时时间
AT+CIPSNTPCFG?: 查询 time zone and SNTP server.
AT+CIPMODE: 查询传输模式
0: 普通传输模式
1: Wi-Fi 透传模式, 只有在TCP单连接模式, 以及UDP+remote host and port do not change模式, 或者SSL单连接模式时允许