【FreeRTOS学习】(1)在项目中添加FreeRTOS

【FreeRTOS学习】(1)在项目中添加FreeRTOS

  • 一、FreeRTOS简介
  • 二、在项目中添加FreeRTOS
    • 1. 下载FreeRTOS源码
    • 2. 添加需要的文件
    • 3. 添加 FreeRTOSConfig.h 配置文件
    • 4. 编译验证

一、FreeRTOS简介

FreeRTOS(Free Real-Time Operating System)是一个常用的实时操作系统内核,具有高可用性、可裁剪性、可移植性和可扩展性等特性。它是一个基于事件驱动的,用于嵌入式系统的实时操作系统,广泛应用于航空、航天、工业自动化、医疗设备、智能家居、汽车电子等领域。FreeRTOS提供了任务管理、时间管理、内存管理、中断处理、软件定时器和通信机制等功能。其内核非常小巧,仅需几KB的内存和一些最基本的处理器资源即可运行。同时FreeRTOS还提供多种语言接口,并且支持多种处理器架构,如ARM、AVR、PIC、MIPS等,因此备受广大嵌入式软件开发人员的欢迎。

二、在项目中添加FreeRTOS

FreeRTOS提供的对开发者的支持还是很多的,官网 也有中文版的开发文档,可以查阅API函数、内核机制,实在有问题解决不了还有一个官方的论坛,可以注册一个账号在上面提问。

1. 下载FreeRTOS源码

官网有最新版和最新长期支持版(LTS),如果是为了学习建议直接下最新版,源码里提供一些demo项目可以参考。

  1. 官方下载页面:
    【FreeRTOS学习】(1)在项目中添加FreeRTOS_第1张图片
  2. Github 上获取源码
    FreeRTOS-Plus是一些其他协议(如物联网相关)的扩展,最基础的实时操作系统内核只需要FreeRTOS就可以了。
    【FreeRTOS学习】(1)在项目中添加FreeRTOS_第2张图片
    可以自行下载压缩包或 git clone 。

2. 添加需要的文件

  1. 文件构成
    下载好后解压后的文件构成是这样的:
    【FreeRTOS学习】(1)在项目中添加FreeRTOS_第3张图片
    其实也很好分辨,目录下的 .c 文件就是通用的源文件;include文件夹内是头文件;portable文件夹内是一些跟不同编译器、处理器架构相关和动态内存分配的源文件和头文件。
  2. 在项目添加所需要的文件
    这里假设已经会如何新建一个对应处理器的(Keil / IAR / GCC)项目。
    那么要做的其实就是(1)添加需要的代码文件并添加到项目中(2)在项目include项中添加所需头文件的路径。
    比如以我使用的 IAR 作为 IDE 开发环境,首先在项目中新建一个FreeRTOS文件夹,再在其中创建src、inc、port三个文件夹:
    【FreeRTOS学习】(1)在项目中添加FreeRTOS_第4张图片
    demo和license无关紧要,只是一些对应处理器的demo项目和许可证相关。
    【FreeRTOS学习】(1)在项目中添加FreeRTOS_第5张图片
    将FreeRTOS内核下的所有.c文件复制到 项目路径/FreeRTOS/src 下。
    【FreeRTOS学习】(1)在项目中添加FreeRTOS_第6张图片
    将FreeRTOS内核下/include/ 的所有.h文件复制到 项目路径/FreeRTOS/src 下。
    【FreeRTOS学习】(1)在项目中添加FreeRTOS_第7张图片
    将portable/MemMang文件夹整个复制到 项目路径/FreeRTOS/port 下,它里面是几个关于(即动态分配内存)的.c文件,使用过程按需选择一个添加到项目中就可以,一般选择heap_4,具体区别建议点击里面的ReadMe网页查看;
    选取portable中对应编译器和处理器架构,比如我的是 ./portable/IAR/ARM_CM4F 其中有一个汇编文件一个.c,一个.h,将这些文件复制到 项目路径/FreeRTOS/port/IAR/ARM_CM4F 下。
    【FreeRTOS学习】(1)在项目中添加FreeRTOS_第8张图片
    【FreeRTOS学习】(1)在项目中添加FreeRTOS_第9张图片
    【FreeRTOS学习】(1)在项目中添加FreeRTOS_第10张图片
    完成后,在IDE项目中添加源文件和在选项(Options)添加对应头文件路径:
    注意:MemMang 中只用选择其中 一个!!(一般是4)
    【FreeRTOS学习】(1)在项目中添加FreeRTOS_第11张图片
    需要注意的是刚刚那个portasm.s由于它里面有一句 #include ,所有汇编文件的pre include 路径也要添加相关的(起码FreeRTOS相关的都要添加)。就是Options的C/C++ Compiler和Assembler选项都要添加。
    【FreeRTOS学习】(1)在项目中添加FreeRTOS_第12张图片

3. 添加 FreeRTOSConfig.h 配置文件

完成上面的步骤后,可以尝试编译一下,会报一个找不到 FreeRTOSConfig.h 的错误,因为 FreeRTOSConfig.h 这个文件根据官方来说是需要用户自己创建的,算作用户文件,不算内核,它是一个配置文件,define一些宏来选择相应的功能。
那我们就创建一个 FreeRTOSConfig.h 到 user group 下,这里给一个有注释的模板,以后再有需要可以用这个改,不过如果你是Keil/GCC需要修改一两行。

#ifndef FREERTOS_CONFIG_H
#define FREERTOS_CONFIG_H

/*-----------------------------------------------------------
 * Application specific definitions.
 * 可以在下面这个网站查看对应选项的官方解释,此文件的注释只做参考
 * See http://www.freertos.org/a00110.html
 *----------------------------------------------------------*/
//#include "gd32f30x.h"
/* 这个是踩过的坑,因为汇编也要用到FreeRTOSConfig.h文件,所以有些关键字
 * 如 enum 就会报 bad instruction 的错误,所以最好还是直接把如 
 * SystemCoreClock 这样的宏的值直接粘贴过来用,像我下面那样,就不要再包含
 *  gd32f30x.h 了。*/
/* Ensure stdint is only used by the compiler, and not the assembler. */
//这个地方__ICCARM__表示是IAR的编译器,若是Keil或GCC要改成__CC_ARM、__GNUC__
#if defined __ICCARM__
	#include 
	extern uint32_t SystemCoreClock;
#endif

//assert断言
/* Normal assert() semantics without relying on the provision of an assert.h
header file. */
#define configASSERT( x ) if( ( x ) == 0 ) { taskDISABLE_INTERRUPTS(); for( ;; ); }	

/*****************************************************************************
 *                                基础配置项
 ****************************************************************************/
//调度器:抢占式 1 协作式 0
#define configUSE_PREEMPTION			1

//1 使能时间片调度(默认是使能的)
#define configUSE_TIME_SLICING          1

//是否使用硬件计算前导0的方式选择下一个可执行任务
#define configUSE_PORT_OPTIMISED_TASK_SELECTION 1

/* 置1:使能低功耗tickless 模式;置0:保持系统节拍(tick)中断一直运行 */
#define configUSE_TICKLESS_IDLE         0

//写入实际CPU内核时钟频率
#define configCPU_CLOCK_HZ				((uint32_t)(120000000))//( SystemCoreClock )

//1s中断的次数
#define configTICK_RATE_HZ				( ( TickType_t ) 1000 )

//可用的最大优先级
#define configMAX_PRIORITIES			( 32 )

//空闲任务堆栈大小
#define configMINIMAL_STACK_SIZE		( ( unsigned short ) 128 )

//任务名称最大长度
#define configMAX_TASK_NAME_LEN			( 16 )

//系统节拍计数器变量的数据类型,1 16位无符号整数,0 32位无符号整数
#define configUSE_16_BIT_TICKS			0

//空闲任务放弃CPU使用权给其他同级任务(建议所有其他任务的优先级高于空闲任务)
#define configIDLE_SHOULD_YIELD			1

//启用队列
#define configUSE_QUEUE_SETS            1

//开启任务通知功能,默认开启
#define configUSE_TASK_NOTIFICATIONS    1

//互斥信号量
#define configUSE_MUTEXES               1

//可以注册的信号量的个数
#define configQUEUE_REGISTRY_SIZE       10

//使用递归互斥信号量
#define configUSE_RECURSIVE_MUTEXES     1

//可注册的消息队列的个数
#define configUSE_APPLICATION_TASK_TAG  0

//使用计数信号量
#define configUSE_COUNTING_SEMAPHORES   1

/*****************************************************************************
 *                                内存申请相关
 ****************************************************************************/
//支持动态内存申请
#define configSUPPORT_DYNAMIC_ALLOCATION 1
//支持静态内存
#define configSUPPORT_STATIC_ALLOCATION 0
//总堆栈大小
#define configTOTAL_HEAP_SIZE			( ( size_t ) ( 36 * 1024 ) )

/*****************************************************************************
 *                                钩子函数相关
 ****************************************************************************/
//空闲钩子(空闲任务执行的应用程序函数,void vApplicationIdleHook( void );
#define configUSE_IDLE_HOOK				0
//时间片钩子函数 void vApplicationTickHook( void );
#define configUSE_TICK_HOOK				0
//malloc失败时(返回NULL)调用的钩子函数 void vApplicationMallocFailedHook( void );
#define configUSE_MALLOC_FAILED_HOOK    0
//堆栈溢出检测 大于0时启用,必须由用户提供堆栈溢出钩子函数
#define configCHECK_FOR_STACK_OVERFLOW  0

/*****************************************************************************
 *                         运行时间和任务状态收集相关
 ****************************************************************************/
//启用运行时间统计功能
#define configGENERATE_RUN_TIME_STATS   0
//启用可视化跟踪调试
#define configUSE_TRACE_FACILITY        0
/* 与宏configUSE_TRACE_FACILITY 同时为 1 时会编译下面 3 个函数
 * prvWriteNameToBuffer()
 * vTaskList(),
 * vTaskGetRunTimeStats()
 */
