IACMall工业自动化(中国)商城--论坛

首页 » 技术交流专区 » 嵌入式系统 » 嵌入式系统:uC/OS-ii 专题开放!(强烈推荐)
dm15 - 2008-5-3 20:16:00
uC/OS是面向中小型嵌入式系统的,它占用空间少、执行效率高、实时性好,且针对新处理器的移植相对简单!同时源码开放、网络资源丰富、简单易学!特别对于想要从单片机转到嵌入式系统的朋友来说uC/OS是一种不错的选择!
junbao99 - 2008-5-3 20:17:00
欢迎大家,在这里一起讨论学习,共享学习资源!
uranas - 2008-5-3 20:17:00
uC/OS-II实时操作系统在嵌入式平台上进行移植的一般方法和技巧


---实时操作系统的使用,能够简化嵌入式系统的应用开发,有效地确保稳定性和可靠性,便于维护和二次开发。

��C/OS-II是一个基于抢占式的实时多任务内核,可固化、可剪裁、具有高稳定性和可靠性,除此以外,��C/OS-II的鲜明特点就是源码公开,便于移植和维护。

在��C/OS-II官方的主页上可以查找到一个比较全面的移植范例列表。但是,在实际的开发项目中,仍然没有针对项目所采用芯片或开发工具的合适版本。那么,不妨自己根据需要进行移植。

本文则以在TMS320C6711 DSP上的移植过程为例,分析了��C/OS-II在嵌入式开发平台上进行移植的一般方法和技巧。��C/OS-II移植的基本步骤

在选定了系统平台和开发工具之后,进行��C/OS-II的移植工作,一般需要遵循以下的几个步骤:

● 深入了解所采用的系统核心
● 分析所采用的C语言开发工具的特点
● 编写移植代码
● 进行移植的测试
● 针对项目的开发平台,封装服务函数
(类似80x86版本的PC.C和PC.H)

系统核心

无论项目所采用的系统核心是MCU、DSP、MPU,进行��C/OS-II的移植时,所需要关注的细节都是相近的。

首先,是芯片的中断处理机制,如何开启、屏蔽中断,可否保存前一次中断状态等。还有,芯片是否有软中断或是陷阱指令,又是如何触发的。

此外,还需关注系统对于存储器的使用机制,诸如内存的地址空间,堆栈的增长方向,有无批量压栈的指令等。

在本例中,使用的是TMS320C6711 DSP。这是TI公司6000系列中的一款浮点型号,由于其时钟频率非常高,且采用了超常指令字(VLIW)结构、类RISC指令集、多级流水等技术,所以运算性能相当强大,在通信设备、图像处理、医疗仪器等方面都有着广泛的应用。

在C6711中,中断有3种类型,即复位、不可屏蔽中断(NMI)和可屏蔽中断(INT4-INT15)。可屏蔽中断由CSR寄存器控制全局使能,此外也可用IER寄存器分别置位使能。而在C6711中并没有软中断机制,所以��C/OS-II的任务切换需要编写一个专门的函数实现。

此外,C6711也没有专门的中断返回指令、批量压栈指令,所以相应的任务切换代码均需编程完成。由于采用了类RISC核心,C6711的内核结构中,只有A0-A15和B0-B15这两组32bit的通用寄存器。

C语言开发工具

无论所使用的系统核心是什么,C语言开发工具对于��C/OS-II是必不可少的。
最简单的信息可以从开发工具的手册中查找,比如:C语言各种数据类型分别编译为多少字节;是否支持嵌入式汇编,格式要求怎样;是否支持“interrupt”非标准关键字声明的中断函数;是否支持汇编代码列表(list)功能,等等。

上述的这样一些特性,会给嵌入式的开发带来很多便利。TI的C语言开发工具CCS for C6000就包含上述的所有功能。

而在此基础上,可以进一步地弄清开发工具的一些技术细节,以便进行之后真正的移植工作。

首先,开启C编译器的“汇编代码列表(list)”功能,这样编译器就会为每个C语言源文件生成其对应的汇编代码文件。

在CCS开发环境中的方法是:在菜单“/Project/Build options”的“Feedback”栏中选择“Interlisting:Opt/C and ASM(-s)”;或者,也可以直接在CCS的C编译命令行中加上“-s”参数。

然后分别编写几个简单的函数进行编译,比较C源代码和编译生成的汇编代码。例如:

void FUNC_TEMP (void)
{
Func_tmp2(); //调用任一个函数
}
在CCS中编译后生成的ASM代码为:
.asg B15, SP // 宏定义
_FUNC_TEMP:
STW B3,*SP--(8) // 入栈
NOP 2
CALL _ Func_tmp2 //-----------
MVKL BACK, B3 // 函数调用
MVKH BACK, B3 //-----------
NOP 3
BACK: LDW *++SP(8),B3 // 出栈
NOP 4
RET B3 // 函数返回
NOP 5

由此可见,在CCS编译器的规则中,B15寄存器被用作堆栈指针,使用通用存取指令进行栈操作,而且堆栈指针必须以8字节为单位改变。

此外,B3寄存器被用来保存函数调用时的返回地址,在函数执行之前需要入栈保护,直到函数返回前再出栈。

当然,CCS的C编译器对于每个通用寄存器都有约定的用途,但对于��C/OS-II的移植来说,了解以上信息就足够了。

最后,再编写一个用“interrupt”关键字声明的函数:

interrupt void ISR_TEMP (void)
{
int a;
a=0;
}
生成的ASM代码为:
_ISR_TEMP:
STW B4,*SP--(8) // 入栈
NOP 2
ZERO B4 //---------
STW B4,*+SP(4) // a=0
NOP 2 //----------
B IRP // 中断返回
LDW *++SP(8),B4 // 出栈
NOP 4

与前一段代码相比,对于中断函数的编译,有两点不同:

● 函数的返回地址不再使用B3寄存器,相应地也无需将B3入栈。(IRP寄存器能自动保存中断发生时的程序地址)

