基础知识补全
句柄通常是一个指针(Pointer)或者一个整数(Integer/ID)。在 FreeRTOS/ESP-IDF 中,绝大多数句柄都是结构体指针。
·
1、解释一下什么是句柄以及句柄的作用
句柄通常是一个指针(Pointer)或者一个整数(Integer/ID)。
- 在 FreeRTOS/ESP-IDF 中,绝大多数句柄都是结构体指针。
-
A. 任务句柄 (Task Handle)
当你创建一个任务时,系统会返回一个句柄,让你以后能控制这个任务(比如删除它、挂起它)。
TaskHandle_t xHandleTouch = NULL; // 定义一个触摸任务的句柄 // 创建任务,将句柄地址传入 xTaskCreatePinnedToCore( touch_task_function, // 任务函数 "TouchTask", // 任务名 4096, // 栈大小 NULL, // 参数 20, // 优先级 &xHandleTouch, // 【关键】这里填入句柄的地址,创建成功后它会指向任务控制块(TCB) 1 // 绑定核心 ); // 后续使用:想删除这个任务?不用知道它在哪,直接用句柄 vTaskDelete(xHandleTouch);B. 外设驱动句柄 (Driver Handle - ESP-IDF 特色)
ESP-IDF 的驱动程序(如 I2C, SPI, LCD)广泛使用句柄来管理设备实例。
#include "driver/i2c.h" #include "esp_lcd_panel_ops.h" // I2C 句柄 (其实是个配置结构体的标识) i2c_master_bus_handle_t i2c_bus; // LCD 面板句柄 (指向 AXS15231B 驱动实例的指针) esp_lcd_panel_handle_t panel_handle = NULL; // 初始化屏幕,系统分配资源并返回句柄,将地址传给panel_handle,此时他指向一个结构体 esp_lcd_new_panel_st7789(..., &panel_handle); // 后续操作:刷新屏幕、休眠、设置亮度,全部通过这个句柄 esp_lcd_panel_draw_bitmap(panel_handle, ...); esp_lcd_panel_disp_on_off(panel_handle, true);C. LVGL 对象句柄
LVGL 中几乎所有控件(按钮、标签、图表)都是对象,通过句柄(
lv_obj_t *)操作。
2、信号量
-
二值信号量 (Binary Semaphore)
- 取值:只有两个状态:0 或 1(也可理解为“空”或“满”,“锁定”或“解锁”)。
- 本质:类似于一个互斥锁(Mutex),但通常没有“所有权”概念(即哪个任务释放都可以,不一定非要是获取它的那个任务)。
- 初始状态:通常初始化为 1(表示资源可用/事件未发生)或 0(表示资源不可用/事件已发生,视具体逻辑而定)。
-
普通信号量 / 计数信号量 (Counting Semaphore)
- 取值:可以是 0 到任意正整数 N 之间的任何值。
- 本质:用于管理具有多个实例的资源池,或者统计事件发生的次数。
- 初始状态:初始化为资源的最大数量 N。
-
特性 二值信号量 计数信号量 (普通信号量) 主要用途 同步 (Synchronization) 或 互斥 (Mutual Exclusion)。 资源计数 (Resource Counting) 或 多事件同步。 典型场景 1. 任务间同步:任务A等待任务B完成某个动作(如中断服务程序通知任务数据已准备好)。
2. 互斥访问:保护临界区(虽然 Mutex 更常用,但二值信号量也可实现,只是缺乏优先级继承等高级特性)。1. 资源池管理:例如系统有 5 个缓冲区,信号量初值为 5。每取走一个减 1,归还一个加 1。
2. 生产者-消费者模型:统计缓冲区中有多少个数据项可供消费。事件累积 不累积(通常情况下)。如果事件发生多次而没人接收,信号量保持为 1,后续的事件可能会丢失(取决于具体实现,有些RTOS允许累积但无意义,因为上限是1)。 累积。如果事件发生了 5 次而没人接收,信号量值会变为 5。后续可以有 5 个任务依次获取成功。
示例代码二值信号量
#include "FreeRTOS.h"
#include "task.h"
#include "semphr.h"
#include "gpio.h" // 假设的GPIO驱动头文件
// 1. 定义信号量句柄
SemaphoreHandle_t xBtnSemaphore;
// 模拟硬件中断服务程序 (ISR)
// 在实际芯片中,这是绑定到 GPIO 中断的函数
void BUTTON_ISR_Handler(void)
{
BaseType_t xHigherPriorityTaskWoken = pdFALSE;
// 清除硬件中断标志 (具体代码取决于芯片)
HAL_GPIO_ClearInterruptFlag();
// 【关键操作】在中断中“释放”(Give) 信号量
// 注意:中断中必须使用带 "FromISR" 后缀的函数
xSemaphoreGiveFromISR(xBtnSemaphore, &xHigherPriorityTaskWoken);
// 如果释放信号量唤醒了一个高优先级任务,请求进行上下文切换
portYIELD_FROM_ISR(xHigherPriorityTaskWoken);
}
// 2. 处理任务
void vButtonTask(void *pvParameters)
{
// 初始化:先获取一次信号量,将其置为 0 (空)
// 这样任务第一次执行 xSemaphoreTake 时就会立即阻塞,等待中断
xSemaphoreTake(xBtnSemaphore, portMAX_DELAY);
for(;;)
{
// 【关键操作】“获取”(Take) 信号量
// portMAX_DELAY 表示无限期等待,直到信号量有效
// 此时任务进入 Blocked 状态,CPU 调度给其他任务
if(xSemaphoreTake(xBtnSemaphore, portMAX_DELAY) == pdTRUE)
{
// 只有当中断调用了 Give,这里才会返回 pdTRUE
// --- 临界区/处理逻辑开始 ---
// 1. 读取按键状态 (消抖等)
// 2. 执行具体的业务逻辑
printf("Button Pressed! Handling event...\n");
// 模拟处理耗时
vTaskDelay(pdMS_TO_TICKS(50));
// --- 临界区/处理逻辑结束 ---
// 注意:这里不需要 Release 信号量,因为这是“事件通知”,
// 下一次等待需要由新的中断来触发。
}
}
}
// 3. 初始化函数
void vStartSystem(void)
{
// 创建二值信号量
// 宏定义通常实现为创建一个计数最大值为1的信号量
xBtnSemaphore = xSemaphoreCreateBinary();
if(xBtnSemaphore != NULL)
{
// 创建任务
xTaskCreate(vButtonTask, "BtnTask", 256, NULL, 2, NULL);
// 配置并启用 GPIO 中断...
HAL_GPIO_InitInterrupt(BUTTON_PIN, BUTTON_ISR_Handler);
}
// 启动调度器
vTaskStartScheduler();
}
示例代码计数信号量
#include "FreeRTOS.h"
#include "task.h"
#include "semphr.h"
// 1. 定义信号量句柄
SemaphoreHandle_t xDmaSemaphore;
#define TOTAL_DMA_CHANNELS 3
// 2. 通用工作任务 (模拟多个任务竞争资源)
void vWorkerTask(void *pvParameters)
{
const char *pcTaskName = (const char *)pvParameters;
for(;;)
{
// 模拟做其他无关工作
vTaskDelay(pdMS_TO_TICKS(100 + rand() % 100));
// 【关键操作】请求资源
// 尝试获取信号量,超时时间为 1 秒
// 如果当前信号量计数 > 0,计数减 1,任务继续
// 如果当前信号量计数 == 0,任务阻塞,直到有资源释放或超时
if(xSemaphoreTake(xDmaSemaphore, pdMS_TO_TICKS(1000)) == pdTRUE)
{
// --- 成功获取资源 ---
printf("[%s] Got a DMA channel. Remaining: %d\n",
pcTaskName, uxSemaphoreGetCount(xDmaSemaphore));
// 使用资源 (例如配置 DMA 并传输数据)
// 假设耗时 200ms
vTaskDelay(pdMS_TO_TICKS(200));
// 使用完毕,归还资源
// 【关键操作】释放信号量,计数加 1
// 如果有其他任务在等待,最高优先级的等待任务会被唤醒
xSemaphoreGive(xDmaSemaphore);
printf("[%s] Released DMA channel.\n", pcTaskName);
}
else
{
// --- 获取失败 (超时) ---
printf("[%s] Timeout! No DMA channel available.\n", pcTaskName);
}
}
}
// 3. 初始化函数
void vStartResourceSystem(void)
{
// 创建计数信号量
// 参数1: 初始计数 (Initial Count) = 3 (一开始有3个资源可用)
// 参数2: 最大计数 (Maximum Count) = 3 (资源总数上限)
xDmaSemaphore = xSemaphoreCreateCounting(TOTAL_DMA_CHANNELS, TOTAL_DMA_CHANNELS);
if(xDmaSemaphore != NULL)
{
// 创建 5 个任务来竞争这 3 个资源
xTaskCreate(vWorkerTask, "Worker1", 256, (void*)"T1", 2, NULL);
xTaskCreate(vWorkerTask, "Worker2", 256, (void*)"T2", 2, NULL);
xTaskCreate(vWorkerTask, "Worker3", 256, (void*)"T3", 2, NULL);
xTaskCreate(vWorkerTask, "Worker4", 256, (void*)"T4", 2, NULL);
xTaskCreate(vWorkerTask, "Worker5", 256, (void*)"T5", 2, NULL);
}
vTaskStartScheduler();
}
3、互斥锁
在普通的互斥锁中,如果一个任务已经持有了锁,再次尝试获取同一把锁时,通常会因为“锁已被占用”而进入阻塞状态,从而导致该任务永远等待自己释放锁,形成死锁。递归互斥锁通过引入持有计数(Hold Count)和所有者记录(Owner ID)机制解决了这个问题。作用:允许函数多层嵌套调用
openvela 操作系统专为 AIoT 领域量身定制,以轻量化、标准兼容、安全性和高度可扩展性为核心特点。openvela 以其卓越的技术优势,已成为众多物联网设备和 AI 硬件的技术首选,涵盖了智能手表、运动手环、智能音箱、耳机、智能家居设备以及机器人等多个领域。
更多推荐


所有评论(0)