TI ARM Tiva TM4C123G - 1.3 Interrupts & the Vector Table.

The aim of the following exercises is to: 


  • Explain the concept of Interrupts.

  • Explain the concept of a Vector Table.

  • Provide an example to demonstrate how to toggles the red LED ON & OFF by triggering an timeout events interrupt and/or switches SW0 and SW1 from the TM4C123GH6PM.


The steps to follows, as an overview are:

PART I. Initialization and Configuration the General Purpose Input/Output (GPIO) pins for the LEDs and switches (SW0 & SW4)

PART II. Initialization and Configuration the General Purpose Timer Module (GPTM) for the timeout event. 

PART III. Configure the NVIC (Nested Vector Interrupt Controller) 

PART IV. Add Functions to the Interrupt Vector Table   


I will summarize those steps with a visual representation for better understanding of the procedure and to easily go through the datasheet. Eventually each step will  be part of an entire source code for you to use. Complete source code is at the end of this article.


Note: For the purpose of this tutorial we are going to use IAR as our integrated development environment tool (IDE).


Reference Documents: 

  1. TM4C123G User Manual 

  2. TM4C123G datasheet 

  3. IAR IDE tool header file: <TM4C123GH6PM.H>



User Manual - Board Overview, p.4

It shows the location of the RGB User LED & Switches in the LaunchPad Evaluation Board.




User Manual - Schematics, p.20


The schematic shows where the three LEDs (Red, Green & Blue) are connected to the microcontroller, as well as the switches: PF0, PF1, PF2, & PF3. The “P” stands for “port” and the letter “F” stands for the block F, one of the six blocks of the General-Purpose Input/Outputs (GPIOs) module. The numbers 0, 1, 2,  3 & 4 represent the pins (or bit in register to be manipulated - explained later). 



GPIO Port F is the block in the GPIO module where the LEDs and switches are connected to.  



Interrupts


A single microprocessor can serve several devices. There are two ways to do that:

Interrupts or polling


In polling, the CPU continuously monitors the status of a given device; when the status condition is met, it performs the service. After that, it moves on to monitor the next device until each one is serviced. While doing so wastes much of the CPU’s time by polling devices that do not need service. So in order to avoid limiting the CPU, interrupts are used. 


In the interrupt method, whenever any device needs service, the device notifies the CPU by sending it an interrupt signal. Upon receiving an interrupt signal, the CPU completes the execution of whatever it is doing and serves the device. The program associated with the interrupt is called the interrupt service routine (ISR) or interrupt handler.  



Therefore, an interrupt is a signal to the processor emitted by hardware or software indicating an event that needs immediate attention.


The interrupts can be either hardware interrupts or software interrupts



Interrupt Service Routine


For every interrupt there must be a program associated with it. When an interrupt occurs this program is executed to perform a certain service for the interrupt. This program is commonly referred to as an interrupt service routine (ISR). The interrupt service routine is also called the interrupt handle. 


For every interrupt, there is a fixed location in memory that holds the address of its interrupt service routine, ISR. The table of memory locations set aside to hold the addresses of ISRs is called the Interrupt Vector Table. This is how the CPU knows which ISRs should be executed.

Interrupt Vector Table


Since there is a program (ISR) associated with every interrupt and this program resides in memory (RAM or ROM), there must be a look-up table to hold the addresses of these ISRs. This look-up table is called interrupt vector table. (pointers to routines that handle interrupts).


Cortex M Vector Table


Let’s Get started…


Pay particular attention to STEP 7 from Part I. GPIOF Initialization and Configuration and STEP 7 from Part II. GPTM Initialization and Configuration PART II. Those two parts enable the “switch interrupt” and the “time event interrupt”, respectively. The other two parts: Configure the NVIC (Nested Vector Interrupt Controller) and Add Functions to the Interrupt Vector Table are necessary for complete setup.


Part I. GPIOF Initialization and Configuration

General-Purpose Input/Outputs (GPIOs) module, Port F. 

DATA SHEET, p.656.


This section describes the steps to follow to access and configure the GPIO pins of a particular block. In our case the block we want to manipulate is block F: Base Address: 0x400FE000.