● 编译器会自动统计中断函数所用到的寄存器,从而在中断一开始将他们全部入栈保护——例如上述程序段中,只用到了B4寄存器。

编写移植代码

在深入了解了系统核心与开发工具的基础上,真正编写移植代码的工作就相对比较简单了。

��C/OS-II自身的代码绝大部分都是用ANSI C编写的,而且代码的层次结构十分干净,与平台相关的移植代码仅仅存在于OS_CPU_A.ASM、OS_CPU_C.C以及OS_CPU.H这三个文件当中。

在移植的时候,结合前面两个步骤中已经掌握的信息,基本上按照《嵌入式实时操作系统��C/OS-II》一书的相关章节的指导来做就可以了。

但是,由于系统核心、开发工具的千差万别,在实际项目中,一般都会有一些处理方法上的不同,需要特别注意。以C6711的移植为例:

● 中断的开启和屏蔽的两个宏定义为:
#define OS_ENTER_CRITICAL() Disable_int()
#define OS_EXIT_CRITICAL() Enable_int()

Disable_int和Enable_int是用汇编语言编写的两个函数。在这里使用了控制状态寄存器(CSR)的一个特性——CSR中除了控制全局中断的GIE位之外,还有一个PGIE位,可用于保存之前的GIE状态。

因此在Disable_int中先将GIE的值写入PGIE,然后再将GIE写0,屏蔽中断。而在Enable_int中则从PGIE读出值,写入GIE,从而回复到之前的中断设置。

这样,就可以避免使用这两个宏而意外改变了系统的中断状态——此外,也没有使用堆栈或局部变量,比原作者推荐的方法要好。

● 任务的切换:
前文说过,C6711中没有软中断机制,所以任务的切换需要用汇编语言自行编写一个函数_OSCtxSw来实现,并且
#define OS_TASK_SW() OSCtxSw()
在C6711中需要入栈保护的寄存器包括A0-A15、B0-B15、CSR、IER、IRP和AMR,这些再加上当前的程序地址构成一个存储帧,需要入栈保存。
_OSCtxSw函数中,需要像发生了一次中断那样,将上述存储帧入栈,然后获取被激活任务的TCB指针,将其存储帧的内容弹出,从而完成任务切换。
需要特别注意的是,在这里OS_TASK_SW是作为函数调用的,所以如前文所述,调用时的当前程序地址是保存在B3寄存器中的,这也就是任务重新激活时的返回地址。

● 中断的编写:
如前文所述,如果用“interrupt”关键字声明函数,CCS在编译时,会自动将该函数中使用到的寄存器入栈、出栈保护。
但是,这会导致各种中断发生时,出入栈的内容各不相同。这对于��C/OS-II是会引起严重错误的。因为��C/OS-II要求中断发生时的入栈操作使用和发生任务切换时完全一样的存储帧结构。
因此,在移植时、基于��C/OS-II进行开发时,都不应当使用“interrupt”关键字,而应用如下结构编写中断函数:

void OSTickISR (void)
{
DSP_C6x_Save(); // 服务函数,入栈
OSIntEnter();
if (OSIntNesting == 1) // v2.51版本新增加
{
OSTCBCur->OSTCBStkPtr
=(OS_STK*) DSP_C6x_GetCurrentSP(); // 服务函数
} // 获取当前SP的值
// 允许中断嵌套 则在此处开中断
OSTimeTick();
OSIntExit();
DSP_C6x_Resume(); // 服务函数,出栈
}
DSP_C6x_Save和DSP_C6x_Resume是两个服务函数,分别完成中断的出、入栈操作。它们与OS_TASK_SW函数的区别在于:中断发生时的当前程序地址是自动保存在IRP寄存器的,应将其作为任务返回地址,而不再是B3。此外,DSP_C6x_Resume是一个永远不会返回的函数,在将所有内容出栈后,它就直接跳转回到中断发生前的程序地址处,继续执行。

进行移植的测试
在编写完了所有的移植代码之后,就可以编写几个简单的任务程序进行测试了,大体上可以分三个步骤来进行,相关资料比较详尽,这里就不多作赘述了。

封装服务函数
最后这个步骤,往往是容易被忽视的,但对于保持项目代码的简洁、易维护有很重要的意义。
��C/OS-II的原作者强烈建议将源代码分路径进行存储,例如本文例子中的所有源代码就应按如下路径结构存储:
\uCOS-II
├─SOURCE // 平台无关代码
│ OS_CORE.C
│ ......
└─TI_C6711 // 系统核心
├─CCS // 开发工具
│ OS_CPU.H
│ OS_CPU_A.ASM
│ OS_CPU_C.C

├─ DSP_C6x_Service // 服务函数
│ DSP_C6x_ Service.H
│ DSP_C6x_ Service.ASM

└─ TEST // 具体的开发项目代码
OS_CFG.H
INCLUDES.H
TEST.C ......

如上,DSP_C6x_Service中的服务函数,类似于原作者提供的80x86版本中的PC.C和PC.H文件。在本文的例子中,服务函数则包括了上文提及的中断相关函数,以及系统初始化函数DSP_C6x_SystemInit()和时钟初始化函数DSP_C6x_TimerInit()等。
而具体的开发项目代码,则可以分别在“/TI_C6711”路径下新建自己的目录,就如同移植测试的“TEST”项目,而无需再关注��C/OS-II的源代码和服务函数。
如此,就可以避免不必要的编译错误,也便于开发项目的维护。
[aijun9801] 清清嗓子唱起歌来:东方红,太阳升




[ 此贴被aijun9801在2006-08-09 12:38重新编辑 ]
long0 - 2008-5-3 20:17:00
uC/OS-II 初级程序员指南(强烈推荐)


本文面向首次接触uC/OS-II的程序员,为他们介绍一下这个系统的一些基本特征和编程上的注意事项,并介绍几个值得了解的API。本文作者已经成功的将uC/OS-II移植到几种不同CPU之上。包括EPSON S1C33和Sunplus unSP?等,积累了丰富的经验,现在愿意和朋友们分享这些经历。希望本文的资料对于希望使用这个系统来开发的朋友有所帮助,作者乐意与您分享任何您成功的喜悦。

