目录
基于STM32F103RCT6
1、实验原理
FreeRTOS 支持多个任务同时拥有一个优先级,一个任务运行一个时间片(一个时 钟节拍的长度)后让出 CPU 的使用权,让拥有同优先级的下一个任务运行。FreeRTOS 中的这种调度方法就是时间片调度。
图 9.6.1 展示了运行在同一优先级下的执行时间图,在优先级 N 下有 3 个就绪的任务。
1、任务 3 正在运行。
2、这时一个时钟节拍中断(滴答定时器中断)发生,任务 3 的时间片用完,但是任务 3 还 没有执行完。
3、FreeRTOS 将任务切换到任务 1,任务 1 是优先级 N 下的下一个就绪任务。
4、任务 1 连续运行至时间片用完。
5、任务 3 再次获取到 CPU 使用权,接着运行。
6、任务 3 运行完成,调用任务切换函数 portYIELD()强行进行任务切换放弃剩余的时间片, 从而使优先级 N 下的下一个就绪的任务运行。
7、FreeRTOS 切换到任务 1。
8、任务 1 执行完其时间片。
1、同等优先级任务,轮流执行;时间片流转
2、一个时间片大小,取决为滴答定时器中断频率
3、注意没有用完的时间片不会再使用,下次任务Task3得到执行还是按照一个时间片的时钟节拍运行
要使用时间片调度的话宏 configUSE_PREEMPTION 和宏 configUSE_TIME_SLICING 必须 为 1。时间片的长度由宏 configTICK_RATE_HZ 来确定,一个时间片的长度就是滴答定时器的 中断周期,比如本教程中 configTICK_RATE_HZ 为 1000,那么一个时间片的长度就是 1ms。时间片调度发生在滴答定时器的中断服务函数中,前面讲解滴答定时器中断服务函数的时候说了 在中断服务函数 SysTick_Handler()中会调用 FreeRTOS 的 API 函数 xPortSysTickHandler(),而函 数 xPortSysTickHandler() 会 引 发 任 务 调 度 , 但 是 这 个 任 务 调 度 是 有 条 件 的 , 函 数 xPortSysTickHandler()如下:
void xPortSysTickHandler( void )
{
vPortRaiseBASEPRI();
{
if( xTaskIncrementTick() != pdFALSE )
{
portNVIC_INT_CTRL_REG = portNVIC_PENDSVSET_BIT;
}
}
vPortClearBASEPRIFromISR();
}
查看函数 xTaskIncrementTick()会发现有如下条件编译语句:
BaseType_t xTaskIncrementTick(void)
{
TCB_t *pxTCB;
TickType_t xItemValue;
BaseType_t xSwitchRequired = pdFALSE;
if (uxSchedulerSuspended == (UBaseType_t)pdFALSE)
{
/***************************************************************************/
/***************************此处省去一大堆代码******************************/
/***************************************************************************/
#if ((configUSE_PREEMPTION == 1) && (configUSE_TIME_SLICING == 1))(1)
{
if (listCURRENT_LIST_LENGTH(&(
pxReadyTasksLists[pxCurrentTCB->uxPriority])) > (UBaseType_t)1)
(2)
{
xSwitchRequired = pdTRUE;
(3)
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
#endif /* ( ( configUSE_PREEMPTION == 1 ) && ( configUSE_TIME_SLICING == 1 ) )
}
return xSwitchRequired;
}
(1)、当宏 configUSE_PREEMPTION 和宏 configUSE_PREEMPTION 都为 1 的时候下面的 代码才会编译。所以要想使用时间片调度的话这这两个宏都必须为 1,缺一不可!
(2)、判断当前任务所对应的优先级下是否还有其他的任务。
(3)、如果当前任务所对应的任务优先级下还有其他的任务那么就返回 pdTRUE。 从上面的代码可以看出,如果当前任务所对应的优先级下有其他的任务存在,那么函数 xTaskIncrementTick() 就 会 返 回 pdTURE , 由 于 函 数 返 回 值 为 pdTURE 因 此 函 数 xPortSysTickHandler()就会进行一次任务切换。
2、时间片调度实验
1、实验目的
学习使用 FreeRTOS 的时间片调度。
2、实验设计
本实验设计三个任务:start_task、task1_task 和 task2_task ,其中 task1_task 和 task2_task 的任务优先级相同,都为 2,这三个任务的任务功能如下:
start_task:用来创建其他 2 个任务。
task1_task :控制 LED0 灯闪烁,并且通过串口打印 task1_task 的运行次数。
task2_task :通过串口打印 task2_task 的运行次数。
3、实验程序与分析
为了观察方便,将系统的时钟节拍频率设置为 20,也就是将宏 configTICK_RATE_HZ 设置 为 20:
#define configTICK_RATE_HZ (20)
这样设置以后滴答定时器的中断周期就是 50ms 了,也就是说时间片值为 50ms,这个时间 片还是很大的,不过大一点我们到时候观察的时候方便。
任务设置
//任务优先级
#define START_TASK_PRIO 1
//任务堆栈大小
#define START_STK_SIZE 128
//任务句柄
TaskHandle_t StartTask_Handler;
//任务函数
void start_task(void *pvParameters);
//任务优先级
#define TASK1_TASK_PRIO 2
//任务堆栈大小
#define TASK1_STK_SIZE 128
//任务句柄
TaskHandle_t Task1Task_Handler;
//任务函数
void task1_task(void *pvParameters);
//任务优先级
#define TASK2_TASK_PRIO 2
//任务堆栈大小
#define TASK2_STK_SIZE 128
//任务句柄
TaskHandle_t Task2Task_Handler;
//任务函数
void task2_task(void *pvParameters);
(1)和(2)、任务 task1_task 和 task2_task 的任务优先级设置为相同的,这里都设置为 2。
main函数
int main(void)
{
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_4);//设置系统中断优先级分组4
delay_init(); //延时函数初始化
uart_init(115200); //初始化串口
LED_Init(); //初始化LED
LCD_Init(); //初始化LCD
//创建开始任务
xTaskCreate((TaskFunction_t )start_task, //任务函数
(const char* )"start_task", //任务名称
(uint16_t )START_STK_SIZE, //任务堆栈大小
(void* )NULL, //传递给任务函数的参数
(UBaseType_t )START_TASK_PRIO, //任务优先级
(TaskHandle_t* )&StartTask_Handler); //任务句柄
vTaskStartScheduler(); //开启任务调度
}
//开始任务任务函数
void start_task(void *pvParameters)
{
taskENTER_CRITICAL(); //进入临界区
//创建TASK1任务
xTaskCreate((TaskFunction_t )task1_task,
(const char* )"task1_task",
(uint16_t )TASK1_STK_SIZE,
(void* )NULL,
(UBaseType_t )TASK1_TASK_PRIO,
(TaskHandle_t* )&Task1Task_Handler);
//创建TASK2任务
xTaskCreate((TaskFunction_t )task2_task,
(const char* )"task2_task",
(uint16_t )TASK2_STK_SIZE,
(void* )NULL,
(UBaseType_t )TASK2_TASK_PRIO,
(TaskHandle_t* )&Task2Task_Handler);
vTaskDelete(StartTask_Handler); //删除开始任务
taskEXIT_CRITICAL(); //退出临界区
}
//task1任务函数
void task1_task(void *pvParameters)
{
u32 task1_num=0;
while(1)
{
task1_num++; //任务1执行次数加1 注意task1_num1加到255的时候会清零!!
LED0=!LED0;
taskENTER_CRITICAL(); //进入临界区
printf("任务1已经执行:%d次\r\n",task1_num);
taskEXIT_CRITICAL(); //退出临界区
delay_xms(10); //延时10ms,模拟任务运行10ms,此函数不会引起任务调度
}
}
//task2任务函数
void task2_task(void *pvParameters)
{
u32 task2_num=0;
while(1)
{
task2_num++; //任务2执行次数加1 注意task2_num1加到255的时候会清零!!
//LED1=!LED1;
taskENTER_CRITICAL(); //进入临界区
printf("任务2已经执行:%d次\r\n",task2_num);
taskEXIT_CRITICAL(); //退出临界区
delay_xms(10); //延时10ms,模拟任务运行10ms,此函数不会引起任务调度
}
}
调用函数 delay_xms()延时 10ms。在一个时间片内如果任务不主动放弃 CPU 使用权的 话那么就会一直运行这一个任务,直到时间片耗尽。在 task1_task 任务中我们通过串口打印字 符串的方式提示 task1_task 在运行,但是这个过程对于 CPU 来说执行速度很快,不利于观察, 所以这里通过调用函数 delay_xms()来默认任务占用 10ms 的 CPU。函数 delay_xm()不会引起任 务调度,这样的话相当于 task1_task 的执行周期>10ms,基本可以看作等于 10ms,因为其他的 函数执行速度还是很快的。一个时间片的长度是 50ms,任务执行所需的时间以 10ms 算,理论 上在一个时间片内 task1_task 可以执行 5 次,但是事实上很少能执行 5 次,基本上是 4 次。
4、程序运行结果分析
不管是 task1_task 还是 task2_task 都是连续执行 4,5 次,和前面程 序设计的一样,说明在一个时间片内一直在运行一个任务,当时间片用完后就切换到下一个任 务运行。
链接:https://pan.baidu.com/s/1hyUr3eqajQpuSdiBg2EFng?pwd=rtos
提取码:rtos