STEP 1. Enable signal clock to the appropriate GPIO module.


Datasheet - General-Purpose Input/Output Run Mode Clock Gating Control (RCGCGPIO), offset 0x608, p.340


It’s telling you that to enable the clock and access to the GPIO Port F module, bit 5 in the RCGCGPIO register must be set to 1


SYSCTL->RCGCGPIO |= 0x20;     //STEP1: enable clock signal to GPIOF


STEP 2. Set the direction of the pins either as input or output.  


By programming the GPIODIR register, you set the direction of the GPIO port as input or output. A 1 indicates output and a 0 indicates input. 


Datasheet - GPIO Direction (GPIODIR), offset 0x400. Page 663


Base: 0x4002.5000 / Offset: 0x400


To blink an LED you will like to use the pins 1, 2 & 3 as outputs. Therefore, pins 1, 2 & 3 in the GPIODIR register needs to be set as 1


GPIOF ->DIR = 0x0E; //PF3:PF1 set as output.


STEP 3A. Enable the corresponding GPIO line with an alternate function. 

STEP 4. Set the drive strength for each of the pins through the GPIODR2R, GPIODR4R, and GPIODR8R registers.


Note:  STEPs 3 - 4 would be skipped since those are features not needed at the moment.


STEP 5. Configure SW1 & SW0 by enabling the pull up resistor.

Pullup resistor is a resistor that it’s connected to Vcc or a high value voltage in your microcontroller. When enabled, your switch pin is high and when you press it is low, allowing a change in state that will be used to turn ON or OFF the LEDs.


To use the SW1 or SW2 we need to enable the internal pull up resistor option for PF0 and PF4 in the PUR register since the switch circuit does not have an pull-up resistor. See schematic page 20, you won’t find a pull-up resistor.


To do so, we first need to take special consideration for PF0, since “this pin is configured as a GPIO by default but is locked and can only be reprogrammed by unlocking the pin in the GPIOLOCK register and uncommitting it by setting the GPIOCR register”:


Datasheet - GPIO Pull-Up Select (GPIOPUR), offset 0x510. Page 677




5A. Datasheet - GPIO Lock (GPIOLOCK), offset 0x520. Page 684


The GPIOLOCK register enables write access to the GPIOCR register (see page 685). Writing 0x4C4F.434B to the GPIOLOCK register unlocks the GPIOCR register.



GPIOF->LOCK = 0x4C4F434B;/*STEP5A: unlock the GPIOCR Commit register.*/


5.B Datasheet - GPIO Commit (GPIOCR), offset 0x524. Page 685


When the GPIOCR register is set (unlock), the data being written to the corresponding bit of the GPIOPUR register is committed to the register and reflects (keep) the new value.Otherwise, if GPIOCR register is lock the data written to the GPIOPUR won’t be committed and will retain its previous value. In other words, setting GPIOCR register with a value one (bit “1”) enable GPIOPUR register to be configurable.



GPIOF->CR = 0x01/* STEP5B: make PF0 configurable.*/


5C. Now the pull-up control register (GPIOPUR) can be modified. When a bit is set, a weak pull-up resistor on the corresponding GPIO signal is enabled.



GPIOF->PUR = 0x11// Enable pull up resistor at PF4 & PF0.


Now, we can say that  PF0 & PF4 are normally high.

STEP 6. Set up GPIO pins as digital I/Os.

To enable GPIO pins as digital I/Os, set the appropriate DEN bit in the GPIODEN register.


Datasheet - GPIO Digital Enable (GPIODEN), offset 0x51C. Page 682


GPIO Port F (APB) Base: 0x4002.5000 / Offset 0x51C


Enable GPIODEN register pins 3:1 as digital pins by setting pins 3:1 from 0 to 1. 


GPIOF->DEN = 0x1F;


STEP 7: Enable and configure the GPIO Interrupt options


Program the GPIOIS, GPIOIBE, GPIOIEV, and GPIOIM registers to configure the type, event, and mask of the interrupts for each port.


7A. Datasheet - GPIO Interrupt Sense (GPIOIS), offset 0x404. Page 664


