ESP-IDF 框架是根据FreeRTOS改过来的。
为什么和原生的 FreeRTOS 有区别呢?
因为 ESP32 单片机为双核,一般的单片机为单核,。
ESP-IDF中的FreeRTOS与原生FreeRTOS的区别分析_esp-idf支持freertos吗-CSDN博客
FreeRTOS (IDF) - ESP32 - — ESP-IDF 编程指南 v5.5 文档
SMP 多核系统
特点:
多个核独立运行。每个核都有自己的寄存器文件、中断和中断处理。
每个核看到的内存是一样的,无论是哪个核,相同地址指向的是同一块内存。
任务调度机制
单核中仅看任务优先级,优先级高的任务先执行。
多核中不仅看任务优先级,还看任务对核心的亲和度,若任务 A 指定了核心 0,那它只能在核心 0 上运行,不能在核心 1 上运行。
(一)抢占
(1)指定核心
每个核心有单独的调度器,优先级高的任务抢占。
核 0 会调度在创建时核心指定为 0 的任务,核 1 会调度在创建时核心指定为 1 的任务。
(2)不指定核心
两个核心有自己独立的时钟,调度器不是同时调度的,当一个核心的调度器调度时,如果当前核心上有未分配核心的任务就绪了,且优先级比当前任务高,那就在当前核心上运行,因此运行在哪个核心上是不固定的。
比如:
优先级为 8 的任务 A 当前在核 0 上运行
优先级为 9 的任务 B 当前在核 1 上运行
优先级为 10 的任务 C 未分配,并由任务 B 解除了阻塞
任务 C 就绪且优先级比任务 B 高,那就在当前的核 1 上运行。
(二)时间片
在原生 FreeRTOS 的时间片调度中,多个最高优先级的就绪任务,会按时间片轮流执行。
ESP32是双核,每个核心都有自己的时间片调度。区别在于,原生FreeRTOS是按照最高的、相同的优先级,来让任务时间片执行。
ESP-IDF的,时间片可能发生在不同优先级之间,因为相同优先级的任务可以指定在两个不同的核,或者不指定核心,那既可能在同一核心上,又可能不在同一核心上。
为了实现理想的时间片调度,应保证特定的相同优先级的任务都分配在同一个核心上。
举例:
X:未指定;0:核0;1:核1
就绪队列头部 [ AX , B0 , C1 , D0 ] 就绪队列尾部
// 核0调度,运行AX,然后将其放在尾部
就绪队列头部 [ B0 , C1 , D0 , AX ] 就绪队列尾部
// 核1调度,不亲和任务B0,跳过B0去执行C1,并将其放在尾部
就绪队列头部 [ B0 , D0 , AX , C1 ] 就绪队列尾部
// 核0调度,运行B0,然后将其放在尾部
就绪队列头部 [ D0 , AX , C1 , B0 ] 就绪队列尾部
// 核1调度,不亲和任务D0,跳过D0去执行AX,并将其放在尾部
就绪队列头部 [ D0 , C1 , B0 , AX ] 就绪队列尾部
// 核0调度,运行D0,然后将其放在尾部
就绪队列头部 [ C1 , B0 , AX , D0 ] 就绪队列尾部
......与原生 FreeRTOS 的区别
系统时钟中断
每个核上都有滴答定时器,每个核上的滴答中断,周期是相同的,但可能不是同步的。
ESP-IDF 的 FreeRTOS 使用核 0 来负责时间计数。
暂停核 0 上的调度器,会导致整个系统暂停,也就是核 0 暂停了,核 1 也会暂停。
创建任务函数不同
原生动态创建:
变为xTaskCreatePinnedToCore()
BaseType_t xTaskCreatePinnedToCore( TaskFunction_t pxTaskCode, // 任务执行函数
const char *const pcName, // 任务名
const uint32_t ulStackDepth, // 栈大小
void *const pvParameters, // 传给执行函数的参数
UBaseType_t uxPriority, // 优先级
TaskHandle_t *const pxCreatedTask, // 任务句柄
const BaseType_t xCoreID ) // 指定核心
// 显式绑定到Core 0
xTaskCreatePinnedToCore(task_function, "Task", 4096, NULL, 2, NULL, 0);
// 不绑定核心(由调度器自动分配)
xTaskCreatePinnedToCore(task_function, "Task", 4096, NULL, 2, NULL, tskNO_AFFINITY);0:核心0
1:核心1
tskNO_AFFINITY:执行时会根据调度切换核心
也可以以静态方式创建。
任务堆栈单位不同
FreeRTOS:字(4字节)
ESP-IDF:字节
浮点运算需指定核心
如果任务中用到浮点运算,则创建任务的时候必须指定具体运行在哪个核上,不能由系统自动安排。
ESP32只有一个浮点运算单元 FPU,两个核共享。
FreeRTOS中,当任务上下文切换时:
切出:核寄存器的当前状态保存到要切出的任务栈中
切入:核寄存器的先前保存状态从要切入的任务栈中加载
FPU 实现了延迟上下文切换,同一个核中任务切换:
切出的任务A使用了FPU,切入的任务B也使用了FPU:将触发异常,FPU 寄存器将保存到任务 A 的堆栈中。
切出的任务A使用了FPU,切入的任务B不使用FPU:没有任何动作,数据仍保存在 FPU 寄存器中。
如果任务A未指定核心,在核0上使用了FPU,将它从核 0 调度到核 1 时,如果核0新切入的任务B也用到了FPU,那FPU的数据不能跨核保存到任务A的堆栈中。那就造成数据丢失,系统错乱。因此必须指定核心。
你代码中用到了float的运算,那编译的时候,编译器会自动生成对应的FPU指令。
FPU不能在中断上下文中使用。
还有其他的:。。。

