使用freeRTOS实现esp32与pc端WiFi通信(vscode)
总的来说这个东西做的不咋行,特别是platformIO上的代码,c代码真的没python好写:),并且运行速度很慢啊,pc端的页面点击次数多了会卡死(难不成pc端的页面也需要多线程吗,但是也没几个任务啊),但是能基本完成最初的设想,也通过这个过程学习了很多,代码能力和学习能力得到的进一步的锻炼。如果有时间的话,后续还会继续改进。
本篇文章的项目是课程作业,由于以前没接触过此类东西,故记录一下开发过程
一、准备
首先是freeRTOS,freeRTOS简单的来说,就是一个很小的可以实现多任务的操作系统,多任务的完成是靠中断实现的,所以也可以把freeRTOS当成一个能完成中断小系统(纯属个人理解,不喜勿喷)。然后是esp32,我所使用的是老师发的esp32,其中一个串口已经被改过了,如图

pc端我就使用的笔记本电脑了。
二、思路+做法
首先我要实现的是,在pc端通过一个页面去控制esp32上的小灯闪烁情况。
那么问题就是:1、页面如何写;2、怎么去控制;3、如何优化
对于问题1,我选择python,用pyqt简单写一个页面就行,毕竟主要问题就是如何控制和优化。
对于问题二,无线通讯主要是有蓝牙和WiFi,这里采用wifi通讯,WiFi通讯里分TCP\UDP,具体又分三种方式,分别是AP STA AP+STA。
TCP(Transmission Control Protocol,传输控制协议)这种是更安全的一种连接方式,就是传输端(服务端)和接收端(客户端)必须建立可靠的联系后才能收发信息。
UDP(User Data Protocol,用户数据报协议)相比较就没有那么安全,但是更方便,不需要建立稳固的链接,传输端把要传输的信息扔上去,那个接收端想要,自己接收就行。
AP模式是自身作为热点,“散发出”wifi热点;STA是连接外部的WiFi型号;AP+STA就很好理解了,两者混合。
我这儿就用的UDP+AP了。
对于问题三,如何优化其实就是为什么要用freeRTOS的原因了。因为在我的要求中,esp32灯的闪烁情况是会经常变的,且变化的时间、节点也是不固定的,且如何实现改变灯闪烁和接收pc端发出的信息并做出相应的变化,这都是需要使用到多任务的,因此选择采用freeRTOS.
三、具体实现
首先,是编一个小页面,要求就是通过点击不同的模式,使esp32变化不同的闪烁方式,然后能输出一些错误提示信息。方便起见就用pyqt简单的写了一个,这是页面和代码。

