Peripheral features
The Interrupt Controller (INTC) module arbitrates among the various interrupt requests(IRQs).
- The module supports unique interrupt vectors and programmable interrupt priority.
- It signals to the DSC core when an interrupt of sufficient priority exists and to what address to jump to service this interrupt.
- Programmable priority levels for each IRQ.
- Two programmable fast interrupts.
- Fast Interrupts vector directly to a service routine based on values in the Fast Interrupt Vector Address registers without having to go to a jump table first.
How this driver is designed to make this peripheral work
This driver provide multiple functions to configure the INTC module. The APIs can be classified in 3 API groups:
- Normal Interrupt Interfaces
- The APIs in this function group can be used to enable/disable IRQ with priority and configure interrupt vector base address. Normal interrupt handling concatenates the vector base address (VBA) and the vector number to determine the vector address.
- Fast Interrupt Interfaces
- The APIs in this function group can be used to configure two programmable fast interrupts.
- Interrupt Status Interfaces
- The APIs in this function group can be used to check whether an IRQ is pending, get the latest responded IRQ's vector number and get which interrupt priority level allow the servicing of an IRQ with higher priority than the current exception.
How to use this driver
Before go to the details of using INTC driver, some background knowledge of SDK vector table should be established. SDK defines vector table in xxxx_Vectors.c, taking MC56F83xxx_Vectors.c as an example to describe the vector table and weak ISR entry implementation: Vector table _vect is defined as a function, all ISR entries in the table are defined as two words instruction, "JMP/JSR" followed by weak ISR entries, there are two kinds of weak ISR entries:
- Single weak function, most of ISR entries are single weak ISR. These weak ISR entries are defined to call the default_interrupt() function, when user enable the related vector, just simply define a strong ISR entry to overwrite the weak definition. Take COP interrupt as an example. COP interrupt will jump to ivINT_COP_INT as defined in vector table. The implementation of single weak ivINT_COP_INT show as below:
#pragma interrupt alignsp saveall
__attribute__((weak)) void ivINT_COP_INT(void);
__attribute__((weak)) void ivINT_COP_INT(void)
{
default_interrupt();
}
#pragma interrupt off
User should implement the ISR entry in his application to specify his own interrupt routine, such as below: #pragma interrupt alignsp saveall
void ivINT_COP_INT(void)
{
copIsrFlag = true;
copRefreshCount++;
}
#pragma interrupt off
Then COP interrupt will jump to the strong implementation in application.
- Double weak function. This is special design architecture to ease user to directly use interrupt/DMA driven transactional layer SDK driver, such layer of API integrated in bus drivers such as QSCI/QSPI/I2C/DMA. When interrupt happens, will firstly call the first level ISR entry defined in vector table, which is defined as an weak ISR entry and call to another weak function, alias second level weak ISR, the naming of such ISR ends with "_DriverIRQHandler" and already have a strong implementation in specified SDK driver. Take DMA0 interrupt as an example, when interrupt happens, weak ISR entry ivINT_DMA0 is jumped, and the definition of the weak ISR is show below:
__attribute__((weak)) static void DMA0_DriverIRQHandler(void)
{
default_interrupt();
}
#pragma interrupt alignsp saveall
__attribute__((weak)) void ivINT_DMA0(void);
__attribute__((weak)) void ivINT_DMA0(void)
{
DMA0_DriverIRQHandler();
}
#pragma interrupt off
The weak ISR entry ivINT_DMA0 call function DMA0_DriverIRQHandler, which also defined as a weak function, and the function default also call to default_interrupt(). In SDK DMA driver, there's definition of DMA0_DriverIRQHandler and it's strong definition, then when application integrate DMA driver and DMA0 interrupt happens, the DMA0_DriverIRQHandler() defined in SDK DMA driver will be executed in interrupt routine. This way allows user to use driver pre-defined IRQ routine to do complete transfer, also leave the possibility for user to define his own ISR entry for DMA0. He could use the previous way as re-write single-weak ISR entry, implement first level weak ISR entry ivINT_DMA0.
INTC driver provides settings of vector table address, IRQ priority, fast interrupt handle and interrupt status.
- For vector table base address setting, call INTC_SetVectorAddress() function, you could refer to vector table definition in xxxx_Vectors.c.
- To enable IRQ with priority or disable IRQ, invoke INTC_SetIRQPriorityNum() function. Refer to SOC header file to use the IRQn_Type structure. Please note a none-zero priority number for different IP may means different priority level, check the RM INTC chapter for more details.
- For fast interrupts, INTC_SetIRQPriorityNum() function can used to set the priority of the interrupt as level 2, then invoke the corresponding API in Fast Interrupt Interfaces, such as INTC_SetFastIRQVectorHandler0 to set the fast IRQ handler.
- If needed, the APIs in Interrupt Status Interfaces can be used. Invoke INTC_GetPendingIRQ() to check whether an IRQ is pending. Get the latest responded IRQ's vector number can use INTC_GetLatestRespondedVectorNumber() function. To get which interrupt priority level is permitted, INTC_GetIRQPermittedPriorityLevel() can be invoked.
- Note: IRQ number and vector number are different. IRQ number is an IP enumerator in INTC IPRx register. Vector number is an IP enumerator in vector table.
Typical Use Case
- Normal interrupt Handling: Use normal interrupts when a significant amount of code is needed to serve the interrupt. Follow below steps to realize normal interrupt:
- Setup interrupt priority in INTC module. call INTC_SetIRQPriorityNum to do the setting.
- Enable interrupt in the peripheral module.
- Define strong implementation of ISR entry for the definition in vector table. Use #pragma interrupt xxxxx for the ISR. This is to tell the compiler to store necessary registers before the execution of ISR, and return with RTI instruction.
- #pragma interrupt saveall is the safest way to define an ISR. It saves all the registers no matter what. Use it when:
- There is library function invoking in the ISR.
- There are too many function nesting invoking in the ISR, you don't want to identify them all.
#pragma interrupt saveall
void isrFunc(void)
{
}
#pragma interrupt off
- Use #pragma interrupt on when there isn't any other function invoking in the ISR.
#pragma interrupt on
void isrFunc(void)
{
}
#pragma interrupt off
Use #pragma interrupt on when there is function invoking in the ISR, and use #pragma interrupt called for the invoked functions in the ISR.
#pragma interrupt on
void isrFunc(void)
{
func1();
}
#pragma interrupt off
#pragma interrupt called
void func1(void)
{
}
#pragma interrupt off
Simple sample code for normal interrupt setup
#pragma interrupt on
void CMPA_IRQHandler()
{
isInterrupted = true;
}
#pragma interrupt off
INTC_SetIRQPriorityNum(CMPA_IRQn, 3); // Set CMPA_IRQn to priority level 2
Fast Interrupt Handling: Fast interrupts are intended for use with interrupts that do not require a large amount of code to service, suggested uses for fast interrupts are:
- Data move operations
- Repetitive interrupt source with minimal ISR code
Steps:
- Set the priority of the interrupt as level 2, call INTC_SetIRQPriorityNum to do the setting.
- Use #pragma interrupt fast for fast interrupt ISR.
- It is preferred that there should be no function invoking in this ISR.
- There shouldn't be library function invoking in this ISR.
- If there is function invoking (not lib function) in this ISR, use #pragma interrupt called for the invoked function.
- Call INTC_SetFastIRQVectorHandler0 or INTC_SetFastIRQVectorHandler1 to configure the vector number and ISR entry for the specified fast interrupt.
Simple sample code for fast interrupt setup
#pragma interrupt fast
void IRQ_HANDLER()
{
isInterrupted = true;
}
#pragma interrupt off