Scientific Computing

Cross-compiler Raspberry Pi Pico

Microcontrollers generally use a different CPU architecture than the developer’s laptop. This implies a cross compiler is needed to build code for the microcontroller on the laptop. It’s straightforward to install the Raspberry Pi Pico ARM C / C++ cross-compiler on macOS, Windows, and Linux.

Please follow the section relevant to the laptop operating system. About 2..5 gigabytes of hard drive space is required by the Raspberry Pi Pico SDK and associated tools.

Git, CMake and a cross compiler are used by almost all full projects as well. This script will install the cross-compiler and other tools needed for the Raspberry Pi Pico SDK, or follow the manual process below.

#!/usr/bin/env bash

set -e

# determine OS and arch
case "$OSTYPE" in
linux*)
sudo apt update
sudo apt install git cmake g++ gcc-arm-none-eabi libnewlib-arm-none-eabi libstdc++-arm-none-eabi-newlib;;
darwin*)
brew install git cmake
brew install --cask gcc-arm-embedded;;
*)
echo "$OSTYPE not supported"
exit 1
esac

macOS

Homebrew install Git, CMake build system, and ARM cross-compiler by:

brew install cmake git

brew install --cask gcc-arm-embedded

The cross-compiler executables have a prefix “arm-none-eabi-” that are linked into $(brew --prefix)/bin for easy use from the command line.

It’s likely the macOS laptop has an Apple Silicon CPU. If so, it’s necessary to enable Rosetta. Rosetta enables most x86 apps on Apple Silicon at nearly full performance.

Note: if on macOS you get a build error like:

arm-none-eabi-gcc: fatal error: cannot read spec file ’nosys.specs’: No such file or directory

and you’ve installed the GCC cross-compiler via:

brew install --cask gcc-arm-embedded

Then something is wrong with the cross-compiler setup.

Linux

Install Git, CMake build system, and ARM cross-compiler on:

  • laptop/desktop with Ubuntu / Debian-like distros
  • full Raspberry Pi (e.g. Raspberry Pi 4)
apt update

apt install git make cmake g++ gcc-arm-none-eabi libnewlib-arm-none-eabi libstdc++-arm-none-eabi-newlib

Windows MSYS2

pacman -S mingw-w64-ucrt-x86_64-arm-none-eabi-gcc

This takes about 2GB of storage.

Windows WSL

Windows Subsystem for Linux (WSL) is useful for many projects including the Raspberry Pi Pico SDK. WSL can be the easiest way to work with non-Windows projects on Windows.

The Ubuntu WSL process takes about 10 minutes depending on download speed. WSL can be installed via the Microsoft Store Ubuntu app. If the Microsoft Store isn’t available on the computer, it is also possible to install Ubuntu WSL manually.

WSL can access the native Windows filesystem. WSL sees the native Windows filesystem “C:” in WSL via “/mnt/c”. Use the native Windows drive under “/mnt/c/pico” from WSL. Then use Windows File Explorer in path “C:/pico” to drag and drop .uf2 file to Pico.

The cross-compiler install on WSL Ubuntu is just like plain Linux in the section above.

To make switching between Windows and WSL easier, optionally use Windows Terminal.

Alternative: Visual Studio

The Visual Studio cross-compiler setup is described in Section 9.2 of the Pico install guide.

Fix Snap too early for operation

When setting up an older Linux operating system on a physical computer or virtual machine, the Snap package manager might not work correctly at first. Snap is handy to get the latest versions of software on older operating systems. The following message might appear on first trying to use Snap to install a package:

too early for operation, device not yet seeded or device model not acknowledged

A fix is to purge the Snap package manager and reinstall it.

apt purge snapd

apt install snapd

CMake C++ standard with fallback

CMake describes language standards supported by the specific C++ compiler being used by the Cmake project in CMAKE_CXX_COMPILE_FEATURES. For example, a project may with to fallback to a lower C++ standard version if the requested standard is not available. This is useful for building projects that need to support older compilers.

Use feature macros and / or CMake source code checks to define symbols within the code to fallback as necessary.

project(example LANGUAGES CXX)

set(cxx_std 17)

if("cxx_std_20" IN_LIST CMAKE_CXX_COMPILE_FEATURES)
  set(cxx_std 20)
elseif(NOT "cxx_std_17" IN_LIST CMAKE_CXX_COMPILE_FEATURES)
  message(WARNING "${CMAKE_CXX_COMPILER_ID} ${CMAKE_CXX_COMPILER_VERSION} may not support at least C++17 standard")
endif()

add_executable(my_exe main.cpp)
target_compile_features(my_exe PRIVATE cxx_std_${cxx_std})

Normally the project wishes to use CMAKE_CXX_COMPILE_FEATURES instead of CMAKE_CXX_KNOWN_FEATURES, as the latter tells all the features CMake knows, rather that what the compiler knows.


The C / C++ code can use macro feature checks that detect compiler C++ / C standard support.

#if __cplusplus >= 201703L
// C++17 features
#endif

For C code likewise:

#if __STDC_VERSION__ >= 201112L
// C11 features
#endif

Related:

Grub force text console boot

