Файл: Real-time processing with the Philips LPC ARM mcu using GCC and uCOS II RTOS (D.W. Hawkins, 2006).pdf

ВУЗ: Не указан

Категория: Не указан

Дисциплина: Не указана

Добавлен: 15.06.2025

Просмотров: 484

Скачиваний: 2

ВНИМАНИЕ! Если данный файл нарушает Ваши авторские права, то обязательно сообщите нам.
= 0; /* Select IRQ */ = (unsigned long)irq_handler; /* Vector 0 */ = 0x20 | 15; /* EINT1 Interrupt */ = (1 << 15); /* Enable */

AR1803

May 10, 2006

b loop

/* data abort */

nop

/* reserved for the bootloader checksum */

ldr pc, [pc, #-0x0FF0]

/* VicVectAddr */

ldr pc, fiq_addr

/* Address of the handler function */ fiq_addr: .word fiq_handler

The FIQ vector loads the program counter with the address of the FIQ handler, while the IRQ handler loads the address determined by the VIC. The second change is that when the system mode stack is setup, the FIQ and IRQ interrupts are left enabled.

Interrupt handling for Example 6(a) is fairly simple, since the interrupt vector loads the program counter with the address of the handler. The setup of an IRQ handler is slightly more complex. The VIC setup from Example 6(b) showing how to setup the VIC for EINT1 IRQs is;

void irq_init(void)

{

/* Enable P0.14 EINT1 pin function: PINSEL0[29:28] = 10b */ PINSEL0 = (2 << 28);

/* Make EINT1 falling edge-sensitive

* (level sensitive increments the LED count too fast) */

EXTMODE = 2;

EXTPOLAR = 0;

/* Clear register after mode change */ EXTINT = EXTINT;

/* Setup the VIC to have EINT1 generate IRQ * (EINT1 is interrupt source 15)

*/ VICIntSelect VICVectAddr0 VICVectCntl0 VICIntEnable

}

The IRQ initialization code sets up the EINT1 source and then the VIC. The VIC initialization sets up vector slot 0 for EINT1 interrupts. The IRQ handler has an additional step relative to the FIQ handler; an acknowledge to the VIC, i.e., VICVectAddr = 0;. Chapter 5 of the LPC213x user manual has a clear discussion on the VIC setup [10].

27

AR1803

May 10, 2006

3.7Example 7: I/O pin toggling

A simple technique for benchmarking operations, is to toggle an I/O pin around a block of code and measure the pulse time with an oscilliscope. Interrupt service routine (ISR) context save and restore routine times can also be determined using this technique. The measured I/O pin pulse time should be adjusted for the time it takes to simply toggle an I/O pin. The examples in this section demonstrate the fastest I/O toggle speed coded in assembler, and then the more practical case of toggle speed due to LED set and clear function calls from C-code.

Example 7(a) determines the maximum frequency an I/O pin can be toggled by; configuring the PLL for 60MHz operation, configuring the MAM, and configuring the peripheral bus clock divider (VPB divider) to 1. The code then drops into a loop that sets the LEDs high, then low, then loops back to high. The main loop from ex7a.s is

/* LED register addresses and control value */ ldr r0, IODIR1

ldr r1, IOCLR1 ldr r2, IOSET1

ldr r3, IODIR1_VALUE

/* Set pins as output */ str r3, [r0]

loop:

/* Set LEDs */ str r3, [r2]

/* Clear LEDs */ str r3, [r1]

b loop

The high time will be slightly shorter than the low time due to the branch that occurs as part of the loop.

Figure 3 shows that a 3.5MHz square-wave is produced on the MCB2130 board; a high time of 119ns (about 7 clocks) and a low time of 164ns (10 clocks). If the VPB divider is left in its default state of divide-by-four, a 1.66MHz square-wave is produced; 266ns (16 processor clocks) high-time and 333ns (20 processor clocks) low-time.

Example 7(b) is similar to the code in Example 5. The Example 7(b) startup file initializes the PLL, sets up the MAM, sets up the C environment and jumps to main. The main application sets the peripheral bus divider to 1, and then falls into a while loop that toggles the LEDs high, and then low. Figure 4 shows that a 984Hz square-wave is produced; with a high time of 402ns (24 clocks) and a low time of 615ns (37 clocks). If the VPB divider is left in its default state of divide-by-four, a 789kHz square-wave is produced; 536ns (32 processor clocks) high-time and 731ns (44 processor clocks) low-time. A block of code benchmarked by pulsing an LED pin using the C-coded LED control functions, should adjust the measured pulse time by 402ns (for VPBDIV = 1) or 536ns (for VPBDIV = 0) to account for the LED pulsing overhead.

28


AR1803

May 10, 2006

Figure 3: LPC2138 maximum I/O toggle speed; 3.5MHz. The oscilliscope screen capture shows the waveform frequency, duty cycle, period, high-time, and low-time.

Figure 4: LPC2138 I/O toggle speed using C; 984kHz. The C-code uses a general purpose LED control function (making it slower).

29

AR1803

May 10, 2006

Figure 5: LPC2138 FIQ context save/restore benchmarking. The Example 8(a) test application toggles an output pin connected to an input pin configured as an EINT1 source. EINT1 is handled using an FIQ handler. The EINT1 interrupt is setup for rising-edge sensitivity, the main application toggles the pin high, and the FIQ handler toggles the pin low. The high-time of the waveform is 1.27µs (76 clocks), while the low time is 1.20µs (72 clocks).

3.8Example 8: Interrupt context save/restore benchmarking

Example 8(a) takes the push-button FIQ handler code from Example 6(a) and modifies it so that EINT1 is generated from P0.3, and a jumper was placed between LED[0] (P1.16) and P0.3. The EINT1 interrupt was setup to be rising-edge sensitive. The main code in Example 8(a) sets all the LEDs low, enables FIQ interrupts, and then drops into a while loop that always sets the LEDs high. The rising-edge that occurs when the program starts triggers an FIQ interrupt, and the FIQ interrupt handler clears the LEDs. When the handler returns to the main application, the LEDs are set high again, and a FIQ interrupt is generated. The result is a square-wave on the LEDs. Figure 5 shows the waveform. The context save plus LED pulse high-time is 1.27µs, while the context restore and while loop time is 1.20µs.

This benchmark analysis indicates that an FIQ handler has a context save/restore time of approximately 2.5µs. So if the LPC was being used in a system processing a 1kHz FIQ interrupt, the FIQ context save/restore time represents a 0.25% CPU load. This benchmark represents the overhead of the save/restore sequence for a C-coded FIQ handler. Disassembly of the example code shows that the handler saves eight registers on entry (r0-r3, fp, ip, lr, pc), and restores seven registers on exit. When using an RTOS, an interrupt can cause a higher-priority task to become ready, and so additional context save or restore operations are required. For example, registers could need to be moved o the FIQ stack onto the task stack, and the new tasks registers moved onto the FIQ stack, or the context save routine might be setup to save registers directly to the task stack,

30


AR1803

May 10, 2006

Figure 6: LPC2138 software generated FIQ context save/restore benchmarking. The Example 8(b) test application uses the vectored interrupt controller (VIC) to software generate an EINT1 interrupt. EINT1 is handled using an FIQ handler. The main application toggles the LED pins high, and the FIQ handler toggles the LED pins low. The high-time of the waveform is 1.20µs (72 clocks), while the low time is 1.06µs (64 clocks).

and make minimal use of the FIQ stack. Benchmarking of the uCOS-II RTOS is shown later. Example 8(a) used an external interrupt pin to test interrupt latency. The Vectored Interrupt

Controller in the LPC-series provides an alternative option for software testing of interrupts; the VIC software interrupt register, and software interrupt clear register (p49 [10]). Example 8(b) modifies Example 8(a) to use a software generated EINT1 interrupt. The main code still writes to the LEDs so that the code can be benchmarked using an oscilloscope, however, code is added to set and clear the software interrupt register (code to setup the external EINT1 interrupt is also removed). Figure 6 shows the waveform from the software generated FIQ interrupt. The high-time and low-time of the waveform are both slightly smaller than in Figure 5.

31

AR1803

May 10, 2006

Figure 7: LPC2138 vectored IRQ prioritization. The Example 9(a) test application uses the vectored interrupt controller (VIC) to software generate EINT[0:3] interrupts which are enabled on the vectored IRQs 0 to 3. The main application toggles the LED pins 0 to 3 high, and the IRQ handlers each toggle one LED low. The figure shows how the EINT interrupts are serviced in order 0 to 3. The high-time of the EINT0 waveform is 1.34µs; similar to the previous tests. The high times of the other interrupts are progressively longer.

3.9Example 9: Multiple interrupts

This section benchmarks interrupt handling when dealing with multiple interrupt sources. The ARM core has two interrupt lines; the FIQ and IRQ. The FIQ is generally expected to have a single interrupt source, while the IRQ line can have multiple interrupt sources. The VIC on the LPC-series can be used to divide the IRQ sources into vectored (prioritized) IRQs, and non-vectored IRQs. For the vectored IRQs, the VIC acts like a hardware multiplexer, causing the processor to jump to the address of the handler of the highest priority interrupt. For the non-vectored IRQs, the same handler is provided, and the handler code has to perform the demultiplexing for multiple non-vectored sources.

Example 9(a) follows on from Example 8(b) and uses the VIC to setup four software interrupts on EINT[0:3]. The interrupts are set up to generate IRQ interrupts. The interrupt handlers are placed in IRQ vector slots VICVectAddr[0:3]. The main application sets LED[0:3] high, and each EINT handler sets a single LED low. Figure 7 shows the resulting LED waveforms.

The VIC IRQ controller prioritizes when interrupts occur simultaneously. However, if an IRQ handler has already started, and a higher priority interrupt occurs, the higher priority handler will not run until after the current handler completes. Example 9(b) and Figure 8 demonstrate the problem. Example 9(b) starts by generating an interrupt on EINT3, and then EINT3’s IRQ handler is used to set EINT2’s LED high and generate an EINT2 interrupt. EINT2’s handler does the same for EINT1, EINT1’s handler does the same for EINT0, and EINT0 leaves it to main to restart the sequence. Its obvious from the figure that each lower priority interrupt is completing while a higher

32


AR1803

May 10, 2006

Figure 8: LPC2138 IRQ priority inversion. The Example 9(b) starts by generating a software interrupt on EINT3, and then each IRQ handler generates a software interrupt on the next higherpriority interrupt (except for EINT0, that handler leaves it to main to restart the process). Note how even though a higher priority interrupt has occurred, it does not get processed until the current handler completes.

priority interrupt is pending. The next section shows how interrupts can be made interruptible. Examples 6(a), 6(b), 9(a) and 9(b) used the GCC keyword interrupt to define interrupt handler

functions. Example 9(c) shows how interrupt handlers can be written without using the interrupt keyword. The example uses a short assembler coded sequence to save processor state, and then call a C-coded FIQ or IRQ handler. The C-coded IRQ handler reads from the VICVectAddr register to dispatch handler routines. Since those routines are called from a C function, they all need to be written as standard C-functions (without the interrupt keyword). The IRQ and FIQ exception vectors in the startup code were modified as follows;

_start:

b reset

/* reset */

b loop

/* undefined instruction */

b loop

/* software interrupt */

b loop

/* prefetch abort */

b loop

/* data abort */

nop

/* reserved for the bootloader checksum */

ldr pc, irq_addr

/* FIQ ISR */ fiq_isr:

sub lr, lr, #4

stmfd sp!, {r0-r3, ip, lr} bl fiq_handler

33

AR1803

May 10, 2006

Figure 9: Example 9(c) waveforms. Example 9(c) uses assembler coded ISRs that then call C-coded functions. EINT0 is setup as a vectored IRQ, while EINT[1:3] are setup as non-vectored.

ldmfd sp!, {r0-r3, ip, pc}^

irq_addr: .word irq_isr irq_isr:

sub lr, lr, #4

stmfd sp!, {r0-r3, ip, lr} bl irq_handler

ldmfd sp!, {r0-r3, ip, pc}^

The FIQ vector service routine starts at its interrupt vector location, so the processor starts in the FIQ ISR when an FIQ interrupt occurs. The IRQ vector loads the program counter with the address of the IRQ assembler service routine, which is located just after the FIQ ISR. The FIQ ISR adjusts the link register address, saves it, along with the ARM Procedure Calling Standard (APCS) ‘scratch’ registers [3], to the stack, and calls the C-coded FIQ handler. When the handler returns, the FIQ IRQ performs a return-from-interrupt sequence. The IRQ ISR is similarly coded.

The reason for saving the APCS registers can be seen by disassembling the Example 9(c) code via arm-elf-objdump -d ex9c.elf. Looking at the code for irq_handler shows the prolog of that code saving several registers (not the ones saved by the ISR), and then the handler code uses r0-3. If the ISR code did not save these registers, then whatever was in those registers prior to the interrupt would be corrupted.

Figure 9 shows the waveforms from Example 9(c). The code is similar to Example 9(a), however, EINT0 is configured as a vectored IRQ, while EINT[1:3] are all configured as non-vectored. The example code provides details on how to setup the VIC. Comparison of Figure 7 to Figure 9 shows the increase in interrupt processing time caused by the implementation of Example 9(c).

34


AR1803

May 10, 2006

3.10Example 10: Interrupt nesting

The Philips LPC-series ARM microcontroller vectored interrupt controller (VIC) prioritizes interrupts under the condition of multiple interrupts occurring simultaneously, and it also prioritizes interrupts while interrupts are being serviced. When an interrupt occurs, the VIC modifies the interrupt enable state so that only higher-priority interrupts generate an IRQ to the processor core. This feature is not mentioned in the LPC2138 user manual (Chapter 5 [10]), but it is described in the ARM VIC PL190 documentation [2]. Without this feature, if IRQs were enabled to the core when a handler started, then any IRQ would interrupt the currently executing handler. The PL190 documentation also clarifies why you have to read from the VICVectAddr register, and then write to it once an interrupt has been serviced (p2-2 [2]);

Reading from the Vector Interrupt Address Register, VICVECTADDR, provides the address of the ISR, and updates the interrupt priority hardware that masks out the current, and any lower priority interrupt requests. Writing to the VICVECTADDR Register indicates to the interrupt priority hardware that the current interrupt is serviced, enabling lower priority or the same priority interrupts to be removed, and for the interrupts to become active to go active.

To allow interrupts of higher priority to interrupt an interrupt handler requires re-enabling IRQ interrupts to the processor core. The generation of an IRQ to the core while in IRQ mode will overwrite the IRQ mode saved program status register (SPSR_irq) and any return address currently in the link register (LR_irq). To avoid corrupting the IRQ mode state by re-enabling IRQ interrupts, a nested interrupt handler needs to change processor modes when enabling IRQ interrupts; ARM recommends changing to the system-mode (the privileged version of user-mode). Philips application note AN10381 [9] gives two examples of the implementation of interrupt nesting; a version implemented using assembler coded prolog and epilog code, containing a C-coded handler function, and a version implemented using a C-compiler generated interrupt handler containing inline-assembler code to perform the additional steps required to implement nesting.

Interrupt nesting can utilize an important feature provided by the VIC hardware prioritization logic; that reading the VICVectAddr register adjusts the interrupt enable mask to allow interrupts of higher-priority. This feature results in an alternative implementation of the nesting entry sequence demonstrated in AN10381, i.e., for a specific handler loaded into the VIC address registers, the entry sequence is (see the full list in Section 3.1 in reference [9])

1.Save IRQ context.

2.Clear the interrupt source.

3.Switch to system-mode and enable IRQ.

4.Save SYS context.

5. . . .

Since the handler function is executing as a consequence of the IRQ vector reading the VICVectorAddr register (via the statement ldr pc, [#-0xFF0] at the IRQ vector location), the currently executing IRQ priority has already been masked and can not generate another IRQ to the processor core. This means that the clearing of the interrupt source can be moved into the handler code. The consequence of that modification is that all handler prolog and epilog code is identical, so it can be replaced by a single assembler coded sequence, and all handler functions loaded into the VIC are simple C-functions. This improves the portability of the code, as compiler-specific interrupt handling keywords are no longer required.

35

AR1803

May 10, 2006

Nested interrupts can be implemented by having the IRQ vector jump to a routine that performs the following sequence;

1.Save IRQ context; by adjusting the link register, and saving the APCS registers (r0-3, ip), work registers r4-6, and the link register to the IRQ mode stack.

2.Save the IRQ mode SPSR into r4.

3.Read the handler address from the VICVectAddr register into r6 (causing the VIC to mask the current interrupt).

4.Write to the CPSR to switch to system mode with IRQ interrupts enabled.

5.Save the system/user-mode link register to the user-mode stack.

6.Call the C-coded handler function.

7.Restore the system/user mode link register.

8.Write to the CPSR to switch to IRQ mode with IRQ interrupts disabled.

9.Restore the SPSR.

10.Acknowledge the interrupt to the VIC.

11.Restore IRQ mode saved context, and return from the IRQ.

The contents of the work registers r4-6 are preserved across calls, so their contents can be loaded in the prolog code, and reused in the epilog code.

The nested IRQ assembler code is;

nested_irq_isr:

/* (1) Save IRQ context, including the APCS registers, and r4-6 */

sub

lr, lr, #4

stmfd

sp!, {r0-r6, ip, lr}

/* (2) Save the SPSR_irq register */ mrs r4, spsr

/* (3) Read the VICVectAddr */ ldr r5, VICVECTADDR

ldr r6, [r5]

/* (4) Change to SYS mode and enable IRQ */ msr cpsr_c, #SYS_MODE

/* (5) Save the banked SYS mode link register */ stmfd sp!, {lr}

/* (6) Call the C-coded handler */ mov lr, pc

ldr pc, r6

/* (7) Restore SYS mode link register */ ldmfd sp!, {lr}

36