A template project for quick-starting C-based RP2040 Raspberry Pi Pico projects in Visual Studio Code, using the picoprobe programmer.
This uses a library derived from the the excellent pico-lib2 library. We have forked that and are actively developing it at https://github.com/gherlein/pico-lib3. Until we set this up as a git submodule, just clone that repo alongside this one and "ln -s ../pico-lib3 ." inside this repo.
This is a WORK IN PROGRESS. When it stabalizes a bit we'll clean it up to use git submodules and be a little more organized.
This is the "blinky" program for the Pico W board. We are using that board because the projects we are doing all are IoT and need networking or BLE, and having that on the board is a huge advantage. Later we may add other boards. We have tried to make it fairly easy to make a few edits and use a plain Pico board (or a clone) if that's what you need to do.
We wanted an easy way to start a project around the Pico W with enough plumbing in place that we are not doing grunge work to do basic things. There are probably other solutions for this but we also wanted to grok this ecosystem from first principles, so here we are.
You can manually flash the Pico (see below). The PicoProbe can let you debug using OpenOCD and GDB, but that's outside the scope of this repo/README. The biggest advantage of using the PicoProbe (or another Pico set up to be a PicoProve) is DEVELPMENT SPEED. You can work at the command line and flash your Pico. No fumbling for the 'bootsel' button and plugging and unplugging your Pico. This can be a big deal since microUSB plugs do wear out!
If you use the USB for both power and serial connection then you will need to reconnect after every reset. When you connect to the UART0 pins (GP0, GP1, GND) using the serial on the PicoProbe then your connection stays open through resets.
It's often useful to restart your program. For example, if you are debugging a chip initialization sequence over SPI. You don't need to flash the program just to get a restart. The included script "reset" uses openocd to send a command to the Pico to restart. Saves time and flash cycles!
This assumes that
- You have already installed OpenOCD with picoprobe, according to the steps in Getting Started, Appendix A
- Your VSCode has already been configured according to the steps in Getting Started, Chapter 7
- You have installed the picoprobe uf2 to one Pico, and it is connected to the other Pico with the correct wiring (refer again to Appendix A).
- OR, you bought a PicoProbe - Amozon Link
- You have added yourself to the plugdev group so that you don't need root privledges to use the PicoProbe.
The easiest is if you are using Ubuntu or Debian Linux (which includes the RPi - Raspberry Pi recommends just using a Pi as your dev box). Follow these instructions to use a great script to do 99% of the work for you:
Take special note to get your ENV variables set. If you don't set them in your .bashrc you can just "source" them in your working terminal. The "envrc" file in this repo has the variables to be set, defaulting to the locations the setup script uses. The current example envrc file:
export PICO_SDK_PATH=$HOME/src/pico/pico-sdk
export PICO_BOARD=pico
#export PICO_BOARD=pico_w
export PICO_PLAYGROUND_PATH=$HOME/src/pico/pico-playground
export PICO_EXAMPLES_PATH=$HOME/src/pico/pico-examples
export PICO_EXTRAS_PATH=$HOME/src/pico/pico-extras
export PICO_LIB2_PATH=$HOME/src/pico/pico-lib2
You really should consider using direnv to handle environment variable management. You can take a file like the "envrc" file and make it a dotfile in your working folder and direnv with AUTOMATICALLY source it for you when you enter that directory. So if you need different variables for different repos, it's EASY. Especially for PICO_BOARD.
Add a file like /etc/udev/rules.d/ (perhaps called 99-picotool.rules) with contents like this:
SUBSYSTEM=="usb", \
ATTRS{idVendor}=="2e8a", \
ATTRS{idProduct}=="000c", \
TAG+="uaccess" \
MODE="666", \
GROUP="plugdev"
NOTE: you may need to verify the values. Do a "tail -f /var/log/syslog" (or equivalent for your linux flavor) and note the idVendor and idProduct codes for YOUR PicoProbe and make the file match.
You can reload the rules with "sudo udevadm trigger" - no reboot required. After that it's going to be automatically loaded at boot. You should no longer need sudo to run any PicoProbe commands.
- Make a project directory in your pico-sdk parent directory (usually
~/pico/projects
, with sdk at~/pico/pico-sdk
), e.g. make~/pico/projects
. - Press [Use this template], or, download this template as a zip.
- Clone your repository/Unzip the downloaded folder to your new project directory.
- Clone the pico-lib3 project parallel to this repo on your filesystem.
- Link that folder inside this one: "ln -s ../pico-lib3 ." - this makes a symlink to that folder (this will be git submodules later)
- Write whatever you want in main.c, add more files, go wild...
- Create a "build" directory (mkdir build) - or just type 'make prep'
- Edit the Cmake file to include dependencies and such (see below)
- Build! (make or make -JX where X is the number of cores you have)
The included makefile has a few targets to make life easier:
- prep - makes the build directory and sets up for a build
- flash - uses the Picoprobe to flash the code to the board
- clean - cleans the build directory
- git - does a fast git add/commit - you really should squash the little commits up later
- debug - makes a connection to the openocd instance started from the command below
- openocd - starts openocd and readies it for debug use
In addition, the makefile that is generated in the build directory has a few more:
- reset - uses the "reset" script in this repo to force the board to reboot - great in testing!
- flash - same as above, but from the folder you build in
- strip - strips the debug sympbols from your binary to make it smaller - larger programs may need this!
The templace CMakeLists.txt file is extremely basic and looks like this:
make_minimum_required(VERSION 3.13)
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
include(pico_sdk_import.cmake)
project(main C CXX ASM)
set(CMAKE_C_STANDARD 11)
set(CMAKE_CXX_STANDARD 17)
pico_sdk_init()
add_executable(main
main.c
)
pico_enable_stdio_usb(main 1)
pico_enable_stdio_uart(main 1)
target_include_directories(main PRIVATE
${CMAKE_CURRENT_LIST_DIR}
)
pico_add_extra_outputs(main)
target_link_libraries(main
pico_stdlib
lib2_sys
# other libs as needed
)
add_custom_target(flash
COMMAND /bin/bash ../flash
DEPENDS main
)
If you use any of the hardware other than serial you are going to have to configure the libraries into CMakeLists.txt. Add the needed library to this line:
target_link_libraries(main
pico_stdlib
# other libs as needed
)
CMake will include headers as well.
This README cannot substitute for learning more about CMake.
To go beyond the basics outlined here, a great tool to help you build configurations is the Pico Project Generator. You should at least be aware of it, even if you are manually handling your cmake configuration.
Edit the CMakeLists.txt file to include serial or not. Set a 1 for incuded, 0 for disabled, in these two lines:
''' pico_enable_stdio_usb(main 0) pico_enable_stdio_uart(main 1) '''
USB is over the power/usb port, and uart is for the uart1 on physical pins 1 and 2.
This template assumes you are using the picoprobe, and you have the picoprobes serial pins attached to pins 1-2-3. You can then use the uart and choose to either not use the usb port at all or only use it for power.
To build:
make build
This will delete everything in the build folder, redo the CMake step and build the binary. It is set up to always build "main" and later flash that binary. The name of the binary will be "main.___" with the file extension identifying the type. The two you care about most are:
- build/main.elf - the file that gets installed by the 'make flash' tool (see below)
- build/main.uf2 - the file you can manually copy to the Pico using the 'bootsel' method (see below)
The makefile supports direct flashing of the pico (make flash) - see the script "flash" to see what it does
make flash
You can also install the binary on the pico using it's native bootloader:
- Unplug the USB from the Pico
- Press and hold the "bootsel" button on the Pico
- Plug in the USB
- Wait for a beep. Optionally verify the Pico was mounted by using the command line "df" tool
- Copy the build/main.uf2 file to the mounted Pico - on Ubuntu it's mounted at /media/$USER/RPI-RP2
- Once copied the Pico will reboot and run your program
The makefile supports direct reset of the pico (make reset) - see the script "reset" to see what it does. This script will reset BOTH cores, so be sure that's what you want.
make reset
The default cmake configuration (see above) will output serial from the Pico over UART via the PicoProbe. I generally use the UART. This makes it easy to just keep a serial connection open in a window at all times.
You can use any serial program you like, but "screen" is super easy:
screen /dev/ttyACM0 115200
The PicoProbe will use /dev/ttyACM0 for the flashing over Serial Wire Debug (SWD) but ACM1 should be your USB or UART. NOTE: I've seen these get backwards, so pay attention. OpenOCD just figures out what port to use, but if you get nothing on serial you may have the wrong port specified.
This is another reason to just use the PicoProbe for serial. One less thing to think about!
If you didn't use the setup script and manually installed the SDK you MUST follow the instructions exactly. If you miss the " git submodule update --init" step then it won't include the TinyUSB library and you will NEVER get any output over serial. It just fails silently.
I've seen a bunch of cases where my code has resulted in an image on the Pico that turns it into a completely unresonsive paperweight. Can't flash it using the PicoProbe, no serial, etc.
DON'T WORRY!
Go back and make one of the exampes - perhap "blinky" - but any known good working example. Use the manual flashing method described above to install that image on the Pico. Reboot. You have now recovered the Pico.
I'm planning on really understanding how the bootloader part works, but for now, this recovery method will save you WHEN you bork your Pico with bad code. :)
The official example repo is useful to look at, and to run the examples if you want to make sure you are set up. However, they are really crap if you want to use them as a template to work from. This is because none of the examples can be used standalone. To build them you must build the whole tree, since they use parent directory CMakeLists.txt files. This flaw is a major reason I wrote this template!
These two books are really best in print since the author does not publish them to Amazon in ePub. So there's no indexing or jumping using the ToC.
- Programming The Raspberry Pi Pico/W In C, Second Edition
- Master the Raspberry Pi Pico in C: WiFi with lwIP & mbedtls
These are both really useful as a starting point and really pretty critical if you want to try to figure out how WiFi works on the Pico W.
- Making Embedded Systems: Design Patterns for Great Software - Elicia White is amazing. MUST read.
- Applied Embedded Electronics: Design Essentials for Robust Systems - lessons for the REAL WORLD
V. Hunter Adams of Cornell has put his entire course online:
NOTE: these all assume you have the rig they have in their lab, and that you use the examples right out of their repo. They use an internally hacked/derived version of a threading library that makes it harder to just lift code examples out, but all of this material is amazing as a learning resource. And damn, I wish all teachers were like Hunter Adams! He's astounding. Should win professor of the year!
By the way, the threading library they use is not on GitHub, so I put it there in hope of adding it to my toolbox and making it useful. Protothreads is here, with full credit to it's actual author Adam Dunkels. The email address in his code now bounces so if you have a better one please contact me!
I derived this repo from the excellent work done ealier here