首先,实现就序列表
发布时间:2025-06-24 20:31:13 作者:北方职教升学中心 阅读量:511
首先,定义任务栈、本文的目录参照野火的实战指南。
void prvInitialiseTaskLists(void){ UBaseType_t uxPriority; for( uxPriority = (UBaseType_t ) 0U; uxPriority < (UBaseType_t ) configMAX_PRIORITES; uxPriority++ ) { vListInitialise( & ( pxReadyTasksLists[ uxPriority])); }}
5.3、所以我决定,先不看代码的实现,把任务的创建与切换整体流程过一遍,然后再来看具体的函数功能实现和C语言语法结构,顺便补充一下我贫瘠的C语言知识。新概念名词
任务栈,任务函数,任务控制块,就绪列表,调度器,任务切换
总结
中间好几天都是只看了一点点,就这两天认真看了。启动调度器
vTaskStartScheduler()——在 task.c 中定义——手动指定第一个运行的任务,启动调度器
xPortStartScheduler()——在 port.c 中实现——配置PendSV和SysTick中断优先级为最低
prvStartFirstTask()——在 port.c 实现——更新MSP的值,产生SVC系统调用
vPortSVCHandler()函数——在这里真正切换到第一个任务
8、
4、3、
多任务系统中: 每个任务都是独立的,互不干扰,要为每个任务分配独立的栈空间。各个函数所在文件
1、main函数
任务的创建,就绪列表的实现,调度器的实现均已完成,测试代码都放在main.c里面
/*包含的头文件*/#include "FreeRTOS.h"#include "task.h"/*全局变量*/portCHAR flag1;portCHAR flag2;extern List_t pxReadyTaskLists[ configMAX_PRIORITIES];/*任务控制块*/TaskHandle_t Task1_Handle;#define TASK1_STACK_SIZE 128StackType_ Task1Stack[TASK1_STACK_SIZE];TCB_t Task1TCB;TaskHandle_t Task2_Handle;#define TASK2_STACK_SIZE 128StackType_ Task2Stack[TASK2_STACK_SIZE];TCB_t Task2TCB;/*函数声明*/void delay (uint32_t count);void Task1_Entry(void *p_arg);void Task2_Entry(void *p_arg);/*main函数*/int main(void){ /*初始化与任务相关的列表,如就绪列表*/ prvInitialiseTaskLists(); /*创建任务*/ Task1_Handle = xTaskCreateStatic((TaskFunction_t)Task1_Entry, /*任务入口*/ (char *)"Task1", /*任务名称,字符串形式*/ (uint32_t)TASK1 STACK SIZE,/*任务栈大小,单位为字*/ (void *) NULL, /*任务形参*/ (StackType_t *)Task1Stack, /*任务栈起始地址*/ (TCB_t *)&Task1TCB); /*任务控制块*/ /**将任务添加到就绪列表/ vListInsertEnd(&( pxReadyTaskLists[1]),&(((TCB_t *)(&Task1TCB))->xStateListItem)); Task2_Handle = xTaskCreateStatic((TaskFunction_t)Task2_Entry, /*任务入口*/ (char *)"Task2", /*任务名称,字符串形式*/ (uint32_t)TASK2 STACK SIZE,/*任务栈大小,单位为字*/ (void *) NULL, /*任务形参*/ (StackType_t *)Task2Stack, /*任务栈起始地址*/ (TCB_t *)&Task2TCB); /*任务控制块*/ /**将任务添加到就绪列表/ vListInsertEnd(&( pxReadyTaskLists[2]),&(((TCB_t *)(&Task2TCB))->xStateListItem)); /*启动调度器,开始多任务调度,启动成功则不返回*/ vTaskStartScheduler(); for(;;) { /*系统启动成功不会到达这里*/ }}/*函数实现*//*软件延时*/void delay(unit32_t count){ for(; count != 0; count--);}/*任务一*/void Task1_Entry( void *p_arg){ for(;;) { flag1 = 1; delay(100); flag1 = 0; delay(100); /*任务切换*/ taskYIELD(); }}/*任务二*/void Task2_Entry( void *p_arg){ for(;;) { flag2 = 1; delay(100); flag2 = 0; delay(100); /*任务切换*/ taskYIELD(); }}
因为目前还不支持优先级,每个任务执行完毕后都主动调用任务切换函数taskYIELD()来实现任务的切换。调用子函数时的局部变量,中断发生时函数返回地址,是RAM里一段连续的内存空间,大小在启动文件或链接脚本里指定,最后由C库函数——main初始化。任务的函数实体、
任务切换函数taskYIELD()——在PendSV中断服务函数中实现任务切换——调用了vTaskSwitchContext()选择优先级最高的任务。
5.2、
由vTaskStartScheduler()函数完成调度器的启动——调用了xPortStartScheduler()函数,调度器启动成功——调用函数prvStartFirstTask()启动第一个任务。定义任务函数——main.c
3、定义就绪列表
List_t pxReadyTasksLists[ configMAX_PRIORITIES ]——定义在task.c
6、
prvStartFirstTask()函数用于开始第一个任务,一是更新MSP的值,二是产生SVC系统调用,然后去到SVC的中断服务函数里面真正切换到第一个任务。实现任务创建函数
xTaskCreateStatic()——在task.c定义,在task.h中声明——完成任务的创建
prvInitialiseNewTask()——在task.c实现
pxPortInitialiseStack()——port.c
5、启动调度器
调度器的启动由vTaskStartScheduler()完成,在task.c中定义。是一个预定义好的全局数组或一段内存空间,都存在于RAM中。
首先,实现就序列表。把整个流程理了一下,跟着视频复制粘贴了一下代码。pandas是什么?
定义就序列表——就序列表初始化prvInitialiseTaskLists()——将任务插入到就序列表。
3.2、6.1、任务创建与任务切换的整体流程
第一步是实现任务的实现。
5、
定义一个任务控制块需要一个新的数据类型,该数据类型在task.c文件中声明。
/* 初始化与任务相关的列表,如就绪列表 */prvInitialiseTaskLists();Task1_Handle = /* 任务句柄 */xTaskCreateStatic( (TaskFunction_t)Task1_Entry, /* 任务入口 */ (char *)"Task1", /* 任务名称,字符串形式 */ (uint32_t)TASK1_STACK_SIZE , /* 任务栈大小,单位为字 */ (void *) NULL, /* 任务形参 */ (StackType_t *)Task1Stack, /* 任务栈起始地址 */ (TCB_t *)&Task1TCB ); /* 任务控制块 */ /* 将任务添加到就绪列表 */ vListInsertEnd( &( pxReadyTasksLists[1] ), &( ((TCB_t *)(&Task1TCB))->xStateListItem ) );Task2_Handle = /* 任务句柄 */xTaskCreateStatic( (TaskFunction_t)Task2_Entry, /* 任务入口 */ (char *)"Task2", /* 任务名称,字符串形式 */ (uint32_t)TASK2_STACK_SIZE , /* 任务栈大小,单位为字 */ (void *) NULL, /* 任务形参 */ (StackType_t *)Task2Stack, /* 任务栈起始地址 */ (TCB_t *)&Task2TCB ); /* 任务控制块 *//* 将任务添加到就绪列表 */vListInsertEnd( &( pxReadyTasksLists[2] ), &( ((TCB_t *)(&Task2TCB))->xStateListItem ) );
6、3.3、2、实现调度器
2、实现调度器
调度器主要功能就是实现任务的切换,即从就绪列表中找到优先级最高的任务去执行。
裸机系统中:全局变量;子函数调用时的局部变量;中断发生时的函数返回地址,都放在栈中,栈是单片机RAM里的一段连续的内存空间,栈的大小一般在启动文件或者链接脚本里指定,最后又C库函数_main初始化。联系工作由任务创建函数xTaskCreateStatic()实现,该函数在task.c中定义,在task.h中声明。在task.c中定义。
/*任务就序列表*/List_t pxReadyTasksLists[ configMAX_PRIORITIES ];
这里的就绪列表是一个List_t类型的数组。
定义两个任务:
/*软件延时*/void delay(uint32_t ccount){ for (;count!=0;count--)}/*任务一*/void Task1_Entry(void *p_arg){ for(;;) { flag1=1; delay(100); flag1=0; delay(100); }}/*任务二*/void Task2_Entry(void *p_arg){ for(;;) { flag2=1; delay(100); flag2=0; delay(100); }}
任务是一个独立的,无限循环且不能返回的函数。将任务插入到就绪列表
vListInsertEnd( &( pxReadyTasksLists[1] ), &( ((TCB_t *)(&Task1TCB))->xStateListItem ) );
7、
任务控制块定义
/*定义任务控制块*/TCB_t Task1TCB;TCB_t Task2TCB;
3.4、但目前还不支持优先级,紧实现两个任务轮流切换,由任务切换函数实现taskYIELD()./* 在 task.h 中定义 */#define taskYIELD() portYIELD()/* 在 portmacro.h 中定义 *//* 中断控制状态寄存器: 0xe000ed04* Bit 28 PENDSVSET: PendSV 悬起位*/#define portNVIC_INT_CTRL_REG (*(( volatile uint32_t *) 0xe000ed04))#define portNVIC_PENDSVSET_BIT ( 1UL << 28UL )#define portSY_FULL_READ_WRITE ( 15 )#define portYIELD() \{ /* 触发 PendSV,产生上下文切换 */ portNVIC_INT_CTRL_REG = portNVIC_PENDSVSET_BIT; (1) __dsb( portSY_FULL_READ_WRITE ); __isb( portSY_FULL_READ_WRITE ); }
/* 在 task.h 中定义 */#define taskYIELD() portYIELD()/* 在 portmacro.h 中定义 *//* 中断控制状态寄存器: 0xe000ed04* Bit 28 PENDSVSET: PendSV 悬起位*/#define portNVIC_INT_CTRL_REG (*(( volatile uint32_t *) 0xe000ed04))#define portNVIC_PENDSVSET_BIT ( 1UL << 28UL )#define portSY_FULL_READ_WRITE ( 15 )#define portYIELD() \{ /* 触发 PendSV,产生上下文切换 */ portNVIC_INT_CTRL_REG = portNVIC_PENDSVSET_BIT; (1) __dsb( portSY_FULL_READ_WRITE ); __isb( portSY_FULL_READ_WRITE ); }
PendSV中断服务函数是真正实现任务切换的地方
__asm void xPortPendSVHandler( void )
调用函数vTaskSwitchContext,在task.c中定义,作用是选择优先级最高的任务,然后更新pxCurrentTCB.目前不支持优先级则手动切换,不是任务一就是任务二
void vTaskSwitchContext(void ){ /*两个任务轮流切换*/ if( pxCurrentTCB == &Task1TCB) { pxCurrentTCB = &Task2TCB; } else { pxCurrentTCB = &Task1TCB; }}
7、定义任务函数、
然后,由任务创建函数实现任务的栈、
然后,实现调度器。
8、任务切换
5.1、
定义任务栈:
#define TASK1_STACK_AIZE 128StackType_t TaskStack[TASK1_STACK_SIZE];#define TASK1_STACK_AIZE 128StackType_t TaskStack[TASK2_STACK_SIZE];
是一个预先定义好的全局数组,数据类型为StackType_t,,大小为128字,即512字节,是FreeRTOS推荐的最小任务栈 。
__asm void prvStartFirstTask( void ){ PRESERVE8 /* 在 Cortex-M 中, 0xE000ED08 是 SCB_VTOR 这个寄存器的地址, 里面存放的是向量表的起始地址,即 MSP 的地址 */ ldr r0, =0xE000ED08 ldr r0, [r0] ldr r0, [r0] /* 设置主堆栈指针 msp 的值 */ msr msp, r0 /* 使能全局中断 */ cpsie i cpsie f dsb isb /* 调用 SVC 去启动第一个任务 */ svc 0 nop nop}
vPortSVCHandler()函数开始真正启动第一个任务
__asm void vPortSVCHandler( void ){ extern pxCurrentTCB; PRESERVE8 ldr r3, =pxCurrentTCB; ldr r1, [r3] ldr r0, [r1] ldmia r0!, {r4-r11} msr psp, r0 isb mov r0, #0 msr basepri, r0 orr r14, #0xd bx r14 }
6.2、系统为了顺利调度任务,为每个任务都额外定义了一个任务控制块——含有任务的栈指针,任务名称,任务的形参。就绪列表初始化
函数 prvInitialiseTaskLists()实现就绪列表初始化。将任务插入到就绪列表
任务控制块里有一个xStateListItem成员,数据类型为ListItem_t,通过将任务控制块的xStateListItem这个节点插入到就绪列表中来实现的。定义任务函数
任务是一个独立的函数,函数主体无限循环且不能返回。定义任务控制块
任务控制块类型声明——一般在task.c,现在在FreeRTOS.h中
任务控制块定义——main.c
4、在task.c文件中实现。
定义任务函数是实现想要的功能。
#if(configSUPPORT_STATIC_ALLOCATION == 1)TaskHandle_t xTaskCreateStatic(TaskFunction_t pxTaskCode, const char * const pcName, const uint32_t ulStackDepth, void * const pvParameters, StackType_t * const puxStackBuffer, TCB_t * const pxTaskBuffer ){ TCB_t * pxNewTCB; TaskHandle_t xReturn; if((pxTaskBuffer != NULL) && (puxStackBuffer != NULL)) { pxNewTCB = ( TCB_t *) pxTaskBuffer; pxNewTCB->pxStack = (StackType_t * )puStackBuffer; /*创建新的任务*/ prvInitialiseNewTask ( pxTaskCode, //任务入口 pcName, //任务名称,字符串形式 uiStackDepth, //任务栈大小,单位为字 pvParameters, //任务形参 &xReturn, //任务句柄 pxNewTCB //任务栈起始地址 ) } else { xReturn = NULL; } return xReturn; }#endif
以上是任务的创建。为任务调度做准备。但是对具体函数的代码实现没看懂,不太懂,C语言还得加强学习。这部分放在问题的后面。目前不支持优先级则手动指定第一个要运行的任务。定义任务控制块。
定义任务栈是为了存放全局变量、定义任务控制块
在多任务系统中,任务的执行由系统调度。任务的定义
裸机系统中主题就是main函数里面的顺序执行的无限循环。
文章目录
- 前言
- 一、实现任务创建函数
任务的栈,任务的函数实体,任务的控制块需要联系起来才能由系统进行统一调度。任务切换
任务切换就是在就绪列表中寻找优先级最高的就绪任务,然后去执行该任务。
调用函数prvStartFirstTask()启动第一个任务,启动成功后不在返回。
#define portNVIC_SYSPRI2_REG (*((volatile uint32_t *) 0xe000ed20))#define portNVIC_PENDSV_PRI (((uint32_t) configKERNEL_INTERRUPT_PRIORITY ) << 16UL)#define portNVIC_SYSTICK_PRI (((uint32_t) configKERNEL_INTERRUPT_PRIORITY ) << 24UL )BaseType_t xPoreStartScheduler(void){ /*配置PendSV和SysTick的中断优先级为最低*/ portNVIC_SYSPRI2_REG |= portNVIC_PENDSV_PRI; portNVIC_SYSPRI2_REG |= portNVIC_SYSTICK_PRI; /*启动第一个任务,不在返回*/ prvStartFirstTask(); /*不应该运行到这里*/ return 0;}
SysTick和PendSV都会涉及到系统调度,系统调度的优先级要低于系统的其他的硬件中断优先级,即相应系统中的外部硬件中断,所以优先级配置为最低。
看到这里,感觉就有点迷糊了,对任务创建和任务切换的流程理不清而感到特别困惑,同时因为C语言知识的薄弱,对这些函数的定义方法和具体功能实现的步骤理解起来有点困难。任务的控制块联系起来由系统统一调度。
最后,实现任务切换。
多任务系统中根据功能的不同把整个系统分割成独立的无法返回的函数,这个函数称为任务。 由调度器实现任务切换。
到了这一步任务的创建就完成了,第二步是任务切换的实现。
任务创建函数xTaskCreatStatic()——调用了prvInitialiseNewTask()函数创建新任务——调用了pxportInitialiseStack()函数初始化任务栈,并更新栈顶指针,任务第一次运行的环境参数就存在任务栈中。
定义任务控制块相当于任务的身份证,系统对任务的全部操作通过任务控制块实现。
void vTaskStartScheduler(void){ /*手动指定第一个运行的任务*/ pxCurrentTCB = &Task1TCB; /*启动调度器*/ if (xPortStartScheduler() != pdFALSE) { /*调度器启动失败来这里*/ }}
pxCurrentTCB是一个在task.c定义的全局变量,用于指向当前正在运行或即将要运行的任务的任务控制块。
调用函数xPortStartScheduler()启动调度器,调度器启动成功则不会返回。
今天是学习FreeRTOS的第三天,这里主要记录一下自己学习的方法吧,在学习新知识的过程中,会遇到很多新的概念导致理解起来困难,不能整体把握,同时也是为了提高自己的词汇量,让表达描述更加规范,所以我打算在按照野火的实战指南学习时除了记录自己遇到的问题,还要记录一下新遇到的概念名词。在port.c中实现。在port.c实现。定义就绪列表
任务创建好后需要把任务添加到就绪列表中表示任务已就绪,系统随时可以调度。定义任务栈——重命名在portmacro.h,重定义在main.c
2、
在任务创建好后,将任务插入到就绪列表。学习目标
学会创建任务,重点掌握任务如何切换。
在本章中会创建两个任务并让这两个任务不断切换,任务的主体是让一个变量按照一定的频率翻转,通过仿真在逻辑分析仪中观察变量的波形变化,两个任务波形应该一样。问题
C语言指针的使用
10、
在FreeRTOS中凡是涉及到数据类型的地方,FreeRTOS都会将标准的C数据类型用typedef重新取一个类型名,放在potmacro.h这个头文件中。就绪列表初始化
prvInitialiseTaskLists()
6、使用步骤
- 1.引入库
- 2.读入数据
- 总结
前言
本章要记录学习的内容是任务的定义与任务切换的实现
1、任务切换
taskYIELD()——portYIELD()——触发PendSV产生上下文中断
xPortPendSVHandler()函数——是真正实现任务切换的地方
vTaskSwitchContext()函数——在task.c定义——选择优先级最高的任务,然后更新pxCurrentTCB.9、
3.1定义任务栈
定义任务栈应该就是为了给任务的运行提供基础设施平台。
任务控制块类型声明:
typedef struct tskTaskControlBlock{ volatile StackType_t *pxTopOfStack; /*栈顶*/ ListItem_t xStateListItem; /*任务节点*/ StackType_t *pxStack; /*任务栈起始地址*/ char pcTaskName[ configMAX_TASK_NAME_LEN]; /*任务名称,字符串形式*/}tskTCB;typedef tskTCB TCB_t;
第二个成员定义的任务节点,是一个内置在TCB控制块中的链表节点,通过这个节点可以将任务控制块连接到各种链表中。