FreeRTOS调度有3种,
第一种:抢占式+时间切片
时间切片又有宏configIDLE_SHOULD_YIELD定义
configIDLE_SHOULD_YIELD = 0
空闲任务和优先级相同的任务0 交替使用 占空比为(50%)
configIDLE_SHOULD_YIELD = 1
空闲任务会让出时间,时间会给任务优先级相同的任务(90以上%)
第二种:抢占式(无时间切片)
第三种:合作式(无时间切片) (手动切换)方式
只有当运行状态任务进入阻塞状态时才会发生任务切换*,或者运行状态任务通过**手动请求重新调度taskYIELD().**任务永远不会被抢占,所以不能使用时间切片。
那么下面说人话:
村里有个姑娘叫小花(CPU),村里有很多小伙子都在追求她,小花她妈(任务调度器),每天都要去选小伙子(任务),选中的小伙子会被允许跟小花交往一天(任务占用CPU一个心跳周期),小花她妈选小伙子的标准是:
第一,小伙子要喜欢小花(任务处于就绪态)。
第二,小伙子要是所有小花追求者中条件最好的(就绪态任务中处于最高优先级)。
第三,如果有多个喜欢小花的小伙子条件一样好,就让他们轮流,一人跟小花交往一天(相同优先级的任务,任务调度器会让每个任务执行一个心跳周期,轮流执行)。
小伙子中有一个叫小绿的,特别喜欢小花,人又帅,条件又好,小花他妈一眼就看上了,小绿因为没有敌手,所以每天都能跟小花交往,同村里条件不好的小伙组,见小绿天天霸占着小花,殉情了(低优先级的任务被饿死),小绿见局势不妙,决定先去避一避,等过一段时间再回来(进入阻塞态),他对小花她妈谎称自己不喜欢小花了,由于小花他妈选择小伙子的标准的第一条就是小伙子要喜欢小花(任务必须处于就绪态)。所以小花妈就开始从别的追求小花的小伙子中选条件最好的(在就绪态的任务里选择最高优先级的任务)。过了一段时间,小绿见自己天天被绿,他决定复出(从阻塞态重返就绪态),这天绿小绿的小黄在跟小花逛街,被刚复出的小绿撞见,可小绿此时没办法,只能等小花他妈第二天来评评理(等待下一个心跳周期任务调度器仲裁),因为小花他妈一天只出现一次(每个心跳周期任务调度器运行一次),第二天,小花他妈见小绿回心转意,条件还那么好,就让他继续跟小花交往,这次小绿学聪明了,每隔一段时间就跟小花妈说自己不喜欢小花了,然后出去歇一阵子再回来(循环进入阻塞态),这样大家都还有机会可以跟小花交往,同村的小伙子们不会想殉情(高低优先级的任务都可以使用CPU资源,低优先级任务不会被饿死),直到有一天,头上已经是青青草原的小绿实在受不了了,他决定从此隐居山林(进入挂起态),除非有人去找他(调用resume函数唤醒),否则他再也不见小花了(挂起态的任务对任务调度器来说不可见)。同村的小伙子见小绿都这样了,也就收敛了很多,甚至有时候,小花他妈都找不到小伙子,但小花没有人陪着聊天咋办呢?那只找来门口的二傻(空闲任务)陪小花,二傻子从小就一直喜欢小花(空闲任务除了在运行态就在就绪态),二傻并不傻,他也会像其他小伙子一样做很多事情(空闲任务中也可以写一用户自己的函数(称为空闲任务钩子函数)),但他的条件太差了(优先级最低,为0),只要有小伙子比二傻条件好点,来追求小花,小花他妈就会让二傻走(只要该任务优先级高于0,空闲任务就会被抢占)。有一天又来了一个三傻,跟二傻条件差不多(用户自己的任务也可以跟空闲任务共享优先级:0),又没有其他小伙子来追求小花,小花他妈就让三傻跟跟二傻轮流陪小花聊天。
这里再补充一点,小花妈并不会凭主观判断哪个小伙子的条件好不好,因为村子里从古至今都流传着一个法则,每个小伙子都会有一个数字,这个数字越高,条件越好,这个数字就由掌管世界的神秘人来掌握,神秘人觉得谁条件好就把谁的数字调高,所以神秘人为了让小花和小伙子们相处和睦,必须谨慎分配这个数字,需要洞悉小花什么时候需要什么(根据任务的要紧程度,调整任务优先级),这是一件让人头疼的工作,毕竟洞悉女孩子的想法是很难的事。
stm32F103C8T6
MDK keil5
stm32cube + FreeRTOS
创建一个任务函数
下面展示一些 内联代码片
。
void ATaskFunction( void *pvParameters )
{
int32_t lVariableExample = 0;
/* A task will normally be implemented as an infinite loop. */
for( ;; )
{
/* The code to implement the task functionality will go here. */
}
vTaskDelete( NULL );
}
任务是使用FreeRTOSx任务创建()API函数创建的。 这可能是所有API函数中最复杂的,因此不幸的是,它是第一次遇到的,但是任务必须首先掌握,因为它们是多任务系统中最基本的组件。 本书中的所有示例都使用了xTaskCreate()函数,因此有大量的示例可供参考。
代码:
下面展示一些 内联代码片
。
BaseType_t xTaskCreate( TaskFunction_t pxTaskCode,
const char * const pcName,
const configSTACK_DEPTH_TYPE usStackDepth,
void * const pvParameters,
UBaseType_t uxPriority,
TaskHandle_t * const pxCreatedTask )
参数说明:
void Func_usar1(void const * argument)
{
/* USER CODE BEGIN Func_usar1 */
const char *pcTaskName = "Task 1 is running\r\n";
/* Infinite loop */
for(;;)
{
HAL_UART_Transmit(&huart1,(uint8_t *)pcTaskName,strlen(pcTaskName),0xffff);
osDelay(1000);
}
/* USER CODE END Func_usar1 */
}
/* USER CODE BEGIN Header_StartTask04 */
/**
* @brief Function implementing the myTask04 thread.
* @param argument: Not used
* @retval None
*/
/* USER CODE END Header_StartTask04 */
void StartTask04(void const * argument)
{
/* USER CODE BEGIN StartTask04 */
const char *pcTaskName = "Task 2 is running\r\n";
/* Infinite loop */
for(;;)
{
HAL_UART_Transmit(&huart1,(uint8_t *)pcTaskName,strlen(pcTaskName),0xffff);
osDelay(1000);
}
/* USER CODE END StartTask04 */
}
void Func_usar1(void const * argument)
{
/* USER CODE BEGIN Func_usar1 */
const char *pcTaskName = "Task 1 is running\r\n";
osThreadDef(myTask04, StartTask04, osPriorityLow, 0, 128);
myTask04Handle = osThreadCreate(osThread(myTask04), NULL);
/* Infinite loop */
for(;;)
{
HAL_UART_Transmit(&huart1,(uint8_t *)pcTaskName,strlen(pcTaskName),0xffff);
osDelay(1000);
}
/* USER CODE END Func_usar1 */
}
下面展示一些 内联代码片
。
static char *pcTask1Name = "Task 1 is running\r\n";
static char *pcTask2Name = "Task 2 is running\r\n";
osThreadDef(Task_LED1, Func_LED1, osPriorityNormal, 0, 128);
Task_LED1Handle = osThreadCreate(osThread(Task_LED1), NULL);
/* definition and creation of Task_LED2 */
osThreadDef(Task_LED2, Func_LED2, osPriorityIdle, 0, 128);
Task_LED2Handle = osThreadCreate(osThread(Task_LED2), NULL);
/* definition and creation of Task_Usar1 */
osThreadDef(Task_Usar1, Func_usar1, osPriorityIdle, 0, 128);
Task_Usar1Handle = osThreadCreate(osThread(Task_Usar1), (void*)pcTask1Name);
/* definition and creation of myTask04 */
osThreadDef(myTask04, StartTask04, osPriorityLow, 0, 128);
myTask04Handle = osThreadCreate(osThread(myTask04), (void*)pcTask2Name);
void Func_usar1(void const * argument)
{
/* USER CODE BEGIN Func_usar1 */
char *pcTaskName;
/* Infinite loop */
pcTaskName = (char * )argument;
for(;;)
{
HAL_UART_Transmit(&huart1,(uint8_t *)pcTaskName,strlen(pcTaskName),0xffff);
osDelay(1000);
}
/* USER CODE END Func_usar1 */
}
/* USER CODE BEGIN Header_StartTask04 */
/**
* @brief Function implementing the myTask04 thread.
* @param argument: Not used
* @retval None
*/
/* USER CODE END Header_StartTask04 */
void StartTask04(void const * argument)
{
/* USER CODE BEGIN StartTask04 */
char *pcTaskName;
/* Infinite loop */
pcTaskName = (char * )argument;
for(;;)
{
HAL_UART_Transmit(&huart1,(uint8_t *)pcTaskName,strlen(pcTaskName),0xffff);
osDelay(1000);
}
/* USER CODE END StartTask04 */
}
结果如下
任务优先是由宏configMAX_PRIORITIES定义 FreeRTOSConfig.h.
configMAX_PRIORITIES在最小的必要时,由于其值越高,RAM将被消耗越多
任务优先级范围的话是在0~(configMAX_PRIORITIES-1)
修改函数可以使用vTaskPrioritySet()
宏定义必须要开启configUSE_PORT_OPTIMISED_TASK_SELECTION = 0
configMAX_PRIORITIES的值不能超过32个
如果在Free RTOSConfig.h中configUSE_PORT_OPTIMISED_TASK_SELECTION设置为1,则将使用架构优化方法
time slicing 时间切片
tick interrupt 嘀嗒中断
是有宏configTICK_RATE_HZ设定 这边为1ms
如果configTICK_RATE_HZ设置为100(Hz),那么时间片将为10毫秒。 两个滴答中断之间的时间称为“滴答周期’。 一次切片等于一个滴答周期。
configTICK_RATE_HZ的最佳值取决于正在开发的应用程序,尽管100的值是典型
的
任务切换图
pdMS_TO_TICKS()函数(限制使用当 configTICK_RATE_HZ大于1000)
宏将以毫秒为单位指定的时间转换为滴答中指定的时间。
举例:TickType_t xTimeInTicks = pdMS_TO_TICKS( 200 );
结果:
为了使任务有用,必须重新编写它们,以实现事件驱动。 事件驱动的任务只有在触发事件发生后才有工作(处理)可执行,并且无法在事件发生之前进入运行状态。 调度程序总是选择能够运行的最高优先级任务。 无法运行的高优先级任务意味着调度程序无法选择它们,因此必须选择能够运行的低优先级任务。 因此,使用事件驱动的任务意味着任务可以在不同的优先级上创建,而不需要最高优先级的任务饥饿所有处理时间的低优先级任务。
任务进入阻塞状态
任何形式的轮询都有其他几个缺点,其中最重要的是效率低下。 在轮询期间,任务实际上没有任何工作要做,但它仍然使用最大的处理时间,因此浪费处理器周期
因此要使用下vTaskDelay() (宏INCLUDE_vTaskDelay = 1才能使用)
任务延迟()将调用任务放置到阻塞状态,以获得固定数量的滴答中断。 任务处于阻塞状态时不使用任何处理时间,因此任务只在实际有工作要做时使用处理时间。
参数xTicksToDelay
测试如下
void Func_usar1(void const * argument)
{
/* USER CODE BEGIN Func_usar1 */
char *pcTaskName;
const TickType_t xDelay250ms = pdMS_TO_TICKS( 250 );
/* Infinite loop */
pcTaskName = (char * )argument;
for(;;)
{
HAL_UART_Transmit(&huart1,(uint8_t *)pcTaskName,strlen(pcTaskName),0xffff);
vTaskDelay( xDelay250ms );
// osDelay(1000);
}
}
void StartTask04(void const * argument)
{
/* USER CODE BEGIN StartTask04 */
char *pcTaskName;
const TickType_t xDelay250ms = pdMS_TO_TICKS( 250 );
/* Infinite loop */
pcTaskName = (char * )argument;
for(;;)
{
HAL_UART_Transmit(&huart1,(uint8_t *)pcTaskName,strlen(pcTaskName),0xffff);
vTaskDelay( xDelay250ms );
// osDelay(1000);
}
/* USER CODE END StartTask04 */
}
pxPreviousWakeTime
任务以固定的频率周期性地执行
xTimeIncrement
pdMS_TO_TICKS()可用于将以毫秒为单位指定的时间转换为以滴答为单位指定的时间
API 函数 vTaskDelayUntil()可以
用于实现一个固定执行周期的需求(当你需要让你的任务以固定频率周期性执行的时
候)。由于调用此函数的任务解除阻塞的时间是绝对时刻
vTaskDelayUntil()比调用 vTaskDelay()可以实现更精确的周期性
案例:
osThreadDef(Task_Usar1, Func_usar1, 1, 0, 128);
Task_Usar1Handle = osThreadCreate(osThread(Task_Usar1), (void*)pcTask1Name);
osThreadDef(Task_Usar2, Func_usar1, 1, 0, 128);
Task_Usar1Handle = osThreadCreate(osThread(Task_Usar1), (void*)pcTask2Name);
/* definition and creation of myTask04 */
osThreadDef(myTask04, StartTask04, 0, 0, 128);
myTask04Handle = osThreadCreate(osThread(myTask04), NULL);
void Func_usar1(void const * argument)
{
/* USER CODE BEGIN Func_usar1 */
char *pcTaskName;
/* Infinite loop */
pcTaskName = (char * )argument;
for(;;)
{
HAL_UART_Transmit(&huart1,(uint8_t *)pcTaskName,strlen(pcTaskName),0xffff);
osDelay(20);
}
/* USER CODE END Func_usar1 */
}
/* USER CODE BEGIN Header_StartTask04 */
/**
* @brief Function implementing the myTask04 thread.
* @param argument: Not used
* @retval None
*/
/* USER CODE END Header_StartTask04 */
void StartTask04(void const * argument)
{
/* USER CODE BEGIN StartTask04 */
static char *pcTask2Name = "Periodic task is running\r\n";
TickType_t xLastWakeTime;
xLastWakeTime = xTaskGetTickCount();
/* Infinite loop */
for(;;)
{
// HAL_UART_Transmit(&huart1,(uint8_t *)xLastWakeTime,4,0xffff);
HAL_UART_Transmit(&huart1,(uint8_t *)pcTask2Name,strlen(pcTask2Name),0xffff);
vTaskDelayUntil( &xLastWakeTime, pdMS_TO_TICKS( 250 ) );
}
/* USER CODE END StartTask04 */
}
用 vTaskStartScheduler()时,调度器会自动创建一个空闲任务,**空闲任务拥有最低优先级(优先级 0)**以保证其不会妨碍具有更高优先级的应用任务进入运行态——当然,没有任何限制说是不能把应用任务创建在与空闲任务相同的优先级上;如果需要的话,你一样可以和空闲任务一起共享优先级
首先先介绍两个函数
void vTaskPrioritySet( TaskHandle_t pxTask, UBaseType_t uxNewPriority );
宏定义开启INCLUDE_vTaskPrioritySet = 1才能使用
UBaseType_t uxTaskPriorityGet( TaskHandle_t pxTask );
宏定义开启INCLUDE_uxTaskPriorityGet = 1才能使用
案例如下
osThreadDef(Task_Usar1, Func_usar1, osPriorityNormal, 0, 128);
Task_Usar1Handle = osThreadCreate(osThread(Task_Usar1), (void*)pcTask1Name);
/* definition and creation of myTask04 */
osThreadDef(myTask04, StartTask04,osPriorityNormal , 0, 128);
myTask04Handle = osThreadCreate(osThread(myTask04), (void*)pcTask2Name);
void Func_usar1(void const * argument)
{
/* USER CODE BEGIN Func_usar1 */
char *pcTaskName;
static char *string = "About to raise the Task 2 priority\r\n";
UBaseType_t uxPriority;
uxPriority = uxTaskPriorityGet( NULL );
/* Infinite loop */
pcTaskName = (char * )argument;
for(;;)
{
HAL_UART_Transmit(&huart1,(uint8_t *)pcTaskName,strlen(pcTaskName),0xffff);
HAL_UART_Transmit(&huart1,(uint8_t *)string,strlen(string),0xffff);
vTaskPrioritySet( myTask04Handle, ( uxPriority + 1 ) );
// osDelay(20);
}
/* USER CODE END Func_usar1 */
}
/* USER CODE BEGIN Header_StartTask04 */
/**
* @brief Function implementing the myTask04 thread.
* @param argument: Not used
* @retval None
*/
/* USER CODE END Header_StartTask04 */
void StartTask04(void const * argument)
{
/* USER CODE BEGIN StartTask04 */
UBaseType_t uxPriority;
static char *string = "About to lower the Task 2 priority\r\n";
char *pcTaskName;
uxPriority = uxTaskPriorityGet( NULL );
pcTaskName = (char * )argument;
/* Infinite loop */
for(;;)
{
// HAL_UART_Transmit(&huart1,(uint8_t *)xLastWakeTime,4,0xffff);
HAL_UART_Transmit(&huart1,(uint8_t *)pcTaskName,strlen(pcTaskName),0xffff);
HAL_UART_Transmit(&huart1,(uint8_t *)string,strlen(string),0xffff);
vTaskPrioritySet( NULL, ( uxPriority - 2 ) );
}
/* USER CODE END StartTask04 */
}
这边会在任务一和任务二之间相互切换
每个任务都可以通过简单地使用NULL来查询和设置自己的优先级
uxPriority = uxTaskPriorityGet( NULL );
宏定义INCLUDE_vTaskDelete = 1才能使用
删除的任务不再存在,不能再次进入运行状态
空闲任务的责任是释放分配给此后被删除的任务的内存。
案例说明:
案例如下
osThreadDef(Task_Usar1, Func_usar1, osPriorityNormal, 0, 128);
Task_Usar1Handle = osThreadCreate(osThread(Task_Usar1), (void*)pcTask1Name);
/* definition and creation of myTask04 */
// osThreadDef(myTask04, StartTask04,osPriorityNormal , 0, 128);
// myTask04Handle = osThreadCreate(osThread(myTask04), (void*)pcTask2Name);
void Func_usar1(void const * argument)
{
/* USER CODE BEGIN Func_usar1 */
char *pcTaskName;
const TickType_t xDelay100ms = pdMS_TO_TICKS( 100UL );
/* Infinite loop */
pcTaskName = (char * )argument;
for(;;)
{
HAL_UART_Transmit(&huart1,(uint8_t *)pcTaskName,strlen(pcTaskName),0xffff);
osThreadDef(myTask04, StartTask04,osPriorityNormal , 0, 128);
myTask04Handle = osThreadCreate(osThread(myTask04), (void*)pcTask2Name);
vTaskDelay( xDelay100ms );
// osDelay(20);
}
/* USER CODE END Func_usar1 */
}
/* USER CODE BEGIN Header_StartTask04 */
/**
* @brief Function implementing the myTask04 thread.
* @param argument: Not used
* @retval None
*/
/* USER CODE END Header_StartTask04 */
void StartTask04(void const * argument)
{
/* USER CODE BEGIN StartTask04 */
static char *string = "Task 2 is running and about to delete itself\r\n";
char *pcTaskName;
pcTaskName = (char * )argument;
/* Infinite loop */
for(;;)
{
// HAL_UART_Transmit(&huart1,(uint8_t *)pcTaskName,strlen(pcTaskName),0xffff);
HAL_UART_Transmit(&huart1,(uint8_t *)string,strlen(string),0xffff);
vTaskDelete( NULL );
}
/* USER CODE END StartTask04 */
}
调度器调度的是就绪状态而不是阻塞或者是挂起的任务,调度程序将始终选择优先级最高的就绪状态任务进入运行状态。
调度算法修改可修改以下宏定义
该设置将内核配置为使用具有时间分割的优先
术语 | 定义 |
---|---|
固定优先级 | 被描述为“固定优先级”的调度算法不会改变分配给正在调度的任务的优先级,但也不会阻止任务本身改变自己的优先级或其他任务的优先级 |
抢占式 | 优先级高于运行状态任务的任务进入就绪状态,则抢占式调度算法将立即“抢占”运行状态任务 |
时间切片 | 时间切片用于在同等优先级的任务之间共享处理时间,即使任务没有显式屈服或进入阻塞状态。 使用“时间切片”描述的调度算法将选择一个新任务,在每个时间片的末尾进入运行状态,如果有其他准备状态任务具有与运行任务相同的优先级。 时间片等于两个RTOS滴答中断之间的时间。 |
26和图27演示了在使用具有时间切片算法的固定优先级抢占式调度时如何调度任务。 图26显示了当应用程序中的所有任务具有唯一优先级时,选择任务进入运行状态的顺序。 图27显示了当应用程序中的两个任务共享优先级时,选择任务进入运行状态的顺序。
如果configIDLE_SHOULD_YIELD设置为0,则空闲任务将保持其整个时间片的运行状态,除非它被更高优先级的任务抢占。
空闲任务和任务2: 都是连续处理任务,两者的优先级都为0(尽可能低的优先级)。 调度程序只在没有能够运行的优先级更高的任务时,将处理时间分配给优先级0任务,并通过时间切片共享分配给优先级0任务的时间。 在每个滴答中断上启动一个新的时间片,图27中的时间是t1、t2、t3、t4、t5、t8、t9、t10和t11。 空闲任务和任务2依次进入运行状态,这可能导致两个任务在同一时间片的一部分处于运行状态,就像在时间T5和时间T8之间发生的那样。****!!!!
任务1的优先级高于空闲优先级。 任务1是一个事件驱动的任务,它的大部分时间都在阻塞状态中等待它感兴趣的事件,每次事件发生时从阻塞状态过渡到就绪状态。 感兴趣的事件发生在时间t6,因此在t6,Task1成为能够运行的最高优先级任务,因此Task1通过时间片预先提示空闲任务部分。 事件的处理在时间t7完成,此时Task1重新进入阻塞状态
configIDLE_SHOULD_YIELD 设置为1,然后空闲任务将在其循环的每次迭代中产生(自愿放弃其分配时间片的任何剩余),如果在就绪状态下有其他空闲优先级任务。这边任务2在就绪状态,所以运行
在空闲任务之后选择进入运行状态的任务不执行整个时间片,而是执行空闲任务产生的时间片的任何剩余
没有时间切片的优先抢占调度保持与上一节中描述的相同的任务选择和抢占算法,但不使用时间切片在同等优先级的任务之间共享处理时间。
如果不使用时间切片,那么调度程序将只选择一个新任务进入运行状态,当两者之一:
总结:抢占式不分时片方式,绝对的抢占式,相同等级任务就轮流执行。
本书的重点是抢占式调度为主,但是也可以使用协助式调度。
使用合作调度程序,只有当运行状态任务进入阻塞状态时才会发生任务切换,或者运行状态任务通过**手动请求重新调度taskYIELD().**任务永远不会被抢占,所以不能使用时间切片。
当使用合作(协助)调度器时,通常比使用先发制人调度器时更容易避免由同时访问引起的问题:
例如:协助式确保任务1在将其整个字符串写入UART之前不会离开运行状态,并在这样做时,消除字符串被另一个任务的激活损坏的可能性。
当使用抢占式调度程序时,**调度程序将立即开始运行任务,该任务将成为最高优先级就绪状态任务。 这在实时系统中往往是必不可少的,**这些系统必须在规定的时间内对高度优先事件作出反应。
当使用合作调度程序时,将切换到已成为最高优先级的就绪状态任务,直到运行状态任务进入阻塞状态或调用任务YIELD()才会执行。