FreeRTOS参考手册中的模拟程序都是基于windows版本的模拟器。因此为了学习FreeRTOS,需要在Windows中搭建一下模拟器的运行环境。网络上的一般都是直接跑一下FreeRTOS源码中的WSVC中的Demo。就没有下文了,怎么加入自己的代码到模拟器,怎么删除模拟器中已经存在的监测任务,完全没有介绍。所以我在这里总结一下怎么在FreeRTOS源码基础上搭建一个自己的模拟器环境。我试了可以有以下2种方法:
FreeRTOS提供的源码文件中有三个Windows平台可用的Demo。一个用于mingw32,一个用于MSVC。其中MSVC默认使用Visual studio开发环境。因此简单起见就用Visual studio。可以用Visual studio提供的MSVC编译环境编译出程序后,需要考虑的就是移除实例代码中默认的程序,加入自己需要学习、测试的代码。MSVC的Demo中有不少的任务,这里我就不做介绍了,我也是踩过不少坑后才知道其中一部份。本来下载它的模拟器下来就是准备从简单的任务开始跑起,结果发现简单任务还不方便加入,好不容易加入了,还有很多调试信息输出。不得已还要研究它的编译过程,麻烦。闲话不多说进入正题,MSVC搭建环境有以下几个步骤:
这个步骤简单,有很多地方可以下载。我习惯用腾讯软件,普通下载直接下载,软件也没什么广告插件。
下载git-bash并安装,将git命令加入到系统path。不会自己可以搜索下。
git clone https://gitee.com/rogerbowu/FreeRTOS.git
cd FreeRTOS/FreeRTOS/Source
git clone https://gitee.com/pai3_1415926/FreeRTOS-Kernel.git
mv FreeRTOS-Kernel/* ..
rm -rf FreeRTOS-Kernel
选择Visual studio右侧文件浏览器中的RTOSDemo前面的三角形展开,再点击Demo App Source,选择打开main.c
更改main.c
// 在头文件后面加一行
extern void simulator_run();
// 后面这个宏要设置为1,不然运行过程会被一个信号打断
#define mainCREATE_SIMPLE_BLINKY_DEMO_ONLY 1
// 找到main函数,改成下面的样子。主要就是注释部分代码,加入simulator_run()的调用
int main( void )
{
// 前面这两个函数调用是必须要的,不要模拟器就会报错
/* This demo uses heap_5.c, so start by defining some heap regions. heap_5
* is only used for test and example reasons. Heap_4 is more appropriate. See
* http://www.freertos.org/a00111.html for an explanation. */
prvInitialiseHeap();
/* Initialise the trace recorder. Use of the trace recorder is optional.
* See http://www.FreeRTOS.org/trace for more information. */
// 这个是系统追踪初始化
configASSERT( xTraceInitialize() == TRC_SUCCESS );
/* Start the trace recording - the recording is written to a file if
* configASSERT() is called. */
// 这里的提示信息就注释了
/*printf(
"Trace started.\r\n"
"The trace will be dumped to the file \"%s\" whenever a call to configASSERT()\r\n"
"fails or the \'%c\' key is pressed.\r\n"
"Note that the trace output uses the ring buffer mode, meaning that the output trace\r\n"
"will only be the most recent data able to fit within the trace recorder buffer.\r\n",
mainTRACE_FILE_NAME, mainOUTPUT_TRACE_KEY );*/
configASSERT( xTraceEnable(TRC_START) == TRC_SUCCESS );
/* Set interrupt handler for keyboard input. */
vPortSetInterruptHandler( mainINTERRUPT_NUMBER_KEYBOARD, prvKeyboardInterruptHandler );
/* Start keyboard input handling thread. */
xWindowsKeyboardInputThreadHandle = CreateThread(
NULL, /* Pointer to thread security attributes. */
0, /* Initial thread stack size, in bytes. */
prvWindowsKeyboardInputThread, /* Pointer to thread function. */
NULL, /* Argument for new thread. */
0, /* Creation flags. */
NULL);
/* Use the cores that are not used by the FreeRTOS tasks for the Windows thread. */
SetThreadAffinityMask( xWindowsKeyboardInputThreadHandle, ~0x01u );
// 这里是用一个宏确定用那个实例程序,这里我们用自己的程序。就不用源码自带的了,都注释了
/* The mainCREATE_SIMPLE_BLINKY_DEMO_ONLY setting is described at the top
* of this file. */
/*#if ( mainCREATE_SIMPLE_BLINKY_DEMO_ONLY == 1 )
{
main_blinky();
}
#else
{
main_full();
}
#endif*/ /* if ( mainCREATE_SIMPLE_BLINKY_DEMO_ONLY == 1 ) */
// 加入我们自己的函数调用
simulator_run();
return 0;
}
上面对main.c文件的更改主要就是增加了一个外部调用函数原型的声明extern void simulator_run();
。后面我们需要在自己的实例中实现这个函数。这里main函数已经调用了这个函数,因此我们自己写的函数就不能是main函数了,而是声明那个函数原型。
下面就再加入一个自己的实例函数。Demo App Source源码分类上点击鼠标右键,选择"添加",新建项。选择C++文件,名称可以输入一个first.c(可以是C文件,不用非是Cpp文件),最后选择一个电脑上的任意位置。
#include
#include "FreeRTOS.h"
#include "task.h"
#define mainDELAY_LOOP_COUNT 100000
void vPrintString(char * pcString) {
printf("%s", pcString);
fflush(stdout);
}
void task1(void* pvParameters) {
const char* pcTaskName = "Task1 is running\r\n";
volatile uint32_t ul; /* volatile 防止编译器优化掉,不进行循环*/
/* 大多数任务,都以这样的内联循环运行*/
for (;;) {
/* 打印任务名字 */
vPrintString(pcTaskName);
/* 延迟 */
for (ul = 0; ul < mainDELAY_LOOP_COUNT; ul++)
{
/* 这个循环只是延迟实现,啥都不干。后面的例子会用delay或sleep函数替代*/
}
}
}
void task2(void* pvParameters) {
const char* pcTaskName = "Task2 is running\r\n";
volatile uint32_t ul; /* volatile 防止编译器优化掉,不进行循环*/
/* 大多数任务,都以这样的内联循环运行*/
for (;;) {
/* 打印任务名字 */
vPrintString(pcTaskName);
/* 延迟 */
for (ul = 0; ul < mainDELAY_LOOP_COUNT; ul++)
{
/* 这个循环只是延迟实现,啥都不干。后面的例子会用delay或sleep函数替代*/
}
}
}
// 主函数开始调度器之前创建任务
int simulator_run(void) {
/* 创建2个任务。真正的程序应当检测xTaskCreate()函数的返回值,确保函数成功执行。*/
xTaskCreate(task1, "Task1", 1000, NULL, 1, NULL);
xTaskCreate(task2, "Task2", 1000, NULL, 1, NULL);
/* 开启调度器 */
vTaskStartScheduler();
/* 如果一切顺利,主函数就不会运行到接下来的循环。如果主函数运行到这个循环,就说明没有足够的堆用于创建空闲任务。第二章提供了更加详细的堆管理信息。*/
for (;;);
}
再次点击绿色三角形运行,运行结果如下图表示成功。如果还要想加入新的实例程序,就实现void simulator_run()
函数就可以了。这个文件也可以保存在任何你自己的项目目录中。
如果你不想用MSVC,或你对mingw32更加熟悉。您可以用下面的方法搭建freeRTOS模拟器环境。和上面使用Visual studio大部分都一样,只是编译环境不同。Visual studio是采用MSVC的编译工具,新加入代码文件也会自动加入到项目中。使用mingw32提供的mingw32-make.exe等编译工具需要自己动手更改Makefile文件,相对而言更复杂,也更困难。但可以让你更多的了解程序编译过程。下面就是具体步骤:
也是可以在腾讯软件下载。
不同包选择方法不同,安装后确定有gcc,g++,make就可以了,这三个程序都在安装目录的bin目录下。make前面可能有个mingw32或其它前缀。可以链接成make命令。下载会花不少时间。
安装好后将bin目录加入到系统的环境变量path中。打开一个cmd窗口,确定已经安装好。
gcc --version
gcc.exe (x86_64-posix-seh-rev0, Built by MinGW-W64 project) 8.1.0
Copyright (C) 2018 Free Software Foundation, Inc.
This is free software; see the source for copying conditions. There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
g++ version
g++.exe (x86_64-posix-seh-rev0, Built by MinGW-W64 project) 8.1.0
Copyright (C) 2018 Free Software Foundation, Inc.
This is free software; see the source for copying conditions. There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
mingw32-make --version
GNU Make 4.2.1
Built for x86_64-w64-mingw32
Copyright (C) 1988-2016 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.
同上
打开FreeRTOS源码文件中的FreeRTOS/Demo/WIN32-MingW/目录编译mingw32-make.exe
编译完成后会在当前目录的build目录下生成一个RTOSDemo.exe可执行文件。双击打开这个文件,出现如下的运行图片表示成功。
因为思路一样,所以也是更改main.c文件为如下样子。
// 在头文件后面加一行
extern void simulator_run();
// 后面这个宏要设置为1,不然运行过程会被一个信号打断
#define mainCREATE_SIMPLE_BLINKY_DEMO_ONLY 1
// 找到main函数,改成下面的样子。主要就是注释部分代码,加入simulator_run()的调用
int main( void )
{
// 前面这两个函数调用是必须要的,不要模拟器就会报错
/* This demo uses heap_5.c, so start by defining some heap regions. heap_5
* is only used for test and example reasons. Heap_4 is more appropriate. See
* http://www.freertos.org/a00111.html for an explanation. */
prvInitialiseHeap();
/* Initialise the trace recorder. Use of the trace recorder is optional.
* See http://www.FreeRTOS.org/trace for more information. */
// 这个是系统追踪初始化
configASSERT( xTraceInitialize() == TRC_SUCCESS );
/* Start the trace recording - the recording is written to a file if
* configASSERT() is called. */
// 这里的提示信息就注释了
/*printf(
"Trace started.\r\n"
"The trace will be dumped to the file \"%s\" whenever a call to configASSERT()\r\n"
"fails or the \'%c\' key is pressed.\r\n"
"Note that the trace output uses the ring buffer mode, meaning that the output trace\r\n"
"will only be the most recent data able to fit within the trace recorder buffer.\r\n",
mainTRACE_FILE_NAME, mainOUTPUT_TRACE_KEY );*/
configASSERT( xTraceEnable(TRC_START) == TRC_SUCCESS );
/* Set interrupt handler for keyboard input. */
vPortSetInterruptHandler( mainINTERRUPT_NUMBER_KEYBOARD, prvKeyboardInterruptHandler );
/* Start keyboard input handling thread. */
xWindowsKeyboardInputThreadHandle = CreateThread(
NULL, /* Pointer to thread security attributes. */
0, /* Initial thread stack size, in bytes. */
prvWindowsKeyboardInputThread, /* Pointer to thread function. */
NULL, /* Argument for new thread. */
0, /* Creation flags. */
NULL);
/* Use the cores that are not used by the FreeRTOS tasks for the Windows thread. */
SetThreadAffinityMask( xWindowsKeyboardInputThreadHandle, ~0x01u );
// 这里是用一个宏确定用那个实例程序,这里我们用自己的程序。就不用源码自带的了,都注释了
/* The mainCREATE_SIMPLE_BLINKY_DEMO_ONLY setting is described at the top
* of this file. */
/*#if ( mainCREATE_SIMPLE_BLINKY_DEMO_ONLY == 1 )
{
main_blinky();
}
#else
{
main_full();
}
#endif*/ /* if ( mainCREATE_SIMPLE_BLINKY_DEMO_ONLY == 1 ) */
// 加入我们自己的函数调用
simulator_run();
return 0;
}
还需要更改一下Makefile,让它做为一个子编译脚本,主Makefile会传递几个变量过来,包括编译中间文件与可执行程序存放目录,实现simulator_run()
的源文件和自定义头文件目录,编译产生文件名称等。
# 在BUILD_DIR那一行行首加一个#,表示注释掉。具体编译输出目录由主Makefile指定
# BUILD_DIR := ./build
# 向下再注释掉执行文件名称行
# EXE := $(BUILD_DIR)/RTOSDemo.exe
# 向下翻到最后一页,找到生成可执行文件行。复制一下,其中一行注释了。另外一行结尾加入我们实现simulator_run()和自己项目文件中间文件(也就是各种.o文件)
# 主Makefile传递了一个PROJECT_EXE代替原Makefile中的EXE
# $(EXE) : $(MAIN_OBJS) $(FREERTOS_KERNEL_OBJS) $(FREERTOS_DEMOS_OBJS) $(FREERTOS_PLUS_TRACE_OBJS)
$(PROJECT_EXE) : $(MAIN_OBJS) $(FREERTOS_KERNEL_OBJS) $(FREERTOS_DEMOS_OBJS) $(FREERTOS_PLUS_TRACE_OBJS) $(PROJECT_OBJ)
# 最后是在# Clean rule前加入生成我们自己添加外部文件的编译规则,生成*.o文件,用于最终链接成可执行文件
# project demo objects rules
$(PROJECT_OBJ): %.o: $(PROJECT_DEMO_C) $(FREERTOS_PLUS_TRACE_INCLUDES) $(FREERTOS_KERNEL_INCLUDE_DIRS) $(FREERTOS_DEMOS_INCLUDE_DIRS) $(MAIN_INCLUDES)
mkdir -p $(@D)
$(CC) $(CFLAGS) $(PROJECT_DEMO_INC) $(FREERTOS_PLUS_TRACE_INCLUDE_DIRS) $(FREERTOS_KERNEL_INCLUDE_DIRS) $(FREERTOS_DEMOS_INCLUDE_DIRS) $(MAIN_INCLUDE_DIRS) -o $@ $(PROJECT_DEMO_C)
# Clean rule
以上步骤还好,最复杂的地方就是如何在外部加入一个自己的代码到这个项目中。
在任意位置新建一个目录,再在里面新建一个inc和src目录。inc存放自己的头文件,src存放源代码。
再新建一个Makefile文件,这个Makefile文件中一定要根据自己FreeRTOS源码位置,设定好FREERTOS_SRC
变量,不然会找不到。
# 如果你的模拟器和FreeRTOS源码目录位于同一个目录,就不用更改这个目录
# 如果你的模拟器和FreeRTOS源码位于不同的目录,需要设定这个变量为FreeRTOS源码目录
FREERTOS_SRC := $(abspath ../FreeRTOS)
FREERTOS_SIMULATOR_SRC := $(FREERTOS_SRC)/FreeRTOS/Demo/WIN32-MingW
PROJECT_NAME := just_a_test.exe
PROJECT_PATH := $(abspath .)
BUILD_DIR := $(PROJECT_PATH)/build
export BUILD_DIR
# 项目编译中间件存放目录
PROJECT_BUILD_DIR := $(BUILD_DIR)/Project
# 项目源码文件
PROJECT_DEMO_C := $(wildcard $(PROJECT_PATH)/src/*.c)
PROJECT_DEMO_C_NAMES := $(notdir $(PROJECT_DEMO_C))
# 项目头文件搜索目录
PROJECT_DEMO_INC := -I $(abspath ./inc)
# 项目源文件编译中间文件*.o
PROJECT_OBJ := $(PROJECT_DEMO_C_NAMES:%.c=$(PROJECT_BUILD_DIR)/%.O)
# 目标文件
PROJECT_EXE := $(BUILD_DIR)/$(PROJECT_NAME)
export PROJECT_DEMO_C
export PROJECT_DEMO_INC
export PROJECT_OBJ
export PROJECT_EXE
all:
$(MAKE) -C $(FREERTOS_SIMULATOR_SRC)
# 只清除当前目录生成的.o和可执行文件
clean:
rm $(PROJECT_OBJ)
rm $(PROJECT_EXE)
# 清除所有编译产生的.o文件
distclean:
$(MAKE) -C $(FREERTOS_SIMULATOR_SRC) clean
src目录下建立一个用于实现simulator_run()
函数的文件:
#include
#include "FreeRTOS.h"
#include "task.h"
#define mainDELAY_LOOP_COUNT 100000
void vPrintString(char * pcString) {
printf("%s", pcString);
fflush(stdout);
}
void task1(void* pvParameters) {
const char* pcTaskName = "Task1 is running\r\n";
volatile uint32_t ul; /* volatile 防止编译器优化掉,不进行循环*/
/* 大多数任务,都以这样的内联循环运行*/
for (;;) {
/* 打印任务名字 */
vPrintString(pcTaskName);
/* 延迟 */
for (ul = 0; ul < mainDELAY_LOOP_COUNT; ul++)
{
/* 这个循环只是延迟实现,啥都不干。后面的例子会用delay或sleep函数替代*/
}
}
}
void task2(void* pvParameters) {
const char* pcTaskName = "Task2 is running\r\n";
volatile uint32_t ul; /* volatile 防止编译器优化掉,不进行循环*/
/* 大多数任务,都以这样的内联循环运行*/
for (;;) {
/* 打印任务名字 */
vPrintString(pcTaskName);
/* 延迟 */
for (ul = 0; ul < mainDELAY_LOOP_COUNT; ul++)
{
/* 这个循环只是延迟实现,啥都不干。后面的例子会用delay或sleep函数替代*/
}
}
}
// 主函数开始调度器之前创建任务
int simulator_run(void) {
/* 创建2个任务。真正的程序应当检测xTaskCreate()函数的返回值,确保函数成功执行。*/
xTaskCreate(task1, "Task1", 1000, NULL, 1, NULL);
xTaskCreate(task2, "Task2", 1000, NULL, 1, NULL);
/* 开启调度器 */
vTaskStartScheduler();
/* 如果一切顺利,主函数就不会运行到接下来的循环。如果主函数运行到这个循环,就说明没有足够的堆用于创建空闲任务。第二章提供了更加详细的堆管理信息。*/
for (;;);
}
终于可以编译了,进入新建目录中。输入mingw32-make.exe
编译。如果一切顺利在当前目录的build目录下就会生成一个可执行文件,双击执行就可以看到2个任务正常运行了。
最后说说为什么非要使用Windows版本的模拟器。因为FreeRTOS提供的手册就是Windows版本的,linux版本的我也已经移植完成了一个,而且很小,完全可以脱离FreeRTOS源码运行,但遗憾的是linux版本模拟器API没有生成中断的接口,学习到中断的章节就没有办法了。要不就只有上MCU直接运行了。所以没办法只能研究下Window版本的模拟器了。