Dev Log
Engineering Tom Whitfield

Generated Driver Interrupt Handling

Generated IRQ vectors, NVIC setup, and ISR stubs based on datasheet interrupt sources, with guidance for shared lines and RTOS constraints.

Cortex-M NVIC interrupt priority configuration diagram

The nRF52840 exposes 48 external interrupt lines, but its datasheet does not explain the RTOS consequences of choosing a priority. A one-bit error in NVIC priority grouping can bypass FreeRTOS kernel protection. The scheduler treats configMAX_SYSCALL_INTERRUPT_PRIORITY as a strict boundary: a numerically lower peripheral priority makes RTOS calls unsafe in that ISR, while a value that is too high can let the interrupt run before scheduler setup is complete.

Finding IRQ Data in Datasheets

Chip datasheets usually place interrupt details in an early vector table or inside each peripheral chapter, alongside interrupt sources and status bits. Embedd parses both locations and reconciles the extracted information.

The vector table supplies the IRQ number and handler name, such as USART1_IRQn = 37. The peripheral section identifies the source flags, their level- or edge-sensitive behavior, and the status bits the ISR must clear to release the line. A usable driver needs both sets of data. Embedd links them by matching peripheral names in the parsed register tables.

Generated NVIC Configuration

For Cortex-M chips, generated NVIC setup uses the CMSIS API. Embedd creates an embx___irq_enable function around the standard NVIC calls:

void embx_stm32l4_usart1_irq_enable(uint32_t priority) {
    /* Clear any pending interrupt before enabling */
    NVIC_ClearPendingIRQ(USART1_IRQn);
    /* Set priority in the NVIC priority register */
    NVIC_SetPriority(USART1_IRQn, priority);
    /* Enable the interrupt in the NVIC */
    NVIC_EnableIRQ(USART1_IRQn);
}

The caller supplies the priority because its correct value depends on the RTOS and the application's interrupt scheme. The generated header also documents the IRQ number, this peripheral's datasheet-defined sources, including RXNE, TXE, TC, and errors, plus the ISR clearing bits for each one.

Cortex-M3 and M4 devices can divide the NVIC priority field through SCB->AIRCR. PRIGROUP assigns portions of the 8-bit field to preemption priority and sub-priority. Embedd leaves that system-wide setting untouched, since it governs every interrupt on the chip. A generated comment records the split under the CMSIS default, where all bits serve as preemption priority, so the caller can account for it.

Generated ISR Stubs

For each interrupt source, the driver contains a weak ISR stub. If you provide a function with the same name, the linker selects it; otherwise, the stub remains in use:

/* Weak stub -- override by defining USART1_IRQHandler in your application */
void __attribute__((weak)) USART1_IRQHandler(void) {
    /* Read ISR to identify interrupt source */
    uint32_t isr = USART1->ISR;

    if (isr & EMBX_USART1_ISR_RXNE_MSK) {
        /* Receive data register not empty -- read RDR to clear */
        (void) USART1->RDR;
    }
    if (isr & EMBX_USART1_ISR_ORE_MSK) {
        /* Overrun error -- write 1 to ORECF in ICR to clear */
        USART1->ICR = EMBX_USART1_ICR_ORECF_MSK;
    }
    /* ... other source handling */
}

This stub is not a production ISR. Instead, it illustrates the required clearing order for every source, a common source of mistakes in handwritten drivers. Embedd obtains that order from the datasheet's access annotations: RO fields cleared by reading, W1C fields cleared by writing 1, and fields that clear themselves after a peripheral operation.

Shared IRQ Lines

Some MCU families route multiple peripherals through one IRQ line. On several STM32F0 devices, USART3 through USART8 use USART3_8_IRQn. The ISR must inspect each peripheral's status register, identify the source that fired, and clear the relevant bits for each peripheral.

Embedd marks shared lines with a clear comment and generates a dispatcher stub that reads every shared peripheral's status register:

/* SHARED IRQ: USART3, USART4, USART5, USART6, USART7, USART8 share this line.
 * The ISR must check all peripheral ISR registers. */
void __attribute__((weak)) USART3_8_IRQHandler(void) {
    if (USART3->ISR & EMBX_USART3_ISR_RXNE_MSK) { /* handle USART3 */ }
    if (USART4->ISR & EMBX_USART4_ISR_RXNE_MSK) { /* handle USART4 */ }
    /* ... */
}

Application context sets the limit here. Only the application knows which shared peripherals are active and what their handling should do. The generated code supplies the framework, while the application adds the specific logic.

RTOS Integration Limits

FreeRTOS ISR calls must use the FromISR forms, such as xQueueSendFromISR, and the interrupt priority must be numerically greater than or equal to configMAX_SYSCALL_INTERRUPT_PRIORITY. With a Cortex-M4's default NVIC setup, where 3 priority bits are implemented, an ISR that calls RTOS APIs typically needs priority 2 or higher (numerically).

Embedd leaves the priority choice to you. A fixed offset from configMAX_SYSCALL_INTERRUPT_PRIORITY would require the FreeRTOS settings from FreeRTOSConfig.h, which the chip datasheet does not contain. Instead, the generated comments state the requirement, point to FreeRTOS guidance on interrupt priority, and show how to calculate a safe value when configMAX_SYSCALL_INTERRUPT_PRIORITY is known.

ThreadX and Zephyr impose comparable restrictions through different APIs. For Zephyr targets, Embedd initializes IRQs with IRQ_CONNECT rather than CMSIS NVIC calls because Zephyr controls the vector table and disallows direct NVIC access in driver code. The target RTOS therefore changes the generated structure, not only the selected MCU details.

Your Remaining Driver Work

The boundary is deliberate. Embedd generates vector-table wiring, an NVIC configuration function, an ISR stub with the required clearing sequences, and annotations for the remaining work. It does not create application ISR behavior such as queue pushes, DMA completion callbacks, or state-machine transitions. Those choices depend on the application architecture and cannot come from the datasheet.

Generated interrupt infrastructure removes errors that occur before application logic begins: an incorrect IRQ number, a missed W1C status clear, a wrong priority-group setting, or a missing NVIC_EnableIRQ call. These are translation mistakes between the datasheet and C, which is the problem Embedd is designed to address.

Generate a driver today

Upload a chip datasheet and receive C99 peripheral drivers in under two minutes.

Get started free