前言
由于作者本人之前学了一下FreeRTOS,想着移植到板子中玩玩,恰好前阵子嘉立创的天空星搞优惠活动,活动期间我就购入了一块GD版本的F407开发板,想着给移植过程进行一个简单的记录,方便本人回忆并供需要的读者参考学习,本文中的移植适合开发环境为Keil5的读者,由于本人技术有限,如果文章中出现错误,也请读者能够指出并纠正,万分感谢!
准备工作
工程模板获取
以下的移植过程中,工程结构使用立创官方提供的库函数点灯工程模板,以便作为统一参考。
立创官方飞书文档地址:
【立创·天空星GD32F407VET6】入门手册 - 飞书云文档 (feishu.cn)
在官方资料中获取工程模板
具体文件夹中的目录结构说明可以查看官方文档的描述,这里不过多赘述。
解压并打开该工程文件,这里我们先新建一个FreeRTOS文件夹,用于存放裁剪后的FreeRTOS源码
文件结构如下:
FreeRTOS源码获取
首先我们要移植一个RTOS,我们就得先去获得官方给的源代码,这里我们去到FreeRTOS的官方网站
FreeRTOS官网网址:FreeRTOS - Market leading RTOS (Real Time Operating System) for embedded systems with Internet of Things extensions
点击下载FreeRTOS
这里有两个版本,不带LTS的版本是官方发布的最新版本,带有示例代码,LTS版本是长期支持版本,没有示例代码,可以按需选择,这里我选择带示例代码的最新版本。
裁剪FreeRTOS文件
以下是带例程版本的FreeRTOS工程源码的目录结构
在FreeRTOS/Source路径下可以看到FreeRTOS要移植的核心文件,我们对非必要的文件进行删除
只保留上图中选中的这些文件,其余删除,删除完后的文件结构如下:
进一步删除portable文件夹中多余的文件,只留下Keil、MemMang、RVDS这三个文件夹
MemMang存放的是内存分配有关的文件,RVDS保存了对应处理器架构的文件
把裁剪好的文件放入先前在工程中创建好的FreeRTOS文件夹中
修改Keil5工程文件
根据上图文件路径,在官方例程的该目录下打开工程
点击魔术棒-->C/C++-->省略号, 添加以下文件路径到工程中
添加工程文件组FreeRTOSCore、FreeRTOSPort
FreeRTOSCore存放核心文件
FreeRTOSPort存放接口文件
(可以根据个人喜好进行创建和命名,此处仅为个人习惯)
FreeRTOSCore中添加以下文件(FreeRTOS文件夹内):
FreeRTOSPort中添加以下文件:
heap_4.c在MemMang文件夹中
因为天空星是F4内核的,port.c接口文件在RVDS/ARM_CM4路径下选择
最后工程整体为:
此时我们的移植尚未完成,FreeRTOS官方还提供了相关的配置文件,我们可以在带例程版本的官方源码中找到配置文件FreeRTOSConfig.h文件
找到M4内核例程代码,路径为:FreeRTOS\Demo\CORTEX_M4F_STM32F407ZG-SK
复制该文件到工程的include文件夹中,如下图:
编译Keil工程
此时出现以下报错:
这是因为配置文件的宏定义存在问题
在编译完成后的文件中找到配置文件,修改图示部分的代码:
修改为:
//#ifdef __ICCARM__
#if defined(__ICCARM__)||defined(__CC_ARM)||defined(__GNUC__)
#include <stdint.h>
extern uint32_t SystemCoreClock;
#endif
接着我们下滑找到SysTick的宏定义,将其注释
往上滑将FreeRTOS使用的钩子函数相关的宏定义改为0
此时再编译,已经是0e0w,非常令人愉悦
修改系统中断函数
systick定时器是FreeRTOS的“心脏”,我们需要对SysTick_Handler函数进行修改,使其能够提供一个系统的Tick。
在Borad文件夹下的board.c中添加头文件FreeRTOS.h和task.h,并修改SysTick_Handler:
将其修改如下:
代码如下:
// 头文件
#include "FreeRTOS.h"
#include "task.h"
// 修改的代码
extern void xPortSysTickHandler(void);
void SysTick_Handler(void){
if(xTaskGetSchedulerState()!=taskSCHEDULER_NOT_STARTED)//假如系统已经运行
{
xPortSysTickHandler();
}
}
此时编译,无报错即为修改正确。
使用串口打印调试信息
天空星上引出了串口0的TX和RX引脚,分别为U0T和U0R,正好可以用来作为打印调试信息的串口。如下图:
立创官方提供的库函数点灯例程中已经帮我们写好了串口初始化函数,波特率为115200,如下图:
串口打印需要使用微库,点击魔术棒,点击use MicroLIB勾选
实现LED灯闪烁
修改board.c中的延迟函数
在头文件申明的下方加上以下全局变量:
static uint8_t fac_us=0; //us延时倍乘数
static uint16_t fac_ms=0; //ms延时倍乘数,在rtos下,代表每个节拍的ms数
删除board.c原本的systick_config函数,更改为以下代码:
//初始化延迟函数
//当使用OS的时候,此函数会初始化OS的时钟节拍
void systick_config(void)
{
uint32_t reload;
systick_clksource_set(SYSTICK_CLKSOURCE_HCLK);//选择外部时钟 HCLK
fac_us=SystemCoreClock/1000000; //不论是否使用OS,fac_us都需要使用
reload=SystemCoreClock/1000000; //每秒钟的计数次数 单位为M
reload*=1000000/configTICK_RATE_HZ; //根据configTICK_RATE_HZ设定溢出时间
//reload为24位寄存器,最大值:16777216,在72M下,约合0.233s左右
fac_ms=1000/configTICK_RATE_HZ; //代表OS可以延时的最少单位
SysTick->CTRL|=SysTick_CTRL_TICKINT_Msk; //开启SYSTICK中断
SysTick->LOAD=reload; //每1/configTICK_RATE_HZ秒中断一次
SysTick->CTRL|=SysTick_CTRL_ENABLE_Msk; //开启SYSTICK
}
删除原本的delay_us和delay_ms函数,添加以下延迟函数:
//延时nus
//nus为要延时的us数.
void delay_us(uint32_t nus)
{
uint32_t ticks;
uint32_t told,tnow,tcnt=0;
uint32_t reload=SysTick->LOAD; //LOAD的值
ticks=nus*fac_us; //需要的节拍数
told=SysTick->VAL; //刚进入时的计数器值
while(1)
{
tnow=SysTick->VAL;
if(tnow!=told)
{
if(tnow<told)tcnt+=told-tnow; //这里注意一下SYSTICK是一个递减的计数器就可以了
else tcnt+=reload-tnow+told;
told=tnow;
if(tcnt>=ticks)break; //时间超过/等于要延迟的时间,则退出.
}
};
}
//延时nms
//nms:要延时的ms数,会引起任务调度
void delay_ms(uint16_t nms)
{
if(xTaskGetSchedulerState()!=taskSCHEDULER_NOT_STARTED)//系统已经运行
{
if(nms>=fac_ms) //延时的时间大于OS的最少时间周期
{
vTaskDelay(nms/fac_ms); //FreeRTOS延时
}
nms%=fac_ms; //OS已经无法提供这么小的延时了,采用普通方式延时
}
delay_us((uint32_t)(nms*1000)); //普通方式延时
}
//延时nms,不会引起任务调度
//nms:要延时的ms数
void delay_xms(uint32_t nms)
{
uint32_t i;
for(i=0;i<nms;i++) delay_us(1000);
}
修改完.c文件记得修改board.h文件
添加FreeRTOS的头文件,FreeRTOS.h和task.h
定义任务优先级和堆栈大小等,此处LED任务1为点亮LED,任务2为熄灭LED
任务优先级的值越大,优先级越大
#include "board.h"
#include "bsp_led.h"
#include "bsp_uart.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 LED1_TASK_PRIO 3
//任务堆栈大小
#define LED1_STK_SIZE 50
//任务句柄
TaskHandle_t LED1Task_Handler;
//任务函数
void LED_Thread1(void *pvParameters);
//任务优先级
#define LED2_TASK_PRIO 4
//任务堆栈大小
#define LED2_STK_SIZE 50
//任务句柄
TaskHandle_t LED2Task_Handler;
//任务函数
void LED_Thread2(void *pvParameters);
注意:定义优先级时不能使用0和2,因为在FreeRTOSConfig.h文件中,为软件定时器分配了优先级2,而优先级0为空闲中断,这两个优先级都被占用.
创建任务
//创建开始任务
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(); //进入临界区
//创建LED1任务
xTaskCreate((TaskFunction_t )LED_Thread1,
(const char* )"led1_task",
(uint16_t )LED1_STK_SIZE,
(void* )NULL,
(UBaseType_t )LED1_TASK_PRIO,
(TaskHandle_t* )&LED1Task_Handler);
xTaskCreate((TaskFunction_t )LED_Thread2,
(const char* )"led2_task",
(uint16_t )LED2_STK_SIZE,
(void* )NULL,
(UBaseType_t )LED2_TASK_PRIO,
(TaskHandle_t* )&LED2Task_Handler);
vTaskDelete(StartTask_Handler); //删除开始任务
taskEXIT_CRITICAL(); //退出临界区
}
LED任务1及LED任务2
//LED1任务函数
void LED_Thread1(void *pvParameters)
{
while(1)
{
bsp_led_on(LED1);
delay_ms(100);
}
}
//LED2任务函数
void LED_Thread2(void *pvParameters)
{
while(1)
{
bsp_led_off(LED1);
delay_ms(250);
}
}
主函数总体代码如下:
#include "board.h"
#include "bsp_led.h"
#include "bsp_uart.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 LED1_TASK_PRIO 3
//任务堆栈大小
#define LED1_STK_SIZE 50
//任务句柄
TaskHandle_t LED1Task_Handler;
//任务函数
void LED_Thread1(void *pvParameters);
//任务优先级
#define LED2_TASK_PRIO 4
//任务堆栈大小
#define LED2_STK_SIZE 50
//任务句柄
TaskHandle_t LED2Task_Handler;
//任务函数
void LED_Thread2(void *pvParameters);
int main(void)
{
board_init();
bsp_led_init(); // 初始化板上LED
bsp_uart_init(); // 初始化uart
//创建开始任务
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(); //进入临界区
//创建LED1任务
xTaskCreate((TaskFunction_t )LED_Thread1,
(const char* )"led1_task",
(uint16_t )LED1_STK_SIZE,
(void* )NULL,
(UBaseType_t )LED1_TASK_PRIO,
(TaskHandle_t* )&LED1Task_Handler);
xTaskCreate((TaskFunction_t )LED_Thread2,
(const char* )"led2_task",
(uint16_t )LED2_STK_SIZE,
(void* )NULL,
(UBaseType_t )LED2_TASK_PRIO,
(TaskHandle_t* )&LED2Task_Handler);
vTaskDelete(StartTask_Handler); //删除开始任务
taskEXIT_CRITICAL(); //退出临界区
}
//LED1任务函数
void LED_Thread1(void *pvParameters)
{
while(1)
{
bsp_led_on(LED1);
delay_ms(100);
}
}
//LED2任务函数
void LED_Thread2(void *pvParameters)
{
while(1)
{
bsp_led_off(LED1);
delay_ms(250);
}
}
编译烧录,此时LED灯闪烁。至此,FreeRTOS的简单移植也已经完成。