Cstyle的札记,Freertos内核详解,第4.3篇

    移植OS汇编是绕不过去的,先从MDK默认提供的s3c2440.s开始,这里我们需要完成以下几个事情:
1.cpu状态和模式
2.设置clock,包括系统时钟,sdram时钟,外设时钟等等
3.设置sdram的时序参数,这个是非常重要的,否则我们的内存就不能使用
4.堆,栈
5.选择初始化外设,IO等
       废话不多说先上图,下面是我花了几天时间在TQ2440+Freertos V8.2.3+MDK5.16上移植的成功的,一共创建了4个任务分别以不同的频率闪烁LED,下面记录下需要注意的细节。因为Freertos对三星的处理器支持不太好,有可能是三星是韩国人开的而Freertos主要是日本的一家公司在维护,日韩关系紧张所以才不维护的吧^_^。说正题下面一个一个来说。应为freertos没有对任何的一款三星的处理器的支持,同时也没有任何一款arm9的处理器在mdk工具下移植,所以我这个算是慢慢摸索。
    1.首先是处理器的初始化,这里选择了处理器时钟400Mz,Fclk:Hclk:Pclk=1:4:8的设置,具体如下,这些都是s3c2440时钟相关的寄存器,在MDK当中startup.s设置很简单:
            CLOCK_SETUP     EQU     1
            LOCKTIME_Val    EQU     0x0FFF0FFF
            MPLLCON_Val     EQU     0x0007F021
            UPLLCON_Val     EQU     0x00038022
            CLKCON_Val      EQU     0x001FFFF0
            CLKSLOW_Val     EQU     0x00000004
            CLKDIVN_Val     EQU     0x00000005
            CAMDIVN_Val     EQU     0x00000000
  2.其次是SDRAM设置,s3c2440虽然是有内在的sram但是只有4KB太小不适合跑操作系统,所以我们使用外部sdram,同样设置简单,使用mdk设置向导,主要关注bank6就行,其他的之后再细究:
            ;----------------------- Memory Controller Definitions -------------------------
            MC_BASE         EQU     0x48000000      ; Memory Controller Base Address
            BWSCON_OFS      EQU     0x00            ; Bus Width and Wait Status Ctrl Offset
            BANKCON0_OFS    EQU     0x04            ; Bank 0 Control Register        Offset
            BANKCON1_OFS    EQU     0x08            ; Bank 1 Control Register        Offset
            BANKCON2_OFS    EQU     0x0C            ; Bank 2 Control Register        Offset
            BANKCON3_OFS    EQU     0x10            ; Bank 3 Control Register        Offset
            BANKCON4_OFS    EQU     0x14            ; Bank 4 Control Register        Offset
            BANKCON5_OFS    EQU     0x18            ; Bank 5 Control Register        Offset
            BANKCON6_OFS    EQU     0x1C            ; Bank 6 Control Register        Offset
            BANKCON7_OFS    EQU     0x20            ; Bank 7 Control Register        Offset
            REFRESH_OFS     EQU     0x24            ; SDRAM Refresh Control Register Offset
            BANKSIZE_OFS    EQU     0x28            ; Flexible Bank Size Register    Offset
            MRSRB6_OFS      EQU     0x2C            ; Bank 6 Mode Register           Offset
            MRSRB7_OFS      EQU     0x30            ; Bank 7 Mode Register           Offset
    3.设置timer,这里使用pwm timer0.这个里面一定要注意几个地方:
            a.时钟设置一定要仔细检查,计算准确,不然timer就不准
            b.中断向量部分,这里选择Preemptive模式(configUSE_PREEMPTION =1)建议使用一个存储在数字段的数组 IntVTAddress[32],有必要的话使用一些mdk编译选
               项关键词__align(4)或者类似的关键词来对齐。我之前使用函数指针数组, 然后把中断函数向量放在         数组当中,结果发现当irq发生的时候,在中断分配函数里面从
               数组里面取地址的时候,不止为何总是跟实际存储的地址有4个字节的偏差,比 如:我存储在指向函数指针的数组 里面的第0x0a个元素,也就 IntVTAddress【 0x0a】
               的时候,在irq里面读取到数组内容的时候,逻辑来说应该是数组基地址+4*0xa的位置,可是不知道为何得到的总是会
              比这个值要偏移4个字节,几番调 试未果,所以就放弃了,换了 一个纯数组的方法,也就是现在的办法,反正数组里面存储的都是每一个外设的中断向量的跳转地址,用
              一个32位整形数组存储和用指向函数指  针的数组并没有本质的差别;至 于为什么用后者不行,暂时还 不知道。
            //all 32 int entry address
            unsigned int IntVTAddress[32];
            static void prvSetupTimerInterrupt( void )
            {
                  uint32_t ulCompareMatch;
                  IntVTAddress[0x0a]=(unsigned int)vPreemptiveTick;                           //设置系统tick中断向量
                  TCFG0=0x000000ff; //Prescaler 255 ,  timer0 1 tick=81.9188us            //设置timer0的工作时钟,分频等            
                  TCFG1=0x00000003;  //mux =1/16 ;                            
                  ulCompareMatch =(uint32_t)(1000/81.9188);//1221 tick=1ms            //设置系统tick最小单位1ms
                  TCNTB0 =ulCompareMatch;                                                                    //预装初始值
                  //TCMPB0= ulCompareMatch;
                    INTMOD=0x0;                                                                                        //all irq ,not fiq
                 PRIORITY=0x0;                                                                                           //irq Priority rotate disable
                 INTSUBMSK=0xffffffff;
                 INTMSK &=(~(0x1<<10));                                                                        //bit10 enable timer0 intttupt
                 SUBSRCPND=0xffffffff;                                                                              //clear all pending irq frist
                 SRCPND=0xffffffff;
                 INTPND=0xffffffff;
 
                 TCON |= (1<<1);
                 TCON =0x09;                                                                                                //start timer0,should not enable the cpu interrupt bit I or F bit,it will be enabled at  portRESTORE_CONTEXT                         
            }
    4.中断向量表的查询函数,这个是跟体系结构相关,其他的处理器还没见过这种需要用户自己处理的情况。这个函数相对简单,无非是从 INTOFFSET寄存器读出当前的
    中断是哪一个,然后使用中断号码,在上面建 立的中断向量表中查询,然后跳转到里面去执行,我们目前就一个irq也就是timer0.注意的是每个中断
    向量是4个字节,一般来说是4字节对齐。从IRQ_Handler跳到中断服务函数之后,系统处理器的状态如下, 通用寄存器的值跟中断之前一样没有被破
    坏,中断返回地址被保存在irq模式的LR寄存器当中,PC指向了中断服务函数。
       IRQ_Handler     PROC        
              IMPORT IntVTAddress 
              sub sp,sp,#4                       ;reserved for PC
             stmfd sp!,{r8-r9}                //    函数需要使用r8 ,r9 先保存在irq栈
               
             ldr r9,=INTOFFSET            //读中断类型
             ldr r9,[r9]
             ldr r8,=IntVTAddress        //取中断向量表的基地址
             add r8,r8,r9,lsl #2            //每个中断向量是4字节
             ldr r8,[r8]                        //读取中断服务函数的入口
             str r8,[sp,#8]                    //存入堆栈
             ldmfd sp!,{r8-r9,pc}       //把PC弹出来,刚好跳转到中断服务函数里面去
      ENDP

    5.关于cpu模式,这里我们选择的是sys模式,注意必须是要在进入main之前设置好,否则可能有问题,在startup.s里面设置就好了。发现如果你在进入__main之前设置了usr
        态的堆栈,系统在跑__main之后,main之前就会自动把系统设置为usr模式,这个可能是mdk里面的默认状态,所以你只需要把startup里面的设置usr模式栈的部分注释掉就
        可以了 。
              ;  Enter User Mode and set its Stack Pointer
              ;  MSR     CPSR_c, #Mode_USR
              ;  MOV     SP, R0
              ;  SUB     SL, SP, #USR_Stack_Size

             ;  Enter User Mode and set its Stack Pointer
             ;   MSR     CPSR_c, #Mode_USR
              ;  IF      :DEF:__MICROLIB
             ;   EXPORT __initial_sp
             ;   ELSE
              ;  MOV     SP, R0
             ;   SUB     SL, SP, #USR_Stack_Size
             ;   ENDIF
    6.设置好sys和svc模式下的堆栈,在mdk startup向导设置就行了。0x300B貌似够用,当然我们板子上有64MB的内存,你随便设置一个也可以。
    7.级的在timer0 isr   vPreemptiveTick 里面清中断标志。
            MOV R0,#BIT_TIMER0     ;clear s3c2440 timer0 interrupt 
         LDR R1, =SRCPND
         STR R0, [R1]
 
         MOV R0,#BIT_TIMER0
         LDR R1, =INTPND
         STR R0, [R1]
 8.portRESTORE_CONTEXT,portSAVE_CONTEXT这两个不需要修改。它两的最主要的功能是在timer0 irq到来的时候或者是swi到来的时候,我们通过把当前通用寄存器保存在pxCurrentTCB所指向的tcb里面的堆栈里面,同时根据不同的策略选择下一个需要执行的任务,也就是把pxCurrentTCB指向一个新的或者原来的tcb,然后把tcb里面堆栈里存的数据,回填到通用寄存器里面,最后都使用irq返回的方式,对pc值做一定的调整( SUBS PC, LR, #4)之后,跳到对应的任务,完成任务切换。至于是选哪一个任务,用什么策略来选择,我们都是通过c语言来实现,的真正需要汇编的地方就很少。需要注意的是,这里的这两个函数默认的算法里面,要求cpu初始的时候工作在sys模式,不然你就要修改这里面的汇编,也就是前面说的进入main之前,设置好cpu为sys模式。

9.关于最简单的测试代码,这里选择LED flash,刚好我板子上有4个LED。建立4个任务,同样的优先级,轮流闪烁,分别实现了1s,2s,3s,4s不同的周期的闪烁。代码很简单如下,这里有一个经验就是当你没有合适的调试工具,如jlink的时候,你 可以选择在代码流程当中适当的地方插入代码点亮或者熄灭led灯来做简单的诊断,虽然这个人肉debug方法很残忍,但是除非你很牛能够一次写出毫无错误的代码,不然这个是你唯一的选择。
 #define ledNUMBER_OF_LEDS ( 4 )
#define ledFLASH_RATE_BASE ( ( TickType_t ) 1000 )

    void vParTestToggleLED( unsigned portBASE_TYPE uxLED )
    {

        unsigned long ulLED = partstFIRST_IO, ulCurrentState;
         if( uxLED < partstNUM_LEDS )
         {
              /* Rotate to the wanted bit of port 0.  Only P10 to P13 have an LED
              attached. */
              ulLED <<= ( unsigned long ) uxLED;

              /* If this bit is already set, clear it, and vice versa. */
              ulCurrentState = GPBDAT;
               if( ulCurrentState & ulLED )
              {
                    GPBDAT &= (~ulLED)&0x000001e0;//only bit 5,6,7,8 ,other bit set to 0
              }
              else
              {
                    GPBDAT |= ulLED&0x000001e0;//only bit 5,6,7,8 ,other bit set to 0
              }
            }
}
Cstyle的札记,Freertos内核详解,第4.3篇_第1张图片
先到这里,下面再来看看细节部分,欢迎有对rtos有兴趣的人一起讨论。

转载请注明出处
[email protected]  //   http://blog.csdn.net/CStyle_0x007




你可能感兴趣的:(Cstyle的嵌入式导读,Freertos内核详解)