ARC features: Interrupts

Purpose

  • To introduce the interrupt handling of DesignWare® ARC® processors
  • To know how to use the interrupt and timer APIs already defined in embARC OSP

Requirements

The following hardware and tools are required:

  • PC host
  • GNU Toolchain for ARC Processors / MetaWare Development Toolkit
  • ARC board (EM Starter Kit / IoT Development Kit)
  • embarc_osp/arc_labs/labs/lab_interrupt

Content

  • Through embarc_osp/arc_labs/labs/lab_interrupt/part1 to learn the basics of interrupt handling of DesignWare® ARC® processors and the interrupt API provided by embARC OSP
  • Through embarc_osp/arc_labs/labs/lab_interrupt/part2 to learn the interrupt priority and interrupt nesting of DesignWare® ARC® processors and corresponding API of embARC OSP

Principles

  1. Interrupt

An interrupt is a mechanism in processor to respond to special interrupt signals emitted by hardware or software. Interrupts can be used by processor to perform a specific function after some specific event happens and then return to normal operation. For this purpose there are many different types of interrupts possible to be issued by hardware and software and each interrupt can have it’s own functions called Interrupt Service Routine (ISR). ISR is a function (sequence of commands) to deal with the immediate event generated by a given interrupt.

  1. Interrupt unit of DesignWare® ARC® processors

The interrupt unit of DesignWare® ARC® processors has 16 allocated exceptions associated with vectors 0 to 15 and 240 interrupts associated with vectors 16 to 255. The ARCv2 interrupt unit is highly programmable and supports the following interrupt types:

  • Timer — triggered by one of the optional extension timers and watchdog timer
  • Multi-core interrupts —triggered by one of the cores in a multi-core system
  • External — available as input pins to the core
  • Software-only — triggered by software only

The interrupt unit of DesignWare® ARC® processors has the following interrupt specifications:

  • Support for up to 240 interrupts
    • User configurable from 0 to 240
    • Level sensitive or pulse sensitive
  • Support for up to 16 interrupt priority levels
    • Programmable from 0 (highest priority) to 15 (lowest priority)
  • The priority of each interrupt can be programmed individually by software
  • Interrupt handlers can be preempted by higher-priority interrupts
    • Optionally, highest priority level 0 interrupts can be configured as “Fast Interrupts”
    • Optional second core register bank for use with Fast Interrupts option to minimize interrupt service latency by minimizing the time needed for context saving
  • Automatic save and restore of selected registers on interrupt entry and exit for fast context switch
  • User context saved to user or kernel stack, under program control
  • Software can set a priority level threshold in STATUS32.E that must be met for an interrupt request to interrupt or wake the processor
  • Minimal interrupt / wake-up logic clocked in sleep state
    • Interrupt prioritization logic is purely combinational
  • Any Interrupt can be triggered by software

The interrupt unit can be programmed by auxiliary registers. For more details, See DesignWare® ARC® processors ISA.

  1. Interrupt API in embARC OSP

In embARC OSP, a basic exception and interrupt processing framework is implemented in embARC OSP. Through this framework, you can handle specific exceptions or interrupts by installing the desired handlers. This can help you analyze the underlying details of saving and operating registers. See here for detais.

The interrupt and exception related API are defined in arc_exception.h.

Steps

Part I: implement a customized timer0 interrupt handling

  1. Build and Run
$ cd <embarc_root>/arc_labs/labs/lab4_interrupt/part1
# for emsk
$ make BOARD=emsk BD_VER=22 CUR_CORE=arcem7d TOOLCHAIN=gnu run
# for iotdk
$ make BOARD=iotdk TOOLCHAIN=gnu run
  1. Output
embARC Build Time: Mar 16 2018, 09:58:46
Compiler Version: Metaware, 4.2.1 Compatible Clang 4.0.1

This is an example about timer interrupt
/********TEST MODE START********/
0s

1s

2s

3s

4s

5s

...
  1. Code analysis

