freertos学习笔记3--个人自用-9.3 任务优先级和Tick

.

9.3.1 任务优先级

FreeRTOS 的任务优先级(Task Priority)是决定“谁先运行”的核心机制。

在 FreeRTOS 中,调度器(Scheduler)遵循一条铁律

“永远让处于就绪态(Ready)且优先级最高的任务运行。”

1. 优先级的数值定义

数字越大 = 优先级越高

  • 这是 FreeRTOS 的设定(注意:这与 ARM Cortex-M 硬件中断优先级的逻辑相反,硬件中断通常是数字越小优先级越高,不要混淆)。

2. 调度机制:抢占与时间片

FreeRTOS 的调度主要处理两种情况:

情况 A:优先级不同 —— 抢占 (Preemption)

如果系统中有一个优先级为 3 的任务和一个优先级为 1 的任务,且它们都处于“就绪态”:

  • 结果:优先级 3 的任务会独占 CPU。

  • 抢占:假设优先级 1 的任务正在运行,此时优先级 3 的任务(比如等待的定时器到了)突然变成了就绪态。调度器会立即暂停优先级 1 的任务,切换到优先级 3 的任务。

  • 注意:除非优先级 3 的任务自己主动放弃 CPU(进入阻塞态,如调用 vTaskDelay),否则优先级 1 的任务永远得不到运行机会。

情况 B:优先级相同 —— 时间片轮转 (Time Slicing)

如果系统中有两个优先级都为 2 的任务(Task A 和 Task B),且都就绪:

  • 结果:它俩会“轮流坐庄”。

  • 机制:FreeRTOS 利用系统滴答定时器(Systick),将时间切成一个个“时间片”(Tick,通常是 1ms)。

    • 第 1ms:运行 Task A

    • 第 2ms:运行 Task B

    • 第 3ms:运行 Task A

    • ...

  • 宏观上看,它们好像在同时运行。

3. 两个特殊的优先级任务

在学习调度方法之前,你只要初略地知道:

  • FreeRTOS会确保最高优先级的、可运行的任务,马上就能执行
  • 对于相同优先级的、可运行的任务,轮流执行

这无需记忆,就像我们举的例子:

  • 厨房着火了,当然优先灭火
  • 喂饭、回复信息同样重要,轮流做

----------------------------------------------分界线-----------------------------------------------------------

9.3.2 Tick

1. 什么是 Tick?

  • 物理本质:它是一个周期性的硬件定时器中断

  • 来源:在 STM32(ARM Cortex-M)上,通常由内核自带的 SysTick 定时器 产生。

  • 形象比喻:Tick 就像是一个节拍器或者心跳

    • “滴...滴...滴...”,每响一下,操作系统就会醒过来处理一次事务。

在 FreeRTOS(以及大多数实时操作系统)中,Tick(系统节拍) 是整个系统运转的“心跳”。

如果没有 Tick,操作系统就无法感知时间的流逝,也就无法实现“延时”、“超时等待”或者“时间片轮转调度”。

以下是关于 Tick 的核心知识点讲解:

1. 什么是 Tick?

  • 物理本质:它是一个周期性的硬件定时器中断

  • 来源:在 STM32(ARM Cortex-M)上,通常由内核自带的 SysTick 定时器 产生。

  • 形象比喻:Tick 就像是一个节拍器或者心跳

    • “滴...滴...滴...”,每响一下,操作系统就会醒过来处理一次事务。

2. Tick 的频率

Tick 的频率(configTICK_RATE_HZ)

Tick 发生的快慢由 FreeRTOSConfig.h 中的宏 configTICK_RATE_HZ 决定。

  • 含义:每秒钟产生多少次 Tick 中断。

  • 常见设置1000 (即 1000Hz)。

    • 如果设为 1000,意味着每 1ms 发生一次中断。

    • 这是 STM32 开发中最常用的配置,因为它让 1 个 Tick 正好等于 1 毫秒,便于计算。(通常1ms)

3. 每当 Tick 发生时,OS 做了什么?

每当硬件产生一次 Tick 中断,FreeRTOS 的内核代码(xPortSysTickHandler)就会执行。它主要做两件事:

  1. 时间的维护 (Timekeeping)

    • 系统内部有一个全局变量 xTickCount(Tick 计数器),它会 +1

    • 就像墙上的时钟走了一秒。系统通过由于这个变量来计算“现在运行了多久”。

    • 检查延时列表:OS 会检查所有正在“睡觉”(Blocked)的任务:“喂,你设定的唤醒时间到了没?”。如果到了,就把任务叫醒(改为 Ready 态)。

  2. 调度决策 (Scheduling)

    • 如果配置了时间片轮转,OS 会在 Tick 中断里判断:“当前任务的时间片用完了吗?是不是该换同优先级的另一个任务上场了?”

    • 如果需要切换,就会触发上下文切换(Context Switch)。

4. 这里的坑:Tick 与 毫秒 (ms) 的换算

5. 深入理解:Tick 对精度的影响

该处结论,在第七点说明了.vTaskDelay误差并不大

6/总结

7/为什么用vTaskDelay来延迟

      我们用 vTaskDelay 不是因为它准,而是因为它懂事(懂得让出 CPU)。       

9.3.4 修改优先级

1. 获取任务优先级 (uxTaskPriorityGet)

2. 设置任务优先级 (vTaskPrioritySet)

3. 核心机制:发生什么了?(重要!)

4. 代码示例

场景一:任务“自省” (操作自己)

场景描述:任务想知道自己当前的优先级是多少,并在串口打印出来。 用到函数uxTaskPriorityGet(NULL)

void vTaskMyInfo( void * pvParameters )
{
    UBaseType_t uxMyPriority;

    for( ;; )
    {
        // 1. 获取自己的优先级
        // 参数传 NULL,表示“获取我当前正在运行的这个任务的优先级”
        uxMyPriority = uxTaskPriorityGet( NULL );

        // 2. 打印出来
        printf("My current priority is: %ld\n", uxMyPriority);

        vTaskDelay( pdMS_TO_TICKS( 1000 ) );
    }
}

场景二

void vTaskDataProcess( void * pvParameters )
{
    UBaseType_t uxNormalPriority;

    // 保存一下原来的优先级 (假设原来是 1)
    uxNormalPriority = uxTaskPriorityGet( NULL );

    for( ;; )
    {
        // --- 阶段1:普通工作 ---
        printf("Working lazily...\n");
        
        // --- 阶段2:进入紧急状态 ---
        // 假设检测到大量数据涌入,需要全力处理
        // 将自己提升到最高优先级 (configMAX_PRIORITIES - 1)
        printf("Emergency! Boosting priority!\n");
        vTaskPrioritySet( NULL, configMAX_PRIORITIES - 1 );

        // 此时,我变成了全场最高,几乎没人能打断我
        Heavy_Calculation_Function(); 

        // --- 阶段3:恢复正常 ---
        // 事情干完了,把优先级降回原来的值 (1)
        // 否则其他任务就饿死了
        printf("Done. Going back to normal.\n");
        vTaskPrioritySet( NULL, uxNormalPriority );

        vTaskDelay( pdMS_TO_TICKS( 500 ) );
    }
}

场景三:老板指挥员工 (操作别的任务)

场景描述

  • Task A (老板/按键检测):负责监控。

  • Task B (员工/LED):平时慢闪(优先级低)。

  • 当 Task A 检测到按键按下时,把 Task B 的优先级调高,让它立刻响应。

用到函数:需要用到任务句柄 TaskHandle_t

/* FreeRTOS 优先级动态修改示例 - "老板指挥员工"
   场景:Task A (Boss) 检测按键,动态修改 Task B (Employee) 的优先级
*/

#include "FreeRTOS.h"
#include "task.h"
#include <stdio.h>

