本文在正点原子STemWin裸机例程上对FreeRTOS进行移植。由于正点原子的教程是基于UCOSIII操作系统的,此文补充一下FreeRTOS移植到STemWin的方法,便于学习FreeRTOS的同学使用。本教程的硬件平台是正点原子的STM32战舰开发板,作者ustc修竹。
一、移植要准备的工作
由于本移植过程是在 STemWin不带操作系统的基础上进行的,所以我们先介绍一下子已有的文件,并且补充哪些需要修改及添加进来。
图1.1
图1.2
如上图所示,左边图1.1是已经移植好的裸机模板,我们在此基础上移植,右边图1.2是移植后的模板。
首先是SYSTEM文件夹,这个是为UCOSIII编写的,这个原文件替换需要替换。具体文件位置打开正点原子的资料:4.程序源码->3.扩展例程->5.FreeRTOS例程,从例程中随便找一个SYSTEM文件夹复制然后替换掉裸机模板例程中的SYSTEM例程。不需要改动。
其次是模板例程中的EMWIN_Config里面的GUI_X.c得替换成GUI_X_FreeRTOS.c,这个文件的位置在STemWin文件夹的OS文件夹中,做过裸机移植的应该知道。将EMWIN_LIB中的STemWin526_CM3_keil.lib文件删除,添加STemWin526_CM3_OS_keil.lib到EMWIN_LIB中,这个文件的位置在STemWin文件夹的Lib文件夹中,做过裸机移植的应该也知道。
在例程模板的USER里面需要添加一个FreeRTOSConfig.h,这个文件的具体位置:4.程序源码->3.扩展例程->5.FreeRTOS例程,从例程中随便找一个,复制到模板文件里面然后添加进USER。这个可以直接用,但是要检查FreeRTOSConfig.h中有没有打开互斥信号量,计数信号量功能,如果没有打开需要打开一下,不然会报错。
在项目里新建两个文件夹:FreeRTOS/src,FreeRTOS/port。这两个文件用来放FreeRTOS的源代码,是移植RTOS的文件。
二、STemWin文件的修改
主要需要修改两个文件:GUIConfig.h与GUI_X_FreeRTOS.c。
1、GUIConfig.h的修改
加了操作系统以后,主要是操作系统的功能得开启。
修改前:#define GUI_OS (0) // 不使用操作系统
修改后:#define GUI_OS (1) // 使用操作系统
参考正点原子的UCOSIII移植教程,貌似还需要定义可以调用STemWin的任务最大数目,这个本人没有定义也可以正常使用,如果觉得需要的自己设置,感兴趣的可以看一下正点原子这方面的内容。
2、GUI_X_FreeRTOS.c的修改
该文件主要是配置操作系统的接口,STemWin使用这些接口管理系统资源。其中与裸机不同的是,裸机主要采用的是简单的延时,使用RTOS以后要使用阻塞延时。该文件代码如下(相关的信息已在代码中标注):
#include "GUI.h"
/* FreeRTOS头文件*/
#include "FreeRTOS.h"
#include "task.h"
#include "semphr.h"
/*********************************************************************
*
* Global data
*/
static xSemaphoreHandle xQueueMutex = NULL; //互斥信号量
static xSemaphoreHandle xSemaTxDone = NULL; //二值信号量
/*********************************************************************
*
* Timing:
* GUI_X_GetTime()
* GUI_X_Delay(int)
Some timing dependent routines require a GetTime
and delay function. Default time unit (tick), normally is
1 ms.
*/
int GUI_X_GetTime(void)
{
return ((int) xTaskGetTickCount());
}
void GUI_X_Delay(int ms) //延时ms
{
vTaskDelay( ms );
}
/*********************************************************************
*
* GUI_X_Init()
*
* Note:
* GUI_X_Init() is called from GUI_Init is a possibility to init
* some hardware which needs to be up and running before the GUI.
* If not required, leave this routine blank.
*/
void GUI_X_Init(void) {
}
/*********************************************************************
*
* GUI_X_ExecIdle
*
* Note:
* Called if WM is in idle state
*/
void GUI_X_ExecIdle(void) {
GUI_X_Delay(1); //延时1ms
}
/*********************************************************************
*
* Multitasking:
*
* GUI_X_InitOS()
* GUI_X_GetTaskId()
* GUI_X_Lock()
* GUI_X_Unlock()
*
* Note:
* The following routines are required only if emWin is used in a
* true multi task environment, which means you have more than one
* thread using the emWin API.
* In this case the
* #define GUI_OS 1
* needs to be in GUIConf.h
*/
/* Init OS */
void GUI_X_InitOS(void)
{
/* 创建互斥信号量 */
xQueueMutex = xSemaphoreCreateMutex();
configASSERT (xQueueMutex != NULL);
/* 创建二值信号量 */
vSemaphoreCreateBinary( xSemaTxDone );
configASSERT ( xSemaTxDone != NULL );
}
void GUI_X_Unlock(void) //释放互斥量
{
xSemaphoreGive(xQueueMutex);
}
void GUI_X_Lock(void) //锁互斥量
{
if(xQueueMutex == NULL)
{
GUI_X_InitOS();
}
/* 获取互斥量 */
xSemaphoreTake(xQueueMutex, /* 信号量句柄 */
portMAX_DELAY);/* 阻塞等待 */
}
/* 获取任务句柄 */
U32 GUI_X_GetTaskId(void)
{
return ((U32) xTaskGetCurrentTaskHandle());
}
void GUI_X_WaitEvent (void)
{
/* 获取信号量 */
while(xSemaphoreTake(xSemaTxDone, /* 信号量句柄 */
portMAX_DELAY) != pdTRUE);/* 阻塞等待 */
}
void GUI_X_SignalEvent (void)
{
/* 给出信号量 */
xSemaphoreGive(xSemaTxDone);
}
/*********************************************************************
*
* Logging: OS dependent
Note:
Logging is used in higher debug levels only. The typical target
build does not use logging and does therefor not require any of
the logging routines below. For a release build without logging
the routines below may be eliminated to save some space.
(If the linker is not function aware and eliminates unreferenced
functions automatically)
*/
//下面三个函数主要是用来调试、输出错误信息的,用的不多
void GUI_X_Log (const char *s) { }
void GUI_X_Warn (const char *s) { }
void GUI_X_ErrorOut(const char *s) { }
/*************************** End of file ****************************/
至此,关于STemWin的相关文件修改完成,后面是关于FreeRTOS的修改。
三、FreeRTOS的移植
关于这一部分,可以参考野火关于FreeRTOS移植的教程,本人对于FreeRTOS的学习是参考野火的教程完成的。开头已经在例程模板中建了两个文件夹:FreeRTOS/src,FreeRTOS/port。我们要在这两个文件夹中添加需要移植的文件如下:
图3.1
熟悉FreeRTOS移植的用户对此应该很熟悉,首先就直接将野火的FreeRTOS教程书籍配套程序的第13个例程移植工程模板中的FreeRTOS文件夹复制到我们的裸机工程模板中。作者:ustc修竹。然后我们在例程中开始添加文件,其中上图3.1中FreeRTOS/src里面的六个文件,对应的我们添加FreeRTOS里面的六个文件,六个文件的路径:FreeRTOS->src,把这六个文件添加好以后如图3.1所示。图3.1中FreeRTOS/port的两个文件分别从不同的文件夹中选择添加的,heap_4.c文件位置:FreeRTOS->port->MenMang,这个文件夹中有五个内存管理文件,我们选择第四个;port.c文件位置:FreeRTOS->port->RVDS->ARM_CM3,从这个路径选择文件然后添加进去。至此我们所需要的文件添加完毕。在本文的开头部分添加的FreeRTOSConfig.h可以选择正点原子的也可以选择野火的,如果采用野火的头文件,则需要把bsp_usart.h修改为usart.h。
这一块的内容不管是通过野火还是正点原子又或者是其他途径学习FreeRTOS的,大致得方法都差不多。
关于系统时钟的开启,由于我们用得是正点原子得delay.c文件,所以我们要stm32f10x_it.c文件得部分代码修改一下。把三个中断在这个文件注释掉:SVC_Handler,PendSV_Handler,SysTick_Handler。其中前两个函数FreeRTOS已经为用户实现了,而正点原子的在delay.c中实现了第三个函数,所以全部注释即可,如图3.2所示:
图3.2
delay.c文件中实现系统中断的代码如下:
//systick中断服务函数
void SysTick_Handler(void)
{
if(xTaskGetSchedulerState()!=taskSCHEDULER_NOT_STARTED)//ϵͳÒѾÔËÐÐ
{
xPortSysTickHandler();
}
}
其余的像FreeRTOSConfig.h中的配置前文也说过,要将互斥信号量,计数信号量功能等功能打开。
四、关于main.c函数的修改
因为换了操作系统,所以main.c函数需要修改的地方较多,就不单独解释了,相关的信息已经在代码中详细标注了,这对于已经完成FreeRTOS学习的用户来说不难理解。
#include "led.h"
#include "delay.h"
#include "key.h"
#include "sys.h"
#include "ILI93xx.h"
#include "usart.h"
#include "24cxx.h"
#include "flash.h"
#include "touch.h"
#include "sram.h"
#include "malloc.h"
#include "GUI.h"
#include "GUIDemo.h"
#include "WM.h"
#include "DIALOG.h"
/* FreeRTOS头文件 */
#include "FreeRTOS.h"
#include "task.h"
static void AppTaskCreate(void);/* 用于创建任务 */
static void LED0_Task(void* pvParameters);/* LED1_Task任务实现 */
static void touch_task(void* pvParameters);/* touch_task任务实现*/
static void emwindemo_task(void* pvParameters);/* emwindemo_task任务实现 */
/* 创建任务句柄 */
static TaskHandle_t AppTaskCreate_Handle = NULL;
/* LED0任务句柄 */
static TaskHandle_t LED0_Task_Handle = NULL;
/* touch_task任务句柄 */
static TaskHandle_t touch_task_Handle = NULL;
/*emwindemo_task任务句柄 */
static TaskHandle_t emwindemo_task_Handle = NULL;
int main(void)
{
BaseType_t xReturn = pdPASS;/* 定义一个创建信息返回值,默认为pdPASS */
delay_init(); //延时函数初始化
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_4); //设置中断分组
uart_init(115200); //串口初始化115200
LED_Init(); //LED端口初始化
TFTLCD_Init(); //LCD初始化
KEY_Init(); //按键初始化
TP_Init(); //触摸屏初始化
FSMC_SRAM_Init(); //SRAM初始化
// TIM3_Int_Init(999,71); //1KHZ定时器,这是裸机用的
// TIM6_Int_Init(999,719); //10ms中断,这是裸机用的
my_mem_init(SRAMIN); //初始化内部内存池
my_mem_init(SRAMEX); //初始化外部内存池
/* 创建AppTaskCreate任务 */
xReturn = xTaskCreate((TaskFunction_t )AppTaskCreate, /* 任务入口函数 */
(const char* )"AppTaskCreate",/* 任务名字 */
(uint16_t )256, /* 任务栈大小 */
(void* )NULL,/* 任务入口函数参数 */
(UBaseType_t )6, /* 任务的优先级 */
(TaskHandle_t* )&AppTaskCreate_Handle);/* 任务控制块指针 */
/* 启动任务调度 */
if(pdPASS == xReturn)
vTaskStartScheduler(); /* 启动任务,开启调度 */
else
return -1;
//ustc修竹
while(1);
}
static void AppTaskCreate(void)
{
BaseType_t xReturn = pdPASS;/* 定义一个创建信息返回值,默认为pdPASS */
RCC_AHBPeriphClockCmd(RCC_AHBPeriph_CRC,ENABLE);//开启CRC时钟
WM_SetCreateFlags(WM_CF_MEMDEV);//使用自动存储设备
GUI_Init(); //STemWin初始化
taskENTER_CRITICAL(); //进入临界区
/* 创建LED0_Task */
xReturn = xTaskCreate((TaskFunction_t )LED0_Task, /* 任务入口函数 */
(const char* )"LED0_Task",/* 任务名字 */
(uint16_t )128, /* 任务栈大小 */
(void* )NULL, /* 任务入口函数参数 */
(UBaseType_t )4, /* 任务的优先级 */
(TaskHandle_t* )&LED0_Task_Handle);/* 任务控制块指针 */
if(pdPASS == xReturn)
/* 创建touch_task */
xReturn = xTaskCreate((TaskFunction_t )touch_task, /* 任务入口函数 */
(const char* )"touch_task",/* 任务名字 */
(uint16_t )256, /* 任务栈大小 */
(void* )NULL, /* 任务入口函数参数 */
(UBaseType_t )5, /* 任务的优先级 */
(TaskHandle_t* )&touch_task_Handle);/* 任务控制块指针 */
if(pdPASS == xReturn)
/* 创建emwindemo_task */
xReturn = xTaskCreate((TaskFunction_t )emwindemo_task, /* 任务入口函数 */
(const char* )"emwindemo_task",/* 任务名字 */
(uint16_t )2048, /* 任务栈大小 */
(void* )NULL, /* 任务入口函数参数 */
(UBaseType_t )3, /* 任务的优先级 */
(TaskHandle_t* )&emwindemo_task_Handle);/* 任务控制块指针 */
if(pdPASS == xReturn)
vTaskDelete(AppTaskCreate_Handle); //删除AppTaskCreate任务
taskEXIT_CRITICAL(); //退出临界区
//ustc修竹
}
//EMWINDEMO任务
void emwindemo_task(void *p_arg)
{
while(1)
{
GUIDEMO_Main();
}
}
//TOUCH任务
void touch_task(void *p_arg)
{
while(1)
{
GUI_TOUCH_Exec();
vTaskDelay(5); //延时5ms
}
}
static void LED0_Task(void* parameter)
{
while (1)
{
LED0 = !LED0;
vTaskDelay(500); /* 延时500个tick */
}
}
至此,可以对该工程进行编译了。但是值得注意的是,在编译后系统可能会提示堆栈不够用的情况然后报错,这个时候我们需要修改一个头文件的内容,该头文件是malloc.h,找到该头文件后对其修改内容如下:
图4.1
malloc.h中第31行,一般默认是40*1024,我们需要修改一下,一般根据经验修改成20*1024即可正常编译。最后的编译结果如图4.2:
图4.2
我们希望跑例程,我们在main.c中的emwindemo_task任务里面跑我们想跑的例程,同时在例程的EMWIN_Demo添加我们要跑的文件,如图4.3所示:
图4.3
因为我们是在正点原子的裸机模板上移植的RTOS,所以例程在模板中已经添加了,直接复制上面的main.c的代码即可运行例程。
最后,如果还有报错,请考虑头文件有没有添加完整,关于FreeRTOS的头文件路径不能少了,相关的路径如图4.4所示:
图4.4
将程序烧录进开发板以后,应该与正点原子的UCOSIII系统所展示的结果是一样的。至此移植完成。
五、补充内容(可以不看,与移植操作系统无关了)
正点原子用的版本是5.26,相比于野火用的5.44版本来说,有很多功能都没有。如果想在正点原子的板子上跑5.44版本的emWin,可以直接把STemWin526_CM3_OS_keil.lib从例程模板中删掉,换成野火的STemWin_CM3_OS_wc16_ARGB.a。但是直接编译以后会报错,需要完成下面两步操作才能正常编译:
1、右键STemWin_CM3_OS_wc16_ARGB.a文件options,将文件类型从"File Type" 改至 "Library file";
2、在Target中把USE MicroLIB勾选上。
在完成以上两步操作以后,重新编译即可使用5.44版本的一些功能了。但是使用的时候编译会出现警告,一般是警告部分函数未在头文件定义。下面是原因的解释:
图5.1
如图5.1所示,这是笔者本人自己编写的一段代码,主要在文本控件里面实现一个数值显示功能。如果使用第170行的TEXT_SetDec函数,会发现可以编译而且烧录了可以使用,但是编译会出现警告,原因在于TEXT.h中并没有声明这个函数,所以在TEXT.h中声明一下这个函数,编译以后警告就会消失。
图5.2
图5.3
图5.2是5.26版本的TEXT.h的声明内容,在其中加入TEXT_SetDec函数的声明如图5.3报错即可解决。当然,也可以直接用野火的5.44版本的头文件,但是如果全都用野火的,在正点原子的模板上又可能带来新的问题,所以本人倾向于需要用到什么功能就去查一下手册然后5.26没有的功能,就去头文件声明一下子,这样子省去了重新移植5.44版本的麻烦,大多数需求5.26版本的stemwin已经可以完成了。至于本人为什么这样子做,因为本人用的是正点原子的开发板,如果重新修改液晶屏驱动,笔者觉得比较麻烦,又或者重新移植5.44版本的emwin笔者也觉得麻烦。
以上就是关于FreeRTOS移植的方法以及笔者个人的理解,如有疏漏,欢迎评论留言。