uC/OS ii文件体系结构:
1:核心部分(OSCore.c)
这部分代码是操作系统的核心,包括操作系统的初始化、操作系统运行、中断进出的前导、时钟节拍、任务调度、事件处理等多部分。能够维持系统基本工作的部分都在这里。
2:任务处理部分(OSTask.c)
任务处理部分中的代码内容都是与任务的操作密切相关的,包括任务的建立、删除、挂起、恢复等待。因为uC/OS ii是以任务为基本单位进行调度的,所以这部分内容相当重要。
3:时钟部分(OSTime.c)
uC/OS ii的最小时钟单位是timetick(时钟节拍)。任务延时等操作就是在这里完成。
4:任务同步与通信部分
这部分代码为事先处理部分(需要在OS_cfg.h文件中进行功能选择使能),包括信号量、邮箱、邮箱队列、事件标志等部分,主要用于任务间的相互联系和对临界资源的访问。
5:CPU的接口部分(Ports)--需要移植的代码部分
这部分是需要根据具体的CPU来做移植,且这部分由于很多涉及到具体硬件,所以很多都是用汇编来写。主要包括中断级任务切换的底层实现、任务级任务切换的底层实现、时钟节拍的产生和处理、中断的相关部分等。
uC/OS ii移植需要满足的条件:
1:处理器支持中断,并能够产生定时中断(通常在10~1000HZ)
uC/OS ii是通过中断(软)来实现任务现场的切换,做到任务的切换;uC/OS ii中需要时基函数来进行任务的延时等(uC/OS iii支持时间片轮转,所以时基还用来做任务切换),M3中有一个硬件的systick定时器,专门用来产生定时中断。
2:在程序中可以打开或者关闭中断
uC/OS ii中,打开/关闭中断主要通过OS_ENTER_CRITICAL()或OS_EXIT_CRITICAL()两个宏实现,在任务的切换时,是需要关中断的(进入临界区)。
3:处理器的C编译器能产生可重入代码
uC/OS ii中,大部分函数都是用C编写的,所以编译时,需要产生可重入的代码(可以被一个以上的任务调用),可重入代码中不能使用全局变量,且可重入代码中的局部变量一般使用CPU的通用寄存器或堆栈(这个需要编译器支持),所以对编译器是有要求的。
4:处理器需具备一定量数据的硬件堆栈
uC/OS ii中,有一些系统使用的数据结构(各种列表来管理系统的资源)需要一定的RAM空间,同时任务切换、局部变量等都需要使用堆栈,所以对RAM(堆栈也有要求),M3中,应用程序(PSP)与异常服务程序(MSP)使用的堆栈指针是不一样的,这样起保护作用。
5:处理器有将堆栈指针和其他CPU寄存器 存储与读出到内存(堆栈)的指令
uC/OS ii中,任务切换时一般要读取CPU的各个通用与特殊功能寄存器,M3用MRS、MSR等命令。
uC/OS ii的移植要点:
uC/OS ii的移植相当LINUX来说是非常简单的,uC/OS ii的移植涉及到的代码很少;我们只需要修改与处理器相关的代码即可;
OS_CPU.H:设置与处理器与编译器有关的代码
OS_CPU_C.C:在这其中用C语言编写6个与操作系统相关的函数
OS_CPU.ASM:在这其中用汇编语言编写4个与处理器相关的函数
如上图,需要移植的部分就是Ports路径下的几个代码文件,其他的代码都不要移植(os_cfg.h只是用来配置系统的各个功能,做系统的裁剪而且,ucos_ii.h中提供系统所有的接口函数,os_cpu_a.asm就是OS_CPU.ASM)
1:修改OS_CPU.H
OS_CPU.H中定义了数据类型、处理器的堆栈数据类型的字长、堆栈增长方向、任务切换宏、临界区访问处理。
从方便移植的角度,uC/OS ii使用的数据类型,都是自己定义的那一套。
typedef unsigned char BOOLEAN;
typedef unsigned char INT8U;
typedef signed char INT8S;
typedef unsigned short INT16U;
typedef signed short INT16S;
typedef unsigned long INT32U;
typedef signed long INT32S;
typedef float FP32;
typedef double FP64;
typedef unsigned int OS_STK;
typedef unsigned int OS_CPU_SR;
M3使用的是向下生长的满栈,堆栈指针指向最后一个被压入堆栈的32位整数:
#define OS_STK_GROWTH 1
定义开关中断的宏:
#define OS_CRITICAL_METHOD 3
#if OS_CRITICAL_METHOD == 3
#define OS_ENTER_CRITICAL() {cpu_sr = OS_CPU_SR_Save();}
#define OS_EXIT_CRITICAL() {OS_CPU_SR_Restore(cpu_sr);}
#endif
定义宏OS_TASK_SW执行任务切换:#define OS_TASK_SW() OSCtxSw()
2:在OS_CPU_C.C中要求用户编写10个简单的C函数唯一必要修改的函数是OSTaskStkInit(),其余的函数必须声明,但并不一定要包含任何代码
OSTaskStkInit():任务创建函数OSCreate()与OSCreateExt()通过调用OSTaskStkInit,初始化任务的栈结构,因此,任务的堆栈看起来就像刚发生了中断一样(所以的寄存器按一定的次序都保存在堆栈中,为什么要这样做?因为任务间的切换就是任务堆栈中保存的CPU现场的切换,所以任务被创建时,要初始化它的堆栈,为后面的切换做准备)
OSTaskCreateHook(ptcb):系统提供给用户的钩子函数任务创建时OSCreate()与OSCreateExt()调用OS_TCBInit,OS_TCBInit会调用OSTaskCreateHook,同时传递指向刚刚建立的任务的任务控制块的指针,这样OSTaskCreateHook就可以访问任务控制块结构的所有成员,这个函数的功能有限,不是必要的函数,且需要通过OS_APP_HOOKS_EN使能,且该函数调用时中断是开着的。
OSTaskDelHook(ptcb):OSTaskDel()会调用OSTaskDelHook(ptcb),即在将任务从uC/OS II的内部有效链表中删除之前被调用,同时传递一个指向被删除任务的任务控制块的指针,OSTaskDelHook可以用来检验TCB扩展部分是否已建立(一个非空指针),并可进行一些清0操作,OSTaskDelHook调用时,中断是关闭的,函数太长会影响中断响应时间。
OSTaskSwHook():在任务切换时会调用OSTaskSwHook,不论是在OSCtxSw()还是OSIntCtxSw(),都会调用该函数,OSTaskSwHook可访问全局变量OSTCBCur(指向将被切换出去的任务的任务控制块)、OSTCBHiggRdy(指向新任务的任务控制块) ,OSTaskSwHook被调用时,中断是关闭的,函数太长会影响中断响应时间。
OSTaskIdleHook():很多微处理器都允许执行相应的指令,将CPU置于低功耗模式,而当接收到中断信号时,CPU就会自动退出低功耗模式。OS_TaskIdle()函数可以调用OSTaskIdleHook()来实现CPU的这种低功耗模式很好。
OSTaskStatHook():该函数每秒都会被统计任务OS_TaskStat()统计任务每秒执行一次调用一次,可以通过该函数扩展统计任务的功能,如可跟踪并显示每个任务的执行时间、每个任务所用的CPU份额以及每个任务执行的频率等等,该函数没有任何参数。
OSTimeTickHook():该函数在每个时钟节拍都会被OSTimeTick()调用;systick中断服务程序SysTickHandler调用OSTimeTickHook,在这里面用户可以处理应急的事务。
OSInitHookBegin():进入OSInit()函数后,OSInitHookBegin就会被调用,添加这个函数的原因在于想把与OS有关的初始化代码也放在OSInti()函数中,这个函数使得用户可以将自己特定的代码也放在OSInit()函数中。
OSInitHookEnd():OSInitHookEnd与OSInitHookBegin相似,只是他在OSInit()函数返回之前被调用,添加这个函数的原因与OSInitHookBegin是一样的。
OSTCBInitHook(ptcb):任务创建时OSCreate()与OSCreateExt()调用OS_TCBInit,OS_TCBInit会在调用OSTaskCreateHook前调用 OSTCBInitHook ,用户可以在OSTCBInitHook函数中做一些与初始化控制块OS_TCB有关的处理,在OSTaskCreateHook中做一些与初始化任务有关的处理;OSTCBInitHook收到的ptcb参数指向新添加任务的任务控制块的指针,而这个新添加任务的任务控制块绝大部分已经初始化完成,但是还没有链接到已经建立任务的链表中。
OS_STK * OSTaskStkInit (void (*task)(void *p_arg),
void *p_arg,
OS_STK *ptos,
INT16U opt)
{
OS_STK *stk;
(void)opt;
stk = ptos;
*(stk) = (INT32U)0x01000000L; //xPSR
*(--stk) = (INT32U)task; //Entry Point
*(--stk) = (INT32U)0xFFFFFFFEL; // R14(LR) (init value will cause fault if ever used)
*(--stk) = (INT32U)0x12121212L; // R12
*(--stk) = (INT32U)0x03030303L; // R3
*(--stk) = (INT32U)0x02020202L; // R2
*(--stk) = (INT32U)0x01010101L; // R1
*(--stk) = (INT32U)p_arg; // R0 : argument
//Remaining registers saved on process stack
*(--stk) = (INT32U)0x11111111L; // R11
*(--stk) = (INT32U)0x10101010L; // R10
*(--stk) = (INT32U)0x09090909L; // R9
*(--stk) = (INT32U)0x08080808L; // R8
*(--stk) = (INT32U)0x07070707L; // R7
*(--stk) = (INT32U)0x06060606L; // R6
*(--stk) = (INT32U)0x05050505L; // R5
*(--stk) = (INT32U)0x04040404L; // R4
return (stk);
}
3:在OS_CPU_A.ASM中用汇编写4个与处理器相关的函数
需要移植的函数有,如下所示,
OSStartHighRdy():在调用OSStart()函数时,使就绪态任务中优先级最高的任务开始运行
OSCtxSw():
OSIntCtxSw():
OSTickISR():
OS_CPU_SR_Save()
OS_CPU_SR_Restore():
OSCtxSw():可以参考学习笔记中的记录,M3中任务切换还需实现软中断的处理函数
OSCtxSw ;悬起PSV异常
LDR R0, =NVIC_INT_CTRL
LDR R1, =NVIC_PENDSVSET
STR R1, [R0]
BX LR
OSIntCtxSw():在退出中断前调用OSIntCtxSw,在ISR中执行任务的切换功能,OSIntCtxSw与OSCtxSw基本相同,M3的是一模一样的。
OSPendSV 软中断的处理函数
MRS R0, PSP
CBZ R0, OSPendSV_nosave
SUBS R0, R0, #0x20
STM R0, {R4-R11}
LDR R1, __OS_TCBCur
LDR R1, [R1]
OSPendSV_nosave
PUSH {R14}
LDR R0, __OS_TaskSwHook ; OSTaskSwHook();
BLX R0
POP {R14}
LDR R0, __OS_PrioCur
LDR R1, __OS_PrioHighRdy
LDRB R2, [R1]
STRB R2, [R0]
LDR R0, __OS_TCBCur
LDR R1, __OS_TCBHighRdy
LDR R2, [R1]
STR R2, [R0]
LDR R0, [R2]
LDM R0, {R4-R11}
ADDS R0, R0, #0x20
MSR PSP, R0
ORR LR, LR, #0x04
BX LR
OSTickISR在这里即SysTickHandler函数
void SysTickHandler(void)
{
OS_CPU_SR cpu_sr;
OS_ENTER_CRITICAL();
OSIntNesting++;
OS_EXIT_CRITICAL();
OSTimeTick(); //主要判断延时的任务是否计时到
OSIntExit(); //在os_core.c文件里定义,如果有更高优先级的任务就绪了,则执行一次任务切换
}
OSStartHighRdy
LDR R0, =NVIC_SYSPRI2
LDR R1, =NVIC_PENDSV_PRI
STRB R1, [R0] //这几句是设置PendSV优先级
MOVS R0, #0
MSR PSP, R0 //设置PSP的初始值0(在第一次PendSV中断服务程序中不用保存CPU的各寄存器)
LDR R0, __OS_Running
MOVS R1, #1
STRB R1, [R0] // OSRunning = TRUE,表明操作系统已经运行
LDR R0, =NVIC_INT_CTRL
LDR R1, =NVIC_PENDSVSET
STR R1, [R0] //触发PendSV中断,暂时挂起
CPSIE I //开总中断一定要在这里才开总中断,如果在此之前任何一个地方开中断,系统一旦响应中断,很有可能造成系统因没有做出足够的准备而出错(比如如果在初始化systicks时就开启了中断,uC/OS此时还处于未知阶段,就会崩溃),所在在这里开启总中断才是安全的
OS_CPU_SR_Save()
MRS R0, PRIMASK ;保存全局中断标志
CPSID I ;关中断
BX LR
OS_CPU_SR_Restore()
MSR PRIMASK, R0 ;恢复全局中断标志
BX LR
到此移植完毕
---------下面谈谈uCGUI的移植------------------------------------------------------------------------------------------------------------------
μc/gui与
uC/OS出于同一公司,是美国micrium公司出品的一款针对嵌入式系统的优秀图形软件。它是为任何使用lcd图形显示的应用提供高效的独立于处理器及lcd控制器而设计的图形用户接口,它适用单任务或是多任务系统环境。架构基于模块化设计,由不同的模块中的不同层组成。包括液晶驱动模块,内存设备模块,窗口系统模块,窗口控件模块,反锯齿模块和触摸屏及外围模块。其主要特性包括丰富图形库,多窗口、多任务机制,窗口管理及丰富窗口控件类(按钮、检验框、单/多行编辑框、列表框、进度条、菜单等),多字符集和多字体支持,多种常见图像文件支持,鼠标、触摸屏支持,灵活自由配制等特性。
如下图,uCGUI中,有基于emWin与uCGUI的DEMO,其实两个很类似;INC目录下包含了所有图形库提供的功能函数接口,直接调用就好了,工程中图形库是以lib方式给出的。
参考uCGUI中文手册
1:首先通过网络上相关资料的介绍,大致清楚UCGUI的整个框架与需要移植的地方。
2:结合uCGUI中文手册与自己的开发板具体进行移植。
///////////////////////////////////////
1:指针光标输入设备(触摸屏)
指针光标输入设备包括鼠标和触摸屏。 它们共用一组通用的指针光标输入设备(PID)函数使得鼠标和触摸屏能同时起作用。 该函数一般由视窗管理器自动地调用,如前所述的那样,起刷新显示屏的作用。 如果视窗管理器未使用,你的应用程序要负责调用PID 函数。
//////////////////////////////下面的内容是整理自网络//////////////////////////////////////////
0:准备工作
在移植之前,首先要了解在网上下的UCGUI 3.90源码包,且需要先写好裸机下成功驱动LCD的驱动代码。
1)“tool文件夹” 基本都是字体和模板查看之类的.
2)“sample文件夹” 是实例代码和模版配置头文件,如跟操作系统有关的GUI_X或者一些模板
3)“Start文件夹” 是uCGUI的源代码包
Config,配置文件;
GUI/AntiAlias,抗锯齿支持;
GUI/ConvertMono,用于B/W(黑白两色)及灰度显示的色彩转换程序;
GUI/ConvertColor,用于彩色显示的色彩转换的程序;
GUI/Core ??C/GUI,内核文件;
GUI/Font,字体文件;
GUI/LCDDriver,LCD 驱动;
GUI/Mendev,存储器件支持;
GUI/Touch,触摸屏支持;
GUI/Widget,视窗控件库;
GUI/WM,视窗管理器;
1: 定义/修改GUIConf.h、LCDConf.h 、GUITouchConf.h
GUIConf.h : μc/GUI功能模块、动态存储空间(用于内存设备和窗口对象)大小、默认字体设置等 基本GUI预定 义控制的定义
在图形接口的配置文件GUIconf.h 的默认设置中,窗口
管理、存储器支持、触摸屏等功能被屏蔽。默认字体设置为
GUI_Font6x8。uC/GUI 同样支持汉字的显示,使用生成汉字
字库的软件,可以将Windows 操作系统内的任何字体字库转
换汉字字库文件,也可以根据实际显示的需要,将所需显示
的汉字建立一个自定义字库,添加到uC/GUI 中。文中选用
的汉字字库文件为hzk16s.c(16×16 点阵的宋体汉字字库),
uC/GUI 便支持16×16 点阵宋体汉字的显示。
#define GUI_OS (1) //多任务
#define GUI_SUPPORT_TOUCH (0) //触摸
#define GUI_SUPPORT_UNICODE (1) //Unicode支持
#define GUI_DEFAULT_FONT &GUI_Font6x8 //GUI默认字体
#define GUI_ALLOC_SIZE 5000 //动态内存的大小
#define GUI_WINSUPPORT 1 //窗口控件支持
#define GUI_SUPPORT_MEMDEV 1 //支持内存设备
#define GUI_SUPPORT_AA 1
LCDConf.h:LCD大小、控制器类别、总线宽度、颜色选取等LCD参数控制文件
#define LCD_XSIZE (160) //配置TFT的水平分辨率
#define LCD_YSIZE (128) //配置TFT的垂直分辨率
#define LCD_CONTROLLER (54124) //TFT控制器的名称
#define LCD_BITSPERPIXEL (16) //每个像素的位数
#define LCD_FIXEDPALETTE (565) //调色板格式
#define LCD_SWAP_RB (0) //红蓝反色交换
#define LCD_INIT_CONTROLLER() TFT_Init() ; //此处需要定义的是你的TFT初始化函数
GUITouchConf.h:配备触摸屏,根据触摸屏及其控制芯片编制以下几个函数
void TOUCH_X_ActivateX (void);// 准备Y轴数据测量
void TOUCH_X_ActivateY (void);// 准备X轴数据测量
int TOUCH_X_MeasureX(void); // 根据AD转换结果返回X的值
int TOUCH_X_MeasureY(void); // 根据AD转换结果返回Y的值
以上几个函数在GUI_TOUCH_Exec()会被调用。
GUI/CORE/LCD_ConfDefaults.h文件内可以找到所有囊括LCD配制默认选项,包括LCD屏个数,控制器个数 ,调色板,屏幕反向设置等众多配制选项。
2:LCD配置内容与驱动接口
对于自带控制器的LCD液晶屏,通过LCDConf.h文件中的总线接口和寄存器接口对硬件接口进行配置与定义;
对于片上已经集成了LCD控制器的控制器,通过对片内LCD控制器的寄存器设置来配置LCD各接口信号;
一般LCD的接口信息配置包括VFRAME帧同步信号,VLINE线同步脉冲信号,VCLK象素时钟信号,VM信号和数据位不等的象素点数据输出信号。
3:LCD驱动底层实现
LCD驱动编程的实质是液晶屏上的点对应的显存编程,最底层调用函数为画点函数,用户可根据LCD屏的驱动控制器的实际情况(通过总线接口、寄存器接口或LCD控制器的寄存器来操作)来实现底层驱动。
_SetPixel(),_GetPixel(),XorPixel()为最底层直接对显存操作函数。
uc/GUI提供部分控制器驱动,文件为GUI/LCDDriver/LCDSLin.c,如sed1335,T6963等简单LCD控制器。核心函数为LCD_Write(). _SetPixel()调用LCD_Write()写显存。 uC/GUI 的图形库应用程序、汉字显示、窗口等功能的实
现,都建立在底层的LCD 驱动和画点功能的基础上,uC/GUI
的移植,必须先完成 LCD 的驱动,以及画点、线等基本功
能。控制器TFT3224 在驱动液晶显示时,只需设置好显示的
行列坐标,向显示寄存器写入显示数据,便实现显示功能,
而无需计算显示存储器和显示区域的地址
画点程序如下:
static void _SetPixel(int x, int y, LCD_PIXELINDEX c)
{
Y_ADDR = y;
X_ADDR = x;
CMD = ((x/256)<<2) | (y/256); //控制寄存器CMD 存放行列坐标的高位数据
DAT = c; //数据寄存器DAT
}
让GUI能够找到你的LCD驱动,修改LCDDriver。
在编写你的TFT底层驱动程序的时候,一定不能忘记编写这两个函数:设置一个像素和获取一个像素的颜 色,因为后面很多UCGUI和你的TFT驱动关联起来需要这两个函数为前提。以我的TFT为例:
void LCD_SetPixel(unsigned short x, unsigned short y, unsigned short color)
{
}
unsigned short LCD_GetPixel(unsigned short x, unsigned short y)
{
}
uC/GUI 的验证程序如下:
…
GUI_SetColor(GUI_WHITE); //设置前景色为白色
LCD_L0_DrawHLine(0,100,100); //绘制水平直线
GUI_InitLUT(); //初始化调色板
GUI_DrawBitmap(&bm2,0,0); //绘制位图,位图文件为2.c
GUI_SetFont(&GUI_FontHZ_Song_16);//设置当前字体16×16 宋体
GUI_DispStringAt("uC/GUI 移植实现",10,40); //显示汉字
…
这两个函数写好,并测试可以正常显示后,接下来需要修改TFT与UCGUI关联的函数(在一个C文件下(我的是ili9320_ucgui.c),建议大家在移植GCGUI之前最好下载个别人移植好的例程,对着修改这样难度降低很多): int LCD_L0_Init(void)
{
TFT_Init();
return 0;
}
这个函数,是GCGUI初始化TFT需要调用的,里面的函数就是你TFT底层驱动的初始化函数。
void LCD_L0_SetPixelIndex(int x, int y, int PixelIndex)
{
LCD_SetPixel(x,y,PixelIndex);
}
unsigned int LCD_L0_GetPixelIndex(int x, int y)
{
return LCD_GetPixel(x,y);
}
上面两个函数是其他函数的最基本元素,其他函数比如画线、点、圆都需要调用改函数。里面的LCD_SetPixel(x,y,PixelIndex)、LCD_GetPixel(x,y);就是你TFT底层驱动设置像素和获取像素的两个函数。
紧接着把该ili9320_ucgui.c文件下其他函数修改一下(凡是遇到设置像素和获取像素的地方用以上两个函数代替。)
接着,修改另一个C文件(ili9320_api.c)这里修改很简单,仅仅也是将设置像素和获取像素的函数替换掉自己定义的即可。
4:GUI_X文件夹内包括有与硬件联系紧密的文件
GUI_X文件夹下:
GUI_X.C——无操作系统情况
GUI_X_embOS.c——嵌入式操作系统
GUI_X_ucos.c——UCOS环境下
GUI_X.c包括大部分与硬件的关联函数,如定时器的初始化和触摸屏相关函数。
uc/GUI与操作系统挂接的核心是定时器的设置和挂接。
uc/GUI是通过延时函数GUI_Delay()调用GUI_X_Delay,再调用GUI_Exec()处理窗口部件中的回调函数进行重绘。在任何一款嵌入式操作系统中都需要定时器的心脏跳动作用,支持OS的uc/GUI可以通过定时器的设置达到嵌入式操作系统和图形系统的实时和同步操作。在GUI_X_uCos.c中通过uc/os中的延时程序同μc/GUI挂接实现整合。
4:移植完毕
经过移植之后,GUI应用程序开发通过uc/GUI而变得非常容易,在调用GUI_Init()后,用户可以根据需要正确配制uc/GUI后,可使用其强大的库函数和丰富的GUI资源进行编程。在GUI编程过程中,可以打开抗锯齿功能减小图形失真,得到高质量的图形和字体效果。采用内存设备能有效克服闪烁现象,获得更快的显示速度,但它和抗锯齿功能一样需要额外的内存开销。