ucos学习.docx
《ucos学习.docx》由会员分享,可在线阅读,更多相关《ucos学习.docx(22页珍藏版)》请在冰豆网上搜索。
ucos学习
基于UCOS的ARM嵌入式系统学习
学习嵌入式有一段时间了,从刚开始,只知道有这么个东西,具体是什么,一概不知。
到后来,参加的一个ARM嵌入式培训班,算是对嵌入式系统有所了解,并且通过了ARM嵌入式工程师资格认证,但那些东西都只是停留在概念的基础上,根本不会用它来做点什么,哪怕是点亮一个流水灯程序也不会(我说的是这其中的代码完全有自己写),后来便开始做实验,用的是SmartSOPC实验箱。
刚开始做的时候,只是看应用程序。
因为应用程序都是基于UCOS系统上运行的,比如,创建任务,邮箱,等等。
这些函数以前在C函数里根本没学过,于是便花了一段时间学习UCOS,不过没有去分析它的代码,对UCOS的了解只是停留在概念上,知道怎么用就行。
可是后来发现,对于ARM的体系结构,一知半解,比如说:
当发生中断时,系统改如何处理;ARM7的7中模式,又该如何利用,更无从说起,后来便开始写启动代码,移植UCOS,才慢慢的揭开ARM体系结构那神秘的面纱。
一:
ARM的启动代码
在学启动代码前,首先必须对ARM内核的体系结构有所了解。
刚开始对引导块,存储器映射,和存储器重映射,很难理解,所以在写启动代码前,对这些概念的理解很有必要。
1:
系统在上电或复位后,最开始并不是运行的并不是用户程序,而是引导模块(bootblock),它是芯片厂家在LPC2000系列固化的一段代码,用户无法修改或删除。
(尽管有的芯片没有片内Flash,但它依然存在bootblock)这段代码在芯片复位后首先被运行,其主要功能是:
1.1:
判断运行在哪个存储器上的程序:
LPC2200系列可同时存在片内存储器和片外存储器,bootblock通过芯片上的boot0和boot1引脚来判断程序的运行。
Boot0和boot1决定程序是从片外引导还是从片内引导,以及控制片外存储器的位数。
而MAP[1:
0]决定异常向量表映射到的位置。
P2.27boot1
P2.26boot0
引导方式
MAP[1:
0]
0
0
Cs0控制的8位存储器
11
0
1
Cs0控制的16位存储器
11
1
0
Cs0控制的32位存储器
11
1
1
内部Flash存储器
01
ARM芯片复位后,系统进入管理模式,ARM状态,PC寄存器值为0x00000000,所以必须保证用户的向量表代码定位在0x00000000处,或者映射到0x00000000处(例如向量表代码在0x80000000处,通过存储器映射,访问0x00000000就是访问0x80000000)。
1.2:
检查用户代码是否有效:
bootblock在将芯片的控制权交给应用程序前,首先判断用户程序是否有效:
判断用户程序有效的标志为:
中断向量的机器码值之和是否为0。
1.3:
判断芯片是否加密:
芯片在加密后,将禁止JTAG和ISP应用。
1.4:
芯片是处于在应用编程(IAP)或者是在系统编程(ISP):
LPC2000系列控制器的内部Flash是无法从外部直接擦写的,这些功能必须通过IAP代码来实现;当芯片的P0.14引脚被拉底或用户代码无效时,可以是芯片进入ISP状态。
ARM的启动过程如图所示:
下载(103.22KB)
2009-3-1019:
49
ARM内核在发生异常后,会使程序跳转到位于0x0000~0x001C的异常向量表处,再经过向量跳转到异常服务程序。
但ARM单条指令的寻址范围有限,无法用一条指令实现4G范围的跳转,所以应在其后面的0x0020~0x003F地址上放置跳转目标,这样就可以实现4G范围内的任意跳转,因此一个异常向量表实际上占用了16个字的存储单元。
2.存储器映射:
ARM芯片可以有片内片外存储器,程序是根据这些存储器上的单元的地址来进行操作的。
而存储器本省不具有地址的信息(就像一堆没有编号的纸盒,我们是无法区分和使用的),它们在芯片中的地址是由芯片厂家或用户分配的,那么给存储区分配地址的过程就称为存储器映射,也即是对4G的存储空间分配地址。
如图所示:
LPC2200绝大部分存储单元的地址是在芯片设计生产时就确定的,用户无法修改。
AHB和VPB外设区域都为2M字节,可各自分配最多128个外设。
向量中断控制器和外部存储器控制器部件等挂接在AHB总线上;GPIO部件和UART等部件挂接在VPB总线上,VPB总线速度相对AHB总线较低。
外设的存储器和控制寄存器是统一编址的。
3.存储器重映射:
为了增加系统的灵活性,系统中有部分存储单元可以同时出现在不同的地址上,这称为“存储器重映射”。
存储器重映射并不是对映射单元内容进行了复制,而只是将多个地址指向了同一个存储单元,例如将中断异常向量表映射到不同的位置,这个过程是通过存储器映射控制实现的。
启动代码:
LPC2220的启动代码.rar(20.39KB)
下载次数:
335
2009-3-1019:
49
启动代码主要包括:
定义处理器模式,屏蔽中断,定义各模式堆栈的大小,异常向量表,跳转到相应的异常服务程序。
启动代码主要是对异常复位的处理,当系统上电或复位后,程序跳转到跳转到复位程序入口,设置相应的配置寄存器(这个取决于外部存储器的宽度),初始化堆栈(在这部分代码中,其中用户模式为缺省模式,因为它不能直接从用户模式跳转到异常模式,所以利用系统模式来运行用户的任务,即初始化堆栈后进入系统模式),进行存储器的重映射(这一部分可以不要,如果是处于用户Flash模式下),测试程序(这只是用来检测)。
二:
UCOS操作系统的移植
Ucos的移植有相当一部分工作与所用处理器的体系结构密切相关,因此在移植前需了解处理器的体系结构和相应的汇编语言。
比如处理器的模式及指令集模式,异常等等,在移植过程中,最重要也比较难理解的是对中断的处理,其中主要包括软件中断和异常中断的服务处理;ucos主要是为了实现多任务切换,当用户程序中有多个任务,因此在移植代码中需要对任务的优先级,任务的切换进行设计。
2.1:
Os_cpu.h:
该文件中主要定义了与处理器相关的数据类型,软件中断符号。
因为ucos不适用C语言中的short,int和long等数据类型,因为他们与处理器类型相关,隐含着不可移植性,因此需定义与处理器相关的数据类型,便于移植;而定义的软件中断符号是为了在发生软件中断时,根据定义的软件中断号,跳转到指定的软件中断服务程序。
(1)定义与编译器相关的数据类型:
为了保证可移植性,程序中没有直接使用C语言中的short,int及long等数据类型的定义,因为他们与处理器类型相关,隐含着不可移植性;而是自己定义了一套数据类型。
(2)选择开关中断的方式,同时定义软件中断符号。
(3)定义栈的增长方向:
虽然ARM处理器核对于两种栈的增长方式(向上/向下)都支持,但GCC的C语言编译器仅支持一种方式,即从上往下增长,并且是满递减堆栈。
所以OS_STK_GROWTH的值为1,
2.2:
Os_cpu_c.c:
该文件主要进行任务堆栈的初始化(OS_STK*OSTaskStkInit)和软件中断服务程序(voidSWI_Exception)。
软件中断服务程序中主要是对os_cpu.h中除了00,01中断号的其余中断标志号进行处理。
在该文件要编写10个简单的C函数,但是只有一个函数是必要的,其余的必须声明,但不一定要包含任何代码,唯一需要编写的是OSTaskStkInit()。
这个函数的功能主要是初始化任务的栈结构,在ARM体系结构下,任务堆栈空间由高至低依次将保存着pc、lr、r12、r11、r10、...r1、r0、CPSR、SPSR,因此在初始化任务堆栈空间时,应该与ARM体系结构中一样。
2.3:
Os_cpu_c.s:
该文件中包括软件中断标号入口处理SoftwareInterrupt,启动优先级最高的任务OSStartHighRdy,任务切换时的堆栈处理OSIntCtxSw和OSIntCtxSw_1。
当发生软件中断时,便跳到SoftwareInterrupt标号处,然后根据其中断号进行相应的处理OSStartHighRdy启动就绪态中任务优先级最高的任务。
OSIntCtxSw保存被中断任务的一些状态寄存器保存到该模式下的堆栈中,当优先级高的任务完成后,再从堆栈中返回。
而OSIntCtxSw_1是任务切换时,对新任务所做的堆栈处理。
在移植代码中,很大一部分代码是当中断发生时,对中断的处理,堆栈的保护,任务的切换,对UCOS中软件中断的理解和利用非常重要。
1. 软件中断SWI
从启动代码中开始硬件平台为ARM7内核。
当有软中断发生(即调用2.2中某一个函数时)时,系统首先自动调转到0x0008处执行。
1、第一级中断向量
bHandlerSWI
2、宏展开
继续找HandlerSWI。
HandlerSWI HANDLER HandleSWI
3、内存第二级中断向量
再找HandleSWI。
HandleSWI # 4
现在我们知道软中断的服务程序跑到了内存中去了,我们可以编写相应的代码来实现不同功能号下的不同功能。
在文件OS_CPU_A.S中编写软中断服务程序1、软中断服务代码。
HandleSWI
LDR sp,StackSvc;
STMFD sp!
{r0-r3,r12,lr};//STMDB,保护现场
MOV r1, sp ;//若SWI调用带参,将R1指向第二个参数
MRS r3, spsr
TST r3,#0x20 ;//检查时ARM还是THUMB指令
STMFD sp!
{r0} ;//spsr入栈
LDRNEHr0, [lr,#-2] ;//THUMB指令时执行,获取SWI指令码
BICNE r0, r0,#0xFF00;//获取SWInumber
;//r0nowcontainsSWInumber
;//r1nowcontainspointertostackedregisters
LDREQ r0,r0,#0xFF000000;//ro中存放着软中断的功能号
CMP r0,#1;
LDRLO pc,=OSIntCtxSw ;//LDRLO=LDRCCr0=0时跳转到汇编语言处理
LDREQ pc,=__OSStartHighRdy;//r0=1时跳转到汇编语言处理
BL SWI_Exception //r0>1时跳转,调用C编写的SWI处理函数
LDMFD sp!
{r0-r3,r12,pc}^ ;//恢复现场
我们可以看出当调用2.2中某一个函数并且函数具有两个参数时,函数的第一个参数存放在r0中,第二个参数存放在r1中。
4、OSIntCtxSw的实现
OSIntCtxSw
LDRR2,[SP,#20]
5、__OSStartHighRdy的实现
流程图如图所示
下载(34.78KB)
2009-3-1019:
49
2.中断的执行过程
当发生中断时,将中断服务程序地址装入VICVecAddr,同时微控制器跳转到地址0x00000018处执行代码。
对于向量和非向量IRQ在0x00000018处放如下指令:
在Startup.s中
LDR PC,[PC,#-0xff0];
该指令将VICVecAddr寄存器的值装入PC,而寄存器中的值为相应中断服务程序的地址。
假设发生定时器0中断,则VICVecAddr中存放的是Timer0_Handler(void)的地址,那么PC指针值将是Timer0_Handler(void)的地址,同时进入中断异常向量表0x00000018。
在IRQ.S中
Timer0_Handler HANDLERTimer0_Exception
然后程序进入中断服务程序,即进入中断模式,任务堆栈的保存与恢复及其相应中断服务程序的C函数。
在IRQ.inc,定义了一个宏指令HANDLER,它是UCOS为ARM7提供的中断服务程序与C函数的接口。
MACRO
$IRQ_LabelHANDLER$IRQ_Exception_Function
;CODE
MEND
在Target.c中,定时器0的中断服务C函数。
voidTimer0_Exception(void)
{
T0IR=0x01;
VICVectAddr=0;//interruptclose通知中断控制器中断结束
OSTimeTick();
}
中断流程
下载(29.49KB)
2009-3-1019:
49
1:
表示正在执行的程序
2:
假设发生定时器0中断
3:
将定时中断0的服务程序地址放到VICVecAddr
4:
跳到异常向量表0x00000018处,进入中断模式。
5:
读取VICVecAddr寄存器中的值,并跳转到相应的中断服务程序。
6:
从中断服务程序中返回。
对中断句柄的理解
在IRQ.S中:
IRQ_Handler
HANDLERIRQ_Exception
在IRQ.INC中:
包含这样一个宏定义。
MACRO
{$label}macroname{$parameter{,$parameter}…..}
;code
MEND
$IRQ_LabelHANDLER$IRQ_Exception_Function
这两个语句配套使用,是指伪操作命令,讲一段代码定义为一个整体,称为宏命令,然后就可以在程序中通过宏指令。
其中$在宏指令展开时,标号被替换为用户定义的符号。
IRQ_Handler HANDLER IRQ_Exception
$IRQ_LabelHANDLER$IRQ_Exception_Function
对于中断句柄HANDLER可理解为一个宏,当出现这个宏名时,它包含两个参数,编译过后,$IRQ_Label被替换为IRQ_Handler,$IRQ_Exception_Function被替换为IRQ_Exception。
在中断服务程序中,定时器0的中断为UCOS提供周期性时钟节拍。
UCOS的移植代码:
ucos的移植.rar(302.7KB)
下载次数:
1297
2009-3-1020:
53
三:
基于UCOS的嵌入式系统开发
在进行UCOS的嵌入式系统开发前,首先得在PC机上安装ADS1.2软件,同时在网上下载UCOS-ii的源代码,哦,我这个用的是SmartSOPC实验箱,EasyJTAG调试器,当然不同的目标板,其开发工具和开发平台是不同的,不过基于UCOS的嵌入式系统开发的原理应该大致相同。
开始了,首先启动ADS1.2,单击File菜单,在下拉菜单中选择New,在弹出的菜单中选择Project选项,选择ARMExecutableImage,输入文件名和路径地址,在工程模板的空白区单击右键,选择Creatgroup,重复同样的步骤,依次添加gpio,*.h,scf,arm,ucos,user,在相应的组件中添加对应的文件。
完成后的工程模板如图所示:
下载(87.35KB)
2009-3-1019:
49
这个工程中主要包含
1:
UCOS的移植
这部分的代码主要在ucos和ARM目录下,对于ucos目录下的文件,直接将UCOS—II中的.C文件加载到该目录下就可以了。
而对于ARM目录下的文件以及头文件os_cpu.h,在ucos中已经说过了,就不重复了。
2:
启动代码
尽管启动代码虽然在一开始就说了,但是那个文件主要是用来熟悉和了解ARM的启动过程,没有操作系统,没有应用程序,仅仅是对异常复位的中断处理,相对比较简单。
但当移植了操作系统系统后,就有些不一样。
对异常的处理:
在先前写的启动代码中,对异常的处理是直接进入死循环:
例如
;未定义
Undef_HandlerBUndef_Handle ;死循环
而在这个文件中的启动代码,则是对相应的异常,跳转到相应的异常服务程序:
例如
;未定义
UndefinedAddr DCD Undefined
当然了,也有一些异常的处理是直接进入死循环的。
加入Semihosting进制。
这种调试进制是非常有用的,因为用于开发使用的硬件系统经常没有最终系统的所有输入设备和输出设备。
在这种情况下,Semihosting可让主机代替目标系统系统这些设备的功能。
例如:
此系统可以启用C库中的函数(Printf或Scanf)使用主机的键盘和屏幕,而不必使用目标系统的键盘和屏幕。
因此在启动代码中加上:
IMPORT__use_no_semihosting_swi
但在一个实际的应用系统中,不可能靠PC机的键盘和屏幕来输入和显示,因此在最终的影响代码中必须去掉C库中的Semihosting函数。
代码如下
#pragmaimport(__use_no_semihosting_swi)
;该语句在target.c中。
另外就是分散加载文件,在上面的启动代码中,当编译成功后,在ADS编译器中,设置程序的下载位置,代码就会下载指定的位置,而这时下载的代码和数据就存储到一个相对比较连续的空间,没有分开。
当使用分算加载文件后,他可以将数据和代码存储到不同的存储空间。
同时为各模式分配堆栈空间。
为堆栈分配空间,为堆栈定义的符号将在分散加载文件中使用。
3:
加载文件mem.scf
加载文件中主要包括三个域:
加载域,执行域,输入段。
加载域:
ROM_LOAD0x80000000
ROM_LOAD为加载域名,0x80000000为加载域的起始地址。
执行域:
ROM_EXEC0x80000000
ROM_EXEC为执行域名,0x80000000为执行域起始地址。
输入段:
Startup.o(vectors,+First)
Startup.o为Startup.s的目标文件,(vectors,+First):
表示在0x80000000地址处首先存放vectors向量表。
;在启动代码中对堆栈的标号进行定义
AREA Heap,DATA,NOINIT
bottom_of_heap SPACE 1
AREA StackBottom,DATA,NOINIT
bottom_of_Stacks SPACE 1
AREA HeapTop,DATA,NOINIT
top_of_heap
AREA Stacks,DATA,NOINIT
StackUsr
ROM_LOAD0x80000000 ;加载区,从0x80000000
{ROM_EXEC0x80000000 ;执行区的起始地址0x80000000
{
Startup.o(vectors,+First) ;Startup.s中的向量表
*(+RO) ;其他代码
}
IRAM0x40000000;LPC2220片内RAM的起始地址
{
Startup.o(MyStacks);Startup.s中的MyStacks
}
STACKS_BOTTOM+0UNINIT
{
Startup.o(StackBottom)
}
STACKS0x40010000UNINIT;LPC2220片内RAM的结束地址
{
Startup.o(Stacks);Startup.s中的Stacks
}
ERAM0x81000000;片外RAM的起始地址
{
*(+RW,+ZI);文件中的其他变量
}
HEAP+0UNINIT;系统堆空间
{
Startup.o(Heap)
}
HEAP_BOTTOM0x81020000UNINIT;片外RAM的结束地址
{
Startup.o(HeapTop)
}
}
UNINIT:
指示该段不能被初始化为0,使用它可以创建办含未初始化数据和存储器映射的IO执行区。
ERAM0x81000000 ;片外RAM的起始地址
{
*(+RW,+ZI) ;文件中的其他变量
}
HEAP+0UNINIT ;系统堆空间
+0:
表示HEAP的起始地址为81000000+[*(+RW,ZI)],
使用分散加载文件,更加方便和准确的分配RO(只读段),RW(读写段),ZI(零初始化段),堆栈空间,存储空间如图所示:
下载(40.28KB)
2009-3-1019:
49
4:
目标板的初始化
涉及到目标板的初始化主要包括target.h,target.c,.IRQ.s,LPC2294.h
LPC2294.h:
在该文件中主要包含对特殊寄存器的定义和固件函数
/*外部总线控制器*/
#defineBCFG0 (*((volatileunsignedint*)0xFFE00000) /*lpc22xxonly*/
#defineBCFG1 (*((volatileunsignedint*)0xFFE00004)) /*lpc22xxonly*/
#defineBCFG2 (*((volatileunsignedint*)0xFFE00008)) /*lpc22xxonly*/
#defineBCFG3 (*((volatileunsignedint*)0xFFE0000C)) /*lpc22xxonly*/
固件函数:
/*定义固件函数*/
#definerm_init_entry() ((void(*)())(0x7fffff91))()
#definerm_undef_handler() ((void(*)())(0x7fffffa0))()
#definerm_prefetchabort_handler() ((void(*)())(0x7fffffb0))()
#definerm_dataabort_handler() ((void(*)())(0x7fffffc0))()
#definerm_irqhandler() ((void(*)())(0x7fffffd0))()
#definerm_irqhandler2() ((void(*)())(0x7fffffe0))()
#defineiap_entry(a,b) ((void(*)())(0x7ffffff1))(a,b)
IRQ.s:
Target.h文件主要是对Target.c中用到的函数进行定义:
例如externvoidTargetInit(void)。
而在target.c里主要包含定时