Pico development environment

Thursday, 4 June 2026

Introduction

A while back I bought a Rasberry Pico W1 as I was intrigued about having something that I could possibly run a small web server on. I started off using MicroPython2, had a bit of a ‘play’ then left it alone for a while. Returning to it, I also found out about using C/C++3 on the Pico (languages I have programmed in the past). Then a friend started to use Arduino’s4 for a project involving serial communication, but already having a Pico I had a play with the IDE5 using a board manager by Earle F. Philhower, III6. This made me think more about the process of development for the pico and debugging what I created. Having used debuggers before I wanted something interactive (using an IDE) that just ‘worked’. Researching more, the hardware debuggers for the Arduino and its IDE7 I found that they’re expensive both the actual boards and the debugger board. Instead I turned to the Raspberry Pi3 documentation and did some more reading. This proved to be a bit more complicated but it seems that the Debug Probe8 makes it easier. So I went ahead and bought one, along with the original Pico so I could practice board to board serial commnunication.

Following the instructions to install on Windows seems fine, but it seems to come with a bit of hassle and I didn’t want to mess my PC around too much, so then bought an 8GB ‘Pi 5’9 as the toolchain / setup appears to be easier and if I made a mistake then I could always reflash the SD card. Wanting to understand the whole build process I wanted to use both the command line / text editor and the main supported IDE, being Visual Studio Code10 and the Raspberry Pi Pico VS Code Extension1112. And that’s what I’m covering here.

Disclaimers

If replicating what I’ve done then be sure of what you’re doing, as information presented in this article is written according to my own understanding, there could be inaccuracies, so please do undertake your own research. All sources of information that are not my own have been cited / referenced. All screen shots taken on the ‘Pi 5’9. Images / video are my own.

Build process overview

The process has the general workflow of:

  1. Design / update the hardware solution.
  2. Create / update the build structure.
  3. Create / update the code.
  4. Create / modify the build script. CMake13 is used for this.
  5. Turn the CMake build script into a form form that ‘make’14 that uses to turn your code into a binary for the Pico.
  6. Deploy the binary to the Pico by copying the binary to the Pico when it is in ‘BOOTSEL’ mode or use the ‘picotool’15, which can be used with and without the Pico being in ‘BOOTSEL’ mode, or you can even use the Debug Probe’s Serial Wire Debug (SWD) interface16, being ‘CMSIS-DAP’17.
  7. Run / debug.

The Rasberry Pi Pico documentation is excellent and goes into a lot of detail. Giving lots of ways of doing things, such as to debug with the command line, and what not to do, for instance the connection sequence when using two computers18. I’ll describe what I do, but you can have / develop your own style / way.

Hardware solution

Without repeating what is already available, such as ‘Board layouts’19, to connect up the solution, I’ve gone for:

Pico with LED’s

where four LED’s (quite bright) are connected to pins 24 to 27 with 1K Ω resistors (so that the LED’s are less bright). A better solution as I’m told by a friend is to use Pulse Width Modulation20, such as 21, but that is perhaps for another time. The ‘Debug Probe’8 is also shown connected to the SWD16 pins and the TX, RX and GND UART22 serial pins of 1, 2 and 3 respectively.

Build structure

I’ve gone for:

.
|-- build
|-- CMakeLists.txt
`-- src
    `-- sourcefile.c

where we create the binary(s) in the ‘build’ folder, keeping the source code separate from the build script.

Code

The code I’ve written to be used with the hardware setup is:

test.c

 1/*
 2 * Copyright (c) 2026 G J Barnard.
 3 *
 4 * SPDX-License-Identifier: BSD-3-Clause
 5 */
 6
 7#include <stdint.h>
 8#include "pico/stdlib.h"
 9/* #include "hardware/gpio.h" - Included in pico/stdlib.h. */
10#include "pico/printf.h"
11
12/* Constants */
13const uint8_t FLASH_BASE = 255;
14const uint16_t FLASH_PAUSE = 768;
15const uint8_t FLASH_INC = 16;
16const uint8_t LED_PIN = 20; /* Connect an LED to GP20 / Pin 26 as common for both Pico, Pico W, Pico 2 and Pico 2 W. */
17
18/**
19 * Initialise.
20 */
21void init() {
22    stdio_init_all();
23
24    printf("init()\n");
25
26    gpio_init(LED_PIN);
27    gpio_set_dir(LED_PIN, GPIO_OUT);
28}
29
30/**
31 * Run.
32 */
33void run() {
34    uint8_t flashCount = 0;
35
36    sleep_ms(FLASH_PAUSE);
37
38    while (true) {
39        gpio_put(LED_PIN, 0);
40
41        sleep_ms(FLASH_BASE + flashCount);
42
43        gpio_put(LED_PIN, 1);
44
45        printf("Count was %d", flashCount);
46        flashCount += FLASH_INC;
47        printf(", and is now %d.\n", flashCount);
48
49        sleep_ms(FLASH_BASE + (flashCount * 3));
50    }
51}
52
53/**
54 * Main.
55 */
56void main() {
57    init();
58    run();
59}

This flashes the LED connected to pin 26 at an incremental flash rate until the ‘flashCount’ unsigned byte overflows causing in effect a reset to the initial state.

There is also a more advanced program:

nibble.c

  1/*
  2 * Copyright (c) 2026 G J Barnard.
  3 *
  4 * SPDX-License-Identifier: BSD-3-Clause
  5 */
  6
  7#include <stdint.h>
  8#include "pico/stdlib.h"
  9/* #include "hardware/gpio.h" - Included in pico/stdlib.h. */
 10#include "pico/printf.h"
 11
 12/* Constants */
 13const uint16_t FLASH_PAUSE = 1024;
 14const uint8_t FLASH_INTERMISSION = 255;
 15const uint8_t FLASH_FLASH = 128;
 16const uint8_t FLASH_INC = 1;
 17
 18/* GPs / Pins common for both Pico, Pico W, Pico 2 and Pico 2 W. */
 19const uint8_t LED_8_PIN = 21; /* GP21 / Pin 27 */
 20const uint8_t LED_4_PIN = 20; /* GP20 / Pin 26 */
 21const uint8_t LED_2_PIN = 19; /* GP19 / Pin 25 */
 22const uint8_t LED_1_PIN = 18; /* GP18 / Pin 24 */
 23
 24const uint8_t FIFTEEN = 15;
 25const uint8_t EIGHT = 8;
 26const uint8_t FOUR = 4;
 27const uint8_t TWO = 2;
 28const uint8_t ONE = 1;
 29
 30/**
 31 * Initialise.
 32 */
 33void init() {
 34    stdio_init_all();
 35
 36    printf("init()\n");
 37
 38    gpio_init(LED_8_PIN);
 39    gpio_set_dir(LED_8_PIN, GPIO_OUT);
 40    gpio_init(LED_4_PIN);
 41    gpio_set_dir(LED_4_PIN, GPIO_OUT);
 42    gpio_init(LED_2_PIN);
 43    gpio_set_dir(LED_2_PIN, GPIO_OUT);
 44    gpio_init(LED_1_PIN);
 45    gpio_set_dir(LED_1_PIN, GPIO_OUT);
 46}
 47
 48/**
 49 * Show.
 50 */
 51void show(uint8_t show) {
 52    gpio_put(LED_8_PIN, (show & EIGHT));
 53    gpio_put(LED_4_PIN, (show & FOUR));
 54    gpio_put(LED_2_PIN, (show & TWO));
 55    gpio_put(LED_1_PIN, (show & ONE));
 56}
 57
 58/**
 59 * Intermission.
 60 */
 61void intermission() {
 62    const uint8_t sequence[] = {1, 2, 4, 8, 4, 2, 1};
 63    uint8_t display;
 64
 65    for (display = 0; display < 7; display++) {
 66        show(sequence[display]);
 67        sleep_ms(FLASH_INTERMISSION);
 68    }
 69
 70    for (display = 0; display < 2; display++) {
 71        show(0);
 72        sleep_ms(FLASH_FLASH);
 73        show(FIFTEEN);
 74        sleep_ms(FLASH_FLASH);
 75    }
 76}
 77
 78/**
 79 * Run.
 80 */
 81void run() {
 82    uint8_t flashCount = 0;
 83    uint8_t showing = 0;
 84
 85    sleep_ms(FLASH_PAUSE);
 86
 87    while (true) {
 88        printf("Count was %d", flashCount);
 89        flashCount += FLASH_INC;
 90        printf(", and is now %d, showing %d.\n", flashCount, showing);
 91
 92        show(flashCount);
 93
 94        sleep_ms(FLASH_PAUSE);
 95
 96        if ((flashCount & FIFTEEN) == FIFTEEN) {
 97            intermission();
 98        }
 99    }
100}
101
102/**
103 * Main.
104 */
105void main() {
106    init();
107    run();
108}

This takes things further to demonstrate binary numbers from zero to fifteen using four LED’s. When ‘flashCount’ reaches fifteen, then this is detected and a swipe left / right and quick flash twice is shown before resetting back to zero.

In both cases I’ve made comments about inclusions I think should be there but are not needed because they are already included by the respective includes.

Build script

The build script we need as starting point is, adapted from ‘Getting started with Raspberry Pi Pico-series’23 and ‘Raspberry Pi Pico and RP2040 - C/C++ Part 1: Blink and VS Code’24:

CMakeLists.txt

 1cmake_minimum_required(VERSION 3.13)
 2
 3# Use below when using the Pico VS Code Extension.  Seems to copy 'pico_sdk_import.cmake' when importing the folder.
 4include(pico_sdk_import.cmake)
 5# Uncomment below for 'Manual' and comment out above:
 6#include($ENV{PICO_SDK_PATH}/external/pico_sdk_import.cmake)
 7
 8project(test C CXX ASM)
 9
10set(CMAKE_C_STANDARD 17)
11set(CMAKE_CXX_STANDARD 14)
12
13pico_sdk_init()
14
15# Change as required.
16add_executable(${PROJECT_NAME}
17    src/sourcefile.c
18)
19
20pico_enable_stdio_usb(${PROJECT_NAME} 1)
21pico_enable_stdio_uart(${PROJECT_NAME} 1)
22
23pico_add_uf2_output(${PROJECT_NAME})
24# Have if debugging.
25pico_add_dis_output(${PROJECT_NAME})
26
27target_link_libraries(${PROJECT_NAME} pico_stdlib)

Build, deploy and debug

Two methods

After a bit of thought, the ‘manual’ method does use the IDE10 as the editor even though a text editor such as ’nano’25 could be used.

If you want to compare both on the same machine, then you can use ‘VS Code Profiles’26. This is recommended under ‘VS Code Profiles’ on the ‘Raspberry Pi Pico’ extension12 page. There is also a ‘ sample profile ’ that you can use and it will install the extensions for you. I’m going to install them manually.

Pico VS Code Extension

This is “The official VS Code extension for Raspberry Pi Pico development. It includes several features to simplify project creation and deployment. (Single folder workspaces only)”12. Therefore I anticipate it to be easy to use but with some limitations.

My intent here is not to explain how to use the extension and repeat what is already available in the references I’ve stated, but more to demonstrate it with the code I’ve written within the setup I’ve chosen.

Setup

