Difference between revisions of "Embedded System Tutorial Interrupts"

From Embedded Systems Learning Academy
Jump to: navigation, search
(Assignment)
(Assignment)
Line 108: Line 108:
 
#  Hints:
 
#  Hints:
 
#*  LPC_GPIOINT structure can configure rising or falling edges for Port0 and Port2
 
#*  LPC_GPIOINT structure can configure rising or falling edges for Port0 and Port2
 +
#*  You must CLEAR the source of interrupt by using either IO0IntClr or IO2IntClr otherwise your interrupt will go to an infinite loop.
 +
#*  You cannot use <B><CODE>printf()</CODE></B> to print anything from inside an ISR (if FreeRTOS is running), but you can use the API from <B><CODE>printf_lib.h</CODE></B>

Revision as of 22:46, 10 February 2014

Introduction

This tutorial demonstrates how to use interrupts on a processor. In general, you will understand the concept behind interrupts on any processor, but we will use the SJ-One board as an example.

What is an interrupt?

  • Hardware capability to break normal software flow to attend an urgent request
  • "An event that needs immediate attention"

Science

The science behind interrupts lies in the hardware that allows the CPU to be interrupted. Each peripheral in a microcontroller may be able to assert an interrupt to the CPU core, and then the CPU core would jump to the corresponding interrupt service routine (ISR) to service the interrupt.

ISR Procedure

The following steps demonstrate what happens when an interrupt occurs :

  • CPU manipulates the PC (program counter) to jump to the ISR
  • IMPORTANT: CPU will disable interrupts (or that priority level's interrupts until end of ISR)
  • Registers are saved before running the ISR (pushed onto the stack)
  • ISR is run
  • Registers are restored (popped from stack)
  • Interrupts are re-enabled (or that priority level's interrupt is re-enabled)

On some processors, the savings and restoring of registers is a manual step and the compiler would help you do it. You can google "GCC interrupt attribute" to study this topic further. On SJ-One board, which uses LPC17xx (ARM Cortex M3), this step is automatically taken care of by the CPU hardware.

The SW to HW Connection

Now that we understand how the CPU hardware services interrupts, we need to define how we inform the CPU WHERE our ISR function is located at. There is something called an Interrupt Vector Table. This table is nothing but addresses of functions that correspond to the microcontroller interrupts. Specific interrupts use specific "slots" in this table, and we have to populate these spots with our software functions that service the interrupts.

HW Interrupt Vector Table

SJ-One (LPC17xx) Example

Through some magic of the compiler, and the linker script, the compiler is able to place the software interrupt vector table at a specific location that the CPU expects the interrupt vector table to be located at. This connects the dots about how the CPU is able to determine WHERE your interrupt service routines are located at. From there on, anytime a specific interrupt occurs, the CPU is able to fetch the address and make the JUMP.

SW Interrupt Vector Table

GCC Magic

In the SJ-One sample project, each ISR is named, but is labeled as a "weak" function. What this means in the land of GCC (compiler) is that unless the same function name is defined somewhere else, the weak function serves as the ISR function. If you do define the function name somewhere else, it will override the "weak" function and the compiler will place the address of your new function into the interrupt vector table.

Setup ISR on SJ-One

The first step is to be able to locate the real-name of the "weak" ISR function. For example, you can locate a UART0 ISR function that is weak, and we will override with our new function. The only thing to keep in mind is that due to "C++ Mangling" of function names, if your ISR is located in a *.cpp file, you will need to enclose it into extern "C" tags. See below for examples:

/***************/
/* my_file.cpp */

    extern "C"
    {
        void UART0_IRQHandler()
        {
            /* Your ISR */
        }
    }

/*************/
/* my_file.c */

    /* extern tag not needed for a C file */
    void UART0_IRQHandler()
    {
        /* Your ISR */
    }


What to do inside an ISR

Do very little inside an ISR. When you are inside an ISR, the whole system is blocked (other than higher priority interrupts). If you spend too much time inside the ISR, then you are destroying the realtime operating system principle and everything gets clogged because of you :(

With that said, here is the general guideline

  • Spend as little time as possible. DO NOT POLL FOR ANYTHING.
  • If you are using FreeRTOS API, you must use FromISR functions only!
  • Most important: Clear the source of the interrupt
    For example, if interrupt was for rising edge of a pin, clear the "rising edge" bit such that you will not re-enter into the same interrupt function.

ISR processing inside a FreeRTOS Task

It is a popular scheme to have an ISR quickly exit, and then resume a task or thread to process the event. For example, if we wanted to write a file upon a button press, we don't want to do that inside an ISR because it would take too long and block the system. What we can do instead is have the ISR "give" a semaphore, and a task to block upon a semaphore.

What you may argue with the example below is that we do not process the ISR immediately, and therefore delay the processing. But you can tackle this scenario by resuming a HIGHEST priority task. Immediately, after the ISR exits, due to the ISR "yield", FreeRTOS will resume the high priority task immediately rather than servicing another task.

/* Declare and create the semaphore in main() */
xSemaphoreHandle gButtonPressSemaphore = NULL;

void my_button_press_isr(void)
{
    long yield = 0;
    xSemaphoreGiveFromISR(gButtonPressSemaphore, &yield);
    portYIELD_FROM_ISR(yield);
}

void button_press_task(void *p)
{
    while(1) {
        if (xSemaphoreTake(gButtonPressSemaphore, portMAX_DELAY)) {
            /* Process the interrupt */
        }
    }
}


Assignment

Write the implementation of eint.c from scratch. Before you get any ideas, and to make sure you are not biased, just erase this file and begin with an empty mind.

  1. From the startup.cpp file, identify the name of the function for EINT3
    • This interrupt function is used for Port0 and Port2 interrupts
    • When either EINT3, Port0, or Port2 interrupt occurs, this function will execute
  2. Allow the user to specify the pin number and a callback function.
    • When the interrupt occurs, make the callback for the user function
  3. Test your implementation
    • Attach a couple of switches on Port2, and ensure that the callbacks are made correctly.
    • If SW1 is pressed, it should go to callback1
    • If SW2 is pressed, it should go to callback2
  4. Hints:
    • LPC_GPIOINT structure can configure rising or falling edges for Port0 and Port2
    • You must CLEAR the source of interrupt by using either IO0IntClr or IO2IntClr otherwise your interrupt will go to an infinite loop.
    • You cannot use printf() to print anything from inside an ISR (if FreeRTOS is running), but you can use the API from printf_lib.h