GD32移植FreeRTOS

这是一个比较简单的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文件夹的文件树

文件检索路径

三、创建配置文件

接下来,我们正式开始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时系统的线程挂起延时会失去作用,导致两个线程会轮流进行无间隔打印,目前还不清楚原因。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值