上一节中,介绍了 ESP8266 的使用方法。不过上一节中都是通过串口调试工具手动发送信息的方式来操作 ESP8266 ,这肯定不能用于实际开发。因此,本节介绍如何编写合适的程序来和 ESP8266 交互,从而收发并解析网络数据。

TCP服务器

在 TCP 服务器下,可以使用移动设备主动连接 ESP8266 提供的 WiFi 。如果编写正确的程序,那么可以使用移动设备控制 ESP8266 。

建立TCP服务器

从上一节的介绍可以了解到,程序和 ESP8266 的交互主要是通过发送 AT 指令完成的,因此程序中首要的任务就是编写合适的程序向 ESP8266 发送指令。

不过在发送指令后,可能还需要判断指令是否被成功接收。一般来说,ESP8266 执行失败时可能返回各种信息,但在成功执行指令后都会返回 OK 。发送指令可以通过以下函数完成:

uint8_t ESP8266_SendCmd(char* cmd, uint8_t timeout) {
ESP8266_Buffer.Length = 0;
memset(ESP8266_Buffer.Body, 0, USART_RX_BUF_SIZE);
USART_printf(USART3, "%s\r\n", cmd);
while (timeout--) {
delay_ms(100);
if (strstr(ESP8266_Buffer.Body, "OK"))
return 0;
}
return 1;
}

由于不同指令处理的时间也不一致,因此在程序中引入了一个倒计时器,在倒计时结束前不断检查接收到的信息中是否包含 "OK" ,如果是则结束当前倒计时,这样可以确保在指令执行完后就可以立即退出延时,提高程序执行效率。

程序中与 ESP8266 交互基本是都采用这种方式。例如,在程序下载后若希望使 ESP8266 也重启,则可以通过拉低 RST 引脚复位 ESP8266 ,复位后会接到 "ready" 信息,则可以编写以下函数:

uint8_t ESP8266_Reset(uint16_t timeout) {
ESP8266_RST(RESET);
delay_ms(500);
ESP8266_RST(SET);
while (timeout--) {
delay_ms(100);
if (strstr(ESP8266_Buffer.Body, "ready"))
return 0;
}
return 1;
}

如果某条指令有其余回复的情况,只需要参照以上略做修改即可。

有了以上函数后,就可以编写代码,逐条发送指令了。这里将 ESP8266 设置为 AP 模式,使其变成一个 WiFi 热点,使计算机可以直接连接上 ESP8266 并收发信息,因此首先需要发送 AT+CWMODE=2 指令:

if (ESP8266_Reset(50))
return 1;
if (ESP8266_SendCmd("AT+CWMODE=2", 50))
return 2;

然后可以使用 AT+CWSAP="<ssid>","<password>",<chl>,<enc> 设置 WiFi 参数,一般来说通道号和加密类型都设置为 4 即可:

char cmd[64];
sprintf(cmd, "AT+CWSAP=\"%s\",\"%s\",%d,%d\r\n", SSID, PASSWORD, 4, WPA_WPA2_PSK);
if (ESP8266_SendCmd(cmd, 50))
return 3;

可以将这些参数设置为宏定义以方便修改。接下来的许多设置都和以上代码类似,可以以此为模板替换为其它命令,因此不再展示代码,仅介绍主要命令。

如果要设置固定的局域网 IP ,可以通过 AT+CIPAP="<ip>" 完成。

接下来可以通过指令 AT+CIPMODE=<mode> 设置 ESP8266 的传输模式。该命令可以设置 ESP8266 的两种传输模式:

  1. 普通传输模式(Normal Transmission Mode),该模式下,用户可以通过 AT 指令发送 TCP 数据,同时 ESP8266 也会将接收到的数据以 +IPD 等指令的形式返回
  2. 透传接收模式(Passthrough Receiving Mode):该模式下,ESP8266 无法发送 TCP 数据,同时 ESP8266 会将接收到的数据以原始的形式返回给 STM32

透传接收模式一般用于开启透传模式。关于透传模式会在后续介绍。