#define configUSE_STATS_FORMATTING_FUNCTIONS 0

/*****************************************************************************
 *                                 协程相关
 ****************************************************************************/
//启用协程,必须添加 croutine.c
#define configUSE_CO_ROUTINES 		0
//协程有效优先级数目
#define configMAX_CO_ROUTINE_PRIORITIES ( 2 )

/*****************************************************************************
 *                               软件定时器相关
 ****************************************************************************/
//启用软件定时
#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 )

/*****************************************************************************
 *                               可选函数配置选项
 ****************************************************************************/
/* Set the following definitions to 1 to include the API function, or zero
to exclude the API function. */
#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_xTaskGetSchedulerState  1
#define INCLUDE_eTaskGetState           1
#define INCLUDE_xTimerPendFunctionCall  1

/*****************************************************************************
 *                             中断相关,主要是优先级
 ****************************************************************************/
/* Cortex-M specific definitions. */
#ifdef __NVIC_PRIO_BITS
	/* __BVIC_PRIO_BITS will be specified when CMSIS is being used. */
	#define configPRIO_BITS       		4//__NVIC_PRIO_BITS
#else
	#define configPRIO_BITS       		4        /* 15 priority levels */
#endif

//中断最低优先级
/* The lowest interrupt priority that can be used in a call to a "set priority"
function. */
#define configLIBRARY_LOWEST_INTERRUPT_PRIORITY			0xf

//系统可管理的最高优先级
/* The highest interrupt priority that can be used by any interrupt service
routine that makes calls to interrupt safe FreeRTOS API functions.  DO NOT CALL
INTERRUPT SAFE FREERTOS API FUNCTIONS FROM ANY INTERRUPT THAT HAS A HIGHER
PRIORITY THAN THIS! (higher priorities are lower numeric values. */
#define configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY	5

/* Interrupt priorities used by the kernel port layer itself.  These are generic
to all Cortex-M ports, and do not rely on any particular library functions. */
#define configKERNEL_INTERRUPT_PRIORITY 		( configLIBRARY_LOWEST_INTERRUPT_PRIORITY << (8 - configPRIO_BITS) )
/* !!!! configMAX_SYSCALL_INTERRUPT_PRIORITY must not be set to zero !!!!
See http://www.FreeRTOS.org/RTOS-Cortex-M3-M4.html. */
#define configMAX_SYSCALL_INTERRUPT_PRIORITY 	( configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY << (8 - configPRIO_BITS) )
	
/*****************************************************************************
 *                               中断服务函数相关
 ****************************************************************************/
/* Definitions that map the FreeRTOS port interrupt handlers to their CMSIS
standard names. */
#define vPortSVCHandler SVC_Handler
#define xPortPendSVHandler PendSV_Handler
//#define xPortSysTickHandler SysTick_Handler//不使用默认的systick中断函数

#endif /* FREERTOS_CONFIG_H */


还差最后一步了, SVC_Handler 和 PendSV_Handler 会有重复的问题,对于gd32的芯片来说,这是因为在 gd32f30x_it.c 已经有这俩函数了(虽然是空的),而 FreeRTOS 需要用到这两个函数,前者是中断系统调用,后者是嵌套中断时用的(或者说选取中断级别高的进入),有兴趣可以去 portasm.s 查看汇编代码,FreeRTOS 都帮我们实现好了。因此这里的直接注释掉就好,然后再在这里实现SysTick_Handler函数,就大功告成了,这个函数是系统时钟中断。

/*!
    \brief      this function handles SVC exception
    \param[in]  none
    \param[out] none
    \retval     none
*/
/*void SVC_Handler(void)
{
}*/

/*!
    \brief      this function handles PendSV exception
    \param[in]  none
    \param[out] none
    \retval     none
*/
/*void PendSV_Handler(void)
{
}*/

/*!
    \brief      this function handles SysTick exception
    \param[in]  none
    \param[out] none
    \retval     none
*/
//其实主要还是调用 FreeRTOS 提供的系统时钟中断函数,只不过加了一个判断调度器是否启动的宏
extern void xPortSysTickHandler(void);
void SysTick_Handler(void)
{
#if (INCLUDE_xTaskGetSchedulerState == 1 )
    if (xTaskGetSchedulerState() != taskSCHEDULER_NOT_STARTED) {
#endif /* INCLUDE_xTaskGetSchedulerState */
        xPortSysTickHandler();
#if (INCLUDE_xTaskGetSchedulerState == 1 )
    }
#endif /* INCLUDE_xTaskGetSchedulerState */
}

有兴趣可以看看源码怎么操作的,不过本文只聊怎么通过编译。

4. 编译验证

在main.c添加一些代码然后编译一下:
没有意外的话应该就成功了。。。

#include "FreeRTOS.h"
#include "task.h"

int  main()
{   
    while (1);
}

【FreeRTOS学习】(1)在项目中添加FreeRTOS_第13张图片

你可能感兴趣的:(FreeRTOS,c语言,单片机,mcu,stm32)