import sys
import socket
from PyQt5.QtWidgets import QApplication, QWidget, QPushButton, QVBoxLayout, QLabel
from esp32_udp import Ui_Form
class ESP32Controller(QWidget, Ui_Form):
def __init__(self, esp32_ip, esp32_port):
super().__init__()
self.esp32_ip = esp32_ip
self.esp32_port = esp32_port
self.setupUi(self)
self.modle1.clicked.connect(self.modle_1)
self.modle2.clicked.connect(self.modle_2)
self.modle3.clicked.connect(self.modle_3)
self.connect_off.clicked.connect(self.colse_connect)
client_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
def send_command(self, command):
self.client_socket.settimeout(10) # 设置超时时间10秒
try:
self.client_socket.sendto(command.encode('utf-8'), (self.esp32_ip, self.esp32_port))
response, _ = self.client_socket.recvfrom(1024)
response_text = response.decode('utf-8')
print("Received:", response_text)
self.textshow_lable.setText(f'Status: {response_text}')
except socket.timeout:
print("超时")
self.textshow_lable.setText('超时')
finally:
self.client_socket.close()
def modle_1(self):
self.send_command('modle1')
self.textshow_lable.setText("成功设置为模式1")
def modle_2(self):
self.send_command('modle2')
self.textshow_lable.setText("成功设置为模式2")
def modle_3(self):
self.send_command('modle3')
self.textshow_lable.setText("成功设置为模式3")
def colse_connect(self):
self.client_socket.close()
self.textshow_lable.setText("关闭连接")
if __name__ == '__main__':
esp32_ip = "192.168.9.1" # 替换为你的ESP32 IP
esp32_port = 1234 # 替换为你的ESP32 UDP端口
app = QApplication(sys.argv)
ex = ESP32Controller(esp32_ip, esp32_port)
ex.show()
sys.exit(app.exec_())
代码中需要注意的一个点就是
client_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM),这句代码的意思是创建一个套接字,用于收发信息。
套接字简单来说就是一个节点,网络世界里,每个计算机或者用户都有大量的信息需要收发,那如何在网络世界做到你发我收呢,就是通过套接字,就类似于你家窗台,有快递员来了,你只会让他把快递放在窗台,而不是直接进家里。而套接字就是你的操作系统提供的一个“窗台”,其他用户或者计算机内的其他程序想交流数据,都可以通过这个“窗台”。其中的参数就不过多阐述了,可以自行查阅。
其中的流程大概分为以下几步:
1、客户端创建套接字
client_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
2、设定指定的ip和端口,将信息发送到该ip和端口
server_address = ('localhost', 12345)
message = b'Hello, UDP server!'
client_socket.sendto(message, server_address)
3、服务器创建相应的套接字
server_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
4、绑定到指定端口
server_socket.bind(('localhost', 12345))
5、接收信息
data, server = client_socket.recvfrom(4096) #4096是创建一个该大小的空间用于储存接收的信息
以上代码都是基于python语言,c也可以,只不过库不太一样。
以上是pc端的,接下来就是esp32 ,开发esp32我选择的vscode上的platfromIO,当然也可以用Arduino,但是Arduino在代码可读性上真的难受,所以还是建议用platformIO。
freeRTOS的移植也很简单,在项目根目录下新建一个freertos文件夹,将source文件夹下的源码复制过来,如果内存紧张的话,可以在portable文件下只留以下三个文件夹,如下图。

但是我亲身实验直接在代码里引用TaskHandle_t、vTaskResume等函数的也是可以的,不用专门去移植freeRTOS,就很奇怪,难道是platformIO里有内置?anyway,接下来就进入代码。以下是整个的完整代码。
#include <WiFi.h>
#include <WiFiUdp.h>
#include <Arduino.h>
#include <AsyncUDP.h>
const char* ssid = "esp32_udp";
const char* password = "123456789";
IPAddress IP_local(192, 168, 9, 1); // 本机IP
IPAddress IP_gateway(192, 168, 9, 1); // 网关
IPAddress IP_mask(255, 255, 255, 0); // 掩码
IPAddress IP_start(192,168,9,10);
const unsigned int localUdpPort = 1234; // 本地UDP端口
WiFiUDP udp;
TaskHandle_t receive_task;
TaskHandle_t Task1;
TaskHandle_t Task2;
TaskHandle_t Task3;
char text[255]; // 缓存接收的数据
void receive(void* pvpoint){
udp.begin(localUdpPort);
Serial.printf("UDP server started at port %d\n", localUdpPort);
while (!WiFi.isConnected()) {
int packetSize = udp.parsePacket();
if (packetSize) {
int len = udp.read(text, 255);
if (len > 0) {
text[len] = 0;
}
Serial.printf("Received packet of size %d from %s:%d\n", packetSize,
udp.remoteIP().toString().c_str(), udp.remotePort());
Serial.printf("Packet content: %s\n", text);
}
}
}
void modle1(void* pvpoint){
if (strcmp(text, "模式1") == 0) {
vTaskResume(Task1);
vTaskSuspend(Task2);
vTaskSuspend(Task3);
digitalWrite(22, HIGH); // turn the LED on (HIGH is the voltage level)
delay(1000); // wait for a second
digitalWrite(22, LOW); // turn the LED off by making the voltage LOW
delay(1000);
udp.beginPacket(udp.remoteIP(), udp.remotePort());
udp.print("模式1");
udp.endPacket();
}
}
void modle2(void* pvpoint){
if (strcmp(text, "模式2") == 0) {
vTaskResume(Task2);
vTaskSuspend(Task1);
vTaskSuspend(Task3);
digitalWrite(22, HIGH); // turn the LED on (HIGH is the voltage level)
delay(100); // wait for a second
digitalWrite(22, LOW); // turn the LED off by making the voltage LOW
delay(100); // 关闭LED
udp.beginPacket(udp.remoteIP(), udp.remotePort());
udp.print("模式2");
udp.endPacket();
}
}
void modle3(void* pvpoint){
if (strcmp(text, "模式3") == 0) {
vTaskResume(Task3);
vTaskSuspend(Task1);
vTaskSuspend(Task2);
digitalWrite(22, LOW); // 关闭LED
udp.beginPacket(udp.remoteIP(), udp.remotePort());
udp.print("模式3");
udp.endPacket();
}
}
void setup() {
Serial.begin(115200);
WiFi.mode(WIFI_AP);
WiFi.softAPConfig(IP_local, IP_gateway, IP_mask);
WiFi.softAP(ssid, password);
pinMode(22, OUTPUT);
digitalWrite(22, HIGH); // 初始化LED状态为开启
Serial.println("WiFi AP Started");
Serial.print("IP Address: ");
Serial.println(WiFi.softAPIP());
// 创建任务
xTaskCreate(receive,"receive",4096,NULL,1,&receive_task);
xTaskCreate(modle1,"modle1",4096,NULL,2,&Task1);
xTaskCreate(modle2,"modle2",4096,NULL,2,&Task2);
xTaskCreate(modle3,"modle3",4096,NULL,2,&Task3);
}
void loop() {
// 主循环不需要做任何事情,任务在FreeRTOS中处理
}
其中我是用了消息队列,流程大概就是
1、创建一个句柄:TaskHandle_t Task1;//Task1是任务一对应的句柄
2、写任务一函数;
3、创建任务一:xTaskCreate(modle1,"modle1",4096,NULL,2,&Task1); //这儿的创建任务其实是在内存中创建一个空白窗口,其中modle1是具体的函数(任务一)的函数名,“modle”是想指定的名字(一般都是和函数名一样便于检查),4096是分配多少内存大小给这个任务,2代表任务优先级,&Task1是将这个空白窗口和你的句柄进行连接。
注:其实我在最开始很不理解为什么要用句柄,直接用指针不好吗。后面了解到,句柄是内存中的一部分内容的唯一指定,更像是一种“把柄”,只要抓住了这部分内容的把柄,不关内容怎么变,你都能找到它,并且不像指针指代内容那么“露骨”,更间接。所以在操作系统层面,句柄的使用能更安全,毕竟用指针的话,使用不当很容易造成系统崩溃。
这就是大概的流程,代码中还使用了vTaskResume、vTaskSuspend,这两个函数很简单,就是挂起和恢复某个任务的工作。
vTaskResume(Task3);
vTaskSuspend(Task1);
vTaskSuspend(Task2);
就代表着task3工作,2、3任务挂起。
在整段代码中,我将接收客户端发出的信息单独作为了一个任务,并将任务优先级设为最优先,然后将接收的信息与modle1、modle2、modle3分别进行了比较,然后决定进行那个任务。这样很麻烦,按道理来说写一个比较循环函数就行,但是博主没成功。。。。。。
四、总结
总的来说这个东西做的不咋行,特别是platformIO上的代码,c代码真的没python好写:),并且运行速度很慢啊,pc端的页面点击次数多了会卡死(难不成pc端的页面也需要多线程吗,但是也没几个任务啊),但是能基本完成最初的设想,也通过这个过程学习了很多,代码能力和学习能力得到的进一步的锻炼。如果有时间的话,后续还会继续改进。
更多推荐
所有评论(0)