No Tasks Seem To Run

Hi !

1. I'm trying to port FreeRTOS to an evaluation circuit board designed by my
    company. The circuit board has the LPC2368 procesor.
    a. I have the Keil tools & uVision V4 installed on my Windows 8 PC.
    b. The Keil simulator works on my PC. So I'm able to use the Debug features
        in uVision (set breakpoints, run, stop, step into, step over etc).
    c. To run my project on the circuit board:
        i.   I create a .hex file using the "Project->Build target" option
            in uVision.
        ii.  I write the .hex file to the circuit board using Flash Magic V7.
        iii. I reset the circuit board.
        iv.  I use LEDs & the serial port to debug.

2. I downloaded the FreeRTOS source:
    a. Apparently, FreeRTOS is ported to the LPC2129 processor, for the Keil
        tools. I used the folder named
        "FreeRTOSv202112.00\FreeRTOS\Demo\ARM7_LPC2129_Keil_RVDS".
    b. Previously, developers in my company had configured drivers for LEDs &
        the serial port. I merged the FreeRTOS code with their drivers:
        i.   I was able to make the FreeRTOS driver for LEDs to work.
        ii.  I was unable to make the FreeRTOS driver for the serial port to
            work. So I used my companies' code as a base for the serial port
            driver.

3. After some attempts, I was able to configure the Startup.s file to switch to
    supervisor mode before main() was called. Here's a snippet from Startup.s
    that sets up the last 2 modes before the call to main().
    
    ; Sid - 2022.01.20
                    MSR     CPSR_c, #Mode_SYS
                    MOV     SP, R0
                    SUB     R0, R0, #SYS_Stack_Size    ;SUB     SL, SP, #SYS_Stack_Size
                   
                    IMPORT    TargetResetInit
                    BL        TargetResetInit

    ;  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
    
    If you prefer to see my main() function now, see step 6 & then return here.

4. My uVision project compiles without errors or warnings. Regarding timers:
    a. In the prvSetupTimerInterrupt() function, I have set vPreemptiveTick() as
        the Interrupt Service Routine (ISR) for timer0.
    b. But after main() runs, the timer keeps invoking another function which I
        created in Startup.s - this function is glued to the vector table. The
        relevant snippets from Startup.s are shown below:

    ; Sid - 2022.01.21
                    LDR     PC, Timer0_Addr
                    
    Timer0_Addr     DCD     Timer0_Handler
    
    Timer0_Handler
                    MOV R4, #1
                    LDR R5, =0xE0004000     ; Timer0 Interrupt Register
                    STR R4, [R5]            ; Write 1 to the Timer0
                                            ; Interrupt Register to
                                            ; clear the interrupt.
                    SUBS PC, LR, #0x4       ; Return ? (I'm not sure.)

5. When I write my code to the circuit board & execute there:
    a. Initial LED blinks & serial port prints (programmed by me) occur.
    b. The LED of vErrorChecks():
        i.   toggles once if I use Flash Magic's Terminal tool for the serial
            port.
        ii.  doesn't toggle if I use Putty for the serial port.

6. I DON'T see tasks running. My main() function is shown below, I haven't
    changed FreeRTOS' main() in terms of tasks, I've only performed hardware
    inits (LEDs & serial port). If you understand the issue(s?), please give
    suggestions. Thanks in advance !

    int main( void )
    {    
        /* Setup the hardware for use with the Keil demo board. */
        prvSetupHardware();     // My code inside prvSetupHardware() sets up the serial port (UART0).
        led_init();             // My code

        /* Start the demo/test application tasks. */
        vAltStartComTestTasks( mainCOM_TEST_PRIORITY, mainCOM_TEST_BAUD_RATE, mainCOM_TEST_LED );
        vStartLEDFlashTasks( mainLED_TASK_PRIORITY );
        
        uart_print_tests();     // My code - seems like it works.
        led_tests_rtos();       // My code & FreeRTOS code - seems like it works.

        vStartPolledQueueTasks( mainQUEUE_POLL_PRIORITY );
        vStartBlockingQueueTasks( mainBLOCK_Q_PRIORITY );
        vStartSemaphoreTasks( mainSEM_TEST_PRIORITY );
        vStartDynamicPriorityTasks();

        /* Start the check task - which is defined in this file.  This is the task
        that periodically checks to see that all the other tasks are executing 
        without error. */
        xTaskCreate( vErrorChecks, "Check", configMINIMAL_STACK_SIZE, NULL, mainCHECK_TASK_PRIORITY, NULL );

        /* Now all the tasks have been started - start the scheduler.

        NOTE : Tasks run in system mode and the scheduler runs in Supervisor mode.
        The processor MUST be in supervisor mode when vTaskStartScheduler is 
        called.  The demo applications included in the FreeRTOS.org download switch
        to supervisor mode prior to main being called.  If you are not using one of
        these demo application projects then ensure Supervisor mode is used here. */
        vTaskStartScheduler();

        /* Should never reach here!  If you do then there was not enough heap
        available for the idle task to be created. */
        for( ;; );
    }

Thanks for the details, but it is not clear exactly what is working and what not. Is it that you don’t enter the first task, or the the timer interrupt isn’t working, or that the task only executes once, or something else?

I don’t know if the timers on the LPC2368 and LPC2129 are the same or not - but there are LPC2368 ports for other compilers you can check the code for: https://github.com/FreeRTOS/FreeRTOS-Kernel/blob/c4f9e27c28c018c85b9d23e5ac7470ada508bdb8/portable/GCC/ARM7_LPC23xx/port.c#L181

Then I would try one thing at a time. For example:

  1. Try getting the timer interrupt working without running FreeRTOS. Just configure the timer, and install a handler for it that does nothing other than increment a variable. Once the timer is running at the correct frequency replace the interrupt handler with the FreeRTOS one.

  2. Next create a very simple task:

volatile int x = 0;
void vMyTask( void * pv )
{
    for( ;; )
    {
        x++;
    }
}

Create this task at priority 1 (so it is first to run), and no other tasks (so set configUSE_TIMERS to 0 so the timer task is not created). Set a break point at the start of the task. Then start the scheduler and see if you hit the break point. If you do, you enter the first task correctly.

Do the above without the tick running - we just want to see if you get to the task.

  1. Next add another task, and put a yield into each:
volatile int x = 0;
void vMyTask( void * pv )
{
    for( ;; )
    {
        x++;
        taskYIELD();
    }
}

volatile int y = 0;
void vMyTask( void * pv )
{
    for( ;; )
    {
        y++;
        taskYIELD();
    }
}

Now, still without the tick interrupt, create both tasks, put a break point in each on the line that increments the variable. Each task should yield to the other - so when you run the code each break point should get hit in turn.

  1. Next take the yields out again, but this time start the tick interrupt properly. Now both break points should still get his but the variable should increment many times between each as you should only be switching tasks when the tick interrupt executes.

How far do you get?

Thanks for the response Richard !

It might be easier for me to take out code with #if 0 ... #endif blocks & then bring in the code you suggest in steps.

Give me a day / two, I’ll get back to you !

https:__embdev.net_topic_179152
(replace _ with / - new users can’t post links apparently)

Hi ! I’m going to check if I can get ‘blinky.zip’ (from Michael G.) to work first.

Probably, I’ll get back to you next Monday / Tuesday !

Hi !

1. I got the timer example (blinkyy) to work ! Its ISR is run whenever Timer0
    counts up to its match value. So far, I haven't replaced its ISR with the
    one in FreeRTOS (because this seems difficult).

2. I created the following task (with priority 1) & then started the scheduler:

    volatile int x = 0;
    void increment_x_task( void * pv )
    {
        /* Parameters are not used. */
        ( void ) pv;

        for( ;; )
        {
            x++;
            //taskYIELD();
        }
    }

    a. When taskYIELD() is commented:
        i.   x is incremented till some upper value (like 0x7A).
        ii.  then, a UART0 ISR executes.
        iii. after the UART0 ISR, a Prefetch Abort occurs.

    b. When taskYIELD() is not commented:
        i.   x is incremented without abort.