ESP8266 支持多路连接,即一个 TCP 端口可以建立多个连接。通过 AT+CIPMUX=1 可以启用多连接,每个连接到端口上的客户端通过 <id> 标识,连接的数量最后为 5 个,因此 <id> 的取值范围为 0~4 。

多连接必须在所有连接都断开且服务器也关闭时才可以设置,并且只有普通传输模式下才能设置为多连接。

接下来,可以通过 AT+CIPSERVER=1,8266 开启一个位于端口 8266 上的 TCP 服务器。根据以上步骤,TCP 服务器便建立完成,可以准备接收客户端发来的数据了。

数据获取与解析

在建立了 TCP 服务器后,ESP8266 便会等待客户端的连接。

TCP 客户端在接到客户端的数据时,会以 +IPD,<id>,<len>:<data> 的指令形式转交数据给 STM32 。由于以上开启了多路连接,因此接收的数据中多了一个字段 <id>

因此判断是否有数据收到也很简单,只需要判断接收缓冲区内是否有子串 "+IPD" 即可:

bool ESP8266_HasData(void) {
return strstr(ESP8266_Buffer.Body,"+IPD")
&& strstr(ESP8266_Buffer.Body,":");
}

以上同时查找子串 ":" 确保数据有效性。根据以上格式,拆解该字符串并截取有效数据如下:

int8_t ESP8266_MuxGetData(char* data, uint16_t* len) {
uint8_t id;
char* data_ptr = strstr(ESP8266_Buffer.Body, "+IPD");
if (sscanf(data_ptr,"+IPD,%d,%d", &id, len) == 2) {
memcpy(data, strstr(data_ptr, ":") + 1, *len);
data[*len] = '\0';
ESP8266_Buffer.Length = 0;
memset(ESP8266_Buffer.Body, 0, USART_RX_BUF_SIZE);
return id;
}
return -1;
}

以上函数略显复杂。之所以要这么复杂,主要有以下两个方面的原因:scanf() 类函数使用字符串转换说明时,它在读入数据时如果遇到一个空格或回车符,会丢弃后面的所有数据,这显然不能用于截取用户数据。

另外上一节说过工程中接收串口传来的不定长数据的方式是使用串口空闲中断,然而空闲中断接收的一包数据并不都是符合期望的一包数据:在接收到 TCP 连接时,ESP8266 会发送 <id>,CONNECT 表示连接已建立,如果此时建立的连接接收到任何数据,ESP8266 也会立即转发该数据。因此如果连接建立后马上收到数据,那么两次发送的数据时间相隔过短,可能会没有引起空闲中断而被 STM32 认为是同一包数据。在连接取消时,也有同样的问题。

不过发送数据的函数可能更加复杂:

uint8_t ESP8266_MuxSendData(uint8_t* data, uint16_t length, uint8_t id, uint8_t timeout) {
ESP8266_ClearBuffer();
USART_printf(USART3, "AT+CIPSEND=%d,%d\r\n", id, length);
while (timeout--) {
delay_ms(10);
if (strstr(ESP8266_Buffer.Body, ">"))
break;
}
if (timeout > 0) {
ESP8266_ClearBuffer();
USART_SendBytes(USART3, data, length);
while (timeout--) {
delay_ms(10);
if (strstr(ESP8266_Buffer.Body, "SEND OK")) {
ESP8266_ClearBuffer();
return 0;
}
if (strstr(ESP8266_Buffer.Body, "link is not valid")) {
ESP8266_ClearBuffer();
return 2;
}
}
return 3;
}
else
return 1;
}

上一节介绍了发送数据主要使用 AT+CIPSEND 指令完成(多连接下需要一个额外的字段指示发送给的 <id> ),如果可以发送 ESP8266 会返回 "> " 作提示。如果发送成功,ESP8266 会返回 "SEND OK" ,通过返回提示就可以知道发送状态。

有了以上函数以后,就可以着手编写主程序了。主程序的处理逻辑非常简单,在建立 TCP 服务器后,便不断判断是否有数据到达,如果有那么便读取数据并回复信息:

