🎯 FreeRTOS API 实战应用总结手册

🎯 FreeRTOS API 实战应用总结手册

created by lwPigKing

最好先学习一下《操作系统》,这样理解起来会更简单;但是不学习《操作系统》也不影响对《FreeRTOS》的学习。

📚 一、任务管理 API

1.1 xTaskCreate() - 创建任务

🔍 现实比喻招聘新员工

  • 招聘需求:公司需要新的职能岗位
  • 面试过程xTaskCreate()函数调用
  • 员工信息:职责(函数)、任务名、工位大小(栈)、薪资等级(优先级)
  • 员工编号:任务句柄(TaskHandle_t)

🎯 什么时候用

  • 系统启动时 → 公司开业,一次性招聘所有基础岗位员工
  • 动态功能 → 临时项目需要,招聘项目组(如固件升级任务)
  • 模块化设计 → 不同部门独立运作,互不影响

💡 典型场景

/** System_Monitor_Task的任务配置 **/
#define System_Monitor_Task_STACK 128
#define System_Monitor_Task_PRIORITY 3
TaskHandle_t System_Monitor_Task_handle;
void System_Monitor_Task(void * pvParameters);

// 智能家居公司:招聘各部门员工
void Hire_Company_Staff(void) {
    // 招聘CEO:系统监控任务(最高优先级)
    xTaskCreate(
        (TaskFunction_t)System_Monitor_Task, // 任务函数地址
        (char *)"CEO", // 任务名称
        (configSTACK_DEPTH_TYPE)System_Monitor_Task_STACK,// 任务栈大小,默认是128(最小4B)
        (void *)NULL, // 传递给任务的参数
        (UBaseType_t)System_Monitor_Task_PRIORITY,// 任务优先级
        (TaskHandle_t *)&ceo_handle // 任务句柄地址
    );
    
    // 同上,简写,但是要做配置
    // 招聘保安:传感器采集任务(中等优先级)
    xTaskCreate(Sensor_Collect_Task, "保安", 256, NULL, 3, &guard_handle);
    
    // 招聘保洁:数据清理任务(低优先级)
    xTaskCreate(Data_Clean_Task, "保洁", 128, NULL, 1, &cleaner_handle);
    
    // 招聘临时工:固件升级任务(完成后解雇)
    xTaskCreate(Firmware_Update_Task, "临时工", 256, NULL, 2, &temp_worker);
}

void System_Monitor_Task(void * pvParameters) {
    printf("System_Monitor_Task调度任务....");
}

💡 最常用的场景

/**
freertos_start创建start_task,然后将以上的任务创建全部写在start_task
**/
#define START_TASK_STACK 128
#define START_TASK_PRIORITY 1
TaskHandle_t start_task_handle;
void start_task(void * pvParameters);

/**
 * @brief 启动FreeRTOS
 * 
 */
void freertos_start(void) {
    // 1.创建一个启动任务
    xTaskCreate(
        (TaskFunction_t) start_task, // 任务函数地址
        (char *) "start_task", // 任务名称
        (configSTACK_DEPTH_TYPE) START_TASK_STACK, // 任务栈大小,默认是128(最小4B)
        (void *) NULL, // 传递给任务的参数
        (UBaseType_t) START_TASK_PRIORITY, // 任务优先级
        (TaskHandle_t *) &start_task_handle // 任务句柄地址
    );

    // 2.启动调度器
    vTaskStartScheduler();
}

void start_task(void * pvParameters) {
    taskENTER_CRITICAL();
    // 创建任务
    xTaskCreate(
        (TaskFunction_t) task1, 
        (char *) "task1", 
        (configSTACK_DEPTH_TYPE) TASK1_STACK, 
        (void *) NULL,
        (UBaseType_t) TASK1_PRIORITY,
        (TaskHandle_t *) &task1_handle 
    );

    vTaskDelete(NULL); // 删除启动任务自己
    taskEXIT_CRITICAL();
}

⚠️ 注意事项

  • 不要过度招聘:任务太多会导致调度开销增大
  • 合理分配工位:栈空间不足会导致栈溢出(员工没地方放东西)
  • 明确薪资等级:优先级不合理会导致某些任务"饿死"

1.2 vTaskDelete() - 删除任务

🔍 现实比喻解雇员工

  • 合同到期:临时任务完成使命
  • 工作失职:任务出错需要重置
  • 公司裁员:系统资源不足

🎯 什么时候用

  • 临时任务完成使命后(如固件升级任务)
  • 错误恢复时删除故障任务
  • 动态任务管理(如连接断开时删除对应处理任务)
  • ❌ 不要删除系统关键任务
// 场景:完成固件升级后解雇临时工
void Firmware_Update_Task(void *pvParameters) {
    // 下载固件(1天工作量)
    download_firmware();
    
    // 升级固件(2天工作量)
    update_firmware();
    
    // 验证固件(1天工作量)
    verify_firmware();
    
    // 项目完成,解雇自己
    printf("固件升级完成,临时工被解雇!\n");
    vTaskDelete(NULL);  // NULL表示解雇自己
}

⚠️ 注意事项

  • 做好工作交接:删除前释放分配的资源
  • 避免突然解雇:关键任务不能随意删除
  • 注意赔偿金:动态分配的内存需要由空闲任务释放

1.3 vTaskSuspend() / vTaskResume() - 挂起/恢复任务

🔍 现实比喻员工带薪休假

  • 挂起任务:员工休年假,保留职位但不工作
  • 恢复任务:假期结束,回来继续工作

🎯 什么时候用

  • 临时暂停某个功能(如暂停数据采集)
  • 低功耗模式下挂起非必要任务
  • 调试时挂起任务观察系统行为
// 场景:系统进入低功耗模式,部分员工放假
void Enter_Low_Power_Mode(void) {
    printf("公司进入节能模式,非关键岗位放假...\n");
    
    // 数据展示员放假(非紧急)
    vTaskSuspend(display_task_handle);
    
    // 数据分析师放假(非紧急)
    vTaskSuspend(analysis_task_handle);
       
    // 退出节能模式,召回员工
    printf("退出节能模式,召回所有员工!\n");
    vTaskResume(display_task_handle);
    vTaskResume(analysis_task_handle);
}

⚠️ 注意事项

  • 假期带薪:挂起的任务仍然占用内存
  • 多次休假:多次挂起需要多次恢复才能继续工作
  • 关键岗位:保安、CEO不能放假,否则公司会出问题

1.4 FreeRTOS任务相关API函数

函数 描述
uxTaskPriorityGet() 获取任务优先级
vTaskPrioritySet() 设置任务优先级
uxTaskGetNumberOfTasks() 获取系统中任务的数量
uxTaskGetSystemState() 获取所有任务状态信息
vTaskGetInfo() 获取指定单个的任务信息
xTaskGetCurrentTaskHandle() 获取当前任务的任务句柄
xTaskGetHandle() 根据任务名获取该任务的任务句柄
uxTaskGetStackHighWaterMark() 获取任务的任务栈历史剩余最小值
eTaskGetState() 获取任务状态
vTaskList() 以“表格”形式获取所有任务的信息
vTaskGetRunTimeStats() 获取任务的运行时间

📦 二、队列 API

2.1 xQueueCreate() - 创建队列

🔍 现实比喻建立公司内部快递系统

  • 快递站点:队列对象
  • 货架大小:队列长度(能放多少快递)
  • 快递类型:元素大小(快递包裹尺寸)

🎯 什么时候用

  • 任务间传递数据时(传感器→处理→显示)
  • 中断向任务传递数据
  • 生产者-消费者模式
// 创建公司内部的快递系统
QueueHandle_t Create_Company_Express(void *pvParameters) {
    // 建立文件快递站(能存10个文件,每个文件大小是FileStruct_t)
    QueueHandle_t file_express = xQueueCreate(10, sizeof(FileStruct_t));
    
    // 建立消息快递站(能存20条消息,每条消息大小是Message_t)
    QueueHandle_t message_express = xQueueCreate(20, sizeof(Message_t));
    
    // 建立紧急命令快递站(只能存1个,最新的覆盖旧的)
    QueueHandle_t urgent_express = xQueueCreate(1, sizeof(UrgentCommand_t));
    
    return file_express;
}

2.2 xQueueSend() / xQueueReceive()

🔍 现实比喻快递员送货 vs 客户收件

  • xQueueSend():快递员把包裹放到快递站
  • xQueueReceive():客户从快递站取包裹
// 场景:传感器部门向数据处理部门传递数据
QueueHandle_t sensor_data_express;

// 传感器部门(生产者):采集并发送数据
void Sensor_Department(void *pvParameters) {
    SensorData_t package; // 数据包裹
    
    while(1) {
        // 采集温度数据
        package.temperature = read_temperature();
        package.humidity = read_humidity();
        package.timestamp = xTaskGetTickCount();
        
        // 打包发送给数据处理部门
        if(xQueueSend(sensor_data_express, &package, 0) == pdPASS) {
            printf("传感器部门:数据包裹已发出!\n");
        } else {
            printf("传感器部门:快递站满了,等会儿再发!\n");
        }
        
        vTaskDelay(pdMS_TO_TICKS(1000));  // 每秒发一次
    }
}

// 数据处理部门(消费者):接收并处理数据
void Data_Processing_Department(void *pv) {
    SensorData_t received_package;
    
    while(1) {
        // 等待数据包裹送达
        if(xQueueReceive(sensor_data_express, &received_package, portMAX_DELAY)) {
            printf("数据处理部门:收到包裹,温度=%.1f°C\n",received_package.temperature);
            
            // 处理数据
            process_sensor_data(&received_package);
        }
    }
}

📊 快递系统选择指南

需求 选择 现实比喻
普通数据传递 xQueueSend() 普通快递,按顺序排队
紧急命令 xQueueSendToFront() VIP快递,插队到最前面
只关心最新状态 xQueueOverwrite()(队列长度1) 快递站只放最新包裹,旧的扔掉
查看但不拿走 xQueuePeek() 只看快递单号,不取包裹

🚦 三、信号量 API

3.1 二值信号量 - 等待单一事件

