Posts Tagged ‘real-time’
An Interrupt Service Routine (ISR) executes in reaction to an asynchronous hardware request, interrupting the ongoing computation in the CPU.
As an example, in an Arduino, whenever the USART subsystem receives a byte from the serial line, the CPU execution is redirected to the “USART_RX interrupt vector”, which is a predefined memory address containing the ISR to handle the byte received.
Only after the ISR returns that the interrupted computation resumes.
ISRs are often associated with a high-priority functionality that cannot wait long.
Complementing the USART example, if the execution of the ISR is too much delayed, some received bytes can be lost.
Likewise, the execution of an ISR should never take long, because other interrupts will not trigger in the meantime (although it is possible to nest ISRs).
For this reason, a typical USART ISR simply stores received bytes in a buffer so that the program can handle them afterwards.
ISRs in Céu:
Céu has primitive support for ISRs, which are declared similarly to functions.
However, instead of a name identifier, an ISR declaration requires a number that refers to the index in the interrupt vector for the specific platform.
When an interrupt occurs, not only the ISR executes, but Céu also enqueues the predefined event OS_INTERRUPT passing the ISR index.
This mechanism allows the time-critical operation associated with the interruption to be handled in the ISR, but encourage non-critical operations to be postponed and respect the event queue, which might already be holding events that occurred before the interruption.
The code snippets that follow is part of an USART driver for the Arduino.
The driver emits a READ output event to signal a received byte to other applications (i.e. they are awaiting READ).
The ISR just hold incoming bytes in a queue, while the main body is responsible for signaling each byte to all applications (in a lower priority).
/* variables to manage the buffer */ var byte[SZ] rxs; // buffer to hold received bytes var u8 rx_get; // position to get the oldest byte var u8 rx_put; // position to put the newest byte atomic do rx_get = 0; // initialize get/put rx_put = 0; // the `atomic´ block disables interrupts end /* ISR for receiving byte (index "20" in the manual) */ function isr do var u8 put = (rx_put + 1) % SZ; // next position var byte c = _UDR0; // receive the byte if put != rx_get then // check buffer space rxs[rx_put] = c; // save the received byte rx_put = put; // update to the next position end end /* DRIVER body: receive bytes in a loop */ output byte READ; // the driver outputs received bytes to applications loop do var int idx = await OS_INTERRUPT until idx==20; // USART0_RX_vect var byte c; // hold the received byte ... atomic do // protect the buffer manipulation new interrupts c = rxs[rx_get]; // get the next byte rx_get = (rx_get + 1) % SZ; // update to the next position end emit READ => c; // signal other applications ... end
Note how the real-time/high-priority code to store received bytes in the buffer runs in the ISR, while the code that processes the buffer and signal other applications runs in the body of the driver after every occurrence of OS_INTERRUPT.
Given that ISRs share data with and abruptly interrupt the normal execution body, some sort of synchronization between them is necessary.
As a matter of fact, Céu tracks all variables that ISRs access and enforces all other accesses (outside them, in the normal execution body) to be protected with
Céu provides primitive support for handling interrupt requests:
- An ISR is declared similarly to a function, but specifies the interrupt vector index associated with it.
- An ISR should only execute hard real-time operations, leaving lower priority operations to be handled in reaction to the associated OS_INTERRUPT event.
- The static analysis enforces the use of
atomicblocks for memory shared between ISRs and the normal execution body.
Esterel was developed in the early ’80s in France in parallel with other two languages, Lustre and Signal. These, together with Statecharts, are considered the first synchronous languages.
Esterel found its niche in control intensive real-time applications and evolves as a standard, with several implementations.
Unlike all synchronous languages, Esterel is the only one to provide an imperative style of programming. This is kind of surprising for me considering the high popularity of imperative languages.
Why there’s no attempts to create new languages based on the Esterel foundations?
I cannot argue about functional vs imperative style of programming, but I do feel more comfortable with the latter (most people do), and they will be around for the next years.
I like the Lua approach, which seems to favor imperative style but concisely support most functional features without bloat.
The example below, written in Esterel, implements the basic training for an athlete:
module Runner: input Second, Morning, Step, Meter, Lap; output ...; % not used every Morning do abort loop abort RunSlowly when 15 Second; abort every Step do Jump || Breathe end every when 100 Meter; FullSpeed each Lap when 2 Lap end every end module
Esterel authors argue that, following this imperative style, programs are almost software specifications given as recipes in natural English.
The communication in Esterel is made through broadcast input and output signals.
The synchronous preemption constructs (every do, loop each, abort when, etc) are the heart of the language.
LuaGravity provides constructs based on them, which I consider even more useful than the functional facilities.