17.3 Associating a Handler Function With an Exception

Each exception handler, be it internal or external, is associated with a function. The exception handled by that function depends on its name. The name of a handler function corresponds to the name of the exception it handles, suffixed with _Handler. For example, SysTick_Handler is the exception handler for the SysTick interrupt. To define a custom handler, write a function with the name matching that of the default handler for that exception and link it into your application. Aside from the standard internal handlers (see next section), names of handler functions are specific to the device. To see the full list of handler function names, check the appropriate device header file in pic32c/include/proc.

The following example shows how to create a custom SysTick_Handler.

#include <xc.h>
#include <stdint.h>

const static uint32_t LOWEST_IRQ_PRIORITY =
  (1UL << __NVIC_PRIO_BITS) - 1UL;

static uint32_t tick_counter;

__attribute__((interrupt)) void SysTick_Handler(void) {
    tick_counter += 1;
}

int main(void) {
    // Get the reload value for 10ms.
    uint32_t ticks = SysTick->CALIB & SysTick_CALIB_TENMS_Msk;

    // Set the IRQ priority, the SysTick reload value, the counter
    // value, then enable the interrupt. The same can be achieved
    // using the function SysTick_Config from the CMSIS API.
    NVIC_SetPriority(SysTick_IRQn, LOWEST_IRQ_PRIORITY);
    SysTick->LOAD = (uint32_t) ticks - 1UL;
    SysTick->VAL = 0UL;
    SysTick->CTRL = SysTick_CTRL_CLKSOURCE_Msk
      | SysTick_CTRL_TICKINT_Msk
      | SysTick_CTRL_ENABLE_Msk;

    // Ensure the changes are written before continuing.
    __DSB();
    while (1) { __builtin_nop(); }
    return 0;
}

Some PIC32C/SAM devices have faults that must enabled in software. The following example associates a handler with the UsageFault and enables divide-by-zero errors.

#include <xc.h>
#include <stdint.h>

__attribute__((interrupt)) void UsageFault_Handler(void);

void UsageFault_Handler(void) {
    __builtin_software_breakpoint();
}
uint32_t DemoFunction(uint32_t dividend, uint32_t divisor) {
    return dividend / divisor;
}

int main(void) {
    // Enable UsageFault and divide by zero errors
    SCB->SHCSR |= SCB_SHCSR_USGFAULTENA_Msk;
    SCB->CCR |= SCB_CCR_DIV_0_TRP_Msk;
    // Ensure the changes are written before continuing.
    __DSB();

    DemoFunction(2, 0);

    return 0;
}

Below is a longer example that uses two interrupts to safely access shared data without having to disable interrupts. The space for tick_counter is only accessed by the SysTick and PendSV handlers. Both run at the same priority meaning that they cannot interrupt each other. Hence, access to tick_counter is properly serialized.

#include <xc.h>
#include <stdint.h>
#include <assert.h>

const static uint32_t LIMIT = 50;
const static uint32_t LOWEST_IRQ_PRIORITY =
    (1UL << __NVIC_PRIO_BITS) - 1UL;

static uint32_t tick_counter = 0;

__attribute__((interrupt)) void SysTick_Handler(void) {
    tick_counter += 1;
    __conditional_software_breakpoint(tick_counter <= LIMIT);
}

static uint32_t tick_limit = 0;
static uint32_t result = 0;

__attribute__((interrupt)) void PendSV_Handler(void) {
    if (tick_counter == tick_limit) {
        tick_counter = 0;
        result = 1;
    } else {
        result = 0;
    }
}

static inline void TriggerPendSV(void) {
    SCB->ICSR = SCB_ICSR_PENDSVSET_Msk;
    __DSB();
    __ISB();
}

uint32_t TickCounterReached(uint32_t limit) {
    tick_limit = limit;
    TriggerPendSV();
    return result;
}

int main(void) {
    const uint32_t ticks =
        (SysTick->CALIB & SysTick_CALIB_TENMS_Msk);

    SysTick_Config(ticks);
    NVIC_SetPriority(SysTick_IRQn, LOWEST_IRQ_PRIORITY);
    NVIC_SetPriority(PendSV_IRQn, LOWEST_IRQ_PRIORITY);
    __DSB();

    while (1) {
        if (TickCounterReached(LIMIT)) {
            __builtin_software_breakpoint();
        }
    }

    return 0;
}
In order to set interrupt handlers at runtime, it is best to copy the vector table into RAM. The vector table is located via the Vector Table Offset Register (VTOR) in the System Control Block (SCB). It is described by the DeviceVectors structure and is found in the variable exception_table. These definitions are available in the device specific header file included via xc.h. To set interrupt handlers dynamically, create a DeviceVectors structure, copy exception_table into it, point the VTOR at it, then change the handlers as desired. The actual structure definition is found in the default device startup code. It is defined as weak and is put in the .vectors.default section, which is mapped to the flash memory region by the default linker script. In C code, the definition is the following, with the actual function pointers elided.
__attribute__((section(".vectors.default"), weak, externally_visible))
const DeviceVectors exception_table = { ... 

Setting a handler is best done using the Interrupts and Exceptions portion of the CMSIS API, provided via xc.h. Each interrupt has an IRQ number found in the IRQn_Type enumeration. The name for the IRQ number is similar to the name of the handler function, except with the _IRQn suffix. To set the handler, use the function NVIC_SetHandler() giving it the IRQ number and the function address.

Alignment of the vector table is important but is specific to the device (see the appropriate architecture manual for details). For Cortex-M7 devices (such as the SAME70) the table must be aligned on a power of two greater than or equal to four times the number of exceptions, with a minimum of 128-byte alignment. At present, this must be determined manually if you create your own vector table.

#include <xc.h>
#include <stdint.h>
#include <string.h>

// For a SAME70 device. Supports 90 exceptions (16 + 74).
// 90 * 4 = 360. Next highest power of 2 is 512.
#define TBL_ALIGN 512

DeviceVectors exn_table __attribute__((aligned(TBL_ALIGN)));
extern DeviceVectors exception_table;

static uint32_t counter;
__attribute__((interrupt)) void CustomSysTick_Handler(void) {
    counter += 1;
}

int main(void)
{
    memcpy(&exn_table, &exception_table, sizeof(DeviceVectors));

    // Disable interrupts, set the VTOR, ensure all data
    // operations are complete, then enable interrupts.
    __disable_irq();
    SCB->VTOR = (uint32_t) exn_table;
    __DSB();
    __enable_irq();

    NVIC_SetVector(SysTick_IRQn, (uint32_t) CustomSysTick_Handler

    // Code continues here.
}