Cocoacrumbs

Cocoacrumbs

All of the buildings, all of those cars
were once just a dream
in somebody's head
Mercy Street - Peter Gabriel

Cocoacrumbs

13 minutes read

Pic 1

A few blog posts ago, I documented how I created a Linux System for the STM32MP157F-DK2 board. It wasn’t such a pleasant experience. Although tiny compared to desktop or server Linux Systems it’s still huge and consumes a lot of resources just to be able to run. You also get quickly lost in all the packages and scripts that let everything work together.

One doesn’t get the feeling you can understand the system as a whole anymore and when something goes wrong, a lot of head scratching is only the beginning of the journey of getting it fixed.

That’s in big contrast to our home computers of yesteryear where you really could understand, and master, every single bit of the system in front of you. Which is probably the biggest appeal of the current retro computing movement.

So, I decided to try something a bit more simple (while still being a very healthy upgrade compared to our retro computers). A few years ago, I did a first attempt to get NuttX compiled on a STM32 Discovery board. It was complex and I couldn’t get it to run reliably at that time. Nevertheless, after my embedded Linux System experience just a few weeks before, How hard could it be now?

And, oh my, have things improved in NuttX land. Setting up the build environment and getting a first build running has become almost trivial. In this blog post I’ll document how I got a minimal NuttX build running on the STM32F769-DISCO board that I bought at the same time with the STM32MP157F-DK2 as a backup in case embedded Linux would be a disappointment. And how to setup VSCode for NuttX development.

Some useful pointers to get you started with NuttX


My host PC is running Manjaro Linux and, as it turns out, it’s now perfectly possible to cross compile NuttX on this Linux distribution. However, if you want to stick closer to the recommended way of working, a Docker container with Ubuntu works as well.

The native (Manjaro) solution

I’m not going to give a list of packages that need to be installed here. Depending on your preferred Manjaro install, it could be that you already have (part of) those packages. The best way is to just go ahead and see what errors occur along the way and install the missing package based on that.

For the ARM tool chain, you might consider downloading the official ARM toolchain instead of the one provided by the package manager of Manjaro. These tend to be more recent. The same remark goes for the Docker solution of course.

The docker solution

Setting up Docker

Use this Dockerfile description to set up a suitable docker container with Ubuntu to build NuttX. Save it under the filename Dockerfile (no filename extension required):

    FROM ubuntu

    # The following 2 lines are added to avoid hanging the container creation. 
    # See <https://grigorkh.medium.com/fix-tzdata-hangs-docker-image-build-cdb52cc3360d>
    ENV TZ=Europe/Brussels
    RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone

    RUN apt-get update && apt-get install -y \
        bison flex gettext texinfo libncurses5-dev locales \
        libncursesw5-dev gperf automake libtool pkg-config \
        build-essential genromfs libgmp-dev libmpc-dev \
        libmpfr-dev libisl-dev binutils-dev libelf-dev git \
        libexpat-dev gcc-multilib g++-multilib picocom \
        u-boot-tools util-linux kconfig-frontends sudo \
        gcc-arm-none-eabi binutils-arm-none-eabi

    RUN groupadd -g 1000 dev \
          && useradd -u 1000 -g dev -d /home/dev dev \
          && mkdir /home/dev \
          && chown -R dev:dev /home/dev

    # The following 3 lines allow the 'dev' user to run sudo (password is "dev"). 
    # Useful when later on packages need to be installed that are missing.
    RUN usermod -aG sudo dev
    SHELL ["/bin/bash", "-o", "pipefail", "-c"]
    RUN echo 'dev:dev' | chpasswd

    RUN locale-gen en_US.UTF-8

    ENV LANG en_US.UTF-8

    USER dev

    WORKDIR /home/dev

This Dockerfile creates a user named dev and password dev. sudo is also present so that you can easily install extra packages if there would be a need for it.

Building the docker image

Build the docker image with this command (while standing in the directory with the above Dockerfile):

    docker build -t ubuntu-nuttx .

Using a shared directory

Run the docker image with a shared directory which is, in my case, ~/Documents/Projects/NuttX/stm32f7/10.1.0 on the host PC and /data in the docker container. I use this shared directory to install the NuttX repo’s and resulting builds:

    docker run -dit -P --name nuttx -v ~/Documents/Projects/NuttX/stm32f7/10.1.0:/data ubuntu-nuttx

We get this hash tag back as its identifier:

    3de20d7e0c16b30056f60862a27febd5affc11f4b3453f91390d88d4aea05410

And we attach to the running docker image with:

    docker attach 3de2

Download NuttX

Official NuttX documentation

You can either download a stable release in compressed tar format or use the GIT repositories. At the time of writing this, the latest stable release was 10.1.0:

Download a stable release

    mkdir nuttx-10.1.0
    cd nuttx-10.1.0
    curl -L https://www.apache.org/dyn/closer.lua/incubator/nuttx/10.1.0/apache-nuttx-10.1.0-incubating.tar.gz?action=download -o nuttx.tar.gz
    curl -L https://www.apache.org/dyn/closer.lua/incubator/nuttx/10.1.0/apache-nuttx-apps-10.1.0-incubating.tar.gz?action=download -o apps.tar.gz
    tar zxf nuttx.tar.gz
    tar zxf apps.tar.gz

Clone from GIT repositories

    mkdir nuttx-10.1.0
    cd nuttx-10.1.0
    git clone https://github.com/apache/incubator-nuttx.git nuttx -b releases/10.1
    git clone https://github.com/apache/incubator-nuttx-apps apps -b releases/10.1

In both cases, your directory layout should be like this (It’s important that the apps and nuttx directories are next to each other!):

    └── nuttx-10.1.0
        ├── apps
        └── nuttx

It’s also best to not have spaces in the path’s according to the documentation.

Can be found here.

E.g. the NxWM, the tiny NuttX Window Manager can be found there.


Compiling NuttX for a supported board

Official NuttX documentation

Now we are ready to compile the source code into an executable binary file that can be run on the embedded board. In my case a STM32F769-DISCO. First we need to configure the build environment

Initialize Configuration

To list all supported configurations, do (while being in the nuttx directory):

    ./tools/configure.sh -L | less

The output is in the format <board name>:<board configuration>. You will see that, in general, all boards support the nsh configuration which is a good starting point since it enables booting into the interactive command line NuttShell (NSH).

To choose a configuration, you pass the <board name>:<board configuration> option to configure.sh. For my board, I have two possibilities:

    ./tools/configure.sh stm32f769i-disco:nsh

or

    ./tools/configure.sh stm32f769i-disco:netnsh

You can expect this kind of output for the NSH option:

    nuttx$ ./tools/configure.sh stm32f769i-disco:nsh
    Copy files
    Select CONFIG_HOST_LINUX=y
    Refreshing...
    make[1]: Entering directory '/data/nuttx-10.1.0/nuttx'
    ...
    make[2]: Leaving directory '/data/nuttx-10.1.0/apps/industry'
    mkkconfig in /data/nuttx-10.1.0/apps
    make[1]: Leaving directory '/data/nuttx-10.1.0/apps'
    #
    # configuration written to .config
    #

This configuration can later on be modified by using a menu based configuration system.

Building

Now you just need to use the make command to build NuttX with the configuration we specified earlier.

    make

If you want to compile a lot faster, add the -j option and specify how many cores you want to use to compile the code. E.g.:

    make -j 24

After just a few seconds we already have a NuttX build for our board (compare this to over half an hour for a Linux build for the STM32MP157F-DK2 board of my earlier blog posts…). You can expect this kind of output:

    nuttx$ make
    make[1]: Entering directory '/data/nuttx-10.1.0/nuttx/tools'
    ...
    make[1]: Entering directory '/data/nuttx-10.1.0/nuttx/sched'
    CC:  clock/clock_initialize.c
    ...
    CC:  wdog/wd_recover.c
    AR (create): libsched.a   clock_initialize.o clock_settime.o 
    ...
    make[1]: Leaving directory '/data/nuttx-10.1.0/nuttx/sched'
    IN: sched/libsched.a -> staging/libsched.a
    make[1]: Entering directory '/data/nuttx-10.1.0/nuttx/drivers'
    ...
    make[2]: Leaving directory '/data/nuttx-10.1.0/nuttx/boards/arm/stm32f7/stm32f769i-disco/src'
    LD: nuttx
    make[1]: Leaving directory '/data/nuttx-10.1.0/nuttx/arch/arm/src'
    CP: nuttx.hex
    CP: nuttx.bin

As the above output already indicates, there are 2 files created in the nuttx directory that can be used to flash the board:

  • nuttx.bin
  • nuttx.hex

Other useful make options are:

  • make clean # make a clean start
  • make distclean # make a clean start and clear out old configurations

Flashing the MCU with openOCD

This always needs to be done on the host PC with the openOCD tool (in case you used a docker image to build the code). You will probably need to install it first on the host PC.

Make sure you’re in the nuttx directory which contain the nuttx.bin and nuttx.hex files. It takes this command line to let openOCD flash the nuttx.bin into the MCU:

    openocd -f interface/stlink.cfg  -f target/stm32f7x.cfg -c "program nuttx verify reset exit"