(一)    uC/OS-II 简介

uC/OS-II是一种基于优先级的可抢先的硬实时内核。自从92年发布以来,在世界各地都获得了广泛的应用,它是一种专门为嵌入式设备设计的内核,目前已经被移植到40多种不同结构的CPU上,运行在从8位到64位的各种系统之上。尤其值得一提的是,该系统自从2.51版本之后,就通过了美国FAA认证,可以运行在诸如航天器等对安全要求极为苛刻的系统之上。鉴于uC/OS-II可以免费获得代码,对于嵌入式RTOS而言,选择uC/OS无疑是最经济的选择。

(二)    uC/OS-II 应用程序基本结构

应用uC/OS-II,自然要为它开发应用程序,下面论述基于uC/OS-II的应用程序的基本结构以及注意事项。

每一个uC/OS-II应用至少要有一个任务。而每一个任务必须被写成无限循环的形式。以下是推荐的结构:

void task ( void* pdata )

{

INT8U err;

InitTimer(); // 可选

For( ;; )

{

  // 你的应用程序代码

  …….

  ……..

  OSTimeDly(1); // 可选

}

}

以上就是基本结构,至于为什么要写成无限循环的形式呢?那是因为系统会为每一个任务保留一个堆栈空间,由系统在任务切换的时候换恢复上下文,并执行一条reti 指令返回。如果允许任务执行到最后一个花括号(那一般都意味着一条ret指令)的话,很可能会破坏系统堆栈空间从而使应用程序的执行不确定。换句话说,就是“跑飞”了。所以,每一个任务必须被写成无限循环的形式。程序员一定要相信,自己的任务是会放弃CPU使用权的,而不管是系统强制(通过ISR)还是主动放弃(通过调用OS API)。

现在来谈论上面程序中的InitTimer()函数,这个函数应该由系统提供,程序员有义务在优先级最高的任务内调用它而且不能在for循环内调用。注意,这个函数是和所使用的CPU相关的,每种系统都有自己的Timer初始化程序。在uC/OS-II的帮助手册内,作者特地强调绝对不能在OSInit()或者OSStart()内调用Timer初始化程序,那会破坏系统的可移植性同时带来性能上的损失。所以,一个折中的办法就是象上面这样,在优先级最高的程序内调用,这样可以保证当OSStart()调用系统内部函数OSStartHighRdy()开始多任务后,首先执行的就是Timer初始化程序。或者专门开一个优先级最高的任务,只做一件事情,那就是执行Timer初始化,之后通过调用OSTaskSuspend()将自己挂起来,永远不再执行。不过这样会浪费一个TCB空间。对于那些RAM吃紧的系统来说,还是不用为好。

(三)    一些重要的uC/OS-II API介绍

任何一个操作系统都会提供大量的API供程序员使用,uC/OS-II也不例外。由于uC/OS-II面向的是嵌入式开发,并不要求大而全,所以内核提供的API也就大多和多任务息息相关。主要的有以下几类:

1)任务类

2)消息类

3)同步类

4)时间类

5)临界区与事件类

我个人认为对于初级程序员而言,任务类和时间类是必须要首先掌握的两种类型的API。下面我就来介绍比较重要的:

1)    OSTaskCreate函数

这个函数应该至少再main函数内调用一次,在OSInit函数调用之后调用。作用就是创建一个任务。目前有四个参数,分别是任务的入口地址,任务的参数,任务堆栈的首地址和任务的优先级。调用本函数后,系统会首先从TCB空闲列表内申请一个空的TCB指针,然后将会根据用户给出参数初始化任务堆栈,并在内部的任务就绪表内标记该任务为就绪状态。最后返回,这样一个任务就创建成功了。

2)    OSTaskSuspend函数

这个函数很简单,一看名字就该明白它的作用,它可以将指定的任务挂起。如果挂起的是当前任务的话,那么还会引发系统执行任务切换先导函数OSShed来进行一次任务切换。这个函数只有一个参数,那就是指定任务的优先级。那为什么是优先级呢?事实上在系统内部,优先级除了表示一个任务执行的先后次序外,还起着分别每一个任务的作用,换句话说,优先级也就是任务的ID。所以uC/OS-II不允许出现相同优先级的任务。

3)    OSTaskResume函数

这个函数和上面的函数正好相反,它用于将指定的已经挂起的函数恢复成就绪状态。如果恢复任务的优先级高于当前任务,那么还为引发一次任务切换。其参数类似OSTaskSuspend函数,为指定任务的优先级。需要特别说明是,本函数并不要求和OSTaskSuspend函数成对使用。

4)    OS_ENTER_CRITICAL宏

很多人都以为它是个函数,其实不然,仔细分析一下OS_CPU.H文件,它和下面马上要谈到的OS_EXIT_CRITICAL都是宏。他们都是涉及特定CPU的实现。一般都被替换为一条或者几条嵌入式汇编代码。由于系统希望向上层程序员隐藏内部实现,故而一般都宣称执行此条指令后系统进入临界区。其实,它就是关个中断而已。这样,只要任务不主动放弃CPU使用权,别的任务就没有占用CPU的机会了,相对这个任务而言,它就是独占了。所以说进入临界区了。这个宏能少用还是少用,因为它会破坏系统的一些服务,尤其是时间服务。并使系统对外界响应性能降低。

5)    OS_EXIT_CRITICAL宏

这个是和上面介绍的宏配套使用另一个宏,它在系统手册里的说明是退出临界区。其实它就是重新开中断。需要注意的是,它必须和上面的宏成对出现,否则会带来意想不到的后果。最坏的情况下,系统会崩溃。我们推荐程序员们尽量少使用这两个宏调用,因为他们的确会破坏系统的多任务性能。

6)    OSTimeDly函数

