Porting FreeRtos to LPC2478 under Keil

tns1 wrote on Tuesday, January 27, 2009:

I see some have already tried this, so I wonder if you could look at my list of changes and see if they make sense.
Right now, the first tasks gets launched, but as soon as I call taskSuspend it then it hangs up in the idle task and subsequent tasks to not get launched.
thanks

Create uVision project for LPC2478 (which uses LPC2400.s)
Start with FreeRtos v5.1.1 demo ARM7_LPC2129_Keil_RVDS

use Keil’s lpc23xx.h instead of lpc21xx.h

add LPC2400.s to project, edit to add :
IMPORT    vPortYieldProcessor   
SWI_Addr        DCD     vPortYieldProcessor   
also comment out user mode init and force svr mode before jump to C

add NO_EMC_SETUP to asm options - just want simplest simulation of code in flash memory

edit portable.h to create new device ARM7_LPC2478_KEIL_RVDS 
create folder RVDS/ARM7_LPC2478 for new device by copying folder RVDS/ARM7_LPC21xx
add new device symbol and paths to uvision project options

edit portASM.s to match datasheet and use of T0MR1
;;VICVECTADDR    EQU    0xFFFFF030
VICVECTADDR    EQU    0xFFFFFF00
T0IR        EQU    0xE0004000
;;T0MATCHBIT    EQU    0x00000001
T0MATCHBIT    EQU    0x00000002

edit port.c prvSetupTimerInterrupt() by examining GCC demo for 23xx :
change T0MR0 to T0MR1
change VICVectAddr0 to VICVectAddr4
change VICVectCntl0 to VICVectCntl4

tns1 wrote on Wednesday, January 28, 2009:

Also in port.c :
change     VICVectCntl4 = 1;//priority 1 (0 is highest)

I must not have gotten all the changes necessary. Under the simulator, once the processor gets into IRQ mode by the first timer tick, it never comes out.

I have the timer working, and interrupts are happening at the proper interval.
Using keils peripheral view, I can see the timer interrupt being set and cleared as expected,
but I do not see the interrupt being acknowledged in the vic vicaddress register even though
it is being written to by the code. I am not sure it would actually be cleared though.

I also wonder about the stack setup in LPC2400.s. The user SP is never set since once you enter user mode, you can’t easily hop back to svr mode. How to set the user SP and also start in svr mode?

davedoors wrote on Wednesday, January 28, 2009:

How have you installed the IRQ handler? Does it vector directly to the peripheral, or does it vector to a common interrupt entry function?

The IRQ should be installed something like this, although this might not be quite right:

ldr   pc, [pc,#-0xFF0]                /* IRQ - read the VIC        */

so the whole vector table looks something like this:

    b     _start                        /* reset - _start            */
    ldr   pc, _undf                        /* undefined - _undf        */
    ldr   pc, _swi                        /* SWI - _swi                */
    ldr   pc, _pabt                        /* program abort - _pabt    */
    ldr   pc, _dabt                        /* data abort - _dabt        */
    nop                                    /* reserved                    */
    ldr   pc, [pc,#-0xFF0]                /* IRQ - read the VIC        */
    ldr   pc, _fiq                        /* FIQ - _fiq                */

_undf:  .word __undf                    /* undefined                */
_swi:   .word vPortYieldProcessor       /* SWI                        */
_pabt:  .word __pabt                    /* program abort            */
_dabt:  .word __dabt                    /* data abort                */
_fiq:   .word __fiq                     /* FIQ                        */

__undf: b     .                         /* undefined                */
__pabt: b     .                         /* program abort            */
__dabt: b     .                         /* data abort                */
__fiq:  b     .                         /* FIQ                        */

tns1 wrote on Wednesday, January 28, 2009:

Here is all the modified code, changes marked with ***. Your piece looks like it is for a 21xx under GCC, so similar steps but different syntax. The Vic Vector register is different for 24xx parts.

BTW, why is a read from VicAddress (xFFFFFF00) coded like LDR PC, [PC, #-0x0120] ?
PC is 0x18, so this appears to evaluate to PC <- [PC+(#immed)+8]. Somewhere I saw the extra 8byte
offset documented, but I can’t find it now.

***LPC2400.s startup & vector table mods

        IMPORT    vPortYieldProcessor    ;***   
Vectors         LDR     PC, Reset_Addr        
                LDR     PC, Undef_Addr
                LDR     PC, SWI_Addr
                LDR     PC, PAbt_Addr
                LDR     PC, DAbt_Addr
                NOP                            ; Reserved Vector
;               LDR     PC, IRQ_Addr
                LDR     PC, [PC, #-0x0120]     ; Vector from VicVectAddr (xFFFFFF00)
                LDR     PC, FIQ_Addr

Reset_Addr      DCD     Reset_Handler
Undef_Addr      DCD     Undef_Handler
SWI_Addr        DCD     vPortYieldProcessor    ;***    
PAbt_Addr       DCD     PAbt_Handler
DAbt_Addr       DCD     DAbt_Handler
                DCD     0                      ; Reserved Address
IRQ_Addr        DCD     IRQ_Handler
FIQ_Addr        DCD     FIQ_Handler

Undef_Handler   B       Undef_Handler
SWI_Handler     B       SWI_Handler
PAbt_Handler    B       PAbt_Handler
DAbt_Handler    B       DAbt_Handler
IRQ_Handler     B       IRQ_Handler
FIQ_Handler     B       FIQ_Handler

; Setup Stack for each mode ----------------------------------------------------
                LDR     R0, =Stack_Top
;  Enter Undefined Instruction Mode and set its Stack Pointer
                MSR     CPSR_c, #Mode_UND:OR:I_Bit:OR:F_Bit
                MOV     SP, R0
                SUB     R0, R0, #UND_Stack_Size
;  Enter Abort Mode and set its Stack Pointer
                MSR     CPSR_c, #Mode_ABT:OR:I_Bit:OR:F_Bit
                MOV     SP, R0
                SUB     R0, R0, #ABT_Stack_Size
;  Enter FIQ Mode and set its Stack Pointer
                MSR     CPSR_c, #Mode_FIQ:OR:I_Bit:OR:F_Bit
                MOV     SP, R0
                SUB     R0, R0, #FIQ_Stack_Size
;  Enter IRQ Mode and set its Stack Pointer
                MSR     CPSR_c, #Mode_IRQ:OR:I_Bit:OR:F_Bit
                MOV     SP, R0
                SUB     R0, R0, #IRQ_Stack_Size
;  Enter Supervisor Mode and set its Stack Pointer
                MSR     CPSR_c, #Mode_SVC:OR:I_Bit:OR:F_Bit
                MOV     SP, R0
                SUB     R0, R0, #SVC_Stack_Size
;  Enter User Mode and set its Stack Pointer - skip this ***
;                MSR     CPSR_c, #Mode_USR
;                IF      :DEF:__MICROLIB;
;                EXPORT __initial_sp
;                ELSE
;                MOV     SP, R0
;                SUB     SL, SP, #USR_Stack_Size
;                ENDIF
; Enter the C code -------------------------------------------------------------
                IMPORT  __main
                LDR     R0, =__main
                BX      R0
                IF      :DEF:__MICROLIB
                EXPORT  __heap_base
                EXPORT  __heap_limit
                ELSE
; User Initial Stack & Heap
                AREA    |.text|, CODE, READONLY
                IMPORT  __use_two_region_memory
                EXPORT  __user_initial_stackheap
__user_initial_stackheap
                LDR     R0, =  Heap_Mem
                LDR     R1, =(Stack_Mem + USR_Stack_Size)
                LDR     R2, = (Heap_Mem +      Heap_Size)
                LDR     R3, = Stack_Mem
                BX      LR
                ENDIF

***port.c mods

#define portTIMER_MATCH_ISR_BIT        ( ( unsigned portCHAR ) 0x02 )

static void prvSetupTimerInterrupt( void )
{
unsigned portLONG ulCompareMatch;

    PCLKSEL0 = (PCLKSEL0 & (~(0x3<<2))) | (0x01 << 2); //***setup for CCLK
    T0TCR  = 2;         /* Stop and reset the timer */ //***
    T0CTCR = 0;         /* Timer mode               */ //***

    /* A 1ms tick does not require the use of the timer prescale.  This is
    defaulted to zero but can be used if necessary. */
    T0PR = portPRESCALE_VALUE;

    /* Calculate the match value required for our wanted tick rate (1ms). */
    ulCompareMatch = configCPU_CLOCK_HZ / configTICK_RATE_HZ;

    /* Protect against divide by zero.  Using an if() statement still results
    in a warning - hence the #if. */
    #if portPRESCALE_VALUE != 0
    {
        ulCompareMatch /= ( portPRESCALE_VALUE + 1 );
    }
    #endif
    T0MR1 = ulCompareMatch; //*** timer match value

    /* Generate tick with timer 0 compare match on register 1. */
    T0MCR = ((portRESET_COUNT_ON_MATCH | portINTERRUPT_ON_MATCH) << 3);//***

    /* Clear and Setup the VIC for the timer 0, IRQ (not FIQ). */
    VICIntSelect &= ~( portTIMER_VIC_CHANNEL_BIT );
    VICIntEnable |= portTIMER_VIC_CHANNEL_BIT;
   
    /* The ISR installed depends on whether the preemptive or cooperative
    scheduler is being used. */
    #if configUSE_PREEMPTION == 1
    {   
        VICVectAddr4 = ( unsigned portLONG ) vPreemptiveTick; //*** interrupt 4 is timer0
    }
    #else
    {
        VICVectAddr4 = ( unsigned portLONG ) vNonPreemptiveTick;//***
    }
    #endif

    VICVectCntl4 = 1;//*** priority 1 (0 is highest)

    /* Start the timer - interrupts are disabled when this function is called
    so it is okay to do this here. */
    T0TCR = portENABLE_TIMER;
}

*** portASM.s mods

;VICVECTADDR    EQU    0xFFFFF030
VICVECTADDR    EQU    0xFFFFFF00    ;***
T0IR        EQU    0xE0004000
;T0MATCHBIT    EQU    0x00000001
T0MATCHBIT    EQU    0x00000002    ;***

vPreemptiveTick

    PRESERVE8

    portSAVE_CONTEXT                ; Save the context of the current task.   

    LDR R0, =vTaskIncrementTick            ; Increment the tick count. 
    MOV LR, PC                    ; This may make a delayed task ready
    BX R0                        ; to run.
   
    LDR R0, =vTaskSwitchContext            ; Find the highest priority task that
    MOV LR, PC                    ; is ready to run.
    BX R0
   
    MOV R0, #T0MATCHBIT                ; Clear the timer event
    LDR R1, =T0IR
    STR R0, [R1]

    LDR    R0, =VICVECTADDR            ; Acknowledge the interrupt   
    STR    R0,[R0]

    portRESTORE_CONTEXT                ; Restore the context of the highest
                            ; priority task that is ready to run.
    END

tns1 wrote on Friday, January 30, 2009:

*** more mods in LPC2400.s
change PLLCFG_Val      EQU     0x0000000E ;for 60mhz with a 12Mhz osc
change CCLKCFG_Val     EQU     0x00000005

*** mods in serial.c
change ulDivisor = configCPU_CLOCK_HZ / ulWantedClock /4;//div by 4 for pclk
change VICVectAddr7 = ( unsigned portLONG ) vUART_ISREntry;//vector 7
change VICVectCntl7 = serU1VIC_CHANNEL | serU1VIC_ENABLE;

After these last changes, my project runs correctly in both the Keil simulator and on the EA board (some pin select changes required)

edwards3 wrote on Friday, January 30, 2009:

Thank you for taking the time to report this back. It will be helpful to the rest of the community.