Skip to content

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:

Image info

EM distro bundle

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)

  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:

  1. 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.

Image info

Porting Milestones

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:

Image info

Porting Milestones [ details ]

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 pre­paration 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)

  1. At the same time, we'll also present examples for other
    EM distros to further reinforce xx.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.