FreeRTOS列表和列表项 & FreeRTOS任务切换


从路径中找我的Star Divine。

FreeRTOS 开发 —— 基于STM32F429

06_FreeRTOS列表和列表项

  • 要想看懂 FreeRTOS 源码并学习其原理,有一个东西绝对跑不了,那就是 FreeRTOS 的列表和列表项。
  • 列表和列表项是 FreeRTOS 的一个数据结构,FreeRTOS 大量使用到了列表和列表项, 它是 FreeRTOS 的基石。

什么是列表和列表项

列表

  • 列表是 FreeRTOS 中的一个数据结构,概念上和链表有点类似,列表被用来跟踪 FreeRTOS 中的任务。
  • 与列表相关的全部东西都在文件 list.c 和 list.h 中,在 list.h 中定义了一个叫 List_t 的 结构体:

列表项

  • 列表项就是存放在列表中的项目,FreeRTOS 提供了两种列表项:列表项迷你列表项。这两个都在文件list.h中有定义:

列表项结构示意图

  • 图中并未列出用于列表项完整性检查的成员变量!

迷你列表项

  • 迷你列表项在文件 list.h 中有定义:

迷你列表项结构示意图

  • 注意!图中并未列出用于迷你列表项完整性检查的成员变量!

列表和列表项的初始化

列表初始化

列表初始化图

列表项初始化

列表项的插入

列表项插入函数分析

列表项插入过程图示

  • 上一节我们分析了列表项插入函数vListInsert(),本小节我们就通过图片来演示一下这个插入过程,本小节我们会向一个空的列表中插入三个列表项,这三个列表项的值分别为40,60和 50。

插入值为 40 的列表项

插入值为 60 的列表项

插入值为 50 的列表项

列表项末尾插入

列表项末尾插入函数分析

列表项末尾插入图示

  • 跟函数vListInsert()一样,我们也用图片来看一下函数vListInsertEnd()的插入过程。

默认列表

  • 在插入列表项之前我们先准备一个默认列表:
  • 注意图中列表的pxIndex所指向的列表项,这里为ListItem1,不再是xListEnd了。

插入值为 50 的列表项

  • 在上面的列表中插入一个值为 50 的列表项ListItem3,插入完成以后如图所示:
  • 列表ListpxIndex指向列表项 ListItem1,因此调用函数vListInsertEnd()插入ListItem3的话就会在ListItem1的前面插入。

列表项的删除

  • 有列表项的插入,那么必然有列表项的删除,列表项的删除通过函数uxListRemove()来完成,函数原型如下:
UBaseType_t uxListRemove( ListItem_t * const pxItemToRemove )

列表项的遍历

列表项的插入和删除实验

实验设计

程序代码(main.c)

#include "sys.h"
#include "delay.h"
#include "usart.h"
#include "led.h"
#include "lcd.h"
#include "sdram.h"
#include "key.h"
#include "FreeRTOS.h"
#include "task.h"

//任务优先级
#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 LIST_TASK_PRIO        3
//任务堆栈大小    
#define LIST_STK_SIZE         128  
//任务句柄
TaskHandle_t ListTask_Handler;
//任务函数
void list_task(void *pvParameters);

//定义一个测试用的列表和3个列表项
List_t TestList;        //测试用列表
ListItem_t ListItem1;    //测试用列表项1
ListItem_t ListItem2;    //测试用列表项2
ListItem_t ListItem3;    //测试用列表项3

int main(void)
{
    HAL_Init();                     //初始化HAL库   
    Stm32_Clock_Init(360,25,2,8);   //设置时钟,180Mhz
    delay_init(180);                //初始化延时函数
    uart_init(115200);              //初始化串口
    LED_Init();                     //初始化LED 
    KEY_Init();                        //初始化按键
    SDRAM_Init();                    //初始化SDRAM
    LCD_Init();                        //初始化LCD
    
    POINT_COLOR = RED;
    LCD_ShowString(30,10,200,16,16,"Apollo STM32F4/F7");    
    LCD_ShowString(30,30,200,16,16,"FreeRTOS Examp 7-1");
    LCD_ShowString(30,50,200,16,16,"list and listItem");
    LCD_ShowString(30,70,200,16,16,"ATOM@ALIENTEK");
    LCD_ShowString(30,90,200,16,16,"2024/4/27");
    //创建开始任务
    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);   
    //创建LIST任务
    xTaskCreate((TaskFunction_t )list_task,     
                (const char*    )"list_task",   
                (uint16_t       )LIST_STK_SIZE,
                (void*          )NULL,
                (UBaseType_t    )LIST_TASK_PRIO,
                (TaskHandle_t*  )&ListTask_Handler); 
    vTaskDelete(StartTask_Handler); //删除开始任务
    taskEXIT_CRITICAL();            //退出临界区
}

//task1任务函数
void task1_task(void *pvParameters)
{
    while(1)
    {
        LED0=!LED0;
        vTaskDelay(500);                           //延时500ms,也就是500个时钟节拍    
    }
}

//list任务函数
void list_task(void *pvParameters)
{
    //第一步:初始化列表和列表项
    vListInitialise(&TestList);
    vListInitialiseItem(&ListItem1);
    vListInitialiseItem(&ListItem2);
    vListInitialiseItem(&ListItem3);
    
    ListItem1.xItemValue=40;            //ListItem1列表项值为40
    ListItem2.xItemValue=60;            //ListItem2列表项值为60
    ListItem3.xItemValue=50;            //ListItem3列表项值为50
    
    //第二步:打印列表和其他列表项的地址
    printf("/*******************列表和列表项地址*******************/\r\n");
    printf("项目                              地址                    \r\n");
    printf("TestList                          %#x                    \r\n",(int)&TestList);
    printf("TestList->pxIndex                 %#x                    \r\n",(int)TestList.pxIndex);
    printf("TestList->xListEnd                %#x                    \r\n",(int)(&TestList.xListEnd));
    printf("ListItem1                         %#x                    \r\n",(int)&ListItem1);
    printf("ListItem2                         %#x                    \r\n",(int)&ListItem2);
    printf("ListItem3                         %#x                    \r\n",(int)&ListItem3);
    printf("/************************结束**************************/\r\n");
    printf("按下KEY_UP键继续!\r\n\r\n\r\n");
    while(KEY_Scan(0)!=WKUP_PRES) delay_ms(10);                    //等待KEY_UP键按下
    
    //第三步:向列表TestList添加列表项ListItem1,并通过串口打印所有
    //列表项中成员变量pxNext和pxPrevious的值,通过这两个值观察列表
    //项在列表中的连接情况。
    vListInsert(&TestList,&ListItem1);        //插入列表项ListItem1
    printf("/******************添加列表项ListItem1*****************/\r\n");
    printf("项目                              地址                    \r\n");
    printf("TestList->xListEnd->pxNext        %#x                    \r\n",(int)(TestList.xListEnd.pxNext));
    printf("ListItem1->pxNext                 %#x                    \r\n",(int)(ListItem1.pxNext));
    printf("/*******************前后向连接分割线********************/\r\n");
    printf("TestList->xListEnd->pxPrevious    %#x                    \r\n",(int)(TestList.xListEnd.pxPrevious));
    printf("ListItem1->pxPrevious             %#x                    \r\n",(int)(ListItem1.pxPrevious));
    printf("/************************结束**************************/\r\n");
    printf("按下KEY_UP键继续!\r\n\r\n\r\n");
    while(KEY_Scan(0)!=WKUP_PRES) delay_ms(10);                    //等待KEY_UP键按下
    
    //第四步:向列表TestList添加列表项ListItem2,并通过串口打印所有
    //列表项中成员变量pxNext和pxPrevious的值,通过这两个值观察列表
    //项在列表中的连接情况。
    vListInsert(&TestList,&ListItem2);    //插入列表项ListItem2
    printf("/******************添加列表项ListItem2*****************/\r\n");
    printf("项目                              地址                    \r\n");
    printf("TestList->xListEnd->pxNext        %#x                    \r\n",(int)(TestList.xListEnd.pxNext));
    printf("ListItem1->pxNext                 %#x                    \r\n",(int)(ListItem1.pxNext));
    printf("ListItem2->pxNext                 %#x                    \r\n",(int)(ListItem2.pxNext));
    printf("/*******************前后向连接分割线********************/\r\n");
    printf("TestList->xListEnd->pxPrevious    %#x                    \r\n",(int)(TestList.xListEnd.pxPrevious));
    printf("ListItem1->pxPrevious             %#x                    \r\n",(int)(ListItem1.pxPrevious));
    printf("ListItem2->pxPrevious             %#x                    \r\n",(int)(ListItem2.pxPrevious));
    printf("/************************结束**************************/\r\n");
    printf("按下KEY_UP键继续!\r\n\r\n\r\n");
    while(KEY_Scan(0)!=WKUP_PRES) delay_ms(10);                    //等待KEY_UP键按下
    
    //第五步:向列表TestList添加列表项ListItem3,并通过串口打印所有
    //列表项中成员变量pxNext和pxPrevious的值,通过这两个值观察列表
    //项在列表中的连接情况。
    vListInsert(&TestList,&ListItem3);    //插入列表项ListItem3
    printf("/******************添加列表项ListItem3*****************/\r\n");
    printf("项目                              地址                    \r\n");
    printf("TestList->xListEnd->pxNext        %#x                    \r\n",(int)(TestList.xListEnd.pxNext));
    printf("ListItem1->pxNext                 %#x                    \r\n",(int)(ListItem1.pxNext));
    printf("ListItem3->pxNext                 %#x                    \r\n",(int)(ListItem3.pxNext));
    printf("ListItem2->pxNext                 %#x                    \r\n",(int)(ListItem2.pxNext));
    printf("/*******************前后向连接分割线********************/\r\n");
    printf("TestList->xListEnd->pxPrevious    %#x                    \r\n",(int)(TestList.xListEnd.pxPrevious));
    printf("ListItem1->pxPrevious             %#x                    \r\n",(int)(ListItem1.pxPrevious));
    printf("ListItem3->pxPrevious             %#x                    \r\n",(int)(ListItem3.pxPrevious));
    printf("ListItem2->pxPrevious             %#x                    \r\n",(int)(ListItem2.pxPrevious));
    printf("/************************结束**************************/\r\n");
    printf("按下KEY_UP键继续!\r\n\r\n\r\n");
    while(KEY_Scan(0)!=WKUP_PRES) delay_ms(10);                    //等待KEY_UP键按下
    
    //第六步:删除ListItem2,并通过串口打印所有列表项中成员变量pxNext和
    //pxPrevious的值,通过这两个值观察列表项在列表中的连接情况。
    uxListRemove(&ListItem2);                        //删除ListItem2
    printf("/******************删除列表项ListItem2*****************/\r\n");
    printf("项目                              地址                    \r\n");
    printf("TestList->xListEnd->pxNext        %#x                    \r\n",(int)(TestList.xListEnd.pxNext));
    printf("ListItem1->pxNext                 %#x                    \r\n",(int)(ListItem1.pxNext));
    printf("ListItem3->pxNext                 %#x                    \r\n",(int)(ListItem3.pxNext));
    printf("/*******************前后向连接分割线********************/\r\n");
    printf("TestList->xListEnd->pxPrevious    %#x                    \r\n",(int)(TestList.xListEnd.pxPrevious));
    printf("ListItem1->pxPrevious             %#x                    \r\n",(int)(ListItem1.pxPrevious));
    printf("ListItem3->pxPrevious             %#x                    \r\n",(int)(ListItem3.pxPrevious));
    printf("/************************结束**************************/\r\n");
    printf("按下KEY_UP键继续!\r\n\r\n\r\n");
    while(KEY_Scan(0)!=WKUP_PRES) delay_ms(10);                    //等待KEY_UP键按下
    
    //第七步:删除ListItem2,并通过串口打印所有列表项中成员变量pxNext和
    //pxPrevious的值,通过这两个值观察列表项在列表中的连接情况。
    TestList.pxIndex=TestList.pxIndex->pxNext;            //pxIndex向后移一项,这样pxIndex就会指向ListItem1。
    vListInsertEnd(&TestList,&ListItem2);                //列表末尾添加列表项ListItem2
    printf("/***************在末尾添加列表项ListItem2***************/\r\n");
    printf("项目                              地址                    \r\n");
    printf("TestList->pxIndex                 %#x                    \r\n",(int)TestList.pxIndex);
    printf("TestList->xListEnd->pxNext        %#x                    \r\n",(int)(TestList.xListEnd.pxNext));
    printf("ListItem2->pxNext                 %#x                    \r\n",(int)(ListItem2.pxNext));
    printf("ListItem1->pxNext                 %#x                    \r\n",(int)(ListItem1.pxNext));
    printf("ListItem3->pxNext                 %#x                    \r\n",(int)(ListItem3.pxNext));
    printf("/*******************前后向连接分割线********************/\r\n");
    printf("TestList->xListEnd->pxPrevious    %#x                    \r\n",(int)(TestList.xListEnd.pxPrevious));
    printf("ListItem2->pxPrevious             %#x                    \r\n",(int)(ListItem2.pxPrevious));
    printf("ListItem1->pxPrevious             %#x                    \r\n",(int)(ListItem1.pxPrevious));
    printf("ListItem3->pxPrevious             %#x                    \r\n",(int)(ListItem3.pxPrevious));
    printf("/************************结束**************************/\r\n\r\n\r\n");
    while(1)
    {
        LED1=!LED1;
        vTaskDelay(1000);                           //延时1s,也就是1000个时钟节拍    
    }
}

调试结果

'-?LCD ID:9341
    /*******************列表和列表项地址*******************/
    项目                              地址                    
    TestList                          0x200000bc                    
    TestList->pxIndex                 0x200000c4                    
    TestList->xListEnd                0x200000c4                    
    ListItem1                         0x200000d0                    
    ListItem2                         0x200000e4                    
    ListItem3                         0x200000f8                    
    /************************结束**************************/
    按下KEY_UP键继续!


    /******************添加列表项ListItem1*****************/
    项目                              地址                    
    TestList->xListEnd->pxNext        0x200000d0                    
    ListItem1->pxNext                 0x200000c4                    
    /*******************前后向连接分割线********************/
    TestList->xListEnd->pxPrevious    0x200000d0                    
    ListItem1->pxPrevious             0x200000c4                    
    /************************结束**************************/
    按下KEY_UP键继续!


    /******************添加列表项ListItem2*****************/
    项目                              地址                    
    TestList->xListEnd->pxNext        0x200000d0                    
    ListItem1->pxNext                 0x200000e4                    
    ListItem2->pxNext                 0x200000c4                    
    /*******************前后向连接分割线********************/
    TestList->xListEnd->pxPrevious    0x200000e4                    
    ListItem1->pxPrevious             0x200000c4                    
    ListItem2->pxPrevious             0x200000d0                    
    /************************结束**************************/
    按下KEY_UP键继续!


    /******************添加列表项ListItem3*****************/
    项目                              地址                    
    TestList->xListEnd->pxNext        0x200000d0                    
    ListItem1->pxNext                 0x200000f8                    
    ListItem3->pxNext                 0x200000e4                    
    ListItem2->pxNext                 0x200000c4                    
    /*******************前后向连接分割线********************/
    TestList->xListEnd->pxPrevious    0x200000e4                    
    ListItem1->pxPrevious             0x200000c4                    
    ListItem3->pxPrevious             0x200000d0                    
    ListItem2->pxPrevious             0x200000f8                    
    /************************结束**************************/
    按下KEY_UP键继续!


    /******************删除列表项ListItem2*****************/
    项目                              地址                    
    TestList->xListEnd->pxNext        0x200000d0                    
    ListItem1->pxNext                 0x200000f8                    
    ListItem3->pxNext                 0x200000c4                    
    /*******************前后向连接分割线********************/
    TestList->xListEnd->pxPrevious    0x200000f8                    
    ListItem1->pxPrevious             0x200000c4                    
    ListItem3->pxPrevious             0x200000d0                    
    /************************结束**************************/
    按下KEY_UP键继??


    /***************在末尾添加列表项ListItem2***************/
    项目                              地址                    
    TestList->pxIndex                 0x200000d0                    
    TestList->xListEnd->pxNext        0x200000e4                    
    ListItem2->pxNext                 0x200000d0                    
    ListItem1->pxNext                 0x200000f8                    
    ListItem3->pxNext                 0x200000c4                    
    /*******************前后向连接分割线********************/
    TestList->xListEnd->pxPrevious    0x200000f8                    
    ListItem2->pxPrevious             0x200000c4                    
    ListItem1->pxPrevious             0x200000e4                    
    ListItem3->pxPrevious             0x200000d0                    
    /************************结束**************************/