Please follow ‘Getting started with Raspberry Pi Pico-series’23, ‘Chapter 2. Install Visual Studio Code’ and ‘Chapter 3. Install the Raspberry Pi Pico VS Code Extension’12 - type ‘code’. When complete, close VS Code.

Setup the build structure:

.
|-- build
|-- CMakeLists.txt
`-- src
    `-- test.c

and then run ‘code .’ within the main folder, where ‘CMakeLists.txt’ lives, and click on the Pico extension view icon:

Pico extension view icon

This will cause a notitification to import the project:

Import project

After clicking on ‘Yes’, go with the defaults but change as required / if you need to before clicking on ‘Import’:

Import project two

Let things install:

Import sdk

Import toolchain

etc. Then click on the ‘Extensions’ view icon:

Extensions view icon

A notification will pop-up:

Extensions recommendation notification

Click on ‘Show recommendtations’, then choose the ones you want, I’ve gone for all of the additional ones:

Extensions recommended

But decided at this stage not to install ‘CMake Tools’ as thought it might duplicate functionality.

Clicking on the ‘Explorer’ view, we find that:

  1. The file ‘pico_sdk_import.cmake’ has been added to our project.
  2. There are configuration files in a new ‘.vscode’ folder.
  3. One of these configuraton files is ‘c_cpp_properties.json’ where we can check the versions for the ‘C’ and ‘C++’ standard:

Explorer .vscode

Then see if they are the same in our ‘CMakeLists.txt’ file:

Explorer .vscode

As those are the standard versions the extension thinks we need to be using. However if we had a program that required different versions then we’d have to resolve that.

I’ve changed my mind and opted to accept the recommendation to actually install the ‘CMake Tools’ extension as that will add syntax highlighting. The ‘C/C++’ extension will add syntax highlighting, ‘IntelliSense’ for us and the already mentioned ‘c_cpp_properties.json’ file configures it. However something is not correct as ‘uint_8t’ and ‘uint_16t’ are not found in ‘test.c’:

IntelliSense uint_8t and uint_16t not found

despite ‘#include <stdint.h>’. After a bit of detective work, where the manual method does not need the following fix despite having the same version of the compiler, then add:

1"${userHome}/.pico-sdk/toolchain/14_2_Rel1/lib/gcc/arm-none-eabi/14.2.1/include/stdint-gcc.h"

to the ‘"forcedInclude"’ section in the ‘c_cpp_properties.json’ file, i.e:

1            "forcedInclude": [
2                "${workspaceFolder}/build/generated/pico_base/pico/config_autogen.h",
3                "${userHome}/.pico-sdk/sdk/2.2.0/src/common/pico_base_headers/include/pico.h",
4                "${userHome}/.pico-sdk/toolchain/14_2_Rel1/lib/gcc/arm-none-eabi/14.2.1/include/stdint-gcc.h"
5            ],

There is also errors about ‘compile_commands.json’ and ‘config_autogen.h’ which I’m not sure about, so I investigated first and found ‘How to generate a JSON compile commands database with CMake’27 and ‘Compile commands’28. Adding ‘set(CMAKE_EXPORT_COMPILE_COMMANDS ON)’ to ‘CMakeLists.txt’:

1...
2set(CMAKE_C_STANDARD 17)
3set(CMAKE_CXX_STANDARD 14)
4set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
5...

Then running ‘Configure CMake’ in the Pico extension:

Configure CMake

generated the ‘compile_commands.json’ file, and with ‘config_autogen.h’ being in the ‘build’ folder it was created too. This then got me thinking about what Josh said on ‘How to generate a JSON compile commands database with CMake’27 causing issues with the editor, i.e. for me ‘IntelliSence’, so I removed:

1"${userHome}/.pico-sdk/toolchain/14_2_Rel1/lib/gcc/arm-none-eabi/14.2.1/include/stdint-gcc.h"

from the ‘c_cpp_properties.json’ file and now after a restart of VS Code, it no longer complains about ‘uint_8t’ and ‘uint_16t’. Clearly more to learn about what is going on between what the editor can see and what the compliler can.

Running

To run, simply select ‘Run Project(USB)’ in the Pico extension, with ‘Switch Build Type’ set at ‘Release’ and the extension will do the rest for you.

Debugging

Change ‘Switch Build Type’ to ‘Debug’, set an appropriate breakpoint, e.g. line 48, and click on ‘Debug Project’, selecting in this case ‘Pico Debug’:

Setup debugging

Initially the first breakpoint will be ‘main’, then after clicking on the ‘Continue’ icon or pressing ‘F5’, we can debug and inspect the variable ‘flashCount’:

Debugging

Practice

Now perhaps you have a go yourself and also try debugging ‘nibble.c’:

Debugging Nibble.c

In doing so I’ve noticed that even though ‘flashCount’ is declared as a single unsigned byte, then it is stored in a 64 bit register as a four byte integer:

Debugging Nibble.c 64 bit

Perhaps it will be possible to find out where the value is in memory when it’s not needed.

Manual with VSCode debug

Follow ‘Getting started with Raspberry Pi Pico-series’23, ‘Appendix C: Manual toolchain setup’, ‘Configure your environment via Script’ to run the script ‘pico_setup.sh’. This is because I found it a bit problematic creating OpenOCD29 and looked at the script to see what I was doing wrong, but still didn’t get it working so have deferred for another day. This is on the principle that the people whom wrote the script should know what they are doing and its there to learn from.

Note: If you’ve already installed the Pico VS Code Extension, then there is no need to do ‘sudo raspi-config’ for the serial and reboot as this has already been done.

Setup

The setup script should add the following environment variables to ‘.bashrc30, where I’ve run the script in my home folder:

1export PICO_SDK_PATH=/home/gareth/pico/pico-sdk
2export PICO_EXAMPLES_PATH=/home/gareth/pico/pico-examples
3export PICO_EXTRAS_PATH=/home/gareth/pico/pico-extras
4export PICO_PLAYGROUND_PATH=/home/gareth/pico/pico-playground

If they’re not showing up when you type ‘env’, then type source .bashrc.

I decided for convienince to add:

1export PICO_OPENOCD_PATH=/home/gareth/pico/openocd

for debugging.

Now we can build manually, so in a new folder called ‘build’ within where we have our CMakeLists.txt file - I’ve created a new folder ‘test_manual’ for this with the structure:

.
|-- build
|-- CMakeLists.txt
`-- src
    `-- test.c

In the ‘build’ folder type:

1cmake .. -D CMAKE_BUILD_TYPE=Debug -D PICO_BOARD=pico

Where you change -D CMAKE_BUILD_TYPE to Release or MinSizeRel as per ‘ CMAKE_BUILD_TYPE ’ if wanted. And set -D PICO_BOARD (‘7.1. Full List of SDK Configuration Variables’31’) to your board.

This will produce something like:

 1Using PICO_SDK_PATH from environment ('/home/gareth/pico/pico-sdk')
 2PICO_SDK_PATH is /home/gareth/pico/pico-sdk
 3Target board (PICO_BOARD) is 'pico'.
 4Using board configuration from /home/gareth/pico/pico-sdk/src/boards/include/boards/pico.h
 5Defaulting platform (PICO_PLATFORM) to 'rp2040' based on PICO_BOARD setting.
 6Defaulting compiler (PICO_COMPILER) to 'pico_arm_cortex_m0plus_gcc' since not specified.
 7Configuring toolchain based on PICO_COMPILER 'pico_arm_cortex_m0plus_gcc'
 8Defaulting PICO_GCC_TRIPLE to 'arm-none-eabi'
 9-- The C compiler identification is GNU 14.2.1
10-- The CXX compiler identification is GNU 14.2.1
11-- The ASM compiler identification is GNU
12-- Found assembler: /usr/bin/arm-none-eabi-gcc
13-- Detecting C compiler ABI info
14-- Detecting C compiler ABI info - done
15-- Check for working C compiler: /usr/bin/arm-none-eabi-gcc - skipped
16-- Detecting C compile features
17-- Detecting C compile features - done
18-- Detecting CXX compiler ABI info
19-- Detecting CXX compiler ABI info - done
20-- Check for working CXX compiler: /usr/bin/arm-none-eabi-g++ - skipped
21-- Detecting CXX compile features
22-- Detecting CXX compile features - done
23Build type is Debug
24Using regular optimized debug build (set PICO_DEOPTIMIZED_DEBUG=1 to de-optimize)
25-- Found Python3: /usr/bin/python3 (found version "3.13.5") found components: Interpreter
26TinyUSB available at /home/gareth/pico/pico-sdk/lib/tinyusb/hw/bsp/rp2040; enabling build support for USB.
27Compiling TinyUSB with CFG_TUSB_DEBUG=1
28BTstack available at /home/gareth/pico/pico-sdk/lib/btstack
29cyw43-driver available at /home/gareth/pico/pico-sdk/lib/cyw43-driver
30mbedtls available at /home/gareth/pico/pico-sdk/lib/mbedtls
31lwIP available at /home/gareth/pico/pico-sdk/lib/lwip
32Using PICO_EXAMPLES_PATH from environment ('/home/gareth/pico/pico-examples')
33Using picotool from /usr/local/bin/picotool
34-- Configuring done (1.6s)
35-- Generating done (0.1s)
36-- Build files have been written to: /home/gareth/code/test_manual/build

This creates the ‘Makefile’ and everything ‘make’ needs (and other options like cleaning the build) to make your executable.

Then build with (the Pico 5 has four cores, so -j4):

1make -j4

This will produce something like:

 1[  1%] Building ASM object pico-sdk/src/rp2040/boot_stage2/CMakeFiles/bs2_default.dir/compile_time_choice.S.o
 2[  2%] Linking ASM executable bs2_default.elf
 3[  2%] Built target bs2_default
 4[  3%] Generating bs2_default.bin
 5[  4%] Generating bs2_default_padded_checksummed.S
 6[  5%] Building ASM object pico-sdk/src/rp2040/boot_stage2/CMakeFiles/bs2_default_library.dir/bs2_default_padded_checksummed.S.o
 7[  5%] Built target bs2_default_library
 8[ 10%] Building C object CMakeFiles/test.dir/src/test.c.o
 9[ 10%] Building C object CMakeFiles/test.dir/home/gareth/pico/pico-sdk/src/rp2_common/pico_stdlib/stdlib.c.o
10[ 10%] Building C object CMakeFiles/test.dir/home/gareth/pico/pico-sdk/src/rp2_common/hardware_gpio/gpio.c.o
11[ 10%] Building C object CMakeFiles/test.dir/home/gareth/pico/pico-sdk/src/rp2040/pico_platform/platform.c.o
12[ 11%] Building C object CMakeFiles/test.dir/home/gareth/pico/pico-sdk/src/rp2_common/pico_platform_common/common.c.o
13[ 12%] Building C object CMakeFiles/test.dir/home/gareth/pico/pico-sdk/src/rp2_common/pico_platform_panic/panic.c.o
14[ 13%] Building C object CMakeFiles/test.dir/home/gareth/pico/pico-sdk/src/common/hardware_claim/claim.c.o
15[ 14%] Building C object CMakeFiles/test.dir/home/gareth/pico/pico-sdk/src/rp2_common/hardware_sync/sync.c.o
16[ 15%] Building C object CMakeFiles/test.dir/home/gareth/pico/pico-sdk/src/rp2_common/hardware_sync_spin_lock/sync_spin_lock.c.o
17[ 17%] Building C object CMakeFiles/test.dir/home/gareth/pico/pico-sdk/src/rp2_common/hardware_irq/irq.c.o
18[ 17%] Building ASM object CMakeFiles/test.dir/home/gareth/pico/pico-sdk/src/rp2_common/hardware_irq/irq_handler_chain.S.o
19[ 19%] Building C object CMakeFiles/test.dir/home/gareth/pico/pico-sdk/src/common/pico_sync/sem.c.o
20[ 20%] Building C object CMakeFiles/test.dir/home/gareth/pico/pico-sdk/src/common/pico_sync/lock_core.c.o
21[ 21%] Building C object CMakeFiles/test.dir/home/gareth/pico/pico-sdk/src/common/pico_sync/mutex.c.o
22[ 22%] Building C object CMakeFiles/test.dir/home/gareth/pico/pico-sdk/src/common/pico_sync/critical_section.c.o
23[ 23%] Building C object CMakeFiles/test.dir/home/gareth/pico/pico-sdk/src/common/pico_time/time.c.o
24[ 24%] Building C object CMakeFiles/test.dir/home/gareth/pico/pico-sdk/src/common/pico_time/timeout_helper.c.o
25[ 25%] Building C object CMakeFiles/test.dir/home/gareth/pico/pico-sdk/src/rp2_common/hardware_timer/timer.c.o
26[ 26%] Building C object CMakeFiles/test.dir/home/gareth/pico/pico-sdk/src/common/pico_util/datetime.c.o
27[ 28%] Building C object CMakeFiles/test.dir/home/gareth/pico/pico-sdk/src/common/pico_util/pheap.c.o
28[ 29%] Building C object CMakeFiles/test.dir/home/gareth/pico/pico-sdk/src/common/pico_util/queue.c.o
29[ 30%] Building C object CMakeFiles/test.dir/home/gareth/pico/pico-sdk/src/rp2_common/hardware_uart/uart.c.o
30[ 31%] Building C object CMakeFiles/test.dir/home/gareth/pico/pico-sdk/src/rp2_common/hardware_clocks/clocks.c.o
31[ 32%] Building C object CMakeFiles/test.dir/home/gareth/pico/pico-sdk/src/rp2_common/hardware_pll/pll.c.o
32[ 34%] Building C object CMakeFiles/test.dir/home/gareth/pico/pico-sdk/src/rp2_common/hardware_watchdog/watchdog.c.o
33[ 34%] Building C object CMakeFiles/test.dir/home/gareth/pico/pico-sdk/src/rp2_common/hardware_vreg/vreg.c.o
34[ 35%] Building C object CMakeFiles/test.dir/home/gareth/pico/pico-sdk/src/rp2_common/hardware_ticks/ticks.c.o
35[ 37%] Building C object CMakeFiles/test.dir/home/gareth/pico/pico-sdk/src/rp2_common/pico_bootrom/bootrom.c.o
36[ 38%] Building C object CMakeFiles/test.dir/home/gareth/pico/pico-sdk/src/rp2_common/pico_bootrom/bootrom_lock.c.o
37[ 39%] Building C object CMakeFiles/test.dir/home/gareth/pico/pico-sdk/src/rp2_common/hardware_boot_lock/boot_lock.c.o
38[ 40%] Building C object CMakeFiles/test.dir/home/gareth/pico/pico-sdk/src/rp2_common/pico_flash/flash.c.o
39[ 41%] Building ASM object CMakeFiles/test.dir/home/gareth/pico/pico-sdk/src/rp2_common/hardware_divider/divider.S.o
40[ 42%] Building C object CMakeFiles/test.dir/home/gareth/pico/pico-sdk/src/rp2_common/hardware_xosc/xosc.c.o
41[ 43%] Building C object CMakeFiles/test.dir/home/gareth/pico/pico-sdk/src/rp2_common/pico_runtime/runtime.c.o
42[ 46%] Building C object CMakeFiles/test.dir/home/gareth/pico/pico-sdk/src/rp2_common/pico_runtime_init/runtime_init.c.o
43[ 46%] Building C object CMakeFiles/test.dir/home/gareth/pico/pico-sdk/src/rp2_common/pico_runtime_init/runtime_init_clocks.c.o
44[ 47%] Building C object CMakeFiles/test.dir/home/gareth/pico/pico-sdk/src/rp2_common/pico_runtime_init/runtime_init_stack_guard.c.o
45[ 48%] Building ASM object CMakeFiles/test.dir/home/gareth/pico/pico-sdk/src/rp2_common/pico_bit_ops/bit_ops_aeabi.S.o
46[ 49%] Building ASM object CMakeFiles/test.dir/home/gareth/pico/pico-sdk/src/rp2_common/pico_divider/divider_hardware.S.o
47[ 50%] Building C object CMakeFiles/test.dir/home/gareth/pico/pico-sdk/src/rp2_common/pico_double/double_init_rom_rp2040.c.o
48[ 51%] Building ASM object CMakeFiles/test.dir/home/gareth/pico/pico-sdk/src/rp2_common/pico_double/double_aeabi_rp2040.S.o
49[ 52%] Building C object CMakeFiles/test.dir/home/gareth/pico/pico-sdk/src/rp2_common/pico_double/double_math.c.o
50[ 53%] Building ASM object CMakeFiles/test.dir/home/gareth/pico/pico-sdk/src/rp2_common/pico_double/double_v1_rom_shim_rp2040.S.o
51[ 55%] Building ASM object CMakeFiles/test.dir/home/gareth/pico/pico-sdk/src/rp2_common/pico_int64_ops/pico_int64_ops_aeabi.S.o
52[ 56%] Building ASM object CMakeFiles/test.dir/home/gareth/pico/pico-sdk/src/rp2_common/pico_float/float_aeabi_rp2040.S.o
53[ 57%] Building C object CMakeFiles/test.dir/home/gareth/pico/pico-sdk/src/rp2_common/pico_float/float_init_rom_rp2040.c.o
54[ 58%] Building C object CMakeFiles/test.dir/home/gareth/pico/pico-sdk/src/rp2_common/pico_float/float_math.c.o
55[ 59%] Building ASM object CMakeFiles/test.dir/home/gareth/pico/pico-sdk/src/rp2_common/pico_float/float_v1_rom_shim_rp2040.S.o
56[ 60%] Building C object CMakeFiles/test.dir/home/gareth/pico/pico-sdk/src/rp2_common/pico_malloc/malloc.c.o
57[ 61%] Building ASM object CMakeFiles/test.dir/home/gareth/pico/pico-sdk/src/rp2_common/pico_mem_ops/mem_ops_aeabi.S.o
58[ 62%] Building C object CMakeFiles/test.dir/home/gareth/pico/pico-sdk/src/rp2_common/pico_atomic/atomic.c.o
59[ 64%] Building CXX object CMakeFiles/test.dir/home/gareth/pico/pico-sdk/src/rp2_common/pico_cxx_options/new_delete.cpp.o
60[ 65%] Building C object CMakeFiles/test.dir/home/gareth/pico/pico-sdk/src/rp2_common/pico_standard_binary_info/standard_binary_info.c.o
61[ 66%] Building C object CMakeFiles/test.dir/home/gareth/pico/pico-sdk/src/rp2_common/pico_printf/printf.c.o
62[ 68%] Building C object CMakeFiles/test.dir/home/gareth/pico/pico-sdk/src/rp2_common/pico_clib_interface/newlib_interface.c.o
63[ 68%] Building ASM object CMakeFiles/test.dir/home/gareth/pico/pico-sdk/src/rp2_common/pico_crt0/crt0.S.o
64[ 69%] Building C object CMakeFiles/test.dir/home/gareth/pico/pico-sdk/src/rp2_common/pico_stdio/stdio.c.o
65[ 70%] Building C object CMakeFiles/test.dir/home/gareth/pico/pico-sdk/src/rp2_common/pico_stdio_uart/stdio_uart.c.o
66[ 71%] Building C object CMakeFiles/test.dir/home/gareth/pico/pico-sdk/src/rp2_common/pico_stdio_usb/reset_interface.c.o
67[ 73%] Building C object CMakeFiles/test.dir/home/gareth/pico/pico-sdk/src/rp2_common/pico_stdio_usb/stdio_usb.c.o
68[ 74%] Building C object CMakeFiles/test.dir/home/gareth/pico/pico-sdk/src/rp2_common/pico_stdio_usb/stdio_usb_descriptors.c.o
69[ 75%] Building C object CMakeFiles/test.dir/home/gareth/pico/pico-sdk/src/rp2_common/pico_unique_id/unique_id.c.o
70[ 76%] Building C object CMakeFiles/test.dir/home/gareth/pico/pico-sdk/src/rp2_common/hardware_flash/flash.c.o
71[ 77%] Building C object CMakeFiles/test.dir/home/gareth/pico/pico-sdk/src/rp2_common/hardware_xip_cache/xip_cache.c.o
72[ 78%] Building C object CMakeFiles/test.dir/home/gareth/pico/pico-sdk/lib/tinyusb/src/portable/raspberrypi/rp2040/dcd_rp2040.c.o
73[ 79%] Building C object CMakeFiles/test.dir/home/gareth/pico/pico-sdk/lib/tinyusb/src/portable/raspberrypi/rp2040/rp2040_usb.c.o
74[ 80%] Building C object CMakeFiles/test.dir/home/gareth/pico/pico-sdk/lib/tinyusb/src/device/usbd.c.o
75[ 82%] Building C object CMakeFiles/test.dir/home/gareth/pico/pico-sdk/lib/tinyusb/src/device/usbd_control.c.o
76[ 84%] Building C object CMakeFiles/test.dir/home/gareth/pico/pico-sdk/lib/tinyusb/src/class/audio/audio_device.c.o
77[ 84%] Building C object CMakeFiles/test.dir/home/gareth/pico/pico-sdk/lib/tinyusb/src/class/cdc/cdc_device.c.o
78[ 85%] Building C object CMakeFiles/test.dir/home/gareth/pico/pico-sdk/lib/tinyusb/src/class/dfu/dfu_device.c.o
79[ 86%] Building C object CMakeFiles/test.dir/home/gareth/pico/pico-sdk/lib/tinyusb/src/class/hid/hid_device.c.o
80[ 87%] Building C object CMakeFiles/test.dir/home/gareth/pico/pico-sdk/lib/tinyusb/src/class/dfu/dfu_rt_device.c.o
81[ 88%] Building C object CMakeFiles/test.dir/home/gareth/pico/pico-sdk/lib/tinyusb/src/class/midi/midi_device.c.o
82[ 89%] Building C object CMakeFiles/test.dir/home/gareth/pico/pico-sdk/lib/tinyusb/src/class/msc/msc_device.c.o
83[ 91%] Building C object CMakeFiles/test.dir/home/gareth/pico/pico-sdk/lib/tinyusb/src/class/net/ecm_rndis_device.c.o
84[ 92%] Building C object CMakeFiles/test.dir/home/gareth/pico/pico-sdk/lib/tinyusb/src/class/usbtmc/usbtmc_device.c.o
85[ 93%] Building C object CMakeFiles/test.dir/home/gareth/pico/pico-sdk/lib/tinyusb/src/class/net/ncm_device.c.o
86[ 94%] Building C object CMakeFiles/test.dir/home/gareth/pico/pico-sdk/lib/tinyusb/src/class/vendor/vendor_device.c.o
87[ 96%] Building C object CMakeFiles/test.dir/home/gareth/pico/pico-sdk/lib/tinyusb/src/class/video/video_device.c.o
88[ 96%] Building C object CMakeFiles/test.dir/home/gareth/pico/pico-sdk/lib/tinyusb/src/tusb.c.o
89[ 97%] Building C object CMakeFiles/test.dir/home/gareth/pico/pico-sdk/lib/tinyusb/src/common/tusb_fifo.c.o
90[ 98%] Building C object CMakeFiles/test.dir/home/gareth/pico/pico-sdk/src/rp2_common/pico_fix/rp2040_usb_device_enumeration/rp2040_usb_device_enumeration.c.o
91[100%] Linking CXX executable test.elf
92[100%] Built target test

Where all of the source files, C / C++ / Assembler are ‘compiled / assembled’ individually then ’linked’ together to produce the files:

1-rw-rw-r-- 1 gareth gareth 628661 Jun  1 11:02 test.dis
2-rwxrwxr-x 1 gareth gareth 554508 Jun  1 11:02 test.elf
3-rw-rw-r-- 1 gareth gareth  75264 Jun  1 11:02 test.uf2

Where the ‘uf2’ file is deployed to the Pico and the ‘dis’ and ’elf’ files are for debugging - I think with my current understanding.

What is interesting is the amount that the SDK provides to support the code. That you can see from the filenames the elements, such as ‘usb’ that are required to make the program work.

Running

You then forcably deploy using:

1picotool load test.uf2 -f

Giving:

1Tracking device serial number **************** for reboot
2The device was asked to reboot into BOOTSEL mode so the command can be executed.
3
4Loading into Flash:   [==============================]  100%
5
6The device was asked to reboot back into application mode.

To see the output on serial, type:

1sudo minicom -D /dev/ttyACM0 -b 115200

Where I’m assuming that there will be consistency in the device name.

Output on Minicom

Note: Ctrl-A brings up the commands, with ‘Z’ for help and ‘X’ for exit.

You can do something similar with ‘nibble.c’, and then get the result:

Debugging

Initially I won’t be using the command line for debugging, instead prefering to use a GUI for speed and availability of information. Having used VS Code for xDebug with PHP (xDebug), then I’ve followed: ‘Raspberry Pi Pico and RP2040 - C/C++ Part 1: Blink and VS Code’24 and ‘Raspberry Pi Pico and RP2040 - C/C++ Part 2 Debugging with VS Code’32 by Shawn Hymel as it has configuration that I’m familiar with.

Part two of Shawn’s guide mentions the ‘Cortex-Debug’33 and ‘C/C++ for Visual Studio Code’34 extensions, so install those as we’ll need them. Then in ‘.vscode’ folder in the ‘Configure VS Code’ section, you create two files. There are also examples in the SDK Pico examples folder: ‘ide/vscode35.

launch.json

 1{
 2    "version": "0.2.0",
 3    "configurations": [
 4        {
 5            "name": "Pico Debug",
 6            "cwd": "${workspaceRoot}",
 7            "executable": "${command:cmake.launchTargetPath}",
 8            "request": "launch",
 9            "type": "cortex-debug",
10            "servertype": "openocd",
11            "gdbPath" : "gdb-multiarch",
12            "device": "RP2040",
13            "configFiles": [
14                "interface/cmsis-dap.cfg",
15                "target/rp2040.cfg"
16            ],
17            "svdFile": "${env:PICO_SDK_PATH}/src/rp2040/hardware_regs/rp2040.svd",
18            "runToEntryPoint": "main",
19            "postRestartCommands": [
20                "break main",
21                "continue"
22            ],
23            "searchDir": ["${env:PICO_OPENOCD_PATH}/tcl"]
24        }
25    ]
26}

For ‘"gdbPath"’, instead of ‘arm-none-eabi-gdb’, I’ve changed it to ‘gdb’ as I found ‘arm-none-eabi-gdb’ in the toolchain that the Pico VS Code extension installed, however in ‘Appendix A: Debugprobe’, section ‘Debug with SWD’ of ‘Getting started with Raspberry Pi Pico-series’23 it states to use ‘gdb’ with the Pi (in effect), but the example has ‘gdb-multiarch36 so use what works for you. Note: ‘locate37 is really useful.

Note the use of ‘PICO_OPENOCD_PATH’ we setup earlier.

I’ve also changed ‘"configFiles"’ to have ‘"interface/cmsis-dap.cfg"’ instead of ‘"interface/picoprobe.cfg"’ as I’m using the Debug probe - But then can’t find ‘picoprobe.cfg’ and stated in ‘Debug with OpenOCD’ of ‘Getting started with Raspberry Pi Pico-series’23. Also ‘"runToMain": true’, changed to ‘"runToEntryPoint": "main"’. And made use of ‘PICO_OPENOCD_PATH’ with ‘"searchDir": ["${env:PICO_OPENOCD_PATH}/tcl"]’.

I found that the both Shawn’s and the Pico examples ‘settings.json’ files have ‘"cmake.statusbar.advanced"’ which VSCode reports as an invalid value, so after a bit of trial and error with the extension settings, I’ve come up with:

