基于ESP32的无线数据采集系统设计 ①

2021-01-15 03:27何栋炜
关键词:存储空间线程队列

高 培, 何栋炜

(1.福建商学院,福建 福州 350012;2.福建工程学院,福建 福州 350118)

0 引 言

无线数据采集是现代信息通信研究的重要组成部分。在很多领域,比如人员无法到达的偏僻环境、高腐蚀性及现场无法利用有线连接的环境,选择有线数据采集传输系统显然己无法满足数据采集和传输的需要。无线数据采集方式就成为了一种有效的替代方式,它与传感器网络、信息处理等作为现代数据监测控制的基本技术,在物联网、工业控制、环境监测等方面得到了越来越广泛的应用[1-2]。

ESP32是乐鑫继ESP8266后推出的一款集成Wifi功能的蓝牙系统级芯片,基于ESP32的强大的处理能力、低功耗及高速稳定的Wifi通信等特点。设计一套以ESP32主控芯片为核心的无线数据采集系统,系统通过ESP32连接芯片STM32F407进行数据采集,再使用无线Wifi模块连接指定网络,通过Socket接口与上位机进行通讯,将采集的数据传输给上位机系统。

1 系统硬件设计

无线数据采集系统硬件结构如图1所示,主要由数据源模块(本设计中使用STM32F407ZG)、ESP32模块及上位机三部分构成。其中ESP32与数据源模块通过SPI接口连接,本设计中ESP32模块作为SPI Slave(被动式SPI设备),数据源模块作为SPI Master(SPI主动设备);ESP32通过AP(无线访问接入点),与上位机建立以太网络连接。系统运行时,数据源模块周期性地将数据发送给ESP32模块,ESP32系统对所采集到的数据进行缓存,在与上位机保持连接的同时将所采集到的数据通过WiFi网络发送给上位机,上位机则对数据进行存储及实时显示。

图1 无线数据采集系统硬件结构图

ESP32作为SPI Slave使用时,只能等待SPI Master发起通信连接,并根据主机提供的时钟信号接收数据。STM32F407是由ST(意法半导体)开发的一种高性能微控制器,作为SPI Master使用时,每个SPI 控制器可以使用多个片选信号(CS0~CS2)来连接多个被动式SPI设备。图2为ESP32与STM32F407设备的通信电气连接图,其中CS为片选信号,SCK为时钟信号,MOSI为主设备数据输出线,MISO为从设备数据输出。

2 系统主程序设计

ESP32的主程序设计主要包括两个模块,Wifi网络配置模块和线程与队列模块。其中Wifi网络配置模块负责网络配置,线程与队列模块负责实现无线数据采集系统的主要功能,即:SPI数据采集与网络数据发送[3]。主程序设计流程图见图3所示,具体包括以下几个步骤:

1.程序初始化:初始化NVS存储和Wifi模式配置。

2.连接网络,由Wifi网络配置模块连接到指定AP,并获得AP分配的IP。

3.创建三个线程:SPI数据接收线程用于调用SPI底层驱动来接收STM32F407发送的数据并进行缓存;数据发送线程则创建Socket与上位机PC建立无线通信连接,并与SPI接收线程协同将数据发送到上位机;WEB服务器线程模块为上位机提供信息查询,使上位机可以通过浏览器获取ESP32系统上的运行信息。

图2 ESP32与STM32F407设备的通信电气连接图

图3 系统主程序设计框图

下面具体介绍每个模块的功能及程序设计。

2.1 WIFI网络配置模块程序设计

2.1.1 WIFI模式选择

ESP32芯片支持高速稳定的WIFI通信,并支持三种模式:“AP”、“STA”、”AP + STA”。本设计采用STA模式,使用WIFI_init_sta()函数进行STA模式配置[4]。进入STA模式之前,需要配置WIFI账号和密码才能连接到指定AP。

图4 STA模式配置流程图

2.1.2 STA模式配置

STA模式的配置总体流程如图4所示,使用函数WIFI_init_sta()完成STA模式配置,具体包括以下工作:

1.创建事件组标志,用于识别WIFI连接过程中的各种标志位。

2.初始化硬件/软件:使用“tcpip_adapter_init()”函数初始化TCP/IP适配层,清空之前保存的IP信息;使用“esp_event_loop_init()”初始化事件调度器,使事件回调函数能够从初始位置判断标志位。使用“WIFI_init_config_t cfg=WIFI_INIT_CONFIG_DEFAULT()”语句初始化WIFI模块的底层参数信息;使用“esp_WIFI_init(&cfg)”初始化WIFI驱动。

3.对STA模式参数进行配置,把路由器的WIFI账号、密码数据放入标准变量中。

4.使“esp_WIFI_set_mode(WIFI_MODE_STA)”用来设置STA模式;esp_WiFi_set_config(ESP_IF_WIFI_STA, &WiFi_config)设置STA模式的配置信息;“esp_WIFI_start()”语句启动WIFI状态机。

2.2 线程与队列模块程序设计

线程与队列模块的程序设计框图如图5所示,主要包括三个线程和一个队列的程序设计。三个线程分别是SPI数据采集线程、数据发送线程和WEB服务线程,另外为保证ESP32系统及时有效地进行数据接收和发送,需要采用队列来保持SPI数据采集线程和数据发送线程之间的数据同步[5]。

图5 线程与队列模块的程序设计框图

2.2.1 队列设计

使用FreeRTOS的队列“queue”实现SPI数据采集线程和数据发送线程之间数据同步。

队列的基本原理是不同的线程任务或者中断程序都能够使用队列将数据写入,或者读取数据。队列实际上是一个固定大小的空间,任何线程任务都可以对它进行读写。

主程序中语句“queue = xQueueCreate(BLOCK_NUM, sizeof(char *));”用来创建队列,FreeRTOS会分配固定的空间供线程读写数据。其中BLOCK_NUM设置可存储的数据单元个数与缓冲区存储空间个数一致。参数2设置数据单元的大小,线程间只需传递存储空间的首地址即可。

2.2.2 SPI数据采集线程程序设计

ESP32通过SPI协议与STM32F407进行数据传输,STM32F407间隔一定的时间向ESP32发送数据字节[6]。

为实现SPI数据采集及两个线程间的数据传递,同时考虑到ESP32有限的内存及SPI底层驱动机制,程序设计采用一个环形缓冲区来存储从SPI信号线上接收到的数据,环形存储器的大小为M * N Bytes,其中M是单个存储空间的大小,N代表存储空间个数。本次设计中ESP32的SPI底层驱动采用DMA机制,因此所建立的环形缓冲空间需要向系统申请DMA访问用的内存空间,需要用到heap_caps_malloc函数, MALLOC_CAP_DMA,单个存储空间大小的选择最好根据每个周期SPI发送数据的长度选择,即M为每个周期SPI发送数据长度的整数倍,同时为了保证网络发送效率,M需要选择尽量接近MTU大小1300(ESP32不支持分片,且分片影响效率),每次发送长度为64Bytes,因此选择M为64*20=1280Bytes,存储空间个数N方面,理论上选择越大的N,系统运行中缓存空间越大,SPI接收出现错误的概率越小,但由于ESP32的内存空间有限,N过大会影响其他线程,通过实验选择N为5。

SPI数据采集程序主要包括:SPI驱动初始化,环形缓冲区初始化,SPI Slave读取程序。SPI Slave驱动程序通过操作DMA和SPI控制器与SPI Master进行通信,同时对应用程序提供访问接口,详细的数据结构及程序设计可参考文献[7]。

SPI Slave读取程序流程为:

1.循环调用spi_slave_transmit将当前缓冲区以及接收数据信息通过spi_slave_interface_config_t结构体指定给底层驱动,并启动SPI接收,线程进入挂起状态等待接收结束。

2.接收结束后,线程继续运行,将接收到的数据所在存储空间的首地址通过xQueueSendToBack加入队列。

3.将当前缓冲区切换为环形缓冲区的下一存储空间,重复1。

2.2.3 数据发送线程程序设计

在STA模式下,Socket接口与上位机有两种建立通信连接的方式,即“TCP模式”和“UDP模式”。由于TCP模式传输的数据不容易丢失,而UDP模式传输的数据容易出现错误丢失情况,因此本次连接采用TCP模式。

当ESP32连接上WIFI并获取到AP分配的IP后,即可通过Socket接口与上位机建立通讯,发送获取到的数据。Socket是ESP32和上位机之间建立通讯的接口。ESP32主动对上位机发起连接请求,上位机在连接前开启监听的Socket接口,以实时监听客户端的请求,连接建立完成后,ESP32和上位机即可进行通信。ESP32与上位机通信连接建立流程如图6所示:

主要步骤及具体程序描述如下[8]:

1.定义以下套接字描述符:“sock_fd”监听套接口描述符;“client_fd”数据传输套接口描述符;“bind_fd”绑定IP/端口套接口描述符。定义本地地址变量“my_addr”,用于保存本地IP与端口号;定义目标地址变量“client_addr”,用于保存目标机IP及端口号。

2. 使用socket(AF_INET, SOCK_STREAM, 0)语句创建Socket监听。

图6 ESP32与上位机通信连接建立流程图

3. 将本地的IP、端口号、协议类型与Socket绑定,主要程序如下:

my_addr.sin_family = AF_INET;

my_addr.sin_port = htons(PORT);

my_addr.sin_addr.s_addr=htonl(INADDR_ANY);

bind_fd = bind(sock_fd,(struct sockaddr *)&my_addr,sizeof(my_addr));

4. 开启监听Socket使系统处于监听状态,等待上位机的连接请求,使用语句为“listen(sock_fd,10)”进行监听。

5. 接受上位机的连接请求,使用accept(sock_fd, (struct sockaddr *)&client_addr, &sin_size)语句建立通信socket。

6. 确定连接建立后,调用xQueueReceive读取队列,如果队列为空,线程挂起直至队列非空。如果队列为非空,从队列中获取数据存储空间首地址。

7. 使用write(newconn, (char *)tmp, BUF_SIZE)向上位机发送数据。如果发送失败就会返回-1,程序进入报错停止发送程序;如果发送数据成功会将实际发送出的字符数返回,程序回到6。

2.2.4 Web服务器线程程序设计

Web 是一种网络通信协议,是一种在单个 TCP 连接上进行全双工通讯的协议。如图7所示,ESP32连接上WIFI后,会创建一个WEB服务器线程,该线程用于为上位机提供信息查询。当上位机发送HTTP请求时,系统会将从SPI接收的数据和发送成功的数据信息返回到上位机的浏览器中,方便用户查询在数据通信过程中是否存在报文丢失。

图7 WEB服务线程工作过程示意图

图8 硬件设备连接图

图9 ESP32连接至WIFI过程图

WEB服务线程程序设计主要步骤如下[9]:

1.创建Socket连接,并绑定本地IP地址和80端口,用于监听上位机的连接请求。当上位机发出连接请求后,系统会与上位机建立连接,并建立通信socket。创建Socket和建立连接的具体程序设计可参考3.2.3节。

2.ESP32读取上位机发送的请求数据,将其放在recv_buffer的字符型数组中,通过语句strncmp((char *)recv_buffer, "GET", 3) == 0判断请求报文前三个字符是否为“GET”,如果是的话会执行如下命令:

sprintf(disp,"spi:%d;tcp:%d! ",spi_count,tcp_count);

strcpy(htmldata,htmldata_h); strcat(htmldata,disp); strcat(htmldata,htmldata_t);

write(conn, htmldata, strlen(htmldata));

其中disp为自定义的字符型数组,用于存放返回的数据;spi_count为系统统计接收到的SPI数据,tcp_count为系统发送给上位机的数据;htmldata为长度为200的字符型数组;htmldata_h和htmldata_t定义如下:

char

charhtmldata_t[] ="《/html>";

最后通过write函数将加上HTML标签的数据返回到上位机的浏览器中显示出来。对于需要返回的其它信息也可以由用户自行定义。

图10 上位机接收数据绘图

图11 上位机接收数据图

3 实验验证

本次实验主要通过两个开发板进行系统搭建,一块是ESP32-DevKitC,开发环境是Ai-ThinkerIDEV1.0,另一块是STM32F407,两个开发板之间的SPI连接原理如图2所示。由于我们只需要进行单向发送,所以除去VCC和接地线,只需要连接三条线。用杜邦线根据电路接线图连接好实物图,完成后的硬件设备连接如图8所示。

图中每种颜色的杜邦线对应条接线,黑色为地线,绿色为串行时钟信号线,橙色为MOSI数据信号线,黄色为片选信号线。

图12 WEB查询系统信息图

实验主要验证ESP32与Wifi的连接是否正常,以及是否能正常接收SPI数据并通过WiFi发送数据到上位机,同时计算数据在传输的过程中的速率及丢包率。

首先测试ESP32与WiFi的连接,可以采用串口调试工具打印ESP32输出的信息,明确ESP32当前的运行连接状态;系统连接上电后即进入了准备状态,按下ESP32 RST键,ESP32会自动连接WiFi。通过串口调试工具可以看到整个WiFi连接过程,如图9所示

通过图9可以看到ESP32系统首先会进行STA模式配置,当完成STA模式配置后会输出当前的用户名和密码,接下来AP会为其分配IP地址。当打印获取到IP地址的时候表示ESP32连接AP成功,AP分配的IP地址是192.168.2.30。通过串口打印的提示信息可以看到ESP32系统的准备过程,其中spi_task on表示SPI数据接收线程开启;Data_trans_task on表示数据发送线程开启;Data_trans_task listen和Initial_SPI Done表示ESP32系统开启了SPI的监听端口并完成SPI初始化配置;Http_task on表示WEB服务线程开启; Data_trans_task accept表示ESP32系统与上位机已经建立了TCP连接并且可以开始进行数据传输。

通过图10可以看到上位机接收的数据总字节数为38400Bytes,并且数据从0开始递增到2后又重置为0。上位机将接收到的数据以后缀为.dat的文件进行存储,打开文件可以看到具体的接收数据,如图11所示,在不考虑浮点数存储误差的情况下,每个数据间隔0.001。 最后,为了验证发送过程中的成功率,可以通过浏览器访问ESP32系统获取接收和发送的数据量,如图12所示:

其中spi:384000Bytes表示ESP32系统从STM32F407系统接收到的SPI数据为384000Bytes。tcp:384000Bytes表示ESP32系统从TCP连接发送给上位机的数据,ESP32系统接收的数据量和发送出去的数据量一致,并且通过图10上位机软件可以看到上位机接收到的数据总量也为384000Bytes,ESP32系统通过Wifi发送给上位机的数据与上位机接收到的数据一致,说明数据在无线传输的过程中不存在丢包,丢包率为0%。另外从图中可以看到数据接收时间为6.01S,通过计算可知数据传输速率为62.5KB/s(500Kbps)。实验结果表明本设计的系统可行有效。

4 结 论

以STM32F407连接ESP32,ESP32通过WIFE连接上位机为例,针对ESP32无线网络配置、数据接收和发送程序进行了详细的设计与说明,同时对ESP32系统的WEB服务程序进行设计,以方便上位机进行信息查询。最后通过硬件平台测试来验证应用程序有效性和驱动性能。实验结果表明,基于ESP32的无线数据采集传输系统程序设计合理,易于使用,但性能有待进一步优化和改进。未来将进一步优化硬件及软件设计,提高抗干扰及数据传输效率,提高系统性能。

猜你喜欢
存储空间线程队列
基于多种群协同进化算法的数据并行聚类算法
基于C#线程实验探究
苹果订阅捆绑服务Apple One正式上线
队列里的小秘密
基于多队列切换的SDN拥塞控制*
基于国产化环境的线程池模型研究与实现
用好Windows 10保留的存储空间
在队列里
丰田加速驶入自动驾驶队列
浅谈linux多线程协作