第一步和第二步

  • 第一步和第二步是用来初始化列表和列表项的,并且通过串口输出列表和列表项的地址,这一步是开发板复位后默认运行的,串口调试助手信息如下所示:

第三步

第四步

第五步

第六步和第七步

  • 这两步是观察函数uxListRemove()vListInsertEnd()的运行过程的,分析过程和前五步一样。

07_FreeRTOS调度器开启和任务相关函数详解

  • 略,看看文档得了。
  • 涉及到ARM的汇编指令,有关涉及到的 ARM 指令的详细使用情况请参考《权威指南》的“第 5 章 指令集”。

08_FreeRTOS任务切换

  • RTOS 系统的核心是任务管理,而任务管理的核心是任务切换,任务切换决定了任务的执行顺序,任务切换效率的高低也决定了一款系统的性能,尤其是对于实时操作系统。

PendSV 异常

  • PendSV(可挂起的系统调用)异常对 OS 操作非常重要,其优先级可以通过编程设置。
  • 可以通过将中断控制和壮态寄存器 ICSR 的 bit28,也就是 PendSV 的挂起位置 1 来触发 PendSV中断。
  • 与 SVC 异常不同,它是不精确的,因此它的挂起壮态可在更高优先级异常处理内设置,且 会在高优先级处理完成后执行。
  • 利用该特性,若将 PendSV 设置为最低的异常优先级,可以让 PendSV 异常处理在所有其他中断处理完成后执行,这对于上下文切换非常有用,也是各种 OS 设计中的关键。

上下文切换简单实例

  • 在具有嵌入式 OS 的典型系统中,处理时间被划分为了多个时间片。
  • 若系统中只有两个任务,这两个任务会交替执行:

ISR执行期间的上下文切换会延迟中断服务

  • 若中断请求(IRQ)在SysTick异常前产生,则SysTick异常可能会抢占 IRQ 的处理,在这种情况下,OS不应该执行上下文切换,否则中断请求 IRQ 处理就会被延迟,而且在真实系统中延迟时间还往往不可预知,任何有一丁点实时要求的系统都决不能容忍这种事。
  • 对于 CortexM3 和 Cortex-M4 处理器,当存在活跃的异常服务时,设计默认不允许返回到线程模式,若存在活跃中断服务,且 OS 试图返回到线程模式,则将触发用法fault

PendSV上下文切换

  • 在一些 OS 设计中,要解决这个问题,可以在运行中断服务时不执行上下文切换,此时可以检查栈帧中的压栈 xPSR 或 NVIC 中的中断活跃壮态寄存器。
  • 不过,系统的性能可能会受到影响,特别时当中断源在 SysTick 中断前后持续产生请求时,这样上下文切换可能就没有执行的机会了。
  • 为了解决这个问题,PendSV 异常将上下文切换请求延迟到所有其他 IRQ 处理都已经完成后,此时需要将 PendSV 设置为最低优先级。
  • 若 OS 需要执行上下文切换,他会设置 PendSV 的挂起壮态,并在 PendSV 异常内执行上下文切换:

图中事件的流水账记录如下:

FreeRTOS 任务切换场合

  • 可以执行一个系统调用。
  • 系统滴答定时器(SysTick)中断。