char ipd_data[512];
int8_t ipd_id;
uint16_t ipd_len;
while (ESP8266_CreateTcpServer())
delay_ms(200);
while (1) {
if (ESP8266_HasData()) {
ipd_id = ESP8266_MuxGetData(ipd_data, &ipd_len);
ESP8266_MuxSendData("Acknowledge", 12, ipd_id, 30);
}
delay_ms(500);
}

可以将得到的数据显示在串口中。在计算机客户端,编写如下套接字程序:

import socket, time
client = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
client.connect(('192.168.10.1', 8266))
client.send(time.ctime().encode())
message = client.recv(1024)
print(message.decode())
client.close()

将计算机连接到 ESP8266 创建的 WiFi 上并运行该套接字程序,即可观察到实验现象。如果为程序编写合适的用户界面并在 STM32 上进一步解析数据,那么便可以实现手机端控制 STM32 了。

TCP客户端与HTTP请求

TCP 客户端的建立与 TCP 服务器类似,这里先使 ESP8266 连接到路由器,借助路由器访问公网上的服务器。

前几步操作与 TCP 客户端类似:在复位 ESP8266 后,首先通过指令 AT+CWMODE=1 设置 Sta 模式,然后通过 AT+CWJAP="<ssid>","<password>" 连接到路由器中。由于客户端无需多个连接,可以使用 AT+CIPMUX=0 关闭多连接。

本次采用透传模式(Passthrough Mode)来收发数据。透传模式是一种特殊的收发数据模式,在透传模式下,用户不能发送 AT 指令,发送的任何数据都会作为原始的数据发送到传输对端;从传输对端收到的数据也会不经由任何 +IPD 封装而原封不动地返回给 STM32 。

使用 AT+CIPMODE=1 可以设置传输模式为透传模式。通过 AT+CIPSTART 连接上服务器后,直接执行 AT+CIPSEND ,待 ESP8266 返回 "> " 后就进入了透传模式。透传模式下,每包数据以 20ms 间隔区分,每包最大 2048 字节,发送和接收数据都不需要封装成指令,方便处理。

正常退出透传模式的唯一方式就是单独发送一包发送指令 +++

根据以上原理,可以使用 STM32 发送相应指令,连接到服务器后进入透传模式,并准备发送相应的数据。其代码和上文服务端类似,例如:

if (ESP8266_Reset(50))
while (1);
if (ESP8266_SendCmd("AT+CWMODE=1", 20))
while (1);
if (ESP8266_SendCmd("AT+CWJAP=\"TP_LINK\",\"abc123456\"", 100))
while (1);
// ... and so on

当然,考虑到一些指令执行成功时不总是返回 OK ,并且为了使程序逻辑更清晰,可以将一些指令封装成函数。例如,以下函数根据地址(可以是 IP 地址或域名,DNS 解析将自动完成)和端口号,连接到特定的 TCP 服务器中并进入透传模式:

uint8_t ESP8266_ConnectServer(char* address, uint16_t port, uint8_t timeout) {
ESP8266_ClearBuffer();
USART_printf(USART3, "AT+CIPSTART=\"TCP\",\"%s\",%d\r\n", address, port);
while (timeout--) {
delay_ms(100);
if (strstr(ESP8266_Buffer.Body, "CONNECT")) {
ESP8266_ClearBuffer();
USART_printf(USART3, "AT+CIPSEND\r\n");
while (timeout--) {
delay_ms(100);
if (strstr(ESP8266_Buffer.Body, "\r\nOK\r\n\r\n>"))
return 0;
}
return 4;
}
if (strstr(ESP8266_Buffer.Body, "CLOSED"))
return 1;
if (strstr(ESP8266_Buffer.Body, "ALREADY CONNECTED"))
return 2;
}
return 3; //超时错误,返回3
}

可以仿照该函数将其它指令封装成具有抽象功能的对应函数。

在本示例中,在使用 TCP 连接到远程服务器的 80 端口的基础上,手动构造合适的 HTTP 请求并发送:

while(1) {
USART_SendString(USART3, "GET /api/temperature?time=now HTTP/1.1\r\n"
"Connection: keep-alive\r\n"
"Host: 192.168.1.105:80\r\n\r\n");
delay_s(5);
printf("%s", ESP8266_Buffer.Body);
}

