上几周移植ucos到44B0 去,免得受“裸奔”之苦,折磨了一天后终于成功 ,主要是想能在BootLoader下在到SDRAM中也能跑,网上盛传的程序我看过,应该是可以在FLASH中跑,但在SDRAM中跑就会停掉,其实只是少了几句话而已
1.移植底层文件:
包括OS_CPU.H,OS_CPU_C.C,OS_CPU_A.S
分析OS_CPU.H
/*
*****************************************************************************
* DATA TYPES
* (Compiler Specific)
*****************************************************************************
*/
typedef unsigned char BOOLEAN;
typedef unsigned char INT8U; /* Unsigned 8 bit quantity */
typedef signed char INT8S; /* Signed 8 bit quantity */
typedef unsigned short INT16U; /* Unsigned 16 bit */
typedef signed short INT16S; /* Signed 16 bit */
typedef unsigned int INT32U; /* Unsigned 32 bit */
typedef signed int INT32S; /* Signed 32 bit */
typedef float FP32; /* floating point */
typedef double FP64; /* floating point */
typedef unsigned int OS_STK; /* Each stack entry */
typedef unsigned int OS_CPU_SR; /* 定义CPU状态寄存器数据宽度
#define BYTE INT8S /* Define data types */
#define UBYTE INT8U /* ... to uC/OS V1.xx.*/
#define WORD INT16S /* ... uC/OSII. */
#define UWORD INT16U
#define LONG INT32S
#define ULONG INT32U
开头定义了一写数据类型:
接着是与 ARM 体系结构相关的一些定义:
#define OS_CRITICAL_METHOD 2 /* 选择开、关中断的方式. */
#if OS_CRITICAL_METHOD == 2
#define OS_ENTER_CRITICAL() ARMDisableInt() /*进入临界区*/
#define OS_EXIT_CRITICAL() ARMEnableInt() /*退出临界区*/
#endif
#if OS_CRITICAL_METHOD == 3
#define OS_ENTER_CRITICAL() (cpu_sr = OSCPUSaveSR())
#define OS_EXIT_CRITICAL() (OSCPURestoreSR(cpu_sr))
#endif
#define OS_TASK_SW OSCtxSw /*上下文切换*/
/*
* Definitions specific to ARM/uHAL
*/
#define SVC32MODE 0x13
/* stack stuff */
#define OS_STK_GROWTH 1 /* 方向 堆栈是从上往下长的 */
/* angel takes up stack */
#define SEMIHOSTED_STACK_NEEDS 1024
/* idle task stack size (words) */
#ifdef SEMIHOSTED
#define OS_IDLE_STK_SIZE (32+SEMIHOSTED_STACK_NEEDS)
#else
#define OS_IDLE_STK_SIZE 256
#endif
这里注意的是OS_CRITICAL_METHOD ,ucos提供了3种方法实现,第一种方法是直接简单的开关中断方式,但是一旦嵌套会发生意外,比如:
void Task (void *data)
{
.........
OS_ENTER_CRITICAL();
//进入临界区1
//调用了某一函数,该函数也需要进入临界区:
{
OS_ENTER_CRITICAL();
................
OS_EXIT_CRITICAL();
}
//这里就自然变成废墟了
...........
//临界区切掉
OS_EXIT_CRITICAL();
}
此方法太多弊端,所以新内核中看不到她的影子了
于是出现第二种方法,执行OS_ENTER_CRITICAL()时首先保存中断状态到堆栈中,然后关中断,执行OS_EXIT_CRITICAL()时,再从堆栈中恢复原来的中断开/关状态。这种方法不会改变中断状态,由于用到堆栈,这样会带来隐忧,看邵贝贝翻译的有这样说:
“但是,用户在使用这种方法的时候还得十分小心,因为如果用户在调用象OSTimeDly()之类的服务之前就禁止中断,很有可能用户的应用程序会崩溃。发生这种情况的原因是任务被挂起直到时间期满,而中断是禁止的,因而用户不可能获得节拍中断!很明显,所有的PEND调用都会涉及到这个问题,用户得十分小心。一个通用的办法是用户应该在中断允许的情况下调用μC/OS-Ⅱ的系统服务!”
第3种方法直接保存到任务局部变量中去
#if OS_CRITICAL_METHOD == 3
#define OS_ENTER_CRITICAL() (cpu_sr = OSCPUSaveSR())
#define OS_EXIT_CRITICAL() (OSCPURestoreSR(cpu_sr))
#endif
避免了使用堆栈
接着声明在os_cpu_a.s里定义的函数
extern void OSCtxSw(void); // task switch routine
extern void OSIntCtxSw(void); // interrupt context switch
extern void ARMDisableInt(void); // disable global interrupts
extern void ARMEnableInt(void); // enable global interrupts
extern void OSTickISR(void); // timer interrupt routine
OS_CPU_SR OSCPUSaveSR(void);
void OSCPURestoreSR(OS_CPU_SR cpu_sr);
接着搞汇编底层代码实现移植
文件OS_CPU_A.S
前面一段,是声明外部变量:
IMPORT OSTCBCur ; 指向当前任务TCB的指针.
addr_OSTCBCur DCD OSTCBCur
IMPORT OSTCBHighRdy ; 指向将要运行的任务TCB的指针.
addr_OSTCBHighRdy DCD OSTCBHighRdy
IMPORT OSPrioCur ; 当前任务的优先级.
addr_OSPrioCur DCD OSPrioCur
IMPORT OSPrioHighRdy ; 将要运行的任务的优先级.
addr_OSPrioHighRdy DCD OSPrioHighRdy
这些全局变量在uCOS_II.H中:
OS_EXT OS_TCB *OSTCBCur; /* Pointer to currently running TCB */
OS_EXT OS_TCB *OSTCBHighRdy; /* Pointer to highest priority TCB R-to-R */
OS_EXT INT8U OSPrioCur; /* Priority of current task */
OS_EXT INT8U OSPrioHighRdy; /* Priority of highest priority task */
只要拿到任务TCB指针和优先级,就可以随心所欲地调度了!!
1.OSStartHighRdy 启动最高优先级就绪任务函数
uC/OS-II启动时使用 OSStartHighRdy 运行第一个任务.首先OSRunning内容置1,说明已经在运行了,然后获取准备调度TCB指针addr_OSTCBHighRdy,sp指向addr_OSTCBHighRdy,就是把当前任务指针置为下一调度任务了,接着sp指向的准备调度任务的数据栈全部出栈至r1-r14,于是,最新得以运行的任务得到处理器的使用权。
EXPORT OSStartHighRdy
OSStartHighRdy
LDR r0, =OSRunning ; OSRunning = 1,告诉 uC/OS-II自身已经运行.
MOV r1, #1
STRB r1, [r0] ;r1=1写入[r0]
LDR r5, addr_OSTCBHighRdy ; Get highest priority task TCB address得到将要运行的TCB地址
LDR r5, [r5] ; get stack pointer得内容
LDR sp, [r5] ; switch to the new stack
LDMFD sp!, {r4} ; get new state from top of the stack 出栈
MSR CPSR_cxsf, r4 ; CPSR should be SVC32Mode 存到cpsr
LDMFD sp!, {r0-r12, lr, pc } ; start the new task 出栈
2.OS_TASK_SW 任务级任务切换函数,将在OS_Sched调度函数中调用,功能是上下文切换
注意一定要在ARM的系统模式下进行任务切换。首先是进行当前任务的保存工作,把PC,LR,R0-R12保存到任务栈中,当前TCB保存堆栈指针sp;然后把准备调度任务置到当前任务:OSPrioCur = OSPrioHighRdy,OSTCBCur = OSTCBHighRdy,最后获得新任务指针,接着从指针获得新任务数据栈,出栈到各寄存器,开始新任务调度。
EXPORT OSCtxSw
OSCtxSw
STMFD sp!, {lr} ; save pc 因为处理器不支持压pc,故用调用来获得,调用时lr得pc值
STMFD sp!, {lr} ; save lr
STMFD sp!, {r0-r12} ; save register file and ret address 就是保存所有寄存器
MRS r4, CPSR ;获取CPSR
STMFD sp!, {r4} ; save current PSR,保存CPSR进栈
LDR r4, addr_OSTCBCur ;获取当前任务TCB指针
LDR r5, [r4] ;读取内容
STR sp, [r5] ; store sp in preempted tasks TCB,保存sp
; OSPrioCur = OSPrioHighRdy
LDR r4, addr_OSPrioCur
LDR r5, addr_OSPrioHighRdy
LDRB r6, [r5]
STRB r6, [r4] ;把准备调度任务置到当前任务
; OSTCBCur = OSTCBHighRdy
LDR r4, addr_OSTCBHighRdy
LDR r5, addr_OSTCBCur
LDR r4, [r4]
STR r4, [r5] ;把准备调度TCB置当前TCB
LDR sp, [r4] ; 获取新任务堆栈指针 OSTCBHighRdy->OSTCBStkPtr.
LDMFD sp!, {r4} ; 恢复 CPSR
MSR CPSR_cxsf, r4 ; YYY+
LDMFD sp!, {r0-r12, lr, pc} ; 恢复 R12-R0,LR,PC.运行新任务
3.OSIntCtxSW 中断级任务切换 众所周知, ucos2是可剥夺式内核,故此中断返回不一定要返回到原来中断 前的任务,OSIntCtxSW 的作用就是用来剥夺的...,和OS_TASK_SW(),差不多,也是进行任务级置换,并且新任务数据出栈,开始执行。
EXPORT OSIntCtxSw
OSIntCtxSw
;OSPrioCur = OSPrioHighRdy
LDR r6, addr_OSPrioHighRdy
LDR r5, addr_OSPrioCur
LDRB r6, [r6]
STRB r6, [r5] ;把准备调度任务置到当前任务
;OSTCBCur = OSTCBHighRdy
LDR r4, addr_OSTCBHighRdy
LDR r5, addr_OSTCBCur
LDR r4, [r4]
STR r4, [r5] ;TCB置换
LDR sp, [r4] ;获取新任务各数据
LDMFD sp!, {r4} ; pop new task cpsr
MSR cpsr_cxsf, r4
LDMFD sp!, {r0-r12,lr,pc} ; pop new task r0-r12,lr & pc 运行新任务
4.OSTickISR 内核时钟中断函数,此函数可谓非常重要,为内核提供时钟节拍,如同人的心跳!是和时钟中断函数一起的,移植时出错一般也在这里
中断函数,先保存现场
LINK_SAVE DCD 0
PSR_SAVE DCD 0
EXPORT OSTickISR
OSTickISR
STMFD sp!, {r4}
LDR r4, =LINK_SAVE
STR lr, [r4] ; LINK_SAVE = lr_irq
MRS lr, spsr
STR lr, [r4, #4] ; PSR_SAVE = spsr_irq
LDMFD sp!, {r4}
ORR lr, lr, #0x80 ; Mask irq for context switching before
MSR cpsr_cxsf, lr ; returning back from irq mode.
SUB sp, sp, #4 ; Space for PC
STMFD sp!, {r0-r12, lr}
LDR r4, =LINK_SAVE
LDR lr, [r4, #0]
SUB lr, lr, #4 ; lr = LINK_SAVE - 4,
STR lr, [sp, #(14*4)] ; the return address for pc.
LDR r4, [r4, #4] ; r4 = PSR_SAVE,
STMFD sp!, {r4} ; CPSR of the task
;保存当前任务的tcb
LDR r4, addr_OSTCBCur
LDR r4, [r4]
STR sp, [r4] ; OSTCBCur -> stkptr = sp
LDR r0, =I_ISPC
LDR r1, =BIT_TIMER0
STR r1, [r0]
BL OSIntEnter ;为调度器加锁
BL OSTimeTick ;调用时钟计数
BL OSIntExit ;调度器解锁
;恢复寄存器
LDMFD sp!, {r4} ; pop new task cpsr
MSR cpsr_cxsf, r4
LDMFD sp!, {r0-r12,lr,pc} ; pop new task r0-r12,lr & pc
5. 开关中断
ARMDisableInt关 IRQ 中断
ARMEnableInt开 IRQ 中断
下面是方法2,我在ARM9 移植是用方法3,感觉还是方法3较好吧。
EXPORT ARMDisableInt
ARMDisableInt
MRS r0, cpsr ;r0获取cpsr
STMFD sp!, {r0} ; push current PSR
ORR r0, r0, #0xC0
MSR cpsr_c, r0 ; disable IRQ Int s
MOV pc, lr ;返回
EXPORT ARMEnableInt
ARMEnableInt
LDMFD sp!, {r0} ; pop current PSR
MSR cpsr_c, r0 ; restore original cpsr
MOV pc, lr
方法3:
EXPORT OSCPUSaveSR
OSCPUSaveSR
MRS r0,CPSR
ORR r1,r0,#0xC0
MSR CPSR_c,r1
MOV pc,lr
EXPORT OSCPURestoreSR
OSCPURestoreSR
MSR cpsr_c,r0
MOV pc,lr
底层代码完毕!!
接着看OS_CPU_C.C,这个也是移植文件之一
主要是任务堆栈初始化函数OSTaskInit(), Hook函数不处理
OSTaskInit是任务堆栈的初始函数,是用户的堆栈,不是处理器的堆栈
OS_STK *OSTaskStkInit(void (*task)(void *pd), void *pdata, OS_STK *ptos, INT16U opt)
{
unsigned int *stk;
opt = opt; /* 未用到的参数*/
stk = (unsigned int *)ptos; /* 装载任务堆栈指针*/
/* 建立上下文*/
*--stk = (unsigned int) task; /* pc */
*--stk = (unsigned int) task; /* lr */
*--stk = 0; /* r12 */
*--stk = 0; /* r11 */
*--stk = 0; /* r10 */
*--stk = 0; /* r9 */
*--stk = 0; /* r8 */
*--stk = 0; /* r7 */
*--stk = 0; /* r6 */
*--stk = 0; /* r5 */
*--stk = 0; /* r4 */
*--stk = 0; /* r3 */
*--stk = 0; /* r2 */
*--stk = 0; /* r1 */
*--stk = (unsigned int) pdata; /* r0 */
*--stk = (SVC32MODE|0x0); /* cpsr IRQ, FIQ disable */
return ((void *)stk);
}
ucos-ii移植笔记3(ARM7平台)--加入机器代码实现BootLoader下到SDRAM运行
把ucos底层代码做好后,接下来应该搭建控制台程序完成整个内核的平台实现,一般就是目标板的GPIO初始化,Uart,时钟初始,以及中断初始等
这里最重要一点是时钟中断的实现!
首先实现目标板初始
void ARMTargetInit(void)
{
//配置Cache
rSYSCFG = SYSCFG_8KB ; //使用8K字节的指令缓存
rNCACHBE0=((unsigned int)(Non_Cache_End>>12)<<16)|(Non_Cache_Start>>12);
Port_Init(); //初始化端口
Delay( 200 ) ; //delay some time
Led_Set( 0x0f ); //LED全亮
Delay( 500 ) ; //delay some time
Led_Set( 0x00 ); //LED全亮
ChangePllValue( 58, 3, 1 ) ; //设置CPU频率为66M
Uart_Init( 0, 115200 ); //设置串口0的速率为115200bps
Uart_Select(0); //选择串口0
ARMInitInterrupts(); //初始化中断,设置中断服务程序
Delay(0); //调整延时
}
中断初始化:首先要知道在SDRAM里中断和在FLash是不同的,ARM响应中断时,会跳至开头的几个地址,这个在启动代码中有详细说明:
ResetEntry
b ResetHandler ;0x00000000
b HandlerUndef ;0x00000004
b HandlerSWI ;0x00000008
b HandlerPabort ;0x0000000C
b HandlerDabort ;0x00000010
b . ;0x00000014
b HandlerIRQ ;0x00000018
b HandlerFIQ ;0x0000001C
比如现在我实现的是IRQ中断,那么ARM响应中断时候会先跳至0x00000018,然后在0x00000018地址的内容读取二次跳转地址,如果是FLash中实现中断,那么会直接运行,中断正常;但如果在BootLoader下,由于BootLoader占据了FLash的开头的空间,所以要想实现
BootLoader下到SDRAM运行,首先确认BootLoader支持中断,一般UBoot是支持的,如果你在BootLoader下到SDRAM运行中断测试程序正常,那么可以确认BootLoader是支持中断。
/********************************************************************************************************
* 中断初始化,设置中断服务程序 *
********************************************************************************************************/
void ARMInitInterrupts(void)
{
int i;
/*映射*/
for(i=_RAM_STARTADDRESS;i<(_RAM_STARTADDRESS+0x20);i+=4)
{
*((volatile unsigned *)i)=0xEA000000+0x1FFE;
}
// Non-vectored,IRQ disable,FIQ disable
rINTCON = 0x05 ;
// All=IRQ mode
rINTMOD = 0x0 ;
// All interrupt is masked.
rINTMSK |= BIT_GLOBAL|BIT_EINT4567;
//set interrupt vector routine
// pISR_RESET //reserved
pISR_UNDEF = (unsigned) DebugUNDEF;
pISR_SWI = (unsigned) DebugSWI;
pISR_PABORT = (unsigned) DebugPABORT;
pISR_DABORT = (unsigned) DebugDABORT;
// pISR_RESERVED = (unsigned) BreakPoint; //not used
// pISR_IRQ = (unsigned) 0; //reserved
pISR_FIQ = (unsigned) DebugFIQ;
pISR_ADC= (unsigned) BreakPoint;
pISR_RTC= (unsigned) BreakPoint;
pISR_UTXD1= (unsigned) BreakPoint;
pISR_UTXD0= (unsigned) BreakPoint;
pISR_SIO= (unsigned) BreakPoint;
pISR_IIC= (unsigned) BreakPoint;
pISR_URXD1= (unsigned) BreakPoint;
pISR_URXD0= (unsigned) BreakPoint;
pISR_TIMER5= (unsigned) BreakPoint;
pISR_TIMER4= (unsigned) BreakPoint;
pISR_TIMER3= (unsigned) BreakPoint;
pISR_TIMER2= (unsigned) BreakPoint;
pISR_TIMER1= (unsigned) BreakPoint;
pISR_TIMER0= (unsigned) BreakPoint;
pISR_UERR01= (unsigned) BreakPoint;
pISR_WDT= (unsigned) BreakPoint;
pISR_BDMA1= (unsigned) BreakPoint;
pISR_BDMA0= (unsigned) BreakPoint;
pISR_ZDMA1= (unsigned) BreakPoint;
pISR_ZDMA0= (unsigned) BreakPoint;
pISR_TICK= (unsigned) BreakPoint;
// pISR_EINT4567= (unsigned) OSEINT4567ISR;
pISR_EINT4567= (unsigned)Key_Int;
pISR_EINT3= (unsigned) BreakPoint;
pISR_EINT2= (unsigned) BreakPoint;
pISR_EINT1= (unsigned) BreakPoint;
pISR_EINT0= (unsigned) BreakPoint;
pISR_TIMER0= (unsigned) OSTickISR;
rINTMSK =~( BIT_GLOBAL|BIT_EINT4567);
}
这里注意一句话:
for(i=_RAM_STARTADDRESS;i<(_RAM_STARTADDRESS+0x20);i+=4) { *((volatile unsigned *)i)=0xEA000000+0x1FFE; }
这是移植的关键,首先看跳转码,这里引用hitlerisyou的大作:
“跳转指令的格式为
31 282725 24 23 0
__________________________
|cond |101| L |Signed_immed_24|
|___ |___|_ |______________ |
其中cond为该条指令执行的条件码
101 是25,26,27三位的固定值
L 决定是非保存返回地址,以便返回使用
signed_immed_24是跟跳转目标地址有关的值,呆会讲该值的计算方法
那么
0xEA000000+0x1FFE
就是一条跳转指令,它的条件码为1110,表示无条件跳转;L为0表示不保存返回值(因为这里修改的是异常处理入口,不用返回值);0x1FFE指定跳转目的地址。
下面我们看看signed_immed_24的计算方法:
1.将PC寄存器作为该条跳转指令的基地址;
2.用目标地址减去基地址,生成跳转偏移量(程序要保证该偏移量小于33554432);
3.将这个值的bit[25:2]填入signed_immed_24中。
因此0x1FFE的意义就是目标地址相对当前地址为 0x1FFE*4+8=0x8000
现在我们就明白了这段代码是将RAM开始的32个字节中填入了8个跳转指令,跳转的目标地址为当前指令地址+0x8000
如果你把你的程序加载到_RAM_STARTADDRESS+0x8000(在这块板子上就是0x0C008000),那么这几个异常中断都会跳转到你自己程序中的异常中断处理处,执行你自己的处理过程。”
/********************************************************************************************************
* Initialize timer that is used OS *
********************************************************************************************************/
void ARMInitTimers(void)
{
/*IRQ中断*/
rINTCON = 5;
/*全部为IRQ中断*/
rINTMOD = 0;
/*屏蔽*/
rINTMSK=(BIT_GLOBAL|BIT_TIMER0);
pISR_TIMER0= (unsigned int)OSTickISR;
//dead zone=0, pre0= 150
/*分频150*/
rTCFG0= 0x00000095;
//all interrupt, mux0= 1/16
rTCFG1= 0x00000003;
//set T0 count
rTCNTB0= _CLOCK;
rTCON |=0x00000002;
rTCON &= 0xfffffff0;
rTCON |= 0x0000000d;
}
/********************************************************************************************************
* enable the interrupt *
********************************************************************************************************/
void ARMInstallSystemTimer(void)
{
rINTMSK=~( BIT_TIMER0 | BIT_GLOBAL ); //Default value=0x7ffffff
}
void Main(void)
{
U16 ver ;
ARMTargetInit(); //目标板初始化
OSInit(); //uCOS-II初始化
Uart_Printf("OS Initing/n");
OSTimeSet(0);
//创建主任务
OSTaskCreate( Main_Task,
(void *)0,
(OS_STK *)&Main_Task_Stack[TASK_STACK_SIZE-1],
Main_Task_PRIO ) ;
Uart_Printf("OS Starting/n/n");
OSStart(); //Start uCOS-II
/* never reached */
}
注意ARMInitTimers时钟开启要在创建任务后再在任务开,不然可能会引起崩溃