05-FreeRTOS的移植与适配

获取FreeRTOS源码

在stm32裸机工程中移植FreeRTOS

Source文件

该文件夹下包含RTOS的源码,针对不用芯片架构的底层代码存放在portable目录下,其他部分的代码是通用的; 我们需要关注的是RVDS/ARM_CM3目录,在该目录下有两个文件,分别是port.c和portmacro.h,这两个文件是针对CortexM3架构的MCU设计的。接下来看一下port.c文件中的内容。

c 复制代码
/* 初始化任务栈 */
StackType_t *pxPortInitialiseStack( 
    StackType_t *pxTopOfStack, 
    TaskFunction_t pxCode, 
    void *pvParameters );
/* 调度器函数 */
BaseType_t xPortStartScheduler( void );
/* SVC 系统调用中断处理函数 */
__asm void vPortSVCHandler( void ) ;
/* 临界区管理 */
void vPortEnterCritical( void );
void vPortExitCritical( void );
/* pendSV中断函数处理函数实现上下文切换 */
__asm void xPortPendSVHandler( void )
/* 嘀嗒定时器中断处理函数,实现基础时钟 */ 
void xPortSysTickHandler( void );

还有一个需要关注的内容是内存管理相关的文件,在MemMang下有五种内存管理方式的源码,经常用的是heap_4.c;

移植过程

源码拷贝
  • 在stm32的工程中新建freeRTOS目录,在该目录下新建下面的三个文件夹,将freeRTOS源码中Source目录下的.c文件拷贝到src目录,include文件拷贝include目录,port下存放port.c heap.c等文件;

  • 将FreeRTOS\Demo\CORTEX_STM32F103_Keil文件下的FreeRTOSConfig.h拷贝至工程的user目录下;

Keil软件设置
  • 在工程中创建目录,并添加文件,如下图所示;

  • 头文件路径设置

到此结束,我们就将freeRTOS的源码在我们的stm32工程中完成了移植,下面是RTOS的剪切。

RTOS剪切

配置文件分析

c 复制代码
#ifndef FREERTOS_CONFIG_H 
#define FREERTOS_CONFIG_H 


/* 针对不用的编译器调用不用的stdint.h文件 */ 
#if defined(__ICCARM__) || defined(__CC_ARM) || defined(__GNUC__)
#include <stdint.h> 
extern uint32_t SystemCoreClock;     
#endif 
 
/* 断言 */
#define vAssertCalled(char,int) printf("Error:%s,%d\r\n",char,int) 
#define configASSERT(x) if((x)==0) vAssertCalled(__FILE__,__LINE__)
	
/* 调度器方式配置		0:协作式	1:抢占式 */
#define configUSE_PREEMPTION    1 
/* 使能时间片调度		1:使能	0:关闭 */
#define configUSE_TIME_SLICING    1
/* 下一个任务的选择方法		0:通用方法	1:特殊方法(计算前导0) */
#define configUSE_PORT_OPTIMISED_TASK_SELECTION         1
/* 1:使能低功耗模式	0:系统节拍中断一直运行 */
#define configUSE_TICKLESS_IDLE   0
/* CPU时钟配置 */
#define configCPU_CLOCK_HZ    (SystemCoreClock)
/* RTOS系统节拍的频率配置 */
#define configTICK_RATE_HZ    (( TickType_t )1000)
/* 最大优先级配置 */
#define configMAX_PRIORITIES    (32) 
/* 最小栈空间配置 */
#define configMINIMAL_STACK_SIZE  ((unsigned short)128) 
/* 任务名字长度配置 */
#define configMAX_TASK_NAME_LE   (16)  
/* 系统节拍计数变量的数据类型	1:16位		0:32位 */
#define configUSE_16_BIT_TICKS   0 
/* 空闲任务配置		1:放弃CPU使用权给其他同级别 	0:不放弃 */
#define configIDLE_SHOULD_YIELD  1
/* 队列设置	1:启用队列	0:关闭队列 */
#define configUSE_QUEUE_SETS   0 
/* 任务通知配置 	1:开启		0:关闭 */
#define configUSE_TASK_NOTIFICATIONS     1 
/* 互斥信号量配置		1:开启	0:关闭 */ 
#define configUSE_MUTEXES   0 
/* 递归互斥信号量配置 	1:开启	0:关闭 */
#define configUSE_RECURSIVE_MUTEXES  0 
/* 计数信号量配置 	1:开启	0:关闭 */
#define configUSE_COUNTING_SEMAPHORES  0 
/* 信号量和消息队列个数配置 */  
#define configQUEUE_REGISTRY_SIZE  10  
#define configUSE_APPLICATION_TASK_TAG  0 
/* 是否支持动态内存申请 1:支持 	0:不支持*/ 
#define configSUPPORT_DYNAMIC_ALLOCATION        1 
/* 是否支持静态内存申请 1:支持 	0:不支持*/ 
#define configSUPPORT_STATIC_ALLOCATION  0 
/* 堆大小配置 */
#define configTOTAL_HEAP_SIZE  ((size_t)(36*1024))
/* 空闲钩子函数配置		1:启动		0:关闭	*/
#define configUSE_IDLE_HOOK   0
/* 时间片钩子函数配置		1:启动		0:关闭 */
#define configUSE_TICK_HOOK   0
/* 内存申请失败钩子函数配置		1:启动		0:关闭		*/
#define configUSE_MALLOC_FAILED_HOOK  0 
/* 堆栈溢出检测配置		0:关闭	1:检测方法1		2:检测方法2	*/
#define configCHECK_FOR_STACK_OVERFLOW  0
/* 运行时间统计功能配置		1:启动		0:关闭	*/
#define configGENERATE_RUN_TIME_STATS         0  
/* 可视化跟踪调试配置			1:启用		0:关闭	*/
#define configUSE_TRACE_FACILITY  0 

#define configUSE_STATS_FORMATTING_FUNCTIONS 1 
/* 启动协程配置			1:启动		0:关闭	*/
#define configUSE_CO_ROUTINES   0 
/* 协程有效优先级数目 */
#define configMAX_CO_ROUTINE_PRIORITIES       (2) 
/* 软件定时器配置		1:开启		0:关闭	*/
#define configUSE_TIMERS   1
/* 软件定时器优先级	*/
#define configTIMER_TASK_PRIORITY (configMAX_PRIORITIES-1) 
/* 软件定期器队列长度	*/
#define configTIMER_QUEUE_LENGTH  10
/* 软件定时器任务堆大小	**/
#define configTIMER_TASK_STACK_DEPTH (configMINIMAL_STACK_SIZE*2)
#define INCLUDE_xTaskGetSchedulerState		1      
#define INCLUDE_vTaskPrioritySet   1
#define INCLUDE_uxTaskPriorityGet   1        
#define INCLUDE_vTaskDelete    1
#define INCLUDE_vTaskCleanUpResources 	1         
#define INCLUDE_vTaskSuspend            1
#define INCLUDE_vTaskDelayUntil   1
#define INCLUDE_vTaskDelay    1
#define INCLUDE_eTaskGetState  1         
#define INCLUDE_xTimerPendFunctionCall	1        
#ifdef __NVIC_PRIO_BITS 
#define configPRIO_BITS     __NVIC_PRIO_BITS     
#else 
#define configPRIO_BITS     4   
#endif
/* 中断最低优先级 */
#define configLIBRARY_LOWEST_INTERRUPT_PRIORITY 	15 
/* 系统可以管理的最高优先级 */
#define configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY 	5
#define configKERNEL_INTERRUPT_PRIORITY    ( configLIBRARY_LOWEST_INTERRUPT_PRIORITY << (8 - configPRIO_BITS) ) 
#define configMAX_SYSCALL_INTERRUPT_PRIORITY   ( configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY << (8 - configPRIO_BITS) )
/* 中断服务函数配置 */
#define xPortPendSVHandler  PendSV_Handler   
#define vPortSVCHandler  SVC_Handler   
#if ( configUSE_TRACE_FACILITY == 1 )   
#include "trcRecorder.h"  
#define INCLUDE_xTaskGetCurrentTaskHandle       0          
#endif 

#endif

工程修改

中断处理函数

  • 修改PendSV_Handler中断处理函数,屏蔽默认的函数,在FreeRTOS的port.c文件中重新定义了该中断服务函数以次来实现上下文的切换;
  • 修改PendSV_Handler中断处理函数,屏蔽此函数,在FreeRTOS的port.c文件中重新定义了该中断服务函数来实现任务的启动;
  • 修改SysTick_Handler中断处理函数,在函数中调用xPortSysTickHandler接口,该接口是留给各个芯片时钟节拍的函数;

添加头文件

添加必要的头文件FreeRTOS.h;

在其他处理器下移植FreeRTOS

在一个新的处理器中如果要移植freeRTOS,主要操作的文件则是porttable下面的port.c文件。主要涉及三大块,分别是栈,中断和定时器。除此之外,还有临界区的管理,比如开关中断,系统调用方法等。

