Managing hardware diversity in EM
We've already used the term EM distro when referring to bundles like ti.cc23xx – a collection of build-time as well as run-time software required to support a specific MCU. As emphasized in Introducing EM, distros will work hand-in-hand with EM's generic build-time and run-time bundles [em.build
, em.core
] when targeting a particular hardware platform.
Unlike Using EM – which focused on portable em.core
programs – this document tackles the more daunting task of porting the EM runtime to a new hardware platform. Much of this effort will involve MCU-specific implementations of abstract interfaces you've already encountered – such as ConsoleUartI
, GpioI
, and WakeupTimerI
found in the em.hal package.
Said another way, you'll supply concrete modules which implement client-visible functions like GpioI.toggle
and WakeupTimer.enable
by ultimately reading / writing low-level hardware registers in the target MCU – in other words, by using non-portable code. But as you'll learn, many of the high(er)-level programming constructs available through the EM language can in fact simplify the task of managing this sort of low(er)-level target software.
The configuration phase of the EM program life-cycle, as you'll also learn, plays a pivotal role in managing a wide range of hardware platforms – not only by generating low-level target code in a parameterized fashion, but also by programmtically controlling the compiler and loader tool suite(s) selected for the target MCU.
Looking ahead, we'll first build on the material covered in Introducing EM and explore the organization of an EM distro in greater detail. We'll then lay out a common pattern and process for developing an EM disto which targets an arbitrary MCU – incrementally verifying our progress by using the portable example programs covered in Using EM.
Organization of an EM distro
While we'll continue to use ti.cc23xx as a concrete example when we dive into the details of implementing an EM distro, we'll take a more abstract perspective for now – describing a generic distro bundle named xx.yyyz
. Initially, let's just focus on the overall organization of this bundle – typical of any of the distros we'll visit later on:
Like all EM bundles, our generic MCU distro must have a globally-unique name – typically of the form xx.yyyz
, where xx
designates the MCU vendor [ti
, mchp
, sifive
] and yyyz
identifies the MCU family [cc23xx
, samd21
, fe310
].
The root of each distro bundle contains two .properties
files which play distinct roles in the overall EM build flow:
bundle.properties
— its presence marks a (top-level) workspace folder as an EM bundle; its contents name other bundles required when building elements found in this particular bundle
setup-default.properties
— a slice of its name appears in the EM Setups menu; its contents define critical parameters required to build / load (portable) programs which depend upon this distro
You may recall from Installing EM that a distro can support multiple Setups – different compilers, different memory configurations, and so on. In general, our xx.yyyz
distro bundle can contain multiple setup-setup_name.properties
files – discovered by EM Builder in your workspace and displayed as xx.yyyz://setup_name
.
Turning to the set of EM packages rooted in our distro bundle, xx.build.yyyz
will in general contain one or more EM host
modules named «Abc»Builder
, where «Abc»
designates a compiler suite [Gcc
, Iar
, Segger
, ... ] used to build EM programs targeting xx.yyyz
MCUs.
Leveraging re-usable content found in the em.build
package, each «Abc»Builder
module not only coordinates use of an underlying C/C++ compiler when building EM programs, but also implements a strategy for loading program images onto the MCU itself. The std.h
and startup.c
files conventionally found in our xx.build.yyyz
package support this process.
The xx.distro.yyyz
package provides an entry-point for (portable) applications targeting xx.yyyz
MCUs. Using the em$distro
language intrinsic, clients can import EM composites such as BoardC
and McuC
without hard-coding the name xx.distro.yyyz
within their source files; setup-default.properties
usually assumes responsibility for binding em$distro
.
For now, you should review this material on target-specific content to understand the role played by the BoardC
and McuC
composites alongside the special host
BoardInfo
module found in the xx.distro.yyyz
package; and review the opening paragraphs of Using EM for insight into the special em-boards
file.(1)
- We'll deep-dive into each of these files later on, once we start implementing specific distros based on the
xx.yyyz
pattern
Finally, the xx.mcu.yyyz
package will contain the MCU-specific implementations of abstract em.hal
interfaces [GpioI
, WakeupTimerI
, ... ] described earlier; but the exact number and even the names of these xx.mcu.yyyz
modules (not to mention their low-level contents) can vary widely from one EM distro to the next – as we'll discover in due course.
Prerequistics for porting EM
Based on prior experience, carefully assess each of the following requirements before implementing your own EM distro for the xx.yyyz
family of MCUs:
Knowledge of EM. We assume a working knowledge of EM – starting with the 10,000' overview found in Introducing EM, and then moving to the ground in Installing EM and Using EM.
Usage of EM. As a pragmatic point of reference, you should already have used a target board supported by an existing em$distro
delivered with the EM SDK.
MCU experience. You should have prior experience in building and loading legacy C programs using a development board for the xx.yyyz
MCU family.
Bare-metal code. While MCU vendor xx
might supply a rich set of libraries for their yyyz
device family, you should locate "bare-metal" C code that directly touches MCU registers.
Hardware documentation. You should have access to the datasheets and manuals for the xx.yyyz
MCU family, as well as schematics for its development board(s).
Saleae logic analyzer. Invaluable for timing analysis as well as program tracing, logic captures will often serve as "program output" when working with low-level, bare-metal code.
And finally, you should come to "expect the unexpected" when porting EM to the xx.yyyz
MCU family; while we've tried to normalize and standardize the porting process, every MCU family seems to have its own "special quirks" which only makes the journey that much more interesting. As a wise man(1)once said:
- Doug Gwyn, known for his contributions to the C programming language and a colleague of Dennis Richie at Bell Labs
There is no such thing as portable code, only code that's been ported !!!
Incremental development plan
To mitigate the challenges of implementing a new EM distro for the xx.yyyz
MCU family, we've distilled our prior experience in porting EM to a wide range of resource-constrained MCUs down to a six-step process – a development plan you can tackle incrementally while regularly validating your progress.
Enroute to the final form, we'll create a curated series of approximations to the xx.yyyz
distro bundle – each supporting an ever-growing list of portable em.core
examples first described in Using EM and found in the em.examples.basic package.
xx.yyyz.A
— a minimal distro which supports building / loading the EmptyP example
xx.yyyz.B
— a distro which also supports the functionality covered in Tours 00 - 03
xx.yyyz.C
— a distro which also supports the functionality covered in Tours 04 - 09
xx.yyyz.D
— a distro which also supports the functionality covered in Tours 10 - 12
At a finer level of granularity, we'll implement a curated series of small EM test programs named Gist00_Min
, Gist01_Busy
, and so forth. Found in a special gist.yyyz
package within the working distro bundle, each of these "throwaway" programs focuses on a particular capability needed by the next distro bundle enroute to xx.yyyz
:
In step for example, we'll only implement Gist00_Min
to demonstrate minimal behavior in bundle xx.yyyz.A
; in step however, we'll implement additional gist programs in preparation for bundle xx.yyyz.B
.
As required by many of the gist programs, we'll also add "temporary" modules to the special scratch.yyyz
package depicted above. Used as a staging ground in step , the contents of these modules will find their way into the xx.mcu.yyyz
package of the xx.yyyz.B
bundle during step ; we'll also expand the BoardC
and Mcu
composites as needed.
Returning to the original figure, we'll pick up the pace in steps and as we transition from distro xx.yyyz.B
to distro xx.yyyz.D
. Now able to run all of the portable programs in the em.examples.basic
package – our "80% done" milestone – step pivots to size, speed, and power optimizations inside the final xx.yyyz
distro – the "other 80%" of the job.
Summarizing our six step process:
The bare minimum | distro xx.yyyz.A |
build / load a target program |
Basic MCU Output | distro xx.yyyz.A |
toggle a pin, output a character |
Portable examples | distro xx.yyyz.B |
Blinker*P ,FiberP ,HelloP |
Timers and buttons | distro xx.yyyz.C |
Button*P ,OneShot*P ,PollerP |
Scheduled wakeups | distro xx.yyyz.D |
Alarm*P ,TickerP |
Optimizing EM•Mark | distro xx.yyyz [v1.0] |
{Active,Sleepy}RunnerP |
Preparing your workspace
As we proceed through this process, you'll encounter "real-world" instances of our generic xx.yyyz
bundle – using the ti.cc23xx
distro and the LP-EM-CC2340R5
evaluation board described in Installing EM as our primary example.(1)
- At the same time, we'll also present examples for other
EM distros to further reinforcexx.yyyz
design patterns.
Assuming you've already made your way through Using EM – and have also run the portable em.examples.basic
programs on LP-EM-CC2340R5
hardware – let's launch EM Builder with a fresh workspace and take our first step in the porting process.