前言
回顾下之前的章节:我们在一个简单的定时器 OS 基础上实现了 cortex-M 系列架构的兼容,并基于单片机的基本资源实现了很多实例。
从这个章节开始,我们把 FreeRTOS 移植进来,同时还考虑兼容性。
本文我们将介绍如何移植 FreeRTOS,并基于此实现一个最基本的例子:串口定时打印数据。
关键字:FreeRTOS,STM32,GD32
FreeRTOS 介绍
为什么要用实时操作系统?
软件中的数据交互只有 2 种,一种是轮询,一种是通知。
所谓轮询,就是你每隔 5 分钟去楼下看看快递到了没有,这期间你啥也做不了,只能干等着。
所谓通知,就是快递小哥给你发个短信,“快递到楼下了,快来取”,通知到了再下去,等待期间你该干啥干啥。
用实时操作系统的目的就是为了更高效的利用 CPU 资源。
**另外,**再说,现在物联网这么火,不用实时操作系统怎么行?
为什么选用 FreeRTOS?
开源免费(良心),够简单(就几个 c 文件),够稳定(大家都在用)。
μCOS 要钱 - RT-Thread 要钱 - μClinux 有点难 - NuttX 有点难 - eCOS 用的少 - QNX X86
FreeRTOS 移植
下载
直接去官网:FreeRTOS 官网[1]
下一个 LTS 版本(长期支持版本),大概长这样:
FreeRTOSvyyyymm.xx-LTS.zip
![64496a8ffe70128588713d8afe46d7bb.png](https://i-blog.csdnimg.cn/blog_migrate/c7ad8c9127da2d3afb229e51309dfa83.png)
把这个目录简要说明下:
[蓝色]include:头文件
[黄色]*.c:几个核心头文件
[绿色]portable:移植相关的
portable 目录特别说明,手册上面有,直接贴图
![dd60ab2e41c0e2954f92a3ec8adbf127.png](https://i-blog.csdnimg.cn/blog_migrate/efc9b06a0526ea17579c97cea410e25b.png)
其中,MemMang 是内存管理接口,一般用 Heap_4,具体是什么含义,手册上有写,这里不细说。
另外,对应使用 keil 移植 M4 内核,对应的 portable 文件夹为:portable\RVDS\ARM_CM4F。
我们把必须的文件加到我们的工程中,长这样:
![48c385721d0da47ed219c1de106f0ba3.png](https://i-blog.csdnimg.cn/blog_migrate/21fe0bd1a800108a4d75e7c6a567f952.png)
其中,FreeRTOSConfig.h 是配置头文件,几个.c 是核心文件。
FreeRTOS 还有其他扩展,包括 http,json,mqtt 等网络的支持,可以按需下它们的扩展包。
FreeRTOSConfig.h 的配置项很多,官方的参考在:FreeRTOSConfig[2],但是里面内容太多了,可以从 demo 里面 copy 一个现成的过来用。
demo 在 github 上下载:FreeRTOS DEMO[3]
目录是:FreeRTOS\Demo
可参考:FreeRTOS\Demo\CORTEX_STM32F103_Keil
编译
先把最简单的程序弄出来:调试串口打印输出。
代码如下(1000ms 周期打印):
void vTask(void *pvParameters)
{
while(1)
{
#ifdef STM32
printf("[STM32] hello, this is freertos!\r\n");
#endif
#ifdef GD32
printf("[ GD32] hello, this is freertos!\r\n");
#endif
vTaskDelay(pdMS_TO_TICKS(1000));
}
}
main 函数中(创建任务,开始调度):
/* 3. app */
#ifndef USING_FREE_RTOS
virtual_timer_init();
while(1)
{
timer0_task();
}
#else
xTaskCreate(vTask, "Task1", 50, NULL, 1, NULL);
vTaskStartScheduler();
#endif
编译一把,OK。
![5c608080b1ff00862e88ba3387a9103b.png](https://i-blog.csdnimg.cn/blog_migrate/0c035e8c3747c2aac0c41ed62ba2183d.png)
当然,最重要的一点,我们要把还有FreeRTOSConfig.h
中关键的配置搞清楚。
FreeRTOSConfig.h
/*
* FreeRTOS V202111.00
* Copyright (C) 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
* the Software, and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*
* http://www.FreeRTOS.org
* http://aws.amazon.com/freertos
*
* 1 tab == 4 spaces!
*/
#ifndef FREERTOS_CONFIG_H
#define FREERTOS_CONFIG_H
/*-----------------------------------------------------------
* Application specific definitions.
*
* These definitions should be adjusted for your particular hardware and
* application requirements.
*
* THESE PARAMETERS ARE DESCRIBED WITHIN THE 'CONFIGURATION' SECTION OF THE
* FreeRTOS API DOCUMENTATION AVAILABLE ON THE FreeRTOS.org WEB SITE.
*
* See http://www.freertos.org/a00110.html
*----------------------------------------------------------*/
#include "io.h"
#define configUSE_PREEMPTION 1
#define configUSE_IDLE_HOOK 0
#define configUSE_TICK_HOOK 0
#define configCPU_CLOCK_HZ (SystemCoreClock)
#define configTICK_RATE_HZ ( ( TickType_t ) 1000 )
#define configMAX_PRIORITIES ( 5 )
#define configMINIMAL_STACK_SIZE ( ( unsigned short ) 128 )
#define configTOTAL_HEAP_SIZE ( ( size_t ) ( 17 * 1024 ) )
#define configMAX_TASK_NAME_LEN ( 16 )
#define configUSE_TRACE_FACILITY 0
#define configUSE_16_BIT_TICKS 0
#define configIDLE_SHOULD_YIELD 1
/* Co-routine definitions. */
#define configUSE_CO_ROUTINES 0
#define configMAX_CO_ROUTINE_PRIORITIES ( 2 )
/* Software timer definitions. */
#define configUSE_TIMERS 1
#define configTIMER_TASK_PRIORITY ( configMAX_PRIORITIES - 1 )
#define configTIMER_QUEUE_LENGTH 5
#define configTIMER_TASK_STACK_DEPTH ( configMINIMAL_STACK_SIZE * 2 )
/* Set the following definitions to 1 to include the API function, or zero
to exclude the API function. */
#define INCLUDE_vTaskPrioritySet 1
#define INCLUDE_uxTaskPriorityGet 1
#define INCLUDE_vTaskDelete 1
#define INCLUDE_vTaskCleanUpResources 0
#define INCLUDE_vTaskSuspend 1
#define INCLUDE_vTaskDelayUntil 1
#define INCLUDE_vTaskDelay 1
/* This is the raw value as per the Cortex-M3 NVIC. Values can be 255
(lowest) to 0 (1?) (highest). */
#define configKERNEL_INTERRUPT_PRIORITY 255
/* !!!! configMAX_SYSCALL_INTERRUPT_PRIORITY must not be set to zero !!!!
See http://www.FreeRTOS.org/RTOS-Cortex-M3-M4.html. */
#define configMAX_SYSCALL_INTERRUPT_PRIORITY 191 /* equivalent to 0xb0, or priority 11. */
#ifdef USING_FREE_RTOS
/* Definitions that map the FreeRTOS port interrupt handlers to their CMSIS
standard names. */
#define xPortPendSVHandler PendSV_Handler
#define vPortSVCHandler SVC_Handler
#define xPortSysTickHandler SysTick_Handler
#endif
#endif /* FREERTOS_CONFIG_H */
堆栈配置
示例:
#define configMINIMAL_STACK_SIZE ( ( unsigned short ) 128 )
#define configTOTAL_HEAP_SIZE ( ( size_t ) ( 17 * 1024 ) )
#define configMAX_TASK_NAME_LEN ( 16 )
configMINIMAL_STACK_SIZE
空闲任务的堆栈大小配置。
configTOTAL_HEAP_SIZE
FreeRTOS 堆栈配置总大小。
需要根据实际项目来配置大小。
configMAX_TASK_NAME_LEN
任务名称的最大长度限制。
tick 配置
#define configCPU_CLOCK_HZ ( ( unsigned long ) 72000000 )
#define configTICK_RATE_HZ ( ( TickType_t ) 1000 )
顾名思义,configCPU_CLOCK_HZ 就是 CPU 的频率,我们可以用系统全局变量来替代:
#define configCPU_CLOCK_HZ (SystemCoreClock)
configTICK_RATE_HZ 是 tick 的频率,1000 对应的就是 1ms 周期。
还有一个配置是:configSYSTICK_CLOCK_HZ,一般跟 configCPU_CLOCK_HZ 一样就行了,我们可以不管它,在 port.c 中有描述:
#ifndef configSYSTICK_CLOCK_HZ
#define configSYSTICK_CLOCK_HZ configCPU_CLOCK_HZ
/* Ensure the SysTick is clocked at the same frequency as the core. */
#define portNVIC_SYSTICK_CLK_BIT ( 1UL << 2UL )
#else
/* The way the SysTick is clocked is not modified in case it is not the same
* as the core. */
#define portNVIC_SYSTICK_CLK_BIT ( 0 )
#endif
需要注意的一点是:
需要考虑把系统原来的 tick 中断函数替换为 freeRTOS 的 tick 中断,具体就是:
#define xPortPendSVHandler PendSV_Handler
#define vPortSVCHandler SVC_Handler
#define xPortSysTickHandler SysTick_Handler
其中,SysTick_Handler 是 tick 中断,其他两个是异常中断。
考虑兼容性设计,原有框架中涉及 systemTick 的部分需要关闭。
优先级配置
首先,我们需要了解下 NVIC 的基础知识。
NVIC 的全称是 Nested Vectored Interrupt Control,即嵌套向量中断控制器,在 Cortex M3 和 M4 内核的 MCU 中,每个中断的优先级都是 8 bit 的寄存器来表示。
但实际上,厂商一般只用到高 4 bit(对应的配置宏是__NVIC_PRIO_BITS,大家可以在代码中看看这个宏的使用方式),也就是 16 个优先级;这 16 个优先级,又分成 2 bit 抢占式优先级和 2 bit 子优先级。
抢占式优先级高于子优先级;数字越小,优先级越高。
问:为什么要说这个优先级的事?
答:FreeRTOS 要接管(部分)优先级才能把事做好。
为了方便优先级管理,我们可以把 4 bit 优先级都配置为抢占式优先级:
STM32 : NVIC_PriorityGroup_4
GD32 : NVIC_PRIGROUP_PRE4_SUB0
再来看细看下 FreeRTOS 涉及到优先级的配置:
configKERNEL_INTERRUPT_PRIORITY
这个配置定义了 FreeRTOS 用到的 Systick 和 PendSV 中断的优先级,必须配置为最低优先级,也就是:
#define configKERNEL_INTERRUPT_PRIORITY 255
如果未定义的话,在 M3 的 port.c 中会强制定义(M4 中没有):
#ifndef configKERNEL_INTERRUPT_PRIORITY
#define configKERNEL_INTERRUPT_PRIORITY 255
#endif
configMAX_SYSCALL_INTERRUPT_PRIORITY
这个配置定义了受 FreeRTOS 管理的最高优先级中断。
什么意思呢?
当调用了 FreeRTOS 的关中断后,优先级比这个定义低的中断都会被关闭。
所以,这个值不能设置为 0,在 port.c 中有检查:
#if configMAX_SYSCALL_INTERRUPT_PRIORITY == 0
#error configMAX_SYSCALL_INTERRUPT_PRIORITY must not be set to 0. See http: /*www.FreeRTOS.org/RTOS-Cortex-M3-M4.html */
#endif
例如:
#define configMAX_SYSCALL_INTERRUPT_PRIORITY 191
hex(191)='0xbf'
表示低于 11 的优先级受 FreeRTOS 管理。
configMAX_PRIORITIES
延伸阅读:faultmask,primask 和 basepri
中断屏蔽寄存器。
primask:屏蔽除 NMI 和 HardFalut 外的所有异常和中断。faultmask:屏蔽除 NMI 外的所有异常和中断。basepri:屏蔽优先级低于 basepri 的中断,为 0 时停止屏蔽中断功能。
FreeRTOS 采用的是 basepri 方式,μCOS 采用的是 primask 方式。
API 函数包含配置
其形式是:INCLUDE_函数名
比如,要用 vTaskDelay 函数,则定义如下:
#define INCLUDE_vTaskDelay 1
结果展示
![d3c48e04add081f94d28ebbf6023df91.gif](https://i-blog.csdnimg.cn/blog_migrate/a5c078ef51d1a952610daa3ad9fa0119.gif)
当然,这只是 FreeRTOS 的一个hello world
例子,更深层次的还涉及到兼容性设计,FreeRTOS 任务调度、延时方法、信号量以及队列等用法后面再说。
参考资料
[1]
FreeRTOS官网: https://www.freertos.org/
[2]FreeRTOSConfig: https://www.freertos.org/a00110.html
[3]FreeRTOS DEMO: https://github.com/FreeRTOS/FreeRTOS
![89934cb24778cb13c66d697447b51571.gif](https://i-blog.csdnimg.cn/blog_migrate/743df9d44e686f6a4a2ba1b64949bb64.gif)
![8ba98ae4b1bfae5e792f27229894a1a8.png](https://i-blog.csdnimg.cn/blog_migrate/5ce2bbd4a39293ff41b0d379a1312aef.png)
![8fe545d093c74348ec5241b964fbe059.gif](https://i-blog.csdnimg.cn/blog_migrate/5c8e06fb6b4de9c0b199ae12ebc8b508.gif)