执行系统调用

  • 执行系统调用就是执行 FreeRTOS 系统提供的相关 API 函数,比如任务切换函数taskYIELD(),FreeRTOS 有些 API 函数也会调用函数taskYIELD(),这些 API 函数都会导致任务切换,这些 API 函数和任务切换函数taskYIELD()都统称为系统调用。
  • 函数taskYIELD()其实就是个宏,在文件 task.h中有如下定义:
#define taskYIELD() portYIELD()
  • 函数portYIELD()也是个宏,在文件 portmacro.h中有如下定义:

  • 中断级的任务切换函数为portYIELD_FROM_ISR(),定义如下:
#define portEND_SWITCHING_ISR( xSwitchRequired ) 
if( xSwitchRequired != pdFALSE ) portYIELD()
#define portYIELD_FROM_ISR( x ) portEND_SWITCHING_ISR( x )
  • 可以看出portYIELD_FROM_ISR()最终也是通过调用函数portYIELD()来完成任务切换的。

系统滴答定时器(SysTick)中断

  • FreeRTOS 中滴答定时器(SysTick)中断服务函数中也会进行任务切换,滴答定时器中断服务函数如下:
void SysTick_Handler(void)
{ 
    if(xTaskGetSchedulerState()!=taskSCHEDULER_NOT_STARTED)//系统已经运行
    {
        xPortSysTickHandler();
    }
    HAL_IncTick();
}
  • 在滴答定时器中断服务函数中调用了 FreeRTOS 的 API 函数xPortSysTickHandler()

API函数xPortSysTickHandler()函数源码

PendSV 中断服务函数

  • PendSV 中断服务函数本应该为PendSV_Handler(),但是 FreeRTOS 使用#define重定义了,如下:
#define xPortPendSVHandler PendSV_Handler

函数xPortPendSVHandler()源码

查找下一个要运行的任务

  • 在 PendSV 中断服务程序中有调用函数vTaskSwitchContext()来获取下一个要运行的任务,也就是查找已经就绪了的优先级最高的任务。

缩减后(去掉条件编译)的函数源码

通用方法

  • 所有的处理器都可以用的方法。

硬件方法

  • 硬件方法就是使用处理器自带的硬件指令来实现的,比如 Cortex-M 处理器就带有的计算前导 0 个数指令:CLZ
  • 硬件方法借助一个指令就可以快速的获取处于就绪态的最高优先级,但是会限制任务的优先级数。
  • 比如 STM32 只能有 32 个优先级,不过 32 个优先级已经完全够用了。
  • 要知道FreeRTOS 是支持时间片的,每个优先级可以支持无限多个任务。

FreeRTOS 时间片调度

  • 在 FreeRTOS 中允许一个任务运行一个时间片(一个时钟节拍的长度)后让出 CPU 的使用权,让拥有同优先级的下一个任务运行,至于下一个要运行哪个任务?
  • FreeRTOS 中的这种调度方法就是时间片调度。

时间片调度

  • 运行在同一优先级下的执行时间图,在优先级 N 下有 3 个就绪的任务。

任务调度是有条件的,函数xPortSysTickHandler()

  • 要使用时间片调度的话宏configUSE_PREEMPTION和宏configUSE_TIME_SLICING 必须为 1。
  • 时间片的长度由宏configTICK_RATE_HZ来确定,一个时间片的长度就是滴答定时器的中断周期。
  • 比如本教程中configTICK_RATE_HZ为 1000,那么一个时间片的长度就是 1ms。
  • 时间片调度发生在滴答定时器的中断服务函数中,在中断服务函数SysTick_Handler()中会调用 FreeRTOS 的 API 函数xPortSysTickHandler(),而函数xPortSysTickHandler()会引发任务调度 , 但是这个任务调度是有条件的,函数xPortSysTickHandler()如下:

  • 上述代码中红色部分表明只有函数 xTaskIncrementTick()的返回值不为 pdFALSE的时候就会进行任务调度!

查看函数xTaskIncrementTick()会发现有如下条件编译语句:

时间片调度实验

实验设计

完整代码(main.c)