The code can be divided into three parts: interrupt service function, main function, and delay function.

  • Interrupt service function:
static void timer0_isr(void *ptr)
{
  arc_timer_int_clear(TIMER_0);
  t0++;
}

This code is a standard example of an interrupt service routine: enters the service function, clears the interrupt flag bit, and then performs the processing that needs to be done in the interrupt service function. Other interrupt service functions can also be written using this template.

In this function, the count variable t0 is incremented by one.

  • Main function
    int main(void)
    {
      int_disable(INTNO_TIMER0);
      arc_timer_stop(TIMER_0);

      int_handler_install(INTNO_TIMER0, timer0_isr);
      int_pri_set(INTNO_TIMER0, INT_PRI_MIN);

      EMBARC_PRINTF("\r\nThis is a example about timer interrupt.\r\n");
      EMBARC_PRINTF("\r\n/******** TEST MODE START ********/\r\n\r\n");

      int_enable(INTNO_TIMER0);
      arc_timer_start(TIMER_0, TIMER_CTRL_IE | TIMER_CTRL_NH, COUNT);

      while(1)
      {
            timer0_delay_ms(1000);
            EMBARC_PRINTF("\r\n %ds.\r\n",second);
            second ++;
      }
      return E_SYS;
}

The EMBARC_PRINTF function is only used to send information to the computer, which can be ignored during analysis.

This code is divided into two parts: initialization and looping.

In the initialization section, the timer and timer interrupts are configured.

This code uses the embARC OSP API to program Timer0. These two methods are the same. The API just encapsulates the read and write operations of the auxiliary registers for convenience.

First, in order to configure Timer0 and it’s interrupts, turn them off first. This work is done by the functions int_disable and arc_timer_stop.

Then configure the interrupt service function and priority for our interrupts. This work is done by the functions int_handler_install and int_pri_set.

Finally, after the interrupt configuration is complete, enable the Timer0 and interrupts that are previously turned off. This work is done by the functions int_enable and arc_timer_start. The implementation of the arc_timer_start function is the same as the reading and writing of the auxiliary registers in lab_timer. You can view them in the file arc_timer.c. One point to note in this step is the configuration of timer_limit (the last parameter of arc_timer_start). Configure the interrupt time to 1ms, do a simple calculation (the formula is the expression after COUNT).

In this example, the loop body only serves as an effect display. Delay function in the loop body to print the time per second is called.

Note

Since nSIM is only simulated by computer, there may be time inaccuracy when using this function. You can use the EMSK to program the program in the development board. In this case, the time is much higher than that in nSIM.

  • Delay function
static void timer0_isr(void *ptr)
{
  t0 = 0;
  while(t0<ms);
}

This code is very simple and the idea is clear. When the function entered, clear the global variable t0. The interrupt interval is set to 1ms in the above arc_timer_start, assume that every time t0 is incremented, the time has passed 1ms.

Wait through the while(t0<ms) sentence, so that the full ms delay with higher precision is received.

Part II: interrupt priority and interrupt nesting

  1. Build and Run
$ cd <embarc_root>/arc_labs/labs/lab4_interrupt/part2
# for emsk
$ make BOARD=emsk BD_VER=22 CUR_CORE=arcem7d TOOLCHAIN=gnu run
# for iotdk
$ make BOARD=iotdk TOOLCHAIN=gnu run
  1. Output
embARC Build Time: Mar 16 2018, 09:58:46
Compiler Version: Metaware, 4.2.1 Compatible Clang 4.0.1

This test will start in 1s.

/********TEST MODE START********/

Interrupt  nesting!
Interrupt  nesting!
Interrupt  nesting!
Interrupt  nesting!
Interrupt  nesting!
Interrupt
Interrupt
Interrupt
Interrupt
Interrupt
Interrupt  nesting!
Interrupt  nesting!
Interrupt  nesting!
Interrupt  nesting!
Interrupt  nesting!
Interrupt
Interrupt
Interrupt
  1. Code analysis