禁止删除另一个核心上的任务
在删除时,与原生 FreeRTOS 存在差异:
若删除的任务没有在任一核心上运行,会立即释放其内存。
核 A 删除核 B 的任务时,会触发核 B 的任务调度器进行任务切换,目的是让目标删除任务退出,之后由核 B 的空闲任务释放内存。
注意,不要删除另一个核上正在运行的任务,否则可能会导致:
任务中申请的内存还没释放,造成内存泄漏。
任务持有互斥锁,造成需要该临界资源的任务被永久阻塞。
上面两个情况在单核中也有可能发生,任务 A 申请了内存还没释放时、获得了互斥锁还没释放时,被高优先级任务 B 抢占,B 把 A 删除了。
虽然有这种可能,但是一般的程序没有机会出现这种情况,毕竟删除任务的需求很小。
删除任务时,要明确被删除任务的状态。
被删除的任务要先释放自己申请的内存,然后最好使用 vTaskSuspend() 将自己挂起。
最安全的办法还是,任务自己删除自己。
中断和临界区保护
在原生 FreeRTOS 单核处理器中,使用taskDISABLE_INTERRUPTS 和 taskENABLE_INTERRUPTS 禁用和开启中断,IDF FreeRTOS也支持。
由于 ESP32 的每个核都有单独的中断,调用上面的函数,关闭或开启的是当前核上的中断。
原生:
taskENTER_CRITICAL() 通过禁用中断进入临界区
taskEXIT_CRITICAL() 通过重新启用中断退出临界区
taskENTER_CRITICAL_FROM_ISR() 通过禁用中断嵌套从 ISR 进入临界区
taskEXIT_CRITICAL_FROM_ISR() 通过重新启用中断嵌套从 ISR 退出临界区
ESP32:
taskENTER_CRITICAL(&spinlock) 从任务上下文进入临界区
taskEXIT_CRITICAL(&spinlock) 从任务上下文退出临界区
taskENTER_CRITICAL_ISR(&spinlock) 从中断上下文进入临界区
taskEXIT_CRITICAL_ISR(&spinlock) 从中断上下文退出临界区
仅关闭中断,不能实现多核任务对共享资源的互斥,例如核0的任务A进入临界区,核1的任务B也可以进入临界区,造成了冲突。
IDF-FreeRTOS的临界区,是使用自旋锁的临界区。
自旋锁:任务获取不到临界资源,就会一直检查锁的状态,直到成功获取。
自旋锁的判断,是在关闭中断之后的,因此,如果自旋锁要等待,是在关中断的情况下等待的。
写代码和之前一样写就行了,无非多传入了一个变量。
静态分配自旋锁:
// 静态分配并初始化自旋锁
static portMUX_TYPE my_spinlock = portMUX_INITIALIZER_UNLOCKED;
void some_function(void)
{
taskENTER_CRITICAL(&my_spinlock);
// 此时已处于临界区
taskEXIT_CRITICAL(&my_spinlock);
}动态分配自旋锁:
// 动态分配自旋锁
portMUX_TYPE *my_spinlock = malloc(sizeof(portMUX_TYPE));
// 动态初始化自旋锁
portMUX_INITIALIZE(my_spinlock);
...
taskENTER_CRITICAL(my_spinlock);
// 访问资源
taskEXIT_CRITICAL(my_spinlock);临界区API递归调用?
自动创建的后台任务
空闲任务
和 FreeRTOS 的空闲任务作用一样(包括低功耗处理吗?),每个核都有各自的空闲任务。
FreeRTOS 定时器任务
主任务
IPC 任务
ESP 定时器任务
FreeRTOS 单核模式
配置 IDF-FreeRTOS
在 ESP-IDF 的终端中,输入:
idf.py menuconfig运行失败的话,清理构建,重新执行,加载挺慢的。
要把终端拉大一点才好操作:
