准备工作

  1. GD32开发板。案例是以梁山派为开发板。
  2. Windows系统的电脑。当前是以Win11的电脑来实现案例的。
  3. Keil开发工具。并且已经安装好GD32依赖环境。
  4. FreeRTOS源码包。下载地址为: https://github.com/FreeRTOS/FreeRTOS/releases

033.png
当前以FreeRTOSv202212.01版本为例。也是目前的最新版本。

移植流程

项目基础环境准备

参考: https://www.yuque.com/icheima/osa819/uvhcdkzgmrus22iy#HJokw
新建项目名称为:FreeRTOSTemplate
项目结构如下:
034.png
确保以下配置信息的正确性:

  1. Target的配置

035.png

  1. Output的配置

036.png

  1. C/C++(AC6)配置

037.png

  1. Debug配置

038.png

FreeRTOS移植

FreeRTOS源码裁剪

  1. 源码解压

039.png

  1. 进入FreeRTOS/Source目录。此目录就是我们要移植的源码。

040.png

  1. 在你新建的FreeRTOSTemplate工程目录下,新建一个文件夹FreeRTOS,将以上源码拷贝到这个文件夹中。

041.png

  1. 删除不必要的文件。来到portable目录只保留GCCMemManag目录

042.png

源码目录构建

  1. 新建两个Group:FreeRTOS_CoreFreeRTOS_Port

043.png

  1. FreeRTOS_Core添加源码。源码为FreeRTOS根目录下的c文件

044.png
045.png

  1. FreeRTOS_Port添加源码。源码为FreeRTOS/portable/MemMang下的heap_4.c。源码为FreeRTOS/portable/GCC/ARM_CM4F下的port.c

046.png047.png

  1. 添加头文件支持。将FreeRTOS/includeFreeRTOS/portable/GCC/ARM_CM4F添加到头文件依赖中。

048.png

FreeRTOSConfig.h文件缺失解决

编译项目,会出现以下错误:
049.png
来到FreeRTOS源码目录中,找到FreeRTOSv202212.01\FreeRTOS\Demo\CORTEX_M4F_STM32F407ZG-SK目录中的FreeRTOSConfig.h文件,进行拷贝。
050.png
FreeRTOSConfig.h文件拷贝到项目目录中的FreeRTOS目录下
051.png
添加头文件引入。将FreeRTOS目录添加到include path中。
052.png

SystemCoreClock问题解决

编译项目,会出现以下错误:
053.png
来到FreeRTOSConfig.h文件中,观察以下内容:
054.png

  1. SystemCoreClock其实是有的
  2. SystemCoreClock在编译预处理前没有被定义出来。原因是extern uint32_t SystemCoreClock;这一段没有进入编译,因为在编译预处理前不满足#ifdef __ICCARM__这个条件。编译器c语言条件不满足。我们将以上代码进行修改。
    1. #ifdef __ICCARM__
    2. #include <stdint.h>
    3. extern uint32_t SystemCoreClock;
    4. #endif
    1. #if defined(__ICCARM__) || defined(__CC_ARM) || defined(__GNUC__)
    2. #include <stdint.h>
    3. extern uint32_t SystemCoreClock;
    4. #endif

    xxx_Handler问题解决

    编译项目,会出现以下错误:
    055.png
    来到gd32f4xx_it.c文件中。修改三个函数SVC_Handler,PendSV_Handler,SysTick_Handler,将函数通过宏定义包裹: ```c

    ifndef SYS_SUPPORT_OS

endif

  1. 修改后如下:
  2. ```c
  3. #ifndef SYS_SUPPORT_OS
  4. void SVC_Handler(void)
  5. {
  6. }
  7. #endif
  8. ...
  9. #ifndef SYS_SUPPORT_OS
  10. void PendSV_Handler(void)
  11. {
  12. }
  13. #endif
  14. ...
  15. #ifndef SYS_SUPPORT_OS
  16. void SysTick_Handler(void)
  17. {
  18. delay_decrement();
  19. }
  20. #endif

配置C/C++(AC6)中的define为:USE_STDPERIPH_DRIVER,GD32F470,SYS_SUPPORT_OS
056.png

vApplicationXXXHook问题解决

编译项目,会出现以下错误:
057.png
修改FreeRTOSConfig.h中的配置:

  1. #define configCHECK_FOR_STACK_OVERFLOW 0
  1. #define configUSE_IDLE_HOOK 0
  1. #define configUSE_TICK_HOOK 0
  1. #define configUSE_MALLOC_FAILED_HOOK 0

Systick硬件delay

修改systick.c源码,修改如下

  1. #ifndef SYS_SUPPORT_OS
  2. void delay_1ms(uint32_t count)
  3. {
  4. delay = count;
  5. while(0U != delay) {
  6. }
  7. }
  8. void delay_decrement(void)
  9. {
  10. if(0U != delay) {
  11. delay--;
  12. }
  13. }
  14. #else
  15. void delay_1us(uint32_t count) {
  16. uint32_t ticks;
  17. uint32_t told, tnow, reload, tcnt = 0;
  18. reload = SysTick->LOAD;
  19. ticks = count * (SystemCoreClock / 1000000);
  20. told = SysTick->VAL;
  21. while(1) {
  22. tnow=SysTick->VAL;
  23. if(tnow != told) {
  24. if(tnow<told) tcnt+=told-tnow;
  25. else tcnt+=reload-tnow+told;
  26. told=tnow;
  27. if(tcnt>=ticks)break;
  28. }
  29. }
  30. }
  31. void delay_1ms(uint32_t count)
  32. {
  33. uint32_t i;
  34. for (i=0; i<count; i++)
  35. {
  36. delay_1us(1000);
  37. }
  38. }
  39. #endif

LED点亮测试

009.png
开启任务,点亮PE3PD7
在main.c中编写代码如下:

  1. #include "gd32f4xx.h"
  2. #include "systick.h"
  3. #include <stdio.h>
  4. #include "main.h"
  5. #include "FreeRTOS.h"
  6. #include "task.h"
  7. TaskHandle_t StartTask_Handler;
  8. TaskHandle_t Task1_Handler;
  9. TaskHandle_t Task2_Handler;
  10. void task1(void *pvParameters) {
  11. while(1) {
  12. vTaskDelay(300);
  13. gpio_bit_set(GPIOE, GPIO_PIN_3);
  14. vTaskDelay(300);
  15. gpio_bit_reset(GPIOE, GPIO_PIN_3);
  16. }
  17. }
  18. void task2(void *pvParameters) {
  19. while(1) {
  20. vTaskDelay(1000);
  21. gpio_bit_set(GPIOD, GPIO_PIN_7);
  22. vTaskDelay(1000);
  23. gpio_bit_reset(GPIOD, GPIO_PIN_7);
  24. }
  25. }
  26. void start_task(void *pvParameters) {
  27. taskENTER_CRITICAL();
  28. xTaskCreate((TaskFunction_t)task1,
  29. (const char* )"task1",
  30. 50,
  31. NULL,
  32. 2,
  33. (TaskHandle_t* )&Task1_Handler);
  34. xTaskCreate((TaskFunction_t)task2,
  35. (const char* )"task2",
  36. 50,
  37. NULL,
  38. 2,
  39. (TaskHandle_t* )&Task2_Handler);
  40. vTaskDelete(StartTask_Handler);
  41. taskEXIT_CRITICAL();
  42. }
  43. void GPIO_config() {
  44. // 1. 时钟初始化
  45. rcu_periph_clock_enable(RCU_GPIOE);
  46. // 2. 配置GPIO 输入输出模式
  47. gpio_mode_set(GPIOE, GPIO_MODE_OUTPUT, GPIO_PUPD_NONE, GPIO_PIN_3);
  48. // 3. 配置GPIO 模式的操作方式
  49. gpio_output_options_set(GPIOE, GPIO_OTYPE_PP, GPIO_OSPEED_MAX, GPIO_PIN_3);
  50. // 1. 时钟初始化
  51. rcu_periph_clock_enable(RCU_GPIOD);
  52. // 2. 配置GPIO 输入输出模式
  53. gpio_mode_set(GPIOD, GPIO_MODE_OUTPUT, GPIO_PUPD_NONE, GPIO_PIN_7);
  54. // 3. 配置GPIO 模式的操作方式
  55. gpio_output_options_set(GPIOD, GPIO_OTYPE_PP, GPIO_OSPEED_MAX, GPIO_PIN_7);
  56. }
  57. int main(void)
  58. {
  59. systick_config();
  60. GPIO_config();
  61. xTaskCreate((TaskFunction_t)start_task,
  62. (const char* )"start_task",
  63. 128,
  64. NULL,
  65. 1,
  66. (TaskHandle_t* )&StartTask_Handler);
  67. vTaskStartScheduler();
  68. while(1) {}
  69. }

HelloWorld

实现多任务串口打印功能。
开发流程:

  1. 通过模板创建项目
  2. 添加串口支持库
  3. 添加所需要c依赖,以及包含所需要h头
  4. 运行测试,查看效果。 ```c

    include “gd32f4xx.h”

    include “systick.h”

    include

    include “main.h”

    include “FreeRTOS.h”

    include “task.h”

    include “Usart0.h”

TaskHandle_t StartTask_Handler; TaskHandle_t Task1_Handler; TaskHandle_t Task2_Handler;

void task1(void *pvParameters) { while(1) { vTaskDelay(500); printf(“task 1\n”); } }

void task2(void *pvParameters) { while(1) { vTaskDelay(1000); printf(“task 2\n”); } }

void start_task(void *pvParameters) { taskENTER_CRITICAL();

  1. xTaskCreate((TaskFunction_t)task1,
  2. (const char* )"task1",
  3. 50,
  4. NULL,
  5. 2,
  6. (TaskHandle_t* )&Task1_Handler);
  7. xTaskCreate((TaskFunction_t)task2,
  8. (const char* )"task2",
  9. 50,
  10. NULL,
  11. 2,
  12. (TaskHandle_t* )&Task2_Handler);
  13. vTaskDelete(StartTask_Handler);
  14. taskEXIT_CRITICAL();

}

void Usart0_recv(uint8_t *data, uint32_t len) { printf(“recv: %s\n”, data); }

int main(void) { nvic_priority_group_set(NVIC_PRIGROUP_PRE2_SUB2); systick_config(); Usart0_init();

  1. xTaskCreate((TaskFunction_t)start_task,
  2. (const char* )"start_task",
  3. 128,
  4. NULL,
  5. 1,
  6. (TaskHandle_t* )&StartTask_Handler);
  7. vTaskStartScheduler();
  8. while(1) {}

}

  1. ```c
  2. #ifndef __USART0_H__
  3. #define __USART0_H__
  4. #include "gd32f4xx.h"
  5. #include "systick.h"
  6. #define USART0_RECV_CALLBACK 1
  7. void Usart0_init();
  8. void Usart0_send_data(uint8_t data);
  9. void Usart0_send_string(char *data);
  10. #if USART0_RECV_CALLBACK
  11. extern void Usart0_recv(uint8_t *data, uint32_t len);
  12. #endif
  13. #endif
  1. #include "Usart0.h"
  2. #include <stdio.h>
  3. #define USART0_RECV_LEN 4096
  4. static uint8_t usart0_recv_buff[USART0_RECV_LEN]; // 接收缓冲区
  5. static uint32_t usart0_recv_len = 0;
  6. void Usart0_init() {
  7. uint32_t usartx = USART0;
  8. uint32_t usartx_rcu = RCU_USART0;
  9. uint32_t usartx_irqn = USART0_IRQn;
  10. uint32_t tx_gpio_rcu = RCU_GPIOA;
  11. uint32_t tx_gpio_port = GPIOA;
  12. uint32_t tx_gpio_pin = GPIO_PIN_9;
  13. uint32_t rx_gpio_rcu = RCU_GPIOA;
  14. uint32_t rx_gpio_port = GPIOA;
  15. uint32_t rx_gpio_pin = GPIO_PIN_10;
  16. ////////// gpio config ///////////////
  17. // 配置时钟
  18. rcu_periph_clock_enable(tx_gpio_rcu);
  19. rcu_periph_clock_enable(rx_gpio_rcu);
  20. // 配置模式
  21. gpio_mode_set(tx_gpio_port, GPIO_MODE_AF, GPIO_PUPD_PULLUP, tx_gpio_pin);
  22. gpio_mode_set(rx_gpio_port, GPIO_MODE_AF, GPIO_PUPD_PULLUP, rx_gpio_pin);
  23. // 配置复用功能
  24. gpio_af_set(tx_gpio_port, GPIO_AF_7, tx_gpio_pin);
  25. gpio_af_set(rx_gpio_port, GPIO_AF_7, rx_gpio_pin);
  26. // 配置输出参数
  27. gpio_output_options_set(tx_gpio_port, GPIO_OTYPE_PP, GPIO_OSPEED_50MHZ, tx_gpio_pin);
  28. gpio_output_options_set(rx_gpio_port, GPIO_OTYPE_PP, GPIO_OSPEED_50MHZ, rx_gpio_pin);
  29. ////////// usart config /////////////
  30. // 串口时钟
  31. rcu_periph_clock_enable(usartx_rcu);
  32. // USART复位
  33. usart_deinit(usartx);
  34. usart_baudrate_set(usartx, 115200);// 波特率
  35. usart_parity_config(usartx, USART_PM_NONE);// 校验位
  36. usart_word_length_set(usartx, USART_WL_8BIT);// 数据位数
  37. usart_stop_bit_set(usartx, USART_STB_1BIT);// 停止位
  38. usart_data_first_config(usartx, USART_MSBF_LSB);// 先发送高位还是低位
  39. // 发送功能配置
  40. usart_transmit_config(usartx, USART_TRANSMIT_ENABLE);
  41. // 接收功能配置
  42. usart_receive_config(usartx, USART_RECEIVE_ENABLE);
  43. // 接收中断配置
  44. nvic_irq_enable(usartx_irqn, 2, 2);
  45. // usart int rbne
  46. usart_interrupt_enable(usartx, USART_INT_RBNE);
  47. usart_interrupt_enable(usartx, USART_INT_IDLE);
  48. // 使能串口
  49. usart_enable(usartx);
  50. }
  51. void Usart0_send_data(uint8_t data) {
  52. //通过USART发送
  53. usart_data_transmit(USART0, data);
  54. //判断缓冲区是否已经空了
  55. while(RESET == usart_flag_get(USART0, USART_FLAG_TBE));
  56. }
  57. //发送字符串
  58. void Usart0_send_string(char *data) {
  59. while(data && *data) {
  60. Usart0_send_data((uint8_t)(*data));
  61. data++;
  62. }
  63. }
  64. void USART0_IRQHandler(void) {
  65. if ((usart_interrupt_flag_get(USART0, USART_INT_FLAG_RBNE)) == SET) {
  66. uint16_t value = usart_data_receive(USART0);
  67. usart0_recv_buff[usart0_recv_len] = value;
  68. usart0_recv_len++;
  69. // 接收到单个byte,可以在此处处理接收到value。TODO:
  70. }
  71. if (usart_interrupt_flag_get(USART0, USART_INT_FLAG_IDLE) == SET) {
  72. //读取缓冲区,清空缓冲区
  73. usart_data_receive(USART0);
  74. usart0_recv_buff[usart0_recv_len] = '\0';
  75. // 接收停止时,缓冲区中的数据,可以在此处处理缓存数据。TODO:
  76. #if USART0_RECV_CALLBACK
  77. Usart0_recv(usart0_recv_buff, usart0_recv_len);
  78. #endif
  79. usart0_recv_len = 0;
  80. }
  81. }
  82. int fputc(int ch, FILE *f){
  83. Usart0_send_data((uint8_t)ch);
  84. return ch;
  85. }