• About

Embedded Tales

  • Zephyr – “The Linux for MCUs” Part 2: Hardware

    March 14th, 2024

    A few weeks ago, we published the first article of this series, in which we laid a framework with a set of criteria to meet on the roads towards becoming the Linux for MCUs.

    This time, we’ll look into the first point on the list, which is the ability of being Harware Agnostic.

    Hardware Agnostic, what?

    Hardware Agnostic, in this context, means that software developed for a given hardware configuration may work for another one with minimal to no changes. To achieve this, the software relies on re-usable software foundation principles, which includes at a minimum:

    • Well-defined APIs defined at a high, abstracting the what from the how, and being valid across all hardware variants.
    • A well-defined hardware abstraction scheme, to treat hardware dependencies focalized and isolated from the remainder of the code.

    Use-Case: FocusIO

    To understand this in practical terms, let’s throw an example application: let’s call it, “FocusIO”.

    This application uses an accelerometer-based motion detection to evaluate your ability to remain focused for long periods of time.

    • The Accelerometer is phasing-out as end-of-life and the product needs updated.
      • How easy is to have the codebase work with Accelerometer-C?
      • Does it only require swapping the accelerometer driver?
    • The very same product now also needs a microcontroller change because of chip-shortage (Let’s say this was 2020…).
      • Does it only need to change the peripheral drivers to make it work?

    Hardware Abstraction in Zephyr

    As previously mentioned, Zephyr keeps a clear hardware abstraction by using the well-known Linux concept: The Device-Tree.

    Let’s take a look at an extract of the Thingy53 device-tree source (dts) board files:

    &i2c1 {
    compatible = "nordic,nrf-twim";
    status = "okay";
    clock-frequency = <I2C_BITRATE_FAST>;

    pinctrl-0 = <&i2c1_default>;
    pinctrl-1 = <&i2c1_sleep>;
    pinctrl-names = "default", "sleep";
    bmm150: bmm150@10 {
    compatible = "bosch,bmm150";
    reg = <0x10>;
    };

    bh1749: bh1749@38 {
    compatible = "rohm,bh1749";
    reg = <0x38>;
    int-gpios = <&gpio1 5 (GPIO_PULL_UP | GPIO_ACTIVE_LOW)>;
    };

    bme688: bme688@76 {
    compatible = "bosch,bme680";
    reg = <0x76>;
    };
    };

    In the Device-tree, we have a hierarchy outlining the board features. In this example:

    • i2c1 is the main node, defining the I2C bus.
    • The following fields are properties of the I2C bus (e.g: clock frequency, pins through pinctrl, etc).
    • The devices that belong to the bus are defined as child nodes, each one defining their properties (e.g: 7-bit I2C address, Interrupt GPIOs, etc). On this sample: BMM150, BH1749, BME688 are the I2C-attached devices to I2C1.

    At build time, the dts definition is translated into a set of applicable macro-defines as well as drivers that are selected based on the compatible property, node-label, etc. Then, the applicable drivers implement the public APIs (in this case, I2C and Sensor), which then the application uses to interface with the underlying parts of the system.

    Check out this Zephyr example of the LIS2DH sensor (3-Axis accelerometer), which works on any target that has this sensor defined on its device-tree (e.g: thingy52_nrf52832, actinius_icarus, stm32f3_disco_board, among others).

    MCU Support

    The Zephyr project supports a variety of Microcontroller boards, with different families (e.g: Cortex-M4, Risc-V) and vendors (STMicro, Nordic, NXP, Silabs, etc). To take a closer look at the level of support, we’ve chosen a set of boards with different vendors and families, and have tested different boards with various samples present in the SDK to determine which have out-of-the-box support:

    Board / OOTB SupportHello WorldBlinky (GPIO)Echo Bot (UART)Shell (I2C)
    STM32G0 Nucleo
    (nucleo_g0b1re)
    YYYY
    Nordic nRF52840 Dev-kit
    (nrf52840dk)
    YYYY
    ESP32 WROOM (esp32_devkitc_wroom)YNYY
    Raspberry Pi Pico
    (rpi_pico)
    YYYN
    Silabs EFR32xG21 WSTK board
    (efr32_radio)
    YNYN
    Table: Results of boards running samples using different peripherals.
    Figure: Snapshot of a sample running I2C scan. Device 0x77 (BME280) is in the bus.

    Some observations:

    • Building the boards is relatively consistent across targets: issue west build -b <board_target> <application>, which will build as long as the particular target has the hardware features required to run it.
    • Flashing each target may be consistent, with some gotcha’s:
      • If not debugging probes (an external J-Link or an on-board J-Link), “west flash” may not be supported. For instance, the RPi Pico comes with an UF2 bootloader and, to program the board, one needs to drag-and-drop the binary into the Mass Storage Device window.
      • If the code built is a multi-application (e.g: Including MCUBoot in the build, or using TF-M), care needs to be taken to properly load each image in the correct sequence (potentially a manual process).
      • The same is the case when using a board with a multi-core SOC (e.g: the nRF5340, with an application and a network cores).
    • When working with different boards, do not assume that all the features are arranged to guarantee a unified user experience. For instance:
      • RPi Pico:
        • The I2C peripheral does not work right off the bat.
        • The UART console is not mapped by default to the USB-micro port. One needs to declare a CDC ACM port in the device-tree (e.g: an overlay).
      • ESP32-WROOM:
        • Does not have an LED in the device-tree by default.
      • EFR32MG21:
        • It does not have I2C buses instantiated in the device-tree (this is also true for the other efr32_radio targets).
        • The on-board LEDs are not mapped to the device-tree definition (this could be due to my board rev not being an exact match).

    Sensors Support

    In this aspect, as well, Zephyr has an extensive list of sensors supported in-tree. All of these sensors follow a standard pattern, which after being instantiated in the device-tree (in the corresponding pattern node), then are exposed through a defined set of APIs, defined in zephyr/include/drivers/sensor.h.

    The idiom for this API basically consists in two main operations: fetch() and get(). The fetch() API (sensor_sample_fetch) consists in polling the sensor values by physically interacting with the sensor itself (e.g: data registers being read through I2C or SPI) and store them in the driver. The get() API (sensor_channel_get) is simply getting and converting those results for the application usage.
    The following snippet showcases this in a sample for the BME280:

    int main(void)
    {
    const struct device *dev = get_bme280_device();

    if (dev == NULL) {
    return 0;
    }

    while (1) {
    struct sensor_value temp, press, humidity;

    sensor_sample_fetch(dev);
    sensor_channel_get(dev, SENSOR_CHAN_AMBIENT_TEMP, &temp);
    sensor_channel_get(dev, SENSOR_CHAN_PRESS, &press);
    sensor_channel_get(dev, SENSOR_CHAN_HUMIDITY, &humidity);

    printk("temp: %d.%06d; press: %d.%06d; humidity: %d.%06d\n",
    temp.val1, temp.val2, press.val1, press.val2,
    humidity.val1, humidity.val2);

    k_sleep(K_MSEC(1000));
    }
    return 0;
    }

    Figure: Console output for BME280 sample-code

    Some thoughts around the Sensors Support:

    • Defining APIs as generic as possible, and standardizing the sensor data units makes it very easy to use various sensors interchangeably, irrespective of the vendor (e.g: BME280 and SHTC3, sharing the same APIs to acquire temperature and humidity).
    • Also, the concept of fetch() and get() is also very intuitive and easy to learn/use across different sensors.
    • On the other hand, being generic implies that the level of granularity at which sensors can operate is somewhat limited. This means that it is not un-common to find yourself looking through the driver code and concluding that the mode you had envisioned for the sensor is not supported, and that the code needs to be reworked.
    • Along the same lines, up until recently, using features such as a data-buffering FIFO would likely end up in using device-specific APIs, which defeat the purpose of the general standardized APIs.

    Conclusions

    • Zephyr has a strong foundation where a well-defined and consistent framework for developing hardware agnostic code is presented, clearly allowing swapping physical components with minimal modifications to applications.
    • The balance between between functional granularity and re-usability of the existing APIs is still evolving and we should only expect more configurability and features across releases, as more components are introduced and standardized thorugh hierarchiccaly structured models, such as the sensors module.
    • The ratio between variety of boards supported vs the quality of support still exhibits a high variability between vendors and families, with some community and vendors still to catch-up to the most supported ones. The determining factor here is the vendor involvement; and we’ve seen more silicon manufacturers joining the project. We should also expect significant improvement on this aspect as well in the upcoming releases.
  • Zephyr – “The Linux for MCUs”

    February 6th, 2024

    Re-inventing the Wheel

    It is interesting how the Firmware development ecosystem is so different to other software disciplines. Each chip-vendor maintain their own suite of tools which are presented as the all-in-one solution designed to meet all your needs. Consequently, each vendor endeavors to establish the foundations of their own software development kit (SDK) and ecosystem to cover basic requirements (usually with very similar features). Likewise, application developers often find themselves rewriting drivers or creating OS primitives that aren’t supported.

    The drawback of this paradigm is that a significant amount of development effort is spent reinventing the wheel, leaving less room for addressing the unique intrinsic challenges of each application through innovation.

    When we consider how software development works in other disciplines, we realize that these problems have been solved by reaching a consensus on a common foundation to build upon. From this perspective, it becomes apparent that there’s a need for an equivalent approach in firmware development, something akin to a “Linux for MCUs”.

    Zephyr: more than an RTOS, an Ecosystem

    The Zephyr Project, originating from the Linux Foundation, stands as one of the main contenders to change this paradigm.

    Compared to other real-time operating system (RTOS) offerings, Zephyr not only provides the essential OS primitives but also features a collection of drivers, subsystems, and libraries maintained by a diverse, multi-vendor community.

    Zephyr shares strong similaries its counterpart, Linux, on its very foundations by using two well-known Linux concepts:

    1. Device-Tree: Zephyr implements a clear abstraction between hardware and applications through the use of the Device-Tree concept. Similar to board support packages (BSPs), each supported hardware target contains its own device-tree source, along with adjacent configuration files, allowing it to initialize properly based on the system configuration. Subsequently, each software component that depends on it retrieves the relevant nodes and operates based on their properties.
    2. Kconfig: serving as the software configuration settings, allowing the build system to determine which libraries should be enabled and, if so, with which set of features.

    Road towards the “Linux for MCUs”

    As Zephyr contributors, we’re very excited on the potential the Zephyr Project has to effectively become the “Linux for MCUs” and shift the current paradigm on developing firmware.

    However, to truly fulfill the promise of becoming the Linux for MCUs, Zephyr is set to accomplish a set of goals:

    1. Hardware Agnostic: ensuring reusability across various hardware vendors.
    2. Extensive Library Support: offering mature drivers, subsystems, protocol stacks and OS primitives.
    3. External Integration: facilitating integration with third-party modules.
    4. Security: incorporating updated standardized security guidelines.
    5. Community Contributions: continuously improving the ecosystem.

    In upcoming posts, we will delve into each of these points with a hands-on approach, analyzing the advantages and disadvantages of this relatively new ecosystem. We will also point to valuable resources for anyone interested in getting started.

    Leave your Feedback!

    • Have you tried Zephyr? If so, what has been your experience?
    • Are there any areas related to Zephyr that you would like us to analyze?

    Very excited for the Purple Kite RTOS!

    Luis Ubieda.
    Lead Firmware Engineer
    Croxel, Inc.

  • blog->helloWorld()

    January 23rd, 2024

    Background

    Ever since I can remember, I have always been amazed and intrigued by technology and electronics and how their existence has changed the way we think and interact as human beings. This interest has driven me down a lifelong path to becoming an engineer and working in the tech industry.

    As a Firmware Engineer, I have had the opportunity to work on projects in different industries: commercial, industrial, medical, among others. One thing has become crystal clear:

    Things are changing FAST and the pace is not slowing down!

    This strikes me in two ways:

    First, because of the need for us, engineers and developers, to find a way to stay on top of new technologies, new protocols, and new toolsets. We must always evaluate them as they come and decide when is the right time to adopt a new option versus sticking with a known-good existing one.

    The second realization is that the work we’re doing today may help shape what the future will look like. This is both exciting and scary.

    More importantly, we should face this challenge as a community. It only makes sense to help each other by sharing relevant information, both directly and indirectly. I continually benefit from the efforts of my colleagues who share their experiences and lessons learned through newsletters, blogs, podcasts, and other media.

    I believe the time has come for me to start giving back, which is why I have decided to create this blog, “Embedded Tales,” as a space to share my own stories, experiences, guides, and to connect with others’ experiences and interests.

    Content Ideas

    Personally, I would like to write about the following topics I’ve had recent experiences with:

    • Developing Firmware with Zephyr RTOS.
    • Test-Driven Development.
    • File-Systems in Embedded Systems.
    • Working with Bluetooth Low Energy.
    • Working with Cellular IoT.
    • Working with the USB device stack.

    Leave your Feedback!

    • What topics would you be interested in?
    • What type of content would you like to see more of?

    I hope this is the beginning of an interesting journey!

    Luis Ubieda.
    Lead Firmware Engineer
    Croxel, Inc.

Blog at WordPress.com.

 

Loading Comments...
 

You must be logged in to post a comment.

    • Subscribe Subscribed
      • Embedded Tales
      • Already have a WordPress.com account? Log in now.
      • Embedded Tales
      • Subscribe Subscribed
      • Sign up
      • Log in
      • Report this content
      • View site in Reader
      • Manage subscriptions
      • Collapse this bar