Skip to content

Instantly share code, notes, and snippets.

@louigi600
Last active January 18, 2022 14:03
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save louigi600/3936bc961793704cfba01d68ea76667a to your computer and use it in GitHub Desktop.
Save louigi600/3936bc961793704cfba01d68ea76667a to your computer and use it in GitHub Desktop.
RPI pico play
How to get started on the RaspberiPi pico but on a unknown linux distribution (potentially a linux from scratch).
People using an officially supported distributions should be reading
https://datasheets.raspberrypi.org/pico/getting-started-with-pico.pdf
People wanting mode control on their supported distribuiton or a way to go about it on an unsupported distribution are in the right place/
Here's what we will need to get started: (**)
working native gcc and gcc-c++ v10+
cmake v3.13+
make
a cross toolchain for the target platform (*)
It is not scrictly mandatory but you will find it handy to have minicom or other such tool. You might like to give picocom a try if you have limited resources, in many aspects it is much like minicom but just really minimalistic.
*)There are several ways to go about it, maybe the easiest is to download one from
https://developer.arm.com/tools-and-software/open-source-software/developer-tools/gnu-toolchain/gnu-rm/downloads
Somewhere there are even archived toolchains gfor 32bit ARM.
If you just can't find what you need you can go the source way and build it on your platform but be prapared to get your hands dirty as it's likely it won't just build out of the box (specially if you don't meet the other requirements).
**)I'm assuming that if you are here you are familiar with linux and bash ... you would be running a supported distribution to make your life easy if that were not the case.
BIG FAT WARNING: as of 2021/12/12 there is something wrong with the C SDK and although the buld seems to go fine
if you actually flash the pico with the uf2 it may not work as expected (and may even crash your usb host driver).
I've reproduced this issue on 2 different architectures (including ARM) but I was unable to reproduce it on a RPi running PI OS.
Should youhave this issue you can address it by checking out an older release of the c SDK
git checkout bfcbefa
the issue seems to be related to TinyUSB
Create a folder where ywe are going to keep all our raspberryPI pico related stuff.
I chose to do that in /usr/local/pico but you are free to do it where ever you like. We will reger to this place as PICO_HOME.
Go in that folder and checkout the following github repositories
export PICO_HOME=/usr/local/pico
sudo mkdir -p $PICO_HOME
sudo chown <user>:<group> $PICO_HOME
cd $PICO_HOME
git clone -b master https://github.com/raspberrypi/pico-sdk.git
cd pico-sdk
git submodule update --init
cd ..
git clone -b master https://github.com/raspberrypi/pico-examples.git
I downloaded the cross toolchain from developer.arm.com and unpacked the tarball in ${PICO_HOME}/pico-sdk/toolchain
The pico-examples are not strictly necessary but they have a nice set of working examples that may be helpfull.
The pico-extras and pico-playground might also be helpfull.
Now the pico-examples will mostly work but will quickly put us into trouble if we want to create our own projecs
possibly not in the pico-examples folder.
Let's have a look at how we might go about making our own stuff.
First we need to setup some environment variables so that we can correctly use the SDK and the cross toolchain.
Each time we want to use the pico sdk we will need to set these environment variables :
export PICO_SDK_PATH=/usr/local/pico/pico-sdk
export PICO_TOOLCHAIN_PATH=/usr/local/pico/pico-sdk/toolchain/gcc-arm-none-eabi-10-2020-q4-major
export CMAKE_FIND_ROOT_PATH=/usr/local/pico/pico-sdk/toolchain/gcc-arm-none-eabi-10-2020-q4-major
export CMAKE_FIND_ROOT_PATH_MODE_PROGRAM=NEVER
export CMAKE_FIND_ROOT_PATH_MODE_LIBRARY=ONLY
export CMAKE_FIND_ROOT_PATH_MODE_INCLUDE=ONLY
I've conveniently wrapped all that I need to use the toolchain is a script. I just run it and it will fork a new bash and set up envirnomnebt for me ... when done I exit this subshell and everything is back to normal.
Now it's time to setup the toolchain so you can use it. To do that I create la launcher that forks a subshel setup to use the pico sdk ... so that you can exit abg go back to a normal native build environment:
cat << EOF > picosdk
PICO_HOME="\$(dirname \$(realpath \$0))"
PICO_HOME=\$PICO_HOME bash --rcfile \${PICO_HOME}/setup_xtoolchain
EOF
and here's what the setup_xtoolchain looks like (you might need to make small changes to get a nice prompt on your distro):
if [ -f ~/.bashrc ]; then
. ~/.bashrc
fi
echo "Setting up pico build environment in : $PICO_HOME"
export PICO_SDK_PATH=${PICO_HOME}/pico-sdk
ToolChain="$(ls ${PICO_SDK_PATH}/toolchain)"
export PICO_TOOLCHAIN_PATH=${PICO_SDK_PATH}/toolchain/$ToolChain
export CMAKE_FIND_ROOT_PATH=$PICO_TOOLCHAIN_PATH
export CMAKE_FIND_ROOT_PATH_MODE_PROGRAM=NEVER
export CMAKE_FIND_ROOT_PATH_MODE_LIBRARY=ONLY
export CMAKE_FIND_ROOT_PATH_MODE_INCLUDE=ONLY
export PATH=${PICO_TOOLCHAIN_PATH}/bin:$PATH
[ ! -d ${PICO_HOME}/projects ] && mkdir -p ${PICO_HOME}/projects
cd ${PICO_HOME}/projects
If you want you can make a link to picosdk in /usr/local/bin so it will be found in your path
Now let's put in the projects subfolder what we need:
mkdir -p ${PICO_HOME}/projects
cd ${PICO_HOME}/projects
In that subfolder we will create a couple of template CMakeLists.txt files (one for simple projects and one for projects
that use PIO)
The examples shown below will allow us to make new projects very quickly as long as the projects are respectively
one .c file or one .c and one .pio file projects.
Simple single .c file project simple-CMakeLists.txt
#This cmake lists txt file is setup so that you can just copy it as long as the project is composed of a singe .c file
#NAME.c
#where name is the folder name ... eg:
# myproject/myproject.c
cmake_minimum_required(VERSION 3.13)
if(DEFINED ENV{PICO_SDK_PATH})
include($ENV{PICO_SDK_PATH}/external/pico_sdk_import.cmake)
else()
include(../pico_sdk_import.cmake)
endif()
#Set the projec name from path
string(REGEX REPLACE "/build" "" NAME $ENV{PWD})
string(REGEX REPLACE ".*/" "" NAME ${NAME})
message("## ${NAME}")
project(${NAME})
pico_sdk_init()
add_executable(${NAME} ${NAME}.c)
target_link_libraries(${NAME} pico_stdlib)
pico_add_extra_outputs(${NAME})
# add url via pico_set_program_url
#example_auto_set_url(pio_blink)
Simple pio project (with one .c file and one .pio file) pio-CMakeLists.txt
#This cmake lists txt file is setup so that you can just copy it as long as the project is composed of 2 files:
#NAME.c
#NAME.pio
#where name is the folder name ... eg:
# myproject/myproject.c
# myproject/myproject.pio
cmake_minimum_required(VERSION 3.13)
if(DEFINED ENV{PICO_SDK_PATH})
include($ENV{PICO_SDK_PATH}/external/pico_sdk_import.cmake)
# message("Used pico_sdk_import.cmake from PICO_SDK_PATH env var")
else()
include(../pico_sdk_import.cmake)
# message("FUCK !!!")
endif()
#Set the projec name from path
string(REGEX REPLACE "/build" "" NAME $ENV{PWD})
string(REGEX REPLACE ".*/" "" NAME ${NAME})
message("## ${NAME}")
project(${NAME})
pico_sdk_init()
add_executable(${NAME})
# by default the header is generated into the build dir
pico_generate_pio_header(${NAME} ${CMAKE_CURRENT_LIST_DIR}/${NAME}.pio)
# however, alternatively you can choose to generate it somewhere else (in this case in the source tree for check in)
#pico_generate_pio_header(${NAME} ${CMAKE_CURRENT_LIST_DIR}/${NAME}.pio OUTPUT_DIR ${CMAKE_CURRENT_LIST_DIR})
target_sources(${NAME} PRIVATE ${NAME}.c)
target_link_libraries(${NAME} PRIVATE pico_stdlib hardware_pio)
pico_add_extra_outputs(${NAME})
# add url via pico_set_program_url
#example_auto_set_url(pio_blink)
Alternatively you might like to add to the cmakefile this option to use the USB serial (just after the target libraries) :
# enable usb output, disable uart output
pico_enable_stdio_usb(${NAME} 1)
pico_enable_stdio_uart(${NAME} 0)
Now let's make a project using what we have just setup. We will borrow the code from the pico-examples blink but only the .c file ... we will use the simple-CMakeLists.txt we just created (for a single .c file project):
make sure the environment variables are set
cd /usr/local/pico/projects
mkdir blink
cd blink
cp /usr/local/pico/pico-examples/blink/blink.c
cp ../simple-CMakeLists.txt ./CMakeLists.txt
mkdir build
cd build
cmake ..
make
Now at this point it would be ok to load the blink.uf2 file on the pico but automounting on our distro might not be working
so let's have a look at that too.
First we will create a udev rule for dealing with adding or removing block devices.
On my system the udev rules are in /lib/udev/rules.d (on your system it may differ but you can find the location by looking
at the udev package or eudev on non systemd environments). I created a new rule: 10-usb-storage.rules
root@nuc8i5:/lib/udev# cat rules.d/60-usb-storage.rules
ACTION=="add", KERNEL=="sd?[0-9]", SUBSYSTEM=="block", RUN+="usb_automount"
ACTION=="remove", KERNEL=="sd?[0-9]", SUBSYSTEM=="block", RUN+="usb_automount"
root@nuc8i5:/lib/udev#
The above seems to work fine on systems not using systemd, if you have systemd you might prefer to do something like this (and skip the helper script part):
[root@nuc8i5 rules.d]# cat 99-usb-storage.rules
ACTION=="add", SUBSYSTEMS=="usb", SUBSYSTEM=="block", ENV{ID_FS_USAGE}=="filesystem", ENV{ID_FS_LABEL}=="RPI-RP2", RUN{program}+="/usr/bin/systemd-mount --no-block --automount=no --options='umask=000,user' --collect $devnode /media"
[root@nuc8i5 rules.d]#
That is a generic rule that would call the helper script /lib/udev/usb_automount each time a sd?[0-9] block device is
detected by the kernel. Yo could potentially get this to work for any block device appearing but I chose not to and the helper
script will only deal with the RPi pico.
Now we can create the helper script that will do the job fro us:
root@nuc8i5:/lib/udev# cat usb_automount
#!/bin/bash
#this will automatically mount and umount th RPi Pico on /mnt/pico
Name=$(basename $0)
Logger="/usr/bin/logger -p local3.info -t $Name "
#logger -p local3.info -t aoutomount -- testing
Message="$* $DEVNAME $ACTION $ID_FS_LABEL"
$Logger <<< $Message
pico_add ()
{ Message="automounting $DEVNAME $ID_FS_LABEL"
$Logger <<< $Message
/sbin/mount -o umask=000 $DEVNAME /mnt/pico && $Logger <<< "mounted" || $Logger <<< "failed"
}
pico_remove ()
{ Message="umounting $DEVNAME $ID_FS_LABEL"
$Logger <<< $Message
/sbin/umount -f /mnt/pico && $Logger <<< "umounted" || $LOGGER "failed"
}
case $ID_FS_LABEL in
RPI-RP2) pico_$ACTION ;;
*) $Logger <<< "$ID_FS_LABEL is not configured for any automatic action" ;;
esac
root@nuc8i5:/lib/udev#
Now we can reboot or try get udev to reread the config:
udevadm control --reload-rules && udevadm trigger
Now plugging in the pico while pressing the bootsel button should get the pico automatically mounted.
We can now copy the u2f2 file into the picos root folder. As soon as the os commits the data to the block device the
pico will automatically reboot and the kernel will detect the removal of the block device and the newly created udev
rules should force an umount of the pico.
cp /usr/local/pico/projects/blink/build/blink.uf2 /mnt/pico ; sync
It is not necessary to force a sysnc after the copy buy it will make the pico reboot faster and start executing the new
code almost immediately whereas if we do not force the sync we will need to wait untill the os decides to physically commit
the data to the block device.
At this point we are ready to try something. The pico-examples repo has quite a few examples but I find most of them ither
far too simple or requiring a conciderable amount of learning before we can make customizations to make them of any real use,
specially for the PIO (which is one of the features that makes the pico very intresting for it's price).
So I made a custom PIO exaple for flashing a led with dynamically changable on and off time ... so without further ado let's
get going with it.
PIO led blinker introduction
We want to flash the onboard led (on gpio 25) and we want to be able to change the on time and the off time of the led at
any given time. Now the tricky part of this is that the state machines (SM) only have 2 scratch registers and one we need to
use to looping so we have to think of a smart way of retaining the on time and the off time because one of the scratch
registers will ne used for looping untill it reaches 0. Now the moc instruction can target both OSR and ISR and it can,
among other sources, use ISR as source. In this example we will not be using RX fifo so we can actually use ISR as a storing
location and move information to and from ISR so in this particular example we have 2 storage locations:
ISR and Y scratch register.
There is another thing we need to sort out: the SM will need to keep on looping on previous on and off times if no new data
is in the TX fifo but it must also be ready to catch the ne on anf off times if there is information on the TX fifo.
I was unable to find an instruction that would only read if there is info without blocking, however there is an option to do
almost the same: read if there is info and if there is not use information from X scratch register.
That is what the nonblocking pull does: it pulls 32 bits from TX fifo into OSR if there is something to pull else it places
the content of X scratch refister in OSR.
By default the SM will be clocked at the same speed as the main cores (125 Mhz) and for blinking a led I think on and off
times can be expressed in milliseconds. just for simplicity we will slow down the SM to 10 Mhz this will help us in 2 ways:
1) multiply the on and off millisecond delays by 10000 anf we get instruction count for loops
2) the clock is sufficiently high to ignore the time it takes the SM to execute the instructions compated to the requested
on and off times for the led
We are now readu to start looking at the code.
If we stick to the above suggestions we can start by creatin a folder for the project.
mkdir /usr/local/pico/projects/pio_blink
cd /usr/local/pico/projects/pio_blink
cp ../pio-CMakeLists.txt ./CMakeLists.txt
Make sure the environment variables are set for the pico xtoolchain.
Let's start with the core part of it: the acrual code for the SM in PIO assembly language pio_blink.pio
;
; the pio only hase 2 scratch registers and we want to have an on time delay loop and an off time delay loop
; we also want to be able to tell the sm to change the on time and off time at any time
; we will haveto use isr,osr,x,y and non blocking pull in clever way: nonblocking pull will copy x into osr if tx fifo is empty
; so if we keep the previous value in x before pulling then we only get change if a new value is present in tx fifo
;
; SET pin 0 should be mapped to your LED GPIO
.program blink
;this part is out of the wrap loop because we want to use blocking pulls so that we won't start flashing
;untill we have on and off delay times
pull block ;pull on delay into osr then copy it into isr (we well use isr as a storage ifor the on delay time)
out x, 32
mov isr x ;isr now contains the on delay
pull block ;pull off dalay into osr
out y 32 ;y now has the off delay
.wrap_target
;checking if we have new on delay
mov x, isr ;copy on delay from isr to x so that noblock pull is safe
pull noblock ;if tx fifo is empty osr conains a copy of x
out x, 32
mov isr x ;isr now contains new on delay or old value if tx fifo was empty
;on loop setup , x already contains on delay
set pins, 1 ; Turn LED on
lp1:
jmp x-- lp1 ; Delay for (x + 1) cycles, x is a 32 bit number
;checking if there is a new off delay
mov x y ;now x contains the old on delay saved on y scratch register
pull noblock
out x 32
mov y x ;save new off delay in y
;off loop setup , x already has the off delay
set pins, 0 ; Turn LED off
lp2:
jmp x-- lp2 ; Delay for the same number of cycles again
.wrap ; Blink forever!
% c-sdk {
// this is a raw helper function for use by the user which sets up the GPIO output, and configures the SM to output on a particular pin
void blink_program_init(PIO pio, uint sm, uint offset, uint pin)
{ pio_gpio_init(pio, pin);
pio_sm_set_consecutive_pindirs(pio, sm, pin, 1, true);
pio_sm_config c = blink_program_get_default_config(offset);
sm_config_set_set_pins(&c, pin, 1);
pio_sm_init(pio, sm, offset, &c);
}
%}
Now let's have a loop at the main c file: pio_blink.c
/**
* use the rp2040 pio to blink led on gpio 25
* with a specific on and off time expressed in milliseconds
* To make all calculations easyer we will slow down the pio clock to 10 MHz
* so that even the full 32 pio instructions have a negligeble weight ( 3.2 uS )
* compared to the smallest possible delay ( 1000 uS )
* making the full 32 instructions less then 1% of the smallest delay
*
* The RPi pico should be clocked at about 125 Mhz so we will divide the SM
* clock by a factor of 12.5 ...keep in mind that the crystal has a 1% tollerance
* so your pico may be clocking at anything between 123.75 to 126.25 Mhz.
*
* We are going to be sending the SM an on delay and an off delay via 2 entries in the Tx fifo
* The SM will loop with these values untill new onese are sent
*/
#include <stdio.h>
#include "pico/stdlib.h"
#include "hardware/pio.h"
#include "pio_blink.pio.h"
void blink_pin_forever(PIO pio, uint sm, uint offset, uint pin, uint freq);
int main()
{ unsigned short ontime=150;
unsigned short offtime=350;
setup_default_uart();
PIO pio = pio0; //we will use pio0
//now load pio assembly program "blink_program" into pio0 and store the entry point offset
uint offset = pio_add_program(pio, &blink_program);
printf("Loaded program at %d\n", offset);
//initialize SM 0
blink_program_init(pio, 0, offset, 25);
pio_sm_set_enabled(pio, 0, true);
pio->sm[0].clkdiv = (uint32_t) (12.5f * (1 << 16));
sleep_ms(1000); //give the SM time to initialize itself ... else it will act erraticaly for a whilebefore it starts parsing tx fifo correctly
while (1)
{ pio->txf[0] = 10000 * ontime;
pio->txf[0] = 10000 * offtime;
sleep_ms(3000);
pio->txf[0] = 10000 * offtime; //use offtime instead of ontime
pio->txf[0] = 10000 * ontime; //use ontime instead of offtime
sleep_ms(3000);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment