摘要:今天玩一下RTOS,将FreeRTOS移植到C8T6单片机。后期会不断更新RTOS和Linux的内容,单片机的内容也会持续更新。想看更加详细的内容的小伙伴可以访问果果的博客:https://zhiguoxin.blog.csdn.net/
。
本期主角:9块9的C8T6核心板和一个JlinkOB。

1、FreeRTOS介绍
FreeRTOS其实不用再多介绍了,现在太火了,主要是免费,感觉很多厂家的出场demo都会带。而且FreeRTOS是一个十分小巧的系统,占用资源也不多,甚至可以在STM32F103C8T6
(64k FLASH,20K RAM)上跑起来。具体能裁剪到多小倒是没有看到有介绍,但是基本够用。
在FreeRTOS官网可以轻松下载到最新的FreeRTOS源码、组件、demo、新功能,而且现在的官网比以前好看多了。


2、FreeRTOS源码下载
本次不使用STM32CubeMX
来配置工程,所以需要去官网下载源码:https://www.freertos.org/


3、FreeRTOS文件结构
现在是2021.8.19,下载之后,打开文件夹,文件列表如下:
FreeRTOSv202107.00
|
| -- FreeRTOS
| -- Demo
| -- License
| -- Source
| -- include
| -- portable
| -- croutine.c
| -- event_groups.c
| -- list.c
| -- queue.c
| -- stream_buffer.c
| -- tasks.c
| -- timers.c
|
| -- FreeRTOS-Plus
| -- Demo
| -- Source
| -- FreeRTOS-Plus-CLI
| -- FreeRTOS-Plus-IO
| -- FreeRTOS-Plus-TCP
| -- FreeRTOS-Plus-Trace

下面这些文件就是需要移植到你的工程中去的。

4、移植FreeRTOS
4.1、文件需要
这里只是为了把系统跑起来,所以只移植了核心组件。我们需要移植的,最少的文件只需要:
FreeRTOSv202107.00\FreeRTOS\Source\tasks.c, queue.c and list.c 3个文件(kernel核心)
FreeRTOSv202107.00\FreeRTOS\Source\portable\RVDS\ARM_CM3\port.c + portmacro.h(3个中断都是在这里做的,pendSV,SVC,tick)
FreeRTOSv202107.00\FreeRTOS\Source\portable\MemMang\heap_4.c(内存管理,5中选1)
FreeRTOSv202107.00\FreeRTOS\Source\include 所有API接口
FreeRTOSv202107.00\FreeRTOS\Demo\CORTEX_STM32F103_Keil\FreeRTOSConfig.h(freeRTOS系统裁剪配置文件)
4.2、include文件夹
include
文件夹是一些头文件,移植的时候是需要的。

4.3、portable文件夹
portable
这个文件夹,我们知道FreeRTOS是个系统,归根结底就是个纯软件的东西,它是怎么和硬件联系在一起的呢?软件到硬件中间必须有一个桥梁,portable
文件夹里面的东西就是FreeRTOS系统和具体的硬件之间的连接桥梁!不同的编译环境,不同的MCU,其桥梁应该是不同的,打开portable
文件夹,如图所示

4.4、FreeRTOSConfig.h文件
FreeRTOSConfig.h
是FreeRTOS的配置文件,一般的操作系统都有裁剪、配置功能,而这些裁剪及配置都是通过一个文件来完成的,基本都是通过宏定义来完成对系统的配置和裁剪的。

4.5、添加 FreeRTOS源码
在基础工程中新建一个名为FreeRTOS的文件夹,把刚刚上面说到的文件全部添加进去。注意一定要将FreeRTOSConfig.h
添加到FreeRTOS的include文件夹中。

4.6、向工程分组中添加文件
打开基础工程,新建分组FreeRTOS
,然后向这两个分组中添加文件,如图所示


5、修改stm32f10x_it.c
SysTick
中断服务函数是一个非常重要的函数,FreeRTOS所有跟时间相关的事情都在里面处理,SysTick就是 FreeRToS的一个心跳时钟,驱动着 FreeRTOS的运行,就像人的心跳一样,假如没有心跳,我们就相当于“死了”,同样的,Freertos没有了心跳,那么它就会卡死在某个地方,不能进行任务调度,不能运行任何的东西,因此我们需要实现个FreeRTOS的心跳时钟,FreeRTOS帮我们实现了SysTick的启动的配置:在port.c
文件中已经实现vPortSetupTimerInterrupt()
函数,并且FreeRTOS
通用的SysTick
中断服务函数也实现了:在port.c文件中已经实现xPortsysTickHandler()
函数,所以移植的时候只需要我们在stm32f10xit.c
文件中注释掉SysTick_Handler
函数即可。
FreeRTOS为开发者考虑得特别多,PendV_Handler与SVC_Handler这两个很重要的函数都帮我们实现了,在port.c
文件中已经实现xPortPendSV_Handler()
与vPortSVChandler()
函数,防止我们自己实现不了,那么在stm3210x_it.c
中就需要我们注释掉PendSV_Handler与SVC_Handler这两个函数。

