aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--notes/interrupts.txt348
1 files changed, 348 insertions, 0 deletions
diff --git a/notes/interrupts.txt b/notes/interrupts.txt
new file mode 100644
index 0000000..631e018
--- /dev/null
+++ b/notes/interrupts.txt
@@ -0,0 +1,348 @@
+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 tehre'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 M3s book to
+understand all the details; these are just the basics):
+
+- Each of the series of STM32 microcontroller 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 the 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 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 keep these functions, what they're called, and
+how you can write your own.
+
+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 (like STM32F1, STM32F2) 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 various vector_table.S must live somewhere
+where nonportable code goes: somewhere 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 stack table 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
+whether 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
+<libmaple/nvic.h> 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 an 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 <libmaple/timer.h>:
+
+ /**
+ * @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.