这里 5 秒钟便查询一次数据。如果间隔过长,连接可能断开,那么可以先主动断开连接,等需要查询时再发起 TCP 连接。

通过 HTTP 服务器提供的合适接口,ESP8266 便可以从互联网中获取到非常广泛的数据。在测试用的服务器中,该接口返回一个 json 响应并被转发到 STM32 中,串口调试工具中显示的原始数据如下:

HTTP/1.1 200 OK
Date: Wed, 13 Jul 2022 10:57:48 GMT
Server: WSGIServer/0.2 CPython/3.9.1
Content-Type: application/json
X-Frame-Options: DENY
Content-Length: 93
X-Content-Type-Options: nosniff
Referrer-Policy: same-origin {"temperature": {"high": 37.6, "low": 28.1, "now": 36.7}, "humidity": "50%", "wind": "11mph"}

通过解析请求头 Content-Length 就可以获取数据的长度,然后查找子串 "{" 的位置便可以提取出接口返回的 json 数据,并可以使用 cJSON 等第三方库解析其中的数据。互联网中存在许多类似的接口,只需要构造合适的请求头,便可以抓取很多有用的信息,不过这需要有一定的抓包或爬虫的基础。

通过路由器可以接入互联网,在 TCP 服务的基础上,构造出合适的 HTTP 等应用层协议的封装,便可以采集互联网中的各种数据,或者向服务器报告自身传感器的数据,由此真正实现物联网的基础。

例如,可以向 HTTP 服务器提供的接口发送 POST 请求,将传感器数据作为参数发送给服务器,服务器解析 POST 请求并更新数据库,然后便可以显示在前端上,这样便可以在任何地点查看 STM32 的状态了。不过由于其实现涉及到的知识点过于广泛,无论是环境的配置还是程序的编写都不是一篇文章能完整介绍的,这里便不再涉及。

一个比较有趣的实现是利用 SMTP 发送电子邮件,可以阅读这篇文章了解 SMTP 应用层协议的原理与基本报文格式,文章中附带了 Python 套接字程序实现,它和 AT 指令的思路具有一定相似性,移植到 STM32 的主要难点是使用 base64 编码完成身份验证,有兴趣的读者可以尝试自行实现。

参考资料/延伸阅读

https://docs.espressif.com/projects/esp-at/en/release-v2.2.0.0_esp8266/AT_Command_Set/TCP-IP_AT_Commands.html

TCP/IP 相关 AT 指令集的官方文档。

STM32与物联网02-网络数据收发的更多相关文章

  1. linux 网络数据收发网络流量监控

    网卡流量 1.iftop命令 iftop可以用来监控网卡的实时流量(可以指定网段).反向解析IP.显示端口信息.TCP/IP连接等官网:http://www.ex-parrot.com/~pdw/if ...

  2. 转载 STM32 使用Cubemx 建一个USB(HID)设备下位机,实现数据收发

    STM32 使用Cubemx 建一个USB(HID)设备下位机,实现数据收发  本文转载自 https://www.cnblogs.com/xingboy/p/9913963.html 这里我主要说一 ...

  3. STM32 使用Cubemx 建一个USB(HID)设备下位机,实现数据收发

    这里我主要说一下如何做一个USB下位机,这里主要分3部分:1.建立工程:2.添加报文描述符:3.数据的传输.这里就不讲USB的理论知识了,有想要了解的自行百度一下就可以了. 建立工程:工程建立参考:h ...

  4. java网络编程——多线程数据收发并行

    基本介绍与思路 收发并行 前一篇博客中,完成了客户端与服务端的简单TCP交互,但这种交互是触发式的:客户端发送一条消息,服务端收到后再回送一条.没有做到收发并行.收发并行的字面意思很容易理解,即数据的 ...

  5. STM32与物联网01-ESP8266基本操作

    ESP8266物联网简介 ESP8266简介 ESP8266 是上海乐鑫公司开发的一款具有 WiFi 功能的控制芯片,它带有完整的 TCP/IP 协议栈,因此可以用作物联网开发. ESP8266 本身 ...

  6. FC网络学习笔记02 -网络配置方法

    随着新一代飞机的综合化航电系统对通信需求的不断提高,传统的ARINC429.1553B总线的传输速率分别只有100Kbps和1Mbps,其带宽已远远不 论文联盟 http://Www.LWlm.cOm ...

  7. 发现新大陆:一个最简单的破解SSL加密网络数据包的方法

    1. 简介 相信能访问到这篇文章的同行基本上都会用过流行的网络抓包工具WireShark,用它来抓取相应的网络数据包来进行问题分析或者其他你懂的之类的事情. 一般来说,我们用WireShark来抓取包 ...

  8. Linux网络底层收发探究【转】

    转自:https://blog.csdn.net/davion_zhang/article/details/51536807 本文为博主原创文章,未经博主允许不得转载. https://blog.cs ...

  9. 【转】为什么 MQTT 是最适合物联网的网络协议

    初识 MQTT 为什么 MQTT 是最适合物联网的网络协议 Michael Yuan2017 年 6 月 14 日发布 WeiboGoogle+用电子邮件发送本页面 0 物联网 (IoT) 设备必须连 ...

随机推荐

  1. 【kubevirt】VirtualMachineInstanceReplicaSet(vmis)-扩缩容-弹性伸缩

    @ 目录 概述/理解 使用场景 创建vmis 扩缩容 弹性伸缩 方法1 方法2 概述/理解 VirtualMachineInstanceReplicaSet(vmis)确保指定数量的 VirtualM ...

  2. 团队Beta演示

    组长博客 本组(组名)所有成员 短学号 姓名 2236 王耀鑫(组长) 2210 陈超颖 2209 陈湘怡 2228 许培荣 2204 滕佳 2205 何佳琳 2237 沈梓耀 2233 陈志荣 22 ...

  3. IT人的修炼之路

    前言 计算机技术更新迭代的速度太快了,作为ITer每天除了面对工作,就要学习新技术,自己的感觉是一直在为技术疲于奔命,直到现在,也不敢放缓脚步.程序员每天必须抽出一定时间学习新技术,避免被淘汰. 1. ...

  4. 1.Docker简介

    Docker是个什么东西 假定您在开发一个项目,您使用的是一台笔记本电脑而且您的开发环境具有特定的配置.其他开发人员身处的环境配置也各有不同.您正在开发的应用依赖于您当前的配置且还要依赖于某些配置文件 ...

  5. logging、openpyxl、第三方模块下载

    ### 日志模块的组成部分 ```pythonimport logging# 1.logger对象:产生日志logger = logging.getLogger('转账记录')# 2.filter对象 ...

  6. mybatis plus 使用 SQL 保留字(关键字)

    MybatisPlus:使用SQL保留字(关键字)的操作 必须要手动在 entity 的字段上加注解,否则最终会报错,因为 mybatis 或者 mybatis plus 不会自动处理

  7. 【多线程】可重入锁 ReentrantLock

    java除了使用关键字synchronized外,还可以使用ReentrantLock实现独占锁的功能.而且ReentrantLock相比synchronized而言功能更加丰富,使用起来更为灵活,也 ...

  8. unity---脚本创建文本

    脚本创建文本 新建文件夹 Resources 方便引用字体 在文件Resources中新建Fonts,并且下载一个ttf字体 没有字体,文本内容无法显示 脚本如下 public GameObject ...

  9. 目标检测复习之Loss Functions 总结

    Loss Functions 总结 损失函数分类: 回归损失函数(Regression loss), 分类损失函数(Classification loss) Regression loss funct ...

  10. 对抗噪音,一键清晰,HMS Core音频编辑服务给你“录音棚”般的体验

    短视频时代来临,一部手机就可以玩转多种花样,所以越来越多的自由创作者加入这个行业,平时生活中用手机拍短视频.街头唱歌的非专业从业者随处可见.离开了录音棚,没有专业.统一的录音设备,无论在家里还是在路边 ...