3. I left taskYIELD() uncommented. I created a second task (shown below, also
    with priority 1) & then started the scheduler:
    
    volatile int y = 0;
    void increment_y_task( void * pv )
    {
        /* Parameters are not used. */
        ( void ) pv;

        for( ;; )
        {
            y++;
            taskYIELD();
        }
    }

    a. This task ran forever & the breakpoint inside the increment_x_task was
        never reached.

4. I'll continue with your remaining suggestions from tomorrow. In a day / two,
    I'll tell you what I see.

Strange the UART interrupt only occurs when taskYIELD() is commented - make sure the interrupt is disabled for now.

Do you install vPortYieldProcessor() as the SWI handler in the vector table. I don’t know how you are defining the vector table, but if in the startup code it would look something like this, although your code may use different names for vectors not used by FreeRTOS:

	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						*/
Hi !

Ok, I'll disable the UART interrupt & see what happens !

Though the timer code in the blinkyy example isn't merged with the FreeRTOS code
yet:
1. I see that the timer code uses an IntEnable() function which calls SWI with a
    parameter.
    a. Based on the parameter, SoftwareInterrupt (which is glued to SWI_Handler
        in the vector table) calls 1 of 4 functions (IRQDisable / IRQEnable / 
        FIQDisable / FIQEnable).

2. I suppose the timer code mentioned above must be preserved. So maybe I don't
    want to glue vPortYieldProcessor to SWI_Handler.
    a. In the FreeRTOS code, taskYIELD() uses portYIELD(), which uses 
        vPortYield().
    b. vPortYield() is an assembly language function which uses the "SVC 0"
        instruction to invoke SWI_Handler.
    c. Since I don't want vPortYieldProcessor to be glued to SWI_Handler, I
        could remove the "SVC 0" instruction & use "BL vPortYieldProcessor"
        instead.

3. Does the above approach look valid ? I'll try it & tell you what I see !

No, it didn’t work ! I get a Data Abort after vPortYieldProcessor.

I’ll check if IntEnable() in the timer code can be rewritten without the SWI.

Hi !

1. In the timer code, I rewrote IntEnable() without the SWI. It has only 1
    statement (B IRQEnable), which seems enough for now. The blinkyy timer
    worked !

2. In the FreeRTOS code:
    a. I glued vPortYieldProcessor to SWI_Handler.
    b. Now the taskYIELD() function worked ! (I saw the switch between the x & y
       tasks for each increment).
    c. But the UART0 interrupt occurred when y was 0xB & x was 0xA. Following
        the UART0 ISR, a Prefetch Abort occurred.
    d. I had to: 
        i.   disable the UART interrupt.
        ii.  remove the install_irq() call which glued the UART ISR.
        Then the x & y tasks switched and x & y were incremented continuously
        without abort.

3. Next, as you suggested, I will:
    a. bring the timer code into FreeRTOS.
    b. remove the taskYIELD() calls.
    c. see if tasks are switched (by the scheduler ?) at timer tick interrupts.

[FYI - when you format your posts like that I can’t read them on my phone, so response times are longer]

The purpose of the instructions I provided was to make the system as simple as possible and then try one thing at a time. That is not going to work if you have other interrupts interfering with what you are trying to observe.

I see that the timer code uses an IntEnable() function which calls SWI with a
parameter.

What is that timer code? It’s not going to work if both your timer and FreeRTOS are trying to use the SWI interrupt.

Since I don’t want vPortYieldProcessor to be glued to SWI_Handler, I
could remove the “SVC 0” instruction & use “BL vPortYieldProcessor”
instead.

That would be 100% guaranteed not to work. If that were an option, it wouldn’t be using the SWI interrupt.

Sorry about that Richard ! It’s not my intention to make posts hard to read on your phone. By the way, I may not mind if response times are longer. I tried using the “Numbered List” button earlier, but it’s very difficult to use (for me at least). When I make nested lists, even the best GUI tools (like Microsoft Word) are hard to use. Web GUIs are miles behind !

Yes, I understand, interrupts must not interfere ! I’m a bit curious about how we’ll bring such interrupts in later, but that can wait !