#include "delay.h"
#include "sys.h"
//////////////////////////////////////////////////////////////////////////////////      
//如果使用OS,则包括下面的头文件即可.
#if SYSTEM_SUPPORT_OS
#include "FreeRTOS.h"                    //FreeRTOS使用     
#include "task.h"
#endif

static u32 fac_us=0;                            //us延时倍乘数

#if SYSTEM_SUPPORT_OS        
    static u16 fac_ms=0;                        //ms延时倍乘数,在os下,代表每个节拍的ms数
#endif

 
extern void xPortSysTickHandler(void);
//systick中断服务函数,使用OS时用到
void SysTick_Handler(void)
{  
    if(xTaskGetSchedulerState()!=taskSCHEDULER_NOT_STARTED)//系统已经运行
    {
        xPortSysTickHandler();    
    }
    HAL_IncTick();
}
               
//初始化延迟函数
//当使用ucos的时候,此函数会初始化ucos的时钟节拍
//SYSTICK的时钟固定为AHB时钟
//SYSCLK:系统时钟频率
void delay_init(u8 SYSCLK)
{
    u32 reload;
    HAL_SYSTICK_CLKSourceConfig(SYSTICK_CLKSOURCE_HCLK);//SysTick频率为HCLK
    fac_us=SYSCLK;                            //不论是否使用OS,fac_us都需要使用
    reload=SYSCLK;                            //每秒钟的计数次数 单位为K       
    reload*=1000000/configTICK_RATE_HZ;        //根据configTICK_RATE_HZ设定溢出时间
                                            //reload为24位寄存器,最大值:16777216,在180M下,约合0.745s左右    
    fac_ms=1000/configTICK_RATE_HZ;            //代表OS可以延时的最少单位        
    SysTick->CTRL|=SysTick_CTRL_TICKINT_Msk;//开启SYSTICK中断
    SysTick->LOAD=reload;                     //每1/configTICK_RATE_HZ断一次    
    SysTick->CTRL|=SysTick_CTRL_ENABLE_Msk; //开启SYSTICK
}                                    

//延时nus
//nus:要延时的us数.    
//nus:0~190887435(最大值即2^32/fac_us@fac_us=22.5)                                           
void delay_us(u32 nus)
{        
    u32 ticks;
    u32 told,tnow,tcnt=0;
    u32 reload=SysTick->LOAD;                //LOAD的值             
    ticks=nus*fac_us;                         //需要的节拍数 
    told=SysTick->VAL;                        //刚进入时的计数器值
    while(1)
    {
        tnow=SysTick->VAL;    
        if(tnow!=told)
        {        
            if(tnow<told)tcnt+=told-tnow;    //这里注意一下SYSTICK是一个递减的计数器就可以了.
            else tcnt+=reload-tnow+told;        
            told=tnow;
            if(tcnt>=ticks)break;            //时间超过/等于要延迟的时间,则退出.
        }  
    };                                        
}  
    
//延时nms,会引起任务调度
//nms:要延时的ms数
//nms:0~65535
void delay_ms(u32 nms)
{    
    if(xTaskGetSchedulerState()!=taskSCHEDULER_NOT_STARTED)//系统已经运行
    {        
        if(nms>=fac_ms)                        //延时的时间大于OS的最少时间周期 
        { 
               vTaskDelay(nms/fac_ms);             //FreeRTOS延时
        }
        nms%=fac_ms;                        //OS已经无法提供这么小的延时了,采用普通方式延时    
    }
    delay_us((u32)(nms*1000));                //普通方式延时
}

//延时nms,不会引起任务调度
//nms:要延时的ms数
void delay_xms(u32 nms)
{
    u32 i;
    for(i=0;i<nms;i++) delay_us(1000);
}

运行测试

  • 不管是task1_task还是task2_task都是连续执行 4、5 次,和前面程序设计的一样,说明在一个时间片内一直在运行一个任务,当时间片用完后就切换到下一个任务运行。

声明:三二一的一的二|版权所有,违者必究|如未注明,均为原创|本网站采用BY-NC-SA协议进行授权

转载:转载请注明原文链接 - FreeRTOS列表和列表项 & FreeRTOS任务切换


三二一的一的二