.. _lab4: ARC features: Interrupts ########################### Purpose ======== - To introduce the interrupt handling of |arc| - To know how to use the interrupt and timer APIs already defined in |embarc| Requirements ============ The following hardware and tools are required: * PC host * |arcgnu| / |mwdt| * ARC board (|emsk| / |iotdk|) * ``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 |arc| and the interrupt API provided by |embarc| * Through ``embarc_osp/arc_labs/labs/lab_interrupt/part2`` to learn the interrupt priority and interrupt nesting of |arc| and corresponding API of |embarc| 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. 2. Interrupt unit of |arc| The interrupt unit of |arc| 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 |arc| 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 |arc| ISA. 3. Interrupt API in |embarc| In |embarc|, 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 .. code-block:: console $ cd /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 2. Output .. code-block:: console 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 ... 3. Code analysis The code can be divided into three parts: interrupt service function, main function, and delay function. - Interrupt service function: .. code-block:: c static void timer0_isr(void *ptr) { 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 .. code-block:: c int main(void) { int_disable(INTNO_TIMER0); 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); 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| 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 ``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 ``timer_start``. The implementation of the ``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 ``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 .. code-block:: c static void timer0_isr(void *ptr) { t0 = 0; while(t0/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 2. Output .. code-block:: console 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 3. Code analysis The code for PART II can be divided into two parts: the interrupt service routine and the main function. - Interrupt service function .. code-block:: c static void timer0_isr(void *ptr) { 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) { 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 .. code-block:: c int main(void) { timer_stop(TIMER_0); 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); timer_start(TIMER_0, TIMER_CTRL_IE | TIMER_CTRL_NH, MAX_COUNT); timer_start(TIMER_1, TIMER_CTRL_IE | TIMER_CTRL_NH, MAX_COUNT/100); while(1) { if((hits >= 5) && (nesting_flag == 1)) { timer_stop(TIMER_0); 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); timer_start(TIMER_0, TIMER_CTRL_IE | TIMER_CTRL_NH, MAX_COUNT); timer_start(TIMER_1, TIMER_CTRL_IE | TIMER_CTRL_NH, MAX_COUNT/10); } else if((hits >= 10) && (nesting_flag == 0)) { timer_stop(TIMER_0); 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); timer_start(TIMER_0, TIMER_CTRL_IE | TIMER_CTRL_NH, MAX_COUNT); 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)