Jump straight to the GitHub template. It provides a boilerplate workspace to kickstart Zephyr application development in a containerized environment.
Check out the previous article and the vscode-nordic-template for a boilerplate based on Zephyr and the nRF Connect SDK.
]]>Jump straight to the Buildroot config file.
As a developer, I’ve often encountered situations where I needed a real-time-capable system. Whether it’s for work or personal projects, having a responsive and predictable system is crucial. Recently, I delved into the world of real-time Linux kernels, specifically using the PREEMPT-RT patch. In this blog post, I’ll share my journey and provide step-by-step instructions for patching the Linux kernel with PREEMPT-RT using Buildroot.
Real-time (RT) systems are designed to meet strict timing requirements, ensuring that tasks are executed within specific time constraints. These systems are critical for applications such as industrial automation, robotics, automotive control, and audio/video streaming.
The PREEMPT-RT patch enhances the standard Linux kernel, making it suitable for real-time applications. By enabling full preemption, the kernel can respond promptly to high-priority tasks, reducing latency and ensuring predictable behavior. This is achieved by enabling threaded interrupt handlers, making locking primitives preemptible, and minimizing non-preemptible code sections.
Succesfully applying the Linux Kernel patch with Buildroot is a matter of properly picking and configuring the kernel source itself and a compatible PREEMPT_RT patch source.
For the specific case of the Raspberry these are the most important config changes:
Using the Raspberry Pi kernel has the clear advange that it comes with any customization for the hardware we’re targeting. In particular, we can still make use of the intree DTS that would not be available patching a vanilla kernel.
BR2_LINUX_KERNEL_INTREE_DTS_NAME="bcm2711-rpi-4-b"
However, when browsing the Raspberry Pi kernel repository it can be seen that the descriptions of the Pi are not defined for all the branches and tags the kernel sources. When available (e.g., in the stable_ tags but not in the rpi-x.x.x tags), the corresponding device tree files are found in
arch/arm/boot/dts
In the end, I picked the stable_20211118 tag of the Raspberry Pi Linux kernel.
What is still left to know at this point is what kernel version the selected tag will build. This information can be found in the main Makefile which, for the given tag looks like
VERSION = 5
PATCHLEVEL = 10
SUBLEVEL = 63
OLDER KERNEL - In hindsight, the reason to roll back to an older stable tag is that I couldn’t find a proper PREEMPT_RT patch for the later kernel that Buildroot was defaulting to.
Once the kernel version is known, a proper PREEMPT_RT patch can be identified from the official list at The Linux Foundation - PREEMPT_RT patch versions.
MATCHING VERSION - The ideal patch would have version, patchlevel and sublevel all matching. In my case I was able to match only version and patchlevel, then I picked the latest patch sublevel older than the kernel sublevel, and with a stable RT version (i.e., not rc, or release candidate), which points to 5.10.59-rt52.
# selected PREEMPT_RT patch
5.10.59-rt52
The following snippet shows the most important changes to the standard Raspberry Pi 4 defconfig file, to build the RT kernel. The complete defconfig file is available here.
# source kernel from Raspberry Pi GitHub (at an explicti tag)
BR2_LINUX_KERNEL_CUSTOM_GIT=y
BR2_LINUX_KERNEL_CUSTOM_REPO_URL="https://github.com/raspberrypi/linux.git"
BR2_LINUX_KERNEL_CUSTOM_REPO_VERSION="stable_20211118"
# point to a compatible patch version
BR2_LINUX_KERNEL_PATCH="https://cdn.kernel.org/pub/linux/kernel/projects/rt/5.10/older/patch-5.10.59-rt52.patch.xz"
# keep the RT flag in a fragment file, for modularity
BR2_LINUX_KERNEL_CONFIG_FRAGMENT_FILES="$(BR2_EXTERNAL)/board/raspberrypi-rt/linux.config"
# align the toolchain header files
BR2_PACKAGE_HOST_LINUX_HEADERS_CUSTOM_5_10=y
Keeping the RT flag in a separate file has the advantage that we don’t have to issue the make linux-menuconfig command manually. Buildroot automatically applies the RT config flag, only after the kernel has been patched (i.e., only when the RT config flag is actually available, since before the patch it is not). The kernel fragment file is a one liner:
# enable full preemption
CONFIG_PREEMPT_RT=y
BUILDROOT - If you don’t have Buildroot installed you can follow the setup steps (and a shortlist of useful commands) described in Buildroot basics
On each board Buildroot ships configuration for, only eth0 is configured by default. The Raspberry Pi boards, on the other hand, are generally capable of wireless connectivity.
Trying to enable wlan0 proved not to be so straight forward, as many of the articles I’ve found are now a bit outdated. But eventually it boils down to few little settings.
Jump to the configuration file already: rpi4_wifi_defconfig
TODO: On top of the default buildroot (2023.11) config, the following settings enabled the wifi:
BR2_PACKAGE_BRCMFMAC_SDIO_FIRMWARE_RPI=y
BR2_PACKAGE_BRCMFMAC_SDIO_FIRMWARE_RPI_WIFI=y
BR2_PACKAGE_RPI_FIRMWARE=y
BR2_PACKAGE_RPI_FIRMWARE_VARIANT_PI4=y
The brcmfmac drivers alone however, will not get us quite there. If you try to build and run the system now, it will show no dmesg about the driver.
As described in this post the WiFi components on the Pi come up a little late, so we need to modprobe the WiFi driver brcmfmac, manually, or use an hotplug mechanism that would automate that: enter MDEV.
will set the following value:
BR2_ROOTFS_DEVICE_CREATION_DYNAMIC_MDEV=y
For some more insights refer to 6.2. /dev management.
# network interfaces manager
BR2_PACKAGE_IWD=y
# SSH support
BR2_PACKAGE_DROPBEAR=y
Here iwd is preferred to the wpa_supplicant, since it works pretty much out-of-the-box with a simple rootfs overlay:
# /var/lib/iwd/<MyWifiSSID>.psk
[Security]
Passphrase=<MyWifiPassword>
The Buildroot manual describes how to add custom users, besides “root”, in 9.6. Adding custom user accounts. A user(table) file that defines the standard Raspberry Pi user looks like this:
pi -1 pi -1 =raspberry /home/pi /bin/sh -
Then the user file must be added to the configuration:
BR2_ROOTFS_USERS_TABLES="$(BR2_EXTERNAL)/board/users.txt"
Watch out! Each line MUST be terminated and MUST be terminated right, which is each line having a trailing \n, and the last line being an empty one.
Watch out! The user home folder like for example /home/pi is created automatically by Buildroot.
Watch out! To make sure the format is correct and no other character are injected, use nano, vi or another basic editor.
SO - How to connect to Wifi on start-up using Buildroot? : hint on using iwd in place of wpa_supplicant
Just the kick-off to start the whole journey with the Buildroot latest stable release:
git clone https://github.com/buildroot/buildroot.git
# checkout latest stable release
cd buildroot
git fetch --all --tags
git checkout <yyyy.mm>
The steps above will put you in a detached head, which is fine if you’re “just using” buildroot for your builds, without contributing back.
cd buildroot
make O=../<buildroot-output> menuconfig # exit without saving
cd ../<buildroot-output>
Note! A relative path here, is interpreted relative to the main Buildroot source directory.
When using out-of-tree builds, the Buildroot .config and temporary files are also stored in the output directory.
Buildroot generates a Makefile wrapper in the output directory
This first part is optional if you already have the ext folder.
mkdir <buildroot-out-of-tree-folder>
cd <buildroot-out-of-tree-folder>
touch external.desc
touch Config.in
touch external.mk
Let Buildroot know about the ext folder setting the BR2_EXTERNAL variable:
make BR2_EXTERNAL=/path/to/ext/folder menuconfig # exit without saving
Note! If building out-of-tree run the above commands from the Buildroot output folder.
Note! A relative path is interpreted relative to the main Buildroot source directory, not to the Buildroot output directory.
See 9.2. Keeping customizations outside of Buildroot.
# list configurations
make list-defconfigs
# load one of the available configurations (then edit it)
make rpi4_defconfig
make menuconfig
# save at the location specified in .config
make savedefconfig
# or save overriding the location
make BR2_DEFCONFIG=<whatever/path/to/file_defconfig> savedefconfig
See Buildroot manual, 9.6. Adding custom user accounts. https://buildroot.org/downloads/manual/manual.html#customize-users
Watch out! Each line MUST be terminated and MUST be terminated right, which is each line having a trailing \n, and the last line being an empty one.
# build current configuration
make
TODO: read manual and summarize
See 6.1. Cross-compilation toolchain (specifically 6.1.3).
If you need to build application for the system that is built by Buildroot you need a cross-compilation toolchain (and possibly the target rootfs).
The compilation toolchain that comes with your system runs on and generates code for the processor in your host system. As your embedded system has a different processor, you need a cross-compilation toolchain - a compilation toolchain that runs on your host system but generates code for your target system (and target processor).
cd ~/buildroot
make sdk
cd ~
cp buildroot/output/images/arm-buildroot-linux-gnueabihf_sdk-buildroot.tar.gz .
tar -xvf arm-buildroot-linux-gnueabihf_sdk-buildroot.tar.gz
mv arm-buildroot-linux-gnueabihf_sdk-buildroot rpi-buildroot-sdk
Then fix the simlinks running the relocator script:
cd rpi-buildroot-sdk
./relocate-sdk.sh
KiMotor automates the design of PCB motors. The process is controlled by a set of parameters that a user can provide via a wxPython GUI, designed with wxformbuilder. The parameters describe the features of the PCB motor that are created according to the following workflow:
TODO: add picture
slot | Conventional motor’s slots are mechanical features of the stator, resembling teeth, which allow for the motor windings to be wound onto. In a KiMotor PCB, the slot is the way the “active” area of the stator is partitioned (it spans an angle of 2*pi/n_slots) |
coil | Series of turns made up of PCB tracks, either straight lines or arcs, all contained onto a single PCB layer |
winding | Motor windings are loops of copper wire, packed together to achieve high copper density for a given volume (around one or more motor slot). In KiMotor a winding is a multi-layer stack of coils. The current version of KiMotor only supports concentrated windings, so each winding is fully contained inside a single slot. |
rings | A ring, or phase ring are circular areas laying between the motor windings and the motor shaft bore. They contain arc tracks and junctions that connects multiple windings to form a motor phase. |
waypoints | Locations in the XY-plane used to define the shape of a coil |
Further references at Circuit Globe and OSWOS
In KiCad’s internal coordinate system X increases from left to right, which is normal, but Y increases from top to bottom. The reason is historical and KiCad users that are bothered by this behavior can change it. However, as a developer interacting programmatically with the system, you have to deal with the reference frame described above, which is the one always used in the background.
KiCad system resolution is 1nm. When expressed in this way, coordinates can be treated as integers (e.g. VECTOR2I type).
KiMotor dimensions are given in mm and stored as floating point. They’re converted internally via pcbnew.IU_PER_MM (< KiCad 7) or pcbnew.FromMM(1) (KiCad 7) scaling factor.
KiMotor might internally convert 2D to 3D coordinates (think of them as VECTOR3I, with Z=0) to simplify some of the vector calculations used for coil planning and fillets.
KiMotor refers to a coil being clockwise (CW) or counter-clockwise (CCW) turning, from the point of view of an observer that looks down from the Z+ axis, and runs the waypoints from the first to the last (ascending indexes).
Note that, as the reference frame used is a right-hand one, the CW / CCW rotation are inverted for an user looking at the screen (as he looks up from the Z- axis)
The source code tries to stick to the following prefix/suffix as much as possible:
r_ | radial locations (in polar coordinates) |
th_ | angles, in radians (in polar coordinates) |
xy_ | points in the 2D space (in cartesian coordinates) |
wp_ | coil waypoints in 3D space (in cartesian coordinates) |
n_ | length of array-like objects |
_s | start/first point (of a line, arc, track, coil, etc.) |
_e | end/last point (of a line, arc, track, coil, etc.) |
_cw | waypoints ordered for a clockwise turning coil |
_ccw | waypoints ordered for a counter-clockwise turning coil |
At the core of the KiMotor functionalities lies the ability to parametrically generate the motor windings, across multiple PCB layers:
a coil_planner is called to generate the waypoints that describe a particular shape of coil, rotating CW
a coil_tracker is called to connect the waypoints into straight or arc PCB tracks that form the single-layer coil
the waypoints are transformed (mirrored around X) to make a template for the next layer, rotating CCW, then they’re tracked
the CW/CCW alternating coils are created for all the PCB layers needed, then they’re connected to make a winding
A coil planner is responsible for “solving a coil”, which is, to calculate a series of waypoints properly spaced on the XY-plane such as to describe a certain coil geometry, within the given constraints (e.g., inner and outer radius, coil “width”, shape).
The waypoints generated by the planner are used as a template. They’re capable of representing the coils on any layer at any angular position, when proper rotation matrixes are applied (to rotate around X and Z axis).
The waypoints generated are ordered from the outermost end of the coil to the innermost. However, there’s no explicit information returned about a waypoint role (e.g. if it is a line endpoint or an arc mid point) that is left to the coil tracker to define.
TODO: add description
TODO: add description
Making the coil look nice requires adding some fillet at every sharp corner. That said, actually doing it turns out to be quite a feat.
The geometry and linear algebra involved can be found in kimotor_linalg.py.
It works. It can be improved, though.
The remaining of the motor wirings can be divided into two groups, the phase rings and junctions and the motor terminals.
TODO: add description
Motor terminals are fan out added to the first winding of each motor phase. From the starting point of the winding (inner side), a track is added that runs in between two adjacent slots up to a radial location r_term where a terminal pad is placed.
]]>WireViz requires Python 3 and Graphviz.
As the summary page says, WireViz is a tool for documenting cables, wiring harnesses and connector pinouts. It takes a structured input file (YAML) describing the cable features and produces a graphical representation of the cable (image file), and its BOM (CSV file).
Install WireViz running the following command:
pip3 install wireviz
VSCode is a good place to start editing the WireViz YAML input files, as it comes with sintax highlighting for that language.
Additionally, install a couple of extensions to automate the steps of the workflow a bit, which is:
"settings": {
"emeraldwalk.runonsave": {
"commands": [
{
"match": "\\.yml$",
"cmd": "wireviz ${file}"
},
]
}
}
A VSCode code editor tab layout that worked for me is to have the YAML input file open on the left and the Graphviz preview on the right, on top of the .gv source.
NOTE : the first time saving the YAML file, the Graphviz source and Preview must be opened manually since they don’t exist yet. Updating and saving the YAML file afterwards wil trigger the automated workflow and the preview updates live (unless the input file contains errors and is not processed successfully by WireViz).
Here’s a list of some of the references used throughout this project:
]]>TODO:
Flash a 32bit raspi OS (lite is ok, but stick to 32, do not use 64bit !!! )… 32bit OS is a requirements for the Control Raspberry MC SL
enable SSH as usual with the .ssh file in the boot partition
# add the following / uncomment
# sudo nano /etc/dhcpcd.conf
interface wlan0 (or eth0 or whatever)
static_routers=192.168.1.1
static domain_name_servers=192.168.1.1
static ip_address=192.168.1.69/24
SSH to the RPi and modify the /boot/config.txt as follows (https://forge.codesys.com/forge/talk/Engineering/thread/510303ceaa/#464c)
arm_64bit=0
failing to do so will trigger a bug where the RTE does not detect the OS as 32bit even though it is …..
[ERROR] Architecture seems to be arm64. CODESYS Control for Raspberry Pi SL is only available on the 32-bit kernel for Raspberry Pi!
RESTART raspberry??
CODESYS IDE requires a Windows machine to run.
make sure the ethernet port on the dev PC is configured to be some 192.168.1.1 or similar on the same subnet of the rpi (get to the dialog box shown below… changing to Manual IP from elsewhere might not work properly)
flash the 4.8.0 RTE… select multicore
then try to scan to find it
for now the authentication by the user is mandatory so setup admin/admin credential for privileged user
(TODO consider disabling it https://faq.codesys.com/pages/viewpage.action?pageId=115834919)
then go on with the login….(it asks twice)
finally
then go on and login
and again with app compiled and deployed
TODO: describe the many levels of codesys authentications…
Setting up SSH keys for CODESYS development will save the coder from manually login each time he needs to deploy an artifact (i.e. connect to the Raspberry Pi)
https://help.codesys.com/webapp/_rbp_connect_with_public_key_authentication;product=CODESYS_Control_for_Raspberry_Pi_SL;version=4.2.0.0#id1
TL;DR: https://help.codesys.com/webapp/_rbp_connect_with_public_key_authentication;product=CODESYS_Control_for_Raspberry_Pi_SL;version=4.2.0.0#copying-the-public-key-to-the-controller
Start generate the ssh key on the dev pc (either win or linux)… the use the following to set them as authorized keys on the server
# -i specifies the (public) key you want to push to the server (remote), the rest is the
# usual login to the your remoter
ssh-copy-id -i ~/.ssh/id_rsa.pub pi@192.168.1.69
ssh keys are added on the server to the same .ssh/authorize_keys file
PermitRootLogin no
PasswordAuthentication no # yes, if you want allow "ssh" pwd access (local pwd is always possible)
PubkeyAuthentication yes
AuthorizedKeysFile .ssh/authorized_keys
ChallengeResponseAuthentication no
UsePAM no
restart the service after the changes
sudo systemctl reload sshd
Without a valid license, the CODESYS RTE will run for 2 hours then stop. Assuming the application and/or process you’re running is not impacted by it (e.g., simple mockups and lab setups) a cron job can be configured to periodically restart the CODESYS service (below we do it every hour). Cron job is active immediately, with no need to restart cron. Note that we use sudo to edit the root cron table, and not the user cron table.
sudo crontab -e
*/60 * * * * /usr/sbin/service codesyscontrol restart
To check the job runs correctly use top to check the PID of the codesysruntime.service is changed every hour.
Few ideas I’d like to experiment with in the future are:
TODO:
Here’s a list of some of the references used throughout this project:
Jump straight to the GitHub template. It provides a boilerplate workspace to kickstart nRF application development in a containerized environment.
install segger jlink software
enjoy all the features of all the ide views
s
I’ve created a VScode template project that supports Jupyter development inside a Docker container. It includes some improvement on the notebook export functionality with respect to other similar projects I’ve found online. It is available on GitHub (checkout the branch named custom).
First things first, make sure Docker is installed on your system; on Linux this can be done with the following command:
sudo apt-get install docker-ce
Next, go ahead download and install Visual Studio Code from the official page and once it’s done make sure to add the following extensions:
As it does for other IDE features, VScode offers a mechanism to have container-related settings configured and persisted in a .json file. A Dev Containers-aware folder/workspace only needs a .devcontainer folder with two files in it:
Basically, the Dev Containers take an existing Dockerfile, fully compatible with standard Docker, and add some metadata to better integrate it in a typical VSCode development workflow e.g., launching Docker in the background, hiding away some mundane tasks, automate some others and most important let you focus on the development that really matters.
As most of the times happens these days we can avoid writing our Dev Container from scratch and get inspired by some of the existing ones. In the particular case of Jupyter, the jupyter-devbox project is an excellent starting point.
The jupyter-devbox is a project that wraps most of what described above in a ready-to-use folder that comes with a Dockerfile tailored to machine learning and computer vision development.
Instead of cloning the original repo I worked on my own and made some changes, available on GitHub.
Things I’ve changed from the original jupyter-devbox are:
Check them out in the new Dockerfile
On top of the customizations described above, I’ve added .vscode/tasks.json to leverage the automation exposed by the VSCode Tasks framework.
In my case I wanted to have quick access to converting and exporting the current notebook, so I’ve added few custom tasks like so:
"tasks": [
{
"label": "New Task",
"type": "shell",
"group": "build",
"command": "jupyter-nbconvert",
"args": [
"--to", "pdf",
"--TemplateExporter.exclude_input", "True",
"${file}"
],
}
]
More custom tasks can be added simply appending more of the same blocks to the tasks.json file.
Few ideas I’d like to experiment with in the future are:
Here’s a list of some of the references used throughout this project: