裸机编程通过中断实现不同任务的切换,实际上RTOS中通过不断更换CPU的使用权达到多任务运行的目的。FreeRTOS 中任务存在四种任务状态,分别为运行态、就绪态、阻塞态和挂起态。任务一般通过函数 vTaskSuspend()和函数 vTaskResums()进入和退出挂起态,处于挂起态的任务无法被运行;一般通过函数 vTaskDelay(),进行一段时间的延时,那么在延时超时之前,这个任务就处理阻塞态。任务也可以处于阻塞态以等待队列、信号量、事件组、通知或信号量等外部事件,但阻塞状态一定有延时时间。
FreeRTOS 的任务优先级高低与其对应的优先级数值,是成正比的,也就是说任务优先级数
值为 0 的任务优先级是最低的任务优先级,FreeRTOS 的任务优先级高低与其对应数值的逻辑关系正好与STM32 的中断优先级高低与其对应数值的逻辑关系相反。
FreeRTOS 的官方强烈建议 STM32 在使用 FreeRTOS 的时候,使用中断优先级分组 4(NVIC_PriorityGroup_4)即0-15级抢占优先级,0级子优先级。HAL_NVIC_SetPriorityGrouping(NVIC_PRIORITYGROUP_4);
FreeRTOS 进出临界区:临界区是指那些必须完整运行的区域,在临界区中的代码必须完整运行,不能被打断。FreeRTOS 在进出临界区的时候,通过关闭和打开受 FreeRTOS 管理的中断但只是管理RTOS管理的优先级5之后的中断,以保护临界区中的代码。taskENTER_CRITICAL() 、 taskENTER_CRITICAL_FROM_ISR() 、 taskEXIT_CRITICAL() 、taskEXIT_CRITICAL_FROM_ISR(x),这四个宏定义分别用于在中断和非中断中进出临界区。在初始化任务时候使用。
FreeRTOS 任务调度方式为抢占式调度和时间片调度相结合。抢占式调度主要时针对优先级不同的任务,每个任务都有一个优先级,优先级高的任务可
以抢占优先级低的任务,只有当优先级高的任务发生阻塞或者被挂起,低优先级的任务才可以运行。时间片调度主要针对优先级相同的任务,当多个任务的优先级相同时,任务调度器会在每一次系统时钟节拍到的时候切换任务,也就是说 CPU 轮流运行优先级相同的任务,每个任务运行的时间就是一个系统时钟节拍。
静态创建任务需要自己设定栈等参数,一般都直接选着动态创建。
列表是 FreeRTOS 中最基本的一种数据结构,其在物理存储单元上是非连续、非顺序的。FreeRTOS 中的列表是一个双向链表,在list.h 文件中。列表项是列表中用于存放数据的地方,也在 list.h 文件中。
列表的意义:
跟踪任务: 列表被用来跟踪FreeRTOS中的任务。通过使用列表,FreeRTOS能够有效地管理任务的执行和调度。
数据结构: 列表是FreeRTOS中的一个数据结构,与链表类似。它提供了一种组织和管理任务的方式,使得任务的添加、删除和调度变得高效。
双向环形链表: FreeRTOS中的列表是一个双向环形链表,意味着列表的头和尾可以相互连接,形成一个闭环。这种设计有助于简化某些操作的实现,如任务的插入和删除。
列表项的意义:
任务信息存储: 列表项是存放在列表中的项目,它们包含了任务的相关信息。这些信息可能包括任务的优先级、状态、挂起条件等,对于FreeRTOS的任务管理和调度至关重要。
节点概念: 在数据结构中,列表项相当于链表中的节点。每个节点都包含有关任务的信息,并且可以通过指针与其他节点(任务)相互连接。
灵活性: 列表项的使用提供了灵活性,允许用户根据需要自定义任务的信息和属性。通过修改列表项的内容,用户可以实现对任务的精细控制和定制化操作。
函数 vTaskStartScheduler()用于启动任务调度器,任务调度器启动后,FreeRTOS 便会开始
进行任务调度,除非调用函数 xTaskEndScheduler()停止任务调度器,否则不会再返回。
调用函数 vTaskGetRunTimeStats()获取并通过串口打印系统任务运行时间信息,以此来合理进行每个任务资源的分配。
FreeRTOS 的系统时钟节拍计数器为全局变量 xTickCount,那么 FreeRTOS 又是何时操作这
个系统时钟节拍计数器的呢?本教程的配套例程而言,是在 SysTick 的中断服务函数中,一般也推荐使用 SysTick 作为 RTOS 的时钟节拍。
vTaskDelay() 任务延时函数,延时单位:系统时钟节拍。函数 vTaskDelay()传入的参数 xTicksToDelay 是任务被延时的具体延时时间,时间的单位为系统时钟节拍,这里要特别注意,很多 FreeRTOS 的初学者可能会一会此函数延时的时间单位为微妙、毫秒、秒等物理时间单位,当时 FreeRTOS 是以系统时钟节拍作为计量的时间单位 的 , 而 系 统 时 钟 节 拍 对 应 的 物 理 时 间 长 短 于 FreeRTOSConfig.h 文 件 中 的 配 置 项。
配置项 configTICK_RATE_HZ 是用于配置系统时钟节拍的频率的,本教程的所有配套例程,将此配置项配置成了 1000,即系统时钟节拍的频率为 1000,换算过来,一个系统时钟节拍就是 1 毫秒。
队列是一种任务到任务、任务到中断、中断到任务数据交流的一种机制。在队列中可以存
储数量有限、大小固定的多个数据,队列中的每一个数据叫做队列项目,队列能够存储队列项目的最大数量称为队列的长度,在创建队列的时候,就需要指定所创建队列的长度及队列项目的大小。因为队列是用来在任务与任务或任务于中断之间传递消息的一种机制,因此队列也叫做消息队列。队列通常采用 FIFO(先进先出)的存储缓冲机制,当有新的数据被写入队列中时,永远都是写入到队列的尾部,而从队列中读取数据时,永远都是读取队列的头部数据
队列不属于某个特定的任务,可以在任何的任务或中断中往队列中写入消息,或者从队列
中读取消息。
队列的结构体为 Queue_t,在 queue.c 文件中有定义
在使用队列进行任务之间的“沟通交流”时,一个队列只允许任务间传递的消息为同一种
数据类型,如果需要在任务间传递不同数据类型的消息时,那么就可以使用队列集。FreeRTOS
提供的队列集功能可以对多个队列进行“监听”,只要被监听的队列中有一个队列有有效的消息,那么队列集的读取任务都可以读取到消息,如果读取任务因读取队列集而被阻塞,那么队列集将解除读取任务的阻塞。
创建队列、队列集和其他任务,并添加队列到队列集中.
/* 创建队列集 */
xQueueSet = xQueueCreateSet(QUEUESET_LENGTH);
/* 创建队列 */
xQueue1 = xQueueCreate(QUEUE_LENGTH, QUEUE_ITEM_SIZE);
xQueue2 = xQueueCreate(QUEUE_LENGTH, QUEUE_ITEM_SIZE);
/* 创建二值信号量 */
xSemaphore = xSemaphoreCreateBinary();
/* 将队列和二值信号量添加到队列集 */
xQueueAddToSet(xQueue1, xQueueSet);
xQueueAddToSet(xQueue2, xQueueSet);
xQueueAddToSet(xSemaphore, xQueueSet);
xQueueSend(xQueue1, &key, portMAX_DELAY);/* 队列 1 发送消息 */
xSemaphoreGive(xSemaphore);/* 释放二值信号量 */
activate_member = xQueueSelectFromSet(xQueueSet, portMAX_DELAY);
xQueueReceive(activate_member, &queue_recv, portMAX_DELAY);
printf("接收到来自 xQueue1 的消息: %d\r\n", queue_recv);
xSemaphoreTake(activate_member, portMAX_DELAY);
printf("获取到二值信号量: xSemaphore\r\n");
基于队列和队列集实现了不同任务之间的信息交流。
信号量可以用在多任务访问同一资源时的资源管理FreeRTOS 提供了多种信号量,按信号量的功能可分为二值信号量、计数型信号量、互斥信号量和递归互斥信号量。
解决同步问题的机制,可以实现对共享资源的有序访问。其中,“同步”指的是任务间的同步,即信号量可以使得一个任务等待另一个任务完成某件事情后,才继续执行;而“有序访问”指的是对被多任务或中断访问的共享资源(如全局变量)的管理,当一个任务在访问(读取或写入)一个共享资源时,信号量可以防止其他任务或中断在这期间访问(读取或写入)这个共享资源。
前面说过,信号量是基于队列实现的,二值信号量也不例外,二值信号量实际上就是一个
队列长度为 1 的队列,在这种情况下,队列就只有空和满两种情况,这不就是二值情况吗?二值信号量通常用于互斥访问或任务同步。
计数型信号量与二值信号量类似,二值信号量相当于队列长度为 1 的队列,因此二值信号
量只能容纳一个资源,这也是为什么命名为二值信号量,而计数型信号量相当于队列长度大于0 的队列,因此计数型信号量能够容纳多个资源,这是在计数型信号量被创建的时候确定的。
/* 创建计数型信号量 */
CountSemaphore =
xSemaphoreCreateCounting( (UBaseType_t)255, /* 计数型信号量最大值 */
(UBaseType_t)0); /* 计数型信号量初始值 */
/* 释放计数型信号量 */
xSemaphoreGive(CountSemaphore);
/* 获取计数型信号量资源数 */
semaphore_val = uxSemaphoreGetCount(CountSemaphore);
互斥信号量其实就是一个拥有优先级继承的二值信号量,在同步的应用中(任务与任务或
中断与任务之间的同步)二值信号量最适合。互斥信号量适合用于那些需要互斥访问的应用中。在互斥访问中互斥信号量相当于一把钥匙,当任务想要访问共享资源的时候就必须先获得这把钥匙,当访问完共享资源以后就必须归还这把钥匙,这样其他的任务就可以拿着这把钥匙去访问资源。