/* ============================================================ */
/* 1. 全局变量:用于存放"员工"的任务句柄 (相当于工号)             */
/* ============================================================ */
TaskHandle_t xLedTaskHandle = NULL; 

/* 模拟按键读取函数 (返回 1 表示按下,0 表示松开) */
int Key_Pressed(void) {
    // 实际项目中这里读取 GPIO
    // 这里为了演示,假设一直返回 1 (模拟按下状态) 或者你可以自己修改逻辑
    return 1; 
}

/* ============================================================ */
/* 2. Task B (员工/LED):被控制的对象                            */
/* 平时优先级低(1),可能会被提升到(4)                          */
/* ============================================================ */
void vTaskLed(void *pvParameters)
{
    UBaseType_t uxCurrentPriority;

    for( ;; )
    {
        /* 获取自己当前的优先级 (NULL 代表自己) */
        uxCurrentPriority = uxTaskPriorityGet(NULL);

        /* 打印当前状态 */
        printf("[Task B - LED] Running... My Current Priority is: %ld\r\n", uxCurrentPriority);

        /* 模拟干活 */
        /* 如果优先级变为 4,这里会抢占 Task A 的运行时间 */
        
        vTaskDelay(pdMS_TO_TICKS(500)); 
    }
}

/* ============================================================ */
/* 3. Task A (老板/按键):控制者                                  */
/* 优先级固定为 3                                             */
/* ============================================================ */
void vTaskKey(void *pvParameters)
{
    /* 定义两个常量表示优先级 */
    const UBaseType_t uxLowPriority = 1;
    const UBaseType_t uxHighPriority = 4;

    for( ;; )
    {
        /* 模拟检测按键 */
        if( Key_Pressed() == 1 )
        {
            /* 重点:使用全局句柄 xLedTaskHandle 来指定要修改谁 */
            /* 将 Task B 提升到 4 (比老板自己 3 还高) */
            /* 现象:这行代码执行完瞬间,如果 Task B 处于就绪态,CPU 会立即跳转去运行 Task B */
            vTaskPrioritySet(xLedTaskHandle, uxHighPriority);
            
            printf("[Task A - Boss] Key Pressed! Promoted Task B to High Priority!\r\n");
        }
        else
        {
            /* 按键松开,把 Task B 打回原形 */
            vTaskPrioritySet(xLedTaskHandle, uxLowPriority);
            
            printf("[Task A - Boss] Key Released. Reset Task B to Low Priority.\r\n");
        }

        /* 延时 100ms,避免按键检测太快占用 CPU */
        vTaskDelay(pdMS_TO_TICKS(100));
    }
}

/* ============================================================ */
/* 4. 主函数                                                    */
/* ============================================================ */
int main(void)
{
    /* 硬件初始化 (时钟、串口、GPIO等) ... */
    
    /* 创建 Task B (员工) */
    /* 关键点:最后一个参数 &xLedTaskHandle */
    /* 只有传入这个地址,创建成功后 xLedTaskHandle 里才会有值,Task A 才能用 */
    xTaskCreate(vTaskLed, 
                "LED_Task", 
                128, 
                NULL, 
                1,              /* 初始优先级:1 */
                &xLedTaskHandle /* 【关键】将句柄保存到全局变量 */
                );

    /* 创建 Task A (老板) */
    /* 不需要被别人控制,句柄参数填 NULL */
    xTaskCreate(vTaskKey, 
                "Key_Task", 
                128, 
                NULL, 
                3,              /* 初始优先级:3 */
                NULL            
                );

    /* 启动调度器 */
    vTaskStartScheduler();

    /* 正常情况永远不会执行到这里 */
    while(1);
}

句柄设置为null 

写成 TaskHandle_t xLedTaskHandle = NULL; 的作用就像是: 给员工发一张空白的工牌,而不是随便捡一张废纸写个假号码。

  • 安全:防止野指针乱指导致死机。

  • 严谨:方便后续代码判断任务是否已经合法存在。

TCB

TCB是什么

TCB和句柄关系

复习:对于指针的地址和数据

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值