When using a virtual machine, and especially if the virtual machine is in emulation mode, which is generally many times slower than virtualization mode, it can greatly help interaction speed to use a console boot rather than graphical desktop. In operating systems like Ubuntu, this can be done by editing /etc/default/grub and setting:

GRUB_CMDLINE_LINUX_DEFAULT="text"
GRUB_TERMINAL=console

Then from Terminal:

update-grub

systemctl set-default multi-user.target

Then the system will boot into a text console on all future boots.


Reference

Install Windows Subsystem for Linux

Windows Subsystem for Linux WSL has Ubuntu LTS releases among other Linux distros on the Microsoft Store. The Microsoft Store is the recommended method to install WSL.

If the Microsoft Store isn’t available on the computer, manual WSL install is also available. The WSL changelog shows the continually expanding WSL feature set.

WSL can use GUI and sound with programs like Spyder via WSLg.

List WSL distros already installed on the computer from PowerShell / Command Prompt:

wsl --list --verbose

Install, list, and switch between Linux distros on Windows default for bash from Command Prompt:

wslconfig

WSL configuration

Limit the amount of RAM WSL2 can use for all installed WSL instances by editing Windows file $HOME/.wslconfig to include:

[wsl2]
swap=0
memory=4GB

Set memory= to less than the total computer physical RAM to help avoid using Windows swap.

A per-WSL instance default that is confusing and slows down WSL program-finding is stuffing Windows PATH into WSL PATH. We normally disable Windows PATH injection into WSL, because it also breaks library finding in build systems like CMake. Additionally, we enable filesystem metadata, as weird permission errors can occur, even causing CMake to fail to configure simple projects.

Add to each WSL instance /etc/wsl.conf file:

[interop]
enabled=false
appendWindowsPath=false

Run Ubuntu apps from Windows Command Prompt or PowerShell:

wsl ls -l

Run Windows program from Ubuntu terminal:

/mnt/c/Windows/System32/notepad.exe

Note that capitalization matters and .exe must be at the end.

Select CMake cURL version to build with

CMake uses the bundled cURL library by default. When debugging connectivity issues with CMake, a developer may wish to build CMake with a specific cURL version. To do so, from the cmake/ project source directory, use options like:

cmake -Bbuild -DCURL_ROOT=/path/to/curl -DCMAKE_USE_SYSTEM_LIBRARY_CURL=yes

Then build CMake as usual.

Verify the Curl version used with this script.

macOS software update from Terminal

macOS can update system software from Terminal, even macOS itself. However, to complete the upgrade requires using the graphical desktop, perhaps over VNC.

softwareupdate -l

shows the available updates.

softwareupdate -i -a

installs all available updates.

Fortran character allocatable

Variable length strings are implemented in Fortran 2003 standard like:

character(:), allocatable :: str

Passing such variables to procedures is declared the same as fixed length strings. In fact, we always declare actual arguments with “*” to avoid needing every string an exact length.

subroutine example(str)
character(*), intent(in) :: str

Before Fortran 202X standard, intrinsic Fortran functions need traditional fixed length character variables. For example:

integer :: i, L
character(:), allocatable :: buf

call get_command_argument(1, length=L, status=i)
if (i /= 0) error stop "first command argument not available"
allocate(character(L) :: buf)
call get_command_argument(1, buf)

Fortran function can return allocatable characters. If you run into bugs with this on old compilers, try manually allocating the character variable. Fortran standard compliant compilers auto-allocate character functions like numeric arrays.

It is proper to manually allocate character variable actual arguments when bind(C) interfaces are used.

function greet(b)

logical, intent(in) :: b

character(:), allocatable :: greet

!! Manual allocation of character variable. This could be necessary on old or buggy compilers.

if(b) then
  allocate(character(5) :: greet)
  greet = 'hello'
else
  allocate(character(3) :: greet)
  greet = 'bye'
endif

end function greet

GitHub Actions per-job compiler

GitHub Actions workflows can use different compilers per job by writing the compiler name or path to environment files in each job. This is useful for programs and libraries that need distinct compiler versions. An example of this is Matlab, where each Matlab release has a range of compatible compilers.

  • Matlab R2021a, R2021b: GCC-8
  • Matlab R2022a and newer: GCC-10

Implement in GitHub Actions:

jobs:

  linux:
    runs-on: ubuntu-latest

    strategy:
      matrix:
        release: [R2021b, R2024a, latest-including-prerelease]

    steps:

    - name: GCC-8 - Matlab < R2022a
      if: ${{ matrix.release < 'R2022a' && matrix.release != 'latest-including-prerelease' }}
      run: |
        echo "CC=gcc-8" >> $GITHUB_ENV
        echo "CXX=g++-8" >> $GITHUB_ENV
        echo "FC=gfortran-8" >> $GITHUB_ENV

    - name: GCC-10 - Matlab >= R2022a
      if: ${{ matrix.release >= 'R2022a' || matrix.release == 'latest-including-prerelease' }}
      run: |
        echo "CC=gcc-10" >> $GITHUB_ENV
        echo "CXX=g++-10" >> $GITHUB_ENV
        echo "FC=gfortran-10" >> $GITHUB_ENV

    - name: Install MATLAB
      uses: matlab-actions/setup-matlab
      with:
        release: ${{ matrix.release }}
        cache: true