1、准备材料

正点原子stm32f407探索者开发板V2.4

STM32CubeMX软件(Version 6.10.0

Keil µVision5 IDE(MDK-Arm

野火DAP仿真器

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的应用正是掌握和理解下列各个特性

  1. 抢占式(pre-emptive)或合作式(co-operative)任务调度方式
  2. 非常灵活的优先级管理
  3. 灵活、快速且轻量化的任务通知机制(task notification)
  4. 消息队列(Queue)
  5. 二值信号量(Binary Semaphores)
  6. 计数信号量(Counting Semaphores)
  7. 互斥量(Mutex)
  8. 递归互斥量(Recursive Mutex)
  9. 软件定时器(Timers)
  10. 事件组(Events)
  11. 时间节拍钩子函数
  12. 空闲任务钩子函数
  13. 栈溢出检测
  14. 任务运行时间统计收集
  15. 完整的中断嵌套模型
  16. 用于低功耗的无节拍(Tickless)特性

RTOS主要应用于对实时性有要求的嵌入式系统,所谓实时性就是任务完成的时间是确定的,实时性又分为软实时和硬实时

软实时指任务完成时间是确定的,但是如果任务超时了也不会对整个系统产生破坏性影响;硬实时是指任务完成时间是确定的,但是如果任务超时未完成则会对整个系统产生灾难性影响,基于FreeRTOS开发的系统可以完成硬实时的要求

3.2、源码函数命名规律

FreeRTOS源码中函数命名规律:FreeRTOS源码中各个函数并非随机命名,而是有规律的命名,这样方便使用者看到名字就能获得该函数更多的信息,其函数名一般由 ① 函数返回值类型简写,② 函数所在文件 和 ③ 函数作用名称这三部分组成

① 函数返回值类型简写主要有:

  1. ‘u’表示’unsigned’
  2. ‘c’表示’char’
  3. ‘s’表示’int16_t(short)’
  4. ‘l’表示’int32_t(long)’
  5. ‘p’表示指针类型变量
  6. ‘x’表示’BaseType_t’结构体和其他非标准类型的变量名
  7. ‘uc’表示’UBaseType_t’结构体
  8. ‘v’表示’void’
  9. ‘prv’表示私有函数无返回值

这些简写可以自由组合在一起,例如 ‘pc’ 表示 ‘char *’ 类型,’uc’ 表示 ‘unsigned char’ 类型

② 函数所在文件:

  1. ‘CoRoutine’表示该函数定义在’coroutine.c’文件中的
  2. ‘EventGroup’表示该函数定义在’event_groups.c’文件中的
  3. ‘List’表示该函数定义在’list.c’文件中的
  4. ‘Queue’表示该函数定义在’queue.c’文件中的
  5. ‘StreamBuffer’表示该函数定义在’stream_buffer.c’文件中的
  6. ‘Task’表示该函数定义在’tasks.c’文件中的
  7. ‘Timer’表示该函数定义在’timers.c’文件中的
  8. ‘Port’表示该函数定义在’port.c’或’heap_x.c’文件中的

举几个例子:

  1. xTaskCreate 表示函数返回值为 BaseType_t 结构体类型,函数被定义在 ‘tasks.c’ 文件中,函数作用为“创建”
  2. vTaskSuspend 表示函数返回值为 void 类型,函数被定义在 ‘tasks.c’ 文件中,函数作用为“挂起”
  3. 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个配置选项卡及其包含的参数做简单的介绍

  1. Tasks and Queues:任务和队列管理
  2. Timers and Semaphores :定时器和信号量管理
  3. Mutexes:互斥量管理
  4. Events:事件组管理
  5. FreeRTOS Heap Usage:FreeRTOS内存使用详情
  6. Config parameters:Config参数配置(对应FreeRTOSConfig.h中变量名config开始的宏定义)
  7. Include parameters:Include参数配置(对应FreeRTOSConfig.h中变量名INCLUDE开始的宏定义)
  8. Advanced settings:高级设置
  9. 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、注释详解

注释1:图片来源 Getting Started with STM32 – Introduction to FreeRTOS

注释2:图片来源 CMSIS-Core Device Templates

参考资料

STM32Cube高效开发教程(高级篇)