How to blink an LED with Raspberry Pi Pico in C

The new Raspberry Pi Pico is very different from a traditional Raspberry Pi. Pico is a microcontroller, rather than a microcomputer. Unlike a Raspberry Pi it’s a platform you develop for, not a platform you develop on.

Blinking the on-board LED
Blinking the onboard LED

But you still have choices if you want to develop for Pico, because there is both a C/C++ SDK and an official MicroPython port. Beyond that there are other options opening up, with a port of CircuitPython from Adafruit and the prospect of Arduino support, or even a Rust port.

Here I’m going to talk about how to get started with the C/C++ SDK, which lets you develop for Raspberry Pi Pico from your laptop or Raspberry Pi.

I’m going to assume you’re using a Raspberry Pi; after all, why wouldn’t you want to do that? But if you want to develop for Pico from your Windows or Mac laptop, you’ll find full instructions on how to do that in our Getting Started guide.

Blinking your first LED

When you’re writing software for hardware, the first program that gets run in a new programming environment is typically turning an LED on, off, and then on again. Learning how to blink an LED gets you halfway to anywhere. We’re going to go ahead and blink the onboard LED on Pico, which is connected to pin 25 of the RP2040 chip.

We’ve tried to make getting started with Raspberry Pi Pico as easy as possible. In fact, we’ve provided some pre-built binaries that you can just drag and drop onto your Raspberry Pi Pico to make sure everything is working even before you start writing your own code.

Go to the Getting Started page and click on the “Getting started with C/C++” tab, then the “Download UF2 file” button in the “Blink an LED” box.

Getting started with Raspberry Pi Pico

A file called blink.uf2 will be downloaded to your computer. Go grab your Raspberry Pi Pico board and a micro USB cable. Plug the cable into your Raspberry Pi or laptop, then press and hold the BOOTSEL button on your Pico while you plug the other end of the micro USB cable into the board. Then release the button after the board is plugged in.

A disk volume called RPI-RP2 should pop up on your desktop. Double-click to open it, and then drag and drop the UF2 file into it. The volume will automatically unmount and the light on your board should start blinking.

Blinking an LED

Congratulations! You’ve just put code onto your Raspberry Pi Pico for the first time. Now we’ve made sure that we can successfully get a program onto the board, let’s take a step back and look at how we’d write that program in the first place.

Getting the SDK

Somewhat unsurprisingly, we’ve gone to a lot of trouble to make installing the tools you’ll need to develop for Pico as easy as possible on a Raspberry Pi. We’re hoping to make things easier still in the future, but you should be able to install everything you need by running a setup script.

However, before we do anything, the first thing you’ll need to do is make sure your operating system is up to date.

$ sudo apt update
$ sudo apt full-upgrade

Once that’s complete you can grab the setup script directly from Github, and run it at the command line.

$ wget -O pico_setup.sh https://rptl.io/pico-setup-script
$ chmod +x pico_setup.sh
$ ./pico_setup.sh

The script will do a lot of things behind the scenes to configure your Raspberry Pi for development, including installing the C/C++ command line toolchain and Visual Studio Code. Once it has run, you will need to reboot your Raspberry Pi.

$ sudo reboot

The script has been tested and is known to work from a clean, up-to-date installation of Raspberry Pi OS. However, full instructions, along with instructions for manual installation of the toolchain if you prefer to do that, can be found in the “Getting Started” guide.

Once your Raspberry Pi has rebooted we can get started writing code.

Writing code for your Pico

There is a large amount of example code for Pico, and one of the things that the setup script will have done is to download the examples and build both the Blink and “Hello World” examples to verify that your toolchain is working.

But we’re going to go ahead and write our own.

We’re going to be working in the ~/pico directory created by the setup script, and the first thing we need to do is to create a directory to house our project.