The GPIOIS register is the interrupt sense register. Setting a bit in the GPIOIS register configures the corresponding pin to detect levels, while clearing a bit configures the corresponding pin to detect edges. All bits are cleared by a reset.



For the purpose of this example, we have chosen edge-sensitive. 


GPIOF->IS  &= ~0x11;   /* STEP7A. make bit 4, 0 edge sensitive */


7B. Datasheet - GPIO Interrupt Both Edges (GPIOIBE), offset 0x408, p 665


The GPIOIBE register allows both edges to cause interrupts. When the corresponding bit in the GPIO Interrupt Sense (GPIOIS) register (see page 664) is set to detect edges, setting a bit in the GPIOIBE register configures the corresponding pin to detect both rising and falling edges, regardless of the corresponding bit in the GPIO Interrupt Event (GPIOIEV) register (see page 666). Clearing a bit configures the pin to be controlled by the GPIOIEV register. All bits are cleared by a reset.



In our case, because we don’t want to detect both rising and falling edges, we would clear the bit so that it could be controlled by the GPIOIEV register.


GPIOF->IBE &= ~0x11;   /* STEP7B. trigger is controlled by IEV */



7C. Datasheet - GPIO Interrupt Event (GPIOIEV), offset 0x40C, p. 666


The GPIOIEV register is the interrupt event register. Setting a bit in the GPIOIEV register configures the corresponding pin to detect rising edges or high levels, depending on the corresponding bit value in the GPIO Interrupt Sense (GPIOIS) register (see page 664). Clearing a bit configures the pin to detect falling edges or low levels, depending on the corresponding bit value in the GPIOIS register. All bits are cleared by a reset.



In our case, a falling edge trigger is what we aim for. Therefore, the code is as follows:


GPIOF->IEV &= ~0x11;          /* STEP7C. falling edge trigger */



7D. Datasheet - GPIO Interrupt Mask (GPIOIM), offset 0x410, p. 667


The GPIOIM register is the interrupt mask register. Setting a bit in the GPIOIM register allows
interrupts that are generated by the corresponding pin to be sent to the interrupt controller on the combined interrupt signal. Clearing a bit prevents an interrupt on the corresponding pin from being sent to the interrupt controller. All bits are cleared by a reset.



GPIOF->IM  |= 0x11; /* STEP7D. unmaks interrupt (PF4 and PF0 are enable)*/


Part II. GPTM Initialization and Configuration

This section shows initialization and configuration for the General Purpose Timer Module (GPTM), datasheet p. 722


A set of 16/32-bit or 32/64-bit timers in the TI Tiva TM4C123G microcontroller, in addition to the PWM timer and the embedded SysTick in ARM Cortex-M processor.  

STEP 1. Enable clock signal to the GPTM.


For us to be able to use TimerA we must first enable the clock signal to the Timer Block0 by setting the appropriate TIMERn bit in the RCGCTIMER register.


SYSCTL->RCGCTIMER |= 1; /* STEP1. Enable clock signal to timer module 0. */


STEP 2. Configure the global operation of the GPTM module, either as 32- or 64-bit mode or in 16- or 32-bit mode.

Determine the global operation of the GPTM module by writing the corresponding value to the GPTMCFG register field: 2:0  


TIMER0->CFG = 0x00; //For a 16/32-bit timer, this value selects the 32-bit timer config.

TIMER0->CFG = 0x04; //For a 16/32-bit timer, this value selects the 16-bit timer config.


STEP3. Disable TimerA0 before making any changes.


Disable TimerA by setting bit TAEN in the GPTMCTL register to low before making any changes. Modification to TimerA during running time may cause unpredictable results.


TIMER0->CTL = 0; //disable Timer before initialization


STEP4. Configure TimerA0 as One-shot mode or Periodic Mode.


Configure TimerA as One-shot mode or Periodic Mode by setting field TAMR in the GPTM Timer A Mode Register (GPTMTnMR). Same register can be used to select count up or down


//TIMER0->TAMR = 0x01//One-Shot mode and down-counter

//TIMER0->TAMR = 0x02//Periodic mode and down-counter


STEP5. Enable the 8-bit Prescaler register for TimerA


By default the clock frequency driving the CPU is 


