freeRTOS
##列表和列表项
列表初始化
void vListInitialise( List_t * const pxList )
{
pxList->pxIndex = ( ListItem_t * ) &( pxList->xListEnd ); (1)
pxList->xListEnd.xItemValue = portMAX_DELAY; (2)
pxList->xListEnd.pxNext = ( ListItem_t * ) &( pxList->xListEnd ); (3)
pxList->xListEnd.pxPrevious = ( ListItem_t * ) &( pxList->xListEnd );(4)
pxList->uxNumberOfItems = ( UBaseType_t ) 0U; (5)
listSET_LIST_INTEGRITY_CHECK_1_VALUE( pxList ); (6)
listSET_LIST_INTEGRITY_CHECK_2_VALUE( pxList ); (7)
}
列表项初始化
void vListInitialiseItem( ListItem_t * const pxItem )
插入列表项
void vListInsert( List_t * const pxList, ListItem_t * const pxNewListItem );
//pxList 列表项要插入的列表 pxNewListItem 要插入的列表项
这是插入了一个值为40和一个值为60的列表项。
节点的next指向下一个列表的头节点,pxprevious指向上一个列表的头节点。
末尾插入列表项
void vListInsertEnd( List_t * const pxList, ListItem_t * const pxNewListItem );
删除列表项
UBaseType_t uxListRemove( ListItem_t * const pxItemToRemove );
列表的遍历
#define listGET_OWNER_OF_NEXT_ENTRY( pxTCB, pxList )
任务创建
xTaskCreateStatic();
任务创建的方法:(configSUPPORT_STATIC_ALLOCATION == 1)
- 动态创建:任务控制块和栈的内存是创建任务时动态分配的,任务删除的时候,内存可以释放。
- 静态创建:任务控制块和栈的内存需要事先定义好,是静态的内存,任务删除时,内存不能释放。
就绪列表
任务创建好之后,我们需要把任务添加到就绪列表里面,表示任务已经就绪,
系统随时可以调度。就绪列表在 task.c 中定义,具体代码如下。
List_t pxReadyTasksLists[ configMAX_PRIORITIES ];
就绪列表使用前需要初始化。
static void prvInitialiseTaskLists( void );
将任务插入就绪列表:
void vListInsertEnd( List_t * const pxList, ListItem_t * const
pxNewListItem )
实现调度器
调度器是操作系统的核心,其主要功能就是实现任务的切换,即从就绪列表里面找到优先级最高的任务,然后去执行该任务。从代码上来看,调度器无非也就是由几个全局变量和一些可以实现任务切换的函数组成,全部都在 task.c 文件中实现。
void vTaskStartScheduler( void );
任务切换
taskYIELD();
任务创建——静态任务
定义任务函数
vTaskDelay(800);
任务里面的延时函数必须使用 FreeRTOS 里面提供的延时函数,并不能使用我们裸机编程中的那种延时。这两种的延时的区别是 FreeRTOS 里面的延时是阻塞延时,即调用 vTaskDelay()函数的时候,当前任务会被挂起,调度器会切换到其它就绪的任务,从而实现多任务。如果还是使用裸机编程中的那种延时,那么整个任务就成为了一个死循环,如果恰好该任务的优先级是最高的,那么系统永远都是在这个任务中运行,比它优先级更低的任务无法运行,根本无法实现多任务。但 FreeRTOS 中的延时最小值只能是 ms,对于 us 级别的延时怎
么办呢,这个在前面移植操作系统时我们介绍过,使用 SysTick.c 文件中的延时函数亦可,包括 ms 和 us 级延时,这两个延时函数与裸机实验中是不一样的,所以不要搞混。
空闲任务与定时器任务堆栈函数
当我们使用了静态创建任务的时候,configSUPPORT_STATIC_ALLOCATION 这个宏定义必须为 1(在 FreeRTOSConfig.h 文件中),并且我们需要实现两个函数:vApplicationGetIdleTaskMemory() vApplicationGetTimerTaskMemory(),这两个函数是用户设定的空闲(Idle)任务与定时器(Timer)任务的堆栈大小,必须由用户自己分配,而不能是动态分配,具体代码如下。
定义任务栈和任务控制块
当任务进入延时的时候,因为没有另外就绪的用户任务,那么系统就会进入空闲任务,空闲任务是 FreeRTOS 系统自己启动的一个任务,优先级最低。当整个系统都没有就绪任务的时候,系统必须保证有一个任务在运行,空闲任务就是为这个设计的。当用户任务延时到期,又会从空闲务切换回用户任务。
在 FreeRTOS 系统中,每一个任务都是独立的,他们的运行环境都单独的保存在他们的栈空间当中。那么在定义好任务函数之后,我们还要为任务定义一个栈,目前我们使用的是静态内存,所以任务栈是一个独立的全局变量,具体见代码。任务的栈占用的是 MCU 内部的 RAM,当任务越多的时候,需要使用的栈空
间就越大,即需要使用的 RAM 空间就越多。一个 MCU 能够支持多少任务,就得看芯片的 RAM 空间有多少。
//任务堆栈大小
#define TASK1_STK_SIZE 128
99
//任务堆栈
StackType_t Task1TaskStack[TASK1_STK_SIZE];
//任务堆栈大小
#define TASK2_STK_SIZE 128
//任务堆栈
StackType_t Task2TaskStack[TASK2_STK_SIZE];
定义好任务函数和任务栈之后,我们还需要为任务定义一个任务控制块,通常我们称这个任务控制块为任务的身份证。在 C 代码上,任务控制块就是一个结构体,里面有非常多的成员,这些成员共同描述了任务的全部信息。
//任务控制块
StaticTask_t Task1TaskTCB;
//任务控制块
StaticTask_t Task2TaskTCB;
静态创建任务函数
一个任务的三要素是任务主体函数,任务栈,任务控制块,那么怎么样把这
三个要素联合在一起?FreeRTOS 里面有一个叫静态任务创建函数
xTaskCreateStatic(),它就可以实现。它将任务主体函数,任务栈(静态的)
和任务控制块(静态的)这三者联系在一起,让任务可以随时被系统启动,具体
如下。
//创建开始任务
StartTask_Handler=xTaskCreateStatic((TaskFunction_t )start_task, //任务函数 (1)
(const char* )"start_task",//任务名称 (2) 最好和任务函数名称相同
(uint32_t )START_STK_SIZE,//任务堆栈大小(3)
(void* )NULL,//传递给任务函数的参数 (4)
(UBaseType_t )START_TASK_PRIO, //任务优先级 (5)
(StackType_t* )StartTaskStack,//任务堆栈 (6)
(StaticTask_t* )&StartTaskTCB);//任务控制块 (7)
启动任务
当任务创建好后,是处于任务就绪(Ready),在就绪态的任务可以参与操
作系统的调度。但是此时任务仅仅是创建了,还未开启任务调度器,也没创建空
闲任务与定时器任务(如果使能了 configUSE_TIMERS 这个宏定义),那这两个
任务就是在启动任务调度器中实现,每个操作系统,任务调度器只启动一次,之
后就不会再次执行了,FreeRTOS 中启动任务调度器的函数是
vTaskStartScheduler(),并且启动任务调度器的时候就不会返回,从此任务管
理都由 FreeRTOS 管理,此时才是真正进入实时操作系统中的第一步。
动态任务创建
任务管理
任务的简介
在 FreeRTOS 中,任务可以使用或等待 CPU、使用内存空间等系统资源,并独立于其它任务运行,任何数量的任务可以共享同一个优先级,如果宏 configUSE_TIME_SLICING 定义为 1,处于就绪态的多个相同优先级
任务将会以时间片切换的方式共享处理器。
- FreeRTOS 调度器决定运行哪个任务
- 在任务切入切出时保存上下文环境(寄存器值、堆栈内容)是调度器主要的职责
- 为了实现这点,每个 FreeRTOS 任务都需要有自己的栈空间
当任务切出时,它的执行环境会被保存在该任务的栈空间中,这样当任务再次运行时,就能从堆栈中正确的恢复上次的运行环境,任务越多,需要的堆栈空间就越大,而一个系统能运行多少个任务,取决于系统的可用的 SRAM。
任务的四个状态
图(1):创建任务→就绪态(Ready):任务创建完成后进入就绪态,表明任务已准备就绪,随时可以运行,只等待调度器进行调度。
图(2):就绪态→运行态(Running):发生任务切换时,就绪列表中最高优先级的任务被执行,从而进入运行态。
图(3):运行态→就绪态:有更高优先级任务创建或者恢复后,会发生任务调度,此刻就绪列表中最高优先级任务变为运行态,那么原先运行的任务由运行态变为就绪态,依然在就绪列表中,等待最高优先级的任务运行完毕继续运行原来的任务(此处可以看做是 CPU 使用权被更高优先级的任务抢占了)。
图(4):运行态→阻塞态(Blocked):正在运行的任务发生阻塞(挂起、延时、读信号量等待)时,该任务会从就绪列表中删除,任务状态由运行态变成阻塞态,然后发生任务切换,运行就绪列表中当前最高优先级任务。
图(5):阻塞态→就绪态:阻塞的任务被恢复后(任务恢复、延时时间超时、读信号量超时或读到信号量等),此时被恢复的任务会被加入就绪列表,从而由阻塞态变成就绪态;如果此时被恢复任务的优先级高于正在运行任务的优先级,则会发生任务切换,将该任务将再次转换任务状态,由就绪态变成运行态。图(6)(7)(8):就绪态、阻塞态、运行态→挂起态(Suspended):任务可以通过调用 vTaskSuspend() API 函数都可以将处于任何状态的任务挂起,被挂起的任务得不到 CPU 的使用权,也不会参与调度,除非它从挂起态中解除。图(9):挂起态→就绪态:把一个挂起状态的任务恢复的唯一途径就是调用vTaskResume() 或 vTaskResumeFromISR() API 函数,如果此时被恢复任务的优先级高于正在运行任务的优先级,则会发生任务切换,将该任务将再次转换任务状态,由就绪态变成运行态。
消息队列
消息队列数据存储
队列采用先进先出(FIFO)的存储缓冲机制
消息队列阻塞机制
- 出队阻塞 当任务尝试从消息队列读取消息的时候,现在消息队列里面并没有消息,现在就要选择是否进行等待。
- 入队阻塞 同理,当消息进入消息队列的时候,如果此时消息队列满了,那么也要进行这个选择
当设置阻塞的时候就是进行这个选择,等待或者不等待,或者等待多久?
cubeIDE
Cube-pas = xYK20011018!
##时钟
HSI:内部高速时钟,精度不高
HSE:需要外接晶振,时间精度高
PLLCLK:经过分频器、倍频器后的频率。
基本常识——freeRTOS
当开启freeRTOS的任务调度以后,主程序中的while后的代码不会执行,考虑是因为进入了freertos的任务调度。