$ cd pico
$ ls -la
total 59284
drwxr-xr-x  9 pi pi     4096 Jan 28 10:26 .
drwxr-xr-x 19 pi pi     4096 Jan 28 10:29 ..
drwxr-xr-x 12 pi pi     4096 Jan 28 10:24 openocd
drwxr-xr-x 28 pi pi     4096 Jan 28 10:20 pico-examples
drwxr-xr-x  7 pi pi     4096 Jan 28 10:20 pico-extras
drwxr-xr-x 10 pi pi     4096 Jan 28 10:20 pico-playground
drwxr-xr-x  5 pi pi     4096 Jan 28 10:21 picoprobe
drwxr-xr-x 10 pi pi     4096 Jan 28 10:19 pico-sdk
drwxr-xr-x  7 pi pi     4096 Jan 28 10:22 picotool
-rw-r--r--  1 pi pi 60667760 Dec 16 16:36 vscode.deb
$ mkdir blink
$ cd blink

Now open up your favourite editor and create a file called blink.c in the blink directory.

#include "pico/stdlib.h"
#include "pico/binary_info.h"

const uint LED_PIN = 25;

int main() {

    bi_decl(bi_program_description("First Blink"));
    bi_decl(bi_1pin_with_name(LED_PIN, "On-board LED"));

    gpio_init(LED_PIN);
    gpio_set_dir(LED_PIN, GPIO_OUT);
    while (1) {
        gpio_put(LED_PIN, 0);
        sleep_ms(250);
        gpio_put(LED_PIN, 1);
        sleep_ms(1000);
    }
}

Create a CMakeLists.txt file too.

cmake_minimum_required(VERSION 3.12)

include(pico_sdk_import.cmake)

project(blink)

pico_sdk_init()

add_executable(blink
    blink.c
)

pico_add_extra_outputs(blink)

target_link_libraries(blink pico_stdlib)

Then copy the pico_sdk_import.cmake file from the external folder in your pico-sdk installation to your test project folder.

$ cp ../pico-sdk/external/pico_sdk_import.cmake .

You should now have something that looks like this:

$ ls -la
total 20
drwxr-xr-x  2 pi pi 4096 Jan 28 11:32 .
drwxr-xr-x 10 pi pi 4096 Jan 28 11:01 ..
-rw-r--r--  1 pi pi  398 Jan 28 11:06 blink.c
-rw-r--r--  1 pi pi  211 Jan 28 11:32 CMakeLists.txt
-rw-r--r--  1 pi pi 2721 Jan 28 11:32 pico_sdk_import.cmake
$

We are ready to build our project using CMake.

$ mkdir build
$ cd build
$ export PICO_SDK_PATH=../../pico-sdk
$ cmake ..
$ make

If all goes well you should see a whole bunch of messages flash past in your Terminal window and a number of files will be generated in the build/ directory, including one called blink.uf2.

Just as we did before with the UF2 file we downloaded from the Getting Started page, we can now drag and drop this file on to our Pico.

Unplug the cable from your Pico, then press and hold the BOOTSEL button on your Pico and plug it back in. Then release the button after the board is plugged in.

The new Blink binary
The new blink.uf2 binary can be dragged and dropped on to our Pico

The RPI-RP2 disk volume should pop up on your desktop again. Double-click to open it, then open a file viewer in the pico/blink/build/ directory and drag and drop the UF2 file you’ll find there on to the RPI-RP2 volume. It will automatically unmount, and the light on your board should start blinking. But this time it will blink a little bit differently from before.

Try playing around with the sleep_ms( ) lines in our code to vary how much time there is between blinks. You could even take a peek at one of the examples, which shows you how to blink the onboard LED in Morse code.

Using Picotool

One way to convince yourself that the program running on your Pico is the one we just built is to use something called picotool. Picotool is a command line utility installed by the setup script that is a Swiss Army knife for all things Pico.

Go ahead and unplug your Pico from your Raspberry Pi, press and hold the BOOTSEL button, and plug it back in. Then run picotool.

$ sudo picotool info -a 
Program Information
 name:          blink
 description:   First Blink
 binary start:  0x10000000
 binary end:    0x10003344

Fixed Pin Information
 25:  On-board LED

Build Information
 sdk version:       1.0.0
 pico_board:        pico
 build date:        Jan 28 2021
 build attributes:  Release

Device Information
 flash size:   2048K
 ROM version:  1
$