settings.json

 1{
 2    "cmake.options.statusBarVisibility": "hidden",
 3    "cmake.options.advanced": {
 4        "build": {
 5            "statusBarVisibility": "visible"
 6        },
 7        "buildTarget": {
 8            "statusBarVisibility": "hidden"
 9        },
10        "launch": {
11            "statusBarVisibility": "hidden"
12        },
13        "debug": {
14            "statusBarVisibility": "hidden"
15        }
16    }
17}

To configure the status bar.

As we’re using the IDE, then having ‘IntelliSense’ will help with readability of the code, please see ‘VS Code IntelliSense’.

We can now debug using the ‘Run and debug’ view with our setup after connecting up the ‘Debug Probe’8 and powering it and the Pico on:

Run and debug

Interactively debugging:

Interactive debug

Where the first break point of ‘main’ we set up in ‘launch.json’. To add extra breakpoints you can use the space to the left of the line numbers. You can find out more on ‘Debug code with Visual Studio Code’38

If you want to switch to ‘Run without debuging’ then the ‘Reset device’ will help with this:

Run without debugging

Reset device

VS Code IntelliSense

‘IntelliSense’39 allows us to quickly visualise issues as the IDE is aware of the syntax of the code. It also can to other things like code completion. To set this up for the ‘C/C++ for Visual Studio Code’34 extension I adapted what the ‘Pico extension’12 created:

c_cpp_properties.json

 1{
 2    "configurations": [
 3        {
 4            "name": "Pico",
 5            "includePath": [
 6                "${workspaceFolder}/**",
 7                "${PICO_SDK_PATH}/src/**"
 8            ],
 9            "defines": [],
10            "compilerPath": "/usr/bin/arm-none-eabi-gcc",
11            "cStandard": "c17",
12            "cppStandard": "gnu++14",
13            "intelliSenseMode": "linux-gcc-arm"
14        }
15    ],
16    "version": 4
17}

in the ‘.vscode’ folder, where I’ve adapted the values to be for the Pi and specific versions used for the Pico as were set in the ‘CMakeLists.txt file’.

Build thoughts

In the examples I’ve seen there is (along the lines of) ‘pico_add_extra_outputs(${PROJECT_NAME})’ in ‘CMakeLists.txt’. According to the ‘Raspberry Pi Pico-series C/C++ SDK’31, this ‘Perform picotool processing and add disassembly, hex, bin, map, and uf2 outputs for the target’, but I think that we don’t need all of them, even for debuging, only ‘disassembly’, being ‘dis’ and ‘elf’ for debuging and ‘uf2’ for putting on the Pico. Debugging seems to work without the others.

Another thing is that with the line ‘target_link_libraries(${PROJECT_NAME} pico_stdlib)’, there feels as if there needs to be pico_printf added as its not a part of pico_stdlib, see github.com/raspberrypi/pico-sdk/blob/master/src/rp2_common/pico_stdlib/CMakeLists.txt but the programs compile and link, so something correct is going on.

Conclusion

I had anticipated that the Pico extension would be easier and in some respects it is. However, currently for me it has oddities. For instance going from debugging to release and redeploying, that ‘Flash Project (SWD)’ has to be used. Perhaps its just me.

What is certain is that understanding both methods helps to provide a foundation upon which to have an enviroment where software can be developed and more functionality of the Pico can be explored, combined with a refresh of C and C++.

This I hope is a base upon which to learn more.

References


  1. Pico microcontroller boards↩︎

  2. MicroPython↩︎

  3. The C/C++ SDK↩︎ ↩︎

  4. Ardunio↩︎

  5. Ardunio IDE↩︎

  6. Ardunio-Pico↩︎

  7. Debugging with the Arduino IDE 2↩︎

  8. Raspberry Pi Debug Probe↩︎ ↩︎ ↩︎

  9. Raspberry Pi 5↩︎ ↩︎

  10. Visual Studio Code↩︎ ↩︎

  11. Boost Your Pico Projects with the new Pico VS Code Extension↩︎

  12. Raspberry Pi Pico Visual Studio Code extension↩︎ ↩︎ ↩︎ ↩︎ ↩︎

  13. CMake↩︎

  14. GNU Make↩︎

  15. Picotool↩︎

  16. The Engineer's Guide to SWD Debug Probes↩︎ ↩︎

  17. CMSIS-DAP↩︎

  18. Raspberry Pi Debug Probe - Getting started↩︎

  19. Pico Board Layouts↩︎

  20. Pulse-width modulation↩︎

  21. pwm_led_fade.c↩︎

  22. Universal asynchronous receiver-transmitter↩︎

  23. Getting started with Raspberry Pi Pico-series↩︎ ↩︎ ↩︎ ↩︎ ↩︎

  24. Raspberry Pi Pico and RP2040 - C/C++ Part 1: Blink and VS Code↩︎ ↩︎

  25. GNU Nano↩︎

  26. VS Code Profiles↩︎

  27. How to generate a JSON compile commands database with CMake ’. ↩︎ ↩︎

  28. Compile commands↩︎

  29. Open On-Chip Debugger↩︎

  30. What Is .bashrc in Linux? Configuration Explained↩︎

  31. Raspberry Pi Pico-series C/C++ SDK↩︎ ↩︎

  32. Raspberry Pi Pico and RP2040 - C/C++ Part 2 Debugging with VS Code↩︎

  33. Cortex Debug↩︎

  34. C/C++ for Visual Studio Code↩︎ ↩︎

  35. github.com/raspberrypi/pico-examples/tree/master/ide/vscode↩︎

  36. launch-probe-swd.json example↩︎

  37. locate Command in Linux: Find Files Quickly↩︎

  38. Debug code with Visual Studio Code↩︎

  39. IntelliSense↩︎