STM32与物联网02-网络数据收发
在上一节中,介绍了 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 的两种传输模式:
- 普通传输模式(Normal Transmission Mode),该模式下,用户可以通过 AT 指令发送 TCP 数据,同时 ESP8266 也会将接收到的数据以 +IPD 等指令的形式返回
- 透传接收模式(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 编码完成身份验证,有兴趣的读者可以尝试自行实现。
参考资料/延伸阅读
TCP/IP 相关 AT 指令集的官方文档。
STM32与物联网02-网络数据收发的更多相关文章
- linux 网络数据收发网络流量监控
网卡流量 1.iftop命令 iftop可以用来监控网卡的实时流量(可以指定网段).反向解析IP.显示端口信息.TCP/IP连接等官网:http://www.ex-parrot.com/~pdw/if ...
- 转载 STM32 使用Cubemx 建一个USB(HID)设备下位机,实现数据收发
STM32 使用Cubemx 建一个USB(HID)设备下位机,实现数据收发 本文转载自 https://www.cnblogs.com/xingboy/p/9913963.html 这里我主要说一 ...
- STM32 使用Cubemx 建一个USB(HID)设备下位机,实现数据收发
这里我主要说一下如何做一个USB下位机,这里主要分3部分:1.建立工程:2.添加报文描述符:3.数据的传输.这里就不讲USB的理论知识了,有想要了解的自行百度一下就可以了. 建立工程:工程建立参考:h ...
- java网络编程——多线程数据收发并行
基本介绍与思路 收发并行 前一篇博客中,完成了客户端与服务端的简单TCP交互,但这种交互是触发式的:客户端发送一条消息,服务端收到后再回送一条.没有做到收发并行.收发并行的字面意思很容易理解,即数据的 ...
- STM32与物联网01-ESP8266基本操作
ESP8266物联网简介 ESP8266简介 ESP8266 是上海乐鑫公司开发的一款具有 WiFi 功能的控制芯片,它带有完整的 TCP/IP 协议栈,因此可以用作物联网开发. ESP8266 本身 ...
- FC网络学习笔记02 -网络配置方法
随着新一代飞机的综合化航电系统对通信需求的不断提高,传统的ARINC429.1553B总线的传输速率分别只有100Kbps和1Mbps,其带宽已远远不 论文联盟 http://Www.LWlm.cOm ...
- 发现新大陆:一个最简单的破解SSL加密网络数据包的方法
1. 简介 相信能访问到这篇文章的同行基本上都会用过流行的网络抓包工具WireShark,用它来抓取相应的网络数据包来进行问题分析或者其他你懂的之类的事情. 一般来说,我们用WireShark来抓取包 ...
- Linux网络底层收发探究【转】
转自:https://blog.csdn.net/davion_zhang/article/details/51536807 本文为博主原创文章,未经博主允许不得转载. https://blog.cs ...
- 【转】为什么 MQTT 是最适合物联网的网络协议
初识 MQTT 为什么 MQTT 是最适合物联网的网络协议 Michael Yuan2017 年 6 月 14 日发布 WeiboGoogle+用电子邮件发送本页面 0 物联网 (IoT) 设备必须连 ...
随机推荐
- .NET桌面程序应用WebView2组件集成网页开发4 WebView2的线程模型
系列目录 [已更新最新开发文章,点击查看详细] WebView2控件基于组件对象模型(COM),必须在单线程单元(STA)线程上运行. 线程安全 WebView2必须在使用消息泵的UI线程上创 ...
- 图文详解:小白也能看懂的 Kubernetes
Kubernetes 这个单词来自于希腊语,含义是舵手或领航员 .其词根是 governor 和 cybernetic.K8s 是它的缩写,用 8 字替代了"ubernete". ...
- Java学习笔记-基础语法ⅩⅠ-UDP、TCP
网络编程 三要素:IP地址.端口.协议 IP地址:使用ipconfig查看,如果装了VM的话,会有VMnet1.VMnet8和WLAN,net1不能从虚拟机到主机,net8不能从主机到虚拟机,net0 ...
- linux篇-linux LAMP yum版安装
LAMP(linux.apache.mysql.php),是四个套件的合成,简单讲就是要把php运行在linux上,需要依赖apache和mysql数据库. 1 准备好一个linux系统(centos ...
- PyTorch保存模型、冻结参数等
此外可以参考PyTorch模型保存.https://zhuanlan.zhihu.com/p/73893187 查看模型每层输出详情 Keras有一个简洁的API来查看模型的每一层输出尺寸,这在调试网 ...
- 深度学习与CV教程(13) | 目标检测 (SSD,YOLO系列)
作者:韩信子@ShowMeAI 教程地址:http://www.showmeai.tech/tutorials/37 本文地址:http://www.showmeai.tech/article-det ...
- 线上问题定位利器 jprofiler
1.导出dump windows: jps -l 查看Java进行 jmap -dump:format=b,file=webapi.hprof 20840 查看进程,根据进程号导出hprof文件 ...
- 2.2 追求并发的极致-线程概论 -《zobolの操作系统学习札记》
2.2 追求并发的极致-线程概论 为了追求程序运行之间的并发性,计算机科学家们发明了进程.为了进一步的追求进程内部的并发性,工程师们又提出了线程. 正是线程的出现,给予了程序员更多地操纵OS的自由,可 ...
- BUUCTF-easycap
easycap 看这个题目应该是流量包来的,wireshark打开即可.没什么特征,直接打开第一个包发现flag
- Javaweb-IDEA 中Maven的操作
1. 在idea中使用Maven 启动idea 创建一个MavenWeb项目 3.等待项目初始化完毕 4. 观察maven仓库中多了哪些东西 5. idea中的maven设置 注意:idea项目创成功 ...