这应该程序员们调用最多的一个函数了,这个函数完成功能很简单,就是先挂起当起当前任务,然后进行任务切换,在指定的时间到来之后,将当前任务恢复为就绪状态,但是并不一定运行,如果恢复后是优先级最高就绪任务的话,那么运行之。简单点说,就是可以任务延时一定时间后再次执行它,或者说,暂时放弃CPU的使用权。一个任务可以不显式的调用这些可以导致放弃CPU使用权的API,但那样多任务性能会大大降低,因为此时仅仅依靠时钟机制在进行任务切换。一个好的任务应该在完成一些操作主动放弃使用权,好东西要大家分享嘛!

(四)    uC/OS-II 多任务实现机制分析

前面已经说过,uC/OS-II是一种基于优先级的可抢先的多任务内核。那么,它的多任务机制到底如何实现的呢?了解这些原理,可以帮助我们写出更加健壮的代码来。由于我们面向的初级程序员,本文不打算写成又一篇uC/OS-II的源码分析,那样的文章太多了,本文打算从实现原理的角度探讨这个问题。

首先我们来看看为什么多任务机制可以实现?其实在单一CPU的情况下,是不存在真正的多任务机制的,存在的只有不同的任务轮流使用CPU,所以本质上还是单任务的。但由于CPU执行速度非常快,加上任务切换十分频繁并且切换的很快,所以我们感觉好像有很多任务同时在运行一样。这就是所谓的多任务机制。

由上面的描述,不难发现,要实现多任务机制,那么目标CPU必须具备一种在运行期更改PC的途径,否则无法做到切换。不幸的使,直接设置PC指针,目前还没有哪个CPU支持这样的指令。但是一般CPU都允许通过类似JMP,CALL这样的指令来间接的修改PC。我们的多任务机制的实现也正是基于这个出发点。事实上,我们使用CALL指令或者软中断指令来修改PC,主要是软中断。但在一些CPU上,并不存在软中断这样的概念,所以,我们在那些CPU上,使用几条PUSH指令加上一条CALL指令来模拟一次软中断的发生。

回想一下你在微机原理课程上学过的知识,当发生中断的时候,CPU保存当前的PC和状态寄存器的值到堆栈里,然后将PC设置为中断服务程序的入口地址,再下来一个机器周期,就可以去执行中断服务程序了。执行完毕之后,一般都是执行一条RETI指令,这条指令会把当前堆栈里的值弹出恢复到状态寄存器和PC里。这样,系统就会回到中断以前的地方继续执行了。那么设想一下?如果再中断的时候,人为的更改了堆栈里的值,那会发生什么?或者通过更改当前堆栈指针的值,又会发生什么呢?如果更改是随意的,那么结果是无法预料的错误。因为我们无法确定机器下一条会执行些什么指令,但是如果更改是计划好的,按照一定规则的话,那么我们就可以实现多任务机制。事实上,这就是目前几乎所有的OS的核心部分。不过他们的实现不像这样简单罢了。

下面,我们来看看uC/OS-II再这方面是怎么处理的。再uC/OS-II里,每个任务都有一个任务控制块(Task Control Block),这是一个比较复杂的数据结构。在任务控制快的偏移为0的地方,存储着一个指针,它记录了所属任务的专用堆栈地址。事实上,再uC/OS-II内,每个任务都有自己的专用堆栈,彼此之间不能侵犯。这点要求程序员再他们的程序中保证。一般的做法是把他们申明成静态数组。而且要申明成OS_STK类型。当任务有了自己的堆栈,那么就可以将每一个任务堆栈再那里记录到前面谈到的任务控制快偏移为0的地方。以后每当发生任务切换,系统必然会先进入一个中断,这一般是通过软中断或者时钟中断实现。然后系统会先把当前任务的堆栈地址保存起来,仅接着恢复要切换的任务的堆栈地址。由于哪个任务的堆栈里一定也存的是地址(还记得我们前面说过的,每当发生任务切换,系统必然会先进入一个中断,而一旦中断CPU就会把地址压入堆栈),这样,就达到了修改PC为下一个任务的地址的目的。

以上就是uC/OS-II的多任务实现机制,我们在这里大费笔墨谈论这个问题,是希望我们的程序员们可以善加利用这个机制,写出更健壮,更富有效率的代码来。
[aijun9801] 清清嗓子唱起歌来:东方红,太阳升
我不是少爷 - 2008-5-3 20:17:00
uC/OS-ii 入门学习 好书推荐


《基于单片机8051的嵌入式开发指南》
介绍地址:http://www.fecit.com.cn/book_detail.aspx?bigcid=1&cid=21&id=7828
[aijun9801] 清清嗓子唱起歌来:东方红,太阳升
my9449 - 2008-5-3 20:17:00
《嵌入式系统设计与实例开发-基于ARM处理器与uCOS II实时操作系统》下载


文档较大,需要请回复邮箱!
[aijun9801] 清清嗓子唱起歌来:东方红,太阳升
夜带水果刀 - 2008-5-3 20:17:00
ucos-II书(推荐下载)



[aijun9801] 清清嗓子唱起歌来:东方红,太阳升





附件:  ucos-II书.rar (1733 K) 下载次数:101
qiusibo - 2008-5-3 20:17:00
ucosII书所附带光盘



[aijun9801] 清清嗓子唱起歌来:东方红,太阳升
icelee - 2008-5-3 20:17:00
从c到嵌入式c编程语言_入门�实用�深(经典)


从c到嵌入式c编程语言_入门�实用�深入
[aijun9801] 清清嗓子唱起歌来:东方红,太阳升
火山赤子 - 2008-5-3 20:17:00
嵌入式系统设计与实例开发-基于ARM处理器与uCOS II实时操作系统.pdf


文档大,需要发Email  给 me!
[aijun9801] 清清嗓子唱起歌来:东方红,太阳升
rayeyes - 2008-5-3 20:18:00
uC/OS应用的简单剖析


一. uC/OS-II的移植

移植uC/OS对目标处理器有一定要求,这个可以参照<<uc/OS-II源码公开的实时嵌入式操作系统>>一书中第8章的内容.
整个嵌入式系统分为两大层:硬件层和软件层.这里主要研究软件层的架构.
软件层主要分为四个部分:实时操作系统内核,与处理器相关部分,与应用相关部分,用户的应用系统.

l 实时操作系统内核

实时操作系统对系统资源进行管理。主要包括任务分配和调度、系统时钟服务、内存管理、消息机制、异常处理等等。uC/OS所有系统服务均由内核提供。内核将应用系统和底层硬件结合成一个完整的实时系统。
移植的时候内核是不变的,开发者根据自己应用系统的需要来选择实时操作系统内核,开发者不能对内核随意访问,只能使用内核提供的功能服务来开发自己的应用系统。内核确定,那么所提供的系统管理能力,系统服务也就得到了限定。开发者只能在规定的范围内对系统作些改动.

2 与处理器相关的代码

这是移植中最关键的部分.内核将应用系统和底层硬件有机的结合成一个实时系统,要使同一个内核能适用于不同的硬件体系,就需要在内核和硬件之间有一个中间层,这就是与处理器相关的代码.处理器不同,这部分代码也不同.
我们在移植时需要自己处理这部分代码,可以自己编写,也可以直接使用已经成功移植的代码.
在uC/OS中这一部分代码分成三个文件:OS_CPU.H, OS_CPU_A.ASM, OS_CPU_C.C

1) OS_CPU.H

包括了用#define定义的与处理器相关的常量,宏和类型定义.
具体来讲有系统数据类型定义,栈增长方向定义,关中断和开中断定义,系统软中断的定义等等.

2) OS_CPU_A.ASM

这部分需要对处理器的寄存器进行操作,所以必须用汇编语言来编写.包括四个子函数:OSStartHighRdy(),OSCtxSw(),OSIntCtxSw(),OSTickISR().
OSStartHighRdy()在多任务系统启动函数OSStart()中调用.完成的功能是:设置系统运行标志位OSRunning = TRUE;将就绪表中最高优先级任务的栈指针Load到SP中,并强制中断返回.这样就绪的最高优先级任务就如同从中断里返回到运行态一样,使得整个系统得以运转.
OSCtxSw()在任务级任务切换函数中调用的.任务级切换是通过SWI或者TRAP人为制造的中断来实现的.ISR的向量地址必须指向OSCtxSw().这一中断完成的功能:保存任务的环境变量(主要是寄存器的值,通过入栈来实现),将当前SP存入任务TCB中,载入就绪最高优先级任务的SP,恢复就绪最高优先级任务的环境变量,中断返回.这样就完成了任务级的切换.
OSIntCtxSw()在退出中断服务函数OSIntExit()中调用,实现中断级任务切换.由于是在中断里调用,所以处理器的寄存器入栈工作已经做完,就不用作这部分工作了.具体完成的任务:调整栈指针(因为调用函数会使任务栈结构与系统任务切换时堆栈标准结构不一致),保存当前任务SP,载入就绪最高优先级任务的SP,恢复就绪最高优先级任务的环境变量,中断返回.这样就完成了中断级任务切换.
OSTickISR()系统时钟节拍中断服务函数,这是一个周期性中断,为内核提供时钟节拍.频率越高系统负荷越重.其周期的大小决定了内核所能给应用系统提供的最小时间间隔服务.一般只限于ms级(跟MCU有关),对于要求更加苛刻的任务需要用户自己建立中断来解决.该函数具体内容:保存寄存器(如果硬件自动完成就可以省略),调用OSIntEnter(),调用OSTimeTick(),调用OSIntExit(),恢复寄存器,中断返回.

3) OS_CPU_C.C

UC/OS中共定义了6个函数在该文件中.但是最重要的是OSTaskStkInit().其他都是对系统内核的扩展时用的.
OSTaskStkInit()是在用户建立任务时系统内部自己调用的,对用户任务的堆栈进行初始化.使建立好的进入就绪态任务的堆栈与系统发生中断并且将环境变量保存完毕时的栈结构一致.这样就可以用中断返回指令使就绪的任务运行起来.
具体的入栈方式要根据不同mcu而定.需要参考用户使用的mcu说明书.同时还要考虑mcu的栈生成方式.这需要根据具体问题来分析,在此不做过多论述.


3 与应用相关的代码

这一部分是用户根据自己的应用系统来定制合适的内核服务功能.包括两个文件:OS_CFG.H, INCLUDES.H.
OS_CFG.H来配置内核,用户根据需要对内核进行定制,留下需要的部分,去掉不需要的部分,设置系统的基本情况.比如系统可提供的最大任务数量,是否定制邮箱服务,是否需要系统提供任务挂起功能,是否提供任务优先级动态改变功能等等.
INCLUDES.H系统头文件,整个实时系统程序所需要的文件,包括了内核和用户的头文件.


4 用户应用系统

这是整个实时系统的最高层,用户通过利用实时操作系统提供的服务来开发自己的具体程序.


二. 用户应用系统编写的模式


kernel提供给用户一些功能函数,使得用户的系统建立更加方便,但是kernel内部不会处理用户的工作,对于整个系统的具体应用工作还得需要用户自己去考虑,如何利用好这些功能服务函数就成为一个比较重要的问题.

1. main函数的结构

void main (void)
{
初始化系统的硬件;
OSInit();
任务的建立,消息机制的建立;
OSStart();
}
这里需要的是在OSStart()执行之前不得启动中断,硬件系统还不能工作.必须先让软件系统进入工作状态后才行.

2. 中断的结构

ISR:
{
保存处理器寄存器的值;
调用OSIntEnter();

执行用户的工作;
调用OSIntExit();
恢复处理器寄存器的值;
RTI;
}
用户的中断形式和以前一样,没有什么大的变化,仅仅是在原来用户ISR的基础上在固定的位置加了两个函数:OSIntEnter(), OSIntExit().

3. 各个任务的结构

void YourTask (void)
{
for(;;)
{
用户代码
调用的系统服务
}
}
在任务启动函数执行完后,系统会切换到最高优先级的任务去执行,此时,可以将系统硬件部分的启动放在该任务的最前边,仅仅是启动时执行一次,主要是启动系统的节拍中断,或者一些必须在多任务系统调度后才能初始化的部分,使系统的真正开始工作,达到软件硬件的基本同步.
Void HighestPrioTask(void)
{
OSStartHardware();
For (;;)
{
用户代码
调用的系统服务
}
}
[aijun9801] 清清嗓子唱起歌来:东方红,太阳升
dm15 - 2008-5-3 20:18:00
在单片机中嵌入操作系统的利弊


早在20世纪60年代,就已经有人开始研究和开发嵌入式操作系统。但直到最近,它才在国内被越来越多的提及,在通信、电子、自动化等需要实时处理的领域所日益显现的重要性吸引了人们越来越多的注意力。但是,人们所谈论的往往是一些著名的商业内核,诸如VxWorks、PSOS等。这些商业内核性能优越,但价格昂贵,主要用于16位和32位处理器中,针对国内大部分用户使用的51系列8位单片机,可以选择免费的uC/OS-II。


uC/OS-II的特点

  1.uC/OS-II是由Labrosse先生编写的一个开放式内核,最主要的特点就是源码公开。这一点对于用户来说可谓利弊各半,好处在于,一方面它是免费的,另一方面用户可以根据自己的需要对它进行修改。缺点在于它缺乏必要的支持,没有功能强大的软件包,用户通常需要自己编写驱动程序,特别是如果用户使用的是不太常用的单片机,还必须自己编写移植程序。

  2.uC/OS-II是一个占先式的内核,即已经准备就绪的高优先级任务可以剥夺正在运行的低优先级任务的CPU使用权。这个特点使得它的实时性比非占先式的内核要好。通常我们都是在中断服务程序中使高优先级任务进入就绪态(例如发信号),这样退出中断服务程序后,将进行任务切换,高优先级任务将被执行。拿51单片机为例,比较一下就可以发现这样做的好处。假如需要用中断方式采集一批数据并进行处理,在传统的编程方法中不能在中断服务程序中进行复杂的数据处理,因为这会使得关中断时间过长。所以经常采用的方法是置一标志位,然后退出中断。由于主程序是循环执行的,所以它总有机会检测到这一标志并转到数据处理程序中去。但是因为无法确定发生中断时程序到底执行到了什么地方,也就无法判断要经过多长时间数据处理程序才会执行,中断响应时间无法确定,系统的实时性不强。如果使用uC/OS-II的话,只要把数据处理程序的优先级设定得高一些,并在中断服务程序中使它进入就绪态,中断结束后数据处理程序就会被立即执行。这样可以把中断响应时间限制在一定的范围内。对于一些对中断响应时间有严格要求的系统,这是必不可少的。但应该指出的是如果数据处理程序简单,这样做就未必合适。因为uC/OS-II要求在中断服务程序末尾使用OSINTEXIT函数以判断是否进行任务切换,这需要花费一定的时间。

  3.uC/OS-II和大家所熟知的Linux等分时操作系统不同,它不支持时间片轮转法。uC/OS-II是一个基于优先级的实时操作系统,每个任务的优先级必须不同,分析它的源码会发现,uC/OS-II把任务的优先级当做任务的标识来使用,如果优先级相同,任务将无法区分。进入就绪态的优先级最高的任务首先得到CPU的使用权,只有等它交出CPU的使用权后,其他任务才可以被执行。所以它只能说是多任务,不能说是多进程,至少不是我们所熟悉的那种多进程。显而易见,如果只考虑实时性,它当然比分时系统好,它可以保证重要任务总是优先占有CPU。但是在系统中,重要任务毕竟是有限的,这就使得划分其他任务的优先权变成了一个让人费神的问题。另外,有些任务交替执行反而对用户更有利。例如,用单片机控制两小块显示屏时,无论是编程者还是使用者肯定希望它们同时工作,而不是显示完一块显示屏的信息以后再显示另一块显示屏的信息。这时候,要是uC/OS-II即支持优先级法又支持时间片轮转法就更合适了。

  4.uC/OS-II对共享资源提供了保护机制。正如上文所提到的,uC/OS-II是一个支持多任务的操作系统。一个完整的程序可以划分成几个任务,不同的任务执行不同的功能。这样,一个任务就相当于模块化设计中的一个子模块。在任务中添加代码时,只要不是共享资源就不必担心互相之间有影响。而对于共享资源(比如串口),uC/OS-II也提供了很好的解决办法。一般情况下使用的是信号量的方法。简单地说,先创建一个信号量并对它进行初始化。当一个任务需要使用一个共享资源时,它必须先申请得到这个信号量,而一旦得到了此信号量,那就只有等使用完了该资源,信号量才会被释放。在这个过程中即使有优先权更高的任务进入了就绪态,因为无法得到此信号量,也不能使用该资源。这个特点的好处显而易见,例如当显示屏正在显示信息的时候,外部产生了一个中断,而在中断服务程序中需要显示屏显示其他信息。这样,退出中断服务程序后,原有的信息就可能被破坏了。而在uC/OS-II中采用信号量的方法时,只有显示屏把原有信息显示完毕后才可以显示新信息,从而可以避免这个现象。不过,采用这种方法是以牺牲系统的实时性为代价的。如果显示原有信息需要耗费大量时间,系统只好等待。从结果上看,等于延长了中断响应时间,这对于未显示信息是报警信息的情况,无疑是致命的。发生这种情况,在uC/OS-II中称为优先级反转,就是高优先级任务必须等待低优先级任务的完成。在上述情况下,在两个任务之间发生优先级反转是无法避免的。所以在使用uC/OS-II时,必须对所开发的系统了解清楚,才能决定对于某种共享资源是否使用信号量。