Yes, at the moment, both the timer & FreeRTOS use the SWI interrupt. That’s precisely why I was trying to un-glue one or the other.

Hmm …, it’s true that my attempt to un-glue FreeRTOS (vPortYieldProcessor) from SWI_Handler gave errors. But I was able to un-glue the timer’s IntEnable() from SWI_Handler. (See point 1 in my post on “Feb 2, 2022 1:38 pm”.)

Anyway, I’ll post either today / tomorrow about what I see, when switching tasks at a timer tick !

Hi !

  1. Setup:

    1. I removed the taskYIELD() calls in both increment_x_task & increment_y_task. Both tasks have priority 1.
    2. configUSE_TIMERS is 1:
      1. configTIMER_TASK_PRIORITY is 1.
      2. configTIMER_QUEUE_LENGTH is 10.
      3. configTIMER_TASK_STACK_DEPTH is 10.
    3. configUSE_PREEMPTION is 1
      1. vPreemptiveTick is installed as the ISR for the timer. It has priority 1.
  2. Upon running the program in the simulator:

    1. increment_x_task() runs & x is continuously incremented.

    2. In parallel, the timer counts up. When the timer reaches its match value:

      1. The vPreemptiveTick ISR calls xTaskIncrementTick() & xTickCount becomes 1.
      2. When the Prescale Counter counts up to its match value, the Timer Counter is reset (made 0).
      3. The vPreemptiveTick ISR calls vTaskSwitchContext() which seems to finish successfully.
      4. After the last statement of the vPreemptiveTick ISR, increment_y_task() starts running & y is continuously incremented.
    3. In parallel, the timer counts up. When the timer reaches its match value:

      1. The vPreemptiveTick ISR calls xTaskIncrementTick() & xTickCount becomes 2.
      2. When the Prescale Counter counts up to its match value, the Timer Counter is reset (made 0).
      3. The vPreemptiveTick ISR calls vTaskSwitchContext() which seems to finish successfully.
      4. After the last statement of the vPreemptiveTick ISR, the next statement that runs is NOT in increment_x_task(). Instead, it is in IRQ_Handler() in Startup.s. Specifically it is the “branch to the ISR” statement in IRQ_Handler().

Have you seen such events before ? What do you suggest ?

That is good progress. It sounds like both the yield and tick ISR are now switching between tasks. If you end up in an IRQ handler that is branching to itself it sounds like it is a default handler for an interrupt you haven’t installed a real handler for - do you know which interrupt it is? As I recall the LPC2368 uses a vectored interrupt controller, so you would need to see which vector is active.

Hi !

Some study of the following is needed:

  1. assembly language functions like vPreemptiveTick, portSAVE_CONTEXT & portRESTORE_CONTEXT
    1. their manipulation of processor modes

I’ll get back to you on next Monday / Tuesday.

Hi !

It might be easier to read this post with 2 web browser windows containing the same link !

The code below shows IRQ_Handler, vPreemptiveTick, portRESTORE_CONTEXT & portSAVE_CONTEXT with statement numbers in the left margin.

IRQ_Handler comes from the blinkyy timer code. The other 3 functions come from FreeRTOS ! The code below is my attempt to merge them !



; /**********************************************************************/

IRQ_Handler

;- Manage Exception Entry
;- Adjust and save LR_irq in IRQ stack
01            SUB         lr, lr, #4
02            STMFD       sp!, {lr}

;- Save SPSR need to be saved for nested interrupt
03            MRS         r14, SPSR
04            STMFD       sp!, {r14}

;- Save and r0 in IRQ stack
05            STMFD       sp!, {r0}