f (frequency) =16MHz


Which in turns gives a clock period of


T(period) = 1f  (frequency)= 116 MHz= 62.5 nsec


This clock signal will go into a divider or counter called the "prescaler." The output of the prescaler is the clock input to the Timer0A.


When using a 16-bit Timer0A timer config. the max. value to be stored in the GPTMTAILR register would be 65,535 (0xFFFF). 


N = 216-1=65,535


With this value, and a given clock frequency of 16MHz, the counter will have a delay time of 4ms to complete, i.e. it would take 4ms to count from 0 to 65,535.


Counter Delay Time  = ( N + 1 ) * clock period = (65,535 + 1) * 62.5ns = 4ms


To create a 1-second delay, you can change the number by which the prescaler divides the incoming clock, you can change the frequency of the Timer0A clock even though the system clock frequency remains the same.


Using a prescaler value of 250 provides a clock input to the Timer0A of 64,000 Hz


16Mhz250=64,000 Hz


That means the clock fed to timer A is 15.625 ms.


T(period) = 1f  (frequency)= 164,000 Hz= 15.625 ms


What value then should be stored in the GPTMTAILR register to obtain a 1s time delay?” - Ans. 63999 (‭FA00‬)


N = ( Counter Delay Time / clock period ) - 1 = ( 1s * 64000 ) - 1 = 63,999

Write the prescale value 250 to the GPTM Timer A Prescale (GPTMTAPR) register, p.760:


TIMER1->TAPR =250;


STEP6. Load the start value into the GPTM Timer n Interval Load Register (GPTMTnILR).


The value to be stored in the GPTMTAILR register will be 63999 (‭FA00‬), as it was explained in the previous step. 


TIMER0->TAILR = 64000 - 1; /* Timer A interval load value register */

//TIMER0->TAILR = FA00; /* Timer A interval load value register */


STEP7. Enable/disable GPTM controller-level interrupts.

If interrupts are required, set the appropriate bits in the GPTM Interrupt Mask Register (GPTMIMR), p.747.  


7A. Set the TATOIM bit in the GPTMIMR to one. This will enable GPTM Timer A Time-Out Interrupt Mask. 


TIMER0->IMR = 0x1 /* Enable GPTM controller-level interrupts. */


7B. Enable the Interrupt in the Nested Vector Interrupt Controller (NVIC), p.142


NVIC->ISER[0] |= (1<<19);

//NVIC->ISER[0] = 0x00080000;


Note: Use the TM4C123GH6PM header file as a reference or Table 2-9 from the datasheet. 

STEP8. Clear timeout flag before start counting. 


Make sure the timeout flag is clear by writing a 1 to the TATOCINT bit in the GPTMICR register.


TIMER0->ICR = 0x1;            //clear the TimerA timeout flag.

TIMER0->ICR |= (1<<0);        //clear the TimerA timeout flag.

STEP9. Enable the TimerA0 and start counting.


Set the TnEN bit in the GPTMCTL register to enable the timer and start counting.


TIMER0->CTL = 0x01; //enable TimerA0 after initialization


STEP10. Monitor the TimerA timeout flag.


Poll the GPTMRIS register to monitor the TimerA timeout flag. That is, the bit 0 (TATORIS) in the GPTMRIS register, p


if (TIMER0->RIS & 0x01)



Value Description


0 Timer A has not timed out.


1 Timer A has timed out. This interrupt is asserted when a one-shot or periodic 

mode timer reaches it's count limit (0 or the value loaded into GPTMTAILR, 

depending on the count direction).


In periodic mode the timer continues counting after each timeout.


Part III. NVIC Configuration

The Cortex-M has an on-chip interrupt controller called NVIC (Nested Vector Interrupt Controller) with the purpose of control and prioritizing interrupts.

Table 2-9 Interrupts in p. 104 from the datasheet lists the interrupts on the TM4C123GH6PM controller. We can see that IRQ19 and IRQ30 are assigned to TIMER0A and GPIOF, respectively. If we do not use priority, the lower IRQ number has higher priority. In other words, if IRQ19(TIMER0A) and IRQ30(GPIOF) are activated at the same time, the IRQ19 is serviced first and then the IRQ30


