文章目录
FreeRTOS移植到STM32F103ZET6上的详细步骤
1. 移植前的准备工作
-
**基础工程:**内存管理部分的例程源码和基本定时器的例程源码
使用实验8 基本定时器和实验36 内存管理两个实验,内存管理实验做为移植的目标文件,为了获得定时器的功能,需要把基本定时器实验部分的
TIMER
文件夹拷贝到内存管理实验的BSP
文件夹里面。 -
FreeRTOS源码:
本例程使用的是FreeRTOS内核源码的版本V10.4.6,即FreeRTOS v2021.00。获取路径:软件资料→FreeRTOS学习资料→FreeRTOSv2202112.00.zip。
2. 添加FreeRTOS文件
-
添加FreeRTOS源码,在基础工程的Middlewares文件夹中新建一个FreeRTOS子文件夹:
将FreeRTOS内核源码的 Source 文件夹下的所有文件添加到工程的 FreeRTOS 文件夹中:
-
将文件添加到工程
打开基础工程,新建两个文件分组,分别为
Middlewares/FreeRTOS_CORE
和Middlewares/FreeRTOS_PORT
Middlewares/FreeRTOS_CORE
:存放FreeRTOS内核C源码文件Middlewares/FreeRTOS_PORT
:存放FreeRTOS内核的移植文件,分别添加heap_x.c
和port.c
然后把相关文件拷贝到这两个路径下:
-
添加头文件路径
一个是FreeRTOS/include,另一个是port.c的路径
-
添加FreeRTOSConfig.h文件
因为需要对配置文件进行裁剪并且要结合STM32单片机,我们直接从移植好的例程里面拷贝即可
-
3. 修改SYSTEM文件
-
sys.h
文件sys.h文件的修改很简单,在sys.h文件中使用了宏SYS SUPPORT_OS来定义是否支持OS,因为要支持FreeRTOS,因此应当将宏SYS SUPPORT_OS定义为1,具体修改如下所示:
修改前:
/** * SYS_SUPPORT_OS用于定义系统文件夹是否支持OS * 0,不支持OS * 1,支持OS */ #define SYS_SUPPORT_OS 0
修改后:
/** * SYS_SUPPORT_OS用于定义系统文件夹是否支持OS * 0,不支持OS * 1,支持OS */ #define SYS_SUPPORT_OS 1
-
usart.c
文件usart.c文件的修改也很简单,一共有两个地方需要修改,首先就是串口的中断服务函数,原本在使用uCOS的时候,进入和退出中断需要添加OSIntEnter()和OSIntExit()两个函数,这是uCOS对于中断的相关处理机制,而FreeRTOS中并没有这种机制,因此将这两行代码删除,修改后串口的中断服务函数如下所示:
修改前:
void USART_UX_IRQHandler(void) { #if SYS_SUPPORT_OS /* 使用OS */ OSIntEnter(); #endif HAL_UART_IRQHandler(&g_uart1_handle); /* 调用HAL库中断处理公用函数 */ #if SYS_SUPPORT_OS /* 使用OS */ OSIntExit(); #endif }
修改后:
void USART_UX_IRQHandler(void) { HAL_UART_IRQHandler(&g_uart1_handle); /* 调用HAL库中断处理公用函数 */ while (HAL_UART_Receive_IT( &g_uart1_handle,(uint8_t *)g_rx_buffer,RXBUFFERSIZE) != HAL_OK)/* 重新开启中断并接收数据 */ { /* 如果出错会卡死在这里 */ } }
接下来usart.c要修改的第二个地方就是导入的头文件,因为在串口的中断服务函数当中已经删除了uCOS的相关代码,并且也没有使用到FreeRTOS的相关代码,因此将usart.c中包含的关于OS的头文件删除,要删除的代码如下所示:
/* 如果使用 os,则包括下面的头文件即可. */ #if SYS_SUPPORT_OS #include "includes.h" /* os 使用 */ #endif
-
delay.c
文件(1)删除适用于 µC/OS 但不适用于 FreeRTOS 的相关代码:
/* 定义 g_fac_ms 变量, 表示 ms 延时的倍乘数, * 代表每个节拍的 ms 数, (仅在使能 os 的时候,需要用到) */ static uint16_t g_fac_ms = 0; /* * 当 delay_us/delay_ms 需要支持 OS 的时候需要三个与 OS 相关的宏定义和函数来支持 * 首先是 3 个宏定义: * delay_osrunning :用于表示 OS 当前是否正在运行,以决定是否可以使用相关函数 * delay_ostickspersec :用于表示 OS 设定的时钟节拍, * delay_init 将根据这个参数来初始化 systick * delay_osintnesting :用于表示 OS 中断嵌套级别,因为中断里面不可以调度, * delay_ms 使用该参数来决定如何运行 * 然后是 3 个函数: * delay_osschedlock :用于锁定 OS 任务调度,禁止调度 * delay_osschedunlock :用于解锁 OS 任务调度,重新开启调度 * delay_ostimedly :用于 OS 延时,可以引起任务调度. * * 本例程仅作 UCOSII 和 UCOSIII 的支持,其他 OS,请自行参考着移植 */ /* 支持 UCOSII */ #ifdef OS_CRITICAL_METHOD /* OS_CRITICAL_METHOD 定义了 * 说明要支持 UCOSII */ #define delay_osrunning OSRunning /* OS 是否运行标记,0,不运行;1,在运行 */ #define delay_ostickspersec OS_TICKS_PER_SEC /* OS 时钟节拍,即每秒调度次数 */ #define delay_osintnesting OSIntNesting /* 中断嵌套级别,即中断嵌套次数 */ #endif /* 支持 UCOSIII */ #ifdef CPU_CFG_CRITICAL_METHOD /* CPU_CFG_CRITICAL_METHOD 定义了 * 说明要支持 UCOSIII */ #define delay_osrunning OSRunning /* OS 是否运行标记,0,不运行;1,在运行 */ #define delay_ostickspersec OSCfg_TickRate_Hz /* OS 时钟节拍,即每秒调度次数 */ #define delay_osintnesting OSIntNestingCtr /* 中断嵌套级别,即中断嵌套次数 */ #endif /** * @brief us 级延时时,关闭任务调度(防止打断 us 级延迟) * @param 无 * @retval 无 */ static void delay_osschedlock(void) { #ifdef CPU_CFG_CRITICAL_METHOD /* 使用 UCOSIII */ OS_ERR err; OSSchedLock(&err); /* UCOSIII 的方式,禁止调度,防止打断 us 延时 */ #else /* 否则 UCOSII */ OSSchedLock(); /* UCOSII 的方式,禁止调度,防止打断 us 延时 */ #endif } /** * @brief us 级延时时,恢复任务调度 * @param 无 * @retval 无 */ static void delay_osschedunlock(void) { #ifdef CPU_CFG_CRITICAL_METHOD /* 使用 UCOSIII */ OS_ERR err; OSSchedUnlock(&err); /* UCOSIII 的方式,恢复调度 */ #else /* 否则 UCOSII */ OSSchedUnlock(); /* UCOSII 的方式,恢复调度 */ #endif } /** * @brief us 级延时时,恢复任务调度 * @param ticks: 延时的节拍数 * @retval 无 */ static void delay_ostimedly(uint32_t ticks) { #ifdef CPU_CFG_CRITICAL_METHOD OS_ERR err; OSTimeDly(ticks, OS_OPT_TIME_PERIODIC, &err); /* UCOSIII 延时采用周期模式 */ #else OSTimeDly(ticks); /* UCOSII 延时 */ #endif }
(2)添加 FreeRTOS 的相关代码:
只需要在delay.c文件中使用 extern关键字导入一个FreeRTOS函数一 xPortSysTickHandler()即可,这个函数是用于处理FreeRTOS系统时钟节拍的,本教程是使用 SysTick作为FreeRTOS操作系统的心跳,因此需要在SysTick的中断服务函数中调用这个函数,因此将代码添加到SysTick中断服务函数之前,代码修改如下:
extern void xPortSysTickHandler(void);
(3)修改部分内容
最后要修改的内容包括两个,分别是包含头文件和 4 个函数。
包含头文件:
//修改前: #include "includes.h" //修改后: #include "FreeRTOS.h" #include "task.h"
4个函数:
(1) SysTick_Handler()
修改前:
void SysTick_Handler(void) { /* OS 开始跑了,才执行正常的调度处理 */ if (delay_osrunning == OS_TRUE) { /* 调用 uC/OS-II 的 SysTick 中断服务函数 */ OS_CPU_SysTickHandler(); } HAL_IncTick(); }
修改后:
void SysTick_Handler(void) { HAL_IncTick(); if(xTaskGetSchedulerState() != taskSCHEDULER_NOT_STARTED) { xPortSysTickHandler(); } }
(2) delay_init()
修改前:
void delay_init(uint16_t sysclk) { #if SYS_SUPPORT_OS /* 如果需要支持OS */ uint32_t reload; #endif g_fac_us = sysclk; /* 由于在HAL_Init中已对systick做了配置,所以这里无需重新配置 */ #if SYS_SUPPORT_OS /* 如果需要支持OS. */ reload = sysclk; /* 每秒钟的计数次数 单位为M */ reload *= 1000000 / delay_ostickspersec; /* 根据delay_ostickspersec设定溢出时间,reload为24位 * 寄存器,最大值:16777216,在168M下,约合0.09986s左右 */ g_fac_ms = 1000 / delay_ostickspersec; /* 代表OS可以延时的最少单位 */ SysTick->CTRL |= 1 << 1; /* 开启SYSTICK中断 */ SysTick->LOAD = reload; /* 每1/delay_ostickspersec秒中断一次 */ SysTick->CTRL |= 1 << 0; /* 开启SYSTICK */ #endif }
修改后:
void delay_init(uint16_t sysclk) { #if SYS_SUPPORT_OS /* 如果需要支持OS */ uint32_t reload; #endif SysTick->CTRL = 0; HAL_SYSTICK_CLKSourceConfig(SYSTICK_CLKSOURCE_HCLK_DIV8); g_fac_us = sysclk / 8; /* 由于在HAL_Init中已对systick做了配置,所以这里无需重新配置 */ #if SYS_SUPPORT_OS /* 如果需要支持OS. */ reload = sysclk / 8; /* 每秒钟的计数次数 单位为M */ reload *= 1000000 / configTICK_RATE_HZ; /* 根据delay_ostickspersec设定溢出时间,reload为24位 * 寄存器,最大值:16777216,在168M下,约合0.09986s左右 */ /* 代表OS可以延时的最少单位 */ SysTick->CTRL |= 1 << 1; /* 开启SYSTICK中断 */ SysTick->LOAD = reload; /* 每1/delay_ostickspersec秒中断一次 */ SysTick->CTRL |= 1 << 0; /* 开启SYSTICK */ #endif }
(3) delay_us()
修改前:
void delay_us(uint32_t nus) { uint32_t ticks; uint32_t told, tnow, tcnt = 0; uint32_t reload = SysTick->LOAD; /* LOAD的值 */ ticks = nus * g_fac_us; /* 需要的节拍数 */ #if SYS_SUPPORT_OS /* 如果需要支持OS */ delay_osschedlock(); /* 锁定 OS 的任务调度器 */ #endif 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; /* 时间超过/等于要延迟的时间,则退出 */ } } } #if SYS_SUPPORT_OS /* 如果需要支持OS */ delay_osschedunlock(); /* 恢复 OS 的任务调度器 */ #endif }
修改后:
void delay_us(uint32_t nus) { uint32_t ticks; uint32_t told, tnow, tcnt = 0; uint32_t reload = SysTick->LOAD; /* LOAD的值 */ ticks = nus * g_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; /* 时间超过/等于要延迟的时间,则退出 */ } } } }
(4) delay_ms()
修改前:
void delay_ms(uint16_t nms) { #if SYS_SUPPORT_OS /* 如果需要支持OS, 则根据情况调用os延时以释放CPU */ if (delay_osrunning && delay_osintnesting == 0) /* 如果OS已经在跑了,并且不是在中断里面(中断里面不能任务调度) */ { if (nms >= g_fac_ms) /* 延时的时间大于OS的最少时间周期 */ { delay_ostimedly(nms / g_fac_ms); /* OS延时 */ } nms %= g_fac_ms; /* OS已经无法提供这么小的延时了,采用普通方式延时 */ } #endif delay_us((uint32_t)(nms * 1000)); /* 普通方式延时 */ }
修改后:
void delay_ms(uint16_t nms) { uint32_t i; for(i=0; i<nms; i++) { delay_us(1000); } }
4. 修改中断相关文件
FreeRTOS系统时基定时器的中断(SysTick中断)、SVC中断、PendSV中断。
SysTick的中断服务函数在delay.c文件中已经定义了,并且FreeRTOS也提供了SVC和PendSV的中断服务函数,因此需要将HAL库提供的这三个中断服务函数注释掉,这里采用宏开关的方式让HAL库中的这三个中断服务函数不加入编译,使用的宏在sys.h中定义,因此还需要导入Sysh头文件,请读者按照表2.1.4.1找到对应的文件进行修改,修改后的代码如下所示:
/* 导入 sys.h 头文件 */
#include "./SYSTEM/SYS/sys.h"
/* 加入宏开关 */
#if (!SYS_SUPPORT_OS)
void SVC_Handler(void)
{
}
#endif
#if (!SYS_SUPPORT_OS)
void PendSV_Handler(void)
{
}
#endif
#if (!SYS_SUPPORT_OS)
void SysTick_Handler(void)
{
HAL_IncTick();
}
#endif
5. 修改FreeRTOSConfig.h文件
在FreeRTOSConfig.h
中
#define configPRIO_BITS __NVIC_PRIO_BITS
在stm32f103xe.h
文件中修改:
#define __NVIC_PRIO_BITS 4U
为:
#define __NVIC_PRIO_BITS 4
6. 可选步骤
-
修改工程目标名称:
本教程是以标准例程-HAL库版本的内存管理实验工程为基础工程,内存管理实验工程的工程日标名为“MALLOC”,为了规范工程,笔者建议将工程目标名修改为“FreeRTOS”或根据读者的实际场景进行修改,修改如下图所示:
-
移出USMART调试组件:
由于本教程并未使用到USMART调试组件,因此建议将USMART调试组件从工程中移除,如果读者需要使用USMART调试组件的话,也可选择保留,移除USAMRT调试组建后工程文件分组如下图所示(这里以正点原子的STM32F1系列开发板为例,其他开发板类似),修改后文件组为:
-
添加定时器驱动:
由于在后续的实验中需要使用到$TM32的基本定时器外设,因此需要向工程中添加定时器的相关驱动文件,读者也可在后续实验需要用到定时器的时候再进行添加。将定时器的相关驱动文件添加到工程的Drivers/BSP文件分组中,如下图所示(这里以正点原子的STM32F1系列开发板为例,其他开发板类似):
)
-
添加应用程序:
添加
freertos_demo.c
和freertos_demo.h
测试文件,对于 main.c 主要是在main()
函数中完成一些硬件的初始化,最后调用freertos_demo.c
文件中的freertos_demo()
函数。main.c
#include "./SYSTEM/sys/sys.h" #include "./SYSTEM/usart/usart.h" #include "./SYSTEM/delay/delay.h" #include "./BSP/LED/led.h" #include "./BSP/LCD/lcd.h" #include "./BSP/KEY/key.h" #include "./BSP/SRAM/sram.h" #include "./MALLOC/malloc.h" #include "freertos_demo.h" const char *SRAM_NAME_BUF[SRAMBANK] = {" SRAMIN ", " SRAMEX "}; int main(void) { HAL_Init(); /* 初始化HAL库 */ sys_stm32_clock_init(RCC_PLL_MUL9); /* 设置时钟, 72Mhz */ delay_init(72); /* 延时初始化 */ usart_init(115200); /* 串口初始化为115200 */ led_init(); /* 初始化LED */ lcd_init(); /* 初始化LCD */ key_init(); /* 初始化按键 */ sram_init(); /* SRAM初始化 */ my_mem_init(SRAMIN); /* 初始化内部SRAM内存池 */ my_mem_init(SRAMEX); /* 初始化外部SRAM内存池 */ lcd_show_string(30, 50, 200, 16, 16, "YIZHI successed", RED); freertos_demo(); }
freertos_demo.c
#include "freertos_demo.h" #include "./SYSTEM/usart/usart.h" #include "./BSP/LED/led.h" #include "./BSP/LCD/lcd.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 TASK1_PRIO 2 // 任务优先级 #define TASK1_STK_SIZE 128 // 任务堆栈大小 TaskHandle_t Task1Task_Handler; // 任务句柄 void task1(void *pvParameters); // 任务函数 #define TASK2_PRIO 3 // 任务优先级 #define TASK2_STK_SIZE 128 // 任务堆栈大小 TaskHandle_t Task2Task_Handler; // 任务句柄 void task2(void *pvParameters); // 任务函数 /*创建开始任务*/ 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(); } /*创建两任务1和任务2*/ 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(); // 退出临界区 } /*任务一: LED0闪烁*/ void task1(void *pvParameters) { while(1) { LED0_TOGGLE(); vTaskDelay(1000); //延时1000ticks } } /*任务二: LED1闪烁*/ void task2(void *pvParameters) { while(1) { LED1_TOGGLE(); vTaskDelay(500); //延时500ticks } }
freertos_demo.h
#ifndef __FREERTOS_DEMO_H #define __FREERTOS_DEMO_H void freertos_demo(void); #endif