这是基于HAL库的
正点原子手把手教你学FreeRTOS实时系统
这是基于标准库的
正点原子FreeRTOS手把手教学-基于STM32
基础知识,直接参考正点原子《FreeRTOS开发指南V1.1》基于标准库的,此处不再赘述。
本文主要对不理解的地方进行查缺补漏,并且先用起来,涉及到原理的部分,可以观看上述教学视频或者开发指南。
官网
官网首页:
FreeRTOS - Market leading RTOS (Real Time Operating System) for embedded systems with Internet of Things extensions
官网参考手册:
官网参考手册:FreeRTOS 内核快速入门指南
注意事项:
官网进入比较慢,API估计都进不去。
这时如果需要查看API,可以查看正点原子给的几个文档
在这几个文档里搜索一下应该就能找到,可以优先看中文文档,没有的话再看英文文档。
使用步骤
1、参考正点原子《FreeRTOS开发指南V1.1》“第二章 FreeRTOS 移植”,进行移植。
其中,如果不用正点原子system目录中的三个文件,可以删除。
STM32裸机可以使用systick延时,但是操作系统就没必要使用systick来实现延时了。
如果删除这三个文件,那么这部分跟RTOS相关的内容就需要单独设置。
其中最相关的就是systick的使用,因为裸机中systick被用来做延时,但是到了操作系统中,systick就要用来做系统的时钟节拍了,不推荐用来延时(但是正点原子实现了鱼与熊掌都可兼得)。
这里插一句,正点原子的移植在这块耦合性太强了,就是说最好使用他的这三个文件,如果不用,移植过程就不太一样了。
这一块颇为麻烦。
所以,试图换个教程看看如何移植的
更换教程链接如下
2.3-FreeRTOS移植--FreeRTOS移植_哔哩哔哩_bilibili
该教程在工程建立之前的步骤和正点原子几乎是一致的。
所以,文件移植部分,可以参考这里,也可以参考正点原子。
绕过正点原子修改SYSTEM文件夹等后续环节,之后的移植部分参考上述视频链接,并在此记录。
引入FreeRTOSConfig.h配置文件后,编译,会提示以下内容:
这是因为freertos中也定义了SVC 中断服务函数SVC_Handler()和 PendSV 中断服务函数PendSV_Handler(),而我们STM32的中断文件stm32f10x_it.c中也定义了。
所以,需要将STM32定义的这两个空中断处理函数给屏蔽掉。
之后再编译就没有错误了。
接下来,还需要配置systick时钟。
看到这里,发现这个视频应该也是参考的正点原子。
但是看了这个视频,可以确认SYSTEM中只有delay.c这个文件和freertos关系比较大,因为就是在这里配置了systick,所以,我们干脆把正点原子的delay.c拿来使用,并将文件改名为systick.c,反正这个文件既能实现延时,也能实现任务调度。
注意,正点原子是将systick的中断服务函数放到了delay.c中,所以我们在stm32中断处理文件中,将SysTick_Handler(void)函数也屏蔽掉。
在systick.c文件中,跟freertos使用systick定时器配置的部分是这几个部分:
这里有个频率设置configTICK_RATE_HZ,是在FreeRTOSConfig.h文件中定义的,我们直接在配置文件中更改,就能更改相应的中断间隔,默认是1000Hz,即每1/1000s,也就是1ms产生一次中断,也就是说,任务调度的时间片就是1ms。
初始化之后,就会周期性地调用systick的中断服务函数。
其中,xPortSysTickHandler();函数是在port.c函数中定义的。
另外,提下该文件中的几个延时函数。
delay_ms中,在操作系统运行时,调用了vTaskDelay函数,所以会引起任务调度,需要注意,delay_xms是单纯调用delay_us实现的,所以不会引起任务调度。
2、参考正点原子《FreeRTOS开发指南V1.1》“第三章 FreeRTOS 系统配置 ”,熟悉配置文件FreeRTOSConfig.h
/***************************************************************************************************************/ /* FreeRTOS基础配置配置选项 */ /***************************************************************************************************************/ #define configUSE_PREEMPTION 1 //1使用抢占式内核,0使用协程 #define configUSE_TIME_SLICING 1 //1使能时间片调度(默认式使能的) #define configUSE_PORT_OPTIMISED_TASK_SELECTION 1 //1启用特殊方法来选择下一个要运行的任务 //一般是硬件计算前导零指令,如果所使用的 //MCU没有这些硬件指令的话此宏应该设置为0! #define configUSE_TICKLESS_IDLE 0 //1启用低功耗tickless模式 #define configUSE_QUEUE_SETS 1 //为1时启用队列 #define configCPU_CLOCK_HZ (SystemCoreClock) //CPU频率 #define configTICK_RATE_HZ (1000) //时钟节拍频率,这里设置为1000,周期就是1ms #define configMAX_PRIORITIES (32) //可使用的最大优先级 #define configMINIMAL_STACK_SIZE ((unsigned short)130) //空闲任务使用的堆栈大小 #define configMAX_TASK_NAME_LEN (16) //任务名字字符串长度 #define configUSE_16_BIT_TICKS 0 //系统节拍计数器变量数据类型, //1表示为16位无符号整形,0表示为32位无符号整形 #define configIDLE_SHOULD_YIELD 1 //为1时空闲任务放弃CPU使用权给其他同优先级的用户任务 #define configUSE_TASK_NOTIFICATIONS 1 //为1时开启任务通知功能,默认开启 #define configUSE_MUTEXES 1 //为1时使用互斥信号量 #define configQUEUE_REGISTRY_SIZE 8 //不为0时表示启用队列记录,具体的值是可以 //记录的队列和信号量最大数目。 #define configCHECK_FOR_STACK_OVERFLOW 0 //大于0时启用堆栈溢出检测功能,如果使用此功能 //用户必须提供一个栈溢出钩子函数,如果使用的话 //此值可以为1或者2,因为有两种栈溢出检测方法。 #define configUSE_RECURSIVE_MUTEXES 1 //为1时使用递归互斥信号量 #define configUSE_MALLOC_FAILED_HOOK 0 //1使用内存申请失败钩子函数 #define configUSE_APPLICATION_TASK_TAG 0 #define configUSE_COUNTING_SEMAPHORES 1 //为1时使用计数信号量 /***************************************************************************************************************/ /* FreeRTOS与内存申请有关配置选项 */ /***************************************************************************************************************/ #define configSUPPORT_DYNAMIC_ALLOCATION 1 //支持动态内存申请 #define configTOTAL_HEAP_SIZE ((size_t)(20*1024)) //系统所有总的堆大小 /***************************************************************************************************************/ /* FreeRTOS与钩子函数有关的配置选项 */ /***************************************************************************************************************/ #define configUSE_IDLE_HOOK 0 //1,使用空闲钩子;0,不使用 #define configUSE_TICK_HOOK 0 //1,使用时间片钩子;0,不使用 /***************************************************************************************************************/ /* FreeRTOS与运行时间和任务状态收集有关的配置选项 */ /***************************************************************************************************************/ #define configGENERATE_RUN_TIME_STATS 0 //为1时启用运行时间统计功能 #define configUSE_TRACE_FACILITY 1 //为1启用可视化跟踪调试 #define configUSE_STATS_FORMATTING_FUNCTIONS 1 //与宏configUSE_TRACE_FACILITY同时为1时会编译下面3个函数 //prvWriteNameToBuffer(),vTaskList(), //vTaskGetRunTimeStats() /***************************************************************************************************************/ /* FreeRTOS与协程有关的配置选项 */ /***************************************************************************************************************/ #define configUSE_CO_ROUTINES 0 //为1时启用协程,启用协程以后必须添加文件croutine.c #define configMAX_CO_ROUTINE_PRIORITIES ( 2 ) //协程的有效优先级数目 /***************************************************************************************************************/ /* FreeRTOS与软件定时器有关的配置选项 */ /***************************************************************************************************************/ #define configUSE_TIMERS 1 //为1时启用软件定时器 #define configTIMER_TASK_PRIORITY (configMAX_PRIORITIES-1) //软件定时器优先级 #define configTIMER_QUEUE_LENGTH 5 //软件定时器队列长度 #define configTIMER_TASK_STACK_DEPTH (configMINIMAL_STACK_SIZE*2) //软件定时器任务堆栈大小 /***************************************************************************************************************/ /* FreeRTOS可选函数配置选项 */ /***************************************************************************************************************/ #define INCLUDE_xTaskGetSchedulerState 1 #define INCLUDE_vTaskPrioritySet 1 #define INCLUDE_uxTaskPriorityGet 1 #define INCLUDE_vTaskDelete 1 #define INCLUDE_vTaskCleanUpResources 1 #define INCLUDE_vTaskSuspend 1 #define INCLUDE_vTaskDelayUntil 1 #define INCLUDE_vTaskDelay 1 #define INCLUDE_eTaskGetState 1 #define INCLUDE_xTimerPendFunctionCall 1 /***************************************************************************************************************/ /* FreeRTOS与中断有关的配置选项 */ /***************************************************************************************************************/ #ifdef __NVIC_PRIO_BITS #define configPRIO_BITS __NVIC_PRIO_BITS #else #define configPRIO_BITS 4 #endif #define configLIBRARY_LOWEST_INTERRUPT_PRIORITY 15 //中断最低优先级 #define configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY 5 //系统可管理的最高中断优先级 #define configKERNEL_INTERRUPT_PRIORITY ( configLIBRARY_LOWEST_INTERRUPT_PRIORITY << (8 - configPRIO_BITS) ) #define configMAX_SYSCALL_INTERRUPT_PRIORITY ( configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY << (8 - configPRIO_BITS) ) /***************************************************************************************************************/ /* FreeRTOS与中断服务函数有关的配置选项 */ /***************************************************************************************************************/ #define xPortPendSVHandler PendSV_Handler #define vPortSVCHandler SVC_Handler
直接查看对应的注释即可。
补充说明
✔
每个任务都有自己的堆栈。设置堆栈大小时,单位是字,也就是四个字节。
✔
任务优先级理论上是无限的,但因为和configUSE_PORT_OPTIMISED_TASK_SELECTION这个宏的功能相关,也就是和硬件相关,所以最大只能设置到32。不过一般也不会用到32个优先级。
✔
关于宏configUSE_16_BIT_TICKS
✔
宏configCHECK_FOR_STACK_OVERFLOW一般只在调试阶段打开。
✔
暂且认为钩子函数就是回调函数吧。
✔
heap4.c中实现了动态内存管理函数。
堆的内存池大小也在heap4.c中有使用。
这里总堆大小的单位是字节,定义的是20K,具体情况具体设置。
✔
这些都是跟调试相关的
3、熟悉正点原子《FreeRTOS开发指南V1.1》“第四章 FreeRTOS 中断配置和临界段 ”
✔在使用FREERTOS时,需要将STM32的优先级分组设置为分组4,即 NVIC_PriorityGroup_4这样的话优先级就都全是抢占优先级了,没有亚优先级,那么就有 0~15 共 16 个优先级。因为FREERTOS有抢占式调度,其中断配置没有处理亚优先级这种情况,为了更好地实现抢占式调度,所以只使用抢占优先级。✔FreeRTOS 的开关中断就是操作 BASEPRI 寄存器来实现的!它可以关闭低于某个阈值的中断,高于这个阈值的中断就不会被关闭!这里注意,STM32的中断机制中,数值越小,优先级越高,数值越大,优先级越低。比如,设置关中断时设置阈值为5,那么数值高于5的中断都会被关闭,而数值低于5的中断就不会被关闭。✔PendSV 和 SysTick 优先级在函数 xPortStartScheduler()中设置,该函数在启动任务调度的函数vTaskStartScheduler中被调用,即PendSV 和 SysTick 优先级在开启任务调度器时被设置,而且在 FreeRTOS中 PendSV 和 SysTick 的中断优先级都是最低的!都是15。✔✔注意临界区代码一定要精简!因为进入临界区会关闭中断,这样会导致优先级低于 configMAX_SYSCALL_INTERRUPT_PRIORITY 的中断得不到及时的响应!✔函数 taskENTER_CRITICAL_FROM_ISR() 和 taskEXIT_CRITICAL_FROM_ISR() 中断级别临界段代码保护,是用在中断服务程序中的,而且这个中断的优先级一定要低于configMAX_SYSCALL_INTERRUPT_PRIORITY !这样才能使用FREERTOS提供的API函数。
4、熟悉正点原子《FreeRTOS开发指南V1.1》“第五章 第六章”之FreeRTOS 任务管理
✔
相对于操作系统来说,裸机主循环里的任务就相当于优先级都是一样的,都在同一个队列中,而且,也没有时间片轮转执行,所以需要排队等候,实时性差。
✔
RTOS是个任务管理系统,终究是针对任务的,任务的优先级总是会低于中断的优先级,不管你任务的优先级有多高,只要中断来了,任务都得靠边站。
✔
任务的数值越高,优先级越高;中断的数值越高,优先级越低。要注意区分。
✔
了解下可重入的概念:可重入函数详解-CSDN博客
重入:同一个函数被不同执行流调用,当前一个执行流还没执行完,就被其他执行流再次进入,我们称之为重入,一个函数在重入的情况下,运行结果不会出现任何不同或任何问题,则该函数被称为可重入函数,否则为不可重入函数。
多线程的情况下,几乎一定会出现这种情况,所以需要仔细考虑下可重入的问题。
✔
任务状态
任务创建后即进入就绪态;
挂起态恢复后进入就绪态;
阻塞态满足条件后进入就绪态;
也就是说,任务如果想要进入运行态,必须先进入就绪态。
就绪态无法直接进入阻塞态。
挂起态、就绪态、阻塞态各自有一个任务列表。
✔
任务优先级数字越低表示任务的优先级越低,0 的优先级最低,configMAX_PRIORITIES-1 的优先级最高。空闲任务的优先级最低,为 0。
✔
中断只有条件触发才会执行一次;有高优先级任务执行的情况下,低优先级任务得不到执行,所以,肯定会有条件不满足让高优先级任务进入阻塞态,这时,低优先级任务才能得到执行。或者手动让高优先级任务挂起。
✔
动态任务创建
使用函数 xTaskCreate() 来动态创建任务。一般,任务名称字符串和调用的任务名保持一致即可。另外就是,任务函数的参数为NULL即可。任务句柄其实就是任务的堆栈首地址,这个参数就是用来存放任务句柄的。其他API操作该任务时,其实就是操作的任务句柄。要创建的任务,要放入死循环中。注意:不能从任务函数中返回或者退出。如果一定要从任务函数中退出的话那一定要调用函数vTaskDelete(NULL)来删除此任务。常规使用示例如下:这里有个问题需要解决,那就是,任务调度是只会调度死循环里的部分,还是会调度整个函数???????????????????也就是说,死循环之外的部分会被反复执行吗???????????????????✔
每个任务都有自己的堆栈,用于保存和恢复上下文环境。
关于堆栈大小
✔
删除任务时,如果传入的是NULL,那么就会默认删除当前正在运行的那个任务。
一般用于当前任务删除自身。
✔
句柄,一般就是访问某个目标的标识符,在不同的场景中具体含义不一样,有的是一个整数,有的是一个对象,有的是一个指针……总之,其抽象含义是一致的,都可以看做是某个对象或者某种行为的指代,类似于名称。可参考如下说法:
句柄是什么?图像句柄,函数句柄,句柄的概念
✔
了解正点原子《FreeRTOS开发指南V1.1》“第七章 FreeRTOS 列表和列表项”
✔
列表是 FreeRTOS 中的一个数据结构,概念上和双向链表有点类似。
✔
FREERTOS有几种列表,就绪列表、阻塞列表、挂起列表。
列表有其管理的结构体,还有结点(列表项)的结构体,结点(列表项)的结构体中就有成员指明该结点当前属于哪个列表。
就绪列表是个列表数组:从纵向看,是不同优先级任务之间的抢占式调度;从横向看,是同一优先级任务之间的时间片轮转调度。
5、熟悉正点原子《FreeRTOS开发指南V1.1》“第十一章 FreeRTOS 其他任务 API 函数”
这些函数一般用来调试使用,具体查看手册即可。
几个重点函数记录:
✔
uxTaskPriorityGet() 查询某个任务的优先级。✔uxTaskGetStackHighWaterMark() 获取任务的堆栈的历史剩余最小值,FreeRTOS 中叫做“高水位线”,此函数可用于调整堆栈大小,防止过大或溢出风险。✔uxTaskGetNumberOfTasks() 获取当前系统中存在的任务数量。✔vTaskGetRunTimeStats() 获取每个任务的运行时间如果IDLE任务占比很大,就说明CPU大部分时间都是处于空闲状态,比如✔vTaskList() 以一种表格的形式输出当前系统中所有任务的详细信息。
6、熟悉正点原子《FreeRTOS开发指南V1.1》“第十二章 FreeRTOS 时间管理”之延时函数
在使用 FreeRTOS 的过程中我们通常会在一个任务函数中使用延时函数对这个任务延时, 当执行延时函数的时候就会进行任务切换,并且此任务就会进入阻塞态,直到延时完成,任务重新进入就绪态。在 FreeRTOS 中延时函数有相对模式和绝对模式,不过在 FreeRTOS 中不同的模式用的函数不同,其中函数 vTaskDelay() 是相对模式 ( 相对延时函数 ) , 函数vTaskDelayUntil() 是绝对模式 ( 绝对延时函数 )要使用此函数的话宏 INCLUDE_vTaskDelay 必须为 1。相对延时就是调用这个函数时直到结束,就是延时那么久;绝对延时就是延时函数加上之前的代码执行时间,总的时间是那么久。我们常用的是相对延时函数 vTaskDelay(),传入要延时的时间数,单位毫秒(ms)延时时间由参数 xTicksToDelay 来确定,为要延时的时间节拍数,延时时间肯定要大于0 。否则的话相当于直接调用函数 portYIELD() 进行任务切换。
7、熟悉正点原子《FreeRTOS开发指南V1.1》“第十三章 FreeRTOS 队列”
✔
注意区分队列和上面说的列表。
队列说的就FIFO队列。
✔
队列中只能存储数据类型相同的值。
✔
由于队列用来传递消息的,所以也称为消息队列。全局变量有什么问题呢?因为全局变量底层实际是要进行读改写的操作的,在多任务的场景下,全局变量可能会在一个任务中还没完成操作,就被下一个任务打断,这样,就会导致某些程序获取到的数据是不对的。而队列在实现时,加入了临界区保护✔
消息队列跟裸机中的缓冲数组类似,属于一种循环队列。
消息队列有哪些应用场景呢?
比如
✔
初学者会创建队列、向队列发送数据、从队列接收数据即可。
✔
8、熟悉正点原子《FreeRTOS开发指南V1.1》“第十四章 FreeRTOS 信号量”
✔
二值信号量的使命就是任务同步,完成任务与任务或中断与任务之间的同步。大多数情况下都是中断与任务之间的同步。类似于裸机中的标志位。✔二值信号量在释放信号量之后就是满状态,接收信号量之后就是空状态。✔
9、熟悉正点原子《FreeRTOS开发指南V1.1》“第十六章 FreeRTOS 事件标志组”
✔
使用信号量来同步的话任务只能与单个的事件或任务进行同步。有时候某个任务可能会需要与多个事件或任务进行同步,此时信号量就无能为力了。FreeRTOS 为此提供了一个可选的解决方法,那就是事件标志组。其实就类似于裸机中的位标志操作。
✔
三个重要的中断
在 FreeRTOS 的移植过程中会这遇到三个重要的中断,分别是 FreeRTOS 系统时基定时器的中断(SysTick 中断)、SVC 中断、PendSV 中断。
SysTick 中断是用来提供系统时基的;
PendSV被用来进行上下文切换。