151ad0d460
Signed-off-by: Marti Bolivar <mbolivar@leaflabs.com>
347 lines
15 KiB
Plaintext
347 lines
15 KiB
Plaintext
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
|
|
<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 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.
|