然后在FreeRTOSConfig.h
文件中重新宏定义。

/****************************************************************
FreeRTOS 与中断服务函数有关的配置选项
****************************************************************/
#define vPortSVCHandler SVC_Handler
#define xPortPendSVHandler PendSV_Handler
#define xPortSysTickHandler SysTick_Handler
FreeRTOSConfig.h
中的configTOTAL_HEAP_SIZE(size_t)(20*1024)
。其中20代表STM32F10x系列的堆栈大小,需要按照型号修改匹配。20为ZET6,10为C8T6。
具体点说就是16和32K的flash属于小;64和128K的为中型;256/384/512K的为大型。对应RAM为6和10K小,20K中,48和64k大。至此移植配置基本完成。
6、编写main函数
1、使用动态任务创建函数xTaskCreate()
创建一个开始任务start_task()
然后在开始任务中创建任务一task1_task()
和任务二task2_task()
2、开始任务start_task();
在执行完创建两个后就马上删除自己这个开始任务,自己删自己,铁锅炖自己。
3、之后系统就开始不断地执行任务一和任务二。
4、在任务一使用RTT不断打印:task1_task is running...
5、在任务二使用RTT不断打印:task1_task is running...
#include "sys.h"
#include "led.h"
#include "delay.h"
#include "SEGGER_RTT.h"
#include "FreeRTOS.h"
#include "task.h"
#define START_TASK_PRIO 1 //任务优先级
#define TASK1_TASK_PRIO 2 //任务优先级
#define TASK2_TASK_PRIO 3 //任务优先级
#define START_STK_SIZE 128 //任务堆栈大小
#define TASK1_STK_SIZE 128 //任务堆栈大小
#define TASK2_STK_SIZE 128 //任务堆栈大小
TaskHandle_t StartTask_Handler; //任务句柄
TaskHandle_t Task1Task_Handler; //任务句柄
TaskHandle_t Task2Task_Handler; //任务句柄
void start_task(void *pvParameters); //任务函数声明
void task1_task(void *pvParameters); //任务函数声明
void task2_task(void *pvParameters); //任务函数声明
int main(void)
{
delay_init();
LED_Init();
//动态创建开始任务
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); //任务句柄
//开启任务调度
/* 启动RTOS,其实就是启动“任务管理器”,启动之后任务管理器就开始调度线程,
* 此时pc(程序计数器)就会指向某线程的指令,开始多线程并发运行。
* 如果没有创建多线程的话,那就只有一个线程。*/
vTaskStartScheduler();
/* 由于调用了vTaskStartScheduler之后,PC就指向了线程中的指令,因此vTaskStartScheduler后面代码
* 并不会被CPU执行,所以vTaskStartScheduler后的代码没有意义。 */
while(1)
{
//这里的代码不会被执行,写了也没用
}
}
//开始任务任务函数
void start_task(void *pvParameters)
{
taskENTER_CRITICAL(); //进入临界区
//创建TASK1任务
xTaskCreate((TaskFunction_t )task1_task,
(const char* )"task1_task",
(uint16_t )TASK1_STK_SIZE,
(void* )NULL,
(UBaseType_t )TASK1_TASK_PRIO,
(TaskHandle_t* )&Task1Task_Handler);
//创建TASK2任务
xTaskCreate((TaskFunction_t )task2_task,
(const char* )"task2_task",
(uint16_t )TASK2_STK_SIZE,
(void* )NULL,
(UBaseType_t )TASK2_TASK_PRIO,
(TaskHandle_t* )&Task2Task_Handler);
vTaskDelete(StartTask_Handler); //删除开始任务
taskEXIT_CRITICAL(); //退出临界区
}
//task1任务函数
void task1_task(void *pvParameters)
{
for(;;)
{
SEGGER_RTT_SetTerminal(0);
SEGGER_RTT_printf(0, "task1_task is running...\r\n");
vTaskDelay(200);/* 延时 200 个 tick */
}
}
//task2任务函数
void task2_task(void *pvParameters)
{
for(;;)
{
SEGGER_RTT_SetTerminal(1);
SEGGER_RTT_printf(0, "task2_task is running...\r\n");
vTaskDelay(200);/* 延时 200 个 tick */
}
}
7、试验现象
编译、链接、下载,然后打开RTT观察效果。


有意思的一点是,我设置的task1
优先级是2,task2
优先级是3,从日志里明显是task2先跑,难道移植出了问题?
实则不然,FreeRTOS中优先级数值越低,优先级等级越低,空闲任务的优先级为0,这一点和很多RTOS都不相同,需要特别注意!
小结
RTOS学起来不难,初学时肯定会有畏难情绪,一看到几十集十几个小时的视频就不想看了,但是搞嵌入式如果RTOS都不学,肯定不行对吧,自己都觉得不合适。考虑大大多数人没有用到F4这样的片子,所以今天用的是F103的片子,这里移植到C8T6上也只是为了学习一下流程,如果真的使用可以用个大点的MCU。
另外也没有说主函数中的代码是怎么来的,关于任务的创建、优先级、堆栈下期再说,先熟悉流程,RTOS到底真的有那么神秘吗?