This is the output openOCD gave me during its execution (which went very fast as well, barely 2 seconds):

    Open On-Chip Debugger 0.11.0
    Licensed under GNU GPL v2
    For bug reports, read
        http://openocd.org/doc/doxygen/bugs.html
    Info : auto-selecting first available session transport "hla_swd". To override use 'transport select <transport>'.
    Info : The selected transport took over low-level target control. The results might differ compared to plain JTAG/SWD
    Info : clock speed 2000 kHz
    Info : STLINK V2J35M26 (API v2) VID:PID 0483:374B
    Info : Target voltage: 3.228947
    Info : stm32f7x.cpu: hardware has 8 breakpoints, 4 watchpoints
    Info : starting gdb server for stm32f7x.cpu on 3333
    Info : Listening on port 3333 for gdb connections
    Info : Unable to match requested speed 2000 kHz, using 1800 kHz
    Info : Unable to match requested speed 2000 kHz, using 1800 kHz
    target halted due to debug-request, current mode: Thread 
    xPSR: 0x01000000 pc: 0x080001f8 msp: 0x20020d5c
    Info : Unable to match requested speed 8000 kHz, using 4000 kHz
    Info : Unable to match requested speed 8000 kHz, using 4000 kHz
    ** Programming Started **
    Info : device id = 0x10016451
    Info : flash size = 2048 kbytes
    Info : Single Bank 2048 kiB STM32F76x/77x found
    ** Programming Finished **
    ** Verify Started **
    ** Verified OK **
    ** Resetting Target **
    Info : Unable to match requested speed 2000 kHz, using 1800 kHz
    Info : Unable to match requested speed 2000 kHz, using 1800 kHz
    shutdown command invoked

The same ST-Link USB connection also functions as a serial port available at /dev/ttyACM0. Simply set up your favourite terminal emulator at 115.200 baud to see what goes on on the board. E.g with picocom:

    picocom -b 115200 /dev/ttyACM0