uC/OS-II在单片机使用中的一些特点
  1.在单片机系统中嵌入uC/OS-II将增强系统的可靠性,并使得调试程序变得简单。以往传统的单片机开发工作中经常遇到程序跑飞或是陷入死循环。可以用看门狗解决程序跑飞问题,而对于后一种情况,尤其是其中牵扯到复杂数学计算的话,只有设置断点,耗费大量时间来慢慢分析。如果在系统中嵌入uC/OS-II的话,事情就简单多了。可以把整个程序分成许多任务,每个任务相对独立,然后在每个任务中设置超时函数,时间用完以后,任务必须交出CPU的使用权。即使一个任务发生问题,也不会影响其他任务的运行。这样既提高了系统的可靠性,同时也使得调试程序变得容易。

  2.在单片机系统中嵌入uC/OS-II将增加系统的开销。现在所使用的51单片机,一般是指87C51或者89C51,其片内都带有一定的RAM和ROM。对于一些简单的程序,如果采用传统的编程方法,已经不需要外扩存储器了。如果在其中嵌入uC/OS-II的话,在只需要使用任务调度、任务切换、信号量处理、延时或超时服务的情况下,也不需要外扩ROM了,但是外扩RAM是必须的。由于uC/OS-II是可裁减的操作系统,其所需要的RAM大小就取决于操作系统功能的多少。举例来说,uC/OS-II允许用户定义最大任务数。由于每建立一个任务,都要产生一个与之相对应的数据结构TCB,该数据结构要占用很大一部分内存空间。所以在定义最大任务数时,一定要考虑实际情况的需要。如果定得过大,势必会造成不必要的浪费。嵌入uC/OS-II以后,总的RAM需求可以由如下表达式得出:

  RAM总需求=应用程序的RAM需求+内核数据区的RAM需求+(任务栈需求+最大中断嵌套栈需求)�任务数
所幸的是,uC/OS-II可以对每个任务分别定义堆栈空间的大小,开发人员可根据任务的实际需求来进行栈空间的分配。但在RAM容量有限的情况下,还是应该注意一下对大型数组、数据结构和函数的使用,别忘了,函数的形参也是要推入堆栈的。

  3.uC/OS-II的移植也是一件需要值得注意的工作。如果没有现成的移植实例的话,就必须自己来编写移植代码。虽然只需要改动两个文件,但仍需要对相应的微处理器比较熟悉才行,最好参照已有的移植实例。另外,即使有移植实例,在编程前最好也要阅读一下,因为里面牵扯到堆栈操作。在编写中断服务程序时,把寄存器推入堆栈的顺序必须与移植代码中的顺序相对应。

  4.和其他一些著名的嵌入式操作系统不同,uC/OS-II在单片机系统中的启动过程比较简单,不像有些操作系统那样,需要把内核编译成一个映像文件写入ROM中,上电复位后,再从ROM中把文件加载到RAM中去,然后再运行应用程序。uC/OS-II的内核是和应用程序放在一起编译成一个文件的,使用者只需要把这个文件转换成HEX格式,写入ROM中就可以了,上电后,会像普通的单片机程序一样运行。

结语
  由以上介绍可以看出,uC/OS-II具有免费、使用简单、可靠性高、实时性好等优点,但也有移植困难、缺乏必要的技术支持等缺点,尤其不像商用嵌入式系统那样得到广泛使用和持续的研究更新。但开放性又使得开发人员可以自行裁减和添加所需的功能,在许多应用领域发挥着独特的作用。当然,是否在单片机系统中嵌入uC/OS-II应视所开发的项目而定,对于一些简单的、低成本的项目来说,就没必要使用嵌入式操作系统了。
[aijun9801] 清清嗓子唱起歌来:东方红,太阳升
jarw - 2008-5-3 20:18:00
��COS-Ⅱ在AVR上的移植应用



[aijun9801] 清清嗓子唱起歌来:东方红,太阳升





附件:  ��COS-Ⅱ在AVR上的移植应用.PDF (81 K) 下载次数:27
wixhpp - 2008-5-3 20:18:00
初学uC/OS-i 个人观点i


如果从看源码开始,建议看源码的话要抓住重点,ucos 开始主要看一下 中断、调度比较好,其它可以以后深入!个人体会
[aijun9801] 清清嗓子唱起歌来:东方红,太阳升
yman9 - 2008-5-3 20:18:00
uC/OS应用的简单剖析


