FreeRTOS多任务系统①
多任务系统
回想一下我们以前在使用 51、STM32 单片机裸机(未使用实时操作系统)的时候一般都是在main 函数里面用 while(1)做一个大循环来完成所有的处理, 即应用程序是一个无限的循环, 循环中调用相应的函数完成所需的处理。 有时候我们也需要中断中完成一些处理。 相对于多任务系统而言, 这个就是单任务系统, 也称作前后台系统, 中断服务函数作为前台程序, 大循环while(1)作为后台程序。
裸机开发缺点
前后台系统的实时性差, 前后台系统各个任务(应用程序)都是排队等着轮流执行,不管你这个程序现在有多紧急, 没轮到你就只能等着!相当于所有任务(应用程序)的优先级都是一样的。 但是前后台系统简单,资源消耗也少!在稍微大一点的嵌入式应用中前后台系统就明显力不从心了,此时就需要多任务系统出马了。
FreeRTOS优势
多任务系统会把一个大问题(应用)“ 分而治之” , 把大问题划分成很多个小问题, 逐步的把小问题解决掉, 大问题也就随之解决了, 这些小问题可以单独的作为一个小任务来处理。 这些小任务是并发处理的。
注意:并不是说同一时刻一起执行很多个任务, 而是由于每个任务执行的时间很短, 导致看起来像是同一时刻执行了很多个任务一样。 多个任务带来了一个新的问题, 究竟哪个任务先运行, 哪个任务后运行呢? 完成这个功能的东西在 RTOS 系统中叫做任务调度器。
任务调度器
FreeRTOS 是一个抢占式的实时多任务系统, 那么其任务调度器也是抢占式的, 运行过程如图所示:
任务调度器类型
FreeRTOS 支持两种调度器模式,通过宏configUSE_PREEMPTION
配置:
1. 抢占式调度器(Preemptive Scheduler)
- 核心机制:
- 高优先级任务可随时中断低优先级任务的执行,确保紧急任务优先运行。
- 由系统滴答定时器(SysTick)触发周期性调度(如每 1ms 一次),或在中断后触发调度。
- 应用场景:
- 对实时性要求高的场景(如工业控制、传感器数据采集)。
就是高优先级的任务可以打断低优先级任务的运行而取得 CPU 的使用权, 这样就保证了那些紧急任务的运行。 抢占式调度,是最高优先级的任务一旦就绪, 总能得到CPU的执行权; 它抢占了低优先级的运行机会。 在抢占式调度系统中,总是运行最高优先级的任务。
2. 非抢占式调度器(Cooperative Scheduler)
- 核心机制:
- 任务主动释放 CPU 控制权(如调用
vTaskDelay()
),调度器才会切换到下一个任务。 - 低优先级任务运行时,高优先级任务需等待当前任务主动让出 CPU。
- 任务主动释放 CPU 控制权(如调用
- 应用场景:
- 对实时性要求较低,或需避免抢占导致资源竞争的场景(如简单的多任务协作)。
就是时间片轮转的调度
时间片轮转的调度方法:是让相同优先级的几个任务轮流运行, 每个任务运行一个时间片, 任务在时间片运行完之后,操作系统自动切换到下一个任务运行; 在任务运行的时间片中, 也可以提前让出CPU运行权,把它交给下一个任务运行。 FreeRTOS的时间片固定为一个时钟节拍,由configTICK_RATE_HZ这个宏设置
在FreeRTOS中,抢占式调度,与时间片轮转可以同时存在; 当有高优先级任务就绪时, 运行高优先级任务; 当最高优先级的任务有好几个时, 这几个任务可以以时间片轮转方式调度。
任务优先级
每 个 任 务 都 可 以 分 配 一 个 从 0~(configMAX_PRIORITIES-1) 的 优 先级 , configMAX_PRIORITIES 在文件 FreeRTOSConfig.h 中有定义, 一般不超过 32。
优先级数字越低表示任务的优先级越低,0 的优先级最低,configMAX_PRIORITIES-1 的优先级最高。空闲任务的优先级最低,为 0。( 注意和中断的优先级区分,任务和中断不一样,中断一般是数字越小优先级越大)
当宏 configUSE_TIME_SLICING 定义为 1 的时候多个任务可以共用一个优先级, 数量不限。
任务状态
FreeRTOS 中的任务永远处于下面几个状态中的某一个:
运行态
当一个任务正在运行时,那么就说这个任务处于运行态,处于运行态的任务就是当前正在使用处理器的任务。如果使用的是单核处理器的话那么不管在任何时刻永远都只有一个任务处于运行态。
就绪态
处于就绪态的任务是那些已经准备就绪(这些任务没有被阻塞或者挂起), 可以运行的任务,但是处于就绪态的任务还没有运行,因为有一个同优先级或者更高优先级的任务正在运行。
挂起态
像阻塞态一样,任务进入挂起态以后也不能被调度器调用进入运行态,但是进入挂起态的任务没有超时时间。任务进入和退出挂起态通过调用函数vTaskSuspend()和 xTaskResume()。
注意:
① 一个更高优先级的任务进入了就绪状态,就可以替换在cpu里比自己优先级低的。
② cpu执行的任务调用系统时间等待,或者等待事件,从而进入阻塞态退出cpu,从就绪状态的任务挑选最高优先级的进入cpu。
③ 你设置了时间片调度,然后就绪状态里最高优先级的任务和正在cpu里的任务优先级一样, 也发生调度任务。
以下代码就是用抢占式调度器实现两个任务,led0_task()函数抢占优先级是3,led1_task()函数抢占优先级是2,所以函数会一直执行led0_task()函数。
如果将 vTaskDelay(500); 打开注释那就会两个任务交替执行,就是用到了时间片轮转的调度方法。led0_task()函数比led1_task()函数抢占优先级高,所以先执行led0_task()函数,然后进入vTaskDelay(500)阻塞,阻塞期间会执行led1_task()函数,一旦vTaskDelay(500)阻塞时间到了led0_task()就会马上抢占led1_task()函数进入cpu执行led0_task(),这就体现了高优先级抢占低优先级,而且抢占式调度,与时间片轮转可以同时存在。
//ÈÎÎñÓÅÏȼ¶
#define LED0_TASK_PRIO 3
//ÈÎÎñ¶ÑÕ»´óС
#define LED0_STK_SIZE 50
//ÈÎÎñ¾ä±ú
TaskHandle_t LED0Task_Handler;
//ÈÎÎñº¯Êý
void led0_task(void *pvParameters);//ÈÎÎñÓÅÏȼ¶
#define LED1_TASK_PRIO 2
//ÈÎÎñ¶ÑÕ»´óС
#define LED1_STK_SIZE 50
//ÈÎÎñ¾ä±ú
TaskHandle_t LED1Task_Handler;
//ÈÎÎñº¯Êý
void led1_task(void *pvParameters);int main(void)
{NVIC_PriorityGroupConfig(NVIC_PriorityGroup_4);//ÉèÖÃϵͳÖжÏÓÅÏȼ¶·Ö×é4 delay_init(); //ÑÓʱº¯Êý³õʼ»¯ uart_init(115200); //³õʼ»¯´®¿ÚLED_Init(); //³õʼ»¯LED//´´½¨LED0ÈÎÎñxTaskCreate((TaskFunction_t )led0_task, //ÈÎÎñº¯Êý (const char* )"led0_task", //ÈÎÎñÃû³Æ (uint16_t )LED0_STK_SIZE,//ÈÎÎñ¶ÑÕ»´óС (void* )NULL, //´«µÝ¸øÈÎÎñº¯ÊýµÄ²ÎÊý(UBaseType_t )LED0_TASK_PRIO,//ÈÎÎñÓÅÏȼ¶ (TaskHandle_t* )&LED0Task_Handler); //ÈÎÎñ¾ä±ú //´´½¨LED1ÈÎÎñxTaskCreate((TaskFunction_t )led1_task, (const char* )"led1_task", (uint16_t )LED1_STK_SIZE, (void* )NULL,(UBaseType_t )LED1_TASK_PRIO,(TaskHandle_t* )&LED1Task_Handler); vTaskStartScheduler(); //¿ªÆôÈÎÎñµ÷¶È
}//LED0ÈÎÎñº¯Êý
void led0_task(void *pvParameters)
{while(1){LED0=~LED0;// vTaskDelay(500);//freertosϵͳµÈ´ýº¯Êýdelay_xms(500);}
} //LED1ÈÎÎñº¯Êý
void led1_task(void *pvParameters)
{while(1){LED1=0;// vTaskDelay(200);delay_xms(500);LED1=1;// vTaskDelay(800);delay_xms(500);}
}