FreeROTS原理学习笔记

前言:这仅是一篇学习笔记记录,无指导意义。想详细了解的人 可看CSDN博主「zhzht19861011」的原创文章。
FreeROTS系统:
使用习惯:
1.一般来说,都是利用下载好的例程进行修改。其中注意的文件有heap1~5.c和配置头文件FreeROTSConfig.h,利用宏定义对系统功能进行对应的裁剪。
使用过程:
2.创建任务
①使用的时候,会用到任务名(描述),任务堆栈大小,任意指针(常用于参数传递给任务),优先级(值小级别高),任务句柄(可用该句柄引用任务)参数。
②堆栈空间太小时,会直接报错误,可通过Debug调试的API接口进行监控剩余未用堆栈大小,仅用于Debug。
③创建的内容,主要包括了TCB结构体,该数据结构由列表和列表项构成,包含了任务的信息,状态(挂起,运行,就绪),优先级(可能做了大小值处理,值小优先级高,如CortexM3)等等信息。这个过程,默认是动态生成空间的,但也有静态生成的,需调用对应static的API创建函数。一般来说,任务都是一个死循环函数。

 typedef struct tskTaskControlBlock
{
    volatile StackType_t    *pxTopOfStack; /*当前堆栈的栈顶,必须位于结构体的第一项*/
  #if ( portUSING_MPU_WRAPPERS == 1 )
        xMPU_SETTINGS   xMPUSettings;      /*MPU设置,必须位于结构体的第二项*/
    #endif
 
    ListItem_t          xStateListItem; /*任务的状态列表项,以引用的方式表示任务的状态*/
    ListItem_t          xEventListItem;    /*事件列表项,用于将任务以引用的方式挂接到事件列表*/
    UBaseType_t         uxPriority;        /*保存任务优先级,0表示最低优先级*/
    StackType_t         *pxStack;           /*指向堆栈的起始位置*/
    char               pcTaskName[ configMAX_TASK_NAME_LEN ];/*任务名字*/
 
    #if ( portSTACK_GROWTH > 0 )
        StackType_t     *pxEndOfStack;     /*指向堆栈的尾部*/
    #endif
 
    #if ( portCRITICAL_NESTING_IN_TCB == 1 )
        UBaseType_t     uxCriticalNesting; /*保存临界区嵌套深度*/
    #endif
 
    #if ( configUSE_TRACE_FACILITY == 1 )
        UBaseType_t     uxTCBNumber;       /*保存一个数值,每个任务都有唯一的值*/
        UBaseType_t     uxTaskNumber;      /*存储一个特定数值*/
    #endif
 
    #if ( configUSE_MUTEXES == 1 )
        UBaseType_t     uxBasePriority;    /*保存任务的基础优先级*/
        UBaseType_t     uxMutexesHeld;
    #endif
 
    #if ( configUSE_APPLICATION_TASK_TAG == 1 )
        TaskHookFunction_t pxTaskTag;
    #endif
 
    #if( configNUM_THREAD_LOCAL_STORAGE_POINTERS > 0 )
        void *pvThreadLocalStoragePointers[configNUM_THREAD_LOCAL_STORAGE_POINTERS ];
    #endif
 
    #if( configGENERATE_RUN_TIME_STATS == 1 )
        uint32_t        ulRunTimeCounter;  /*记录任务在运行状态下执行的总时间*/
    #endif
 
    #if ( configUSE_NEWLIB_REENTRANT == 1 )
        /* 为任务分配一个Newlibreent结构体变量。Newlib是一个C库函数,并非FreeRTOS维护,FreeRTOS也不对使用结果负责。如果用户使用Newlib,必须熟知Newlib的细节*/
        struct _reent xNewLib_reent;
    #endif
 
    #if( configUSE_TASK_NOTIFICATIONS == 1 )
        volatile uint32_t ulNotifiedValue; /*与任务通知相关*/
        volatile uint8_t ucNotifyState;
    #endif
 
    #if( configSUPPORT_STATIC_ALLOCATION == 1 )
        uint8_t ucStaticAllocationFlags; /* 如果堆栈由静态数组分配,则设置为pdTRUE,如果堆栈是动态分配的,则设置为pdFALSE*/
    #endif
 
    #if( INCLUDE_xTaskAbortDelay == 1 )
        uint8_t ucDelayAborted;
    #endif
    } tskTCB;

typedef tskTCB TCB_t;
  • 3.开始任务调度
    1.任务调度之前会先创建一个空闲任务,该任务优先级最低,常用于回收任务。若是空间不足以创建空闲任务,则会执行失败,并立即返回。之后会根据任务优先级,执行当前最高优先级任务,低优先级任务进入就绪状态,同时数据进入堆栈。
    2.task.c文件中定义了一个任务TCB指针变量pxCurrentTCB,指向当前运行的任务。在任务调度之前,该指针指向优先级最高的任务,即每次创建任务都会进行一次判断,只要优先级高于pxCurrentTCB指向的任务,pxCurrentTCB就指向高优先级任务。
    3.调度器是FreeRTOS操作系统的核心,主要负责任务切换,即找出最高优先级的就绪任务,并使之获得CPU运行权。调度器并非自动运行的,需要人为启动它。
    API函数vTaskStartScheduler()用于启动调度器,它会创建一个空闲任务、初始化一些静态变量,最主要的,它会初始化系统节拍定时器并设置好相应的中断,然后启动第一个任务。