栈是用来保存任务中的数据变量的,包括CPU相关寄存器的,每一种CPU中要保存的寄存器数量或者种类可能不同,所以在移植的过程中这一块是需要关注的,要想清楚栈中存储什么内容。

中断

每一种CPU的中断管理可能是不同的,中断在这里的作用有两个,一个是进行任务的切换 ,通常由系统调用实现,另外一个是基础时钟中断,也就是定时器中断,来保障RTOS的运行,在移植的过程中,中断管理是重要的部分。有的处理器中断和例外的入口是同一个,在进入中断后,还需要对中断进行分发。

定时器

定时器是很重要的一部分,要在CPU的资源中找到一个定时器,配置定时器的中断和中断时间。一般来讲,定时器中断时间为1ms左右。

定时器中断中断中一般的处理过程如下:

  • 清除定时器中断的标志位;

  • 调用xTaskIncrementTick函数;

    • xTickCount数值加1,在这个函数中还有一个重要的任务,就是判断当前状态是否需要调度,通过xSwitchRequired变量返回。

      判断的逻辑为:

    • 下一个任务的阻塞时间已到,判断延时链表是否为空,如果不为空,则获取延时链表的头结点,根据头结点获取延时的时间,如果延时的时间大于xConstTickCount时间,则代表延时还没有到,更新xNextTaskUnblockTime时间。如果时间到了,则从延时链表移除,也从事件链表移除,再将其加入到就绪链表中。如果这个任务的优先级大于当前任务的优先级,则直接xSwitchRequired等于true,

    • 时间片轮询:当前优先级任务就绪链表中如果长度大于1,则xSwitchRequired=true,进行时间片轮询调度。

    • 总结:该函数的功能就是查找延时链表中的时间是否到达,如果到达,则从延时链表中删除,加入到就绪链表中,如果这个任务的优先级大于当前任务的优先级,则直接返回需要调度的标志。如果当前就绪链表中任务是多个,也是需要返回调度的标志,进行时间片调度。

    • 抢占式时间片调度算法:就绪态的高优先级先执行,同优先级的就绪任务轮流执行;

  • 判断xTaskIncrementTick的返回数值决定是否需要上下文切换;

    • 上下文切换函数为vTaskSwitchContext,上下文切换实质上是查找优先级最高的就绪任务,将该任务TCB写入到pxCurrentTCB
  • 上下文切换后,将pxCurrentTCB栈内容恢复到CPU寄存器中;

总结

本文主要概述了FreeRTOS的移植和剪切过程,其实在FreeRTOS中已经帮助我们适配了大部分处理器,我们要做的就是拿来会用就行,但是针对新的CPU和架构,就需要自己移植了,理论上讲无论CPU是大的SOC还是小的MCU,只要处理器中有一个定时器,有系统调用中断,基本上都可以运行freeRTOS。

相关推荐
学嵌入式的小杨同学2 小时前
STM32 进阶封神之路(二十二):DMA 实战全攻略 ——ADC 采集 + 串口收发 + 内存复制(库函数 + 代码落地)
c++·stm32·单片机·嵌入式硬件·mcu·硬件架构·pcb
-Springer-2 小时前
STM32 学习 —— 个人学习笔记10-1(I2C 通信协议及 MPU6050 简介 & 软件 I2C 读写 MPU6050)
笔记·stm32·学习
LCG元3 小时前
STM32实战:基于LVGL的嵌入式GUI界面开发(智能手表UI)
stm32·智能手表
DLGXY4 小时前
STM32(二十八)——FLASH闪存
stm32·单片机·嵌入式硬件
学嵌入式的小杨同学4 小时前
STM32 进阶封神之路(二十一):DMA 深度解析 —— 从直接内存访问到无 CPU 干预数据传输(底层原理 + 寄存器配置)
stm32·单片机·嵌入式硬件·mcu·硬件架构·硬件工程·智能硬件
BackCatK Chen4 小时前
STM32U3B5/3C5深度解析:HSP加速器赋能边缘AI与DSP,超低功耗新标杆
人工智能·stm32·嵌入式硬件
_Ningye13 小时前
STM32 — 2.2 新建工程
stm32·单片机·嵌入式硬件
_Ningye14 小时前
STM32 — 3.1 GPIO输出
stm32·单片机·嵌入式硬件
学嵌入式的小杨同学14 小时前
STM32 进阶封神之路(十九):ADC 深度解析 —— 从模拟信号到数字转换(底层原理 + 寄存器配置)
c++·stm32·单片机·嵌入式硬件·mcu·架构·硬件架构