Handling interrupts

This section illustrates an approach that improves on polling. We replace the busy-wait loop and instead configure the USART peripheral to generate an interrupt signal when a new character is received by the input data register (IDR). The interrupt signal causes a special function, known as an interrupt service routine (ISR), to be called, and this, in turn, reads the IDR and clears the interrupt signal. We illustrate this approach by a simple recipe called helloISR_c3v0.

Getting ready

Two small changes to SER_Init() are needed to configure UART4 so that interrupts are generated when a character is received. The value written to CR1 is changed from 0x200C to 0x202C, thereby setting bit-5 (RXNEIE), and the Nested Vectored Interrupt Controller (the NVIC is an ARM interrupt-dedicated peripheral close to the Cortex-M4 processor) is configured for UART4 as follows:

/*--------------------------------------------------------------
 *       SER_Init:  Initialize Serial Interface for interrupts
 *--------------------------------------------------------------*/
void SER_Init (void) {
  /* as before ... */
  
  /* Configure UART4: 115200 baud @ 42MHz, 
                                   8 bits, 
                               1 stop bit, no parity */
  UART4->BRR = (22 << 4) | 12;
  UART4->CR3 = 0x0000;
  UART4->CR2 = 0x0000;
  UART4->CR1 = 0x202C;
  
  /* Enable Interrrupts */
  NVIC_EnableIRQ(UART4_IRQn);
#endif
}

How to do it…

Follow these steps to handle interrupts.

  1. Create a new folder (helloISR_c3v0) and within it a new project named helloISR; use the RTE manager to configure the project as we did for all the previous projects that use the serial port.
  2. Create a file named helloISR.c and add the boilerplate code to configure clocks, and so on. Add this file to the project.
  3. Add a function to handle interrupts from UART4, as follows:
    /********************************************************
     * UART4_IRQHandler
     *
    **********************************************************/
    void UART4_IRQHandler (void) {
      volatile unsigned int IIR;
      volatile unsigned char c;
        
      IIR = UART4->SR;
      if (IIR & USART_FLAG_RXNE) { // read interrupt
        c = UART4->DR;
        printf("Interrupt! You pressed: %c 
    ", c);
        UART4->SR &= ~USART_FLAG_RXNE; // clear interrupt
      }
      else
        printf("Interrupt Error!
    ");
    }
  4. Add a main () function:
    /*
    * main function
    ********/
    int main (void) {
      
      HAL_Init ();   /* Init Hardware Abstraction Layer */
      SystemClock_Config ();           /* Config Clocks */
      
      SER_Init();
      printf ("Hello ISR I/O Example
    ");
      printf ("Pressing a key generates an interupt
    ");
      
      for (;;) {                        /* Loop forever */
        /* Nothing to do here */
      }
    }
  5. Remember to modify the SER_init() function, as described previously.
  6. Build, download, and run the program. Observe the response to keyboard strokes (illustrated in the next screenshot). Please note that when we test the code, it is best to configure PuTTY so that characters are not echoed to the terminal (as the ISR echoes the characters).
    How to do it…

How it works…

Interrupts allow us to eliminate busy-while loops by providing a mechanism for the peripheral to initiate reads and writes to its I/O registers. It does this by sending a signal directly to the central processing unit (CPU) via the Nested Vectored Interrupt Controller (NVIC). This signal, called an interrupt, is automatically checked after each instruction is executed by the CPU, and, if active, the processor responds by executing a special function, known as an Interrupt Service Routine (ISR), that includes the read or write statement. Early processors were designed with only one interrupt signal, and several devices would be connected to this line using wired OR logic. In this case, when the interrupt occurred, the processor first needed to establish which device generated it before it could be serviced. The ARM Cortex employs a NVIC to manage up to 256 interrupts, each having a unique priority. This enables each device to call a unique ISR that is tailored to provide it with the service it needs. System events (for example, reset) and errors use exactly the same mechanism as interrupts but are called exceptions (to emphasize that they arise due to unusual system events). Both the interrupt and exception priorities are processor-specific and defined in stm32F407xx.h. The names of the ISRs are defined in the vector interrupt table, given in the startup_stm32f407xx.s file (the file extension, .s, indicates that this is an assembly language source file). Although interrupts solve the busy-while problem, they rely on the processor to read and write data to peripherals. While this is fine for a small number of data bytes, however, some peripherals (for example, Memory systems) handle blocks of data. So, we may find that a large chunk of the CPU time is consumed moving data rather than performing useful work. Direct Memory Access (DMA) solves this problem by enabling data to be moved directly between peripherals and memory. In this case, the data transfers are managed by a DMA controller, thereby leaving the CPU free to execute other more useful instructions.

Inspecting the interrupt vector table that is defined in startup_stm32f4xx.s allows us to identify the UART4 interrupt vector (that is UART4_IRQHandler). We must define a function named UART_IRQHandler to handle the interrupts. This ISR must read the USART status register (SR) and test the receive register not empty (RXNE) bit to confirm that the interrupt was generated by the port (if not, an error is indicated). Then the data register is read, echoed to the console terminal (PuTTY), and the interrupt is cleared (by writing zero to the RXNE).

The SER_GetChar() function in the retarget.c source file will need to be modified if we wish to use stdio library functions, such as scanf(), and so on. The best strategy would be to arrange for the ISR to write received characters to a buffer that could subsequently be read by SER_GetChar().

There's more…

Interrupts provide a mechanism that allows the processor to multitask. Multitasking is a technique where a single processor divides its time between several instruction streams. This creates an illusion of parallelism as, to the user, it appears that different programs are executed concurrently when, in fact, they are not. Our programs that use ISR's have two threads of execution, but later we will write programs employing a real-time operating system kernel, and these may involve several threads. The differences between how normal threads and ISR threads are used have motivated processor designers to include features that enable multithreaded applications to be robust and recover from errors. Exceptions that are generated automatically when an error occurs are handled using exactly the same mechanism as interrupts and the term exception is generally used to describe either. When an error occurs, the strategy to recover from the exception may well involve reading/writing to processor registers that normal threads cannot access.

The Cortex-M4 processor operates in one of two modes. During the execution of the main program, the processor is in thread mode, and during execution of an exception handler or ISR, the processor is in handler mode. The two modes are distinguished by bits 0:8 of the PSR. In thread mode, bits 0:8 are zero, and in handler mode they are set to a number that identifies the exception type. As there are 8 bits, then 256 types of exceptions can be identified. When an exception is recognized the processor responds as follows:

  1. The contents of processor registers R0:R3, R12, the return address, PSR, and link register (LR) are pushed to the active stack.
  2. The processor identifies the exception number and uses this (offset) to access the interrupt vector table and locate the address of the exception handler, which is loaded into the program counter (PC).
  3. The LR is loaded with a value that represents the execution mode of the processor (that is, thread or handler) prior to the exception having occurred.
  4. The processor switches to handler mode and begins execution.

When the handler finishes, the return sequence pops the eight words from the stack and restores them to registers R0:R3, R12, LR, and PSR. It also loads the PC with the return address.

Access to special registers and system resources is determined by the privilege level of the processor. There are two levels, user and privileged. When in handler mode the processor is always in a privileged access level and can access all registers and memory resources. In thread mode, the processor is normally in user access privilege level and access to the System Control Space, an area of memory used to configure registers and debugging components, and access to some special registers is blocked. However, it is possible to switch from handler mode to user mode and maintain privileged access level, but the scenarios where this would be necessary are few. For most applications, the simple model of thread and handler modes that is shown as follows will suffice. After a reset, the processor is working in privilege mode in order to access all necessary resources.

There's more…
..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.189.22.136