The code for PART II can be divided into two parts: the interrupt service routine and the main function.

  • Interrupt service function
static void timer0_isr(void *ptr)
{
  arc_timer_int_clear(TIMER_0);

  timer_flag = 0;

  board_delay_ms(10, 1);

  if(timer_flag)
  {
          EMBARC_PRINTF("Interrupt nesting!\r\n");
  }
  else
  {
          EMBARC_PRINTF("Interrupt\r\n");
  }

  hits++;
}

static void timer1_isr(void *ptr)
{
  arc_timer_int_clear(TIMER_1);

  timer_flag = 1;
}

Through the above code, when timer0’s interrupt comes in and is serviced, different output messages are sent by ISR according to the value of timer_flag, which is only be set in timer1’s ISR timer1_isr. This means timer0’s interrupt is preempted by timer1’s interrupt as it has a higher interrupt priority.

“Interrupt nesting!” indicates that interrupt nesting has occurred, and “Interrupt” indicates that it has not occurred.

  • main function
int main(void)
{
        arc_timer_stop(TIMER_0);
        arc_timer_stop(TIMER_1);

        int_disable(INTNO_TIMER0);
        int_disable(INTNO_TIMER1);

        int_handler_install(INTNO_TIMER0, timer0_isr);
        int_pri_set(INTNO_TIMER0, INT_PRI_MAX);

        int_handler_install(INTNO_TIMER1, timer1_isr);
        int_pri_set(INTNO_TIMER1, INT_PRI_MIN);

        EMBARC_PRINTF("\r\nThe test will start in 1s.\r\n");

        int_enable(INTNO_TIMER0);
        int_enable(INTNO_TIMER1);

        arc_timer_start(TIMER_0, TIMER_CTRL_IE | TIMER_CTRL_NH, MAX_COUNT);
        arc_timer_start(TIMER_1, TIMER_CTRL_IE | TIMER_CTRL_NH, MAX_COUNT/100);

        while(1)
        {
                if((hits >= 5) && (nesting_flag == 1)) {
                        arc_timer_stop(TIMER_0);
                        arc_timer_stop(TIMER_1);

                        int_disable(INTNO_TIMER0);
                        int_disable(INTNO_TIMER1);

                        int_pri_set(INTNO_TIMER0, INT_PRI_MIN);
                        int_pri_set(INTNO_TIMER1, INT_PRI_MAX);

                        nesting_flag = 0;

                        int_enable(INTNO_TIMER0);
                        int_enable(INTNO_TIMER1);

                        arc_timer_start(TIMER_0, TIMER_CTRL_IE | TIMER_CTRL_NH, MAX_COUNT);
                        arc_timer_start(TIMER_1, TIMER_CTRL_IE | TIMER_CTRL_NH, MAX_COUNT/10);
                } else if((hits >= 10) && (nesting_flag == 0)) {
                        arc_timer_stop(TIMER_0);
                        arc_timer_stop(TIMER_1);

                        int_disable(INTNO_TIMER0);
                        int_disable(INTNO_TIMER1);

                        int_pri_set(INTNO_TIMER0, INT_PRI_MAX);
                        int_pri_set(INTNO_TIMER1, INT_PRI_MIN);

                        hits = 0;
                        nesting_flag = 1;

                        int_enable(INTNO_TIMER0);
                        int_enable(INTNO_TIMER1);

                        arc_timer_start(TIMER_0, TIMER_CTRL_IE | TIMER_CTRL_NH, MAX_COUNT);
                        arc_timer_start(TIMER_1, TIMER_CTRL_IE | TIMER_CTRL_NH, MAX_COUNT/100);
                }
        }
        return E_SYS;
}

First, the timer 0 and timer 1 are configured and install with corresponding ISR. Then in the while loop, the interrupt priority of timer 0 and timer 1 are periodically changed to make the interrupt nesting happen.

Exercises

Try using an interrupt other than a timer to write a small program. (For example, try to implement a button controlled LED using GPIO interrupt)