The CMSIS header file provides a structure definition to easily find the correct bits of the correct register to set the priority. For example, to assign IRQ30 a priority of 18, you only need to use the statement:


NVIC->IP[30] = 18 << 5;    /* STEP1A. Set IRQ30 priority to 18 */


Note: The priority number needs to be shifted 5 positions by default.  


Those two Interrupt Requests, IRQ19 and IRQ30, need to be enabled in the NVIC registers as well. See page 142 of the datasheet for a better description of the NVIC Register. 


NVIC->ISER[0] |= 0x40080000/* STEP1B. Enable IRQ19 & IRQ30 */



Part IV. Interrupt Vector Table - Adding Functions To The Table


How do we modify the Vector Table in the TM4C123GH6PM header file,   <TM4C123GH6PM.h> to add your own define function? 


You need to look for the cstartup_M (C Source File) on your directory file which it would be different depending on your Integrated Development Environment (IDE) tool . 


In the case of IAR IDE tool (or my case) it’s located at:


C:\Program Files (x86)\IAR Systems\Embedded Workbench 8.4\arm\src\lib\thumb


Look for: cstartup_M (C Source File).

Copy the file. Paste it to your IAR project directory. 





Open the cstartup_M (C Source) file and made the following modifications:


  1. Add the following FUNCTIONs to the file


/*---------  LM4F120H5QR Specific Interrupts  ---------- */

extern void TIMER0A_HANDLER( void );

extern void GPIOF_Handler ( void );




  1. Update the Vector Table from the file as shown below:

 


Use the TM4C123GH6PM header file as a reference. It points to you the numbers of zeros before the GPIOF_Handler or TIMER0A_HANDLER. Which is19 and 30, respectively.


III. Add the FUNCTION DECLARATION in the corresponding portion of the file. 




To validate, SAVE the file. Open your project > Right click > Add Files… Add cstartup_M to the project. 



Note: Make sure you remove the startup_LM4F.s file from your project. Doing so, will avoid duplicate errors when compiling. 


Upload the code to your microcontroller. 


Go to: View > Disassembly. 


In the Disassembly Window, go to address zero. Is where the vector table starts. Now, compare your cstartup_M file with the Disassembly windows. They both should match.




Plug your Tiva TM4C123G LaunchPad to your PC. 

(Make sure the Power Select switch is to the right).



Copy and Paste the code onto your development environment tool and run the program. You should be able now to make use of the interrupts giving a time event time-out and/or a switch.


All Together - Complete Code

/*
Lesson_15.2_Blink_PF1_using_Periodic_Mode_&_SW_Interrupt 

This program demonstrates the use of TimerA of Timer0 block to use timeout 
events to trigger interrupts. Timer1A is configured to timeout once every second. 
In the interrupt handler, the red LED is toggled. 

Also, the setup to trigger an interrupt using SW0 and SW1 with higher priority 
that TIMER0A. 

Notice in Table 2-9 from Datasheet (p.104): 
IRQ19 is assigned to Timer1A of Timer0 block. 

All LEDs are high active (a "1" turns ON the LED).
PF1 - red LED 0x02
PF2 - blue LED 0x04
PF3 -green LED 0x08

*/

#include <stdint.h>
#include <TM4C123GH6PM.h>       //Tiva C Series TM4C123G “Header File”.

void GPIO_init(void);
void timer0A_init(void);
void NVIC_init(void);

int main(void)
{
  GPIO_init();
  timer0A_init();
  NVIC_init();
 
  __enable_irq();               // Global enable IRQs
  
  while (1)
  {
  }
}

