文章目录
前言
本专栏基于正点原子的《FreeRTOS开发指南_V1.10》所编写,使用的开发板型号是精英型STM32f103zet6。
一、RTOS介绍
1.1.什么是 FreeRTOS
“RTOS”全称是 Real Time Operating System,中文名就是实时操作系统,RTOS 并不是值某一特定的操作系统,而是指一类操作系统,例如,µC/OS,FreeRTOS,RTX,RT-Thread 等这些都是 RTOS 类的操作系统。因此,从 FreeRTOS 的名字中就能看出,FreeROTS 是一款免费的实时操作系统,也正因FreeRTOS开源免费,全国的FreeRTOS的使用量也非常的靠前。
1.2.操作系统的工作
操作系统是允许多个任务“同时运行”的,操作系统的这个特性被称为多任务。然而实际上,一个 CPU 核心在某一时刻只能运行一个任务,而操作系统中任务调度器的责任就是决定在某一时刻 CPU 究竟要运行哪一个任务,任务调度器使得 CPU 在各个任务之间来回切换并处理任务,由于切换处理任务的速度非常快,因此就给人造成了一种同一时刻有多个任务同时运行的错觉。
1.3.FreeRTOS 的特点
- 可移植性强
- 可裁剪
- 支持多种任务通讯、同步机制
- 高效的软件定时器
- 任务数量、优先级数量不限
- 运行效率高
- 支持抢占式、合作式调度
- 开源、免费、可商用
二、FreeRTOS的移植
2.1.基本工程
以正点原子标准例程-HAL 库版本的内存管理的实验工程为基础工程进行 FreeRTOS的移植。由于内存管理实验例程的 BSP 文件夹中可能不包含定时器的驱动文件,因此如果内存管理试验力撑的 BSP 文件夹如果不包含 TIMER 文件夹的话,需要从定时器相关实验的 BSP 文件夹中拷贝一份 TIMER 到 FreeRTOS 移植基础工程当中。
2.2.FreeRTOS源码获取
在 FreeRTOS 官方网站下载即可,里面的文件夹为这个样子:
FreeRTOS 的源码存放在 FreeRTOS 文件夹里面的 Source 文件夹里:
下图是各个文件的解释:
2.3.创建FreeRTOS的文件夹
在HAL 库版本的内存管理的实验工程里的 Middleware 文件夹下创建一个名字为 FreeRTOS 的文件夹,用于存放上一步骤的源码:
里面存放的代码有一些用不着的需要删除,删成下图所示,特别是 portable文件夹里面的编译器,本实验只用到 keil5,因此其他的编译器可以删除减少内存的使用:
- Keil 文件夹是在 MDK 中使用 ARMCC 编译器(AC5)时使用的。
- MemMang 中的文件是 FreeRTOS 提供的用于内存管理的文件,该文件夹包含了五个 C 源文件,这五个 C 源文件对应了五种内存管理的方法。
- FreeRTOS 提供了 ARM Cortex-M0、ARM Cortex-M3、ARM Cortex-M4、ARM Cortex-M7 等内核芯片的移植文件,本实验是基于STM32F103的,属于Cortex-M3内核,因此使用的部分源码需要在ARM_CM3文件夹里。
2.4.将文件添加到工程
本专栏是学习FreeRTOS,为了更贴合标题,把工程名称改成FreeRTOS,再新建两个文件夹,分别是图中右边框里的两个文件夹:
Middlewares/FreeRTOS_CORE文件夹用于存放FreeRTOS 的内核 C 源码文件,将FreeRTOS文件夹里面所有的 .c 文件全都选上:
Middlewares/FreeRTOS_PORT文件夹用于存放 FreeRTOS 内核的移植文件,需要添加两类文件到这个分组,分别为 heap_x.c (读者在进行 FreeRTOS 移植的时候可以根据需求选择合适的方法,具体这五种内存管理的算法本实验就选择 heap_4.c 即可)和 port.c 文件,heap_x.c 在路径 FreeRTOS/portable/MemMang 下面;port.c的路径在FreeRTOS/portable/RVDS/ARM_CM3里面,port 文件是 FreeRTOS 这个软件与 MCU 这个硬件连接的桥梁,因此对于正点原子的 STM32 系列不同的开发板,所使用的 port 文件是不同的。
最后在User文件夹里添加 FreeRTOSConfig.h 配置文件:
在 FreeRTOSv202112.00\FreeRTOS\Demo\CORTEX_STM32F103_IAR 路径里,找到STM32F103型号的Demo文件夹,里面就有适合该型号的配置文件,或者直接在正点原子的例程里复制过来。
2.5.添加头文件路径
如下图所示:
2.6.修改 SYSTEM 文件
2.6.1.sys.h 文件
sys.h文件的修改很简单,在sys.h文件中使用了宏SYS_SUPPORT_OS来定义是否支持OS,因为要支持 FreeRTOS,因此应当将宏 SYS_SUPPORT_OS 定义为 1,如下面代码所示:
2.6.2.usart.c 文件
原本在使用 µC/OS 的时候,进入和退出中断需要添加 OSIntEnter()和 OSIntExit()两个函数,这是 µC/OS 对于中断的相关处理机制,而 FreeRTOS 中并没有这种机制,因此将这两行代码删除,如下面代码所示:(以F1为例)
接下来 usart.c 要修改的第二个地方就是导入的头文件,因为在串口的中断服务函数当中已经删除了 µC/OS 的相关代码,并且也没有使用到 FreeRTOS 的相关代码,因此将 usart.c 中包含的关于 OS 的头文件删除,要删除的代码如下图所示:
2.6.3.delay.c 文件
- 删除适用于 µC/OS 但不适用于 FreeRTOS 的相关代码,一共需要删除 1 个全局变量、3 个宏定义、3 个函数,这些要删除的代码在使用 µC/OS 的时候会使用到,但是在使用 FreeRTOS 的时候无需使用,需要删除的代码如下所示:
2.7.添加 FreeRTOS 的相关代码
在使用 ARMCC(AC5)的情况下只需要在 delay.c 文件中使用 extern 关键字导入一个 FreeRTOS 函数——xPortSysTickHandler() 即可,这个函数是用于处理 FreeRTOS 系统时钟节拍的,本实验是使用 SysTick 作为 FreeRTOS操作系统的心跳,因此需要在 SysTick 的中断服务函数中调用这个函数,因此将代码添加到 SysTick 中断服务函数之前,代码修改如下:
2.8.修改部分内容
最后要修改的内容包括两个,分别是包含头文件和 4 个函数。首先来看需要修改的 4 个函数,分别是 SysTick_Handler()、delay_init()、delay_us() 和 delay_ms() 。
2.8.1.SysTick_Handler( )
对于使用 ARMCC(AC5)的情况,只需在这个函数中不断调用上个步骤中导入的函数 xPortSysTickHandler(),代码修改后如下图所示:
2.8.2.delay_init( )
函 数 delay_init() 主要用于初始化 SysTick 。这里要说明的是,在后续调用函数 TaskStartScheduler() 的时候,FreeRTOS 会按照 FreeRTOSConfig.h 文件的配置对 SysTick 进行初始化,因此 delay_init()函数初始化的 SysTick 主要使用在 FreeRTOS 开始任务调度之前。函数 delay_init()要修改的部分主要为 SysTick 的重装载值以及删除不用的代码,STM32F1 系列的函数delay_init()将 SysTick 的时钟频率设置为 CPU 时钟频率的 1/8,代码修改如下:
2.8.3.delay_us( )
函数 delay_us()用于微秒级的 CPU 忙延时,原本的函数 delay_us()延时的前后加入了自定义函数 delay_osschedlock()和 delay_osschedunlock()用于锁定和解锁 µC/OS 的任务调度器,以此来让延时更加准确。
2.8.4.delay_ms( )
函数 delay_ms() 用于毫秒级的 CPU 忙延时,原本的函数 delay_ms()会判断 µC/OS 是否运行,如果 µC/OS 正在运行的话,就使用 µC/OS 的 OS 延时进行毫秒级的延时,否则就调用函数delay_us()进行毫秒级的 CPU 忙延时。在 FreeRTOS 中,可以将函数 delay_ms()定义为只进行CPU 忙延时,当需要 OS 延时的时候,调用 FreeRTOS 提供的 OS 延时函数 vTaskDelay() 进行系统节拍级延时,函数 delay_ms()修改后的代码如下所示:
2.8.5.包含头文件
根据上述步骤的修改,delay.c 文件中使用到了 FreeRTOS 的相关函数,因此就需要在 delay.c文件中包含 FreeRTOS 的相关头文件,并且移除掉原本存在的 µC/OS 相关头文件。原来的delay.c 文件中包含的 µC/OS 相关的头文件:
修改过后:
2.9.修改中断相关文件
在 FreeRTOS 的移植过程中会这几到三个重要的中断,分别是 FreeRTOS 系统时基定时器的中断(SysTick 中断)、SVC 中断、PendSV 中断,这三个中断的中断服务函数在 HAL 库提供的文件中都有定义,F1系列的中断服务函数所在 stm32f1xx_it.c 文件里。其中,SysTick 的中断服务函数在 delay.c 文件中已经定义了,并且 FreeRTOS 也提供了 SVC 和 PendSV 的中断服务函数,因此需要将 HAL 库提供的这三个中断服务函数注释掉,这里采用宏开关的方式让 HAL 库中的这三个中断服务函数不加入编译(如果使用操作系统就失能该函数),使用的宏在 sys.h 中定义,因此还需要导入 sys.h 头文件。
2.10.修改stm32f103xe.h
最后修改 __NVIC_PRIO_BITS 这个宏定义的值为4,修改之前4u这个值也是正确的,但是 keil5 在编译 FreeRTOS 工程的时候会报错。
三、实验
3.1.添加定时器驱动
由于在后续的实验中需要使用到 STM32 的基本定时器外设,因此需要向工程中添加定时器的相关驱动文件,也可在后续实验需要用到定时器的时候再进行添加。将定时器的相关驱动文件添加到工程的 Drivers/BSP 文件分组中,如下图所示:
在正点原子的 FreeRTOS实验例程2 FreeRTOS 移植实验的User文件夹里面,复制下图两个文件,到自己的工程的User文件夹里面:
最后,在工程文件里添加 freertos_demo.c 文件,并运行代码,得到屏幕闪烁的同时,LED也跟着闪烁,以下是 freertos_demo.c 代码:
#include "freertos_demo.h"
#include "./SYSTEM/usart/usart.h"
#include "./BSP/LED/led.h"
#include "./BSP/LCD/lcd.h"
#include "FreeRTOS.h"
#include "task.h"
#define START_TASK_PRIO 1
#define START_STK_SIZE 128
TaskHandle_t StartTask_Handler;
void start_task(void *pvParameters);
#define TASK1_PRIO 2
#define TASK1_STK_SIZE 128
TaskHandle_t Task1Task_Handler;
void task1(void *pvParameters);
#define TASK2_PRIO 3
#define TASK2_STK_SIZE 128
TaskHandle_t Task2Task_Handler;
void task2(void *pvParameters);
uint16_t lcd_discolor[11] = {WHITE, BLACK, BLUE, RED,
MAGENTA, GREEN, CYAN, YELLOW,
BROWN, BRRED, GRAY};
void freertos_demo(void)
{
lcd_show_string(10, 10, 220, 32, 32, "STM32", RED);
lcd_show_string(10, 47, 220, 24, 24, "FreeRTOS Porting", RED);
lcd_show_string(10, 76, 220, 16, 16, "ATOM@ALIENTEK", RED);
xTaskCreate((TaskFunction_t )start_task,
(const char* )"start_task",
(uint16_t )START_STK_SIZE,
(void* )NULL,
(UBaseType_t )START_TASK_PRIO,
(TaskHandle_t* )&StartTask_Handler);
vTaskStartScheduler();
}
void start_task(void *pvParameters)
{
taskENTER_CRITICAL();
xTaskCreate((TaskFunction_t )task1,
(const char* )"task1",
(uint16_t )TASK1_STK_SIZE,
(void* )NULL,
(UBaseType_t )TASK1_PRIO,
(TaskHandle_t* )&Task1Task_Handler);
xTaskCreate((TaskFunction_t )task2,
(const char* )"task2",
(uint16_t )TASK2_STK_SIZE,
(void* )NULL,
(UBaseType_t )TASK2_PRIO,
(TaskHandle_t* )&Task2Task_Handler);
vTaskDelete(StartTask_Handler);
taskEXIT_CRITICAL();
}
void task1(void *pvParameters)
{
uint32_t task1_num = 0;
while(1)
{
lcd_clear(lcd_discolor[++task1_num % 14]);
lcd_show_string(10, 10, 220, 32, 32, "STM32", RED);
lcd_show_string(10, 47, 220, 24, 24, "FreeRTOS Porting", RED);
lcd_show_string(10, 76, 220, 16, 16, "ATOM@ALIENTEK", RED);
LED0_TOGGLE();
vTaskDelay(1000);
}
}
void task2(void *pvParameters)
{
float float_num = 0.0;
while(1)
{
float_num += 0.01f;
printf("float_num: %0.4f\r\n", float_num);
vTaskDelay(1000);
}
}