Part 0 - Creating a minimal build environment with Docker.
Charles Dias, M.Sc.
Embedded Software Engineer | M.Sc in Electrical Engineering | C, Modern C++ | IoT | RTOS | Embedded Linux | Yocto Project
Hi folks!
After a few months, I'm back with a new series of articles. I will discuss tools and strategies for automated testing for STM32 microcontroller-based projects this time.
In this series, I plan to cover topics such as:
In this initial article, I'll show you how to create a minimal Docker container to build an STM32 project. As new tools become necessary, I plan to improve this container article by article.
Throughout the series, I'll use the Linux Ubuntu 22.04 LTS distribution.
Check the current code result on the project's GitHub repository, tag part_0.
Let's begin!
Software development environment for STM32 microcontrollers
STMicroelectronics provides an extensive ecosystem of software development tools for STM32 microcontrollers. Most of these tools are free to use with ST products.
A suggested workflow for working with STM32 microcontrollers is a 4-step iterative development process, as shown in the image below:
These tools are typically used in a desktop environment. However, the goal here is to create an environment that allows us to automate the build and test process, enabling the implementation of continuous integration processes integrated into our code repository.
After this introduction, let's create and test a minimal Docker image.
Install and test the Docker
We aim to use Docker to create a container that helps us build and test our project, locally or in an online repository like GitHub. Let's start by installing Docker on a local machine to achieve this.
It's easy to find tutorials online that describe how to install Docker on Ubuntu 22.04, like this one from Digital Ocean. Therefore, I will not repeat the steps here. Consult this or another tutorial to install Docker.
Assuming you have successfully installed Docker on your machine, let's test if you can run the Ubuntu 22.04 image in Docker by running the command:
$ docker run -it ubuntu:22.04
After running the command in your console, you should see an output similar to this:
$ docker run -it ubuntu:22.04
Unable to find image 'ubuntu:22.04' locally
22.04: Pulling from library/ubuntu
a8b1c5f80c2d: Already exists
Digest: sha256:a6d2b38300ce017add71440577d5b0a90460d0e57fd7aec21dd0d1b0761bbfb2
Status: Downloaded newer image for ubuntu:22.04
root@655be2fdf0cd:/#
You may see the message Unable to find image 'ubuntu:22.04' locally. Since Docker couldn't find the image locally, it pulled the image from Docker Hub. Also note that the user is logged in as root, and the string 655be2fdf0cd is the container ID (this ID may differ on your machine).
Using Docker as root is not recommended, but let's leave that aside for now and address this later.
To exit the container, type exit in the container's console.
Revisiting the previous image and descriptions of the STM32 tools, we see that STM32CubeIDE provides the toolchain and performs the compilation process, among other things. But when considering creating an automated compilation and testing process, is this the appropriate tool to use? Let's find out.
Tools like STM32CubeMX and STM32CubeIDE are designed for user interaction and are not well-suited for automation. Therefore, ST provides another tool called STM32CubeCLT, a set of command-line development tools with code compilation, board programming, and debugging capabilities (source: https://www.st.com/en/development-tools/stm32cubeclt.html).
Wait! Did someone say command line development toolkit? This seems much better suited for automated use.
Creating a Docker image for STM32CubeCLT
First, we will create a project folder called stm32-testing-automation. Inside this folder, create another folder called docker, and inside it, create a file called Dockerfile.dev. This file is essentially a script that Docker can run to build a Docker image automatically.
Open the Dockerfile.dev file and type the following text:
# Description: Dockerfile for the development environment of the STM32 microcontroller.
# Use the official Ubuntu 22.04 image as the base image
FROM ubuntu:22.04
# Set the maintainer information
LABEL maintainer="Your Name <[email protected]>"
# Set environment variables to avoid interactive prompts during installation
ENV DEBIAN_FRONTEND=noninteractive
# Changes the default shell for the Docker build process to bash
SHELL ["/bin/bash", "-c"]
# Set the timezone to America/Fortaleza and update the symbolic link for the timezone
ENV TZ=America/Fortaleza
RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone
# Update and install base required packages
RUN apt-get update && apt-get install -y \
build-essential git wget unzip python3 python3-pip \
&& apt-get clean && rm -rf /var/lib/apt/lists/*
Here's a line-by-line explanation:
Next, download the STM32CubeCLT installation file manually. Unfortunately, we cannot include the download process in the dockerfile due to the need for license acceptance and user login.
Create your user account, accept the license, and download STM32CubeCLT 1.15.1. Unzip the file and copy it to the docker folder.
Now, add the instructions to install the package dependencies for STM32CubeCLT and copy the installation file to our image:
# Install the dependencies for STM32CubeCLT
RUN apt-get update && apt-get install -y \
udev libusb-1.0-0-dev libncurses5 libncurses5-dev libpython2.7 libpython2.7-minimal ninja-build expect \
&& apt-get clean && rm -rf /var/lib/apt/lists/*
# Copy the STM32CubeCLT script installer
COPY st-stm32cubeclt_1.15.1_21094_20240412_1041_amd64.deb_bundle.sh /install_stm32cubeclt.sh
If you try to install STM32CubeCLT on your local machine, you must accept the license during the installation process. This makes the Docker build process a little more complex because the installation process does not support a non-interactive mode.
We can utilize the Expect tool to mimic user interaction to solve the previous issue. Within the docker directory, create a new file named install_stm32cubeclt.exp and input the following:
领英推荐
#!/usr/bin/expect
set timeout -1
spawn /install_stm32cubeclt.sh
# Simulate reading the license agreement by sending "space" or "enter" to scroll down
expect {
"More*" {
send -- " \r"
exp_continue
}
"I ACCEPT (y) / I DO NOT ACCEPT (N)" {
send -- "y\r"
}
}
expect eof
The above Expect script starts the install_stm32cubeclt.sh script as a child process, and the expect block simulates user interaction based on the child process output. Let's check more details:
Finish the dockerfile by adding instructions to copy the Expect script and install STM32CubeCLT. Here is our Dockerfile.dev so far:
# Description: Dockerfile for the development environment of the STM32 microcontroller.
# Use the official Ubuntu 22.04 image as the base image
FROM ubuntu:22.04
# Set the maintainer information
LABEL maintainer="Charles Dias <[email protected]>"
# Set environment variables to avoid interactive prompts during installation
ENV DEBIAN_FRONTEND=noninteractive
# Changes the default shell for the Docker build process to bash
SHELL ["/bin/bash", "-c"]
# Set the timezone to America/Fortaleza and update the symbolic link for the timezone
ENV TZ=America/Fortaleza
RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone
# Update and install base required packages
RUN apt-get update && apt-get install -y \
build-essential git wget unzip python3 python3-pip \
&& apt-get clean && rm -rf /var/lib/apt/lists/*
# Install the dependencies for STM32CubeCLT
RUN apt-get update && apt-get install -y \
udev libusb-1.0-0-dev libncurses5 libncurses5-dev libpython2.7 libpython2.7-minimal ninja-build expect \
&& apt-get clean && rm -rf /var/lib/apt/lists/*
# Copy the STM32CubeCLT script installer
COPY st-stm32cubeclt_1.15.1_21094_20240412_1041_amd64.deb_bundle.sh /install_stm32cubeclt.sh
# Copy the STM32CubeCLT expect script
COPY install_stm32cubeclt.exp /install_stm32cubeclt.exp
# Install the STM32CubeCLT
RUN chmod +x /install_stm32cubeclt.sh /install_stm32cubeclt.exp && \
/install_stm32cubeclt.exp
Build and run the Dockerfile.dev image
On your local machine, open the console, navigate to the docker folder, and run the following command to build the image:
$ docker build -f Dockerfile.dev -t <image-name>:<version> .
Replace <image-name> and <version> with your desired image name and version number, and wait a few minutes for Docker to build the image.
When the image build process is finished, start the Docker image by running the command:
$ docker run -it --rm <image-name>:<version>
Again, replace <image-name>:<version> with the image name and version used previously. For example:
$ docker run -it --rm charlesdias/stm32_dev_env:1.0
Note: Notice that we are still logged in as the root user.
Once you are logged into the Docker image, run the following command:
# ls /opt/st/stm32cubeclt_1.15.1/
After running the ls command, you can see the list of files and folders in the STM32CubeCLT installation directory. Among others, we have the CMake, GNU-tools-for-STM32, Ninja, and STLink-gdb-server folders.
Let's try another command. This time, type:
# /opt/st/stm32cubeclt_1.15.1/GNU-tools-for-STM32/bin/arm-none-eabi-gcc --version
and
# /opt/st/stm32cubeclt_1.15.1/CMake/bin/cmake --version
to see the GCC and CMake versions, respectively. Below is the expected output for the two previous commands:
This confirms the correct installation of the STM32CubeCLT tool on a Docker image.
Create a user account on a Docker image
Wait a minute! What about the root user?
As I mentioned earlier, logging in as root on a Docker image is not good practice. Let's fix this by adding the following instructions at the end of the Dockerfile.
# Create a non-root user and set permissions
ENV USER_NAME=mcu
ENV PROJECT_DIR=/project
ARG host_uid=1000
ARG host_gid=1000
RUN groupadd -g $host_gid $USER_NAME && \
useradd -g $host_gid -m -s /bin/bash -u $host_uid $USER_NAME && \
mkdir -p $PROJECT_DIR && chown -R $USER_NAME:$USER_NAME $PROJECT_DIR
# Switch to the non-root user
USER $USER_NAME
# Set the working directory
WORKDIR $PROJECT_DIR
Now, build the Docker image again, but using the command below:
$ docker build -f Dockerfile.dev --build-arg host_uid=$(id -u) --build-arg host_gid=$(id -g) -t <image-name>:<version> .
These build arguments (host_id and host_gid) are then used to create a non-root user inside the Docker container with the same UID and GID as the host user.
Run the new Docker image. Which user are you logged in as now?
Check the current code result on the project's GitHub repository, tag part_0.
Conclusion
I hope you enjoyed this introduction!
In the next article, we'll see how to build an STM32 project using the Docker image created.
See you there!
Embedded Software Engineer
4 个月Hey Charles Dias, M.Sc., Finally trying to implement something similar here at work. Something strange is happening in my case for the last step, the one where you created the non-root user. What i am seeing is: USER $USER_NAME is defaulting the mcu user to UID:GID 1000:1000. This kinda gets masked if your host id is 1000, but if it is not it looks like it will give troubles. As a workaround i modified that line to: USER ${host_uid}:${host_gid} Does anyone else sees this problem? Thanks Again
Just crafting embedded software
9 个月Impressive work, very well done! ??
Embedded Software Engineer
9 个月Parabéns Charles Dias, M.Sc. Great Article
Graduate Electronics Engineer at Sensoteq | UOL‘23
9 个月Very helpful!??
IT Consultant at QuickOps | Embedded Software Engineer
9 个月Series would be a wonderful experience.