🔍 现实比喻公司门口的访客登记牌

  • 初始状态:登记牌显示"无人来访"(信号量值=0)
  • 访客到来:前台把牌子翻到"有访客"(xSemaphoreGive()
  • 员工查看:看到"有访客"后去接待,并把牌子翻回"无人来访"(xSemaphoreTake()

🎯 什么时候用

  • 等待中断发生(按键、串口接收)
  • 任务启动同步(等所有初始化完成)
  • 资源空闲通知
// 场景:前台通知CEO有重要访客
SemaphoreHandle_t visitor_semaphore = xSemaphoreCreateBinary();

// 前台任务:访客到来时通知
void Reception_Task(void *pvParameters) {
    while(1) {
        // 等待访客按门铃(硬件中断)
        wait_for_doorbell();
        
        // 访客来了!翻登记牌
        printf("前台:CEO,有访客!\n");
        xSemaphoreGive(visitor_semaphore);  // 牌子翻到"有访客"
        
        vTaskDelay(pdMS_TO_TICKS(100));
    }
}

// CEO任务:等待访客通知
void CEO_Task(void *pvParameters) {
    while(1) {
        // 等待登记牌显示"有访客"
        if(xSemaphoreTake(visitor_semaphore, portMAX_DELAY) == pdTRUE) {
            printf("CEO:收到,马上接待!\n");
            meet_visitor();  // 接待访客
            
            // 接待完成后,牌子会自动翻回(Take后信号量变0)
        }
    }
}

3.2 计数信号量 - 管理有限资源

🔍 现实比喻公司停车场的剩余车位显示屏

  • 总车位:信号量最大计数值(如100个车位)
  • 当前空位:信号量当前值(如显示"剩余车位:15")
  • 车辆进入:车位-1(xSemaphoreTake()
  • 车辆离开:车位+1(xSemaphoreGive()

🎯 什么时候用

  • 连接池管理(最大同时连接数)
  • 缓冲区管理(可用缓冲区数量)
  • 事件计数(接收到的数据包数)
// 场景:公司停车场管理(最多停50辆车)
#define TOTAL_PARKING_SPACES 50
SemaphoreHandle_t parking_semaphore = xSemaphoreCreateCounting(
    TOTAL_PARKING_SPACES,  // 总车位
    TOTAL_PARKING_SPACES   // 初始空车位
);

// 车辆进入停车场
void Car_Enter_Parking(void) {
    // 尝试获取一个车位(等待最多1分钟)
    if(xSemaphoreTake(parking_semaphore, pdMS_TO_TICKS(60000)) == pdTRUE) {
        printf("车辆%d:成功进入停车场,剩余车位:%d\n", 
               car_id, uxSemaphoreGetCount(parking_semaphore));
        park_car();  // 停车
    } else {
        printf("车辆%d:等了1分钟还没车位,开走了!\n", car_id);
    }
}

// 车辆离开停车场
void Car_Leave_Parking(void) {
    // 释放一个车位
    xSemaphoreGive(parking_semaphore);
    printf("车辆%d:离开停车场,剩余车位:%d\n", 
           car_id, uxSemaphoreGetCount(parking_semaphore));
}

3.3 互斥信号量 - 保护共享资源

🔍 现实比喻公司唯一的一间董事长办公室

  • 特性:一次只能一人使用,且有优先继承机制
  • 员工使用:进去时锁门(xSemaphoreTake()),出来时开门(xSemaphoreGive()
  • 优先继承:如果普通员工在使用时董事长来了,普通员工临时提升为"董事长级别"优先完成

🎯 什么时候用

  • 保护共享外设(LCD、SPI Flash)
  • 保护全局变量(防止数据竞争)
  • 需要防止优先级翻转的场景
// 场景:多部门共享一台彩色打印机
SemaphoreHandle_t printer_mutex = xSemaphoreCreateMutex();

// 市场部打印彩色宣传册
void Marketing_Print(void) {
    // 尝试获取打印机使用权(等待最多5秒)
    if(xSemaphoreTake(printer_mutex, pdMS_TO_TICKS(5000)) == pdTRUE) {
        printf("市场部:开始打印宣传册(需要30秒)\n");
        
        // 打印过程中,如果财务部(高优先级)也需要打印
        // 市场部会临时提升优先级,尽快完成打印
        
        print_color_brochure();  // 打印30秒
        printf("市场部:打印完成,释放打印机\n");
        
        xSemaphoreGive(printer_mutex);  // 开门
    } else {
        printf("市场部:等了5秒打印机还被占用,放弃打印\n");
    }
}

// 财务部打印财务报表(高优先级)
void Finance_Print(void) {
    // 财务部优先级更高,但会等待市场部完成打印
    xSemaphoreTake(printer_mutex, portMAX_DELAY);
    printf("财务部:紧急打印财务报表!\n");
    print_financial_report();
    printf("财务部:打印完成\n");
    xSemaphoreGive(printer_mutex);
}

🚨 信号量选择决策树

需要管理资源吗?
├─ 是 → 资源是单个还是多个?
│   ├─ 单个 → 需要防止优先级翻转吗?
│   │   ├─ 是 → 互斥信号量(有优先继承)
│   │   └─ 否 → 二值信号量(简单事件)
│   └─ 多个 → 计数信号量(可计数)
└─ 否 → 等待事件发生?→ 二值信号量

🎪 四、事件标志组 API

xEventGroupWaitBits()

🔍 现实比喻航空公司登机前的检查点

  • 登机条件:机票√ + 身份证√ + 健康码√ + 核酸报告√
  • 每位旅客:对应一个事件位
  • 检查员:等待所有条件满足(AND操作),或等待任意一个条件满足(OR操作)

🎯 什么时候用

  • 等待多个条件同时满足(AND逻辑)
  • 等待任意条件满足(OR逻辑)
  • 复杂的任务同步
// 场景:智能家居系统启动前检查
#define BIT_WIFI_CONNECTED     (1 << 0)  // 位0:WiFi连接
#define BIT_CLOUD_CONNECTED    (1 << 1)  // 位1:云平台连接
#define BIT_SENSOR_READY       (1 << 2)  // 位2:传感器就绪
#define BIT_ACTUATOR_READY     (1 << 3)  // 位3:执行器就绪

EventGroupHandle_t smart_home_events = xEventGroupCreate();

// 网络部门任务:连接WiFi
void WiFi_Task(void *pvParameters) {
    connect_to_wifi();
    printf("WiFi部门:WiFi连接成功!\n");
    xEventGroupSetBits(smart_home_events, BIT_WIFI_CONNECTED);
}

// 云平台部门任务:连接云端
void Cloud_Task(void *pvParameters) {
    connect_to_cloud();
    printf("云平台部门:云端连接成功!\n");
    xEventGroupSetBits(smart_home_events, BIT_CLOUD_CONNECTED);
}

// 系统启动任务:等待所有条件满足
void System_Start_Task(void *pvParameters) {
    printf("系统:等待所有启动条件...\n");
    
    // 等待四个条件都满足(AND操作),就像登机前检查
    EventBits_t ready_flags = xEventGroupWaitBits(
        smart_home_events,                      // 事件组
        BIT_WIFI_CONNECTED | BIT_CLOUD_CONNECTED | 
        BIT_SENSOR_READY | BIT_ACTUATOR_READY,  // 等待的位
        pdTRUE,   // 成功后清除这些位(检查完就把登机牌打孔)
        pdTRUE,   // 等待所有位(必须全部满足)
        portMAX_DELAY  // 一直等
    );
    
    printf("系统:所有条件满足,智能家居启动!\n");
    start_smart_home_system();
}

⚡ 高级用法:等待任意报警

// 场景:家庭安全系统,任一传感器报警都要处理
#define BIT_DOOR_OPENED      (1 << 0)  // 门被打开
#define BIT_WINDOW_BROKEN    (1 << 1)  // 窗户破碎
#define BIT_MOTION_DETECTED  (1 << 2)  // 检测到移动
#define BIT_SMOKE_DETECTED   (1 << 3)  // 检测到烟雾

void Security_Monitor_Task(void *pvParameters) {
    // 等待任意一个报警事件(OR操作)
    EventBits_t alarm_flags = xEventGroupWaitBits(
        security_events,
        BIT_DOOR_OPENED | BIT_WINDOW_BROKEN | 
        BIT_MOTION_DETECTED | BIT_SMOKE_DETECTED,
        pdTRUE,    // 清除触发的位
        pdFALSE,   // 等待任意位(不是所有位)
        portMAX_DELAY
    );
    
    // 检查具体哪个报警
    if(alarm_flags & BIT_SMOKE_DETECTED) {
        printf("🚨 火警!启动灭火系统!\n");
        activate_fire_system();
    } else if(alarm_flags & BIT_DOOR_OPENED) {
        printf("🚨 门被非法打开!\n");
        trigger_door_alarm();
    }
    // ... 其他报警处理
}

📨 五、任务通知 API

xTaskNotify() / ulTaskNotifyTake()

🔍 现实比喻公司内部的对讲机系统

  • 传统方式:用广播系统(信号量/队列),所有人都能听到
  • 任务通知:用对讲机直接呼叫某个人,快速私密

🎯 什么时候用

  • 替代二值信号量(节省内存,提高速度)
  • 替代计数信号量(轻量级计数)
  • 替代事件标志组(单个任务的事件组)
// 场景:紧急情况下,CEO直接呼叫保安队长
TaskHandle_t security_captain_handle;

// 传统方式:用广播系统(信号量)
SemaphoreHandle_t broadcast_system = xSemaphoreCreateBinary();
// 缺点:所有人都会收到,不知道具体叫谁

// 现代方式:用对讲机(任务通知)
void CEO_Emergency_Call(void) {
    printf("CEO:紧急情况!保安队长请速到办公室!\n");
    
    // 方式1:快速呼叫(计数方式)
    xTaskNotifyGive(security_captain_handle);  // 对讲机喊一声
    
    // 方式2:带信息的呼叫
    uint32_t emergency_type = EMERGENCY_FIRE;
    xTaskNotify(security_captain_handle, emergency_type, eSetValueWithOverwrite);
}

// 保安队长:随时待命
void Security_Captain_Task(void *pvParameters) {
    while(1) {
        // 等待CEO呼叫
        uint32_t notification_value;
        
        // 方式1:简单等待呼叫
        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);
        printf("保安队长:收到!马上到!\n");
        handle_emergency();
        
        // 方式2:接收带信息的呼叫
        xTaskNotifyWait(0, 0xFFFFFFFF, &notification_value, portMAX_DELAY);
        if(notification_value == EMERGENCY_FIRE) {
            printf("保安队长:是火警!带灭火器!\n");
        }
    }
}

💡 任务通知的"呼叫方式"

呼叫方式 现实比喻 代码示例
eNoAction "听到请回复!"(只通知) xTaskNotify(handle, 0, eNoAction)
eSetBits "A区B区注意!"(设置标志位) `xTaskNotify(handle, BIT_A
eIncrement "这是第3次呼叫!"(计数) xTaskNotify(handle, 0, eIncrement)
eSetValueWithOverwrite "最新消息:有火情!"(覆盖) xTaskNotify(handle, FIRE, eSetValueWithOverwrite)
eSetValueWithoutOverwrite "如果没收到过消息,就记下这条"(不覆盖) xTaskNotify(handle, msg, eSetValueWithoutOverwrite)

六、软件定时器 API

xTimerCreate() / xTimerStart()

🔍 现实比喻公司的定时闹钟系统

  • 硬件闹钟:必须有人手动设置,精度高(硬件定时器)
  • 软件闹钟:手机APP设置,灵活但精度稍差(软件定时器)

🎯 什么时候用

  • 周期性任务(每秒采集数据)
  • 延时操作(5秒后关闭LED)
  • 超时检测(等待响应超时)
// 场景:公司日常定时事务
TimerHandle_t company_daily_timer;

void daily_routine_callback(TimerHandle_t timer) {
    printf("⏰ 定时提醒:\n");
    printf("  1. 早上9:00:晨会\n");
    printf("  2. 下午2:00:下午茶时间\n");
    printf("  3. 晚上6:00:下班打卡\n");
}

// 系统初始化时设置公司作息
void Setup_Company_Schedule(void) {
    // 创建每日定时器(24小时周期)
    company_daily_timer = xTimerCreate(
        "CompanySchedule",      // 闹钟名称
        pdMS_TO_TICKS(24 * 60 * 60 * 1000),  // 24小时
        pdTRUE,                 // 自动重载(每天重复)
        (void *)1,              // 闹钟ID
        daily_routine_callback  // 响铃时的处理函数
    );
    
    // 启动闹钟(明天9点开始)
    xTimerStart(company_daily_timer, pdMS_TO_TICKS(1000));
    
    // 创建单次闹钟:5分钟后提醒开会
    TimerHandle_t meeting_timer = xTimerCreate(
        "MeetingReminder",
        pdMS_TO_TICKS(5 * 60 * 1000),  // 5分钟后
        pdFALSE,               // 单次执行
        NULL,
        meeting_reminder_callback
    );
    xTimerStart(meeting_timer, 0);
}

⚠️ 软件定时器特点

  1. 在定时器服务任务中执行 → 闹钟响了,是助理去处理,不是CEO亲自处理
  2. 精度受系统影响 → 如果系统忙,闹钟可能晚几秒响
  3. 不能阻塞 → 助理处理闹钟时要快速,不能打2小时电话

🔌 七、中断管理 API

taskENTER_CRITICAL() / portDISABLE_INTERRUPTS()

🔍 现实比喻董事长的重要会议

  • 轻度保护taskENTER_CRITICAL() → 会议室挂"会议中"牌子,不接普通电话,但紧急电话(高优先级中断)还能接
  • 重度保护portDISABLE_INTERRUPTS() → 会议室锁门,拔掉电话线,任何人不得打扰

🎯 什么时候用

  • 保护关键代码段不被其他任务打断
  • 原子操作(如自增计数器)
// 场景1:财务部门做年度结算(不能被打断)
void Annual_Financial_Report(void) {
    // 挂上"财务结算中,请勿打扰"牌子
    taskENTER_CRITICAL();
    
    // 关键操作:计算年度总账
    calculate_total_revenue();
    calculate_total_expenses();
    generate_financial_report();
    
    // 取下牌子,恢复正常
    taskEXIT_CRITICAL();
    
    printf("财务部:年度报告完成!\n");
}

// 场景2:硬件工程师升级BIOS(任何中断都不能有)
void Update_System_BIOS(void) {
    printf("硬件部:开始升级BIOS,系统将暂时无响应...\n");
    
    // 锁门!拔电话线!任何事都不能打扰!
    portDISABLE_INTERRUPTS();
    
    // 敏感操作:写入BIOS芯片
    write_bios_chunk(0, bios_data, 256);
    delay_us(50);  // 必须精确延时
    write_bios_chunk(256, bios_data + 256, 256);
    
    // 恢复门禁和电话
    portENABLE_INTERRUPTS();
    
    printf("硬件部:BIOS升级完成!\n");
}

xxxFromISR() 系列 API

🔍 现实比喻急诊室的呼叫系统

  • 普通通信:走正常流程(邮件、电话)
  • 急诊通信:直接按紧急呼叫按钮(FromISR函数),立即处理

🎯 什么时候用中断服务函数中与任务通信

// 场景:病人按了急诊呼叫按钮
QueueHandle_t emergency_queue;

// 急诊呼叫按钮被按下(硬件中断)
void Emergency_Button_IRQHandler(void) {
    EmergencyInfo_t emergency;
    emergency.patient_id = read_patient_id();
    emergency.emergency_type = read_emergency_type();
    
    BaseType_t xHigherPriorityTaskWoken = pdFALSE;
    
    // 方式1:放入急诊队列(最常用)
    xQueueSendFromISR(emergency_queue, &emergency, &xHigherPriorityTaskWoken);
    
    // 方式2:直接呼叫值班医生(任务通知)
    vTaskNotifyGiveFromISR(doctor_task_handle, &xHigherPriorityTaskWoken);
    
    // 方式3:释放急诊信号量
    xSemaphoreGiveFromISR(emergency_sem, &xHigherPriorityTaskWoken);
    
    // 如果需要,立即切换任务让医生处理
    if(xHigherPriorityTaskWoken == pdTRUE) {
        portYIELD_FROM_ISR(xHigherPriorityTaskWoken);
    }
}

💾 八、内存管理 API

pvPortMalloc() / vPortFree()

🔍 现实比喻公司的物资仓库

  • 仓库管理员:内存管理算法(heap_1~heap_5)
  • 领用物资pvPortMalloc() → 填写领用单,领取物资
  • 归还物资vPortFree() → 填写归还单,放回仓库

🎯 什么时候用

  • 临时大缓冲区(图像处理、文件传输)
  • 动态数据结构(链表、树)
  • 运行时配置加载
// 场景:市场部需要临时借用展台物料
void Marketing_Exhibition(void) {
    printf("市场部:准备展会,需要临时借用展台物料\n");
    
    // 填写借物单:借100KB的展台装饰材料
    uint8_t *exhibition_materials = (uint8_t *)pvPortMalloc(100 * 1024);
    
    if(exhibition_materials != NULL) {
        printf("仓库:展台物料已出库,请妥善使用\n");
        
        // 使用物料布置展台
        setup_exhibition(exhibition_materials);
        hold_exhibition();
        
        // 展会结束,归还物料
        printf("市场部:展会结束,归还物料\n");
        vPortFree(exhibition_materials);
        printf("仓库:物料已归还,可再次借用\n");
    } else {
        printf("仓库:库存不足,无法出借!\n");
    }
}

⚠️ 嵌入式内存管理原则

  1. 优先静态分配
  2. 避免频繁申请释放
  3. 监控内存使用
  4. 选择合适的heap方案

📊 仓库管理方案选择

管理方案 现实比喻 特点 适用场景
heap_1 固定物资分配 只借不还,开业时一次性分配 启动时分配所有内存,永不释放
heap_2 按大小精准分配 按需分配,但不整理仓库 分配块大小固定的场景
heap_3 外包仓库管理 调用标准库的malloc/free 已有标准库的项目
heap_4 智能仓库管理 自动整理,合并碎片 最常用,推荐
heap_5 多仓库联合管理 管理多个不连续的仓库 内存分布复杂的系统

🏢 综合小项目:智能温室控制系统

项目背景

建立一个智能温室,自动调节温度、湿度、光照,确保植物最佳生长环境。

系统设计

🌡️ 硬件组成(数据用软件模拟,就练习一下FreeRTOS的API):
  - 温度传感器(DS18B20)
  - 湿度传感器(DHT11)
  - 光照传感器(BH1750)
  - 加热器(继电器控制)
  - 加湿器(继电器控制)
  - LED补光灯(PWM控制)
  - 液晶显示屏(OLED)
  - 按键(设置参数)

🔧 软件任务:
  1. 传感器采集任务(温度、湿度、光照)
  2. 环境控制任务(加热、加湿、补光)
  3. 显示任务(OLED显示当前环境)
  4. 用户交互任务(按键处理)
  5. 系统监控任务(健康检查)
  6. 数据记录任务(SD卡存储)

完整代码实现

/*******************************************************
 * 智能温室控制系统 - FreeRTOS综合应用
 * 功能:自动调节温湿度光照,OLED显示,按键设置
 *******************************************************/

#include "FreeRTOS.h"
#include "task.h"
#include "queue.h"
#include "semphr.h"
#include "event_groups.h"
#include "timers.h"
#include <stdio.h>

/* ========== 硬件引脚定义 ========== */
// 传感器引脚
#define TEMP_SENSOR_PIN    GPIO_PIN_0
#define HUMIDITY_SENSOR_PIN GPIO_PIN_1
#define LIGHT_SENSOR_PIN   GPIO_PIN_2

// 执行器引脚
#define HEATER_PIN         GPIO_PIN_3
#define HUMIDIFIER_PIN     GPIO_PIN_4
#define LED_LIGHT_PIN      GPIO_PIN_5

// 按键引脚
#define KEY_UP_PIN         GPIO_PIN_6
#define KEY_DOWN_PIN       GPIO_PIN_7
#define KEY_OK_PIN         GPIO_PIN_8

/* ========== 数据结构定义 ========== */
typedef struct {
    float temperature;      // 温度 (°C)
    float humidity;         // 湿度 (%)
    float light;            // 光照 (lux)
    uint32_t timestamp;     // 时间戳 (ms)
} GreenhouseData_t;

typedef enum {
    MODE_AUTO = 0,          // 自动模式
    MODE_MANUAL,            // 手动模式
    MODE_SETTING            // 设置模式
} SystemMode_t;

typedef struct {
    float target_temp;      // 目标温度
    float target_humidity;  // 目标湿度
    float target_light;     // 目标光照
    SystemMode_t mode;      // 系统模式
} SystemConfig_t;

/* ========== 全局变量 ========== */
// 通信队列
QueueHandle_t sensor_data_queue;      // 传感器数据队列
QueueHandle_t control_command_queue;  // 控制命令队列

// 信号量
SemaphoreHandle_t oled_mutex;         // OLED显示互斥锁
SemaphoreHandle_t config_mutex;       // 配置参数互斥锁

// 事件组
EventGroupHandle_t greenhouse_events;
#define EVENT_NEW_DATA        (1 << 0)  // 新数据就绪
#define EVENT_TEMP_HIGH       (1 << 1)  // 温度过高
#define EVENT_TEMP_LOW        (1 << 2)  // 温度过低
#define EVENT_HUMIDITY_HIGH   (1 << 3)  // 湿度过高
#define EVENT_HUMIDITY_LOW    (1 << 4)  // 湿度过低
#define EVENT_LIGHT_LOW       (1 << 5)  // 光照过低

// 软件定时器
TimerHandle_t data_log_timer;         // 数据记录定时器
TimerHandle_t system_monitor_timer;   // 系统监控定时器

// 任务句柄
TaskHandle_t sensor_task_handle;
TaskHandle_t control_task_handle;
TaskHandle_t display_task_handle;
TaskHandle_t ui_task_handle;
TaskHandle_t monitor_task_handle;

// 系统配置
SystemConfig_t system_config = {
    .target_temp = 25.0f,     // 默认目标温度25°C
    .target_humidity = 60.0f, // 默认目标湿度60%
    .target_light = 500.0f,   // 默认目标光照500lux
    .mode = MODE_AUTO         // 默认自动模式
};

/* ========== 硬件模拟函数 ========== */
float read_temperature(void) {
    // 模拟温度读数:在20-30°C之间波动
    static float temp = 25.0f;
    temp += ((rand() % 100) - 50) / 100.0f;  // ±0.5°C波动
    if(temp < 20.0f) temp = 20.0f;
    if(temp > 30.0f) temp = 30.0f;
    return temp;
}

float read_humidity(void) {
    // 模拟湿度读数:在40-80%之间波动
    static float humidity = 60.0f;
    humidity += ((rand() % 100) - 50) / 100.0f;  // ±0.5%波动
    if(humidity < 40.0f) humidity = 40.0f;
    if(humidity > 80.0f) humidity = 80.0f;
    return humidity;
}

float read_light(void) {
    // 模拟光照读数:白天高,夜晚低
    static uint32_t time_counter = 0;
    time_counter++;
    
    // 模拟昼夜变化(每1000次计数一个周期)
    uint32_t time_of_day = time_counter % 1000;
    
    if(time_of_day < 300) {
        // "夜晚":光照较低 0-100 lux
        return (float)(rand() % 100);
    } else {
        // "白天":光照较高 300-800 lux
        return 300.0f + (float)(rand() % 500);
    }
}

void set_heater(uint8_t state) {
    HAL_GPIO_WritePin(GPIOA, HEATER_PIN, state ? GPIO_PIN_SET : GPIO_PIN_RESET);
    printf("加热器:%s\n", state ? "开启" : "关闭");
}

void set_humidifier(uint8_t state) {
    HAL_GPIO_WritePin(GPIOA, HUMIDIFIER_PIN, state ? GPIO_PIN_SET : GPIO_PIN_RESET);
    printf("加湿器:%s\n", state ? "开启" : "关闭");
}

void set_led_light(uint8_t brightness) {
    // PWM控制LED亮度(0-100%)
    printf("补光灯:亮度%d%%\n", brightness);
}

void oled_display(const char *line1, const char *line2) {
    // 模拟OLED显示
    printf("========== OLED显示 ==========\n");
    printf("%s\n", line1);
    printf("%s\n", line2);
    printf("============================\n");
}

uint8_t read_key(void) {
    // 模拟按键读取
    static uint32_t key_counter = 0;
    key_counter++;
    
    if(key_counter % 50 == 0) return 1;  // 模拟KEY_UP
    if(key_counter % 70 == 0) return 2;  // 模拟KEY_DOWN
    if(key_counter % 90 == 0) return 3;  // 模拟KEY_OK
    
    return 0;  // 无按键
}

/* ========== 任务1:传感器采集任务 ========== */
void Sensor_Task(void *pvParameters) {
    GreenhouseData_t sensor_data;
    TickType_t last_wake_time = xTaskGetTickCount();
    
    printf("[传感器任务] 启动,每2秒采集一次数据\n");
    
    while(1) {
        // 采集传感器数据
        sensor_data.temperature = read_temperature();
        sensor_data.humidity = read_humidity();
        sensor_data.light = read_light();
        sensor_data.timestamp = xTaskGetTickCount();
        
        // 发送到数据队列
        if(xQueueSend(sensor_data_queue, &sensor_data, 0) != pdPASS) {
            printf("[警告] 传感器数据队列已满,数据丢失!\n");
        }
        
        // 设置事件标志:新数据就绪
        xEventGroupSetBits(greenhouse_events, EVENT_NEW_DATA);
        
        // 检查温度是否异常
        if(sensor_data.temperature > 28.0f) {
            xEventGroupSetBits(greenhouse_events, EVENT_TEMP_HIGH);
        } else if(sensor_data.temperature < 22.0f) {
            xEventGroupSetBits(greenhouse_events, EVENT_TEMP_LOW);
        }
        
        // 检查湿度是否异常
        if(sensor_data.humidity > 70.0f) {
            xEventGroupSetBits(greenhouse_events, EVENT_HUMIDITY_HIGH);
        } else if(sensor_data.humidity < 50.0f) {
            xEventGroupSetBits(greenhouse_events, EVENT_HUMIDITY_LOW);
        }
        
        // 检查光照是否不足
        if(sensor_data.light < 100.0f) {
            xEventGroupSetBits(greenhouse_events, EVENT_LIGHT_LOW);
        }
        
        // 每2秒采集一次
        vTaskDelayUntil(&last_wake_time, pdMS_TO_TICKS(2000));
    }
}

/* ========== 任务2:环境控制任务 ========== */
void Control_Task(void *pvParameters) {
    GreenhouseData_t current_data;
    EventBits_t events;
    uint8_t heater_state = 0;
    uint8_t humidifier_state = 0;
    uint8_t light_brightness = 0;
    
    printf("[控制任务] 启动,自动调节温室环境\n");
    
    while(1) {
        // 等待新数据就绪事件
        events = xEventGroupWaitBits(greenhouse_events,
                                    EVENT_NEW_DATA | EVENT_TEMP_HIGH | EVENT_TEMP_LOW |
                                    EVENT_HUMIDITY_HIGH | EVENT_HUMIDITY_LOW | EVENT_LIGHT_LOW,
                                    pdTRUE,  // 清除事件位
                                    pdFALSE, // 等待任意事件
                                    portMAX_DELAY);
        
        // 从队列获取最新传感器数据
        if(xQueueReceive(sensor_data_queue, &current_data, 0) == pdPASS) {
            printf("[控制任务] 收到数据:温度%.1f°C 湿度%.1f%% 光照%.0flux\n",
                   current_data.temperature, current_data.humidity, current_data.light);
            
            // 自动模式:根据目标值自动控制
            if(system_config.mode == MODE_AUTO) {
                // 温度控制
                if(current_data.temperature < system_config.target_temp - 1.0f) {
                    set_heater(1);  // 开启加热器
                    heater_state = 1;
                } else if(current_data.temperature > system_config.target_temp + 1.0f) {
                    set_heater(0);  // 关闭加热器
                    heater_state = 0;
                }
                
                // 湿度控制
                if(current_data.humidity < system_config.target_humidity - 5.0f) {
                    set_humidifier(1);  // 开启加湿器
                    humidifier_state = 1;
                } else if(current_data.humidity > system_config.target_humidity + 5.0f) {
                    set_humidifier(0);  // 关闭加湿器
                    humidifier_state = 0;
                }
                
                // 光照控制
                if(current_data.light < system_config.target_light - 100.0f) {
                    light_brightness = 80;  // 开启补光灯
                    set_led_light(light_brightness);
                } else if(current_data.light > system_config.target_light + 100.0f) {
                    light_brightness = 0;   // 关闭补光灯
                    set_led_light(light_brightness);
                }
            }
            
            // 处理异常事件
            if(events & EVENT_TEMP_HIGH) {
                printf("[警告] 温度过高!强制关闭加热器\n");
                set_heater(0);
            }
            
            if(events & EVENT_HUMIDITY_LOW) {
                printf("[警告] 湿度过低!强制开启加湿器\n");
                set_humidifier(1);
            }
        }
    }
}

/* ========== 任务3:显示任务 ========== */
void Display_Task(void *pvParameters) {
    GreenhouseData_t display_data;
    char line1[32], line2[32];
    TickType_t last_display_time = 0;
    
    printf("[显示任务] 启动,更新OLED显示\n");
    
    while(1) {
        // 每1秒更新一次显示
        if((xTaskGetTickCount() - last_display_time) > pdMS_TO_TICKS(1000)) {
            last_display_time = xTaskGetTickCount();
            
            // 获取互斥锁,保护OLED访问
            if(xSemaphoreTake(oled_mutex, pdMS_TO_TICKS(100)) == pdTRUE) {
                // 尝试获取最新数据(非阻塞)
                if(xQueuePeek(sensor_data_queue, &display_data, 0) == pdPASS) {
                    // 格式化显示内容
                    snprintf(line1, sizeof(line1), "T:%.1fC H:%.1f%%", 
                             display_data.temperature, display_data.humidity);
                    snprintf(line2, sizeof(line2), "L:%.0flux MODE:%s",
                             display_data.light,
                             system_config.mode == MODE_AUTO ? "AUTO" : 
                             system_config.mode == MODE_MANUAL ? "MAN" : "SET");
                    
                    // 更新OLED显示
                    oled_display(line1, line2);
                }
                
                xSemaphoreGive(oled_mutex);
            }
        }
        
        vTaskDelay(pdMS_TO_TICKS(100));  // 100ms检查一次
    }
}

/* ========== 任务4:用户界面任务 ========== */
void UI_Task(void *pvParameters) {
    uint8_t key_value;
    uint8_t setting_item = 0;  // 0:温度 1:湿度 2:光照 3:模式
    
    printf("[UI任务] 启动,处理按键输入\n");
    
    while(1) {
        key_value = read_key();
        
        if(key_value != 0) {
            // 获取配置互斥锁
            if(xSemaphoreTake(config_mutex, pdMS_TO_TICKS(100)) == pdTRUE) {
                switch(key_value) {
                    case 1:  // KEY_UP:增加
                        if(system_config.mode == MODE_SETTING) {
                            switch(setting_item) {
                                case 0: system_config.target_temp += 0.5f; break;
                                case 1: system_config.target_humidity += 1.0f; break;
                                case 2: system_config.target_light += 50.0f; break;
                                case 3: system_config.mode = MODE_AUTO; break;
                            }
                            printf("[设置] 参数增加\n");
                        } else {
                            // 切换为设置模式
                            system_config.mode = MODE_SETTING;
                            printf("[设置] 进入设置模式\n");
                        }
                        break;
                        
                    case 2:  // KEY_DOWN:减少
                        if(system_config.mode == MODE_SETTING) {
                            switch(setting_item) {
                                case 0: system_config.target_temp -= 0.5f; break;
                                case 1: system_config.target_humidity -= 1.0f; break;
                                case 2: system_config.target_light -= 50.0f; break;
                                case 3: system_config.mode = MODE_MANUAL; break;
                            }
                            printf("[设置] 参数减少\n");
                        }
                        break;
                        
                    case 3:  // KEY_OK:确认/下一项
                        if(system_config.mode == MODE_SETTING) {
                            setting_item = (setting_item + 1) % 4;
                            printf("[设置] 切换到下一设置项\n");
                        }
                        break;
                }
                
                xSemaphoreGive(config_mutex);
                
                // 显示当前设置
                printf("[设置] 目标温度:%.1f°C 湿度:%.1f%% 光照:%.0flux\n",
                       system_config.target_temp, system_config.target_humidity, 
                       system_config.target_light);
            }
        }
        
        vTaskDelay(pdMS_TO_TICKS(50));  // 50ms检测一次按键
    }
}

/* ========== 任务5:系统监控任务 ========== */
void Monitor_Task(void *pvParameters) {
    char task_list[512];
    uint32_t last_monitor_time = 0;
    
    printf("[监控任务] 启动,监控系统健康\n");
    
    while(1) {
        // 每10秒输出一次系统状态
        if((xTaskGetTickCount() - last_monitor_time) > pdMS_TO_TICKS(10000)) {
            last_monitor_time = xTaskGetTickCount();
            
            printf("========== 系统监控 ==========\n");
            printf("运行时间: %lu秒\n", xTaskGetTickCount() / 1000);
            printf("剩余堆内存: %d字节\n", xPortGetFreeHeapSize());
            
            // 显示任务状态(需要启用configUSE_TRACE_FACILITY)
            vTaskList(task_list);
            printf("%s\n", task_list);
            
            // 显示队列使用情况
            UBaseType_t queue_messages = uxQueueMessagesWaiting(sensor_data_queue);
            printf("传感器队列: %d/10条消息\n", queue_messages);
        }
        
        vTaskDelay(pdMS_TO_TICKS(1000));  // 1秒检查一次
    }
}

/* ========== 定时器回调函数 ========== */
// 数据记录定时器回调
void Data_Log_Callback(TimerHandle_t xTimer) {
    GreenhouseData_t log_data;
    
    // 获取最新数据
    if(xQueuePeek(sensor_data_queue, &log_data, 0) == pdPASS) {
        printf("[数据记录] 时间:%lu, 温度:%.1f, 湿度:%.1f, 光照:%.0f\n",
               log_data.timestamp, log_data.temperature, 
               log_data.humidity, log_data.light);
        
        // 这里可以添加SD卡存储代码
        // save_to_sd_card(&log_data);
    }
}

// 系统监控定时器回调
void System_Monitor_Callback(TimerHandle_t xTimer) {
    static uint32_t timer_count = 0;
    timer_count++;
    
    printf("[定时器] 系统自检 %lu 次\n", timer_count);
    
    // 检查堆内存使用
    if(xPortGetFreeHeapSize() < 1024) {
        printf("[警告] 堆内存不足!\n");
    }
}

/* ========== 系统初始化 ========== */
void Greenhouse_System_Init(void) {
    printf("\n=================================\n");
    printf("   智能温室控制系统 v1.0\n");
    printf("=================================\n\n");
    
    // 1. 创建队列(快递系统)
    sensor_data_queue = xQueueCreate(10, sizeof(GreenhouseData_t));
    control_command_queue = xQueueCreate(5, sizeof(uint8_t));
    
    if(sensor_data_queue == NULL || control_command_queue == NULL) {
        printf("[错误] 队列创建失败!\n");
        return;
    }
    
    // 2. 创建信号量(门锁系统)
    oled_mutex = xSemaphoreCreateMutex();
    config_mutex = xSemaphoreCreateMutex();
    
    // 3. 创建事件组(检查点系统)
    greenhouse_events = xEventGroupCreate();
    
    // 4. 创建软件定时器(闹钟系统)
    data_log_timer = xTimerCreate("DataLog", 
                                  pdMS_TO_TICKS(30000),  // 30秒记录一次
                                  pdTRUE,                // 自动重载
                                  NULL,
                                  Data_Log_Callback);
    
    system_monitor_timer = xTimerCreate("SystemMonitor",
                                        pdMS_TO_TICKS(60000),  // 60秒监控一次
                                        pdTRUE,
                                        NULL,
                                        System_Monitor_Callback);
    
    // 5. 启动定时器
    xTimerStart(data_log_timer, 0);
    xTimerStart(system_monitor_timer, 0);
    
    printf("[系统] 通信资源初始化完成\n");
    
    // 6. 创建任务(招聘员工)
    xTaskCreate(Sensor_Task, "Sensor", 256, NULL, 3, &sensor_task_handle);
    xTaskCreate(Control_Task, "Control", 256, NULL, 4, &control_task_handle);
    xTaskCreate(Display_Task, "Display", 192, NULL, 2, &display_task_handle);
    xTaskCreate(UI_Task, "UI", 192, NULL, 2, &ui_task_handle);
    xTaskCreate(Monitor_Task, "Monitor", 256, NULL, 1, &monitor_task_handle);
    
    printf("[系统] 所有任务创建完成\n");
    printf("[系统] 温室控制系统启动成功!\n");
    printf("=================================\n\n");
}

/* ========== 主函数 ========== */
int main(void) {
    // 硬件初始化(实际项目需要)
    HAL_Init();
    SystemClock_Config();
    
    // 初始化GPIO
    // MX_GPIO_Init();
    
    // 初始化串口用于调试
    // MX_USART1_UART_Init();
    
    printf("硬件初始化完成\n");
    
    // 启动温室控制系统
    Greenhouse_System_Init();
    
    // 启动FreeRTOS调度器
    vTaskStartScheduler();
    
    // 如果调度器启动失败,执行到这里
    while(1) {
        printf("FreeRTOS调度器启动失败!\n");
        HAL_Delay(1000);
    }
    
    return 0;
}

项目运行效果

=================================
   智能温室控制系统 v1.0
=================================

[系统] 通信资源初始化完成
[系统] 所有任务创建完成
[系统] 温室控制系统启动成功!
=================================

[传感器任务] 启动,每2秒采集一次数据
[控制任务] 启动,自动调节温室环境
[显示任务] 启动,更新OLED显示
[UI任务] 启动,处理按键输入
[监控任务] 启动,监控系统健康

========== OLED显示 ==========
T:24.3C H:58.7%
L:423lux MODE:AUTO
============================

[控制任务] 收到数据:温度24.3°C 湿度58.7% 光照423lux

========== OLED显示 ==========
T:24.5C H:59.1%
L:398lux MODE:AUTO
============================

[数据记录] 时间:30000, 温度:24.5, 湿度:59.1, 光照:398

========== 系统监控 ==========
运行时间: 10秒
剩余堆内存: 8152字节
任务名      状态 优先级 剩余栈
Sensor      R    3      112
Control     B    4      108
Display     R    2      96
UI          R    2      100
Monitor     R    1      120
IDLE        R    0      124
Tmr Svc     B    6      116

传感器队列: 3/10条消息

[设置] 进入设置模式
[设置] 目标温度:25.0°C 湿度:60.0% 光照:500lux
posted @ 2026-01-10 17:23  lwpigking  阅读(40)  评论(0)    收藏  举报