1. ESP32为什么需要多任务
ESP32需要多任务处理主要是因为以下几个原因:
edbb4a78-91a1-4704-b54f-4965d500f317
例如添加如下代码块:
var code = “edbb4a78-91a1-4704-b54f-4965d500f317”
- 并行处理能力:ESP32是一款双核微控制器,拥有两个可以独立运行的处理核心。通过多任务处理,这两个核心可以同时执行不同的任务,提高系统的整体性能和效率。
- 实时响应:在许多嵌入式应用中,系统可能需要对多个输入或事件进行实时响应。例如,一个设备可能需要同时处理用户界面的更新、传感器数据采集、网络通信以及数据处理等任务。通过多任务处理,每个任务可以在其各自的上下文中独立运行,确保各个功能模块能够及时响应。
- 资源利用率:当某些任务在等待外部事件(如I/O操作或网络数据包接收)时,如果只有一个任务在运行,那么处理器的其他部分可能会处于空闲状态。通过多任务处理,其他任务可以在这些等待期间继续执行,提高了处理器资源的利用率。
- 优先级管理:多任务环境允许对任务进行优先级管理。这样,重要的或时间敏感的任务可以得到更高的优先级,确保它们能够在必要时获得处理器资源,而不受低优先级任务的影响。
- 模块化和可扩展性:多任务编程有助于将复杂的系统划分为多个独立的、易于管理的模块。每个模块可以作为一个单独的任务运行,使得代码更易于理解和维护,同时也为系统的扩展和升级提供了便利。
- 事件驱动编程:在事件驱动的系统中,任务通常会在特定事件发生时被触发。通过多任务处理,可以轻松地创建和管理这些事件相关的任务,使得系统能够灵活地应对各种情况。
- 操作系统支持:ESP32支持FreeRTOS等实时操作系统(RTOS),这些系统本身就设计用于多任务环境,并提供了一系列工具和机制来简化多任务编程,如任务调度、同步原语(如互斥量、信号量和事件组)等。
综上所述,ESP32采用多任务处理能够充分利用其双核架构,提高系统性能、响应速度和资源利用率,同时也有利于软件设计的模块化和可扩展性。这对于开发复杂的嵌入式应用,特别是那些涉及多种并发操作和实时响应需求的场景来说至关重要。
在嵌入式系统开发中,多核处理器已成为常态。由于这些处理器具有多个核心,它们能够并行处理多个任务,从而提高系统的整体性能。然而,这也带来了一个挑战:如何确保资源在并行处理过程中的互斥访问,以避免数据冲突和不一致。互斥量(Mutex)是一种常用的同步机制,用于解决这个问题。在这篇文章中,我们将深入探讨ESP32双核处理器的互斥量机制,并通过一个简单的示例来展示如何使用它。我们将使用FreeRTOS库来实现这个示例,该库提供了对互斥量的强大支持。
2. 什么是互斥量?
互斥量是一种同步工具,用于防止多个任务同时访问共享资源。当一个任务持有互斥量时,其他任务必须等待直到该任务释放互斥量。这样可以确保资源的顺序访问,避免数据冲突和不一致。
3. ESP32双核互斥量示例
我们将通过一个简单的示例来展示ESP32双核处理器上互斥量的使用。这个示例包括两个任务:task1和task2。这两个任务将并行运行,并尝试访问一个共享资源(变量number)。我们将使用互斥量来确保这两个任务不会同时访问这个资源。
#include <FreeRTOSConfig.h> xSemaphoreHandle xMutex; //互斥量 int number = 0; //互斥资源 void task1(void* param) { static int count = 0; int p = *((int*)param); while(count++ < 200) { int core = xPortGetCoreID(); //获取当前核 Serial.printf("Core %d -> ", core); Serial.print("I am task1, Param: "); Serial.print(p); if(xSemaphoreTake(xMutex, portMAX_DELAY)) { Serial.printf(" number: %d", number); xSemaphoreGive(xMutex); } Serial.println(); delay(2000); } vTaskDelete(NULL); //结束任务 } void task2(void* param) { static int count = 0; while(count++ < 200) { int core = xPortGetCoreID(); //获取当前核 Serial.printf("Core %d -> ", core); Serial.println("I am task2"); if(xSemaphoreTake(xMutex, portMAX_DELAY)) { number++; xSemaphoreGive(xMutex); } delay(2000); } vTaskDelete(NULL); //结束任务 } void setup() { Serial.begin(115200); TaskHandle_t handle1; int param = 30; xMutex = xSemaphoreCreateMutex(); xTaskCreatePinnedToCore(task1, "task1", 2048, (void*)¶m, 15, &handle1, 0); xTaskCreatePinnedToCore(task2, "task2", 2048, NULL, 15, NULL, 1); } void loop() { int core = xPortGetCoreID(); //获取当前核 Serial.printf("Core %d -> I am loop ", core); auto pri = uxTaskPriorityGet(NULL); Serial.printf(" priority: %d", pri); Serial.println(); delay(2000); //一个任务的delay不会影响到其它任务的运行 }
4. 代码分析
在代码中,我们首先定义了一个互斥量xMutex和一个共享资源number。然后,我们定义了两个任务函数task1和task2。
在task1中,我们使用xPortGetCoreID()函数获取当前运行的核的ID,并通过串口打印出来。然后,我们尝试获取互斥量。如果成功获取到互斥量(即没有其他任务持有它),我们就打印出一些信息,并在完成后释放互斥量。我们使用一个循环来模拟任务的持续运行,并在每次迭代之间等待2秒。
在task2中,我们同样获取当前运行的核的ID并打印出来。然后,我们尝试获取互斥量。如果成功获取到互斥量,我们就增加共享资源的值,并在完成后释放互斥量。同样地,我们使用一个循环来模拟任务的持续运行。
在setup()函数中,我们初始化串口通信、创建一个名为"task1"的任务和一个名为"task2"的任务。我们还创建一个名为"xMutex"的互斥量。最后,我们使用xTaskCreatePinnedToCore()函数将任务创建到特定的核心上。在这个例子中,我们将"task1"创建到核心0上,将"task2"创建到核心1上。
在loop()函数中,我们获取当前运行的核的ID并打印出来。然后,我们获取当前任务的优先级并打印出来。最后,我们等待2秒后再次执行这个循环。
5. 安装必备组件
开始安装 ArduinoOTA 库并为 ESP32 开发板设置 Arduino IDE(如果您尚未这样做)。
首先,我们来安装 ESP32 开发板包:
- 打开Arduino IDE。
- 导航到侧边栏中的 Board Manager。
- 搜索“ESP32”,选择乐鑫的 esp32。
- Arduino IDE:下载并安装 Arduino IDE;
- ESP32 开发板库:在 Arduino IDE 中添加 ESP32 支持;
参考博客:【esp32c3配置arduino IDE教程】
为安装过程留出一些时间,具体时间可能因您的互联网连接而异。
CP2102驱动端口配置,去官网下载:https://www.silabs.com/developers/usb-to-uart-bridge-vcp-drivers?tab=downloads
大家根据自己的系统类型选择安装,通过设备管理器查看匹配端口
6. 运行结果
选择端口和板卡上传成功如下
当我们运行这个程序时,我们将在串口输出中看到每个任务和核的ID以及任务的优先级。我们还将在每次迭代中看到一个或两个任务尝试访问共享资源的情况。由于使用了互斥量,这两个任务将交替地访问共享资源,从而避免了冲突和数据不一致的问题。
7. 总结
🥳🥳🥳现在,我们深入探讨ESP32双核处理器的互斥量机制,并通过一个简单的示例来展示如何使用它。🛹🛹🛹从而实现对外部世界进行感知,充分认识这个有机与无机的环境,后期会持续分享esp32跑freertos实用案列🥳🥳🥳科学地合理地进行创作和发挥效益,然后为人类社会发展贡献一点微薄之力。🤣🤣🤣
希望这篇文章对你有所帮助!如果你有任何问题或建议,欢迎在评论区留言讨论。
参考文献: