ucos-ii移植笔记(ARM7平台)

这是我早期在CSDN博客 http://blog.csdn.net/benny_cen/archive/2009/02/23/3929625.aspx上发表的,一共有三篇,现在把三篇全部整合在一起;ucosii内核较小,但五脏俱全,是学习OS的较好入手的内核。
注:以下代码全部在ARM平台开发板通过

上几周移植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_OSTCBHighRdysp指向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 = OSPrioHighRdyOSTCBCur  = 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时钟开启要在创建任务后再在任务开,不然可能会引起崩溃

你可能感兴趣的:(timer,OS,任务,平台,delay,returning)