;- Load the ISR-Address from VICVectAddr
06            LDR         r14, =LPC_BASE_VIC
07            LDR         r0 , [r14, #VIC_VectAddr]

;- Save scratch/used registers and LR in User Stack
08            STMFD       sp!, { r1-r3, r12, r14 }

;- Branch to the routine pointed by the VIC_VectAddr
09            MOV         r14, pc
10            BX          r0

;- Restore scratch/used registers and LR from User Stack
11            LDMIA       sp!, { r1-r3, r12, r14 }

;- Restore R0
12            LDMIA       sp!, {r0}

;- Restore SPSR_irq and r0 from IRQ stack
13            LDMIA       sp!, {r14}
14            MSR         SPSR_cxsf, r14

;- Restore adjusted  LR_irq from IRQ stack directly in the PC
15            LDMIA       sp!, {pc}^
16            END



; /**********************************************************************/


vPreemptiveTick

    PRESERVE8

17    portSAVE_CONTEXT                    ; Save the context of the current task.

18    LDR R0, =xTaskIncrementTick         ; Increment the tick count.
19    MOV LR, PC                          ; This may make a delayed task ready
20    BX  R0                              ; to run.

21    CMP R0, #0
22    BEQ SkipContextSwitch
23    LDR R0, =vTaskSwitchContext         ; Find the highest priority task that
24    MOV LR, PC                          ; is ready to run.
25    BX  R0
SkipContextSwitch
26    MOV R0, #T0MATCHBIT                 ; Clear the timer event
27    LDR R1, =T0IR
28    STR R0, [R1]

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

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


; /**********************************************************************/

      MACRO
      portRESTORE_CONTEXT

                                          ;                                                               
33    LDR        R0, =pxCurrentTCB        ; Set the LR to the task stack.  The location was...            
34    LDR        R0, [R0]                 ; ... stored in pxCurrentTCB                                    
35    LDR        LR, [R0]                 ;                                                               

36    LDR        R0, =ulCriticalNesting   ; The critical nesting depth is the first item on...            
37    LDMFD      LR!, {R1}                ; ...the stack.  Load it into the ulCriticalNesting var.        
38    STR        R1, [R0]                 ;                                                               

39    LDMFD      LR!, {R0}                ; Get the SPSR from the stack.                                  
40    MSR        SPSR_cxsf, R0            ;                                                               

41    LDMFD      LR, {R0-R14}^            ; Restore all system mode registers for the task.               
42    NOP                                 ;                                                               
                                          ;                                                               
43    LDR        LR, [LR, #+60]           ; Restore the return address                                    

                                          ; And return - correcting the offset in the LR to obtain ...
44    SUBS       PC, LR, #4               ; ...the correct address.                                       

      MEND

; /**********************************************************************/                              

      MACRO
      portSAVE_CONTEXT

                                          ;                                                               
45    STMDB     SP!, {R0}                 ; Store R0 first as we need to use it.                          

46    STMDB     SP,{SP}^                  ; Set R0 to point to the task stack pointer.                    
47    NOP                                 ;
48    SUB       SP, SP, #4                ;                                                               
49    LDMIA     SP!,{R0}                  ;                                                               

50    STMDB     R0!, {LR}                 ; Push the return address onto the stack.                       
51    MOV       LR, R0                    ; Now we have saved LR we can use it instead of R0.             
52    LDMIA     SP!, {R0}                 ; Pop R0 so we can save it onto the system mode stack.          

53    STMDB     LR,{R0-LR}^               ; Push all the system mode registers onto the task stack.       
54    NOP                                 ;
55    SUB       LR, LR, #60               ;                                                               

56    MRS       R0, SPSR                  ; Push the SPSR onto the task stack.                            
57    STMDB     LR!, {R0}                 ;                                                               

58    LDR       R0, =ulCriticalNesting    ;                                                               
59    LDR       R0, [R0]                  ;                                                               
60    STMDB     LR!, {R0}                 ;                                                               

61    LDR       R0, =pxCurrentTCB         ; Store the new top of stack for the task.                      
62    LDR       R1, [R0]                  ;                                                               
63    STR       LR, [R1]                  ;                                                               

      MEND


; /**********************************************************************/





First timer interrupt = First time at IRQ_Handler in the debugger (simulator on Windows 8 PC):

  1. At statement 01, R14 (LR = Link Register) has the address of the x++; statement in increment_x_task !

  2. At statement 08, R14 (LR) has 0xFFFF_F000.

  3. At statement 17, R14 (LR) has the address of statement 11.

    1. At statement 50, R14 (LR) has the address of statement 11.
  4. At statement 44, R14 (LR) has the address of the y++; statement of increment_y_task ! As expected, increment_y_task executes next !

Second timer interrupt = Second time at IRQ_Handler in the debugger (simulator on Windows 8 PC):

  1. At statement 01, R14 (LR = Link Register) has the address of the x++; statement in increment_x_task !

  2. At statement 08, R14 (LR) has 0xFFFF_F000.

  3. At statement 17, R14 (LR) has the address of statement 11.

    1. At statement 50, R14 (LR) has the address of statement 11.
  4. At statement 44, R14 (LR) has the address of statement 11 ! I’m not sure why R14 (LR) has this value !

As you mentioned earlier, it’s branching to statement 11 in IRQ_Handler ! But is this an ISR for another vectored interrupt ? (Wouldn’t such an ISR start at statement 01 of IRQ_Handler ?)

Hi ! Here is some more information (using the code of the previous post) !

First time at IRQ_Handler:

0xFFFF_F110:    0x0000_5990     0xFFFF_FF00:   0x0000_5990
(VICVectAddr4 = TIMER0_INT)     (VICAddress)

Second time at IRQ_Handler:

0xFFFF_F110:    0x0000_5990     0xFFFF_FF00:   0x0000_5990
(VICVectAddr4 = TIMER0_INT)     (VICAddress)

After (single step beyond) statement 44:
R14(LR) - 4 = Address of statement before statement 11 ( = statement 10):
At statement 10:

0xFFFF_F110:    0x0000_5990     0xFFFF_FF00:   0x0000_5990
(VICVectAddr4 = TIMER0_INT)     (VICAddress)

The following pictures are from the LPC2368 User Manual:

  1. VIC Register Map: (Image On The Left)

  2. Interrupt Sources Bit Allocation Table: (Image In The Center)

The following picture is from the Keil IDE:

  1. Defines in irq.h: (Image On The Right)

New users are allowed only 1 image upload, so I had to combine them !

Seems like the only interrupt in play, is that of Timer0 !

Not gone into your last two posts in detail yet - but your IRQ handler does not look right. Been a while since I used an ARM7 but I’m pretty sure on that device you should be branching directly to the ISR for whichever interrupt is active, then writing the ISR exactly as per the tick handler - that is entering via portSAVE_CONTEXT and exiting via portRESTORE_CONTEXT. The RTOS kernel has to manage the stacking of registers before they get altered, and your IRQ entry code is altering the registers. Take a look at the LPC2000 example in the download, and their respective documentation pages. For example, this is branching directly to the ISR: https://github.com/FreeRTOS/FreeRTOS/blob/main/FreeRTOS/Demo/ARM7_LPC2106_GCC/boot.s#L145 and this is an example ISR: https://github.com/FreeRTOS/FreeRTOS/blob/main/FreeRTOS/Demo/ARM7_LPC2106_GCC/serial/serialISR.c#L99

Great suggestion Richard ! When I used the code below, x & y seem to increment to high values without errors. At a timer tick, there is a switch between increment_x_task & increment_y_task !

IRQ_Handler
            LDR         r10, =LPC_BASE_VIC         ; Sid - 2022.02.11 - Assuming r10 is rarely used
            LDR         pc , [r10, #VIC_VectAddr]

Now, I’ll enable the UART interrupt, restore its ISR & see how that goes ! If it works, I’ll start bringing in other tasks from the FreeRTOS main() function one by one !

Hi ! I enabled the UART interrupt & installed its ISR !

Initially, I perform many transmits to the UART. I expect that its ISR will get called on each character’s transmit, but this doesn’t happen. Yet the characters are printed !

I’d like to post to the forum about this in detail. Shall I make a new topic / continue on this topic ?

Thanks in advance !

Your IRQ handler is still modifying r10 before branching to its handler. You need to branch directly to the handler. The code I linked to shows how to do that, as does the code from the chip vendor. Note the offset between irq vector and the interrupt vector (-0xFF0 in the example I linked to) may be different for your chip compared to the lpc2106 used in the linked code.