一. uC/OS-II的移植
    移植uC/OS对目标处理器有一定要求,这个可以参照<<uc/OS-II源码公开的实时嵌入式操作系统>>一书中第8章的内容.
    整个嵌入式系统分为两大层:硬件层和软件层.这里主要研究软件层的架构.
    软件层主要分为四个部分:实时操作系统内核,与处理器相关部分,与应用相关部分,用户的应用系统.
    l 实时操作系统内核
      实时操作系统对系统资源进行管理。主要包括任务分配和调度、系统时钟服务、内存管理、消息机制、异常处理等等。uC/OS所有系统服务均由内核提供。内核将应用系统和底层硬件结合成一个完整的实时系统。
      移植的时候内核是不变的,开发者根据自己应用系统的需要来选择实时操作系统内核,开发者不能对内核随意访问,只能使用内核提供的功能服务来开发自己的应用系统。内核确定,那么所提供的系统管理能力,系统服务也就得到了限定。开发者只能在规定的范围内对系统作些改动.
    2 与处理器相关的代码
      这是移植中最关键的部分.内核将应用系统和底层硬件有机的结合成一个实时系统,要使同一个内核能适用于不同的硬件体系,就需要在内核和硬件之间有一个中间层,这就是与处理器相关的代码.处理器不同,这部分代码也不同.
        我们在移植时需要自己处理这部分代码,可以自己编写,也可以直接使用已经成功移植的代码.
        在uC/OS中这一部分代码分成三个文件:OS_CPU.H, OS_CPU_A.ASM, OS_CPU_C.C
      1)  OS_CPU.H
        包括了用#define定义的与处理器相关的常量,宏和类型定义.
        具体来讲有系统数据类型定义,栈增长方向定义,关中断和开中断定义,系统软中断的定义等等.
      2)  OS_CPU_A.ASM
        这部分需要对处理器的寄存器进行操作,所以必须用汇编语言来编写.包括四个子函数:OSStartHighRdy(),OSCtxSw(),OSIntCtxSw(),OSTickISR().
        OSStartHighRdy()在多任务系统启动函数OSStart()中调用.完成的功能是:设置系统运行标志位OSRunning =
        TRUE;将就绪表中最高优先级任务的栈指针Load到SP中,并强制中断返回.这样就绪的最高优先级任务就如同从中断里返回到运行态一样,使得整个系统得以运转.
        OSCtxSw()在任务级任务切换函数中调用的.任务级切换是通过SWI或者TRAP人为制造的中断来实现的.ISR的向量地址必须指向OSCtxSw().这一中断完成的功能:保存任务的环境变量(主要是寄存器的值,通过入栈来实现),将当前SP存入任务TCB中,载入就绪最高优先级任务的SP,恢复就绪最高优先级任务的环境变量,中断返回.这样就完成了任务级的切换.
        OSIntCtxSw()在退出中断服务函数OSIntExit()中调用,实现中断级任务切换.由于是在中断里调用,所以处理器的寄存器入栈工作已经做完,就不用作这部分工作了.具体完成的任务:调整栈指针(因为调用函数会使任务栈结构与系统任务切换时堆栈标准结构不一致),保存当前任务SP,载入就绪最高优先级任务的SP,恢复就绪最高优先级任务的环境变量,中断返回.这样就完成了中断级任务切换.
        OSTickISR()系统时钟节拍中断服务函数,这是一个周期性中断,为内核提供时钟节拍.频率越高系统负荷越重.其周期的大小决定了内核所能给应用系统提供的最小时间间隔服务.一般只限于ms级(跟MCU有关),对于要求更加苛刻的任务需要用户自己建立中断来解决.该函数具体内容:保存寄存器(如果硬件自动完成就可以省略),调用OSIntEnter(),调用OSTimeTick(),调用OSIntExit(),恢复寄存器,中断返回.
      3)  OS_CPU_C.C
        UC/OS中共定义了6个函数在该文件中.但是最重要的是OSTaskStkInit().其他都是对系统内核的扩展时用的.
        OSTaskStkInit()是在用户建立任务时系统内部自己调用的,对用户任务的堆栈进行初始化.使建立好的进入就绪态任务的堆栈与系统发生中断并且将环境变量保存完毕时的栈结构一致.这样就可以用中断返回指令使就绪的任务运行起来.
        具体的入栈方式要根据不同mcu而定.需要参考用户使用的mcu说明书.同时还要考虑mcu的栈生成方式.这需要根据具体问题来分析,在此不做过多论述.

    3 与应用相关的代码
        这一部分是用户根据自己的应用系统来定制合适的内核服务功能.包括两个文件:OS_CFG.H, INCLUDES.H.
        OS_CFG.H来配置内核,用户根据需要对内核进行定制,留下需要的部分,去掉不需要的部分,设置系统的基本情况.比如系统可提供的最大任务数量,是否定制邮箱服务,是否需要系统提供任务挂起功能,是否提供任务优先级动态改变功能等等.
        INCLUDES.H系统头文件,整个实时系统程序所需要的文件,包括了内核和用户的头文件.

    4  用户应用系统
        这是整个实时系统的最高层,用户通过利用实时操作系统提供的服务来开发自己的具体程序.

    二. 用户应用系统编写的模式

    kernel提供给用户一些功能函数,使得用户的系统建立更加方便,但是kernel内部不会处理用户的工作,对于整个系统的具体应用工作还得需要用户自己去考虑,如何利用好这些功能服务函数就成为一个比较重要的问题.
    1. main函数的结构
      void main (void)
      {
        初始化系统的硬件;
        OSInit();
        任务的建立,消息机制的建立;
        OSStart();
      }
      这里需要的是在OSStart()执行之前不得启动中断,硬件系统还不能工作.必须先让软件系统进入工作状态后才行.
    2. 中断的结构
      ISR:
        {
            保存处理器寄存器的值;
            调用OSIntEnter();
            执行用户的工作;
            调用OSIntExit();
            恢复处理器寄存器的值;
            RTI;
        }
      用户的中断形式和以前一样,没有什么大的变化,仅仅是在原来用户ISR的基础上在固定的位置加了两个函数:OSIntEnter(),
      OSIntExit().
    3. 各个任务的结构
      void YourTask (void)
      {
      for(;;)
      {
      用户代码
      调用的系统服务
      }
      }
      在任务启动函数执行完后,系统会切换到最高优先级的任务去执行,此时,可以将系统硬件部分的启动放在该任务的最前边,仅仅是启动时执行一次,主要是启动系统的节拍中断,或者一些必须在多任务系统调度后才能初始化的部分,使系统的真正开始工作,达到软件硬件的基本同步.
      Void  HighestPrioTask(void)
      {
        OSStartHardware();
        For (;;)
        {
          用户代码
          调用的系统服务
        }
      }
      用户可以按照这些格式去编写自己的任务,建立自己的应用系统
[aijun9801] 清清嗓子唱起歌来:东方红,太阳升
abccabcc - 2008-5-3 20:18:00
太感谢了
qsums - 2008-5-3 20:18:00
��COS-Ⅱ在AVR上的移植应用


��COS-Ⅱ在AVR上的移植应用(PDF格式),不错的参考资料



描述:��COS-Ⅱ在AVR上的移植应用.PDF
附件:  ��COS-Ⅱ在AVR上的移植应用.PDF (81 K) 下载次数:50
1
查看完整版本: 嵌入式系统:uC/OS-ii 专题开放!(强烈推荐)