void GPIO_init(void)
{
  /* ---------------------------- PART I ---------------------------- */
  /* ------------ GPIOF Initialization and Configuration ------------ */
  SYSCTL->RCGCGPIO |= 0x20;     /* STEP1. Enable clock signal to the GPIOF */     
  GPIOF->DIR = 0x0E;            /* STEP2. Set PORTF3-1 output for LEDs. */
  /*SKIP STEP3-4*/
  GPIOF->LOCK = 0x4C4F434B;     /* STEP5A. unlock the GPIOCR Commit register. */
  GPIOF->CR = 0x01;             /* STEP5B. make PORTF0 configurable. */
  GPIOF->LOCK = 0;
  GPIOF->PUR = 0x11;            /* STEP5C.enable pull up for PORTF4, 0. */
  GPIOF->DEN = 0x1F;            /* STEP6. Enable PORTF4-0 DIGITAL pins */           
  GPIOF->IS  &= ~0x11;          /* STEP7A. make bit 4, 0 edge sensitive */
  GPIOF->IBE &= ~0x11;          /* STEP7B. trigger is controlled by IEV */
  GPIOF->IEV &= ~0x11;          /* STEP7C. falling edge trigger */
  GPIOF->ICR |= 0x11;           /* clear any prior interrupt */
  GPIOF->IM  |= 0x11;           /* STEP7D. unmaks interrupt (PF4 and PF0 are enable)*/
  /* ------------------------ PART I (END) -------------------------- */  
}

void timer0A_init(void)
{
  /* --------------------------- PART II -------------------------- */
  /* ----------- GPTM Initialization and Configuration ---------- */
  SYSCTL->RCGCTIMER |= 1;       /* STEP1. Enable clock signal to timer module 0. */  
  TIMER0->CFG = 0X04;           /* STEP2. Configure GPTM global operation as a 16-bit mode */ 
  TIMER0->CTL = 0;              /* STEP3. Disable Timer before initialization */
  TIMER0->TAMR = 0x02;          /* STEP4. Configure TimerA0 as Periodic Mode & down-counter */
  TIMER0->TAPR =250;            /* STEP5. Enable the 8-bit Prescaler register for TimerA */
  TIMER0->TAILR = 64000;        /* STEP6. Load the start value into the(GPTMTnILR) Register */
  TIMER0->IMR |= 0x01;          /* STEP7. Enable GPTM controller-level interrupts. */
  TIMER0->ICR = 0x1;            /* STEP8. Clear timeout flag before start counting. */
  TIMER0->CTL |= 0x01;          /* STEP9. Enable the TimerA0 and start counting. */
  /* ------------------------ PART II (END) ------------------------ */
}

void NVIC_init(void)
{
  /* --------------------------- PART III--------------------------- */
  /* --------------------- NVIC  Configuration --------------------- */
  NVIC->IP[30] = 0 << 5;    /* STEP1A. Set IRQ30 priority to 3 */
  NVIC->ISER[0] |= 0x40080000;  /* STEP1B. Enable IRQ19 & IRQ30 */
  /* ------------------------ PART III (END) ----------------------- */
}

void TIMER0A_HANDLER(void)
{
  volatile int readback;
  if (TIMER0->RIS & 0x01)       /* STEP10. Monitor the TimerA timeout flag. */
  {
    GPIOF->DATA ^= 0x02;            /* Toggle the red LED every second. */ 
    TIMER0->ICR = 0x1;
    readback = TIMER0->ICR;
  }
  else
  {
    /*should not get here, but if we do */
    TIMER0->ICR = TIMER0->MIS;
    readback = TIMER0->ICR;
  }
}

void GPIOF_Handler(void)
{
 volatile int readback;
 
 //GPIOF->DATA |= 0x00;
 
 while (GPIOF->MIS != 0)
 {
   if (GPIOF->MIS & 0x10) /* is it SW1 (PF4)?*/
   { /* GPIOF4 pin interrupt */
     /* turn ON green LED (PD3) */
     GPIOF->DATA ^= 0x08;
     GPIOF->ICR |= 0x10; 
     readback = GPIOF->ICR; 
   }
   else if (GPIOF->MIS & 0x01) /* then it must be SW2 (PF0) */
   { /* GPIOF0 pin interrupt */
     /* turn ON blue LED (PD3) */
     GPIOF->DATA ^= 0x04;
     GPIOF->ICR |= 0x01; 
     readback = GPIOF->ICR;     
   }
   else
   { /* We should never get here */ 
     /* But if we do, clear all pending interrupts */
     GPIOF->ICR = GPIOF->MIS;
     readback = GPIOF->ICR;
   }
 }
}



Comments