You’ll see lots of information about the program currently on your Pico. Then if you want to start it blinking again, just unplug and replug Pico to leave BOOTSEL mode and start your program running once more.

Picotool can do a lot more than this, and you’ll find more information about it in Appendix B of the “Getting Started” guide.

Using Visual Studio Code

So far we’ve been building our Pico projects from the command line, but the setup script also installed and configured Visual Studio Code, and we can build the exact same CMake-based project in the Visual Studio Code environment. You can open it as below:

$ cd ~/pico
$ export PICO_SDK_PATH=/home/pi/pico/pico-sdk
$ code

Chapter 6 of the Getting Started guide has full details of how to load and compile a Pico project inside Visual Studio Code. If you’re used to Visual Studio Code, you might be able to make your way from here without much extra help, as the setup script has done most of the heavy lifting for you in configuring the IDE.

What’s left is to open the pico/blink folder and allow the CMake Tools extension to configure the project. After selecting arm-none-eabi as your compiler, just hit the “Build’ button in the blue bottom bar.

Building our blink project inside Visual Studio Code

While we recommend and support Visual Studio Code as the development environment of choice for developing for Pico β€” it works cross-platform under Linux, Windows, and macOS and has good plugin support for debugging β€” you can also take a look at Chapter 9 of the Getting Started guide. There we talk about how to use both Eclipse and CLion to develop for Pico, and if you’re more used to those environments you should be able to get up and running in either without much trouble.

Where now?

If you’ve got this far, you’ve built and deployed your very first C program to your Raspberry Pi Pico. Well done! The next step is probably going to be saying “Hello World!” over serial back to your Raspberry Pi.

From here, you probably want to sit down and read the Getting Started guide I’ve mentioned throughout the article, especially if you want to make use of SWD debugging, which is discussed at length in the guide. Beyond that I’d point you to the book on the C/C++ SDK which has the API-level documentation, as well as a high-level discussion of the design of the SDK.

Support for developing for Pico can be found on the Raspberry Pi forums. There is also an (unofficial) Discord server where a lot of people active in the new community seem to be hanging out. Feedback on the documentation should be posted as an Issue to the pico-feedback repository on Github, or directly to the relevant repository it concerns.

All of the documentation, along with lots of other help and links, can be found on the same Getting Started page from which we grabbed our original UF2 file.

If you lose track of where that is in the future, you can always find it from your Pico: to access the page, just press and hold the BOOTSEL button on your Pico, plug it into your laptop or Raspberry Pi, then release the button. Go ahead and open the RPI-RP2 volume, and then click on the INDEX.HTM file.

That will always take you to the Getting Started page.

23 comments
Jump to the comment form

Avatar

Unfortunately it still seems rather convoluted. Not an Arduino killer yet. And this is not good: “Go ahead and unplug your Pico from your Raspberry Pi, press and hold the BOOTSEL button, and plug it back in.” To do that for every new iteration of the program, and even before every use of picotool…. that will destroy the USB ports. Adafruit’s double-click solution seems much better. I don’t know if this can still be improved with current hardware but otherwise I’d say you should facilitate & promote using the SWD port for uploading.

Reply to Ed

Avatar

Given that Arduino are producing a RP2040 board, I doubt the Pico was aimed at being an ‘Arduino killer’ as that would be somewhat self defeating. Not sure we need more stupidly easy to code devices, plenty of them already, but there is a huge need for those taking the step up from CopynPasta Arduino to something they can actually be paid to do – VSCode, CMake, GCC, Git, embedded debugging, all surfaced with some great docs, any A Level CS student that gets to grips with this is very employable. Which is somewhat the point of the Raspberry Pi Foundations efforts.

As Norman, myself and others have figured, it’s easy enough to add an extra button to reset. And as you say, as people get going, they can create a whole programmer/debugger out of another Pico if they aren’t on a Pi and do it all over SWD. It’s only the first week, give it time.

Reply to Nick

Avatar

I posted on Twitter an easy and quick way to avoid having to keep unplugging/plugging the USB power in order to program the Pico. It was, apparently, useful.

Fed up unplugging your Pico to reprogram?
Connect push-switch to pins 38 GND and 37 3V3_EN.

To reset:
Press to reset.

To program:
Press Switch & hold
Press BOOTSEL & and hold
Release first switch holding BOOTSEL
Release BOOTSEL when drive appears on the Pi

There’s an image on Twitter, if anyone needs it: https://t.co/Yl5qhDc44N

Cheers,
Norm.

Reply to Norman Dunbar

Avatar

Shorting GND and 3V3 seems a bad idea. Use switch between RUN pin and GND pin.

Reply to rpiMike

Avatar

Shorting 3v3_en pin, not 3v3. That powers down the RP2040. Unshort to power up with BOOTSEL still pressed = same as BOOTSEL plus plugging power in.

Cheers,
Norm.

Reply to Norman Dunbar

Avatar

Given that Alastrair posted on how to add a reset/boot switch to the pico, it’s best you use the run pin and not the 3v3_en pin if you have any gpio pins pulled high at reset/boot time.
https://www.raspberrypi.org/blog/how-to-add-a-reset-button-to-your-raspberry-pi-pico/

Cheers,
Norm.

Reply to Norman Dunbar

Avatar

Needs an optional boot loader that works like the python one does. Makes the flash available as a drive all the time and will restart your app if you write it to disk. Of cause would have to look for a different name.
Maybe if you did that would have to change some of the low level memory stuff in the final binary.
Just some random thoughts. :)

Reply to Richard collins

Avatar

$ cd pico
$ ls -la
total 59284
drwxr-xr-x 9 pi pi 4096 Jan 28 10:26 .
drwxr-xr-x 19 pi pi 4096 Jan 28 10:29 ..
drwxr-xr-x 12 pi pi 4096 Jan 28 10:24 openocd
drwxr-xr-x 28 pi pi 4096 Jan 28 10:20 pico-examples
drwxr-xr-x 7 pi pi 4096 Jan 28 10:20 pico-extras
drwxr-xr-x 10 pi pi 4096 Jan 28 10:20 pico-playground
drwxr-xr-x 5 pi pi 4096 Jan 28 10:21 picoprobe
drwxr-xr-x 10 pi pi 4096 Jan 28 10:19 pico-sdk
drwxr-xr-x 7 pi pi 4096 Jan 28 10:22 picotool
-rw-r–r– 1 pi pi 60667760 Dec 16 16:36 vscode.deb
$ mkdir blink
$ cd blink

Reply to apk modded

Avatar

Ah, Visual Studio Code. No wonder it will download 2.5 GB. Presumably, this will not be compatible with RaspiZero. Oh, well. At least this will help a lot of other people.
That is, until a new RaspiZero version will come out that has enough specs to handle it. Hint. :)

Reply to Harry Hardjono

Avatar

You don’t have to download the entire SDK, including VSCode in one go. That script is a shortcut for anyone wanting to do it “with one click” as it were.
The getting started guide, for c or micropython, has step by step instructions to download the bits you need separately. It’s a bit of a faf getting and configuring it all separately, but it works.
A Pi4 is advised in the book for development, that’s what I’m playing with ATM, but my intention is to use my laptop, eventually, and hopefully PlatformIO, for development.
I’ve not tried using a Zero, yet, but now I’m intrigued…… ;-)

Cheers,
Norm.

Reply to Norman Dunbar

Avatar

Doing Zero development is rather trying due to lack of memory, only 512 MB. A lot of dev kits goes into virtual memory, and that slows things down tremendously. Sometimes I can find alternatives using CLI compilers. Let me know if you do.
Also, the network bandwidth is punishingly large. I had to give up Windows because of it. Having the CPU alone is not enough. You also need large amount of bandwidth, and that’s not acceptable to me.

Reply to Harry Hardjono

Avatar

I tried the instructions to install blink.

The directory appeared empty, and dragging failed. (I have never been a fan of dragon droppings).

All became clear at the command line and the following worked.

sudo cp blink.uf2 /media/pi/RPI-RP2/

The RPI-RP2 immediately disappeared, however the LED began to blink, so success BUT the instructions could be clearer.

Reply to Milliways

Avatar

