Restraining the complexity of embedded system design
[Copy link]
The Raspberry Pi family was recently expanded with the new Raspberry Pi Zero W1 (February 2017), a wireless personal computer that costs just $10. It’s a real treat for hobbyists, makers, tinkerers and hackers. Yes, few of us actually try to do our job well, i.e. design real (electronic) products! When I watched Eben Upton’s video announcement recently, I couldn’t help but think back to my early years. It was the mid-80s and I couldn’t afford a BBC computer, nor could I afford the extravagance of an Amiga. But I spent all my money on a Sinclair ZX Spectrum. So Eben’s goal of making it “affordable for all” really resonated with me. I was
really amazed by the record-breaking size of a powerful personal computer crammed onto a tiny printed circuit board (PCB), a 6 x 3 cm PCB to be exact. Upon further reflection, I often wondered if it was the simplicity of the Spectrum and its many limitations that led me to delve deeper into computers and eventually fall in love with this wonderful field - the boundary between software and hardware, which we call embedded today. The Raspberry Pi Zero design is based on a system-on-
chip
(SoC) (BCM2835) that includes a 1 GHz ARM? core and a graphics processing unit (GPU), a video interface, multiple serial interfaces (USB, UART, SPI, and I2C), and an external memory interface to manage the large amount of RAM (512 MB DDR2) and large storage card (SD card) required to run the Linux? operating system (OS). These are impressive features for a single-chip device, especially compared to the early personal computers I saw in my youth. We might argue that it is not disproportionate compared to the latest simple microcontrollers commonly used in various embedded control applications today. Although the clock speed and processing power are much lower (ranging from 10 MHz to 100 MHz), all of today's small microcontrollers are true small system-on-chip wonders in their own right. All RAM and flash memory are located on the chip, as you would expect from a microcontroller. Serial interfaces are present (USB, UART, SPI, and I2C), but all power regulation and voltage monitoring circuits are also integrated. There are usually five or more different (precision) oscillators on-chip, in order to allow greater flexibility and control power consumption. In addition, there are several analog peripherals (ADC, DAC, op amps and analog comparators...) with large input/output multiplexers, replacing the functions in the Raspberry Pi fantasy videos, reflecting a significant difference in design choices that always favor embedded over computing.
In fact, when Raspberry Pi users need to connect with the real world, for applications beyond the most peaceful I/O applications such as blinking a common LED, it is not surprising that a smaller microcontroller (usually 8-bit microcontrollers in fact) provides the necessary I/O interfaces and the required voltage conversions through a "hat" (a small daughterboard).
I don't want to drag on this unfair comparison between two very different worlds, but I must point out that both have a common concern in terms of supporting developers: "controlling complexity" and ultimately "attracting new users". Needless to say, their solutions are similar, but ultimately different.
Both platforms start by offering free software tools, including an integrated development environment (IDE), compilers, linkers, simulators, debuggers (available in the professional version for a small fee), more or less open middleware and (RT-)OS, and a small selection of hardware (board) options.
The differences between the two camps (embedded computing and general computing) are smaller than you might think. Both ultimately rely on similar (if not identical) toolchains, which are mostly based on GNU. At the middleware level, once you properly abstract the lower (down to the metal) driver layer, the open source options become extremely similar again. The biggest differences are at the OS level, as many microcontrollers will happily run an RTOS but cannot afford the burden of a full Linux kernel. This reflects real industry differences. Real-time is part of the OS's "job description".
Bloat
When you look at the documentation, you'll see that both are ballooning in complexity. One of my favorite examples is a small and simple microcontroller based on the popular 8-bit PIC? architecture. The PIC16F1619 is often used to control small appliances, and to that end it packs a small amount of flash memory (16 KB) into a tiny 20-pin package, with a dozen digital peripheral interfaces and nearly as many analog support modules. Its data sheet is 650 pages long, with feature data, tables, and Figure 2 added later.
Some of the peripherals available on this small SoC, such as the signal measurement timer, require up to 50 pages to be properly documented. This is almost twice the number of pages required to describe the actual PIC core and its entire instruction set.
On the Raspberry Pi side, if you scale it up (10 times), the problem is similar, as there are multiple data sheets to consider, each documenting only a portion of the system-on-chip hardware components (SoC peripherals, GPU, and core), with the core alone occupying more than 750 pages.
Embedded Software Architecture
It is obvious that no one can read or keep up with such a large amount of information. Especially embedded developers, who are always under great pressure to complete applications in a shorter time to achieve the fastest time to market. A common solution is to partition the application using a layered architecture and use standardized peripheral libraries to abstract the hardware details. These layers can be neatly formed into a protocol stack, where the "application" sits on top of the Hardware Abstraction Layer (HAL). In fact, this picture can be further refined to fully identify the HAL, and the middleware layers above the HAL will be responsible for implementing common services/functionality such as networking, file systems, and graphical UI (if present/needed).
Figure 1: Software stack for embedded applications
Note: It is common to further refine the stack by separating the driver layer and the board support layer from the HAL, but for the following considerations, we do not need to go to that level of detail.
This software architecture is directly in the "computing" domain and models most common use cases very well. Unfortunately, as it applies to embedded applications, it has two basic disadvantages:
· The layered architecture can simplify the problem of long documentation as long as the focus is on the standard functions provided by the top middleware layer. At the bottom end of the application spectrum, when the middleware layer (if it exists) is very thin, the result is mostly vague. The developer must rely on the HAL documentation in the form of a large application programming interface (API), which is also long (can reach thousands of pages), but never really explores any details of the device. When problems arise, he/she will be in trouble or forced to delve into unfamiliar territory and a lot of code.
· The HAL layer provides great help in supporting standard middleware services, but due to its extremely rigid nature, it eventually eliminates any unique and differentiating features of a specific device. Otherwise, these unique features can provide technical advantages for specific applications and may be the reason for choosing a specific device model.
At the top of the application spectrum, the middleware layer is so thick that, for example, the Raspberry Pi adds millions of lines of code to the Linux OS kernel alone to cope with issue 3. While this is arguably open source code, it provides little comfort to the average developer who hopes they will never have to dig into it to such an extent.
Let the computer do what it does!
Ultimately, the Raspberry Pi developer will be able to rely on the huge gains in "compute" performance and the vast resources provided by the small board. The convenience of a standard Linux OS more than makes up for the complexity and breadth of the API.
My biggest concern is for developers of brand new small SoCs: modern microcontroller users. For them, the benefits of using a standardized HAL are reduced because there is a performance penalty and the stacked software architecture makes unique functionality monolithic. A
new generation of software tools for rapid development represents an ingenious way out of this dilemma. This is a new class of code generators or configurators that have recently appeared in the embedded control market. Despite obvious (but often justified) initial skepticism, these tools have proven to be not only effective, but also indispensable for any serious embedded developer.
Notable features we found include:
- Full integration in common IDEs, which helps them understand the project context: model (part number) selection and middleware library awareness.
- Support for unique and complex peripherals. For example, the Signal Measurement Timer (SMT) mentioned in the previous example can be intuitively presented to the user in a single page/dialog box with only a few scrolling lists, check boxes, and some intuitive options. See Figure 2 for a screenshot of MPLAB? Code Configurator (MCC) 4, the flagship rapid development tool for PIC microcontrollers from Microchip.
Figure 2 - MPLAB Code Configurator: Signal Measurement Timer Option
- Using a template engine, configuration options are converted into a small set of fully customized functions. This means that a minimal API is generated with only a small number of functions to learn and consistent and intuitive naming conventions. Function customization ensures that most hardware abstractions are performed statically at compile time (actually before compilation). This helps reduce the list of parameters required to be passed to each function, thereby improving performance and code density. See Listing 1 for a typical minimalistic use case of the MPLAB Code Configurator.
- The output consists of very short (C language) source files that can be fully inspected by the user (which can be used as a learning opportunity), but are also further manually optimized by experts. Modern code generators flexibly mix their code with user code, both maintaining integrity and allowing full use of valuable advanced hardware features.
void SMT1_Initialize(void) {
// CPOL rising edge; EN enabled; SPOL high/rising edge enabled; SMT1PS 1:1 Prescaler; …
SMT1CON0 = 0x80;
// SMT1MODE Counter; SMT1GO disabled; MT1TMR1
update complete …
SMT1STAT = 0x00;
SMT1CLK = 0x00;
// SMT1CSEL FOSC;
SMT1WIN = 0x00; // SMT1WSEL SMTWINx;
SMT1SIG = 0x00; // SMT1SSEL SMTxSIG;
SMT1PRU = 0x00; // SMT1PR16 0x0;
SMT1PRH = 0x00; // SMT1PR8 0x0;
SMT1PRL = 0x00; // SMT1PR0 0x0;
}
void SMT1_DataAcquisitionEnable(void) {
SMT1CON1bits.SMT1GO = 1; // Start the SMT module by writing to SMTxGO bit
}
void SMT1_SetPeriod(uint32_t periodVal) {
// Write to the SMT1 Period registers
SMT1PRU = (periodVal >> 16);
SMT1PRH = (periodVal >> 8);
SMT1PRL = periodVal;
}
Listing 1 - Portion of the source file (smt1.c) generated by MCC to configure the SMT peripherals
Fundamentally, the code configurator/generator does the best job of what a "computer" would do. Building the HAL, a repetitive and error-prone phase of hardware peripheral configuration that often leads to hours of tedious searching in datasheets, is now gone or significantly shortened, leaving behind more fun and thought-provoking hours of exploration and creativity. The
fact that users can learn about specific hardware peripheral functions from the same user interface virtually eliminates (or at least greatly reduces) the need for datasheets.
The Hardware Abstraction Layer becomes a flexible part of the project that can actually be regenerated as often and quickly as needed to optimize application performance.
Ten (binary) lines of code
Once the (peripheral) configuration is taken care of, attention can immediately be focused on the application, which is the smarter part of the design (on the application layer), and which is inside the "main loop" rather than before.
Finally, with the code generator, even in the embedded world, the classic "Hello World" example (which was always converted to blinking an LED) becomes a refreshing two-line code exercise!
LED_Toggle();
__delay_ms(500);
Listing 2 - The short two lines of code you need to enter to create your first embedded "Hello World" You will be able to find (20) more examples of the same effective use of rapid development tools in my recent book: "In 10 Lines of Code"5.
Fighting Complexity
As small microcontrollers grow into small SoCs or PCs shrink into Raspberry Pis, not only does it waste time and create cognitive overhead, it also introduces vulnerabilities as we operate systems we do not fully understand/master.
Complexity is not an inevitable consequence of technological advancement. Modern code configurators/generators can help us by extending our software development process, automating and ultimately restoring our mastery of the rapidly growing number of available features/options.
|