BaseType_t xPortStartScheduler( void )
{
    #if(configASSERT_DEFINED == 1 )
    {
        volatile uint32_tulOriginalPriority;
        /* 中断优先级寄存器0:IPR0 */
        volatile uint8_t * constpucFirstUserPriorityRegister = ( uint8_t * ) (portNVIC_IP_REGISTERS_OFFSET_16 +portFIRST_USER_INTERRUPT_NUMBER );
        volatile uint8_tucMaxPriorityValue;
 
        /* 这一大段代码用来确定一个最高ISR优先级,在这个ISR或者更低优先级的ISR中可以安全的调用以FromISR结尾的API函数.*/
       
        /* 保存中断优先级值,因为下面要覆写这个寄存器(IPR0) */
       ulOriginalPriority = *pucFirstUserPriorityRegister;
 
        /* 确定有效的优先级位个数. 首先向所有位写1,然后再读出来,由于无效的优先级位读出为0,然后数一数有多少个1,就能知道有多少位优先级.*/
        *pucFirstUserPriorityRegister= portMAX_8_BIT_VALUE;
       ucMaxPriorityValue = *pucFirstUserPriorityRegister;
 
        /* 冗余代码,用来防止用户不正确的设置RTOS可屏蔽中断优先级值 */
       ucMaxSysCallPriority =configMAX_SYSCALL_INTERRUPT_PRIORITY &ucMaxPriorityValue;
 
        /* 计算最大优先级组值 */
       ulMaxPRIGROUPValue =portMAX_PRIGROUP_BITS;
        while( (ucMaxPriorityValue &portTOP_BIT_OF_BYTE ) ==portTOP_BIT_OF_BYTE )
        {
           ulMaxPRIGROUPValue--;
           ucMaxPriorityValue <<= ( uint8_t ) 0x01;
        }
       ulMaxPRIGROUPValue <<=portPRIGROUP_SHIFT;
       ulMaxPRIGROUPValue &=portPRIORITY_GROUP_MASK;
 
        /* 将IPR0寄存器的值复原*/
        *pucFirstUserPriorityRegister= ulOriginalPriority;
    }
    #endif /*conifgASSERT_DEFINED */
 
    /* 将PendSV和SysTick中断设置为最低优先级*/
   portNVIC_SYSPRI2_REG |=portNVIC_PENDSV_PRI;
   portNVIC_SYSPRI2_REG |=portNVIC_SYSTICK_PRI;
 
    /* 启动系统节拍定时器,即SysTick定时器,初始化中断周期并使能定时器*/
   vPortSetupTimerInterrupt();
 
    /* 初始化临界区嵌套计数器 */
   uxCriticalNesting = 0;
 
    /* 启动第一个任务 */
   prvStartFirstTask();
 
    /* 永远不会到这里! */
    return 0;
}
  • 4.在Cortex-M3架构中,FreeRTOS为了任务启动和任务切换使用了三个异常:SVC、PendSV和SysTick。SVC(系统服务调用),有些操作系统不允许应用程序直接访问硬件,而是提供一些系统服务函数,通过SVC调用。PendSV(可挂起系统调用),用于任务切换,当有优先级高的任务就绪时,当前任务会等待高优先级任务完成后再继续当前任务。SysTick提供一个时钟片,若优先级相同,每次中断,下一个任务将会获得一个时间片的CPU时间。
    5. 启动第一个任务,调用函数 prvStartFirstTask,代码如下:
	__asm void prvStartFirstTask( void )
{
    PRESERVE8
 
    /* Cortext-M3硬件中,0xE000ED08地址处为VTOR(向量表偏移量)寄存器,存储向量表起始地址*/
    ldr r0, =0xE000ED08    
    ldr r0, [r0]
    /* 取出向量表中的第一项,向量表第一项存储主堆栈指针MSP的初始值*/
    ldr r0, [r0]   
 
    /* 将堆栈地址存入主堆栈指针 */
    msr msp, r0
    /* 使能全局中断*/
    cpsie i
    cpsie f
    dsb
    isb
    /* 调用SVC启动第一个任务 */
    svc 0
    nop
    nop
}

6.SVC 0触发SVC中断,中断的内容如下,中断完成后,第一个任务即被执行。

__asm void vPortSVCHandler( void )
{
    PRESERVE8
 
    ldr r3, =pxCurrentTCB   /* pxCurrentTCB指向处于最高优先级的就绪任务TCB */
    ldr r1, [r3]            /* 获取任务TCB地址 */
    ldr r0, [r1]            /* 获取任务TCB的第一个成员,即当前堆栈栈顶pxTopOfStack */
    ldmia r0!, {r4-r11}     /* 出栈,将寄存器r4~r11出栈 */
    msr psp, r0             /* 最新的栈顶指针赋给线程堆栈指针PSP */
    isb
    mov r0, #0
    msr basepri, r0
    orrr14, #0xd           /* 这里0x0d表示:返回后进入线程模式,从进程堆栈中做出栈操作,返回Thumb状态*/
    bx r14
}

7.任务切换,FreeRTOS有两种方法触发任务切换:
①执行系统调用,比如普通任务可以使用taskYIELD()强制任务切换,中断服务程序中使用portYIELD_FROM_ISR()强制任务切换;
②系统节拍时钟中断
当在Cortex-M3中,都是调用PendSV切换上下文,在PendSV中断服务函数中,找到最高优先级的任务,并让CPU执行他。而这两种方法都是在使能PendSV中断,PendSV中断的产生是通过代码:portNVIC_INT_CTRL_REG = portNVIC_PENDSVSET_BIT实现的,它向中断状态寄存器bit28位写入1,将PendSV中断设置为挂起状态,等到优先级高于PendSV的中断执行完成后,PendSV中断服务程序将被执行,进行任务切换工作。
8.PendSV中断服务函数流程

在这里插入图片描述

该笔记来自于对朱工的文章的学习。博客名:zhzht19861011

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值