学习FreeRTOS之前,需要先了解RTOS,RTOS全称是Real Time Operating System,中文名是实时操作系统,实时操作系统是保证在一定时间限制内完成特定功能的操作系统。比如uCOS,FreeRTOS,RTX,RT-Thread等这些都是RTOS类操作系统。一个处理器核心在某一时刻只能运行一个任务。操作系统中任务调度器的责任就是决定在某一时刻究竟运行哪个任务。Free是免费的、自由的意思,也就是说FreeRTOS是免费的,供大家自由使用。相关资料可以在FreeRTOS的官方找到,FreeRTOS官方有两份PDF文档,一份是FreeRTOS的指导手册,一份是FreeRTOS的API函数参考手册,还有一个在线文档,可以直接在官网浏览。链接:Free RTOS Book and Reference Manualhttps://www.freertos.org/Documentation/RTOS_book.html
FreeRTOS相关API函数链接:FreeRTOS API categorieshttps://www.freertos.org/a00106.html
FreeRTOS的源码可以直接在官方下载,这里有两种方式,一种是下载完源码自己移植,一种是直接通过STM32CubeMX软件直接生成对应的项目。
目录
一、创建具备FreeRTOS的工程项目
(1)手动移植RTOS源码到工程中
(2)通过STM32CubeMX新建具备FreeRTOS的工程
二、FreeRTOS任务相关知识点
(1)FreeRTOS任务基础知识
(2)FreeRTOS任务相关API函数
函数xTaxkCreate():
函数xTaskCreateStatic():
函数 xTaskCreateRestricted():
函数 vTaskDelete():
三、FreeRTOS列表和列表项
(1)什么是列表和列表项?
列表
列表项
迷你列表项
(2)列表和列表项初始化
列表初始化vListInitialise()
列表项初始化vListInitialiseItem()
(3)列表项插入vListInsert()
(4)列表项末尾插入vListInsertEnd()
(5)列表项的删除uxListRemove()
(6)列表的遍历listGET_OWNER_OF_NEXT_ENTRY()
四、FreeRTOS调度器开启过程涉及的函数
(1)任务调度器开启函数vTaskStartScheduler()
(2)内核相关硬件初始化函数xPortStartScheduler()
(3)使能FPU函数prvEnableVFP()
(4)启动第一个任务prvStartFirstTask()
(5)SVC 中断服务函数vPortSVCHandler()
(6)空闲任务
五、任务切换
(1)执行一个系统调用
(2)系统滴答定时器(SysTick)中断
(3)PendSV 中断服务函数
先说下载完源码自己移植的方法,可以通过下面的链接直接下载FreeRTOS的.exe文件https://download.csdn.net/download/weixin_46736374/86260608https://download.csdn.net/download/weixin_46736374/86260608
双击.exe文件后选择完路径会自动下载源码,打开FreeRTOS文件,可以看到有三个文件夹,
Demo文件夹里面主要是FreeRTOS的相关例程,License文件夹里面是相关的许可信息,Source文件夹就是FreeRTOS的源码文件了,如下图
include文件夹是一些头文件,下面那些.c文件就是FreeRTOS的源码文件,移植的时候都需要它们。FreeRTOS是个系统,归根到底是个纯软件的东西,想要与硬件联系起来,就需要一个桥梁,portable文件夹里面是FreeRTOS系统和具体的硬件之间的连接桥梁。不同的编译环境,不同的MCU,其桥梁是不同的。下图就是portable文件夹中我们需要移植的文件。
打开keil文件夹,里面有个txt文件,名字叫做See also the RVDS directory,意思就是参考RVDS文件夹里的东西。
打开RVDS文件夹,如下图所示,可以看到有不同的MCU架构,根据具体情况选择对应的MCU架构,我使用的是STM32H743,所以要选择ARM_CM7。
打开ARM_CM7中的r0p1文件夹,里面的两个文件就是我们移植所需要的文件。
接下来就开始FreeRTOS的移植,打开一个想要加入操作系统的工程,在工程下面创建一个名为FreeRTOS的文件夹
如图,将前面提到的文件移到文件夹内
这里portable只需要留下keil、MemMang和RVDS三个文件夹。
用keil打开工程,新建文件夹,然后向这两个分组中添加文件,如下图所示
里面的文件都是前面提到过的,port.c是在RVDS文件夹中,heap_4.c是MemMang文件夹中的,关于内存管理的。移植完文件后别忘记还需要添加文件路径
接着需要找到名为FreeRTOSConfig.h文件夹,它在Demo文件夹里面,要根据MCU类型选择对应的文件夹,直接复制到工程include里面就可以了,需要把头文件改成对应的芯片类型
移植完之后编译,可以看到会有错误
这是因为port.c和stm32h7xx_it.c这两个文件夹中有重复定义的函数,只需要屏蔽stm32h7xx_it.c中的 PendSV_Handler、SVC_Handler、SysTick_Handler这三个函数即可解决,再次编译,会看到还是会有错误。
vAssertCalled()是跟断言函数有关的,移植的时候不需要,在 FreeRTOSConfig.h文件中注释掉两行代码即可解决,剩下的三个未定义函数它们都是Hook结尾的,这些函数有个共同的名称叫钩子函数,在FreeRTOSConfig.h中开启了钩子函数却没有定义钩子函数导致的错误,只需要关闭这些钩子函数即可,如下图所示,将宏configUSE_TICK_HOOK、configUSE_MALLOC_FAILED_HOOK、configCHECK_FOR_STACK_OVERFLOW定义为0
最后编译一下,应该没有错误了,如果有其它错误自行根据错误类型查找和修改错误。
具体操作如下:(1).打开STM32CubeMX(若没安装该软件,可以通过以下链接下载STM32CubeMX6.6.1安装包-其它文档类资源-CSDN文库https://download.csdn.net/download/weixin_46736374/86261947?spm=1001.2014.3001.5501),点击File,点击New Project...新建一个工程,选择对应的芯片。我使用的是STM32H743IIT6芯片。点击Start Project即可完成工程创建。
(2).配置芯片时钟,使能HSE外部高速时钟,选择晶振。调节好比例,可以让芯片发挥最大性能,HAL库使用除了Systick以外的时钟源H,不选择Systick系统定时器,会发生冲突。也就是说当不使用FreeRtos的时候,HAL使用的是systick作为时钟源,现在使用了rtos,不建议hal库和rtos一起使用systick作为时钟源。
(3).点击Pinout&Configuration,在Middleware选项下,选择FREERTOS;
(4).Interface选项中有CMSIS_V1 和CMSIS_V2,具体看下图,V2比V1多出Cortex-A5/A7/A9的目标处理器,根据实际情况选择,我使用的芯片为STM32H743IIT6,是Cortex-M7内核,所以选V2 V1都可以,这里选择了V2。
(5).在Project Manager界面编辑项目名称,生成代码的编译环境,可以选上Code Generator里面的Generate peripheral initialization as a pair of '.c/.h' files per peripheral选项,为每个外设生成一对'.c/.h'文件。
(6).点击右上角的GENERATE CODE即可生成具备FreeRTOS的项目。自此就完成了对FreeRTOS源码的移植,可以开始学习FreeRTOS的基本知识了。下面都是以STM32CubeMX生成的代码为例。
单片机裸机(未使用系统)的时候一般都是在main函数里面用while(1)做一个大循环来完成所有的处理。对于多任务系统而言,这个就是单任务系统,也称作前后台系统,实时性差,各个任务都是排队等着轮流执行,相当于所有任务的优先级是一样的,不管程序有多紧急,没轮到的话只能等待,在大一点的嵌入式应用中前后台系统就显得力不从心了,此时就需要多任务系统出马了。
多任务系统会把大问题划分成很多个小问题,逐步解决小问题,从而实现解决大问题,这些小问题单独的作为一个小任务来处理。那么多任务究竟哪个任务先运行,RTOS系统中有个叫做任务调度器。
不同的系统其任务调度器的实现方法也不同,比如 FreeRTOS 是一个抢占式的实时多任务系统,那么其任务调度器也是抢占式的。会有一个优先级的概念,高优先级的任务可以打断低优先级任务的运行而取得 CPU 的使用权,这样就保证了那些紧急任务的运行。这样我们就可以为那些对实时性要求高的任务设置一个很高的优先级。高优先级的任务执行完成以后重新把 CPU 的使用权归还给低优先级的任务,这个就是抢占式多任务系统的基本原理。FreeRTOS中的任务是以一个函数的形式存在的,具有统一的函数原型,void StartDefaultTask(void *argument); 其必须返回void且带有一个void指针参数,任务函数体内通常有一个死循环,决不能有一条return语句,也不能执行到函数尾部,如果某个任务不再需要,可以显式的将其删除。
FreeRTOS中的任务永远处于下面几个状态中的某一个:1.运行态 2.就绪态 3.阻塞态 4.挂起态
运行态:当一个任务正在运行时,那么就说这个任务处于运行态,处于运行态的任务就是当前正在使用处理器的任务。如果使用的是单核处理器的话那么不管在任何时刻永远都只有一个任务处于运行态。
就绪态:处于就绪态的任务是那些已经准备就绪(这些任务没有被阻塞或者挂起),可以运行的任务, 但是处于就绪态的任务还没有运行,因为有一个同优先级或者更高优先级的任务正在运行!
阻塞态:如果一个任务当前正在等待某个外部事件的话就说它处于阻塞态,比如说如果某个任务调 用了函数 vTaskDelay()的话就会进入阻塞态,直到延时周期完成。任务在等待队列、信号量、事件组、通知或互斥信号量的时候也会进入阻塞态。任务进入阻塞态会有一个超时时间,当超过这个超时时间任务就会退出阻塞态,即使所等待的事件还没有来临!
挂起态:像阻塞态一样,任务进入挂起态以后也不能被调度器调用进入运行态,但是进入挂起态的 任务没有超时时间。任务进入和退出挂起态通过调用函数 vTaskSuspend()和 xTaskResume()。
任务状态之间的转换如下图所示:
每个任务都可以分配一个优先级,像STM32CubeMX生成的代码,会有一个文件cmsis_os2.h里面有枚举类型就是优先级,优先级范围0~(configMAX_PRIORITIES-1),这里已经帮我们全部定义好了,直接使用就行,configMAX_PRIORITIES 在文件 FreeRTOSConfig.h 中有定义,见下图。优先级数字越低表示任务的优先级越低,0 的优先级最低,configMAX_PRIORITIES-1 的优先级最高。空闲任务的优先级最低,为 0。
创建任务我们可以在STM32CubeMX的FREERTOS配置中,选择Tasks and Queues,可以看到默认有一个开始任务了,若需要添加新的任务,点击Add
用户可以自行配置任务名、优先级、堆栈大小、函数入口、代码生成选项、变量、函数配置。
代码生成选项Default是生成默认格式的任务函数,As external是产生一个外部引用的任务函数,用户需要自己实现该函数,As weak是产生一个用__weak修饰符修饰的任务函数,如果函数名称前面加上__weak 修饰符,我们一般称这个函数为“弱函数”。加上了__weak 修饰符的函数,用户可以在用户文件中重新定义一个同名函数,最终编译器编译的时候,会选择用户定义的函数,如果用户没有重新定义这个函数,那么编译器就会执行__weak 声明的函数,并且编译器不会报错。
配置Allocation的话一般都是选择Dynamic动态的,很少使用static静态的。如果使用函数 xTaskCreateStatic()创建任务(静态方法)的话就需要程序员自行定义任务堆栈。
点击OK创建完成,点击右上角生成代码。打开项目可以看到我们文件里面多了一个任务myTask02,想要实现的功能可以在for(;;)中编写。任务函数一般不允许跳出循环,如果一定要跳出循环的话在跳出循环以后一定要调用函数 vTaskDelete(NULL)删除此任务!
函数 | 描述 |
xTaskCreate()
|
使用动态的方法创建一个任务。
|
xTaskCreateStatic()
|
使用静态的方法创建一个任务。
|
xTaskCreateRestricted()
|
创建一个使用 MPU 进行限制的任务,相关内存使用动态内存分配。
|
vTaskDelete()
|
删除一个任务。
|
vTaskSuspend()
|
挂起一个任务。 |
vTaskResume()
|
恢复一个任务。(任务中) |
xTaskResumeFromISR()
|
恢复一个任务。(中断中) |
BaseType_t xTaskCreate( TaskFunction_t pxTaskCode,/*任务函数*/
const char * const pcName,/*任务名字*/
const uint16_t usStackDepth,/*任务堆栈大小*/
void * const pvParameters,/*传递给任务函数的参数*/
UBaseType_t uxPriority,/*任务优先级*/
TaskHandle_t * const pxCreatedTask/*任务句柄*/ )
需要注意的是任务堆栈大小实际申请到的堆栈是usStackDepth的4倍。任务创建成功以后会返回任务句柄,这个句柄其实就是任务的任务堆栈。此参数就用来保存这个任务句柄。其他 API 函数可能会使 用到这个句柄。
返回值:
pdPASS |
任务创建成功
|
errCOULD_NOT_ALLOCATE_REQUIRED_MEMORY
|
任务创建失败,因为堆内存不足
|
TaskHandle_t xTaskCreateStatic( TaskFunction_t pxTaskCode,/*任务函数*/
const char * const pcName,/*任务名字*/
const uint32_t ulStackDepth,/*任务堆栈大小*/
void * const pvParameters,/*传递给任务函数的参数*/
UBaseType_t uxPriority,/*任务优先级*/
StackType_t * const puxStackBuffer,/*任务堆栈*/
StaticTask_t * const pxTaskBuffer/*任务控制块*/ )
/*puxStackBuffer: 任务堆栈,一般为数组,数组类型要为 StackType_t 类型*/
返回值:
NULL |
任务创建失败,puxStackBuffer 或 pxTaskBuffer 为 NULL 的时候会导致这个错误的发生。
|
其他值
|
任务创建成功,返回任务的任务句柄。
|
BaseType_t xTaskCreateRestricted( const TaskParameters_t * const pxTaskDefinition,
TaskHandle_t * pxCreatedTask )
/*
pxTaskDefinition: 指向一个结构体 TaskParameters_t,这个结构体描述了任务的任务函数、
堆栈大小、优先级等。此结构体在文件 task.h 中有定义。
pxCreatedTask: 任务句柄。
*/
返回值:
pdPASS |
任务创建成功
|
其他值
|
任务未创建成功,很有可能是因为 FreeRTOS 的堆太小了
|
删除一个用函数 xTaskCreate()或者 xTaskCreateStatic()创建的任务,被删除了的任务不再存在,也就是说再也不会进入运行态。任务被删除以后就不能再使用此任务的句柄!如果此任务是使用动态方法创建的,也就是使用函数 xTaskCreate()创建的,那么在此任务被删除以后此任务之前申请的堆栈和控制块内存会在空闲任务中被释放掉,因此当调用函数 vTaskDelete()删除任务以后必须给空闲任务一定的运行时间。
只有那些由内核分配给任务的内存才会在任务被删除以后自动的释放掉,用户分配给任务的内存需要用户自行释放掉,比如某个任务中用户调用函数 pvPortMalloc()分配了 500 字节的内存,那么在此任务被删除以后用户也必须调用函数 vPortFree()将这 500 字节的内存释放掉,否则会导致内存泄露。函数原型如下:
vTaskDelete( TaskHandle_t xTaskToDelete )
/* xTaskToDelete: 要删除的任务的任务句柄 */
返回值:
无
/*
* Definition of the type of queue used by the scheduler.
*/
typedef struct xLIST
{
listFIRST_LIST_INTEGRITY_CHECK_VALUE /*< Set to a known value if configUSE_LIST_DATA_INTEGRITY_CHECK_BYTES is set to 1. */
volatile UBaseType_t uxNumberOfItems;
ListItem_t * configLIST_VOLATILE pxIndex; /*< Used to walk through the list. Points to the last item returned by a call to listGET_OWNER_OF_NEXT_ENTRY (). */
MiniListItem_t xListEnd; /*< List item that contains the maximum possible item value meaning it is always at the end of the list and is therefore used as a marker. */
listSECOND_LIST_INTEGRITY_CHECK_VALUE /*< Set to a known value if configUSE_LIST_DATA_INTEGRITY_CHECK_BYTES is set to 1. */
} List_t;
listFIRST_LIST_INTEGRITY_CHECK_VALUE 和 listSECOND_LIST_INTEGRITY_CHECK_VALUE这两个是用来检查列表完整性的,需要将宏configUSE_LIST_DATA_INTEGRITY_CHECK_BYTES设置为1,默认不开启这个功能。
uxNumberOfItems 用来记录列表中列表项的数量。pxIndex 用来记录当前列表项索引号,用于遍历列表。列表中最后一个列表项,用来表示列表结束,此变量类型为 MiniListItem_t,这是一个迷你列表项。
struct xLIST_ITEM
{
listFIRST_LIST_ITEM_INTEGRITY_CHECK_VALUE /*< Set to a known value if configUSE_LIST_DATA_INTEGRITY_CHECK_BYTES is set to 1. */
configLIST_VOLATILE TickType_t xItemValue; /*< The value being listed. In most cases this is used to sort the list in descending order. */
struct xLIST_ITEM * configLIST_VOLATILE pxNext; /*< Pointer to the next ListItem_t in the list. */
struct xLIST_ITEM * configLIST_VOLATILE pxPrevious; /*< Pointer to the previous ListItem_t in the list. */
void * pvOwner; /*< Pointer to the object (normally a TCB) that contains the list item. There is therefore a two way link between the object containing the list item and the list item itself. */
struct xLIST * configLIST_VOLATILE pxContainer; /*< Pointer to the list in which this list item is placed (if any). */
listSECOND_LIST_ITEM_INTEGRITY_CHECK_VALUE /*< Set to a known value if configUSE_LIST_DATA_INTEGRITY_CHECK_BYTES is set to 1. */
};
typedef struct xLIST_ITEM ListItem_t; /* For some reason lint wants this as two separate definitions. */
listFIRST_LIST_ITEM_INTEGRITY_CHECK_VALUE和listSECOND_LIST_ITEM_INTEGRITY_CHECK_VALUE用法和列表一样,用来检查列表项完整性。xItemValue 为列表项值。pxNext 指向下一个列表项。pxPrevious 指向前一个列表项,和 pxNext 配合起来实现类似双向链表的功能。pvOwner 记录此链表项归谁拥有,通常是任务控制块。pvContainer 用来记录此列表项归哪个列表。
struct xMINI_LIST_ITEM
{
listFIRST_LIST_ITEM_INTEGRITY_CHECK_VALUE /*< Set to a known value if configUSE_LIST_DATA_INTEGRITY_CHECK_BYTES is set to 1. */
configLIST_VOLATILE TickType_t xItemValue;
struct xLIST_ITEM * configLIST_VOLATILE pxNext;
struct xLIST_ITEM * configLIST_VOLATILE pxPrevious;
};
typedef struct xMINI_LIST_ITEM MiniListItem_t;
listFIRST_LIST_ITEM_INTEGRITY_CHECK_VALUE用于检查迷你列表项的完整性。xItemValue 记录列表项值。pxNext 指向下一个列表项。pxPrevious 指向上一个列表项。
void vListInitialise( List_t * const pxList )
{
/* The list structure contains a list item which is used to mark the
end of the list. To initialise the list the list end is inserted
as the only list entry. */
pxList->pxIndex = ( ListItem_t * ) &( pxList->xListEnd ); /*lint !e826 !e740 !e9087 The mini list structure is used as the list end to save RAM. This is checked and valid. */
/* The list end value is the highest possible value in the list to
ensure it remains at the end of the list. */
pxList->xListEnd.xItemValue = portMAX_DELAY;
/* The list end next and previous pointers point to itself so we know
when the list is empty. */
pxList->xListEnd.pxNext = ( ListItem_t * ) &( pxList->xListEnd ); /*lint !e826 !e740 !e9087 The mini list structure is used as the list end to save RAM. This is checked and valid. */
pxList->xListEnd.pxPrevious = ( ListItem_t * ) &( pxList->xListEnd );/*lint !e826 !e740 !e9087 The mini list structure is used as the list end to save RAM. This is checked and valid. */
pxList->uxNumberOfItems = ( UBaseType_t ) 0U;
/* Write known values into the list if
configUSE_LIST_DATA_INTEGRITY_CHECK_BYTES is set to 1. */
listSET_LIST_INTEGRITY_CHECK_1_VALUE( pxList );
listSET_LIST_INTEGRITY_CHECK_2_VALUE( pxList );
}
void vListInitialiseItem( ListItem_t * const pxItem )
{
/* Make sure the list item is not recorded as being on a list. */
pxItem->pxContainer = NULL;
/* Write known values into the list item if
configUSE_LIST_DATA_INTEGRITY_CHECK_BYTES is set to 1. */
listSET_FIRST_LIST_ITEM_INTEGRITY_CHECK_VALUE( pxItem );
listSET_SECOND_LIST_ITEM_INTEGRITY_CHECK_VALUE( pxItem );
}
列表项的插入操作通过函数 vListInsert()来完成,函数如下:
pxList:列表项要插入的列表 pxNewListItem: 要插入的列表项 无返回值。
void vListInsert( List_t * const pxList, ListItem_t * const pxNewListItem )
{
ListItem_t *pxIterator;
const TickType_t xValueOfInsertion = pxNewListItem->xItemValue;
/* Only effective when configASSERT() is also defined, these tests may catch
the list data structures being overwritten in memory. They will not catch
data errors caused by incorrect configuration or use of FreeRTOS. */
listTEST_LIST_INTEGRITY( pxList );
listTEST_LIST_ITEM_INTEGRITY( pxNewListItem );
/* Insert the new list item into the list, sorted in xItemValue order.
If the list already contains a list item with the same item value then the
new list item should be placed after it. This ensures that TCBs which are
stored in ready lists (all of which have the same xItemValue value) get a
share of the CPU. However, if the xItemValue is the same as the back marker
the iteration loop below will not end. Therefore the value is checked
first, and the algorithm slightly modified if necessary. */
if( xValueOfInsertion == portMAX_DELAY )
{
pxIterator = pxList->xListEnd.pxPrevious;
}
else
{
/* *** NOTE ***********************************************************
If you find your application is crashing here then likely causes are
listed below. In addition see https://www.freertos.org/FAQHelp.html for
more tips, and ensure configASSERT() is defined!
https://www.freertos.org/a00110.html#configASSERT
1) Stack overflow -
see https://www.freertos.org/Stacks-and-stack-overflow-checking.html
2) Incorrect interrupt priority assignment, especially on Cortex-M
parts where numerically high priority values denote low actual
interrupt priorities, which can seem counter intuitive. See
https://www.freertos.org/RTOS-Cortex-M3-M4.html and the definition
of configMAX_SYSCALL_INTERRUPT_PRIORITY on
https://www.freertos.org/a00110.html
3) Calling an API function from within a critical section or when
the scheduler is suspended, or calling an API function that does
not end in "FromISR" from an interrupt.
4) Using a queue or semaphore before it has been initialised or
before the scheduler has been started (are interrupts firing
before vTaskStartScheduler() has been called?).
**********************************************************************/
for( pxIterator = ( ListItem_t * ) &( pxList->xListEnd ); pxIterator->pxNext->xItemValue <= xValueOfInsertion; pxIterator = pxIterator->pxNext ) /*lint !e826 !e740 !e9087 The mini list structure is used as the list end to save RAM. This is checked and valid. *//*lint !e440 The iterator moves to a different value, not xValueOfInsertion. */
{
/* There is nothing to do here, just iterating to the wanted
insertion position. */
}
}
pxNewListItem->pxNext = pxIterator->pxNext;
pxNewListItem->pxNext->pxPrevious = pxNewListItem;
pxNewListItem->pxPrevious = pxIterator;
pxIterator->pxNext = pxNewListItem;
/* Remember which list the item is in. This allows fast removal of the
item later. */
pxNewListItem->pxContainer = pxList;
( pxList->uxNumberOfItems )++;
}
void vListInsertEnd( List_t * const pxList, ListItem_t * const pxNewListItem )
{
ListItem_t * const pxIndex = pxList->pxIndex;
/* Only effective when configASSERT() is also defined, these tests may catch
the list data structures being overwritten in memory. They will not catch
data errors caused by incorrect configuration or use of FreeRTOS. */
listTEST_LIST_INTEGRITY( pxList );
listTEST_LIST_ITEM_INTEGRITY( pxNewListItem );
/* Insert a new list item into pxList, but rather than sort the list,
makes the new list item the last item to be removed by a call to
listGET_OWNER_OF_NEXT_ENTRY(). */
pxNewListItem->pxNext = pxIndex;
pxNewListItem->pxPrevious = pxIndex->pxPrevious;
/* Only used during decision coverage testing. */
mtCOVERAGE_TEST_DELAY();
pxIndex->pxPrevious->pxNext = pxNewListItem;
pxIndex->pxPrevious = pxNewListItem;
/* Remember which list the item is in. */
pxNewListItem->pxContainer = pxList;
( pxList->uxNumberOfItems )++;
}
UBaseType_t uxListRemove( ListItem_t * const pxItemToRemove )
{
/* The list item knows which list it is in. Obtain the list from the list
item. */
List_t * const pxList = pxItemToRemove->pxContainer;
pxItemToRemove->pxNext->pxPrevious = pxItemToRemove->pxPrevious;
pxItemToRemove->pxPrevious->pxNext = pxItemToRemove->pxNext;
/* Only used during decision coverage testing. */
mtCOVERAGE_TEST_DELAY();
/* Make sure the index is left pointing to a valid item. */
if( pxList->pxIndex == pxItemToRemove )
{
pxList->pxIndex = pxItemToRemove->pxPrevious;
}
else
{
mtCOVERAGE_TEST_MARKER();
}
pxItemToRemove->pxContainer = NULL;
( pxList->uxNumberOfItems )--;
return pxList->uxNumberOfItems;
}
#define listGET_OWNER_OF_NEXT_ENTRY( pxTCB, pxList ) \
{ \
List_t * const pxConstList = ( pxList ); \
/* Increment the index to the next item and return the item, ensuring */ \
/* we don't return the marker used at the end of the list. */ \
( pxConstList )->pxIndex = ( pxConstList )->pxIndex->pxNext; \
if( ( void * ) ( pxConstList )->pxIndex == ( void * ) &( ( pxConstList )->xListEnd ) ) \
{ \
( pxConstList )->pxIndex = ( pxConstList )->pxIndex->pxNext; \
} \
( pxTCB ) = ( pxConstList )->pxIndex->pvOwner; \
}
FreeRTOS开启任务调度主要用的函数是vTaskStartScheduler(),函数如下:
void vTaskStartScheduler( void )
{
BaseType_t xReturn;
/* Add the idle task at the lowest priority. */
#if( configSUPPORT_STATIC_ALLOCATION == 1 )
{
StaticTask_t *pxIdleTaskTCBBuffer = NULL;
StackType_t *pxIdleTaskStackBuffer = NULL;
uint32_t ulIdleTaskStackSize;
/* The Idle task is created using user provided RAM - obtain the
address of the RAM then create the idle task. */
vApplicationGetIdleTaskMemory( &pxIdleTaskTCBBuffer, &pxIdleTaskStackBuffer, &ulIdleTaskStackSize );
xIdleTaskHandle = xTaskCreateStatic( prvIdleTask,
configIDLE_TASK_NAME,
ulIdleTaskStackSize,
( void * ) NULL, /*lint !e961. The cast is not redundant for all compilers. */
portPRIVILEGE_BIT, /* In effect ( tskIDLE_PRIORITY | portPRIVILEGE_BIT ), but tskIDLE_PRIORITY is zero. */
pxIdleTaskStackBuffer,
pxIdleTaskTCBBuffer ); /*lint !e961 MISRA exception, justified as it is not a redundant explicit cast to all supported compilers. */
if( xIdleTaskHandle != NULL )
{
xReturn = pdPASS;
}
else
{
xReturn = pdFAIL;
}
}
#else
{
/* The Idle task is being created using dynamically allocated RAM. */
xReturn = xTaskCreate( prvIdleTask,
configIDLE_TASK_NAME,
configMINIMAL_STACK_SIZE,
( void * ) NULL,
portPRIVILEGE_BIT, /* In effect ( tskIDLE_PRIORITY | portPRIVILEGE_BIT ), but tskIDLE_PRIORITY is zero. */
&xIdleTaskHandle ); /*lint !e961 MISRA exception, justified as it is not a redundant explicit cast to all supported compilers. */
}
#endif /* configSUPPORT_STATIC_ALLOCATION */
#if ( configUSE_TIMERS == 1 )
{
if( xReturn == pdPASS )
{
xReturn = xTimerCreateTimerTask();
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
#endif /* configUSE_TIMERS */
if( xReturn == pdPASS )
{
/* freertos_tasks_c_additions_init() should only be called if the user
definable macro FREERTOS_TASKS_C_ADDITIONS_INIT() is defined, as that is
the only macro called by the function. */
#ifdef FREERTOS_TASKS_C_ADDITIONS_INIT
{
freertos_tasks_c_additions_init();
}
#endif
/* Interrupts are turned off here, to ensure a tick does not occur
before or during the call to xPortStartScheduler(). The stacks of
the created tasks contain a status word with interrupts switched on
so interrupts will automatically get re-enabled when the first task
starts to run. */
portDISABLE_INTERRUPTS();
#if ( configUSE_NEWLIB_REENTRANT == 1 )
{
/* Switch Newlib's _impure_ptr variable to point to the _reent
structure specific to the task that will run first.
See the third party link http://www.nadler.com/embedded/newlibAndFreeRTOS.html
for additional information. */
_impure_ptr = &( pxCurrentTCB->xNewLib_reent );
}
#endif /* configUSE_NEWLIB_REENTRANT */
xNextTaskUnblockTime = portMAX_DELAY;
xSchedulerRunning = pdTRUE;
xTickCount = ( TickType_t ) configINITIAL_TICK_COUNT;
/* If configGENERATE_RUN_TIME_STATS is defined then the following
macro must be defined to configure the timer/counter used to generate
the run time counter time base. NOTE: If configGENERATE_RUN_TIME_STATS
is set to 0 and the following line fails to build then ensure you do not
have portCONFIGURE_TIMER_FOR_RUN_TIME_STATS() defined in your
FreeRTOSConfig.h file. */
portCONFIGURE_TIMER_FOR_RUN_TIME_STATS();
traceTASK_SWITCHED_IN();
/* Setting up the timer tick is hardware specific and thus in the
portable interface. */
if( xPortStartScheduler() != pdFALSE )
{
/* Should not reach here as if the scheduler is running the
function will not return. */
}
else
{
/* Should only reach here if a task calls xTaskEndScheduler(). */
}
}
else
{
/* This line will only be reached if the kernel could not be started,
because there was not enough FreeRTOS heap to create the idle task
or the timer task. */
configASSERT( xReturn != errCOULD_NOT_ALLOCATE_REQUIRED_MEMORY );
}
/* Prevent compiler warnings if INCLUDE_xTaskGetIdleTaskHandle is set to 0,
meaning xIdleTaskHandle is not used anywhere else. */
( void ) xIdleTaskHandle;
}
函数主要做了(1).创建空闲任务,如果 configSUPPORT_STATIC_ALLOCATION == 1 使用的是xTaskCreateStatic()来创建空闲任务,优先级为portPRIVILEGE_BIT,宏portPRIVILEGE_BIT为0,也就是说空闲任务的优先级为最低;(2).如果使用软件定时器的话还需要通过函数xTimerCreateTimerTask()来创建定时器服务任务。定时器服务任务的具体创建过程是在函数xTimerCreateTimerTask()中完成。(3).空闲任务和定时器服务任务创建成功的话,会关闭中断,在SVC 中断服务函数 vPortSVCHandler()中会打开中断;(4).变量 xSchedulerRunning 设置为pdTRUE,表示调度器开始运行;(5).当宏 configGENERATE_RUN_TIME_STATS 为 1 的时候说明使能时间统计功能,此时需要用户实现宏 portCONFIGURE_TIMER_FOR_RUN_TIME_STATS,此宏用来配置一个定时器/计数器;(6)调用函数 xPortStartScheduler()来初始化跟调度器启动有关的硬件,比如滴答定时器、FPU 单元和 PendSV 中断等等。
BaseType_t xPortStartScheduler( void )
{
/* configMAX_SYSCALL_INTERRUPT_PRIORITY must not be set to 0.
See http://www.FreeRTOS.org/RTOS-Cortex-M3-M4.html */
configASSERT( configMAX_SYSCALL_INTERRUPT_PRIORITY );
/* This port can be used on all revisions of the Cortex-M7 core other than
the r0p1 parts. r0p1 parts should use the port from the
/source/portable/GCC/ARM_CM7/r0p1 directory. */
configASSERT( portCPUID != portCORTEX_M7_r0p1_ID );
configASSERT( portCPUID != portCORTEX_M7_r0p0_ID );
#if( configASSERT_DEFINED == 1 )
{
volatile uint32_t ulOriginalPriority;
volatile uint8_t * const pucFirstUserPriorityRegister = ( uint8_t * ) ( portNVIC_IP_REGISTERS_OFFSET_16 + portFIRST_USER_INTERRUPT_NUMBER );
volatile uint8_t ucMaxPriorityValue;
/* Determine the maximum priority from which ISR safe FreeRTOS API
functions can be called. ISR safe functions are those that end in
"FromISR". FreeRTOS maintains separate thread and ISR API functions to
ensure interrupt entry is as fast and simple as possible.
Save the interrupt priority value that is about to be clobbered. */
ulOriginalPriority = *pucFirstUserPriorityRegister;
/* Determine the number of priority bits available. First write to all
possible bits. */
*pucFirstUserPriorityRegister = portMAX_8_BIT_VALUE;
/* Read the value back to see how many bits stuck. */
ucMaxPriorityValue = *pucFirstUserPriorityRegister;
/* The kernel interrupt priority should be set to the lowest
priority. */
configASSERT( ucMaxPriorityValue == ( configKERNEL_INTERRUPT_PRIORITY & ucMaxPriorityValue ) );
/* Use the same mask on the maximum system call priority. */
ucMaxSysCallPriority = configMAX_SYSCALL_INTERRUPT_PRIORITY & ucMaxPriorityValue;
/* Calculate the maximum acceptable priority group value for the number
of bits read back. */
ulMaxPRIGROUPValue = portMAX_PRIGROUP_BITS;
while( ( ucMaxPriorityValue & portTOP_BIT_OF_BYTE ) == portTOP_BIT_OF_BYTE )
{
ulMaxPRIGROUPValue--;
ucMaxPriorityValue <<= ( uint8_t ) 0x01;
}
#ifdef __NVIC_PRIO_BITS
{
/* Check the CMSIS configuration that defines the number of
priority bits matches the number of priority bits actually queried
from the hardware. */
configASSERT( ( portMAX_PRIGROUP_BITS - ulMaxPRIGROUPValue ) == __NVIC_PRIO_BITS );
}
#endif
#ifdef configPRIO_BITS
{
/* Check the FreeRTOS configuration that defines the number of
priority bits matches the number of priority bits actually queried
from the hardware. */
configASSERT( ( portMAX_PRIGROUP_BITS - ulMaxPRIGROUPValue ) == configPRIO_BITS );
}
#endif
/* Shift the priority group value back to its position within the AIRCR
register. */
ulMaxPRIGROUPValue <<= portPRIGROUP_SHIFT;
ulMaxPRIGROUPValue &= portPRIORITY_GROUP_MASK;
/* Restore the clobbered interrupt priority register to its original
value. */
*pucFirstUserPriorityRegister = ulOriginalPriority;
}
#endif /* conifgASSERT_DEFINED */
/* Make PendSV and SysTick the lowest priority interrupts. */
portNVIC_SYSPRI2_REG |= portNVIC_PENDSV_PRI;
portNVIC_SYSPRI2_REG |= portNVIC_SYSTICK_PRI;
/* Start the timer that generates the tick ISR. Interrupts are disabled
here already. */
vPortSetupTimerInterrupt();
/* Initialise the critical nesting count ready for the first task. */
uxCriticalNesting = 0;
/* Ensure the VFP is enabled - it should be anyway. */
prvEnableVFP();
/* Lazy save always. */
*( portFPCCR ) |= portASPEN_AND_LSPEN_BITS;
/* Start the first task. */
prvStartFirstTask();
/* Should not get here! */
return 0;
}
__asm void prvEnableVFP( void )
{
PRESERVE8
/* The FPU enable bits are in the CPACR. */
ldr.w r0, =0xE000ED88
ldr r1, [r0]
/* Enable CP10 and CP11 coprocessors, then save back. */
orr r1, r1, #( 0xf << 20 )
str r1, [r0]
bx r14
nop
}
__asm void prvStartFirstTask( void )
{
PRESERVE8
/* Use the NVIC offset register to locate the stack. */
ldr r0, =0xE000ED08 //R0=0XE000ED08
ldr r0, [r0] //取 R0 所保存的地址处的值赋给 R0
ldr r0, [r0] //获取 MSP 初始值
/* Set the msp back to the start of the stack. */
msr msp, r0 //复位 MSP
/* Clear the bit that indicates the FPU is in use in case the FPU was used
before the scheduler was started - which would otherwise result in the
unnecessary leaving of space in the SVC stack for lazy saving of FPU
registers. */
mov r0, #0
msr control, r0
/* Globally enable interrupts. */
cpsie i //使能中断(清除 PRIMASK)
cpsie f //使能中断(清除 FAULTMASK)
dsb //数据同步屏障
isb //指令同步屏障
/* Call SVC to start the first task. */
svc 0 //触发 SVC 中断(异常)
nop
nop
}
#define xPortPendSVHandler PendSV_Handler
__asm void vPortSVCHandler( void )
{
PRESERVE8
/* Get the location of the current TCB. */
ldr r3, =pxCurrentTCB
ldr r1, [r3]
ldr r0, [r1]
/* Pop the core registers. */
ldmia r0!, {r4-r11, r14}
msr psp, r0
isb
mov r0, #0
msr basepri, r0
bx r14
}
学习任务切换前需要了解PendSV,参考自《权威指南》的“第 10 章 OS 支持特性”的第 10.4 小节
#define taskYIELD() portYIELD()
#define portYIELD() \
{ \
/* Set a PendSV to request a context switch. */ \
portNVIC_INT_CTRL_REG = portNVIC_PENDSVSET_BIT; \
\
/* Barriers are normally not required but do ensure the code is completely \
within the specified behaviour for the architecture. */ \
__dsb( portSY_FULL_READ_WRITE ); \
__isb( portSY_FULL_READ_WRITE ); \
}
#define portEND_SWITCHING_ISR( xSwitchRequired )
if( xSwitchRequired != pdFALSE ) portYIELD()
#define portYIELD_FROM_ISR( x ) portEND_SWITCHING_ISR( x )
void SysTick_Handler (void) {
/* Clear overflow flag */
SysTick->CTRL;
if (xTaskGetSchedulerState() != taskSCHEDULER_NOT_STARTED) {
/* Call tick handler */
xPortSysTickHandler();
}
}
void xPortSysTickHandler( void )
{
/* The SysTick runs at the lowest interrupt priority, so when this interrupt
executes all interrupts must be unmasked. There is therefore no need to
save and then restore the interrupt mask value as its value is already
known - therefore the slightly faster vPortRaiseBASEPRI() function is used
in place of portSET_INTERRUPT_MASK_FROM_ISR(). */
vPortRaiseBASEPRI();
{
/* Increment the RTOS tick. */
if( xTaskIncrementTick() != pdFALSE )
{
/* A context switch is required. Context switching is performed in
the PendSV interrupt. Pend the PendSV interrupt. */
portNVIC_INT_CTRL_REG = portNVIC_PENDSVSET_BIT;
}
}
vPortClearBASEPRIFromISR();
}
xPortSysTickHandler函数主要实现了关闭中断,通过向中断控制和状态寄存器 ICSR 的 bit28 写入 1 挂起 PendSV 来启动 PendSV 中断,这样就可以在 PendSV 中断服务函数中进行任务切换了,最后打开中断。
#define xPortPendSVHandler PendSV_Handler
__asm void xPortPendSVHandler( void )
{
extern uxCriticalNesting;
extern pxCurrentTCB;
extern vTaskSwitchContext;
PRESERVE8
mrs r0, psp //读取进程栈指针,保存在寄存器 R0 里面
isb
/* Get the location of the current TCB. */
ldr r3, =pxCurrentTCB
ldr r2, [r3] //获取当前任务的任务控制块,并将任务控制块的地址保存在寄存器 R2 里面
/* Is the task using the FPU context? If so, push high vfp registers. */
tst r14, #0x10
it eq
/*判断任务是否使用了 FPU,如果任务使用了 FPU 的话在进行任务切换的时候就需要将 FPU 寄存器s16~s31手动保存到任务堆栈中,其中 s0~s15 和 FPSCR 是自动保存的。*/
vstmdbeq r0!, {s16-s31} //保存 s16~s31 这 16 个 FPU 寄存器。
/* Save the core registers. */
stmdb r0!, {r4-r11, r14} //保存 r4~r11 和 R14 这几个寄存器的值。
/* Save the new top of stack into the first member of the TCB. */
str r0, [r2] /*将寄存器 R0 的值写入到寄存器 R2 所保存的地址中去,也就是将新的栈顶保存在任务控制块的第一个字段中。此时的寄存器 R0 保存着最新的堆栈栈顶指针值,所以要将这个最新的栈顶指针写入到当前任务的任务控制块第一个字段,前面已经获取到了任务控制块,并将任务控制块的首地址写如到了寄存器 R2 中*/
stmdb sp!, {r0, r3} /*将寄存器 R3 的值临时压栈,寄存器 R3 中保存了当前任务的任务控制块,而接下来要调用函数 vTaskSwitchContext(),为了防止 R3 的值被改写,所以这里临时将 R3 的值先压栈*/
mov r0, #configMAX_SYSCALL_INTERRUPT_PRIORITY //关闭中断
msr basepri, r0 //进入临界区
dsb
isb
bl vTaskSwitchContext /*调用函数 vTaskSwitchContext(),此函数用来获取下一个要运行的任务,并将pxCurrentTCB 更新为这个要运行的任务*/
mov r0, #0 //打开中断
msr basepri, r0 //退出临界区
ldmia sp!, {r0, r3} /*刚刚保存的寄存器 R3 的值出栈,恢复寄存器 R3 的值。注意,经过bl vTaskSwitchContext,此时pxCurrentTCB 的值已经改变了,所以读取 R3 所保存的地址处的数据就会发现其值改变了,成为了下一个要运行的任务的任务控制块。*/
/* The first item in pxCurrentTCB is the task top of stack. */
ldr r1, [r3]
ldr r0, [r1] //获取新的要运行的任务的任务堆栈栈顶,并将栈顶保存在寄存器 R0 中
/* Pop the core registers. */
ldmia r0!, {r4-r11, r14} //R4~R11,R14 出栈,也就是即将运行的任务的现场
/* Is the task using the FPU context? If so, pop the high vfp registers
too. */
/*判断即将运行的任务是否有使用到 FPU,如果有的话还需要手工恢复 FPU 的 s16~s31 寄存器。*/
tst r14, #0x10
it eq
vldmiaeq r0!, {s16-s31}
msr psp, r0 //更新进程栈指针 PSP 的值
isb
#ifdef WORKAROUND_PMU_CM001 /* XMC4000 specific errata */
#if WORKAROUND_PMU_CM001 == 1
push { r14 }
pop { pc }
nop
#endif
#endif
bx r14 /*执行此行代码以后硬件自动恢复寄存器 R0~R3、R12、LR、PC 和 xPSR 的值,确定异常返回以后应该进入处理器模式还是进程模式,使用主栈指针(MSP)还是进程栈指针(PSP)。很明显这里会进入进程模式,并且使用进程栈指针(PSP),寄存器 PC 值会被恢复为即将运行的任务的任务函数,新的任务开始运行!至此,任务切换成功*/
}