准备工作
- GD32开发板。案例是以梁山派为开发板。
- Windows系统的电脑。当前是以Win11的电脑来实现案例的。
- Keil开发工具。并且已经安装好GD32依赖环境。
- FreeRTOS源码包。下载地址为: https://github.com/FreeRTOS/FreeRTOS/releases
当前以FreeRTOSv202212.01
版本为例。也是目前的最新版本。
移植流程
项目基础环境准备
参考: https://www.yuque.com/icheima/osa819/uvhcdkzgmrus22iy#HJokw
新建项目名称为:FreeRTOSTemplate
项目结构如下:
确保以下配置信息的正确性:
- Target的配置
- Output的配置
- C/C++(AC6)配置
- Debug配置
FreeRTOS移植
FreeRTOS源码裁剪
- 源码解压
- 进入
FreeRTOS/Source
目录。此目录就是我们要移植的源码。
- 在你新建的
FreeRTOSTemplate
工程目录下,新建一个文件夹FreeRTOS
,将以上源码拷贝到这个文件夹中。
- 删除不必要的文件。来到
portable
目录只保留GCC
和MemManag
目录
源码目录构建
- 新建两个Group:
FreeRTOS_Core
和FreeRTOS_Port
FreeRTOS_Core
添加源码。源码为FreeRTOS
根目录下的c文件
FreeRTOS_Port
添加源码。源码为FreeRTOS/portable/MemMang
下的heap_4.c
。源码为FreeRTOS/portable/GCC/ARM_CM4F
下的port.c
- 添加头文件支持。将
FreeRTOS/include
和FreeRTOS/portable/GCC/ARM_CM4F
添加到头文件依赖中。
FreeRTOSConfig.h文件缺失解决
编译项目,会出现以下错误:
来到FreeRTOS源码目录中,找到FreeRTOSv202212.01\FreeRTOS\Demo\CORTEX_M4F_STM32F407ZG-SK
目录中的FreeRTOSConfig.h
文件,进行拷贝。
将FreeRTOSConfig.h
文件拷贝到项目目录中的FreeRTOS
目录下
添加头文件引入。将FreeRTOS
目录添加到include path中。
SystemCoreClock问题解决
编译项目,会出现以下错误:
来到FreeRTOSConfig.h
文件中,观察以下内容:
- SystemCoreClock其实是有的
- SystemCoreClock在编译预处理前没有被定义出来。原因是
extern uint32_t SystemCoreClock;
这一段没有进入编译,因为在编译预处理前不满足#ifdef __ICCARM__
这个条件。编译器c语言条件不满足。我们将以上代码进行修改。#ifdef __ICCARM__
#include <stdint.h>
extern uint32_t SystemCoreClock;
#endif
#if defined(__ICCARM__) || defined(__CC_ARM) || defined(__GNUC__)
#include <stdint.h>
extern uint32_t SystemCoreClock;
#endif
xxx_Handler问题解决
编译项目,会出现以下错误:
来到gd32f4xx_it.c
文件中。修改三个函数SVC_Handler
,PendSV_Handler
,SysTick_Handler
,将函数通过宏定义包裹: ```cifndef SYS_SUPPORT_OS
endif
修改后如下:
```c
#ifndef SYS_SUPPORT_OS
void SVC_Handler(void)
{
}
#endif
...
#ifndef SYS_SUPPORT_OS
void PendSV_Handler(void)
{
}
#endif
...
#ifndef SYS_SUPPORT_OS
void SysTick_Handler(void)
{
delay_decrement();
}
#endif
配置C/C++(AC6)
中的define
为:USE_STDPERIPH_DRIVER,GD32F470,SYS_SUPPORT_OS
vApplicationXXXHook问题解决
编译项目,会出现以下错误:
修改FreeRTOSConfig.h
中的配置:
#define configCHECK_FOR_STACK_OVERFLOW 0
#define configUSE_IDLE_HOOK 0
#define configUSE_TICK_HOOK 0
#define configUSE_MALLOC_FAILED_HOOK 0
Systick硬件delay
修改systick.c
源码,修改如下
#ifndef SYS_SUPPORT_OS
void delay_1ms(uint32_t count)
{
delay = count;
while(0U != delay) {
}
}
void delay_decrement(void)
{
if(0U != delay) {
delay--;
}
}
#else
void delay_1us(uint32_t count) {
uint32_t ticks;
uint32_t told, tnow, reload, tcnt = 0;
reload = SysTick->LOAD;
ticks = count * (SystemCoreClock / 1000000);
told = SysTick->VAL;
while(1) {
tnow=SysTick->VAL;
if(tnow != told) {
if(tnow<told) tcnt+=told-tnow;
else tcnt+=reload-tnow+told;
told=tnow;
if(tcnt>=ticks)break;
}
}
}
void delay_1ms(uint32_t count)
{
uint32_t i;
for (i=0; i<count; i++)
{
delay_1us(1000);
}
}
#endif
LED点亮测试
开启任务,点亮PE3
和PD7
在main.c中编写代码如下:
#include "gd32f4xx.h"
#include "systick.h"
#include <stdio.h>
#include "main.h"
#include "FreeRTOS.h"
#include "task.h"
TaskHandle_t StartTask_Handler;
TaskHandle_t Task1_Handler;
TaskHandle_t Task2_Handler;
void task1(void *pvParameters) {
while(1) {
vTaskDelay(300);
gpio_bit_set(GPIOE, GPIO_PIN_3);
vTaskDelay(300);
gpio_bit_reset(GPIOE, GPIO_PIN_3);
}
}
void task2(void *pvParameters) {
while(1) {
vTaskDelay(1000);
gpio_bit_set(GPIOD, GPIO_PIN_7);
vTaskDelay(1000);
gpio_bit_reset(GPIOD, GPIO_PIN_7);
}
}
void start_task(void *pvParameters) {
taskENTER_CRITICAL();
xTaskCreate((TaskFunction_t)task1,
(const char* )"task1",
50,
NULL,
2,
(TaskHandle_t* )&Task1_Handler);
xTaskCreate((TaskFunction_t)task2,
(const char* )"task2",
50,
NULL,
2,
(TaskHandle_t* )&Task2_Handler);
vTaskDelete(StartTask_Handler);
taskEXIT_CRITICAL();
}
void GPIO_config() {
// 1. 时钟初始化
rcu_periph_clock_enable(RCU_GPIOE);
// 2. 配置GPIO 输入输出模式
gpio_mode_set(GPIOE, GPIO_MODE_OUTPUT, GPIO_PUPD_NONE, GPIO_PIN_3);
// 3. 配置GPIO 模式的操作方式
gpio_output_options_set(GPIOE, GPIO_OTYPE_PP, GPIO_OSPEED_MAX, GPIO_PIN_3);
// 1. 时钟初始化
rcu_periph_clock_enable(RCU_GPIOD);
// 2. 配置GPIO 输入输出模式
gpio_mode_set(GPIOD, GPIO_MODE_OUTPUT, GPIO_PUPD_NONE, GPIO_PIN_7);
// 3. 配置GPIO 模式的操作方式
gpio_output_options_set(GPIOD, GPIO_OTYPE_PP, GPIO_OSPEED_MAX, GPIO_PIN_7);
}
int main(void)
{
systick_config();
GPIO_config();
xTaskCreate((TaskFunction_t)start_task,
(const char* )"start_task",
128,
NULL,
1,
(TaskHandle_t* )&StartTask_Handler);
vTaskStartScheduler();
while(1) {}
}
HelloWorld
实现多任务串口打印功能。
开发流程:
- 通过模板创建项目
- 添加串口支持库
- 添加所需要c依赖,以及包含所需要h头
- 运行测试,查看效果。
```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();
xTaskCreate((TaskFunction_t)task1,
(const char* )"task1",
50,
NULL,
2,
(TaskHandle_t* )&Task1_Handler);
xTaskCreate((TaskFunction_t)task2,
(const char* )"task2",
50,
NULL,
2,
(TaskHandle_t* )&Task2_Handler);
vTaskDelete(StartTask_Handler);
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();
xTaskCreate((TaskFunction_t)start_task,
(const char* )"start_task",
128,
NULL,
1,
(TaskHandle_t* )&StartTask_Handler);
vTaskStartScheduler();
while(1) {}
}
```c
#ifndef __USART0_H__
#define __USART0_H__
#include "gd32f4xx.h"
#include "systick.h"
#define USART0_RECV_CALLBACK 1
void Usart0_init();
void Usart0_send_data(uint8_t data);
void Usart0_send_string(char *data);
#if USART0_RECV_CALLBACK
extern void Usart0_recv(uint8_t *data, uint32_t len);
#endif
#endif
#include "Usart0.h"
#include <stdio.h>
#define USART0_RECV_LEN 4096
static uint8_t usart0_recv_buff[USART0_RECV_LEN]; // 接收缓冲区
static uint32_t usart0_recv_len = 0;
void Usart0_init() {
uint32_t usartx = USART0;
uint32_t usartx_rcu = RCU_USART0;
uint32_t usartx_irqn = USART0_IRQn;
uint32_t tx_gpio_rcu = RCU_GPIOA;
uint32_t tx_gpio_port = GPIOA;
uint32_t tx_gpio_pin = GPIO_PIN_9;
uint32_t rx_gpio_rcu = RCU_GPIOA;
uint32_t rx_gpio_port = GPIOA;
uint32_t rx_gpio_pin = GPIO_PIN_10;
////////// gpio config ///////////////
// 配置时钟
rcu_periph_clock_enable(tx_gpio_rcu);
rcu_periph_clock_enable(rx_gpio_rcu);
// 配置模式
gpio_mode_set(tx_gpio_port, GPIO_MODE_AF, GPIO_PUPD_PULLUP, tx_gpio_pin);
gpio_mode_set(rx_gpio_port, GPIO_MODE_AF, GPIO_PUPD_PULLUP, rx_gpio_pin);
// 配置复用功能
gpio_af_set(tx_gpio_port, GPIO_AF_7, tx_gpio_pin);
gpio_af_set(rx_gpio_port, GPIO_AF_7, rx_gpio_pin);
// 配置输出参数
gpio_output_options_set(tx_gpio_port, GPIO_OTYPE_PP, GPIO_OSPEED_50MHZ, tx_gpio_pin);
gpio_output_options_set(rx_gpio_port, GPIO_OTYPE_PP, GPIO_OSPEED_50MHZ, rx_gpio_pin);
////////// usart config /////////////
// 串口时钟
rcu_periph_clock_enable(usartx_rcu);
// USART复位
usart_deinit(usartx);
usart_baudrate_set(usartx, 115200);// 波特率
usart_parity_config(usartx, USART_PM_NONE);// 校验位
usart_word_length_set(usartx, USART_WL_8BIT);// 数据位数
usart_stop_bit_set(usartx, USART_STB_1BIT);// 停止位
usart_data_first_config(usartx, USART_MSBF_LSB);// 先发送高位还是低位
// 发送功能配置
usart_transmit_config(usartx, USART_TRANSMIT_ENABLE);
// 接收功能配置
usart_receive_config(usartx, USART_RECEIVE_ENABLE);
// 接收中断配置
nvic_irq_enable(usartx_irqn, 2, 2);
// usart int rbne
usart_interrupt_enable(usartx, USART_INT_RBNE);
usart_interrupt_enable(usartx, USART_INT_IDLE);
// 使能串口
usart_enable(usartx);
}
void Usart0_send_data(uint8_t data) {
//通过USART发送
usart_data_transmit(USART0, data);
//判断缓冲区是否已经空了
while(RESET == usart_flag_get(USART0, USART_FLAG_TBE));
}
//发送字符串
void Usart0_send_string(char *data) {
while(data && *data) {
Usart0_send_data((uint8_t)(*data));
data++;
}
}
void USART0_IRQHandler(void) {
if ((usart_interrupt_flag_get(USART0, USART_INT_FLAG_RBNE)) == SET) {
uint16_t value = usart_data_receive(USART0);
usart0_recv_buff[usart0_recv_len] = value;
usart0_recv_len++;
// 接收到单个byte,可以在此处处理接收到value。TODO:
}
if (usart_interrupt_flag_get(USART0, USART_INT_FLAG_IDLE) == SET) {
//读取缓冲区,清空缓冲区
usart_data_receive(USART0);
usart0_recv_buff[usart0_recv_len] = '\0';
// 接收停止时,缓冲区中的数据,可以在此处处理缓存数据。TODO:
#if USART0_RECV_CALLBACK
Usart0_recv(usart0_recv_buff, usart0_recv_len);
#endif
usart0_recv_len = 0;
}
}
int fputc(int ch, FILE *f){
Usart0_send_data((uint8_t)ch);
return ch;
}