An Introduction to Apache Nuttx
Apache NuttX is a real-time operating system (RTOS) for microcontrollers (MCUs). In this article, I discuss what the system consists of, its major features, and when it should be considered for embedded system software development. I also discuss an example in which I use NuttX RTOS and an ESP32 to read a BMP180 barometric pressure sensor.
Embedded systems and software needs to be reliable. This means embedded software should be designed to work under highly variable conditions; it must be well-developed, well-tested and extensively validated. One way to ensure good embedded software design and performance is by using a reliable operating system.
The major advantage of using an operating system in embedded system software is to delegate critical features—such as memory management, CPU usage, network management, and I/O management—to the operating system itself, so developers can focus their efforts on developing applications for the embedded system software. In embedded systems, it’s common to use a real-time operating system (RTOS), due to its small footprint (compared to a general purpose operating system) and real-time features—a requirement for some embedded software.
In this article, you’ll be introduced to the Apache NuttX RTOS, and you’ll learn how to make embedded software to read a Bosch BMP180 barometric pressure sensor, using an Espressif ESP32 microcontroller/System-on-Chip (MCU/SoC) with NuttX.
To reproduce the project described here, you’ll need the following electronic components:
- ESP32-DevKitC board (Espressif) (Figure 1)
- Micro-USB cable (for programming and powering DevKitC and sensors)
- 830-point breadboard
- Male-male jumper wires.
- Bosch BMP180 breakout board (Figure 2) with four male pin-headers soldered
WHAT IS NUTTX?
NuttX is a free, open-source RTOS designed to work in small footprint MCUs . It is scalable from 8-bit to 32-bit MCU environments, and supports many MCU architectures .
NuttX RTOS relies on a significant number of developers around the world to support it (myself included). Global big tech companies currently use NuttX in their solutions, and contribute to its development. These companies include Sony, Xiaomi, Fitbit, WEG (one of the largest companies in industrial equipment development in Latin America), and Engie (one of the largest companies in energy solutions development in Latin America).
The features of NuttX RTOS are listed on the Apache NuttX website . Without a doubt, its major feature is compliance with Posix and ANSI standards. This means you can develop embedded software to run on NuttX in a similar way to what you’d do for embedded Linux, as Posix standards and APIs are common to both operating systems. A strong advantage that NuttX offers, however, is that it can run on much cheaper and more readily available MCUs/SoCs than those required by Linux OS. NuttX source-code is available from its official OS and applications repositories .
WHEN TO USE NUTTX RTOS WITH AN ESP32 MCU
NuttX compliance with Posix and ANSI standards leads to an interesting benefit for those who already have embedded software solutions with embedded Linux, or those who are skilled in developing solutions using embedded Linux: Most embedded Linux applications can be ported for NuttX and run on a great variety of small-footprint MCUs officially supported by NuttX RTOS.
This means it’s possible to port an existing embedded Linux solution (which usually demands an expensive microprocessor/SoC to run) to NuttX, running on a much less expensive hardware set. As a consequence, it can significantly contribute to reducing final solution costs—a major highlight considering how semiconductor prices are rising due to electronic components shortages resulting from the COVID pandemic.
The ESP32 is an affordable (less than $5) MCU, considering its built-in resources. It has plenty of the resources most required in a typical embedded system/IoT solution, including rich peripheral communication interfaces (three UARTs, two I²C, four SPI), a dual-core CPU working at 240MHz, good flash memory size (4MB to 32MB), Wi-Fi (2.4GHz), and Bluetooth connectivity (support for both Bluetooth Low Energy (BLE) and Bluetooth Classic). Most of the ESP32 resources are already supported by NuttX RTOS, and the following ESP32 SoC versions were supported by NuttX at the time of this writing: ESP32 (WROOM and WROVER), ESP32-S2, and ESP32-S3.
Clearly, using NuttX RTOS with ESP32 can be a powerful combination for either developing embedded systems from scratch or porting an existing embedded Linux project to NuttX. Also, active communities support both ESP32 and NuttX, so you can easily get help if you’re stuck in some part of your project, or when learning how to use a specific feature. In addition, you don’t need to pay for any licenses when using NuttX in a commercial product.
With all these features, using NuttX in an ESP32 gives you the best of both worlds—an affordable MCU with plenty of useful resources, and a well-designed, thoroughly tested, and well-developed RTOS that is Posix-compliant.
EMBEDDED SOFTWARE COMPILATIONS WITH NUTTX
Despite resemblances to embedded Linux, a full compilation—of NuttX RTOS and applications—differs from Linux in certain respects. As happens in most of RTOSs available today, the result of a full compilation of NuttX is a single binary file that contains NuttX RTOS and the applications you selected to be part of your NuttX image. Also, in the particular case of ESP32, the NuttX final image needs to be flashed along with two other images: bootloader and partition table images. These are pre-compiled images provided by Espressif. We’ll see how to download and flash these to the ESP32 later in this article. For now, here’s a brief summary of these images:
- Pre-compiled bootloader image, which contains the bootloader for ESP32 and NuttX
- Pre-compiled partition table image, which contains all the information for ESP32 to compose its partition table to run NuttX
- NuttX RTOS image, which contains NuttX RTOS itself and all applications you want to be part of the NuttX image
While it’s convenient to only need to handle a single image that contains NuttX and the selected applications, this has a downside: updating an individual application is not straightforward, and in some cases is not even possible. You’ll have to keep this in mind when planning your firmware upgrade over-the-air (FUOTA) process for your embedded device, as it will probably always require a full upgrade (that is, upgrading the single binary containing NuttX RTOS and applications).
An alternative to updating an individual application is to write the applications in scripts for interpreted languages supported by NuttX (so far, NuttX supports Lua), and just upload applications’ scripts into an SPI flash partition in the ESP32. However, I would not recommend you take this approach. Interpreted languages offer significantly lower performance than compiled programs. Also, the libraries and community support for these interpreted languages to run in interpreters embedded in NuttX RTOS may be not as readily available as using C/C++ to code your applications. Therefore, this can become a dead-end for your application if a critical problem is found, more performance is needed, or a library or peripheral support isn’t available.
For your NuttX development environment, it’s best to use a Linux distro on your PC. A Linux Virtual Machine will work, too. For Windows 10 or 11 users, you can also set up your NuttX development environment with WSL2. However, it can be tricky to correctly configure WSL2 to build NuttX and flash MCUs using USB/UART interfaces, so I strongly recommend a Linux distro, instead.
The NuttX environment also works great in Ubuntu distros. Ubuntu is a popular Linux distro with community support, and it’s user-friendly—you don’t need to be experienced with Linux. So if you don’t have a Linux distro already installed in your PC, and you don’t know how to choose which Linux distro to use, please consider Ubuntu.
Once you have a Linux distro installed and running on your PC, the next step is to install the dependencies that NuttX requires for development and compilation. To do this, update and upgrade your distro’s installed packages, using the following commands:
sudo apt update
sudo apt upgrade
sudo apt-get install automake bison build-essential flex gperf git libncurses5-dev libtool libusb-dev libusb-1.0.0-dev pkg-config kconfig-frontends git
Once the dependencies are successfully installed, it’s time to get your own copies of NuttX repositories. NuttX is composed of two different repositories :
- nuttx: This is the main NuttX repository. It contains the operating system, drivers, architecture-specific codes and NuttX libraries. If you want to see how NuttX RTOS works under the hood, or want to contribute to NuttX RTOS development, this is the place to go.
- nuttx-apps: This is the applications repository for NuttX. It contains all the applications that NuttX uses, from examples to fundamental applications. Go here if you’re looking for a specific application, or want to contribute to NuttX with your own application.
To manage these two repositories correctly, you need to create a folder to use as the NuttX workspace. In this article, this workspace will be called “nuttxspace.” Create and access this workspace by using the following commands:
Once your NuttX workspace is set, clone the NuttX repositories to your NuttX workspace; put the nuttx repository in the nuttx folder, and the nuttx-apps repository in the apps folder. This can be done using the following commands:
git clone https://github.com/apache/nuttx.git nuttx
git clone https://github.com/apache/nuttx-apps.git apps
Now you need to get the ESP32 toolchain (also called the cross-compiler). This toolchain makes your PC able to compile software to run in a target with CPU architecture that’s different from your PC’s—in this case, an ESP32 MCU, which uses Xtensa architecture. NuttX uses it to compile the NuttX RTOS image/binary for the ESP32 MCU.
To get this toolchain, you have two options. The first is to download and install the full ESP-IDF on your computer. Instructions for this are on the Espressif website . The second, since the main goal is to use NuttX in ESP32, is to only download and install the toolchain, referring it in your ~/.bashrc file. To do this, go to a temporary folder (for example, ~/Downloads) and download and extract the toolchain by using the following command:
curl https://dl.espressif.com/dl/xtensa-esp32-elf-gcc8_2_0-esp-2020r2-linux-amd64.tar.gz | tar -xz
In Ubuntu and other Linux distros, the /opt folder is used for storing third-party software. Therefore, it’s a good idea to store the ESP32 toolchain in a sub-folder within /opt. This can be done with these commands:
sudo mkdir /opt/xtensa
sudo mv xtensa-esp32-elf/ /opt/xtensa/
Also, you’ll need to include the ESP32 toolchain path in your PATH environment variable in Linux. To automatically do this every time you start a Linux terminal session, follow these steps.
1) Open your .bashrc file by using sudo nano ~/.bashrc command.
2) At the end of the file, add a line and insert there the following content:
# Add esptool.py and its dependencies directory
PATH=$PATH:/home/<user>/.local/bin # Add the cross compiler path for ESP32
3) To finish your NuttX development environment setup, you need to get the bootloader and partition table pre-compiled images, provided by Espressif. These will be flashed into the ESP32 along with the NuttX image. Go to your nuttxspace folder, create an esp_bins sub-folder to store these pre-compiled binaries, and download them, with the following commands:
curl -L “https://github.com/espressif/esp-nuttx-bootloader/releases/download/latest/bootloader-esp32.bin” -o ../esp-bins/bootloader-esp32.bin
curl -L “https://github.com/espressif/esp-nuttx-bootloader/releases/download/latest/partition-table-esp32.bin” -o ../esp-bins/partition-table-esp32.bin
All set! Now you have a NuttX development environment ready for use.
NUTTX CUSTOMIZATION IN MENUCONFIG
NuttX RTOS is highly customizable. It’s possible to change almost every resource and configuration that will compose your final NuttX image—from very specific CPU architecture configurations to applications that your NuttX RTOS final image will contain—by tweaking configurations in menuconfig. In fact, those who are familiar with the Linux Kernel customization and compilation process will find this process to be similar, including the use of menuconfig (kconfig-frontends).
Once the NuttX development environment is set up, you need all the basic configurations to work on an ESP32 MCU. To do this, NuttX requires some pre-defined configuration files (much like defconfigs used in the Linux Kernel compilation), with multiple variations and pre-configured resources. For example, you can configure NuttX to use ESP32 Wi-Fi. It sets up almost everything that is needed for it, only requiring you to insert Wi-Fi credentials in menuconfig. You do this by configuring the NuttX build system with a pre-defined config file—so you don’t have to know every configuration needed to make Wi-Fi work properly. It helps a lot, since these files automatically load a great part of the required (and sometimes tricky) configurations.
For the project I describe later in this article, you only need basic resources to read some sensors and run NuttX RTOS on an ESP32. Fortunately, these configurations are available in the predefined configuration files sets, called esp32-devkitc:nsh. Thus, NuttX will be configured to be used in ESP32-DevKitC board and only the NuttX Shell (NSH) will be pre-loaded. It can be done using the following commands:
After these commands run, your NuttX development environment is ready to build an ESP32 NuttX image. To customize NuttX RTOS, run the following commands:
The menuconfig window (Figure 3) is an interface that allows you to customize and tweak almost everything related to NuttX RTOS. Feel free to explore all the available options, and get familiar with the navigation.
Figure 4 is the wiring for this project. Note that the Bosch BMP180 barometric pressure sensor uses an I²C communication interface. The sensor’s I²C signals are SCL and SDA; SCL is wired to GPIO 22 and SDA is wired to GPIO 23.
BMP180 SENSOR INTEGRATION WITH NUTTX
Let’s go a little deeper and explore how sensors are integrated into NuttX RTOS. Every peripheral (sensors included) is integrated in the operating system. Each supported peripheral has a driver, which can be found in the official repository’s drivers/sensors folder .
In NuttX, each board must have an initialization code for each peripheral device (sensors, actuators, buttons, and so on) supported by NuttX. These initialization codes link NuttX sensor drivers to the MCU’s particular communication interfaces (such as I²C, SPI, and UART). They assign a specific communication interface to a peripheral, and register the peripheral into this communication interface. The initialization codes for the ESP32 can be found in the boards/xtensa/esp32/common/src/ folder .
Subsequently, each initialization code must be called in the “board bring-up code,” which initializes the board, loading all peripherals and interfaces configured for the current NuttX build. The initialization code for ESP32-DevKitC can be found in the file boards/xtensa/esp32/esp32-devkitc/src/esp32_bringup.c . It’s important to know that, according to ESP32 bring-up code, the BMP180 sensor must be wired to the ESP32’s I²C 0 communication interface only, as shown in Listing 1. The BMP180 initialization code for ESP32 can be found in the file boards/xtensa/esp32/common/src/esp32_bmp180.c .
In this article, we’ll use the BMP180 NuttX app example  as reference. This example is straightforward. It opens BMP180 sensor communication (registered in the /dev/press0 path in NuttX RTOS), and reads the sensor every 500ms, as shown in Listing 2.
Code to wire the BMP180 barometric pressure sensor to the ESP32's I2C 0 communication interface
/* Try to register BMP180 device in I2C0 */
ret = board_bmp180_initialize(0, 0);
if (ret < 0)
syslog(LOG_ERR, “Failed to initialize BMP180 driver: %d\n”, ret);
This code opens BMP180 sensor communication (registered in the /dev/press0 path in NuttX RTOS), and reads the sensor every 500ms.
int main(int argc, FAR char *argv)
fd = open(“/dev/press0”, O_RDONLY);
ret = read(fd, &sample, sizeof(uint32_t));
if (ret != sizeof(sample))
perror(“Could not read”);
printf(“Pressure value = %05” PRId32 “\n”, sample);
Now, let’s proceed to the BMP180 configuration in menuconfig. The first thing to do is to enable I²C 0 in System Type → ESP32 Peripheral Selection, as shown in Figure 5. Then, I²C 0 GPIOs (SCL and SDA) must be configured in System Type → I2C configuration, setting GPIO 22 as SCL and GPIO 23 as SDA, as shown in Figure 6.
Next, the BMP180 device driver sensor should be selected. To do this, enable Bosch BMP180 Barometer Sensor support at Device Drivers → Sensor Device Support, as shown in Figure 7.
Finally, enable the BMP180/280 Barometer sensor example application in Application Configuration → Examples, as shown in Figure 8. Then, exit menuconfig and save configurations.
COMPILING NUTTX AND FLASHING IT TO THE ESP32
At this point, all the configurations and resources are set up, allowing NuttX RTOS to be compiled for the ESP32 with all the customizations done so far—BMP180 sensor support and its example app. So, the next step is to compile NuttX. Go to the ~/nuttxspace/nuttx folder, and trigger compilation with the make command:
You might find this process a little bit slow, since it can demand significant CPU usage, and this command allocates only one CPU core for it. You can speed up the compilation process by using more CPU cores. To do this, first you need to know how many CPU cores are available in your PC processor. You can get this information by executing the nproc –all command in the Linux terminal. Let’s suppose you get “8” as the command output, meaning your PC has an eight-core processor. You can speed up compilation process with the command:
It will use all 8 CPU cores for compilation, significantly reducing the compilation time. As a consequence, you’ll notice a much higher CPU usage. If your PC is running other heavy-CPU-usage tasks while you compile NuttX, it’s a good idea not to use all CPU cores for compilation.
At the end of the compilation process, the NuttX RTOS binary is available in the ~/nuttxspace/nuttx folder, as a file called nuttx. This is the file you’ll use to flash NuttX RTOS to the ESP32.
To flash the ESP32 with the NuttX RTOS image, pre-compiled bootloader binary and pre-compiled partition table binary, use the command below. Note: If the ESP32 is being mapped into a different /dev/ttyUSB* path, replace “/dev/ttyUSB0” with the correct one in the command.
make download ESPTOOL_PORT=/dev/ttyUSB0 ESPTOOL_BAUD=115200 ESPTOOL_BINDIR=../esp-bins
INTERACTING WITH NUTTX FLASHED IN ESP32
Once NuttX has been flashed into the ESP32-DevKitC board, you’re ready to interact with NuttX. To do this, use the picocom terminal. Picocom is a simple (but efficient) and lightweight serial terminal program for Linux . To install it on your Linux machine (assuming you’re using an Ubuntu distro), execute the following command:
sudo apt-get install picocom
With the ESP32DevKitC board connected to your Linux machine by USB cable, you can use the following command to launch picocom to communicate with it and interact with NuttX. Note: As I mentioned, if your board is enumerated in a different interface than /dev/ttyUSB0, replace it with the correct one in the command.
sudo picocom -b 115200 /dev/ttyUSB0
Picocom terminal will run, and you can start interacting with NuttX. Press the “Enter” key twice, and the NuttX Shell prompt will be shown to you as nsh>. You’re now in direct communication with NuttX flashed into the ESP32.
To check all programs and commands available in NuttX flashed into the ESP32, type “?” and press “Enter.” The list of commands and applications will be given, as shown in Figure 9, with the BMP180 application outlined in red.
Finally, let’s run the BMP180 sensor example apps, and watch the sensor being read. To do this, type “bmp180” and press “Enter.” The example application will run, and barometric pressure readings (in Pascal units) will be printed in the picocom terminal (Figure 10).
GETTING DEEPER INTO NUTTX RTOS
In this article, you’ve been introduced to NuttX RTOS, and now you know its main features, how peripherals are integrated into it, and how to compile and run it in an ESP32 MCU. So you have all the information you need to start exploring NuttX RTOS more deeply. If you like NuttX RTOS and want to know more about it, here are some suggested topics for you to study and practice.
First, try to integrate and use other peripherals (sensors, displays, LEDs, buttons, and so on) into NuttX RTOS. By doing this, you’ll accrue knowledge on how to configure the ESP32 peripherals and its communication interfaces, which is fundamental to developing real-world NuttX projects.
Second, learn how to init applications automatically after NuttX boot, especially regarding NuttShell start-up scripts. Because most real-world electronic devices must automatically init their applications (or the main application) just after boot completes, this resource is a “must-know” for those who want to develop solutions using NuttX RTOS.
Third, if you’re an open-source-software enthusiast, I encourage you to contribute to the NuttX project (RTOS itself and/or applications). It will require you to read, learn, and get hands-on experience with how NuttX RTOS works under the hood. The hands-on experience and understanding you’ll get from this is priceless, and will greatly increase your ability to develop solutions using NuttX.
 NuttX official website: https://nuttx.apache.org/
 NuttX RTOS repository: https://github.com/apache/nuttx/tree/master/boards
 NuttX RTOS features: https://nuttx.apache.org/docs/latest/introduction/about.html
 NuttX RTOS applications and OS repositories: https://github.com/apache/nuttx-apps
 Instructions for downloading and installing full ESP-IDF on your computer: https://docs.espressif.com/projects/esp-idf/en/latest/esp32/get-started/index.html
 The official repository’s drivers/sensors folder for peripherals: https://github.com/apache/nuttx/tree/master/drivers/sensors
 Initialization codes for the ESP32 MCU:
 Initialization code for ESP32-DevKitC:
 BMP180 initialization code for ESP32:
 BMP180 NuttX app example: https://github.com/apache/nuttx-apps/tree/master/examples/bmp180
 Picocom official website: https://guix.gnu.org/packages/picocom-3.1/#:~:text=Picocom%20is%20a%20minimal%20dumb,devices%20that%20provide%20serial%20consoles
PUBLISHED IN CIRCUIT CELLAR MAGAZINE • FEBRUARY 2023 #391 – Get a PDF of the issueSponsor this Article