这是一个比较简单的FreeRTOS移植流程,由正点原子《FreeRTOS开发指南_V1.10》略做修改而来,旨在将FreeRTOS基本组件移植至梁山派开发板,移植难度相当低但是存在一些问题。
〇、开发环境
硬件:梁山派GD32F470开发板
软件:MDK v5.40
编译器版本:ARM Compiler V6.22
FreeRTOS版本:202212.01
一、下载源码
首先前往FreeRTOS中文官网下载源码,页面网址为下载 FreeRTOS - FreeRTOS™
下载完成后文件夹内容如下:
二、添加文件至工程
新建或者复制一个GD32F470裸机工程,并在目录下新建一个FreeRTOS子文件夹,用于存储FreeRTOS相关文件。
接下来前往FreeRTOSv202212.01\FreeRTOSv202212.01\FreeRTOS\Source文件夹,根据我们的需要添加文件。在《FreeRTOS开发指南_V1.10》中,对Source文件夹下的文件和文件夹功能描述如下:
接下来我们先将Source文件夹整个拷贝到前面创建的FreeRTOS子文件夹中。
然后我们需要根据芯片架构与编译器版本在portable文件夹中选择对应的FreeRTOS移植文件,主要包括两个部分:依托于编译器和芯片架构的port.c与portmacro.h文件和根据自身需求添加的heap文件。
port文件
port.c与portmacro.h文件依托芯片架构与编译器,GD32F470属于cortex-M4F架构,我们使用的编译器是ARM Compiler V6.22(ARMClang),因此打开portable文件夹下的ARMClang文件夹,如下所示:
根据记事本的要求,ARMClang使用的是GCC编译器的port文件,因此打开GCC文件夹,如下所示(这里仅截取部分):
可以发现,不同的内核架构都有属于自己的文件夹,这里我们使用ARM_CM4F文件夹中的port.c与portmacro.h文件。
如果各位使用的编译器是ARM Compiler V5的话,可以打开Keil文件夹:
根据要求,ARM Compiler V5使用RVDS文件夹中对应架构的port.c与portmacro.h文件即可。
heap文件
heap文件主要负责动态内存分配,位于FreeRTOSv202212.01\FreeRTOSv202212.01\FreeRTOS\Source\portable\MemMang文件夹中,有heap_1.c至heap_5.c五个版本。FreeRTOS官方文档对heap文件描述如下:
每个提供的实现都包含在单独的源文件中 (分别是 heap_1.c、 heap_2.c、heap_3.c、heap_4.c 和 heap_5.c), 位于主 RTOS 源代码下载内容的 Source/Portable/MemMang 目录下。 可根据需要添加其他实现方式。每次一个项目中, 只应包含其中一个源文件[这些可移植层函数定义的堆 将由 RTOS 内核使用, 即使用 RTOS 的应用程序选择使用自己的堆实现]。
以下是:
- heap_1—— 最简单,不允许释放内存。
- heap_2—— 允许释放内存,但不会合并相邻的空闲块。
- heap_3—— 简单包装了标准 malloc() 和 free(),以保证线程安全。
- heap_4—— 合并相邻的空闲块以避免碎片化。包含绝对地址放置选项。
- heap_5—— 如同 heap_4,能够跨越多个不相邻内存区域的堆。
注意:
- heap_1 不太有用,因为 FreeRTOS 添加了静态分配支持。
- heap_2 现在被视为旧版,因为较新的 heap_4 实现是首选。
更详细的描述可以参考官方文档:FreeRTOS 堆内存管理 - FreeRTOS™
GD32F4系列在地址映射上划分出了4块RAM空间,分别是起始地址0x2000 0000的SRAM0(112KB)、起始地址0x2001 C000的SRAM1(16KB)、起始地址0x2002 0000的SRAM2(64KB)与起始地址0x2003 0000的ADDSRAM(最高512KB),此外在代码地址空间内还划分出了64KB的TCMSRAM,一般情况下不使用,就此略过。除TCPRAM之外的各个SRAM地址空间是连续的,因此我们可以将其作为一块RAM使用,所以这里我们使用heap_4.c文件。
关于如何使用TCMSRAM,可以参考:GD32F4的TCMSRAM(紧耦合SRAM)该如何使用?_哔哩哔哩_bilibili
最后,在选择完我们需要的文件后,我们可以删除除了需要文件之外的所有文件,并将其添加至MDK的工程文件与文件路径中,如下所示:



