Programming with EM
Turning now to the EM runtime, a series of curated "guided tours" will incrementally introduce components of the em.core bundle – many of which you may have browsed in your reading of Introducing EM. By convention, each tour revolves around a specific EM program found in the em.examples.basic package – with each of these small programming examples motivating us to visit key elements of the EM runtime.
In the material that follows, we'll reference a number of logical MCU "pins" configured by the current EM distro and generally accessible through headers on your target board:
appBut |
application button pin |
appLed |
application LED pin (usually green) |
appOut |
application console TX pin |
sysDbgA |
system debug pin A |
sysDbgB |
system debug pin B |
sysDbgC |
system debug pin C |
sysDbgD |
system debug pin D |
sysLed |
system LED pin (usually red) |
A special YAML file named em-boards
found the em$distro
package folder binds these logical pin names to physical pin numbers of your target board. Porting EM will have more to say about em-boards
, as well as explain the special setup-*.properties
files located in the root of your distro bundle.
Tour 00 – guided tours
The following animation illustrates the EM - Start Tour command, which you'll use to view each of the tours described in the remainder of this document. You'll find these tours in appropriately named .emtour
files, located inside the .tours/101_Using_EM
folder delivered with the em.docs
bundle.
With that, go ahead and actually take tour 00_HelloP.emtour
within your VS Code environment. Before proceeding to take Tour 01 – Tour 12 , however, you should feel comfortable with the UI presented in Tour 00 ; and do retake any of these tours whenever necessary.
Tour 01 – basic blinker
This tour centers around the BlinkerP program, which toggles the appLed
pin on your target board – typically connected to a green LED. This tour also visits the BusyWaitI and LedI interfaces, as well as presents the Common module and its constituent proxies.
The EM runtime quickly blinks your board's sysLed
at startup.
The BlinkerP
program toggles appLed
every ~0.5 s.
The EM runtime turns on sysLed
upon normal program termination.
The next capture zooms into the appOut
signal.
The EM runtime outputs this sequence of (non-printable) bytes at startup, after blinking sysLed
.
The EM runtime output this single byte upon normal program termination, before turning on sysLed
.
Tour 02 – real-time debug
This tour focuses upon the BlinkerDbgP program, which also toggles the appLed
pin on your target board. This tour highlights a number of capabilities within EM for visualizing and troubleshooting program execution in real-time.
The busy-wait bracketed by %%[d+]
and %%[d-]
measures ~498 ms.
Executing a fail
statement causes sysLed
to blink rapidly.
The %%[>cnt]
statement outputs the (non-printable) 0x82
control byte followed by 2 bytes of payload.
The %%[>bits11]
statement outputs the (non-printable) 0x81
control byte followed by 1 byte of payload.
The %%[c:bits11]
statement causes the dbgC
pin to rapidly toggle 2 times – since (bits11 == 0x1)
The ASCII character output of printf
begins here – 0x63('c')
, 0x6E('n')
, and so on.
Tour 03 – threading with fibers
This tour centers around the FiberP program, which introduces a very lightweight threading construct termed a fiber. This tour also visits the EM runtime's (portable) implementation of fibers found in the FiberMgr module.
The %%[d]
statement in blinkFB
marks each point in time where the blinkF
fiber begins execution.
The ~200 ms between blinkF
cycles includes appLed
on / off time plus FiberMgr
dispatch overhead.
Tour 04 – button handlers
This tour centers around the Button1P program, which handles incoming events generated when pressing appBut
on your board. This tour also visits the GpioEdgeDetectMinI interface that in turn extends the GpioI abstraction.
The EM runtime uses dbgB
to mirror the MCU's execution mode [ L – actively running, H – awaiting wakeup ]
Pressing your board's button drives the appBut
pin low; appLed
then blinks shortly thereafter.
A 125 ns "glitch" on the appBut
signal fired another event at this time – causing an extra appLed
blink.
You've pressed appBut
at this time, which then begins awakening the MCU from its low-power sleep.
Requiring 34.2 μs to fully power the MCU, dbgB
's falling edge marks when Button1P
starts actively running.
Control enters handler
within 3.4 μs, whose initial %%[c]
statement toggles dbgC
for 1.2 μs
After some housekeeping, Button1P
finally turns-on appLed
– 5.9 μs since actively running.
Tour 05 – button fibers
This tour centers around the Button2P program, which uses a Fiber
object to better handle incoming events. This tour also analyzes the performance impact of this approach compared with the earlier Button1P program.
dbgB
shows that Button2P
awoke exactly twice in response to pressing appBut
– no glitches this time !!!
Button2P
resumes execution in 35.2 μs; keep in mind that low-power MCU wakeup times can vary slightly.
Control then enters handler
3.4 μs later – consistent with the Button1P
capture seen above.
Unlike Button1P
, the Button2P
handler
readies the blinkF
fiber which then gains control 5.3 μs later.
Finally, the blinkFB
function turns-on appLed
– with only ~5 μs of FiberMgr
scheduling overhead.
Tour 06 – button objects
This tour centers around the Button3P program, which debounces button input signals in a portable fashion. This tour also visits the ButtonI interface along with a module implementing this interface – the latter generated by the ButtonT template at build-time.
Once triggered by pressing your board's button, the EM runtime polls the state of appBut
every 100 ms.
If pressed for ≥ 100 ms but ≤ 4 s, Button3P
will blink appLed
within 100 ms of releasing the button.
Hitting the 4 s mark, Button3P
immediately blinks sysLed
– regardless of how long appBut
remains low.
After polling appBut
for 4 s, the EM runtime invokes Button3P.onPressedCB
which leaves a %%[c]
mark.
Discovering that appBut
remains pressed [ = L ], onPressedCB
then blinks sysLed
for 40 ms.
Only 7.5 μs elapses from dbgB
falling to sysLed
rising, which includes scheduling fibers plus 1.2 μs for %%[c]
.
Tour 07 – timer handlers
This tour centers around the OneShot1P program, which handles timer events that awaken the MCU. This tour also visits the OneShotMilliI interface, which abstracts the functionality of a short-term timer with millisecond resolution.
The dbgB
signal shows that OneShot1P
awakens from low-power sleep every 500 ms.
Once awake, OneShot1P
toggles dbgC
and dbgD
before blinking appLed
– like our earlier Button1P
capture
The 7.5 μs "wakeup-to-blink" latency seen here includes the overhead of servicing an on-chip MCU timer.
Tour 08 – timer fibers
This tour centers around the OneShot2P program, which now uses Fiber
objects to enhance robustness when handing timer events. This tour also invites performance comparision with the earlier OneShot1P program.
Fiber scheduling adds the extra 1.1 μs of latency seen here, compared with our previous OneShot1P
capture.
Tour 09 – timer service
This tour centers around the PollerP program, which introduces a portable function for pausing execution at run-time. This tour also visits the top-level Poller module as well as the lower-level PollerAux module – both implementing the PollerI interface.
Without %%[c]
and %%[d]
marks, PollerP
further reduces latency compared to OneShot1P
and OneShot2P
.
The [L] dbgB
signal indicates that the PollerP
program has awoken.
PollerP
then calls AppLed.wink
within 2.5 μs, which then asserts the appLed
pin
Calling AppLed.wink
2.5 μs later asserts the appLed
pin and then internally invokes Poller.pause
.
Poller.pause
proceeds to suspend program execution for 5 ms – the duration of the wink.
The EM runtime toggles dbgB
once before awaiting the next wakeup [H] ; we'll explain more in a later tour.
An MCU timer event will eventually awaken the program 5 ms later, with dbgB
now signalling [L] .
Control unwinds from Poller.pause
back to AppLed.wink
, which will now lower the appLed
pin.
em$run
finally regains control, and then suspends execution for 500 ms by calling Poller.pause
directly.
Tour 10 – wakeup alarms
This tour centers around the Alarm1P program, which uses Alarm
objects to schedule longer-term wakeups with (sub-)second resolution. This tour also visits the AlarmMgr module, which internally uses a proxy implementing the WakeupTimerI interface.
The Alarm1P
program alternately awakens from a low-power deep-sleep of 2 s or else 750 ms in duration.
The program's blinkFB
function winks appLed
for 100 ms, with the MCU idling in the interim.
Once AppLed.wink
returns, Alarm1P
calls alarm.wakeup
to re-enter an extended period of deep-sleep.
The single dbgB
mark indicates the MCU has entered its "lite-sleep" mode, after Alarm1P
calls AppLed.wink
The MCU awakens from its lite-sleep within 100 ms, returning to wink
which then turns-off appLed
.
The double dbgB
mark indicates the MCU has entered its "deep-sleep" mode, after calling alarm.wakeup
Tour 11 – aligned wakeups
This tour centers around the Alarm2P program, which now aligns Alarm
wakeups with a given time-window. This tour also re-visits the AlarmMgr implementation, seeing how it schedules the next wakeup event as well as the role played by the EpochTime module.
Note how the wakeups from deep-sleep align with a series of 1.5 s windows along the time-line.
Due to startup overhead, the first alarm.wakeup
call deep-sleeps the MCU for only 1.250 s to stay aligned.
After a 5 ms appLed
wink, this alarm.wakeup
call deep-sleeps the MCU for 1.495 s to stay aligned.
But after a 100 ms wink, this alarm.wakeup
call deep-sleeps the MCU for just 1.400 s to stay aligned.
Tour 12 – cyclic tickers
This tour centers around the TickerP program, which takes duty-cycled functions to a higher level. This tour also visits the TickerMgr module, whose implementation of Ticker
objects emulates as well as builds upon the AlarmMgr and FiberMgr studied earlier.
The appTicker.start
call by the TickerP
program initiates a train of 100 ms appLed
winks, spaced 1 s apart.
The sysTicker.start
call by TickerP
then initiates a train of 100 ms sydLed
winks, but spaced 1.5 s apart.
When appTicker
and sysTicker
wakeups coincide, their callbacks run on a first-come, first-served basis.