FreeRTOS教程1 基础知识
1、准备材料
STM32CubeMX软件(Version 6.10.0)
Keil µVision5 IDE(MDK-Arm)
2、学习目标
了解 FreeRTOS 相关知识,并熟悉使用 STM32CubeMX 软件配置 FreeRTOS 工程的流程
3、前提知识
读者须知:本系列教程中关于 FreeRTOS 的学习重点为应用,不会剖析源码内容,利用 STM32CubeMX 软件生成的 FreeRTOS 工程源代码中一般不会直接调用 FreeRTOS 的 API 函数,而是调用了 CMSIS-RTOS 封装好的上层API函数,但此系列教程主要介绍属于 FreeRTOS 的各类 API 函数及其应用,对于 CMSIS-RTOS 的 API 函数仅简单描述,读者需自行理解,另外对于常见硬件原理不再详细说明,感兴趣读者可阅读笔者的 “STM32CubeMX+STM32F4系列教程”
3.1、FreeRTOS简介
FreeRTOS是一个完全免费且开源的嵌入式实时操作系统(Real-Time Operating System,简称RTOS) ,其一般将任务称为线程,以下列表为FreeRTOS的一些特点,笔者认为学习FreeRTOS的应用正是掌握和理解下列各个特性
- 抢占式(pre-emptive)或合作式(co-operative)任务调度方式
- 非常灵活的优先级管理
- 灵活、快速且轻量化的任务通知机制(task notification)
- 消息队列(Queue)
- 二值信号量(Binary Semaphores)
- 计数信号量(Counting Semaphores)
- 互斥量(Mutex)
- 递归互斥量(Recursive Mutex)
- 软件定时器(Timers)
- 事件组(Events)
- 时间节拍钩子函数
- 空闲任务钩子函数
- 栈溢出检测
- 任务运行时间统计收集
- 完整的中断嵌套模型
- 用于低功耗的无节拍(Tickless)特性
RTOS主要应用于对实时性有要求的嵌入式系统,所谓实时性就是任务完成的时间是确定的,实时性又分为软实时和硬实时
软实时指任务完成时间是确定的,但是如果任务超时了也不会对整个系统产生破坏性影响;硬实时是指任务完成时间是确定的,但是如果任务超时未完成则会对整个系统产生灾难性影响,基于FreeRTOS开发的系统可以完成硬实时的要求
3.2、源码函数命名规律
FreeRTOS源码中函数命名规律:FreeRTOS源码中各个函数并非随机命名,而是有规律的命名,这样方便使用者看到名字就能获得该函数更多的信息,其函数名一般由 ① 函数返回值类型简写,② 函数所在文件 和 ③ 函数作用名称这三部分组成
① 函数返回值类型简写主要有:
- 'u'表示'unsigned'
- 'c'表示'char'
- 's'表示'int16_t(short)'
- 'l'表示'int32_t(long)'
- 'p'表示指针类型变量
- 'x'表示'BaseType_t'结构体和其他非标准类型的变量名
- 'uc'表示'UBaseType_t'结构体
- 'v'表示'void'
- 'prv'表示私有函数无返回值
这些简写可以自由组合在一起,例如 'pc' 表示 'char *' 类型,'uc' 表示 'unsigned char' 类型
② 函数所在文件:
- 'CoRoutine'表示该函数定义在'coroutine.c'文件中的
- 'EventGroup'表示该函数定义在'event_groups.c'文件中的
- 'List'表示该函数定义在'list.c'文件中的
- 'Queue'表示该函数定义在'queue.c'文件中的
- 'StreamBuffer'表示该函数定义在'stream_buffer.c'文件中的
- 'Task'表示该函数定义在'tasks.c'文件中的
- 'Timer'表示该函数定义在'timers.c'文件中的
- 'Port'表示该函数定义在'port.c'或'heap_x.c'文件中的
举几个例子:
- xTaskCreate 表示函数返回值为 BaseType_t 结构体类型,函数被定义在 'tasks.c' 文件中,函数作用为“创建”
- vTaskSuspend 表示函数返回值为 void 类型,函数被定义在 'tasks.c' 文件中,函数作用为“挂起”
- prvTaskIsTaskSuspended 表示该函数为私有函数,仅能在 'tasks.c' 文件中使用,函数作用为“判断任务是否被挂起”
4、动手创建一个FreeRTOS空工程
4.1、CubeMX相关配置
4.1.1、工程基本配置
打开STM32CubeMX软件,单击ACCESS TO MCU SELECTOR选择开发板MCU(选择你使用开发板的主控MCU型号),选中MCU型号后单击页面右上角Start Project开始工程,具体如下图所示
开始工程之后在配置主页面System Core/RCC中配置HSE/LSE晶振,在System Core/SYS中配置Debug模式,因为系统滴答定时器SysTick要被FreeRTOS所使用,所以需要配置HAL库的时基源为除系统滴答定时器SysTick外的选项,笔者这里选择了基本定时器TIM6,这两个时基源均为1ms,具体配置如下图所示
4.1.2、时钟树配置
系统时钟使用8MHz外部高速时钟HSE,HCLK、PCLK1和PCLK2均设置为STM32F407能达到的最高时钟频率,具体如下图所示
4.1.3、外设参数配置
单击Pinout & Configuration页面左边功能分类栏目Middleware and SoftwarePacks中的FREERTOS,在模式配置栏中将其接口设置为CMSIS_V2(CMSIS_V2只是对CMSIS_V1的某些功能进行了扩展)
下方的Configuration配置页面中可以对9个选项卡关于FreeRTOS的所有参数做配置,目前均保持默认即可,具体配置如下图所示
以下列表为对上图所示这9个配置选项卡及其包含的参数做简单的介绍
- Tasks and Queues:任务和队列管理
- Timers and Semaphores :定时器和信号量管理
- Mutexes:互斥量管理
- Events:事件组管理
- FreeRTOS Heap Usage:FreeRTOS内存使用详情
- Config parameters:Config参数配置(对应FreeRTOSConfig.h中变量名config开始的宏定义)
- Include parameters:Include参数配置(对应FreeRTOSConfig.h中变量名INCLUDE开始的宏定义)
- Advanced settings:高级设置
- User Constants:用户自定义常量
对于上面9个选项卡中的前4个,CubeMX都提供了图形化配置的界面,在对应的页面中可以通过按钮非常方便的增加/删除任务、队列、定时器、信号量、互斥量和事件组等实例,而不需要用户在程序中手动编程生成实例
FreeRTOS Heap Usage 选项卡提供了一个显示当前FreeRTOS内存使用详情的页面,该页面无参数可配置,仅仅显示内存占用信息和剩余可用堆大小
Config parameters 和 Include parameters 选项卡中的参数分别对应 FreeRTOSConfig.h 文件中 config 开头和 INCLUDE 开头的宏定义,用来设置FreeRTOS的相关参数及功能裁剪,下面是所有参数列表,读者可简单浏览,后面遇到需要修改的具体参数可以回过头来寻找,如下述列表所示
参数 | 函数功能 |
---|---|
Config parameters/ MPU/FPU | |
ENABLE_MPU | 设置是否使用MPU内存保护单元 |
ENABLE_FPU | 设置是否使用FPU浮点数单元 |
Kernel settings | |
USE_PREEMPTION | 设置任务调度方式,Enable 表示使用抢占式调度,Disable 表示使用合作式调度 |
CPU_CLOCK_HZ | 设置MCU的HCLK始终频率,默认为系统时钟且不可修改 |
TICK_RATE_HZ | 设置FreeRTOS滴答定时器中断频率,默认为1000Hz(1ms),设置范围为1-1000 |
MAX_PRIORITIES | 设置最高优先级,值越大内核花销的内存空间就越多,总是建议将此常量设为能够用到的最小值,STM32CubeMX软件中默认为56且不可修改 |
MINIMAL_STACK_SIZE | 设置空闲任务使用的堆栈大小,默认为128words |
MAX_TASK_NAME_LEN | 设置任务名的最大长度(包括'\0'结束符),如果创建任务时传入的任务名字符串长度超过该参数定义的长度,则任务名会被自动截断,默认为16 |
USE_16_BIT_TICKS | 设置节拍数据类型TickType_t的具体类型,Enable 表示设置类型为 uint16_t,Disable 表示设置类型为 uint32_t |
IDLE_SHOULD_YIELD | 设置空闲任务是否对同优先级的任务主动让出CPU使用权 |
USE_MUTEXES | 设置是否使用互斥量 |
USE_RECURSIVE_MUTEXES | 设置是否使用递归互斥量 |
USE_COUNTING_SEMAPHORES | 设置是否使用计数信号量 |
QUEUE_REGISTRY_SIZE | 设置可注册队列和信号量的最大数量,默认为8,设置范围为0-255 |
USE_APPLICATION_TASK_TAG | 设置是否使用应用程序的任务标签 |
ENABLE_BACKWARD_COMPATIBILITY | 设置是否向后兼容旧版本 |
USE_PORT_OPTIMISED_TASK_SELECTION | 设置任务调度时,选择下一个任务的方法,Disable 表示使用通用方法,不依赖硬件,此处使用 CMSIS-RTOS V2 时该参数默认为 Disable 且不可修改 |
USE_TICKLESS_IDLE | 设置是否使用无节拍(tickless)低功耗模式 |
USE_TASK_NOTIFICATIONS | 设置是否使用任务通知功能 |
RECORD_STACK_HIGH_ADDRESS | 设置是否将栈的起始地址保存到每个任务的任务控制块中 |
Memory management settings | |
Memory_Allocation | 设置内存分配方式,默认为 Dynamic / Static 且不可修改 |
TOTAL_HEAP_SIZE | 设置FreeRTOS总的堆空间大小,设置范围为 512B~128KB |
Memory_Management_scheme | 设置内存管理方案,有 heap_x.h 共计5中可选方案,默认选择 heap_4.h |
Hook function related definitions | |
USE_IDLE_HOOK | 设置是否使用空闲任务钩子函数 vApplicationIdleHook() |
USE_TICK_HOOK | 设置是否使用滴答定时器钩子函数 vApplicationTickHook() |
USE_MALLOC_FAILED_HOOK | 设置是否使用内存分配失败钩子函数 vApplicationMallocFailedHook() |
USE_DAEMON_TASK_STARTUP_HOOK | 设置是否使用内存分配失败钩子函数 vApplicationMallocFailedHook() |
CHECK_FOR_STACK_OVERFLOW | 设置是否使用守护任务启动钩子函数 vApplicationDaemonTaskStartupHook() |
Run time and task stats gathering related definitions | |
GENERATE_RUN_TIME_STATS | 设置是否启动任务运行时间统计功能,启用后可以通过 vTaskGetRunTimeStats() API 函数读取这些信息 |
USE_TRACE_FACILITY | 设置是否启用可视化和跟踪调试,默认为 Enabled |
USE_STATS_FORMATTING_FUNCTIONS | 设置是否编译 vTaskList() 和 vTaskGetRunTimeStats() API 函数,将 USE_TRACE_FACILITY 和 USE_STATS_FORMATTING_FUNCTIONS 设置为 1 将编译构建这两个函数,设置为 0 将不编译构建 |
Co-routine related definitions | |
USE_CO_ROUTINES | 设置是否使用协程 |
MAX_CO_ROUTINE_PRIORITIES | 设置协程最大优先级 |
Software timer definitions | |
USE_TIMERS | 设置是否使用软件定时器 |
TIMER_TASK_PRIORITY | 设置定时器服务任务优先级 |
TIMER_QUEUE_LENGTH | 设置定时器指令队列长度 |
TIMER_TASK_STACK_DEPTH | 设置定时器服务任务栈空间大小 |
Interrupt nesting behaviour configuration | |
LIBRARY_LOWEST_INTERRUPT_PRIORITY | 设置最低中断优先级 |
LIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY | 设置系统能管理的最高中断优先级 |
Added with 10.2.1 support | |
MESSAGE_BUFFER_LENGTH_TYPE | 设置消息缓冲区长度类型 |
USE_POSIX_ERRNO | 设置使用POSIX标准的错误编号 |
CMSIS-RTOS V2 flags (仅适用于 FreeRTOS >= 10.3.1 版本) | |
THREAD_SUSPEND_RESUME | 设置是否使用 CMSIS-RTOS2 的线程挂起 osThreadSuspend() 和恢复 osThreadResume() API 函数 |
USE_OS2_THREAD_ENUMERATE | 设置是否使用 CMSIS-RTOS2 的返回线程枚举数量 osThreadEnumerate() API 函数 |
USE_OS2_EVENTFLAGS_FROM_ISR | 设置是否使用来自 ISR 的 CMSIS-RTOS2 函数 osEventFlagsSet() 和 osEventFlagsClear() 操作 |
USE_OS2_THREAD_FLAGS | 设置是否从应用程序映像中排除 CMSIS-RTOS2 线程标志 API 函数 |
USE_OS2_TIMER | 设置是否从应用程序映像中排除 CMSIS-RTOS2 定时器 API 函数 |
USE_OS2_MUTEX | 设置是否从应用程序映像中排除 CMSIS-RTOS2 Mutex API 函数数 |
Include parameters/ Include definitions | |
vTaskPrioritySet | 设置是否包含 vTaskPrioritySet() API 函数 |
uxTaskPriorityGet | 设置是否包含 uxTaskPriorityGet() API 函数 |
vTaskDelete | 设置是否包含 vTaskDelete() API 函数 |
vTaskCleanUpResources | 设置是否包含 vTaskCleanUpResources() API 函数 |
vTaskSuspend | 设置是否包含 vTaskSuspend() API 函数 |
vTaskDelayUntil | 设置是否包含 vTaskDelayUntil() API 函数 |
vTaskDelay | 设置是否包含 vTaskDelay() API 函数 |
xTaskGetSchedulerState | 设置是否包含 xTaskGetSchedulerState() API 函数 |
xTaskResumeFromlSR | 设置是否包含 xTaskResumeFromlSR() API 函数 |
xQueueGetMutexHolder | 设置是否包含 xQueueGetMutexHolder() API 函数,默认启用(某些工具链(例如MDK-ARM)可能需要编译cmsis_os2.c) |
pcTaskGetTaskName | 设置是否包含 pcTaskGetTaskName() API 函数 |
uxTaskGetStackHighWaterMark | 设置是否包含 uxTaskGetStackHighWaterMark() API 函数 |
xTaskGetCurrentTaskHandle | 设置是否包含 xTaskGetCurrentTaskHandle() API 函数 |
eTaskGetState | 设置是否包含 eTaskGetState() API 函数 |
xEventGroupSetBitFromlSR | 设置是否包含 xEventGroupSetBitFromlSR() API 函数,INCLUDE_xTimerPendFunctionCall 必须设置为 1 使 xEventGroupSetBitFromISR() 函数可用 |
xTimerPendFunctionCall | 设置是否包含 xTimerPendFunctionCall() API 函数,configUSE_TIMERS 必须设置为 1 使 xTimerPendFunctionCall() 函数可用 |
xTaskAbortDelay | 设置是否包含 xTaskAbortDelay() API 函数 |
xTaskGetHandle | 设置是否包含 xTaskGetHandle() API 函数 |
uxTaskGetStackHighWaterMark2 | 设置是否包含 uxTaskGetStackHighWaterMark2() API 函数,适用于支持 FreeRTOS >= 10.2.1 的系列 |
Advanced settings 选项卡中只有两个参数, USE_NEWLIB_REENTRANT 用于配置 Newlib 相关内容,一般不使用; Use FW pack heap file 用于配置是否使用固件包提供的堆管理文件(heap_x.c),不使用的话需要由用户自己提供堆管理文件,这里一般选择使用
User Constants 选项卡可以创建一些用户需要使用的常量参数,创建的常量将以宏定义的形式被定义在main.h文件中
4.1.4、外设中断配置
当启用了FREERTOS之后,整个系统的NVIC会自动发生一些变化,FreeRTOS使用的系统服务可挂起请求中断和系统滴答定时器中断将被强制开启,均为最低优先级且不可设置,这两个中断对于FreeRTOS来说是相当重要的,系统滴答定时器会为FreeRTOS提供时间基准,系统服务可挂起请求中断用于任务切换等管理
另外HAL库的时基源TIM6中断也会被强制打开不可关闭,但是其中断的优先级仍可调节,我们将TIM6中断设置为硬件最高优先级0,其他均按照默认中断优先级即可,具体配置如下图所示
将启用FreeRTOS之后的NVIC与启用之前的NVIC对比可以发现,在配置页面多了一列“Uses FreeRTOS functions”,在增加的这一列的某个硬件中断后面勾选选项框则会改变该硬件中断的抢占优先级,读者目前仅作了解,有关中断具体管理会在后续教程中讲到
4.2、生成代码
4.2.1、配置Project Manager页面
单击进入Project Manager页面,在左边Project分栏中修改工程名称、工程目录和工具链,然后在Code Generator中勾选“Gnerate peripheral initialization as a pair of 'c/h' files per peripheral”,最后单击页面右上角GENERATE CODE生成工程,具体如下图所示
4.2.2、工程代码结构分析
打开生成的工程代码,观察其目录结构,发现在Core目录下相比较以往的工程增加了freertos.c 和 stm32f4xx_hal_timebase_tim.c 两个文件,在工程下还增加了Middlewares/FreeRTOS的源码文件,该源码文件下所有文件均无需用户修改,生成工程代码具体工程目录结构如下图所示
为了减少在使用不同的第三方RTOS嵌入式操作系统(eg:FreeRTOS、UCOS等)对用户应用代码带来的差异,ARM公司为RTOS内核制定了一套通用的接口协议CMSIS-RTOS(cmsis_osx.c),在该文件中规定了RTOS中使用的某些功能函数的统一名称及参数等等
在应用上,用户只需要调用CMSS-RTOS规定的API函数来对任务进行操作,而CMSS-RTOS规定的API函数会使用不同第三方RTOS嵌入式操作系统的接口函数对CMSS-CORE(HAL库等函数)操作,最终控制底层MCU,其中CMSS、RTOS和MCU等的关系图如下图所示 (注释1)
接下来我们来看看空的FreeRTOS工程初始化流程,打开main.c文件,在主函数中分别执行了以下几个函数,下面简单介绍下这些空工程中就使用到的函数,读者了解即可
最后调用osKernelStart()函数时会先创建一个空闲任务,然后启动FreeRTOS调度器,将STM32内核控制权交给FreeRTOS调度器,FreeRTOS调度器启动之后必须要至少有一个任务在不断运行,之后调度器就会按照一定的任务优先级顺序执行用户定义的各种任务,每个任务都应该是一个死循环,所以程序不会运行到osKernelStart()函数之后的任何部分
读者在使用FreeRTOS时,如果需要自己手动创建某些任务、信号量、互斥量等可以直接在freertos.c文件中实现即可,也可以在CubeMX图形化配置界面中创建(推荐后者)
MX_FREERTOS_Init()函数中,ST公司也使用者贴心的提供了各种不同功能的沙箱代码段,当在CubeMX图形化配置界面中创建了对应的实例,该沙箱代码段中就会出现对应实现的程序,如下图所示
4.3、烧录验证
使用STM32CubeMX生成工程代码后,不做任何修改,直接单击KEIL软件的编译按钮应该可以顺利通过,通过编译信息可知出现0错误和0警告,如下图所示
单击魔术手,在debug选项卡中选择使用DAP下载器(该下载器无需下载驱动),单击后方的设置可以在其中看到识别到的下载器,具体如下图所示
将接入电源的开发板通过DAP下载器与PC连接,单击KEIL软件的LOAD按钮将程序烧录进入开发板MCU中,等待下方进度条走完,可以发现 Flash Load finished ,由于是空工程因此无任何现象发生,具体如下图所示
5、FreeRTOS API 与 CMSIS-RTOS API 对照表
FreeRTOS API | 函数功能 | CMSIS-RTOS API |
---|---|---|
About Task | ||
xTaskCreate() | 动态分配内存创建任务 | osThreadNew() |
xTaskCreateStatic() | 静态分配内存创建任务 | osThreadNew() |
vTaskSuspend() | 挂起某个任务 | osThreadSuspend() |
vTaskResume() | 将某个任务从挂起状态恢复 | osThreadResume() |
vTaskPrioritySet() | 设置任务优先级 | osThreadSetPriority() |
uxTaskPriorityGet() | 获取任务优先级 | osThreadGetPriority() |
vTaskDelay() | 延时函数 | osDelay() |
vTaskDelayUntil() | 延时函数,用于实现一个任务固定执行周期 | osDelayUntil() |
xTaskGetTickCount() | 获取滴答信号当前计数值 | osKernelGetTickCount() |
xTaskAbortDelay() | 终止任务延时,退出阻塞状态 | --- |
vTaskDelete() | 任务删除函数 | osThreadTerminate()/osThreadExit() |
vTaskStartScheduler() | 启动调度器 | osKernelStart() |
vTaskSuspendAll() | 挂起调度器 | osKernelLock() |
xTaskResumeAll() | 恢复调度器 | osKernelUnlock() |
taskYIELD() | 让位于另一项同等优先级的任务 | osThreadYield() |
About MessageQueue | ||
xQueueCreate() | 动态分配内存创建队列 | osMessageQueueNew() |
xQueueCreateStatic() | 静态分配内存创建队列 | osMessageQueueNew() |
xQueueSend() | 向队列后方发送数据 | osMessageQueuePut() |
xQueueSendToBack() | 向队列后方发送数据 | osMessageQueuePut() |
xQueueSendToFront() | 向队列前方发送数据 | osMessageQueuePut() |
xQueueOverwrite() | 向长度为1的队发送数据 | osMessageQueueGet() |
xQueueReceive() | 从队列头部接收数据单元,接收的数据同时会从队列中删除 | osMessageQueueGet() |
xQueuePeek() | 从队列头部接收数据单元,不从队列中删除接收的单元 | osMessageQueueGet() |
uxQueueMessagesWaiting() | 查询队列有效数据单元个数 | osMessageQueueGetCount() |
vQueueDelete() | 删除队列 | osMessageQueueDelete() |
xQueueReset() | 将队列重置为其原始空状态 | osMessageQueueReset() |
About Semaphores | ||
About Mutexs | ||
About EventGroup | ||
About Task_Notifications | ||
About Stream_Buffer | ||
About Timer | ||
About LowPower |
6、注释详解
注释1:图片来源 Getting Started with STM32 - Introduction to FreeRTOS
注释2:图片来源 CMSIS-Core Device Templates