I’m new to using microcontrollers and excited to start exploring the PICO to help me expand my RPi4 based robot. I’d like to continue using the Pi for the heavy lifting (I need Tensorflow Lite and OpenCV) but to be able to use the Pico for the hardware control part (have more GPIOs, more accurate servo control, etc.)
I would love to see a “blink LED” tutorial that uses serial communication within a Python script run on the Pi. Looking through the Micropython SDK documentation I don’t see yet how to translate a command from a Python program on the Pi into a response from the PICO. I would love a beginners-friendly guide to using usb serial communication within a Python script run on the Pi to control GPIOs on the Pico.
Thanks for putting all this info together!

Reply to FunkyGandalf

Avatar
Avatar

Thanks!

Reply to JB

Avatar

The standard USB connector is rated for 1500 plug cycles, which is smaller than you might like. To avoid re-plugging USB cables, there are USB hubs that have a separate switch for each port. Search “USB hub with switches”

Reply to JBeale

Alasdair Allan

It’s also really easy to add a reset button, see https://www.raspberrypi.org/blog/how-to-add-a-reset-button-to-your-raspberry-pi-pico/ for more details!

Reply to Alasdair Allan

Avatar

Installed the SDK and VS Code on a Raspbery Pi 4 with 8 GB and a USB3-SSD yesterday. Took way too long, but VS code runs with acceptable speed. I will certainly create my very own stripped down minimalistic GNU-Makefile for my experiments. This recommended infrastructure is totally over the top if you plan to do and TEACH a small example.

Reply to Georg Bisseling

Avatar

Is VSCodium supported ?

Reply to Andrzej

Avatar

I wish you could update the Getting Started documentation to reflect this method of getting the SDK because it just does not work (for me at least!) The script keeps coming up with errors that vary according to where you run it from (downloads, home, custom folder etc.) Freely admit its probably me but it will put off anyone coming to the Pi for the first time even if they are used to C++ on another OS.

Reply to Julian Horn

Avatar

Dont update it – just tried this process on a brand new install as suggested:
pi@raspberrypi:~ $ sudo apt update
Hit:1 http://raspbian.raspberrypi.org/raspbian buster InRelease
Hit:2 http://archive.raspberrypi.org/debian buster InRelease
Get:3 http://packages.microsoft.com/repos/code stable InRelease [10.4 kB]
Get:4 http://packages.microsoft.com/repos/code stable/main amd64 Packages [13.0 kB]
Get:5 http://packages.microsoft.com/repos/code stable/main armhf Packages [13.6 kB]
Get:6 http://packages.microsoft.com/repos/code stable/main arm64 Packages [13.6 kB]
Fetched 50.6 kB in 2s (32.6 kB/s)
Reading package lists… Done
Building dependency tree
Reading state information… Done
All packages are up to date.
pi@raspberrypi:~ $ sudo apt full-upgrade
Reading package lists… Done
Building dependency tree
Reading state information… Done
Calculating upgrade… Done
0 upgraded, 0 newly installed, 0 to remove and 0 not upgraded.
pi@raspberrypi:~ $ wget -o pico_setup.sh https://rptl.io/pico-setup-script
pi@raspberrypi:~ $ chmod +x pico_setup.sh
pi@raspberrypi:~ $ ./pico_setup.sh
./pico_setup.sh: line 1: –2021-02-13: command not found
./pico_setup.sh: line 2: syntax error near unexpected token `(‘
./pico_setup.sh: line 2: `Resolving rptl.io (rptl.io)… 176.126.240.192, 46.235.230.102, 2a00:1098:82:70::1:1, …’
Can someone tell me what i am doing wrong please?
This is driving me up the wall!

Reply to Julian Horn

Avatar

In case anyone else suffers like me the answer is:
wget -o pico_setup.sh https://rptl.io/pico-setup-script
Should be
wget -O pico_setup.sh https://rptl.io/pico-setup-script
The -O option is a capital O not a lower case o
I know its me but . . .

Reply to Julian Horn

Avatar

the easy way is PlatformIO & Arduino
https://github.com/Wiz-IO/wizio-pico
Happy coding !

Reply to Georgi Angelov

Leave a Comment