And this is the output I saw. NuttShell is already running. Typing in help gives a list of available commands and installed applications:

    nsh> help
    help usage:  help [-v] [<cmd>]

      .         cat       dd        help      mkrd      rmdir     time      usleep    
      [         cd        echo      hexdump   mount     set       true      xd        
      ?         cp        exec      kill      mv        sleep     uname     
      basename  cmp       exit      ls        pwd       source    umount    
      break     dirname   false     mkdir     rm        test      unset     

    Builtin Apps:
      sh   nsh  
      
    nsh> help hexdump
    hexdump usage:  hexdump <file or device> [skip=<bytes>] [count=<bytes>]	

    nsh> help -v hexdump
    NSH command forms:
      [nice [-d <niceness>>]] <cmd> [> <file>|>> <file>] [&]

    OR
      if <cmd>
      then
        [sequence of <cmd>]
      else
        [sequence of <cmd>]
      fi

    OR
      while <cmd>
      do
        [sequence of <cmd>]
      done

    OR
      until <cmd>
      do
        [sequence of <cmd>]
      done

    hexdump usage:  hexdump <file or device> [skip=<bytes>] [count=<bytes>]

Configuring Nuttx for a supported board

Official NuttX documentation

The canned NuttX configuration files are retained in:

.../nuttx/boards/arch-name/chip-name/board-name/configs/config-dir

For the STM32F769-DISCO I used, this is in:

.../nuttx/boards/arm/stm32f7/stm32f769i-disco

With the configuration tool it’s possible to configure NuttX further to your needs.

The NuttX Configuration Tool

Official NuttX documentation

Make sure you stand in the nuttx directory and issue the following command:

    make menuconfig

This brings up this text based GUI (if kconfig is installed):

Main screen of kconfig.

Another possibility to use this menu driven configuration tool is to use this command:

    make nconfig

You should see something like this if the required packages are installed to support this menu driven configuration tool:

Main screen of nconfig.

The basic configuration order is “bottom-up”:

  • Select the build environment,
  • Select the processor,
  • Select the board,
  • Select the supported peripherals,
  • Configure the device drivers,
  • Configure the application options on top of this.

Installing basic

A simple test that you can do is installing a basic interpreter with the configuration tool. I’ll use nconfig for this in this example:

Select Application Configuration:

Application Configuration.

Select Interpreters:

Interpreters.

Select Basic Interpreter support:

Basic Interpreter support.

Save the new configuration and after a quick rebuild and reboot:

After reboot. BAS 2.4 is running.

Debugging with VSCode

Official NuttX documentation on fast configuration changes

First of all, we need to make sure to have a debug build of our code. We can use the configuration tool for that. But it’s faster to use the command line via the kconfig-tweak utility like so:

    kconfig-tweak --enable CONFIG_DEBUG_SYMBOLS

And do a quick rebuild of NuttX.

Make sure you have the Cortex-Debug extension installed in VSCode:

Installing Cortex-Debug extension.

Now we need to make a new configuration:

Add a new configuration.

Select Cortex-Debug:

Select "Cortex-Debug".

A launch.json file is created inside the .vscode directory that is created alongside the apps and nuttx directories:

    .
    ├── apps
    ├── nuttx
    └── .vscode

The launch.json is already filled in with a template:

The template for "launch.json".

Replace it with this content in launch.json

    {
        // Use IntelliSense to learn about possible attributes.
        // Hover to view descriptions of existing attributes.
        // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
        "version": "0.2.0",
        "configurations": [
            {
                "name": "Cortex Debug (NuttX)",
                "cwd": "${workspaceRoot}",
                "executable": "${workspaceFolder}/nuttx/nuttx",
                "request": "launch",
                "type": "cortex-debug",
                "servertype": "openocd",
                "device": "stm32f7x",
                "configFiles": [
                    "interface/stlink.cfg",
                    "target/stm32f7x.cfg"
                ]
            }
        ]
    }

And we’re ready for debugging with VSCode.

Debugging

I noticed there seems to be a bug when I want to do use the list command in bas:

The LIST command returns an unexpected error after printing the first line of a program.

With this setup it’s now easy to set a breakpoint where the error string is formed. Which is the Program_PCtoError function:

After setting a breakpoint for "Program_PCtoError".

And from there on, we can search further for this bug. All of it directly on the stm32f769i-disco board itself.

For this kind of debugging, there is generally no need to see the contents of all the registers such a MCU has. But it can be useful for other kinds of applications. Luckily this is something that can easily be accomplished by using the corresponding SVD file that matches the MCU in use.

Because of the missing SVD file, we can't see the registers of the MCU in use.

For this particular case, I needed the STM32F769.SVD file which I dropped in the .vscode directory (next to the launch.json and tasks.json files). Now we only need to add this line to our launch.json file to tell the Cortex-debug extension where it can find the SVD file:

    "svdFile": "${workspaceRoot}/.vscode/STM32F769.svd"

The contents of the launch.json file then becomes:

{
    // Use IntelliSense to learn about possible attributes.
    // Hover to view descriptions of existing attributes.
    // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
    "version": "0.2.0",
    "configurations": [
        {
            "name": "Cortex Debug (NuttX)",
            "cwd": "${workspaceRoot}",
            "executable": "${workspaceFolder}/nuttx/nuttx",
            "request": "launch",
            "type": "cortex-debug",
            "servertype": "openocd",
            "device": "stm32f7x",
            "configFiles": [
                "interface/stlink.cfg",
                "target/stm32f7x.cfg"
            ],
            "svdFile": "${workspaceRoot}/.vscode/STM32F769.svd"
        }
    ]
}

After a restart of the debugging session we see the contents of the registers of the MCU in use:

After Adding the appropriate SVD file.

Building NuttX from within VSCode

To make our lives even more easier, we can add a task to build NuttX again after we made some changes to the code.

Select Configure Tasks... from the Terminal menu:

Selecting the "Configure Tasks..." from the "Terminal" menu.

You probably are going to see an empty template in the tasks.json files. Copy the following contents in it:

    {
        "version": "2.0.0",
        "tasks": [
            {
                "label": "make NuttX",
                "command": "make",
                "options": {
                    "cwd": "${workspaceFolder}/nuttx/"				
                },
                "args": [
                    "-j12"
                ],
                "type": "shell",
                "group": {
                    "kind": "build",
                    "isDefault": true
                }
            }
        ]
    }

If you now select Run Build Task... from the Run menu, you’ll see that make is called and the changed code is rebuild (using 12 threads because of the -j12 argument):

Running the build task.

Debugging NuttX which is compiled in a Docker container

When your code is compiled in a Docker container, all debug info will contains paths to the source code as they were used in the Docker container. More than likely, these paths will be very different compared to the paths where the source code lives in the shared directory of the host PC where VSCode is running.

In that case, GDB will not be able to locate your source code because of the different paths for source code level debugging, which is very annoying of course. Luckily there is a solution for that by telling GDB where to look for the source code by using the set substitute-path

From the GDB manual:

For example, if the file /foo/bar/baz.c was moved to /mnt/cross/baz.c, then the command

set substitute-path /foo/bar /mnt/cross

will tell GDB to replace /foo/bar with /mnt/cross, which will allow GDB to find the file baz.c in the /mnt/cross directory even though it was moved from the /foo/bar directory.


More debugging tips for NuttX:

A few more links that go deeper into debugging NuttX:

Recent posts

See more

Categories

About

Cocoacrumbs