1、FreeRTOS 源码下载
两个下载链接, 一个是官网:http://www.freertos.org/, 另外一个是代码托管网站:https://sourceforge.net/projects/freertos/files/FreeRTOS/
打开代码托管网站链接,我们选择FreeRTOS 的版本 V9.0.0(2016 年),尽管现在 FreeRTOS 的版本已经更新到V10.4.1 了,但是我们还是选择V9.0.0,因为内核很稳定,并且网上资料很多,因为 V10.0.0 版本之后是亚马逊收购了 FreeRTOS 之后才出来的版本,主要添加了一些云端组件,我们所讲的 FreeRTOS 是实时内核,采用 V9.0.0 版本足以。
点击后就会自动下载,然后解压zip压缩包以后:
2、FreeRTOS 文件介绍
FreeRTOS 包含 Demo 例程和内核源码,如下图所示:
Source 文件夹:里面包含的是 FreeRTOS 内核的源代码,我们移植 FreeRTOS的时候就需要这部分源代码。
Demo 文件夹:里面包含了 FreeRTOS 官方为各个单片机移植好的工程代码,FreeRTOS 为了推广自己,会给各种半导体厂商的评估板写好完整的工程程序,这些程序就放在 Demo 这个目录下,这部分 Demo 非常有参考价值。我们把FreeRTOS 到 STM32 的时候,FreeRTOSConfig.h 这个头文件就是从这里拷贝过来的,下面我们对 FreeRTOS 的文件夹进行分析说明。
License 文件夹:这个文件夹里面就是相关的许可信息,要用 FreeRTOS 做产品的得仔细看看,尤其是要出口的产品。
2.1、Source 文件夹
打开“FreeRTOS/Source”文件夹,其内文件及文件夹如下所示:
编号①和③包含的是 FreeRTOS 的通用的头文件和 C 文件,这两部分的文件适用于各种编译器和处理器,是通用的。需要移植的头文件和 C 文件放在编号②portblle 这个文件夹。
我们打开 portblle 这个文件夹,可以看到里面很多与编译器相关的文件
夹,在不同的编译器中使用不同的支持文件,如下所示:
编号①中的 KEIL 就是我们就是我们使用的编译器,打开 KEIL 文件夹的时候,你会看到一句话“See-also-the-RVDS-directory.txt”,其实 KEIL 里面的内容跟 RVDS 里面的内容一样,所以,我们只需要编号③RVDS 文件夹里面的内容即可。而编号②MemMang 文件夹下存放的是跟内存管理相关的,稍后具体介绍。
(1)RVDS 文件夹
打开 RVDS 文件夹,下面包含了各种处理器相关的文件夹,从文件夹的名字我们就非常熟悉了,我们学习的 STM32 有 M0、M3、M4 等各种系列,FreeRTOS 是一个软件,单片机是一个硬件,FreeRTOS 要想运行在一个单片机上面,它们就必须关联在一起,那么怎么关联?还是得通过写代码来关联,这部分关联的文件叫接口文件,通常由汇编和 C 联合编写。这些接口文件都是跟硬件密切相关的,不同的硬件接口文件是不一样的,但都大同小异。编写这些接口文件的过程我们就叫移植,移植的过程通常由 FreeRTOS 和 mcu 原厂的人来负责,移植好的这些接口文件就放在 RVDS 这个文件夹的目录下,如下所示:
FreeRTOS 为我们提供了 cortex-m0、m3、m4 和 m7 等内核的单片机的接口文件,只要是使用了这些内核的 mcu 都可以使用里面的接口文件。通常我们说的移植,其实准确来说,不能够叫移植,应该叫使用官方的移植,因为这些跟硬件相关的接口文件,RTOS 官方都已经写好了,我们只是使用而已。
我们这里以 ARM_CM3 这个文件夹为例,看看里面的文件,里面只有“port.c”与“portmacro.h”两个文件,port.c 文件里面的内容是FreeRTOS 官方的技术人员为 Cortex-M3 内核的处理器写的接口文件,里面核心的上下文切换代码是由汇编语言编写而成,对技术员的要求比较高,我们刚开始学习的时候只需拷贝过来用即可,深入的学习可以放在后面的日子;portmacro.h 则是 port.c 文件对应的头文件,主要是一些数据类型和宏定义。文件如下:
(2)MemMang 文件夹
编号②MemMang 文件夹下存放的是跟内存管理相关的,总共有五个heap 文件以及一个 readme 说明文件,这五个 heap 文件在移植的时候必须使用一个,因为 FreeRTOS 在创建内核对象的时候使用的是动态分配内存,而这些动态内存分配的函数则在这几个文件里面实现,不同的分配算法会导致不同的效率与结果,后面在内存管理中我们会讲解每个文件的区别,由于现在是初学,所以我们选用 heap4.c 即可。如下图所示:
至此,FreeRTOS/source 文件夹下的主要内容就讲完,剩下的可根据兴趣自行查阅。
2.2、Demo 文件夹
这个目录下内容就是 Deme 例程,我们可以直接打开里面的工程文件,各种开发平台的完整 Demo,开发者可以方便的以此搭建出自己的项目,甚至直接使用。FreeRTOS 当然也为 ST 写了很多 Demo,其中就有F1、F4、F7 等工程,这对我们学习 FreeRTOS 是非常方便的,当遇到不懂的直接就可以参考官方的Demo。如下所示:
2.3、FreeRTOS-Plus 文件夹
FreeRTOS-Plus 文件夹里面包含的是第三方的产品,一般我们不需要使用,FreeRTOS-Plus 的预配置演示项目组件(组件大多数都要收费),大多数演示项目都是在 Windows 环境中运行的,使用 FreeRTOS windows 模拟器,所以暂时不需要关注这个文件夹。
2.4、HTML 文件
一些直接可以打开的网页文件,里面包含一些关于 FreeRTOS 的介绍,是FreeRTOS 官方人员所写,所以都是英文的,有兴趣可以打开看看,具体相关内容可以看 HTML 文件名称。
3、FreeRTOS 移植
要将 FreeRTOS 移植到 STM32 上,需要一个基础工程,可直接使用一个之前做好的工程(使用库函数编写的LED灯闪烁的例子),为了不破坏基础工程的完整性,我们重新复制一份,并重命名为“FreeRTOS移植模板_LED闪烁”。
如下:
3.1、添加 FreeRTOS 源码
在前面创建的好的基础工程“FreeRTOS移植模板_LED闪烁”内新建一个“FreeRTOS”文件夹用于保存 FreeRTOS 移植所需源码。如下所示:
创建好 FreeRTOS 文件夹后,就可以将 FreeRTOS 源码添加到该文件中,前面我们已经讲解了 FreeRTOS 源码文件内容,可直接将源码“FreeRTOSv9.0.0\FreeRTOS\Source”内容全部拷贝到新建的“FreeRTOS”文件夹中,如下所示:
粘贴到工程文件夹下的FreeRTOS文件夹中
前面详细的讲解过 portable 文件夹,对于使用 KEIL 环境我们只需要留下keil、MemMang 和 RVDS 这三个文件夹,其他的都可以删除掉(如果使用其他编译环境,根据实际情况选择)。
如下所示:
3.2、向工程组添加文件
打开基础工程,新建分组 FreeRTOS_core 和 FreeRTOS_port,然后向这两个分组中添加FreeRTOS 源码文件。FreeRTOS_core 组所添加的文件位置在“\FreeRTOS移植模板_LED闪烁\FreeRTOS”下源文件,FreeRTOS_port 组所添加的文件位置在“\FreeRTOS移植模板_LED闪烁\FreeRTOS\portable\RVDS\ARM_CM3”和“\FreeRTOS移植模板_LED闪烁\FreeRTOS\portable\MemMang”下源文件。如下图所示:
分组 FreeRTOS_core 中的文件在什么地方就不说了,打开 FreeRTOS 源码一目了然。重点来说说 FreeRTOS_port 分组中的 port.c 和 heap_4.c 是怎么来的,port.c 是 RVDS 文件夹下的 ARM_CM3 中的文件,因为 STM32F103 是Cortex-M3 内核的,因此要选择 ARM_CM3 中的 port.c 文件。假如使用的是其它内核芯片,则选择对应内核文件夹内的 port.c 文件。heap_4.c 是 MemMang 文件夹中的,前面说了 MemMang 是跟内存管理相关的,里面有 5 个 c 文件:heap_1.c、heap_2.c、heap_3.c、heap_4.c 和 heap_5.c。这 5 个 c 文件是五种不同的内存管理方法。这 5 个文件都可以用来作为 FreeRTOS 的内存管理文件,只是它们的实现原理不同,各有利弊。这里我们选择heap_4.c,至于原因,后面会有一章节专门来讲解 FreeRTOS 的内存管理,到时候大家就知道原因了。这里就先选择 heap_4.c,毕竟本章的重点是 FreeRTOS 的移植。
3.3、添加头文件路径
添加完 FreeRTOS 源码中的 C 文件以后还要添加 FreeRTOS 源码的头文件路径,头文件路径如下图所示:
头文件路径添加完成以后编译一下,看看有没有什么错误,结果会发现提示打不开“FreeRTOSConfig.h”这个文件,如下图所示:
这是因为缺少 FreeRTOSConfig.h 文件,这个文件在哪里找呢?你可以自己创建,显然这不是一个明智的做法。我们可以找找 FreeRTOS 的官方移植工程中会不会有这个文件,打开前面下载好的 FreeRTOS 源码文件,里面有各种芯片移植好的 Demo,针对 STM32F103 的移植工程文件,文件夹是CORTEX_STM32F103_Keil,如果使用的是其它内核芯片,请打开对应的工程。打开以后如下图所示:
为了方便管理,我们将该文件复制一份存放到“FreeRTOS移植模板_LED闪烁\FreeRTOS\include”位置下,如下图所示:
我们再编译一下工程,没有错误,如下图所示:
FreeRTOSConfig.h 是何方神圣?看名字就知道,他是 FreeRTOS 的配置文件,一般的操作系统都有裁剪、配置功能,而这些裁剪及配置都是通过一个文件来完成的,基本都是通过宏定义来完成对系统的配置和裁剪的。
3.4、修改 FreeRTOSConfig.h 文件
FreeRTOSConfig.h 是直接从 demo 文件夹下面拷贝过来的,该头文件对裁剪整个 FreeRTOS 所需的功能的宏均做了定义,有些宏定义被使能,有些宏定义被失能,一开始我们只需要配置最简单的功能即可。要想随心所欲的配置FreeRTOS 的功能,我们必须对这些宏定义的功能有所掌握,下面我们先简单的介绍下这些宏定义的含义,然后再对这些宏定义进行修改。
3.5、修改 stm32f10x_it.c 文件
SysTick 中断服务函数是一个非常重要的函数,FreeRTOS 所有跟时间相关的事情都在里面处理,SysTick 就是 FreeRTOS 的一个心跳时钟,驱动着 FreeRTOS的运行,就像人的心跳一样,假如没有心跳,我们就相当于“死了”,同样的,FreeRTOS 没有了心跳,那么它就会卡死在某个地方,不能进行任务调度,不能运行任何的东西,因此我们需要实现一个 FreeRTOS 的心跳时钟,FreeRTOS 帮
我们实现了 SysTick 的启动的配置:在 port.c 文件中已经实现vPortSetupTimerInterrupt()函数,并且 FreeRTOS 通用的 SysTick 中断服务函数也实现了:在 port.c 文件中已经实现 xPortSysTickHandler()函数,所以移植的时候只需要我们在 stm32f10x_it.c 文件中实现我们对应(STM32)平台上的 SysTick_Handler()函数即可。FreeRTOS 为开发者考虑得特别多,PendSV_Handler()与 SVC_Handler()这两个很重要的函数都帮我们实现了,在 port.c 文件中已经实现xPortPendSVHandler()与 vPortSVCHandler()函数,防止我们自己实现不了,那么在 stm32f10x_it.c 中就需要我们注释掉 PendSV_Handler()与SVC_Handler()这两个函数了,具体修改代码如下:
1、添加头文件
#include "FreeRTOS.h" //FreeRTOS 使用
#include "task.h"
2、注释掉PendSV_Handler和SVC_Handler
3、修改SysTick_Handler部分
extern void xPortSysTickHandler(void);
/**
* @brief This function handles SysTick Handler.
* @param None
* @retval None
*/
void SysTick_Handler(void)
{if(xTaskGetSchedulerState()!=taskSCHEDULER_NOT_STARTED)//系统已经运行{xPortSysTickHandler();}
}
3.6、修改 Delay.c 和 Delay.h 文件,设置系统时钟频率,开启SYSTICK和开启SYSTICK中断
首先在 Delay.c 文件开头添加 FreeRTOS 头文件,如下:
Delay.c
#include "FreeRTOS.h" //FreeRTOS 使用
#include "task.h"
#include "stm32f10x.h"static u8 fac_us=0; //us延时倍乘数
static u16 fac_ms=0; //ms延时倍乘数//初始化延迟函数
//SYSTICK的时钟固定为AHB时钟,基础例程里面SYSTICK时钟频率为AHB/8
//SYSCLK:系统时钟频率
void SysTick_Init(u8 SYSCLK)
{u32 reload;SysTick_CLKSourceConfig(SysTick_CLKSource_HCLK);//选择外部时钟 HCLKfac_us=SystemCoreClock/1000000; //不论是否使用OS,fac_us都需要使用reload=SystemCoreClock/1000000; //每秒钟的计数次数 单位为M reload*=1000000/configTICK_RATE_HZ; //根据configTICK_RATE_HZ设定溢出时间//reload为24位寄存器,最大值:16777216,在72M下,约合0.233s左右 fac_ms=1000/configTICK_RATE_HZ; //代表OS可以延时的最少单位 SysTick->CTRL|=SysTick_CTRL_TICKINT_Msk; //开启SYSTICK中断SysTick->LOAD=reload; //每1/configTICK_RATE_HZ秒中断一次 SysTick->CTRL|=SysTick_CTRL_ENABLE_Msk; //开启SYSTICK
} //延时nus
//nus:要延时的us数.
//nus:0~204522252(最大值即2^32/fac_us@fac_us=168)
void Delay_us(u32 nus)
{ u32 ticks;u32 told,tnow,tcnt=0;u32 reload=SysTick->LOAD; //LOAD的值 ticks=nus*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; //时间超过/等于要延迟的时间,则退出.} };
}
//延时nms
//nms:要延时的ms数
//nms:0~65535
void Delay_ms(u32 nms)
{ if(xTaskGetSchedulerState()!=taskSCHEDULER_NOT_STARTED)//系统已经运行{ if(nms>=fac_ms) //延时的时间大于OS的最少时间周期 { vTaskDelay(nms/fac_ms); //FreeRTOS延时}nms%=fac_ms; //OS已经无法提供这么小的延时了,采用普通方式延时 }delay_us((u32)(nms*1000)); //普通方式延时
}//延时nms,不会引起任务调度
//nms:要延时的ms数
void Delay_xms(u32 nms)
{u32 i;for(i=0;i<nms;i++) delay_us(1000);
}/*** @brief 秒级延时* @param xs 延时时长,范围:0~4294967295* @retval 无*/
void Delay_s(u32 xs)
{while(xs--){Delay_ms(1000);}
}
Delay.h
#ifndef __DELAY_H
#define __DELAY_Hvoid Delay_us(u32 us);
void Delay_ms(u32 ms);
void Delay_xms(u32 nms);
void Delay_s(u32 s);#endif