Interrupt (IRQ) Handling in libmaple ==================================== There have been various threads asking about interrupt handling in libmaple. This file explains the libmaple interrupt handling interfaces, how libmaple organizes its interrupt handlers, and how to write new interrupt handlers. If you know the Cortex M3 and the libmaple sources pretty well, you can skip to the end to read how to add a new interrupt handler. Otherwise, read on. 1. Interrupts in Wirish ----------------------- There are very few Wirish-level convenience functions for handling interrupts. The most obvious one is attachInterrupt(), which is used for external interrupt handlers: http://leaflabs.com/docs/lang/api/attachinterrupt.html Another example is HardwareTimer::attachInterrupt(); a usage example is here: http://leaflabs.com/docs/lang/api/hardwaretimer.html#using-timer-interrupts What these have in common is that they take a pointer to the function the user wants to use as an interrupt handler, and pass it down to the libmaple proper interface for the subsystem. For example, attachInterrupt() calls exti_attach_interrupt(), and HardwareTimer::attachInterrupt() calls timer_attach_interrupt(). So, as usual, the Wirish functions are just thin wrappers around the libmaple proper interfaces. 2. Interrupts in libmaple proper -------------------------------- The libmaple proper interfaces all use functions named foo_attach_interrupt(). So there's the exti_attach_interrupt() and timer_attach_interrupt() routines that have already been mentioned, but there are also some others which (at time of writing) don't have Wirish equivalents, like dma_attach_interrupt(). These functions all behave the same way: they take a particular peripheral interrupt and a pointer to a user function, and they do whatever is necessary to turn on the interrupt line and ensure that the user's function gets called exactly when that interrupt occurs. This in itself is a useful abstraction above the hardware. To understand why, here's a bullet-point primer on how interrupts work on STM32/Cortex M3 (read about the NVIC in a Cortex M3 book to understand all the details; these are just the basics): - Each series of STM32 microcontroller (STM32F1, STM32F2, etc.) specifies a certain number of IRQs (the libmaple type which enumerates the IRQs is nvic_irq_num; see the libmaple/nvic.h documentation for all the details). - Each IRQ has a number, which corresponds to a real, physical interrupt line inside the processor. When you talk about an "IRQ", you usually mean one of these interrupt lines. - The interrupt hardware can be configured to call a single function per IRQ line when an interrupt associated with the IRQ has happened (e.g. when a pin changes from low to high for an external interrupt). - However, sometimes, various interrupts share an IRQ line. For example, on Maple, external interrupts 5 through 9 all share a single IRQ line (which has nvic_irq_num NVIC_EXTI_9_5). That means that when any one (or any subset!) of those interrupts occurs, the _same_ function (the IRQ handler for NVIC_EXTI_9_5) gets called. When that happens, your IRQ handler has to figure out which interrupt(s) it needs to handle (usually by looking at bitfields in some sort of status register), do the right thing to handle them, and then sometimes perform cleanup actions after finishing (e.g. external interrupts need to clear pending masks, or the interrupts will fire over and over again). So now it should make sense why libmaple's foo_attach_interrupt() handlers are convenient: they let you pretend that each interrupt has its own IRQ line, even though that's often not true. They also take care of set-up and clean-up tasks for you. This means a performance hit, but the convenience is usually worth it. 3. Where libmaple keeps its IRQ Handlers ---------------------------------------- As noted above, for each nvic_irq_num, there's an IRQ line, and for each IRQ line, you can set up a single function to call. This section explains where libmaple keeps these functions and what they're called. You typically will only need the information in this section if there's no foo_attach_interrupt() routine for the kind of interrupt you're interested in. The discussion is at the hardware level, and assumes you know how the NVIC works. You can try looking in the (freely available) Cortex M3 Technical Reference Manual for the details, but Joseph Yiu's book, "The Definitive Guide to the Cortex M3" is a much more beginner-friendly resource, and covers everything you need to know. 3.1: The vector table files (vector_table.S) ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ While they don't contain interrupt handlers themselves, vector table files are where to look for what they're named. You can find the names libmaple expects for IRQ handlers by looking in the vector table file for the microcontroller you're interested in. This file is always named vector_table.S, but there are multiple such files throughout the libmaple source tree. This is because the different STM32 series and even lines and densities within a series (like the value and performance lines and low/medium/high/XL-densities for STM32F1) each have different sets of IRQs. For portability, then, the vector table files must live somewhere where nonportable code goes, namely, under libmaple/stm32f1/, libmaple/stm32f2/, etc. as appropriate. The libmaple build system knows which one to use for each board. For example, the vector table file for the microcontroller on the Maple (STM32F103RB, a medium-density performance line F1 -- whew!) is libmaple/stm32f1/performance/vector_table.S. Here's a snippet: .globl __stm32_vector_table .type __stm32_vector_table, %object __stm32_vector_table: /* CM3 core interrupts */ .long __msp_init .long __exc_reset .long __exc_nmi .long __exc_hardfault .long __exc_memmanage .long __exc_busfault .long __exc_usagefault [...] .long __irq_exti0 .long __irq_exti1 .long __irq_exti2 .long __irq_exti3 .long __irq_exti4 The names of the interrupt handlers appear one per line, after the .long. The names are chosen to make it pretty obvious what IRQ line is associated with the function. Additionally, since this is the actual vector table for the chip, the names appear in NVIC order, so you can check the interrupts and events chapter in the chip reference manual to make sure which IRQ line a function is associated with. 3.2: Interrupts handled by libmaple ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ The vector table file is just an assembly stub which defines the actual vector table (i.e., the initial stack pointer and table of function pointers that go at address 0x0), but it doesn't define the interrupts themselves. It leaves that up to the rest of libmaple. Though it doesn't handle them all, libmaple does provide many interrupt handlers when it can provide some useful default behavior. For example, it defines USART interrupt handlers that store received bytes in a ring buffer. It defines EXTI interrupt handlers that figure out which external interrupt actually fired, and call the corresponding user interrupt handler (which was set either with attachInterrupt() or exti_attach_interrupt()). When there is a default IRQ handler, it lives in a .c file for the peripheral the interrupt is related to. Again, usually for reasons of portability, these usually live somewhere series-specific. For instance, the USART IRQ handlers for Maple live in libmaple/stm32f1/usart.c. More rarely, they'll be in some top-level file under libmaple/ if the same interrupt is available on all supported series (e.g. at time of writing, the EXTI interrupts in libmaple/exti.c). Use the vector table file and grep to find IRQ handlers for the MCU you're interested in. 3.3: Interrupts not handled by libmaple (isrs.S) ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Though libmaple does provide some IRQ handlers, it doesn't define one for every available interrupt. This is true for various reasons: maybe the peripheral or interrupt isn't supported yet, maybe there's no useful default behavior, etc. In this case, it would be wasteful to have a separate function for each unhandled interrupt. To handle this, there's a single file that deals with all unhandled interrupts. Its name is isrs.S, and it lives in the same directory as the corresponding vector_table.S. For example, for Maple, the file is libmaple/stm32f1/performance/isrs.S. These aren't complicated; read the source to see how they work. 4. Adding your own interrupt handlers ------------------------------------- When adding an interrupt handler (or overriding a default one), you need to decide whether you want it for a particular program, or if what you're writing is general-purpose enough that it should live in libmaple itself. 4.1 Adding a special-purpose interrupt handler ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ If you're just writing a one-off IRQ handler for your own use, your job isn't too complicated, provided you know the peripheral you're interested in well enough. You need to: 1. Define an IRQ handler with the right name 2. Turn on the IRQ line with nvic_irq_enable() 3. Set any relevant interrupt enable bits in peripheral registers You first need to define a function with the right name. Look up the name in the vector table file for your board (see above). For example, to define your own SDIO interrupt handler for Maple, define a function named __irq_sdio(): void __irq_sdio(void) { // Your handler goes here. } The libmaple linker scripts are smart enough to notice that you've done this and put a pointer to this function in the appropriate place in the vector table. IMPORTANT: the function you define MUST HAVE C LINKAGE. C++ name mangling will confuse the linker, and it won't find your function. So if you're writing your IRQ handler in a C++ file, you need to define it like this: extern "C" void __irq_sdio(void) { // etc. } To enable the interrupt, you need to call nvic_irq_enable() with the nvic_irq_num you want to enable. For SDIO, that looks like this: nvic_irq_enable(NVIC_SDIO); This line typically goes in your setup code. Check the docs for to find the nvic_irq_num you need. Beyond that, you also sometimes need to set some interrupt enable bits in a register associated with the peripheral. These bits vary by peripheral; consult the reference manual for your chip for the details. For example, SDIO interupts are enabled using bits in the SDIO_MASK register. 4.2 Adding a general-purpose interrupt handler ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Take this route only when you're sure your handler will be generally useful enough to ship with every copy of libmaple. Since the vector table is always present, your interrupt handler will consume every user's Flash. Normally, this is only worth it when defining some sort of foo_attach_interrupt() routine for a commonly used interrupt, though there are exceptions (e.g. the USART handlers). To add an interrupt handler, you need to define interrupt handlers with the appropriate names as described in the previous section. These will live under the series directory for the microcontroller you're using. For example, for Maple, they'd live under libmaple/stm32f1. DO NOT PUT THEM IN THE TOP-LEVEL LIBMAPLE DIRECTORY UNLESS THE INTERRUPT IS AVAILABLE ON ALL SUPPORTED SERIES, AND YOU CAN TEST IT ON MCUs FROM DIFFERENT SERIES. Just because an IRQ is available and purports to work the same way on multiple STM32 series doesn't mean that it in fact does. For example, there are silicon bugs related to I2C interrupt handling on STM32F1 that require special-purpose workarounds. When in doubt, leave your handler in the series directory you can test. It can always be moved later. After you've added the handler, you need to add IRQ enable and disable routines for the peripheral. At the very least, this needs to take a pointer to the peripheral's device and an argument specifying which IRQ or IRQs to enable. For example, here are some timer IRQ enable/disable routines present in : /** * @brief Enable a timer interrupt. * @param dev Timer device. * @param interrupt Interrupt number to enable; this may be any * timer_interrupt_id value appropriate for the timer. * @see timer_interrupt_id * @see timer_channel */ void timer_enable_irq(timer_dev *dev, uint8 interrupt); /** * @brief Disable a timer interrupt. * @param dev Timer device. * @param interrupt Interrupt number to disable; this may be any * timer_interrupt_id value appropriate for the timer. * @see timer_interrupt_id * @see timer_channel */ void timer_disable_irq(timer_dev *dev, uint8 interrupt); It's OK to take a flags argument for enabling/disabling multiple IRQs at once. If you're adding a foo_attach_interrupt(), it needs to work similarly, except it will also take a pointer to the user function to call when the interrupt occurs. When called, it must enable the correct NVIC line (which is usually available via the device pointer), as well as set any interrupt-enable bits in the appropriate peripheral register necessary to turn the interrupt on. Here's a timer example: /** * @brief Attach a timer interrupt. * @param dev Timer device * @param interrupt Interrupt number to attach to; this may be any * timer_interrupt_id or timer_channel value appropriate * for the timer. * @param handler Handler to attach to the given interrupt. * @see timer_interrupt_id * @see timer_channel */ void timer_attach_interrupt(timer_dev *dev, uint8 interrupt, voidFuncPtr handler) { dev->handlers[interrupt] = handler; timer_enable_irq(dev, interrupt); enable_irq(dev, interrupt); } You also need a corresponding foo_detach_interrupt() routine. In the case of IRQs for which a foo_attach_interrupt() routine is available, the IRQ handler needs to do any register inspection necessary to ensure the user handler is called only when the corresponding interrupt has occurred (for example, don't call timer capture/compare interrupt handlers due to an update event). How this works will depend on the peripheral. The IRQ handler must also perform any cleanup actions that are necessary. For example, various interrupts will cause the IRQ to fire until you clear some bits in a peripheral register. Users get confused and annoyed when their handlers get called forever. Clean up after them, so they don't need to worry about the details.