Interrupts

Interrupts

Interrupts can be very useful: as the name says, they signal when something has happened while you’re busy doing something else. In embedded systems they can tell you when a a timer expires, when a pin level changes, and when an I/O operation completes. Which happens to be exactly how JeeH uses them.

All interrupts are handled by “tasks”, which act as the recipient of events fired by the IRQ handlers: to get the ball rolling, you have to define a task and initialise it, and then you can send it requests. When the moment arrives, that task will then send one or more reply events back to the original sender (also a task).

SysTick #

The System Tick hardware is built into all ARM Cortex µCs as a simple way to generate periodic interrupts based on the system clock. JeeH’s “Ticker” driver uses this hardware to generate one-shot and periodic events as needed. All timer requests are saved in an internal queue, and will fire off reply events as requested. Timers can be requested to go off 1 to 60,000 milliseconds from now. Longer timeouts could be created by a sequence of timers (or counting events from a periodic timer), but are probably better done via the Real Time Clock (RTC).

Timers can only be used from a task, since they rely on events. Here’s how:

#include <jee/ticker.h>
Ticker ticker;
TICKER_TRIGGER(ticker) // note: no semicolon
  • In main, early on, call: ticker.init();
  • One-shot timer, reply with event 123 in 10s: ticker.delay(10'000, 123);
  • Periodic timer, returns event 456 once every 1s: ticker.periodic(1'000, 456);
  • Cancel all event 789 timers for this task: ticker.cancel(789);

Timers will not advance or trigger while the system is in stop, standby, or shutdown mode. The number of active timers is limited to 20 (MAX_TIMERS in jee/ticker.h).

ExtIrq #

Another source of IRQs is “pin change interrupts”. These are handled by the External Interrupt (EXTI) hardware, also present in all STM32 µCs. When triggered, a reply event will be sent with the specified tag. As with timers, and for the same reason, this can only be used from a task. Here’s how:

#include <jee/exti.h>
ExtIrq exti;
EXTIRQ_TRIGGER(exti) // note: no semicolon
  • In main, early on, call: exti.init();
  • Trigger on PA1’s rising edge: exti.enable(Pin ("A1"), ExtIrq::RISE, 123);
  • Trigger on PB2’s falling edge: exti.enable(Pin ("B2"), ExtIrq::FALL, 456);
  • Trigger on both of PC3’s edges: exti.enable(Pin ("C3"), ExtIrq::BOTH, 789);
  • Stop triggering on any PD4 changes: exti.disable(Pin ("D4"));

A limitation of pin-change interrupts is that there are only 16 of them and each one can only be active on a single port at a time, e.g. “PA5” or “PB5” or “PC5”, etc.

UART RX #

A third source of interrupts is when data is received by a UART. Unlike I2C or SPI in “master” mode, UART reception is unpredictable (asynchronous). To deal with this in JeeH, you set up an RX event. Here’s how:

#include <jee/uart.h>
#include "defs.h"
uart::Async<UART_CONF> tty;
UART_TRIGGER(tty) // note: no semicolon
  • In main, early on, call: tty.init(baudrate);
  • To start triggering event 123 when data is available: tty.onAvail(123);
  • To stop triggering on such RX events: tty.onAvail();
  • To read up to num bytes into a suitably-sized buffer: tty.read(buf, num);

While the RX event is active, the driver keeps its receive DMA channel enabled in “continuous circular mode”. This lets reception take place in the background without CPU involvement, also in sleep mode. There is no handshake protocol: it is up to the app to read incoming data before the driver’s internal circular buffer overflows (its size is adjustable, the default is 64 bytes).

RX events will only be triggered in these two cases:

  1. The internal circular receive buffer is either half full or full.
  2. Some data has been received but now the line has gone idle.

The first case lets you read all data collected so far, while more might be arriving in the rest of the circular buffer. Once read, the space in the circular buffer is re-used by the driver for new incoming data. The second case lets you pick up all remaining data as soon as data reception ends, i.e. no data has come in for 10 bit times (as determined by the chosen baudrate). Both conditions are fully supported in hardware, the CPU only needs to wake up to generate the reply event.

The eVal field in the reply event indicates how much data can be read at once. You need not consume all data right away, but new RX events will only be generated once all bytes in the buffer have been read.