三、创建配置文件
接下来,我们正式开始FreeRTOS的移植,首先,我们需要一个文件名为FreeRTOSConfig.h的配置文件,用于根据需求对FreeRTOS各个功能进行配置,根据《FreeRTOS开发指南_V1.10》文档,FreeRTOSConfig.h有三种获取方法,分别为自行编写、从官方演示工程中获取和从正点原子演示工程中获取,文档中推荐从正点原子演示工程获取,因此我也选择这个途径,我的FreeRTOSConfig.h从正点原子DMF407电机开发板的FreeRTOS例程2拷贝而来。
在拷贝完成后,我们需要注意FreeRTOSConfig.h的两处设置,一个是SystemCoreClock,也就是单片机主频,另一个是__NVIC_PRIO_BITS,即中断优先级位数,大部分单片机的SystemCoreClock都是预先设置好的,而__NVIC_PRIO_BITS是cortex-M单片机都有的配置,因此我们基本只需要将对应的头文件包含就行了,具体到GD32F470,我们只需要在文件开头添加如下代码即可:
#include "gd32f4xx.h"
由于 gd32f4xx.h头文件中定义了SystemCoreClock作为单片机主频与__NVIC_PRIO_BITS作为中断优先级位数,因此在包含之后FreeRTOSConfig.h头文件便会直接完成对接,不需要我们进行多余的操作。
四、中断对接
接下来,我们需要修改中断文件,使其能够与FreeRTOS中的相关函数对接,主要包括以下三个:系统时基定时器的中断(SysTick中断)、SVC中断、PendSV中断。
在port.c文件中已经使用汇编语言实现了上述的三个中断,并封装为了xPortSysTickHandler、vPortSVCHandler、xPortPendSVHandler三个函数,这也是为什么port.c文件依赖于单片机架构和编译器,所以我们只需要在gd32f4xx_it.c中将SysTick中断、SVC中断、PendSV中断三个中断服务函数注释,并且在FreeRTOSConfig.h中使用define对这三个函数进行重定义即可,函数重定义如下所示:
/* FreeRTOS中断服务函数相关定义 */
#define xPortPendSVHandler PendSV_Handler
#define vPortSVCHandler SVC_Handler
#define xPortSysTickHandler SysTick_Handler
完成之后,我们还需要进行一步修改,因为系统时基定时器中断需要进行初始化才能启动,因此我们需要在systick.c设置中断的时间间隔,使其与FreeRTOSConfig.h中定义的时间片时长匹配,如下所示:
void systick_config(void)
{
/* setup systick timer for 1000Hz interrupts */
// if(SysTick_Config(SystemCoreClock / 1000U)) {
if(SysTick_Config(configTICK_RATE_HZ)) {
/* capture error */
while(1) {
}
}
/* configure the systick handler priority */
NVIC_SetPriority(SysTick_IRQn, 0x00U);
}
至此,FreeRTOS的移植就初步完成了,不过为了验证我们的移植成果,我们还需要再进行几步操作。
五、串口初始化与printf重定向
这里我使用串口轮流打印来验证移植效果,所以我们需要完成串口的初始化与printf的重定向,如下所示:
void usart_gpio_config(uint32_t band_rate)
{
/* 开启时钟 */
rcu_periph_clock_enable(BSP_USART_TX_RCU);
rcu_periph_clock_enable(BSP_USART_RX_RCU);
rcu_periph_clock_enable(BSP_USART_RCU);
/* 配置GPIO复用功能 */
gpio_af_set(BSP_USART_TX_PORT,BSP_USART_AF,BSP_USART_TX_PIN);
gpio_af_set(BSP_USART_RX_PORT,BSP_USART_AF,BSP_USART_RX_PIN);
/* 配置GPIO的模式 */
/* 配置TX为复用模式 上拉模式 */
gpio_mode_set(BSP_USART_TX_PORT,GPIO_MODE_AF,GPIO_PUPD_PULLUP,BSP_USART_TX_PIN);
/* 配置RX为复用模式 上拉模式 */
gpio_mode_set(BSP_USART_RX_PORT, GPIO_MODE_AF,GPIO_PUPD_PULLUP,BSP_USART_RX_PIN);
/* 配置TX为推挽输出 50MHZ */
gpio_output_options_set(BSP_USART_TX_PORT,GPIO_OTYPE_PP,GPIO_OSPEED_50MHZ,BSP_USART_TX_PIN);
/* 配置RX为推挽输出 50MHZ */
gpio_output_options_set(BSP_USART_RX_PORT,GPIO_OTYPE_PP, GPIO_OSPEED_50MHZ, BSP_USART_RX_PIN);
/* 配置串口的参数 */
usart_deinit(BSP_USART);
usart_baudrate_set(BSP_USART,band_rate);
usart_parity_config(BSP_USART,USART_PM_NONE);
usart_word_length_set(BSP_USART,USART_WL_8BIT);
usart_stop_bit_set(BSP_USART,USART_STB_1BIT);
/* 使能串口 */
usart_enable(BSP_USART);
usart_transmit_config(BSP_USART,USART_TRANSMIT_ENABLE);
}
/* 发送函数 */
void usart_send_data(uint8_t ucch)
{
usart_data_transmit(BSP_USART,(uint8_t)ucch);
while(RESET == usart_flag_get(BSP_USART,USART_FLAG_TBE));
}
/* 串口发送字符串 */
void usart_send_string(uint8_t *ucstr)
{
while(ucstr && *ucstr)
{
usart_send_data(*ucstr++);
}
}
int fputc(int ch, FILE *f)
{
usart_send_data(ch);
// 等待发送数据缓冲区标志置位
return ch;
}
六、例程验证
这里我直接将正点原子DMF407电机开发板的FreeRTOS例程2中的freertos_demo.c文件复制后粘贴到了梁山派工程,由于目前我们完成移植的外设只有串口,因此我删除了LED控制、LCD刷屏的部分,只留下了两个线程中串口轮流打印。代码如下所示:
#include "freertos_demo.h"
/*FreeRTOS*********************************************************************************************/
#include "FreeRTOS.h"
#include "task.h"
#include "bsp_usart.h"
/******************************************************************************************************/
/*FreeRTOS配置*/
/* START_TASK 任务 配置
* 包括: 任务句柄 任务优先级 堆栈大小 创建任务
*/
#define START_TASK_PRIO 1 /* 任务优先级 */
#define START_STK_SIZE 128 /* 任务堆栈大小 */
TaskHandle_t StartTask_Handler; /* 任务句柄 */
void start_task(void *pvParameters); /* 任务函数 */
/* TASK1 任务 配置
* 包括: 任务句柄 任务优先级 堆栈大小 创建任务
*/
#define TASK1_PRIO 2 /* 任务优先级 */
#define TASK1_STK_SIZE 128 /* 任务堆栈大小 */
TaskHandle_t Task1Task_Handler; /* 任务句柄 */
void task1(void *pvParameters); /* 任务函数 */
/* TASK2 任务 配置
* 包括: 任务句柄 任务优先级 堆栈大小 创建任务
*/
#define TASK2_PRIO 3 /* 任务优先级 */
#define TASK2_STK_SIZE 128 /* 任务堆栈大小 */
TaskHandle_t Task2Task_Handler; /* 任务句柄 */
void task2(void *pvParameters); /* 任务函数 */
/******************************************************************************************************/
/**
* @brief FreeRTOS例程入口函数
* @param 无
* @retval 无
*/
void freertos_demo(void)
{
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();
}
/**
* @brief start_task
* @param pvParameters : 传入参数(未用到)
* @retval 无
*/
void start_task(void *pvParameters)
{
taskENTER_CRITICAL(); /* 进入临界区 */
/* 创建任务1 */
xTaskCreate((TaskFunction_t )task1,
(const char* )"task1",
(uint16_t )TASK1_STK_SIZE,
(void* )NULL,
(UBaseType_t )TASK1_PRIO,
(TaskHandle_t* )&Task1Task_Handler);
/* 创建任务2 */
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(); /* 退出临界区 */
}
/**
* @brief task1
* @param pvParameters : 传入参数(未用到)
* @retval 无
*/
void task1(void *pvParameters)
{
uint32_t task1_num = 0;
while(1)
{
printf("hello,RTOS!\r\n");
vTaskDelay(1000); /* 延时1000ticks */
}
}
/**
* @brief task2
* @param pvParameters : 传入参数(未用到)
* @retval 无
*/
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); /* 延时1000ticks */
}
}
七、最终效果
如下所示:
相关问题
最主要的问题是由于我移植的heap_4.c,因此地址与其它RAM不相连的TCPRAM是无法使用的,但是如果我使用heap_5.c的话,打印输出会出现问题,更确切的说,在使用heap_5.c时系统的线程挂起延时会失去作用,导致两个线程会轮流进行无间隔打印,目前还不清楚原因。