FreeRTOS下载包中已经包含很多演示例程- 每一个例程都是针对于:
可以在官方网站首页左侧的树形菜单 'Supported Devices' 中找到这些例程介绍。
可惜的是不可能为所有微控制器、编译器和评估板提供演示例程。因此,官方提供的演示例程可能不完全符合你正在使用的开发平台。本章描述如何通过修改或合并官方提供的演示例程,来满足自己的开发平台需求(包括微处理器和编译器)。
修改一个现有的评估板例程,使之运行到另一个同类评估板上,通常是比较简单的,稍微复杂些的是跨编译器移植。本文介绍这两情况下的修改,只是对相似的平台有效。然而,将FreeRTOS移植到一个全新的平台、未支持的处理器架构,并不是件简单的事情。本文不讨论如何将FreeRTOS移植到一个全新平台。
本节描述如何通过修改一个官方提供的演示例程,使之运行到另一个评估板,这里两个评估板使用同系列微处理器,使用相同编译器。在这个例子中,将运行于SAM7S-EK硬件开发板上的IAR SAM7S演示例程,修改使之运行到Olimex SAM7-P64开发板。(注:两块开发板都是使用ATMEL公司的ARM7微处理器,前者使用AT91SAM7S256,后者使用AT91SAM7S64)
作为修改练习的起点,被修改的演示例程是要能使用的。因此,在未做任何修改之前,首先检查下载的演示例程能否被正确的编译。绝大多数情况下,演示例程编译后是没有任何错误和警告的。
关于演示例程所在目录,参考《FreeRTOS系列第2篇---FreeRTOS入门指南》一文的第三节。
LED灯是用来指示演示例程运行的最简单方法,因此点亮新硬件平台上的LED灯通常是最容易的。
两个不同评估板上的LED连接到相同的IO端口通常是不太可能的,因此,一些小幅度修改是必须的。
在partest.c文件中的vParTestInitialise() 函数包含IO端口的模式和方向配置。在main.c文件中的prvSetupHardware()函数包含更多的硬件初始化(比如,使能IO外设的时钟模块),可能需要根据不同的使用进行一些修改。
根据目标评估板的硬件,在上面两个函数中做必要的修改,然后写一段简单程序,来检查硬件LED是否完好。这个简单程序不使用FreeRTOS,只是为了确保硬件LED能够正常工作。因此,注释掉之前的main()函数,使用下面的例子代替:
int main( void ) { volatile unsigned long ul; /* 禁止编译器优化此变量 */ /* 初始化LED IO为输出-注:prvSetupHardware()也可能会被调用*/ vParTestInitialise(); /*不断开启LED */ for( ;; ) { /* 我们暂时不使用RTOS,这里只是使用一个非常粗糙的延时*/ for( ul = 0; ul < 0xfffff; ul++ ) { } /* 开启4个LED */ vParTestToggleLED( 0 ); vParTestToggleLED( 1 ); vParTestToggleLED( 2 ); vParTestToggleLED( 3 ); } return 0; }
一旦确定硬件LED可以正常工作,就可以恢复原来的main()函数。
作为入门级的多任务应用程序应该尽量的简单,LED闪烁测试程序常常担任这样的角色,可以堪比经典的“Hello Wold”。这个任务几乎在所有演示例程中都能看到,在main()函数中调用vStartLEDFlashTasks() (使用协程版本时调用vStartFlashCoRoutines())来实现。如果你使用的演示例程main()函数中并没有调用vStartLEDFlashTasks()(或vStartFlashCoRoutines()),那么需要你将FreeRTOS/Demo/Common/Minimal/Flash.c文件添加到你的工程,并在main()函数手动的增加vStartLEDFlashTasks()函数。
除了调用vStartLEDFlashTasks()外,注释掉所有用于启动一个或多个演示任务的函数。最后的main()函数仅调用三个函数:prvSetupHardware()、vStartLEDFlashTasks()和vTaskStartScheduler()。例如(基于典型的main()函数):
int main( void ) { /* 设置用于演示的微控制器硬件 */ prvSetupHardware(); /* 留下这个函数 */ vCreateFlashTasks(); /* 所有的其它创建任务的函数统统注释掉 vCreatePollQTasks(); vCreateComTestTasks(); //等等… xTaskCreate( vCheckTask,"check", STACK_SIZE, NULL, TASK_PRIORITY, NULL ); */ /*启动RTOS调度器. */ vTaskStartScheduler(); /* 永远不会执行到这里! */ return 0; }
这是一个非常简单的应用程序,正确执行后,LED0~2(包括2)或分别按照不同的频率闪烁。
一旦简单的LED闪烁例程正确执行后,你可以恢复之前注释掉的所有的演示任务。
以下要点需牢记:
本节主要描述如何修改一个现存的工程或者按照需求合并两个现存的工程。比如,你希望使用GCC编译器创建一个STR9演示工程(demo project),并且你下载的FreeRTOS软件包中并没有GCC版本的STR9演示例程,但是FreeRTOS下载包中有IAR版本的STR9演示例程和GCC版本的STR75x演示例程。则可以通过这两个现存的工程来创 建GCC版本的STR9演示工程。可以有两种方式完成:
使用GCC版本的STR75x演示工程,修改使之符合指定的微处理器(STR9评估板上的微处理器)。
使用GCC创建一个新的工程。从IAR版本的STR9演示工程中获取文件和配置信息,使之符合GCC编译器需求。
对于一个特定平台,大多数(不是全部)硬件接口代码包含在一个叫做FreeRTOS/source/portable/[编译器]/[微控制器/port.c的文件中,和它对应的头文件是FreeRTOS/source/portable/[编译器]/[微控制器]/portmacro.h。
对于一些编译器来说,port.c和portmacro.h就是所需要的全部硬件接口代码。另一些还需要一些汇编文件,这些文件叫做portasm.s或者portasm.asm。
最后,仅对于ARM7 GCC移植,同样存在一个类似的硬件接口文件:portISR.c,portISR.c是从port.c中分离出来的,这些代码必须在ARM模式下编译,port.c中剩余的代码既可以在ARM模式下编译,也可在THUMB模式下编译。
编译器可以为嵌入式系统提供某些特定的C语言扩展。比如某个特定关键字可以标识出一个函数是中断处理服务函数。
扩展的C语言部分,是不属于标准C语言规范的。因此,编译器与编译器之间是有差别的。FreeRTOS的文件中就包含类似的非标准C语言语法,在文件夹FreeRTOS/source/portable中(上文中提到的特定微控制器硬件接口代码也在这个文件中)。此外,一些演示例程会使用到中断服务程序,这些中断服务程序并不属于FreeRTOS的一部分,并且如何定义和使用这些中断服务程序也是编译器所特定的。
C启动文件和链接脚本都属于处理器和编译器特定的。不推荐尝试从无到有的创建这些文件,应该到FreeRTOS演示工程中寻找一个合适的来修改。
要特别小心ARM7启动文件。它必须将IRQ中断服务程序入口地址配置到快速中断处理向量表或者普通中断向量表中。这两种情况,演示工程都提供了例子。
链接脚本必须正确的描述当前使用处理器的内存映射。
每一个工程通常都会定义一些宏,这些预处理宏定义了一些要被编译的特定的硬件接口代码。要包含portmacro.h文件才能识别这些宏。比如,当使用GCC编译MegaAVR硬件接口代码时,宏GCC_MEGA_AVR必须被定义;当使用IAR编译MegaAVR硬件接口代码时,宏IAR_MEGA_AVR必须被定义等等。参考演示例程工程以及FreeRTOS/source/include/portable.h文件可以查找当前工程定义了那些宏。如果预处理宏未定义,那么portmacro.h文件所在目录的路径必须被包含到预处理器的搜索路径中。
其它的编译器设置,比如优化选项,也是很关键的。可以参考提供的演示工程。
具有IDE的编译器通常具有目标微控制器选项并将它作为工程设置的一部分,所以新的工程也必须适应新的目标微控制器,同样的,如果使用到makefile文件,则makefile文件也必须更新以符合新的目标微控制器。
调用函数prvSetupTimerInterrupt()来配置系统节拍中断,这个函数可以在以下路径的文件中找到:FreeRTOS/source/portable/[compiler]/[microcontroller]/port.c
FreeRTOS内存管理一章中描述了FreeRTOS如何使用RAM,并且描述了RAM是如何分配给RTOS内核的。
如果你要将演示例程移植到一个RAM稍小的微处理器上,那么你可能需要减少configTOTAL_HEAP_SIZE的值(位于FreeRTOSConfig.h),并且减少演示例程的任务个数。可以通过简单的注释掉不需要的任务来实现。
如果你要将演示例程移植到一个ROM较小的微处理器中,那么你可能需要减少应用例程的文件数目,他们位于FreeRTOS/Demo/common文件夹目录下。同时你还要删除main函数中对他们的调用函数。