Raspberry Pi Pico C SDK
Raspberry Pi Pico C SDK
Colophon
Copyright © 2020 Raspberry Pi (Trading) Ltd.
The documentation of the RP2040 microcontroller is licensed under a Creative Commons Attribution-NoDerivatives 4.0
International (CC BY-ND).
build-date: 2021-02-01
build-version: ef82dc8-clean
Throughout the text "the SDK" refers to our Raspberry Pi Pico SDK. More details about the SDK can be
found throughout this book.
Release History
Table 1. Release
Release Date Description
History
RPTL reserves the right to make any enhancements, improvements, corrections or any other modifications to the
RESOURCES or any products described in them at any time and without further notice.
The RESOURCES are intended for skilled users with suitable levels of design knowledge. Users are solely responsible for
Release History 1
Raspberry Pi Pico C/C++ SDK
their selection and use of the RESOURCES and any application of the products described in them. User agrees to
indemnify and hold RPTL harmless against all liabilities, costs, damages or other losses arising out of their use of the
RESOURCES.
RPTL grants users permission to use the RESOURCES solely in conjunction with the Raspberry Pi products. All other use
of the RESOURCES is prohibited. No licence is granted to any other RPTL or other third party intellectual property right.
HIGH RISK ACTIVITIES. Raspberry Pi products are not designed, manufactured or intended for use in hazardous
environments requiring fail safe performance, such as in the operation of nuclear facilities, aircraft navigation or
communication systems, air traffic control, weapons systems or safety-critical applications (including life support
systems and other medical devices), in which the failure of the products could lead directly to death, personal injury or
severe physical or environmental damage (“High Risk Activities”). RPTL specifically disclaims any express or implied
warranty of fitness for High Risk Activities and accepts no liability for use or inclusions of Raspberry Pi products in High
Risk Activities.
Raspberry Pi products are provided subject to RPTL’s Standard Terms. RPTL’s provision of the RESOURCES does not
expand or otherwise modify RPTL’s Standard Terms including but not limited to the disclaimers and warranties expressed
in them.
Table of Contents
Colophon . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1
Release History . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1
1.1. Introduction. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7
2. SDK Architecture . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 10
3.1.1. Background . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 29
3.3.1. Usage . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 48
3.3.2. Directives . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 49
3.3.3. Values. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 50
3.3.4. Expressions. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 50
3.3.5. Comments. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 50
3.3.6. Labels. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 50
3.3.7. Instructions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 51
3.3.8. Pseudoinstructions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 51
Table of Contents 3
Raspberry Pi Pico C/C++ SDK
3.4.1. Summary . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 57
3.4.2. JMP . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 58
3.4.3. WAIT. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 59
3.4.4. IN. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 60
3.4.6. PUSH . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 62
3.4.8. MOV . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 64
3.4.9. IRQ . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 65
4. Library Documentation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 68
4.1.1. hardware_adc . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 68
4.1.2. hardware_base . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 73
4.1.3. hardware_claim . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 74
4.1.4. hardware_clocks . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 76
4.1.5. hardware_divider . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 82
4.1.6. hardware_dma . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 90
4.1.7. channel_config . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 97
Table of Contents 4
Raspberry Pi Pico C/C++ SDK
Table of Contents 5
Raspberry Pi Pico C/C++ SDK
Table of Contents 6
Raspberry Pi Pico C/C++ SDK
1.1. Introduction
The SDK (Software Development Kit) provides the headers, libraries and build system necessary to write programs for
RP2040-based devices such as Raspberry Pi Pico in C, C++ or Arm assembly language.
The SDK is designed to provide an API and programming environment that is familiar both to non-embedded C developers
and embedded C developers alike. A single program runs on the device at a time with a conventional main() method.
Standard C/C++ libraries are supported along with APIs for accessing RP2040’s hardware, including DMA, IRQs, and the
wide variety fixed function peripherals and PIO (Programmable IO).
Additionally the SDK provides higher level libraries for dealing with timers, USB, synchronization and multi-core
programming, along with additional high level functionality built using PIO such as audio. These libraries should be
comprehensive enough that your application code rarely, if at all, needs to access hardware registers directly. However, if
you do need or prefer to access the raw hardware, you will also find complete and fully-commented register definition
headers in the SDK. There’s no need to look up addresses in the datasheet.
The SDK can be used to build anything from simple applications, full fledged runtime environments such as MicroPython,
to low level software such as RP2040’s on-chip bootrom itself.
This book documents the SDK APIs, explains the internals and overall design of the SDK, and explores
some deeper topics like using the PIO assembler to build new interfaces to external hardware. For a quick
start with setting up the SDK and writing SDK programs, Getting started with Raspberry Pi Pico is the
best place to start.
1 /**
2 * Copyright (c) 2020 Raspberry Pi (Trading) Ltd.
3 *
4 * SPDX-License-Identifier: BSD-3-Clause
5 */
6
7 #include "pico/stdlib.h"
8
9 int main() {
10 const uint LED_PIN = 25;
11 gpio_init(LED_PIN);
12 gpio_set_dir(LED_PIN, GPIO_OUT);
13 while (true) {
14 gpio_put(LED_PIN, 1);
15 sleep_ms(250);
16 gpio_put(LED_PIN, 0);
17 sleep_ms(250);
18 }
1.1. Introduction 7
Raspberry Pi Pico C/C++ SDK
19 }
This program consists only of a single C file, with a single function. As with almost any C programming environment, the
function called main() is special, and is the point where the language runtime first hands over control to your program,
after doing things like initialising static variables with their values. In the SDK the main() function does not take any
arguments. It’s quite common for the main() function not to return, as is shown here.
NOTE
The return code of main() is ignored by the SDK runtime, and the default behaviour is to hang the processor on exit.
At the top of the C file, we include a header called pico/stdlib.h. This is an umbrella header that pulls in some other
commonly used headers. In particular, the ones needed here are hardware/gpio.h, which is used for accessing the general
purpose IOs on RP2040 (the gpio_xxx functions here), and pico/time.h which contains, among other things, the sleep_ms
function. Broadly speaking, a library whose name starts with pico provides high level APIs and concepts, or aggregates
smaller interfaces; a name beginning with hardware indicates a thinner abstraction between your code and RP2040 on-chip
hardware.
So, using mainly the hardware_gpio and pico_time libraries, this C program will blink an LED connected to GPIO25 on and
off, twice per second, forever (or at least until unplugged). In the directory containing the C file (you can click the link
above the source listing to go there), there is one other file which lives alongside it.
blink
├── blink.c
└── CMakeLists.txt
0 directories, 2 files
The second file is a CMake file, which tells the SDK how to turn the C file into a binary application for an RP2040-based
microcontroller board. Later sections will detail exactly what CMake is, and why it is used, but we can look at the contents
of this file without getting mired in those details.
1 add_executable(blink
2 blink.c
3 )
4
5 # Pull in our pico_stdlib which pulls in commonly used features
6 target_link_libraries(blink pico_stdlib)
7
8 # create map/bin/hex file etc.
9 pico_add_extra_outputs(blink)
10
11 # add url via pico_set_program_url
12 example_auto_set_url(blink)
The add_executable function in this file declares that a program called blink should be built from the C file shown earlier.
This is also the target name used to build the program: in the pico-examples repository you can say make blink in your build
directory, and that name comes from this line. You can have multiple executables in a single project, and the pico-examples
repository is one such project.
The target_link_libraries is pulling in the SDK functionality that our program needs. If you don’t ask for a library, it doesn’t
appear in your program binary. Just like pico/stdlib.h is an umbrella header that includes things like pico/time.h and
hardware/gpio.h, pico_stdlib is an umbrella library that makes libraries like pico_time and hardware_gpio available to your
build, so that those headers can be included in the first place, and the extra C source files are compiled and linked. If you
need less common functionality, like accessing the DMA hardware, you can call those libraries out here (e.g. listing
hardware_dma before or after pico_stdlib).
We could end the CMake file here, and that would be enough to build the blink program. By default, the build will produce
an ELF file (executable linkable format), containing all of your code and the SDK libraries it uses. You can load an ELF into
RP2040’s RAM or external flash through the Serial Wire Debug port, with a debugger setup like gdb and openocd. It’s often
easier to program your Raspberry Pi Pico or other RP2040 board directly over USB with BOOTSEL mode, and this requires
a different type of file, called UF2, which serves the same purpose here as an ELF file, but is constructed to survive the
rigours of USB mass storage transfer more easily. The pico_add_extra_outputs function declares that you want a UF2 file to
be created, as well as some useful extra build output like disassembly and map files.
NOTE
The ELF file is converted to a UF2 with an internal SDK tool called elf2uf2, which is bootstrapped automatically as part
of the build process.
The example_auto_set_url function is to do with how you are able to read this source file in this document you are reading
right now, and click links to take you to the listing on Github. You’ll see this on the pico-examples applications, but it’s not
necessary on your own programs. You are seeing how the sausage is made.
Finally, a brief note on the pico_stdlib library. Besides common hardware and high-level libraries like hardware_gpio and
pico_time, it also pulls in components like pico_standard_link — which contains linker scripts and crt0 for SDK — and
pico_runtime, which contains code running between crt0 and main(), getting the system into a state ready to run code by
putting things like clocks and resets in a safe initial state. These are incredibly low-level components that most users will
not need to worry about. The reason they are mentioned is to point out that they are ultimately explicit dependencies of
your program, and you can choose not to use them, whilst still building against the SDK and using things like the hardware
libraries.
The intention within the SDK has been for features to just work out of the box, with sensible defaults, but also to give the
developer as much control and power as possible (if they want it) to fine tune every aspect of the application they are
building and the libraries used.
The next few sections try to highlight some of the design decisions behind the SDK: the how and the why, as much as the
what.
NOTE
Some parts of this overview are quite technical or deal with very low-level parts of the SDK and build system. You
might prefer to skim this section at first and then read it thoroughly at a later time, after writing a few SDK
applications.
Section 2.6 shows how CMake can set configuration defines for a particular program, or based on which RP2040 board
you are building for, to configure things like default pin mappings and features of SDK libraries. These defines are listed in
Appendix B, and Board Configuration files are covered in more detail in Appendix D. Additionally Appendix C describes
CMake variables you can use to control the functionality of the build itself.
Apart from being a widely used build system for C/C++ development, CMake is fundamental to the way the SDK is
structured, and how applications are configured and built.
1 add_executable(blink
2 blink.c
3 )
4
5 # Pull in our pico_stdlib which pulls in commonly used features
6 target_link_libraries(blink pico_stdlib)
7
8 # create map/bin/hex file etc.
9 pico_add_extra_outputs(blink)
10
11 # add url via pico_set_program_url
12 example_auto_set_url(blink)
Looking here at the blink example, we are defining a new executable blink with a single source file blink.c, with a single
dependency pico_stdlib. We also are using a SDK provided function pico_add_extra_outputs to ask additional files to be
produced beyond the executable itself (.uf2, .hex, .bin, .map, .dis).
The SDK builds an executable which is bare metal, i.e. it includes the entirety of the code needed to run on the device
(other than floating point and other optimized code contained in the bootrom within RP2040).
pico_stdlib is an INTERFACE library and provides all of the rest of the code and configuration needed to compile and link the
blink application. You will notice if you do a build of blink (https://github.com/raspberrypi/pico-examples/tree/master/
blink/blink.c) that in addition to the single blink.c file, the inclusion of pico_stdlib causes about 40 other source files to be
compiled to flesh out the blink application such that it can be run on RP2040.
• Source files
• Include paths
• Compiler definitions (visible to code as #defines)
• Compile and link options
• Dependencies (on other INTERFACE libraries)
The INTERFACE libraries form a tree of dependencies, with each contributing source files, include paths, compiler definitions
and compile/link options to the build. These are collected based on the libraries you have listed in your CMakeLists.txt file,
and the libraries depended on by those libraries, and so on recursively. To build the application, each source file is
compiled with the combined include paths, compiler definitions and options and linked into an executable according to
the provided link options.
When building an executable with the SDK, all of the code for one executable, including the SDK libraries, is (re)compiled
for that executable from source. Building in this way allows your build configuration to specify customised settings for
those libraries (e.g. enabling/disabling assertions, setting the sizes of static buffers), on a per-application basis, at compile
time. This allows for faster and smaller binaries, in addition of course to the ability to remove support for unwanted
features from your executable entirely.
In the example CMakeLists.txt we declare a dependency on the (INTERFACE) library pico_stdlib. This INTERFACE library itself
depends on other INTERFACE libraries (pico_runtime, hardware_gpio, hardware_uart and others). pico_stdlib provides all the
basic functionality needed to get a simple application running and toggling GPIOs and printing to a UART, and the linker
will garbage collect any functions you don’t call, so this doesn’t bloat your binary. We can take a quick peek into the
directory structure of the hardware_gpio library, which our blink example uses to turn the LED on and off:
hardware_gpio
├── CMakeLists.txt
├── gpio.c
└── include
└── hardware
└── gpio.h
Depending on the hardware_gpio INTERFACE library in your application causes gpio.c to be compiled and linked into your
executable, and adds the include directory shown here to your search path, so that a #include "hardware/gpio.h" will pull in
the correct header in your code.
INTERFACE libraries also make it easy to aggregate functionality into readily consumable chunks (such as pico_stdlib),
which don’t directly contribute any code, but depend on a handful of lower-level libraries that do. Like a metapackage, this
lets you pull in a group of libraries related to a particular goal without listing them all by name.
IMPORTANT
SDK functionality is grouped into separate INTERFACE libraries, and each INTERFACE library contributes the code and
include paths for that library. Therefore you must declare a dependency on the INTERFACE library you need directly (or
indirectly through another INTERFACE library) for the header files to be found during compilation of your source file (or
for code completion in your IDE).
NOTE
As all libraries within the SDK are INTERFACE libraries, we will simply refer to them as libraries or SDK libraries from now
on.
There are a number of layers of libraries within the SDK. This section starts with the highest-level libraries, which can be
used in C or C++ applications, and navigates all the way down to the hardware_regs library, which is a comprehensive set of
hardware definitions suitable for use in Arm assembly as well as C and C++, before concluding with a brief note on how
the TinyUSB stack can be used from within the SDK.
NOTE
More libraries will be forthcoming in the future (e.g. - Audio support (via PIO), DPI/VGA/MIPI Video support (via PIO) file
system support, SDIO support via (PIO)), most of which are available but not yet fully supported/stable/documented in
the pico-extras GitHub repository.
pico_runtime aggregates the libraries (listed in pico_runtime) that provide a familiar C environment for executing code,
including:
• Choice of language level single/double precision floating point support (and access to the fast on-RP2040
implementations)
pico_standard_link encapsulates the standard linker setup needed to configure the type of application binary layout in
memory, and link to any additional C and/or C++ runtime libraries. It also includes the default crt0, which provides the
initial entry point from the flash second stage bootloader, contains the initial vector table (later relocated to RAM), and
initialises static data and RAM-resident code if the application is running from flash.
NOTE
TIP
These libraries generally provide functions for configuring or interacting with the peripheral at a functional level, rather
than accessing registers directly, e.g.
rather than:
pio->sm[sm].execctrl =
(pio->sm[sm].execctrl & ~(PIO_SM0_EXECCTRL_WRAP_TOP_BITS |
PIO_SM0_EXECCTRL_WRAP_BOTTOM_BITS)) |
(bottom << PIO_SM0_EXECCTRL_WRAP_BOTTOM_LSB) |
(top << PIO_SM0_EXECCTRL_WRAP_TOP_LSB);
The hardware_ libraries are intended to have a very minimal runtime cost. They generally do not require any or much RAM,
and do not rely on other runtime infrastructure. In general their only dependencies are the hardware_structs and
hardware_regs libraries that contain definitions of memory-mapped register layout on RP2040. As such they can be used by
low-level or other specialized applications that doesn’t want to use the rest of the SDK libraries and runtime.
NOTE
void pio_sm_set_wrap(PIO pio, uint sm, uint bottom, uint top) {} is actually implemented as a static inline function in
https://github.com/raspberrypi/pico-sdk/tree/master/src/rp2_common/hardware_pio/include/hardware/pio.h directly
as shown above.
Using static inline functions is common in SDK header files because such methods are often called with parameters
that have fixed known values at compile time. In such cases, the compiler is often able to fold the code down to to a
single register write (or in this case a read, AND with a constant value, OR with a constant value, and a write) with no
function call overhead. This tends to produce much smaller and faster binaries.
The hardware layer does provide one small abstraction which is the notion of claiming a piece of hardware. This minimal
system allows registration of peripherals or parts of peripherals (e.g. DMA channels) that are in use, and the ability to
atomically claim free ones at runtime. The common use of this system - in addition to allowing for safe runtime allocation
of resources - provides a better runtime experience for catching software misconfigurations or accidental use of the
same piece hardware by multiple independent libraries that would otherwise be very painful to debug.
with something like this (where pio0 is a pointer to type pio_hw_t at address PIO0_BASE):
pio0->sm[1].shiftctrl |= PIO_SM1_SHIFTCTRL_AUTOPULL_BITS;
The structures and associated pointers to memory mapped register blocks hide the complexity and potential error-prone-
ness of dealing with individual memory locations, pointer casts and volatile access. As a bonus, the structs tend to
produce better code with older compilers, as they encourage the reuse of a base pointer with offset load/stores, instead
of producing a 32 bit literal for every register accessed.
The struct headers are named consistently with both the hardware libraries and the hardware_regs register headers. For
example, if you access the hardware_pio library’s functionality through hardware/pio.h, the hardware_structs library (a
dependee of hardware_pio) contains a header you can include as hardware/structs/pio.h if you need to access a register
directly, and this itself will pull in hardware/regs/pio.h for register field definitions. The PIO header is a bit lengthy to include
here. hardware/structs/pll.h is a shorter example to give a feel for what these headers actually contain:
14 typedef struct {
15 io_rw_32 cs;
16 io_rw_32 pwr;
17 io_rw_32 fbdiv_int;
18 io_rw_32 prim;
19 } pll_hw_t;
20
The structure contains the layout of the hardware registers in a block, and some defines bind that layout to the base
addresses of the instances of that peripheral in the RP2040 global address map.
Additionally, you can use one of the atomic set, clear, or xor address aliases of a piece of hardware to set, clear or toggle
respectively the specified bits in a hardware register (as opposed to having the CPU perform a read/modify/write); e.g:
hw_set_alias(pio0)->sm[1].shiftctrl = PIO_SM1_SHIFTCTRL_AUTOPULL_BITS;
Or, equivalently
hw_set_bits(&pio0->sm[1].shiftctrl, PIO_SM1_SHIFTCTRL_AUTOPULL_BITS);
NOTE
The hardware atomic set/clear/XOR IO aliases are used extensively in the SDK libraries, to avoid certain classes of
data race when two cores, or an IRQ and foreground code, are accessing registers concurrently.
NOTE
On RP2040 the atomic register aliases are a native part of the peripheral, not a CPU function, so the system DMA can
also perform atomic set/clear/XOR operation on registers.
These header files are fairly heavily commented (the same information as is present in the datasheet register listings, or
the SVD files). They define the offset of every register, and the layout of the fields in those registers, as well as the access
type of the field, e.g. "RO" for read-only.
TIP
The headers in hardware_regs contain only comments and #define statements. This means they can be included from
assembly files (.S, so the C preprocessor can be used), as well as C and C++ files.
IMPORTANT
RP2040 USB hardware supports both Host and Device modes, but the two can not be used concurrently.
The tinyusb_dev or tinyusb_host libraries within the SDK allow you to add TinyUSB device or host support to your
application by simply adding a dependency in your executable in CMakeLists.txt
You would change your CMakeLists.txt to list both pico_stdlib and hardware_dma as dependencies of the hello_world target
(executable). (Note the line breaks are not required)
target_link_libraries(hello_world
pico_stdlib
hardware_dma
)
And in your source code you would include the DMA hardware library header as such:
#include "hardware/dma.h"
Trying to include this header without listing hardware_dma as a dependency will fail, and this is due to how SDK files are
organised into logical functional units on disk, to make it easier to add functionality in the future.
As an aside, this correspondence of hardware_dma → hardware/dma.h is the convention for all toplevel SDK library headers.
The library is called foo_bar and the associated header is foo/bar.h. Some functions may be provided inline in the headers,
others may be compiled and linked from additional .c files belonging to the library. Both of these require the relevant
hardware_ library to be listed as a dependency, either directly or through some higher-level bundle like pico_stdlib.
NOTE
You may want to actually find the files in question (although most IDEs will do this for you). The on disk files are actually
split into multiple top-level directories. This is described in the next section.
The latter is useful for writing and running unit tests, but also as you develop your software, for example your debugging
code or work in progress software might actually be too big or use too much RAM to fit on the device, and much of the
software complexity may be non-hardware-specific.
Table 2. Top-level
Path Description
directories
src/rp2040/ This contains the hardware_regs and hardware_structs libraries mentioned earlier, which are
specific to RP2040.
src/rp2_common/ This contains the hardware_ library implementations for individual hardware components,
and pico_ libraries or library implementations that are closely tied to RP2040 hardware.
This is separate from /src/rp2040 as there may be future revisions of RP2040, or other
chips in the RP2 family, which can use a common SDK and API whilst potentially having
subtly different register definitions.
src/common/ This is code that is common to all builds. This is generally headers providing hardware
abstractions for functionality which are simulated in host mode, along with a lot of the
pico_ library implementations which, to the extent they use hardware, do so only through
the hardware_ abstractions.
src/host/ This is a basic set of replacement SDK library implementations sufficient to get simple
Raspberry Pi Pico applications running on your computer (Raspberry Pi OS, Linux, macOS
or Windows using Cygwin or Windows Subsystem for Linux). This is not intended to be a
fully functional simulator, however it is possible to inject additional implementations of
libraries to provide more complete functionality.
There is a CMake variable PICO_PLATFORM that controls the environment you are building for:
When doing a regular RP2040 build (PICO_PLATFORM=rp2040, the default), you get code from common, rp2_common and rp2040;
when doing a host build (PICO_PLATFROM=host), you get code from common and host.
Within each top-level directory, the libraries have the following structure (reading foo_bar as something like hardware_uart
or pico_time)
top-level_dir/
top-level_dir/foo_bar/include/foo/bar.h # header file
top-level_dir/foo_bar/CMakeLists.txt # build configuration
top-level_dir/foo_bar/bar.c # source file(s)
As a concrete example, we can list the hardware_uart directory under pico-sdk/rp2_common (you may also recall the
hardware_gpio library we looked at earlier):
hardware_uart
├── CMakeLists.txt
├── include
│ └── hardware
│ └── uart.h
└── uart.c
uart.h contains function declarations and preprocessor defines for the hardware_uart library, as well as some inline
functions that are expected to be particularly amenable to constant folding by the compiler. uart.c contains the
implementations of more complex functions, such as calculating and setting up the divisors for a given UART baud rate.
NOTE
The directory top-level_dir/foo_bar/include is added as an include directory to the INTERFACE library foo_bar, which is
what allows you to include "foo/bar.h" in your application
Functions are prefixed by the library/functional area they belong to; e.g. public functions in the hardware_dma library are
prefixed with dma_. Sometime the prefix refers to a sub group of library functionality (e.g. channel_config_ )
2.5.1.2. Verb
A verb typically follows the prefix specifying that action performed by the function. set_ and get_ (or is_ for booleans) are
probably the most common and should always be present; i.e. a hypothetical method would be oven_get_temperature() and
food_add_salt(), rather than oven_temperature() and food_salt().
2.5.1.3. Suffixes
_blocking The method is blocking, and may potentially block indefinitely until
some specific condition is met.
_blocking_until absolute_time_t until The method is blocking until some specific condition is met,
however it will return early with a timeout condition (see Section
2.5.2) if the until time is reached.
_timeout_ms uint32_t timeout_ms The method is blocking until some specific condition is met,
however it will return early with a timeout condition (see Section
2.5.2) after the specified number of milliseconds
_timeout_us uint64_t timeout_us The method is blocking until some specific condition is met,
however it will return early with a timeout condition (see Section
2.5.2) after the specified number of microseconds
In many cases checking for obviously invalid (likely program bug) parameters in (often inline) functions is prohibitively
expensive in speed and code size terms, and therefore we need to be able to configure it on/off, which precludes return
codes being returned for these exceptional cases.
1. Methods that can legitimately fail at runtime due to runtime conditions e.g. timeouts, dynamically allocated resource,
can return a status which is either a bool indicating success or not, or an integer return code from the PICO_ERROR_
family; non error returns are >= 0.
2. Other items like invalid parameters, or failure to allocate resources which are deemed program bugs (e.g. two
libraries trying to use the same statically assigned piece of hardware) do not affect a return code (usually the
functions return void) and must cause some sort of exceptional event.
As of right now the exceptional event is a C assert, so these checks are always disabled in release builds by default.
Additionally most of the calls to assert are disabled by default for code/size performance (even in debug builds); You
can set PARAMS_ASSERTIONS_ENABLE_ALL=1 or PARAMS_ASSERTIONS_DISABLE_ALL=1 in your build to change the default across
the entire SDK, or say PARAM_ASSERTIONS_ENABLED_I2C=0/1 to explicitly specify the behavior for the hardware_i2c module
In the future we expect to support calling a custom function to throw an exception in C++ or other environments
where stack unwinding is possible.
3. Obviously sometimes the calling code whether it be user code or another higher level function, may not want the
called function to assert on bad input, in which case it is the responsibility of the caller to check the validity (there are
a good number of API functions provided that help with this) of their arguments, and the caller can then choose to
provide a more flexible runtime error experience.
The code space needed to setup parameters for a regular call to a small function in another compilation unit can be
substantially larger than the function implementation. Compilers have their own metrics to decide when to inline function
implementations at their call sites, but the use of static inline definitions gives the compiler more freedom to do this.
One reason this is particularly effective in the context of hardware register access is that these functions often:
2. Are immediately shifted and masked to combine with some register value, and
So if the implementation of a hardware access function is inlined, the compiler can propagate the constant parameters
through whatever bit manipulation and arithmetic that function may do, collapsing a complex function down to "please
write this constant value to this constant address". Again, we are not forcing the compiler to do this, but the SDK
consistently tries to give it freedom to do so.
The result is that there is generally no overhead using the lower-level hardware_ functions as compared with using
preprocessor macros with the hardware_regs definitions, and they tend to be much less error-prone.
1. Readability of code (avoid "death by parameters" where a configuration function takes a dozen integers and
booleans)
3. Less brittle (the addition of another item to a hardware configuration will not break existing code)
Take the following hypothetical code example to (quite extensively) configure a DMA channel:
int dma_channel = 3;
dma_channel_config config = dma_get_default_channel_config(dma_channel);
channel_config_set_read_increment(&config, true);
channel_config_set_write_increment(&config, true);
channel_config_set_dreq(&config, DREQ_SPI0_RX);
channel_config_set_transfer_data_size(&config, DMA_SIZE_8);
dma_set_config(dma_channel, &config, false);
The value of dma_channel is known at compile time, so the compiler can replace dma_channel with 3 when generating code
(constant folding). The dma_ methods are static inline methods (from https://github.com/raspberrypi/pico-sdk/tree/
master/src/rp2_common/hardware_dma/include/hardware/dma.h) meaning the implementations can be folded into your
code by the compiler and, consequently, your constant parameters (like DREQ_SPI0_RX) are propagated though this local
copy of the function implementation. The resulting code is usually smaller, and certainly faster, than the register shuffling
caused by setting up a function call.
The net effect is that the compiler actually reduces all of the above to the following code:
It may seem counterintuitive that building up the configuration by passing a struct around, and committing the final result
to the IO register, would be so much more compact than a series of direct register modifications using register field
accessors. This is because the compiler is customarily forbidden from eliminating IO accesses (illustrated here with a
volatile keyword), with good reason. Consequently it’s easy to unwittingly generate code that repeatedly puts a value into
a register and pulls it back out again, changing a few bits at a time, when we only care about the final value of the register.
The configuration pattern shown here avoids this common pitfall.
NOTE
The SDK code is designed to make builder patterns efficient in both Release and Debug builds. Additionally, even if not
all values are known constant at compile time, the compiler can still produce the most efficient code possible based
on the values that are known.
Remember that because of the use of INTERFACE libraries, all the libraries your application(s) depend on are built from
source for each application in your build, so you can even build multiple variants of the same application with different
baked in behaviors.
Appendix B has a comprehensive list of the available preprocessor defines, what they do, and what their default values
are.
Preprocessor variables may be specified in a number of ways, described in the following sections.
NOTE
Whether compile time configuration or runtime configuration or both is supported/required is dependent on the
particular library itself. The general philosophy however, is to allow sensible default behavior without the user
specifying any settings (beyond those provided by the board configuration).
The board configuration provides a header file which specifies defaults if not otherwise specified; for example
https://github.com/raspberrypi/pico-sdk/tree/master/src/boards/include/boards/pico.h specifies
#ifndef PICO_DEFAULT_LED_PIN
#define PICO_DEFAULT_LED_PIN 25
#endif
The header my_board_name.h is included by all other SDK headers as a result of setting PICO_BOARD=my_board_name. You may
wish to specify your own board configuration in which case you can set PICO_BOARD_HEADER_DIRS in the environment
or CMake to a semicolon separated list of paths to search for my_board_name.h.
add_executable(hello_world
hello_world.c
)
The target_compile_definitions specifies preprocessor definitions that will be passed to the compiler for every source file
in the target hello_world (which as mentioned before includes all of the sources for all dependent INTERFACE libraries).
PRIVATE is required by CMake to specify the scope for the compile definitions. Note that all preprocessor definitions used
by the SDK have a PICO_ prefix.
• A UART interface specified by a board configuration header. The default for Raspberry Pi Pico is 115200 baud on
GPIO0 (TX) and GPIO1 (RX)
• A USB CDC ACM virtual serial port, using TinyUSB’s CDC support. The virtual serial device can be accessed through
RP2040’s dedicated USB hardware interface, in Device mode.
• (Experimental) minimal semihosting support to direct stdout to an external debug host connected via the Serial Wire
Debug link on RP2040
These can be accessed using standard calls like printf, puts, getchar, found in the standard <stdio.h> header. By default,
stdout converts bare linefeed characters to carriage return plus linefeed, for better display in a terminal emulator. This can
be disabled at runtime, at build time, or the CR-LF support can be completely removed.
stdout is broadcast to all interfaces that are enabled, and stdin is collected from all interfaces which are enabled and
support input. Since some of the interfaces, particularly USB, have heavy runtime and binary size cost, only the UART
interface is included by default. You can add/remove interfaces for a given program at build time with e.g.
pico_enable_stdio_usb(target_name, 1)
The physical ROM storage on RP2040 has single-cycle access (with a dedicated arbiter on the RP2040 busfabric), and
accessing code stored here does not put pressure on the flash cache or take up space in memory, so not only are the
routines fast, the rest of your code will run faster due them being resident in ROM.
This implementation is used by default as it is the best choice in the majority of cases, however it is also possible to
switch to using the regular compiler soft floating point support.
2.7.2.1. Functions
The SDK provides implementations for all the standard functions from math.h. Additional functions can be found in
pico/float.h and pico/double.h.
The overall goal for the bootrom floating-point routines is to achieve good performance within a small footprint, the
emphasis being more on improved performance for the basic operations (add, subtract, multiply, divide and square root,
and all conversion functions), and more on reduced footprint for the scientific functions (trigonometric functions,
logarithms and exponentials).
The IEEE single- and double-precision data formats are used throughout, but in the interests of reducing code size, input
denormals are treated as zero and output denormals are flushed to zero, and output NaNs are rendered as infinities. Only
the round-to-nearest, even-on-tie rounding mode is supported. Traps are not supported. Whether input NaNs are treated
as infinities or propagated is configurable.
The five basic operations (add, subtract, multiply, divide, sqrt) return results that are always correctly rounded (round-to-
nearest).
The scientific functions always return results within 1 ULP (unit in last place) of the exact result. In many cases results are
better.
The scientific functions are calculated using internal fixed-point representations so accuracy (as measured in ULP error
rather than in absolute terms) is poorer in situations where converting the result back to floating point entails a large
normalising shift. This occurs, for example, when calculating the sine of a value near a multiple of pi, the cosine of a value
near an odd multiple of pi/2, or the logarithm of a value near 1. Accuracy of the tangent function is also poorer when the
result is very large. Although covering these cases is possible, it would add considerably to the code footprint, and there
are few types of program where accuracy in these situations is essential.
NOTE
Whilst the SDK floating point support makes use of the routines in the RP2040 bootrom, it hides some of the
limitations of the raw ROM functions (e.g. limited sin/cos range), in order to be largely indistinguishable from the
compiler-provided functionality. Certain smaller functions have also been re-implemented for even more speed outside
of the limited bootrom space.
Table 4. SDK
implementation vs
GCC 9 implementation
for ARM AEABI
floating point
functions (these
unusually named
__aeabi_f2d 20 31 155%
Name Description
compiler Use the standard compiler provided soft floating point implementations
pico Map all functions to an runtime assertion. You can use this when you know you don’t
want any floating point support to make sure it isn’t accidentally pulled in by some library.
These settings can be set independently for both "float" and "double":
For "float" you can call pico_set_float_implementation(TARGET NAME) in your CMakeListst.txt to choose a specific
implementation for a particular target, or set the CMake variable PICO_DEFAULT_FLOAT_IMPL to pico_float_NAME to set the
default.
For "double" you can call pico_set_double_implementation(TARGET NAME) in your CMakeListst.txt to choose a specific
implementation for a particular target, or set the CMake variable PICO_DEFAULT_DOUBLE_IMPL to pico_double_NAME to set the
default.
TIP
The pico floating point library adds very little to your binary size, however it must include implementations for any used
functions that are not present in V1 of the bootrom, which is present on early Raspberry Pi Pico boards. If you know
that you are only using RP2040s with V2 of the bootrom, then you can specify defines PICO_FLOAT_SUPPORT_ROM_V1=0 and
PICO_DOUBLE_SUPPORT_ROM_V1=0 so the extra code will not be included. Any use of those functions on a RP2040 with a V1
bootrom will cause a panic at runtime. See the RP2040 Datasheet for more specific details of the bootrom functions.
The SDK implementation by default treats input NaNs as infinites. If you require propagation of NaN inputs to outputs and
NaN outputs for domain errors, then you can set the compile definitions PICO_FLOAT_PROPAGATE_NANS and
PICO_DOUBLE_PROPAGATE_NANS to 1, at the cost of a small runtime overhead.
See Figure 1 and Figure 2 for 32-bit and 64-bit integer divider comparison.
Core 1 (the second core) is started by calling multicore_launch_core1(some_function_pointer); on core 0, which wakes the
core from its low-power sleep state and provides it with its entry point — some function you have provided which hopefully
with a descriptive name like void core1_main() { }. This function, as well as others such as pushing and popping data
through the inter-core mailbox FIFOs, is listed under pico_multicore.
Care should be taken with calling C library functions from both cores simultaneously as they are generally not designed to
be thread safe. You can use the mutex_ API provided by the SDK in the pico_sync library (
https://github.com/raspberrypi/pico-sdk/tree/master/src/common/pico_sync/include/pico/mutex.h) from within your
own code.
NOTE
That the SDK version of printf is always safe to call from both cores. malloc, calloc and free are additionally wrapped to
make it thread safe when you include the pico_multicore as a convenience for C++ programming, where some object
allocations may not be obvious.
C++ files are integrated into SDK projects in the same way as C files: listing them in your CMakeLists file under either the
add_executable() entry, or a separate target_sources() entry to append them to your target.
To save space, exception handling is disabled by default; this can be overridden with the CMake environment variable
PICO_CXX_ENABLE_EXCEPTIONS=1. There are a handful of other C++ related PICO_CXX vars listed in (Appendix C).
Chapter 3 gives some background on RP2040’s unique Programmable I/O subsystem, and walks through building some
applications which use PIO to talk to external hardware.
Chapter 4 is a comprehensive listing of the SDK APIs. The APIs are listed according to groups of related functionality (e.g.
low-level hardware access).
PIO hardware is described extensively in chapter 3 of the RP2040 Datasheet. This is a companion to that text, focussing
on how, when and why to use PIO in your software. To start, we’re going to spend a while discussing why I/O is hard, what
the current options are, and what PIO does differently, before diving into some software tutorials. We will also try to
illuminate some of the more important parts of the hardware along the way, but will defer to the datasheet for full
explanations.
TIP
You can skip to the first software tutorial if you’d prefer to dive straight in.
3.1.1. Background
Interfacing with other digital hardware components is hard. It often happens at very high frequencies (due to amounts of
data that need to be transferred), and has very exact timing requirements.
The custom hardware components take care of specific tasks that the more general multi-tasking CPU is not designed
for. The operating system drivers perform higher level management of what the hardware components do, and
coordinate data transfers via DMA to/from memory from the controller and receive IRQs when high level tasks need
attention. These interfaces are purpose-built, and if you have them, you should use them.
These protocols are simpler to integrate into very low-cost devices (i.e. not the host), due to their relative simplicity and
modest speed. This is important for chips with mostly analogue or high-power circuitry: the silicon fabrication techniques
used for these chips do not lend themselves to high speed or gate count, so if your switchmode power supply controller
has some serial configuration interface, it is likely to be something like I2C. The number of traces routed on the circuit
board, the number of pins required on the device package, and the PCB technology required to maintain signal integrity
are also factors in the choice of these protocols. A microcontroller needs to communicate with these devices to be part of
a larger embedded system.
This is all very well, but the area taken up by these individual serial peripherals, and the associated cost, often leaves you
with a limited menu. You may end up paying for a bunch of stuff you don’t need, and find yourself without enough of what
you really want. Of course you are out of luck if your microcontroller does not have dedicated hardware for the type of
hardware device you want to attach (although in some cases you may be able to bridge over USB, I2C or SPI at the cost of
buying external hardware).
As a bit of background it is worth thinking about types of hardware that you might want to interface, and the approximate
signalling speeds involved:
Table 5. Types of
Interface Speed Interface
hardware
"Bit-Banging" (i.e. using the processor to hammer out the protocol via the GPIOs) is very hard. The processor isn’t really
designed for this. It has other work to do… for slower protocols you might be able to use an IRQ to wake up the processor
from what it was doing fast enough (though latency here is a concern) to send the next bit(s). Indeed back in the early
days of PC sound it was not uncommon to set a hardware timer interrupt at 11kHz and write out one 8-bit PCM sample
every interrupt for some rather primitive sounding audio!
Doing that on a PC nowadays is laughed at, even though they are many order of magnitudes faster than they were back
then. As processors have become faster in terms of overwhelming number-crunching brute force, the layers of software
and hardware between the processor and the outside world have also grown in number and size. In response to the
growing distance between processors and memory, PC-class processors keep many hundreds of instructions in-flight on
a single core at once, which has drawbacks when trying to switch rapidly between hard real time tasks. However, IRQ-
Above certain speeds — say a factor of 1000 below the processor clock speed — IRQs become impractical, in part due to
the timing uncertainty of actually entering an interrupt handler. The alternative when "bit-banging" is to sit the processor in
a carefully timed loop, often painstakingly written in assembly, trying to make sure the GPIO reading and writing happens
on the exact cycle required. This is really really hard work if indeed possible at all. Many heroic hours and likely thousands
of Github repositories are dedicated to the task of doing such things (a large proportion of them for LED strings).
Additionally of course, your processor is now busy doing the "bit-banging", and cannot be used for other tasks. If your
processor is interrupted even for a few microseconds to attend to one of the hard peripherals it is also responsible for,
this can be fatal to the timing of any bit-banged protocol. The greater the ratio between protocol speed and processor
speed, the more cycles your processor will spend uselessly idling in between GPIO accesses. Whilst it is eminently
possible to drive a 115200 baud UART output using only software, this has a cost of >10 000 cycles per byte if the
processor is running at 133 MHz, which may be poor investment of those cycles.
Whilst dealing with something like an LED string is possible using "bit-banging", once your hardware protocol gets faster to
the point that it is of similar order of magnitude to your system clock speed, there is really not much you can hope to do.
The main case where software GPIO access is the best choice is LEDs and push buttons.
Therefore you’re back to custom hardware for the protocols you know up front you are going to want (or more accurately,
the chip designer thinks you might need).
The main drawback of FPGAs in embedded systems is their cost. They also present a very unfamiliar programming
model to those well-versed in embedded software: you are not programming at all, but rather designing digital hardware.
One you have your FPGA you will still need some other processing element in your system to run control software, unless
you are using an FPGA expensive enough to either fit a soft CPU core, or contain a hardened CPU core alongside the
FPGA fabric.
eFPGAs (embedded FPGAs) are available in some microcontrollers: a slice of FPGA logic fabric integrated into a more
conventional microcontroller, usually with access to some GPIOs, and accessible over the system bus. These are
attractive from a system integration point of view, but have a significant area overhead compared with the usual serial
peripherals found on a microcontroller, so either increase the cost and power dissipation, or are very limited in size. The
issue of programming complexity still remains in eFPGA-equipped systems.
These programs operate with cycle accuracy at up to system clock speed (or the program clocks can be divided down to
run at slower speeds for less frisky protocols).
PIO state machines are much more compact than the general-purpose Cortex-M0+ processors on RP2040. In fact, they
are similar in size (and therefore cost) to a standard SPI peripheral, such as the PL022 SPI also found on RP2040,
because much of their area is spent on components which are common to all serial peripherals, like FIFOs, shift registers
and clock dividers. The instruction set is small and regular, so not much silicon is spent on decoding the instructions.
There is no need to feel guilty about dedicating a state machine solely to a single I/O task, since you have 8 of them!
In spite of this, a PIO state machine gets a lot more done in one cycle than a Cortex-M0+ when it comes to I/O: for
example, sampling a GPIO value, toggling a clock signal and pushing to a FIFO all in one cycle, every cycle. The tradeoff is
that a PIO state machine is not remotely capable of running general purpose software. As we shall see though,
programming a PIO state machine is quite familiar for anyone who has written assembly code before, and the small
instruction set should be fairly quick to pick up for those who haven’t.
For simple hardware protocols - such as PWM or duplex SPI - a single PIO state machine can handle the task of
implementing the hardware interface all on its own. For more involved protocols such as SDIO or DPI video you may end
up using two or three.
TIP
If you are ever tempted to "bit-bang" a protocol on RP2040, don’t! Use the PIO instead. Frankly this is true for anything
that repeatedly reads or writes from GPIOs, but certainly anything which aims to transfer data.
Additionally the future intent is to add APIs to trivially have new UARTs, PWM channels etc created for you, using a menu
of pre-written PIO programs, but for now you’ll have to follow along with example code and do that yourself.
• A PIO program
• Some software, written in C, to run the whole show
• A CMake file describing how these two are combined into a program image to load onto a RP2040-based
development board
TIP
The code listings in this section are all part of a complete application on Github, which you can build and run. Just click
the link above each listing to go to the source. In this section we are looking at the pio/hello_pio example in pico-
examples. You might choose to build this application and run it, to see what it does, before reading through this section.
NOTE
The focus here is on the main moving parts required to use a PIO program, not so much on the PIO program itself.
This is a lot to take in, so we will stay high-level in this example, and dig in deeper on the next one.
This is our first PIO program listing. It’s written in PIO assembly language.
7 .program hello
8
9 ; Repeatedly get one word of data from the TX FIFO, stalling when the FIFO is
10 ; empty. Write the least significant bit to the OUT pin group.
11
12 loop:
13 pull
14 out pins, 1
15 jmp loop
The pull instruction takes one data item from the transmit FIFO buffer, and places it in the output shift register (OSR).
Data moves from the FIFO to the OSR one word (32 bits) at a time. The OSR is able to shift this data out, one or more bits
at a time, to further destinations, using an out instruction.
FIFOs?
FIFOs are data queues, implemented in hardware. Each state machine has two FIFOs, between the state
machine and the system bus, for data travelling out of (TX) and into (RX) the chip. Their name (first in, first
out) comes from the fact that data appears at the FIFO’s output in the same order as it was presented to
the FIFO’s input.
The out instruction here takes one bit from the data we just pull-ed from the FIFO, and writes that data to some pins. We
will see later how to decide which pins these are.
The jmp instruction jumps back to the loop: label, so that the program repeats indefinitely. So, to sum up the function of
this program: repeatedly take one data item from a FIFO, take one bit from this data item, and write it to a pin.
Our .pio file also contains a helper function to set up a PIO state machine for correct execution of this program:
18 static inline void hello_program_init(PIO pio, uint sm, uint offset, uint pin) {
19 pio_sm_config c = hello_program_get_default_config(offset);
20
21 // Map the state machine's OUT pin group to one pin, namely the `pin`
22 // parameter to this function.
23 sm_config_set_out_pins(&c, pin, 1);
24 // Set this pin's GPIO function (connect PIO to the pad)
25 pio_gpio_init(pio, pin);
26 // Set the pin direction to output at the PIO
27 pio_sm_set_consecutive_pindirs(pio, sm, pin, 1, true);
28
29 // Load our configuration, and jump to the start of the program
30 pio_sm_init(pio, sm, offset, &c);
31 // Set the state machine running
Here the main thing to set up is the GPIO we intend to output our data to. There are three things to consider here:
1. The state machine needs to be told which GPIO or GPIOs to output to. There are four different pin groups which are
used by different instructions in different situations; here we are using the out pin group, because we are just using
an out instruction.
2. The GPIO also needs to be told that PIO is in control of it (GPIO function select)
3. If we are using the pin for output only, we need to make sure that PIO is driving the output enable line high. PIO can
drive this line up and down programmatically using e.g. an out pindirs instruction, but here we are setting it up
before starting the program.
3.2.1.2. C Program
PIO won’t do anything until it’s been configured properly, so we need some software to do that. The PIO file we just looked
at — hello.pio — is converted automatically (we will see later how) into a header containing our assembled PIO program
binary, any helper functions we included in the file, and some useful information about the program. We include this as
hello.pio.h.
1 /**
2 * Copyright (c) 2020 Raspberry Pi (Trading) Ltd.
3 *
4 * SPDX-License-Identifier: BSD-3-Clause
5 */
6
7 #include "pico/stdlib.h"
8 #include "hardware/pio.h"
9 // Our assembled program:
10 #include "hello.pio.h"
11
12 int main() {
13 // Choose which PIO instance to use (there are two instances)
14 PIO pio = pio0;
15
16 // Our assembled program needs to be loaded into this PIO's instruction
17 // memory. This SDK function will find a location (offset) in the
18 // instruction memory where there is enough space for our program. We need
19 // to remember this location!
20 uint offset = pio_add_program(pio, &hello_program);
21
22 // Find a free state machine on our chosen PIO (erroring if there are
23 // none). Configure it to run our program, and start it, using the
24 // helper function we included in our .pio file.
25 uint sm = pio_claim_unused_sm(pio, true);
26 hello_program_init(pio, sm, offset, PICO_DEFAULT_LED_PIN);
27
28 // The state machine is now running. Any value we push to its TX FIFO will
29 // appear on the LED pin.
30 while (true) {
31 // Blink
32 pio_sm_put_blocking(pio, sm, 1);
33 sleep_ms(500);
34 // Blonk
35 pio_sm_put_blocking(pio, sm, 0);
36 sleep_ms(500);
37 }
38 }
You might recall that RP2040 has two PIO blocks, each of them with four state machines. Each PIO block has a 32-slot
instruction memory which is visible to the four state machines in the block. We need to load our program into this
instruction memory before any of our state machines can run the program. The function pio_add_program() finds free
space for our program in a given PIO’s instruction memory, and loads it.
32 Instructions?
This may not sound like a lot, but the PIO instruction set can be very dense once you fully explore its
features. A perfectly serviceable UART transmit program can be implemented in four instructions, as
shown in the pio/uart_tx example in pico-examples. There are also a couple of ways for a state machine to
execute instructions from other sources — like directly from the FIFOs — which you can read all about in
the RP2040 Datasheet.
Once the program is loaded, we find a free state machine and tell it to run our program. There is nothing stopping us from
ordering multiple state machines to run the same program. Likewise, we could instruct each state machine to run a
different program, provided they all fit into the instruction memory at once.
We’re configuring this state machine to output its data to the LED on your Raspberry Pi Pico board. If you have already
built and run the program, you probably noticed this already!
At this point, the state machine is running autonomously. The state machine will immediately stall, because it is waiting
for data in the TX FIFO, and we haven’t provided any. The processor can push data directly into the state machine’s TX
FIFO using the pio_sm_put_blocking() function. (_blocking because this function stalls the processor when the TX FIFO is
full.) Writing a 1 will turn the LED on, and writing a 0 will turn the LED off.
We have two lovely text files sat on our computer, with names ending with .pio and .c, but they aren’t doing us much good
there. A CMake file describes how these are built into a binary suitable for loading onto your Raspberry Pi Pico or other
RP2040-based board.
1 add_executable(hello_pio)
2
3 pico_generate_pio_header(hello_pio ${CMAKE_CURRENT_LIST_DIR}/hello.pio)
4
5 target_sources(hello_pio PRIVATE hello.c)
6
7 target_link_libraries(hello_pio PRIVATE
8 pico_stdlib
9 hardware_pio
10 )
11
12 pico_add_extra_outputs(hello_pio)
13
14 # add url via pico_set_program_url
15 example_auto_set_url(hello_pio)
• target_sources(): List the source code files for our hello_pio program. In this case, just one C file.
• target_link_libraries(): Make sure that our program is built with the PIO hardware API, so we can call functions like
pio_add_program() in our C file.
• pico_add_extra_outputs(): By default we just get an .elf file as the build output of our app. Here we declare we also
want extra build formats, like a .uf2 file which can be dragged and dropped directly onto a Raspberry Pi Pico
attached over USB.
Assuming you already have pico-examples and the SDK installed on your machine, you can run
mkdir build
cd build
cmake ..
make hello_pio
When serial data is presented at the LED’s input, it takes the first three bytes for itself (red, green, blue) and the remainder
is passed along to its serial data output. Often these LEDs are connected in a single long chain, each LED connected to a
common power supply, and each LED’s data output connected through to the next LED’s input. A long burst of serial data
to the first in the chain (the one with its data input unconnected) will deposit three bytes of RGB data in each LED, so their
colour and brightness can be individually programmed.
Ideally we would like to have all of our CPU cycles available to generate colour patterns to put on the lights, or to handle
any other responsibilities the processor may have in the embedded system the LEDs are connected to.
TIP
Once more, this section is going to discuss a real, complete program, that you can build and run on your Raspberry Pi
Pico. Follow the links above the program listings if you’d prefer to build the program yourself and run it, before going
through it in detail. This section explores the pio/ws2812 example in pico-examples.
7 .program ws2812
8 .side_set 1
9
10 .define public T1 2
11 .define public T2 5
12 .define public T3 3
13
14 .lang_opt python sideset_init = pico.PIO.OUT_HIGH
15 .lang_opt python out_init = pico.PIO.OUT_HIGH
16 .lang_opt python out_shiftdir = 1
17
18 .wrap_target
19 bitloop:
20 out x, 1 side 0 [T3 - 1] ; Side-set still takes place when instruction stalls
21 jmp !x do_zero side 1 [T1 - 1] ; Branch on the bit we shifted out. Positive pulse
22 do_one:
23 jmp bitloop side 1 [T2 - 1] ; Continue driving high, for a long pulse
24 do_zero:
25 nop side 0 [T2 - 1] ; Or drive low, for a short pulse
26 .wrap
The previous example was a bit of a whistle-stop tour of the anatomy of a PIO-based application. This time we will dissect
the code line-by-line. The first line tells the assembler that we are defining a program named ws2812:
.program ws2812
We can have multiple programs in one .pio file (and you will see this if you click the Github link above the main program
listing), and each of these will have its own .program directive with a different name. The assembler will go through each
program in turn, and all the assembled programs will appear in the output file.
Each PIO instruction is 16 bits in size. Generally, 5 of those bits in each instruction are used for the “delay” which is usually
0 to 31 cycles (after the instruction completes and before moving to the next instruction). If you have read the PIO chapter
of the RP2040 Datasheet, you may have already know that these 5 bits can be used for a different purpose:
.side_set 1
This directive .side_set 1 says we’re stealing one of those delay bits to use for "side set". The state machine will use this
bit to drive the values of some pins, once per instruction, in addition to what the instructions are themselves doing. This is
very useful for high frequency use cases (e.g. pixel clocks for DPI panels), but also for shrinking program size, to fit into
the shared instruction memory.
Note that stealing one bit has left our delay range from 0-15 (4 bits), but that is quite natural because you rarely want to
mix side set with lower frequency stuff. Because we didn’t say .side_set 1 opt, which would mean the side set is optional
(at the cost of another bit to say whether the instruction does a side set), we have to specify a side set value for every
instruction in the program. This is the side N you will see on each instruction in the listing.
.define public T1 2
.define public T2 5
.define public T3 3
.define lets you declare constants. The public keyword means that the assembler will also write out the value of the define
in the output file for use by other software: in the context of the SDK, this is a #define. We are going to use T1, T2 and T3 in
calculating the delay cycles on each instruction.
.lang_opt python
This is used to specify some PIO hardware defaults as used by the MicroPython PIO library. We don’t need to worry about
them in the context of SDK applications.
.wrap_target
We’ll ignore this for now, and come back to it later, when we meet its friend .wrap.
bitloop:
This is a label. A label tells the assembler that this point in your code is interesting to you, and you want to refer to it later
by name. Labels are mainly used with jmp instructions.
out x, 1 side 0 [T3 - 1] ; Side-set still takes place when instruction stalls
Finally we reach a line with a PIO instruction. There is a lot to see here.
• This is an out instruction. out takes some bits from the output shift register (OSR), and writes them somewhere else.
In this case, the OSR will contain pixel data destined for our LEDs.
• [T3 - 1] is the number of delay cycles (T3 minus 1). T3 is a constant we defined earlier.
• x (one of two scratch registers; the other imaginatively called y) is the destination of the write data. State machines
use their scratch registers to hold and compare temporary data.
The OSR is a staging area for data entering the state machine through the TX FIFO. Data is pulled from
the TX FIFO into the OSR one 32-bit chunk at a time. When an out instruction is executed, the OSR can
break this data into smaller pieces by shifting to the left or right, and sending the bits that drop off the end
to one of a handful of different destinations, such as the pins.
The amount of data to be shifted is encoded by the out instruction, and the direction of the shift (left or
right) is configured ahead of time. For full details and diagrams, see the RP2040 Datasheet.
So, the state machine will do the following operations when it executes this instruction:
1. Set 0 on the side set pin (this happens even if the instruction stalls because no data is available in the OSR)
2. Shift one bit out of the OSR into the x register. The value of the x register will be either 0 or 1.
3. Wait T3 - 1 cycles after the instruction (I.e. the whole thing takes T3 cycles since the instruction itself took a cycle).
Note that when we say cycle, we mean state machine execution cycles: a state machine can be made to execute at
a slower rate than the system clock, by configuring its clock divider.
jmp !x do_zero side 1 [T1 - 1] ; Branch on the bit we shifted out. Positive pulse
1. side 1 on the side set pin (this is the leading edge of our pulse)
2. If x == 0 then go to the instruction labelled do_zero, otherwise continue on sequentially to the next instruction
Let’s look at what our output pin has done so far in the program.
do_one:
jmp bitloop side 1 [T2 - 1] ; Continue driving high, for a long pulse
2. jmp unconditionally back to bitloop (the label we defined earlier, at the top of the program); the state machine is done
with this data bit, and will get another from its OSR
Figure 5. On a one T3 T1 T2
data bit, the line is
driven low for time T3, GPIO
high for time T1, then
high for an additional
time T2 This accounts for the case where we shifted a 1 data bit into the x register. For a 0 bit, we will have jumped over the last
instruction we looked at, to the instruction labelled do_zero:
do_zero:
nop side 0 [T2 - 1] ; Or drive low, for a short pulse
1. side 0 on the side set pin (the trailing edge of our pulse)
2. nop means no operation. We don’t have anything else we particularly want to do, so waste a cycle
Figure 6. On a zero T3 T1 T2
data bit, the line is
driven low for time T3, GPIO
high for time T1, then
low again for time T1
The final line of our program is this:
.wrap
This matches with the .wrap_target directive at the top of the program. Wrapping is a hardware feature of the state
machine which behaves like a wormhole: you go in through the .wrap statement and appear at the .wrap_target zero cycles
later, unless the .wrap is preceded immediately by a jmp whose condition is true. This is important for getting precise
timing with programs that must run quickly, and often also saves you a slot in the instruction memory.
TIP
Often an explicit .wrap_target/.wrap pair is not necessary, because the default configuration produced by pioasm has an
implicit wrap from the end of the program back to the beginning, if you didn’t specify one.
NOPs
NOP, or no operation, means precisely that: do nothing! You may notice there is no nop instruction defined
in the instruction set reference: nop is really a synonym for mov y, y in PIO assembly.
Why did we insert a nop in this example when we could have jmp-ed? Good question! It’s a dramatic device
we contrived so we could discuss nop and .wrap. Writing documentation is hard. In general, though, nop is
useful when you need to perform a side-set and have nothing else to do, or you need a very slightly longer
delay than is available on a single instruction.
It is hopefully becoming clear why our timings T1, T2, T3 are numbered this way, because what the LED string sees really
is one of these two cases:
The out instruction shifts data out from the OSR, and zeroes are shifted in from the other end to fill the vacuum. Because
the OSR is 32 bits wide, you will start getting zeroes once you have shifted out a total of 32 bits. There is a pull instruction
which explicitly takes data from the TX FIFO and put it in the OSR (stalling the state machine if the FIFO is empty).
However, in the majority of cases it is simpler to configure autopull, a mode where the state machine automatically refills
the OSR from the TX FIFO (an automatic pull) when a configured number of bits have been shifted out. Autopull happens
in the background, in parallel with whatever else the state machine may be up to (in other words it has a cost of zero
cycles). We’ll see how this is configured in the next section.
When we run pioasm on the .pio file we have been looking at, and ask it to spit out SDK code (which is the default), it will
create some static variables describing the program, and a method ws2812_default_program_config which configures a
PIO state machine based on user parameters, and the directives in the actual PIO program (namely the .side_set and
.wrap in this case).
Of course how you configure the PIO SM when using the program is very much related to the program you have written.
Rather than try to store a data representation off all that information, and parse it at runtime, for the use cases where
you’d like to encapsulate setup or other API functions with your PIO program, you can embed code within the .pio file.
31 static inline void ws2812_program_init(PIO pio, uint sm, uint offset, uint pin, float freq,
bool rgbw) {
32
33 pio_gpio_init(pio, pin);
34 pio_sm_set_consecutive_pindirs(pio, sm, pin, 1, true);
35
36 pio_sm_config c = ws2812_program_get_default_config(offset);
37 sm_config_set_sideset_pins(&c, pin);
38 sm_config_set_out_shift(&c, false, true, rgbw ? 32 : 24);
39 sm_config_set_fifo_join(&c, PIO_FIFO_JOIN_TX);
40
41 int cycles_per_bit = ws2812_T1 + ws2812_T2 + ws2812_T3;
42 float div = clock_get_hz(clk_sys) / (freq * cycles_per_bit);
43 sm_config_set_clkdiv(&c, div);
44
45 pio_sm_init(pio, sm, offset, &c);
46 pio_sm_set_enabled(pio, sm, true);
47 }
In this case we are passing through code for the SDK, as requested by this line you will see if you click the link on the
above listing to see the context:
% c-sdk {
We have here a function ws2812_program_init which is provided to help the user to instantiate an instance of the LED driver
program, based on a handful of parameters:
pio
sm
Which state machine on that PIO we want to configure to run the WS2812 program
offset
Where the PIO program was loaded in PIO’s 5-bit program address space
pin
freq
The frequency (or rather baud rate) we want to output data at.
rgbw
True if we are using 4-colour LEDs (red, green, blue, white) rather than the usual 3.
Such that:
• pio_gpio_init(pio, pin); Configure a GPIO for use by PIO. (Set the GPIO function select.)
• pio_set_consecutive_pindirs(pio, sm, pin, 1, true); Sets the PIO pin direction of 1 pin starting at pin number pin to
out
• pio_sm_config c = ws2812_program_default_config(offset); Get the default configuration using the generated function
for this program (this includes things like the .wrap and .side_set configurations from the program). We’ll modify this
configuration before loading it into the state machine.
• sm_config_sideset_pins(&c, pin); Sets the side set to write to pins starting at pin pin (we say starting at because if you
had .side_set 3, then it would be outputting values on numbers pin, pin+1, pin+2)
• sm_config_out_shift(&c, false, true, rgbw ? 32 : 24); False for shift_to_right (i.e. we want to shift out MSB first).
True for autopull. 32 or 24 for the number of bits for the autopull threshold, i.e. the point at which the state machine
triggers a refill of the OSR, depending on whether the LEDs are RGB or RGBW.
• int cycles_per_bit = ws2812_T1 + ws2812_T2 + ws2812_T3; This is the total number of execution cycles to output a
single bit. Here we see the benefit of .define public; we can use the T1 - T3 values in our code.
• float div = clock_get_hz(clk_sys) / (freq * cycles_per_bit); sm_config_clkdiv(&c, div); Slow the state machine’s
execution down, based on the system clock speed and the number of execution cycles required per WS2812 data bit,
so that we achieve the correct bit rate.
• pio_sm_init(pio, sm, offset, &c); Load our configuration into the state machine, and go to the start address (offset)
• pio_sm_enable(pio, sm, true); And make it go now!
At this point the program will be stuck on the first out waiting for data. This is because we have autopull enabled, the OSR
is initially empty, and there is no data to be pulled. The state machine refuses to continue until the first piece of data
arrives in the FIFO.
As an aside, this last point sheds some light on the slightly cryptic comment at the start of the PIO program:
out x, 1 side 0 [T3 - 1] ; Side-set still takes place when instruction stalls
This comment is giving us an important piece of context. We stall on this instruction initially, before the first data is added,
and also every time we finish sending the last piece of data at the end of a long serial burst. When a state machine stalls,
it does not continue to the next instruction, rather it will reattempt the current instruction on the next divided clock cycle.
However, side set still takes place. This works in our favour here, because we consequently always return the line to the
idle (low) state when we stall.
3.2.2.3. C Program
The companion to the .pio file we’ve looked at is a .c file which drives some interesting colour patterns out onto a string
of LEDs. We’ll just look at the parts that are directly relevant to PIO.
Here we are writing 32-bit values into the FIFO, one at a time, directly from the CPU. pio_sm_put_blocking is a helper method
that waits until there is room in the FIFO before pushing your data.
You’ll notice the << 8 in put_pixel(): remember we are shifting out starting with the MSB, so we want the 24-bit colour
values at the top. this works fine for WGBR too, just that the W is always 0.
This program has a handful of colour patterns, which call our put_pixel helper above to output a sequence of pixel values:
The main function loads the program onto a PIO, configures a state machine for 800 kbaud WS2812 transmission, and
then starts cycling through the colour patterns randomly.
RP2040 is equipped with a powerful direct memory access unit (DMA), which can transfer data for you in the background.
Suitably programmed, the DMA can make quite long sequences of transfers without supervision. Up to one word per
system clock can be transferred to or from a PIO state machine, which is, to be quite technically precise, more bandwidth
than you can shake a stick at. The bandwidth is shared across all state machines, but you can use the full amount on one
state machine.
Let’s take a look at the logic_analyser example, which uses PIO to sample some of RP2040’s own pins, and capture a logic
trace of what is going on there, at full system speed.
29 void logic_analyser_init(PIO pio, uint sm, uint pin_base, uint pin_count, float div) {
Our program consists only of a single in pins, <pin_count> instruction, with program wrapping and autopull enabled.
Because the amount of data to be shifted is only known at runtime, and because the program is so short, we are
generating the program dynamically here (using the pio_encode_ functions) instead of pushing it through pioasm. The
program is wrapped in a data structure stating how big the program is, and where it must be loaded — in this case origin
= 1 meaning "don’t care".
The input shift register (ISR) is the mirror image of the OSR. Generally data flows through a state machine
in one of two directions: System → TX FIFO → OSR → Pins, or Pins → ISR → RX FIFO → System. An in
instruction shifts data into the ISR.
If you don’t need the ISR’s shifting ability — for example, if your program is output-only — you can use the
ISR as a third scratch register. It’s 32 bits in size, the same as X, Y and the OSR. The full details are in the
RP2040 Datasheet.
We load the program into the chosen PIO, and then configure the input pin mapping on the chosen state machine so that
its in pins instruction will see the pins we care about. For an in instruction we only need to worry about configuring the
base pin, i.e. the pin which is the least significant bit of the in instruction’s sample. The number of pins to be sampled is
determined by the bit count parameter of the in pins instruction — it will sample n pins starting at the base we specified,
and shift them into the ISR.
We mentioned earlier that there are four pin groups to configure, to connect a state machine’s internal
data buses to the GPIOs it manipulates. A state machine accesses all pins within a group at once, and pin
groups can overlap. So far we have seen the out, side-set and in pin groups. The fourth is set.
The out group is the pins affected by shifting out data from the OSR, using out pins or out pindirs, up to
32 bits at a time. The set group is used with set pins and set pindirs instructions, up to 5 bits at a time,
with data that is encoded directly in the instruction. It’s useful for toggling control signals. The side-set
group is similar to the set group, but runs simultaneously with another instruction. Note: mov pin uses the
in or out group, depending on direction.
Configuring the clock divider optionally slows down the state machine’s execution: a clock divisor of n means 1
instruction will be executed per n system clock cycles. The default system clock frequency for SDK is 125 MHz.
sm_config_set_in_shift sets the shift direction to rightward, enables autopush, and sets the autopush threshold to 32. The
state machine keeps an eye on the total amount of data shifted into the ISR, and on the in which reaches or breaches a
total shift count of 32 (or whatever number you have configured), the ISR contents, along with the new data from the in.
goes straight to the RX FIFO. The ISR is cleared to zero in the same operation.
sm_config_set_fifo_join is used to manipulate the FIFOs so that the DMA can get more throughput. If we want to sample
every pin on every clock cycle, that’s a lot of bandwidth! We’ve finished describing how the state machine should be
configured, so we use pio_sm_init to load the configuration into the state machine, and get the state machine into a clean
initial state.
FIFO Joining
Each state machine is equipped with a FIFO going in each direction: the TX FIFO buffers data on its way
out of the system, and the RX FIFO does the same for data coming in. Each FIFO has four data slots, each
holding 32 bits of data. Generally you want FIFOs to be as deep as possible, so there is more slack time
between the timing-critical operation of a peripheral, and data transfers from system agents which may
be quite busy or have high access latency. However this comes with significant hardware cost.
If you are only using one of the two FIFOs — TX or RX — a state machine can pool its resources to provide
a single FIFO with double the depth. The RP2040 Datasheet goes into much more detail, including how
this mechanism actually works under the hood.
Our state machine is ready to sample some pins. Let’s take a look at how we hook up the DMA to our state machine, and
tell the state machine to start sampling once it sees some trigger condition.
51 void logic_analyser_arm(PIO pio, uint sm, uint dma_chan, uint32_t *capture_buf, size_t
capture_size_words,
52 uint trigger_pin, bool trigger_level) {
53 pio_sm_set_enabled(pio, sm, false);
54 pio_sm_clear_fifos(pio, sm);
55
56 dma_channel_config c = dma_channel_get_default_config(dma_chan);
57 channel_config_set_read_increment(&c, false);
58 channel_config_set_write_increment(&c, true);
59 channel_config_set_dreq(&c, pio_get_dreq(pio, sm, false));
60
61 dma_channel_configure(dma_chan, &c,
62 capture_buf, // Destinatinon pointer
63 &pio->rxf[sm], // Source pointer
64 capture_size_words, // Number of transfers
65 true // Start immediately
66 );
67
68 pio_sm_exec(pio, sm, pio_encode_wait_gpio(trigger_level, trigger_pin));
69 pio_sm_set_enabled(pio, sm, true);
70 }
We want the DMA to read from the RX FIFO on our PIO state machine, so every DMA read is from the same address. The
write address, on the other hand, should increment after every DMA transfer so that the DMA gradually fills up our capture
buffer as data comes in. We need to specify a data request signal (DREQ) so that the DMA transfers data at the proper
rate.
The DMA can transfer data incredibly fast, and almost invariably this will be much faster than your PIO
program actually needs. The DMA paces itself based on a data request handshake with the state
machine, so there’s no worry about it overflowing or underflowing a FIFO, as long as you have selected
the correct DREQ signal. The state machine coordinates with the DMA to tell it when it has room available
in its TX FIFO, or data available in its RX FIFO.
We need to provide the DMA channel with an initial read address, an initial write address, and the total number of
reads/writes to be performed (not the total number of bytes). We start the DMA channel immediately — from this point on,
the DMA is poised, waiting for the state machine to produce data. As soon as data appears in the RX FIFO, the DMA will
pounce and whisk the data away to our capture buffer in system memory.
As things stand right now, the state machine will immediately go into a 1-cycle loop of in instructions once enabled. Since
the system memory available for capture is quite limited, it would be better for the state machine to wait for some trigger
before it starts sampling. Specifically, we are using a wait pin instruction to stall the state machine until a certain pin goes
high or low, and again we are using one of the pio_encode_ functions to encode this instruction on-the-fly.
pio_sm_exec tells the state machine to immediately execute some instruction you give it. This instruction never gets written
to the instruction memory, and if the instruction stalls (as it will in this case — a wait instruction’s job is to stall) then the
state machine will latch the instruction until it completes. With the state machine stalled on the wait instruction, we can
enable it without being immediately flooded by data.
At this point everything is armed and waiting for the trigger signal from the chosen GPIO. This will lead to the following
sequence of events:
2. On the very next cycle, state machine will start to execute in instructions from the program memory
3. As soon as data appears in the RX FIFO, the DMA will start to transfer it.
4. Once the requested amount of data has been transferred by the DMA, it’ll automatically stop
So far our state machines have executed instructions from the instruction memory, but there are other
options. One is the SMx_INSTR register (used by pio_sm_exec()): the state machine will immediately execute
whatever you write here, momentarily interrupting the current program it’s running if necessary. This is
useful for poking around inside the state machine from the system side, for initial setup.
The other two options, which use the same underlying hardware, are out exec (shift out an instruction
from the data being streamed through the OSR, and execute it) and mov exec (execute an instruction
stashed in e.g. a scratch register). Besides making people’s eyes bulge, these are really useful if you want
the state machine to perform some data-defined operation at a certain point in an output stream.
The example code provides this cute function for displaying the captured logic trace as ASCII art in a terminal:
We have everything we need now for RP2040 to capture a logic trace of its own pins, whilst running some other program.
Here we’re setting up a PWM slice to output at around 15 MHz on two GPIOs, and attaching our brand spanking new logic
analyser to those same two GPIOs.
You can also browse the pio/ directory in the pico-examples repository.
If you have built the pico-examples repository at any point, you will likely already have a pioasm binary in your build directory,
located under build/tools/pioasm/pioasm, which was bootstrapped for you before building any applications that depend on
it. If we want a standalone copy of pioasm, perhaps just to explore the available commandline options, we can obtain it as
follows (assuming the SDK is extracted at $PICO_SDK_PATH):
mkdir pioasm_build
cd pioasm_build
cmake $PICO_SDK_PATH/tools/pioasm
make
./pioasm
3.3.1. Usage
A description of the command line arguments can be obtained by running:
pioasm -?
giving:
options:
-o <output_format> select output_format (default 'c-sdk'); available options are:
c-sdk
NOTE
Within the SDK you do not need to invoke pioasm directly, as the CMake function pico_generate_pio_header(TARGET
PIO_FILE) takes care of invoking pioasm and adding the generated header to the include path of the target TARGET for
you.
3.3.2. Directives
The following directives control the assembly of PIO programs:
Table 6. pioasm
.define ( PUBLIC ) <symbol> <value> Define an integer symbol named <symbol> with the value <value> (see Section
directives
3.3.3). If this .define appears before the first program in the input file, then the
define is global to all programs, otherwise it is local to the program in which it
occurs. If PUBLIC is specified the symbol will be emitted into the assembled
output for use by user code. For the SDK this takes the form of:
.program <name> Start a new program with the name <name>. Note that that name is used in
code so should be alphanumeric/underscore not starting with a digit. The
program lasts until another .program directive or the end of the source file. PIO
instructions are only allowed within a program
.origin <offset> Optional directive to specify the PIO instruction memory offset at which the
program must load. Most commonly this is used for programs that must load
at offset 0, because they use data based JMPs with the (absolute) jmp target
being stored in only a few bits. This directive is invalid outside of a program
.side_set <count> (opt) (pindirs) If this directive is present, <count> indicates the number of side set bits to be
used. Additionally opt may be specified to indicate that a side <value> is
optional for instructions (not using this requires stealing an extra bit - in addition
to the <count> bits - from those available for the instruction delay). Finally,
pindirs may be specified to indicate that the side set values should be applied to
the PINDIRs and not the PINs. This directive is only valid within a program
before the first instruction
.wrap_target Place prior to an instruction, this directive specifies the instruction where
execution continues due to program wrapping. This directive is invalid outside
of a program, may only be used once within a program, and if not specified
defaults to the start of the program
.wrap Placed after an instruction, this directive specifies the instruction after which, in
normal control flow (i.e. jmp with false condition, or no jmp), the program wraps
(to .wrap_target instruction). This directive is invalid outside of a program, may
only be used once within a program, and if not specified defaults to after the
last program instruction.
.lang_opt <lang> <name> <option> Specifies an option for the program related to a particular language generator.
(See Section 3.3.10). This directive is invalid outside of a program
.word <value> Stores a raw 16-bit value as an instruction in the program. This directive is
invalid outside of a program.
3.3.3. Values
The following types of values can be used to define integer numbers or branch targets
Table 7. Values in
integer An integer value e.g. 3 or -7
pioasm, i.e. <value>
<label> The instruction offset of the label within the program. This makes most sense when used with a
JMP instruction (see Section 3.4.2)
( <expression> ) An expression to be evaluated; see expressions. Note that the parentheses are necessary.
3.3.4. Expressions
Expressions may be freely used within pioasm values.
Table 8. Expressions
<expression> + <expression> The sum of two expressions
in pioasm i.e.
<expression>
<expression> - <expression> The difference of two expressions
3.3.5. Comments
Line comments are supported with // or ;
3.3.6. Labels
Labels are of the form:
<symbol>:
or
PUBLIC <symbol>:
TIP
A label is really just an automatic .define with a value set to the current program instruction offset. A PUBLIC label is
exposed to the user code in the same way as a PUBLIC .define.
3.3.7. Instructions
All pioasm instructions follow a common pattern:
where:
<instruction> Is an assembly instruction detailed in the following sections. (See Section 3.4)
<side_set_value> Is a value (see Section 3.3.3) to apply to the side_set pins at the start of the instruction. Note that
the rules for a side set value via side <side_set_value> are dependent on the .side_set (see
[pioasm_side_set]) directive for the program. If no .side_set is specified then the side
<side_set_value> is invalid, if an optional number of sideset pins is specified then side
<side_set_value> may be present, and if a non-optional number of sideset pins is specified, then side
<side_set_value> is required. The <side_set_value> must fit within the number of side set bits
specified in the .side_set directive.
<delay_value> Specifies the number of cycles to delay after the instruction completes. The delay_value is specified
as a value (see Section 3.3.3), and in general is between 0 and 31 inclusive (a 5-bit value), however
the number of bits is reduced when sideset is enabled via the .side_set (see [pioasm_side_set])
directive. If the <delay_value> is not present, then the instruction has no delay
NOTE
pioasm instruction names, keywords and directives are case insensitive; lower case is used in the Assembly Syntax
sections below as this is the style used in the SDK.
NOTE
Commas appear in some Assembly Syntax sections below, but are entirely optional, e.g. out pins, 3 may be written out
pins 3, and jmp x-- label may be written as jmp x--, label. The Assembly Syntax sections below uses the first style in
each case as this is the style used in the SDK.
3.3.8. Pseudoinstructions
Currently pioasm provides one pseudoinstruction, as a convenience:
nop Assembles to mov y, y. "No operation", has no particular side effect, but a useful vehicle for a side-set
operation or an extra delay.
For example the following (comment and function) would be included in the generated header when the default c-sdk
language generator is used.
% c-sdk {
% target {
pass through contents
%}
with targets being recognized by a particular language generator (see Section 3.3.10; note that target is usually the
language generator name e.g. c-sdk, but could potentially be some_language.some_some_group if the the language generator
supports different classes of pass through with different output locations.
This facility allows you to encapsulate both the PIO program and the associated setup required in the same source file.
See Section 3.3.10 for a more complete example.
1 ;
2 ; Copyright (c) 2020 Raspberry Pi (Trading) Ltd.
3 ;
4 ; SPDX-License-Identifier: BSD-3-Clause
5 ;
6
7 .program ws2812
8 .side_set 1
9
10 .define public T1 2
11 .define public T2 5
12 .define public T3 3
13
14 .lang_opt python sideset_init = pico.PIO.OUT_HIGH
15 .lang_opt python out_init = pico.PIO.OUT_HIGH
16 .lang_opt python out_shiftdir = 1
17
18 .wrap_target
19 bitloop:
20 out x, 1 side 0 [T3 - 1] ; Side-set still takes place when instruction stalls
21 jmp !x do_zero side 1 [T1 - 1] ; Branch on the bit we shifted out. Positive pulse
22 do_one:
23 jmp bitloop side 1 [T2 - 1] ; Continue driving high, for a long pulse
24 do_zero:
25 nop side 0 [T2 - 1] ; Or drive low, for a short pulse
26 .wrap
27
28 % c-sdk {
29 #include "hardware/clocks.h"
30
31 static inline void ws2812_program_init(PIO pio, uint sm, uint offset, uint pin, float freq,
bool rgbw) {
32
33 pio_gpio_init(pio, pin);
34 pio_sm_set_consecutive_pindirs(pio, sm, pin, 1, true);
35
36 pio_sm_config c = ws2812_program_get_default_config(offset);
37 sm_config_set_sideset_pins(&c, pin);
38 sm_config_set_out_shift(&c, false, true, rgbw ? 32 : 24);
39 sm_config_set_fifo_join(&c, PIO_FIFO_JOIN_TX);
40
41 int cycles_per_bit = ws2812_T1 + ws2812_T2 + ws2812_T3;
42 float div = clock_get_hz(clk_sys) / (freq * cycles_per_bit);
43 sm_config_set_clkdiv(&c, div);
44
45 pio_sm_init(pio, sm, offset, &c);
46 pio_sm_set_enabled(pio, sm, true);
47 }
48 %}
49
50 .program ws2812_parallel
51
52 .define public T1 2
53 .define public T2 5
54 .define public T3 3
55
56 .wrap_target
57 out x, 32
58 mov pins, !null [T1-1]
59 mov pins, x [T2-1]
60 mov pins, null [T3-2]
61 .wrap
62
63 % c-sdk {
64 #include "hardware/clocks.h"
65
66 static inline void ws2812_parallel_program_init(PIO pio, uint sm, uint offset, uint
pin_base, uint pin_count, float freq) {
67 for(uint i=pin_base; i<pin_base+pin_count; i++) {
68 pio_gpio_init(pio, i);
69 }
70 pio_sm_set_consecutive_pindirs(pio, sm, pin_base, pin_count, true);
71
72 pio_sm_config c = ws2812_parallel_program_get_default_config(offset);
73 sm_config_set_out_shift(&c, true, true, 32);
74 sm_config_set_out_pins(&c, pin_base, pin_count);
75 sm_config_set_set_pins(&c, pin_base, pin_count);
76 sm_config_set_fifo_join(&c, PIO_FIFO_JOIN_TX);
77
78 int cycles_per_bit = ws2812_parallel_T1 + ws2812_parallel_T2 + ws2812_parallel_T3;
79 float div = clock_get_hz(clk_sys) / (freq * cycles_per_bit);
80 sm_config_set_clkdiv(&c, div);
81
82 pio_sm_init(pio, sm, offset, &c);
83 pio_sm_set_enabled(pio, sm, true);
84 }
85 %}
3.3.10.1. c-sdk
The c-sdk language generator produces a single header file with all the programs in the PIO source file:
The pass through sections (% c-sdk {) are embedded in the output, and the PUBLIC defines are available via #define
TIP
pioasm creates a function for each program (e.g. ws2812_program_get_default_config()) returning a pio_sm_config based
on the .side_set, .wrap and .wrap_target settings of the program, which you can then use as a basis for configuration
the PIO state machine.
1 // -------------------------------------------------- //
2 // This file is autogenerated by pioasm; do not edit! //
3 // -------------------------------------------------- //
4
5 #if !PICO_NO_HARDWARE
6 #include "hardware/pio.h"
7 #endif
8
9 // ------ //
10 // ws2812 //
11 // ------ //
12
13 #define ws2812_wrap_target 0
14 #define ws2812_wrap 3
15
16 #define ws2812_T1 2
17 #define ws2812_T2 5
18 #define ws2812_T3 3
19
20 static const uint16_t ws2812_program_instructions[] = {
21 // .wrap_target
22 0x6221, // 0: out x, 1 side 0 [2]
23 0x1123, // 1: jmp !x, 3 side 1 [1]
24 0x1400, // 2: jmp 0 side 1 [4]
25 0xa442, // 3: nop side 0 [4]
26 // .wrap
27 };
28
29 #if !PICO_NO_HARDWARE
30 static const struct pio_program ws2812_program = {
31 .instructions = ws2812_program_instructions,
32 .length = 4,
33 .origin = -1,
34 };
35
36 static inline pio_sm_config ws2812_program_get_default_config(uint offset) {
37 pio_sm_config c = pio_get_default_sm_config();
38 sm_config_set_wrap(&c, offset + ws2812_wrap_target, offset + ws2812_wrap);
39 sm_config_set_sideset(&c, 1, false, false);
40 return c;
41 }
42
43 #include "hardware/clocks.h"
44 static inline void ws2812_program_init(PIO pio, uint sm, uint offset, uint pin, float freq,
bool rgbw) {
45 pio_gpio_init(pio, pin);
46 pio_sm_set_consecutive_pindirs(pio, sm, pin, 1, true);
47 pio_sm_config c = ws2812_program_get_default_config(offset);
109 }
110
111 #endif
3.3.10.2. python
The python language generator produces a single python file with all the programs in the PIO source file:
The pass through sections (% python {) would be embedded in the output, and the PUBLIC defines are available as python
variables.
Also note the use of .lang_opt python to pass initializers for the @pico.asm_pio decorator
TIP
The python language output is provided as a utility. MicroPython supports programming with the PIO natively, so you
may only want to use pioasm when sharing PIO code between the SDK and MicroPython. No effort is currently made
to preserve label names, symbols or comments, as it is assumed you are either using the PIO file as a source or
python; not both. The python language output can of course be used to bootstrap your MicroPython PIO development
based on an existing PIO file.
1 # -------------------------------------------------- #
2 # This file is autogenerated by pioasm; do not edit! #
3 # -------------------------------------------------- #
4
5 import rp2
6 from machine import Pin
7 # ------ #
8 # ws2812 #
9 # ------ #
10
11 ws2812_T1 = 2
12 ws2812_T2 = 5
13 ws2812_T3 = 3
14
15 @rp2.asm_pio(sideset_init=pico.PIO.OUT_HIGH, out_init=pico.PIO.OUT_HIGH, out_shiftdir=1)
16 def ws2812():
17 wrap_target()
18 label("0")
19 out(x, 1) .side(0) [2] # 0
20 jmp(not_x, "3") .side(1) [1] # 1
21 jmp("0") .side(1) [4] # 2
22 label("3")
23 nop() .side(0) [4] # 3
24 wrap()
25
26
27
28 # --------------- #
29 # ws2812_parallel #
30 # --------------- #
31
32 ws2812_parallel_T1 = 2
33 ws2812_parallel_T2 = 5
34 ws2812_parallel_T3 = 3
35
36 @rp2.asm_pio()
37 def ws2812_parallel():
38 wrap_target()
39 out(x, 32) # 0
40 mov(pins, not null) [1] # 1
41 mov(pins, x) [4] # 2
42 mov(pins, null) [1] # 3
43 wrap()
3.3.10.3. hex
The hex generator only supports a single input program, as it just dumps the raw instructions (one per line) as a 4-bit
hexadecimal number.
Given:
1 ;
2 ; Copyright (c) 2020 Raspberry Pi (Trading) Ltd.
3 ;
4 ; SPDX-License-Identifier: BSD-3-Clause
5 ;
6
7 .program squarewave
8 set pindirs, 1 ; Set pin to output
9 again:
10 set pins, 1 [1] ; Drive pin high and then delay for one cycle
11 set pins, 0 ; Drive pin low
12 jmp again ; Set PC to label `again`
e081
e101
e000
0001
This section refers in places to concepts and pieces of hardware discussed in the RP2040 Datasheet. You are
encouraged to read the PIO chapter of the datasheet to get the full context for what these instructions do.
3.4.1. Summary
PIO instructions are 16 bits long, and have the following encoding:
Table 9. PIO
Bit: 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0
instruction encoding
The Delay/side-set field is present in all instructions. Its exact use is configured for each state machine by
PINCTRL_SIDESET_COUNT:
• Up to 5 MSBs encode a side-set operation, which optionally asserts a constant value onto some GPIOs, concurrently
with main instruction execution logic
• Remaining LSBs (up to 5) encode the number of idle cycles inserted between this instruction and the next
3.4.2. JMP
3.4.2.1. Encoding
Bit: 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0
3.4.2.2. Operation
Delay cycles on a JMP always take effect, whether Condition is true or false, and they take place after Condition is evaluated
and the program counter is updated.
• Condition:
◦ 000: (no condition): Always
◦ 001: !X: scratch X zero
◦ 010: X--: scratch X non-zero, post-decrement
◦ 011: !Y: scratch Y zero
◦ 100: Y--: scratch Y non-zero, post-decrement
◦ 101: X!=Y: scratch X not equal scratch Y
◦ 110: PIN: branch on input pin
◦ 111: !OSRE: output shift register not empty
• Address: Instruction address to jump to. In the instruction encoding this is an absolute address within the PIO
instruction memory.
JMP PIN branches on the GPIO selected by EXECCTRL_JMP_PIN. The branch is taken if the GPIO is high.
!OSRE compares the bits shifted out since the last PULL with the shift count threshold configured by SHIFTCTRL_PULL_THRESH.
This is the same threshold used by autopull.
where:
<cond> Is an optional condition listed above (e.g. !x for scratch X zero). If a condition code is not specified, the
branch is always taken
<target> Is a program label or value (see Section 3.3.3) representing instruction offset within the program (the
first instruction being offset 0). Note that because the PIO JMP instruction uses absolute addresses in
the PIO instruction memory, JMPs need to be adjusted based on the program load offset at runtime.
This is handled for you when loading a program with the SDK, but care should be taken when encoding
JMP instructions for use by OUT EXEC
3.4.3. WAIT
3.4.3.1. Encoding
Bit: 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0
3.4.3.2. Operation
Like all stalling instructions, delay cycles begin after the instruction completes. That is, if any delay cycles are present, they
do not begin counting until after the wait condition is met.
• Polarity:
◦ 1: wait for a 1.
◦ 0: wait for a 0.
• Source: what to wait on. Values are:
◦ 00: GPIO: System GPIO input selected by Index. This is an absolute GPIO index, and is not affected by the state
machine’s input IO mapping.
◦ 01: PIN: Input pin selected by Index. This state machine’s input IO mapping is applied first, and then Index selects
which of the mapped bits to wait on.
• If Polarity is 1, the selected IRQ flag is cleared by the state machine upon the wait condition being met.
• The flag index is decoded in the same way as the IRQ index field: if the MSB is set, the state machine ID (0…3) is
added to the IRQ index, by way of modulo-4 addition on the two LSBs. For example, state machine 2 with a flag value
of '0x11' will wait on flag 3, and a flag value of '0x13' will wait on flag 1. This allows multiple state machines running
the same program to synchronise with each other.
CAUTION
WAIT 1 IRQ x should not be used with IRQ flags presented to the interrupt controller, to avoid a race condition with a
system interrupt handler
where:
<pin_num> Is a value (see Section 3.3.3) specifying the input pin number (as mapped by the SM input pin
mapping)
<gpio_num> Is a value (see Section 3.3.3) specifying the actual GPIO pin number
<irq_num> ( rel ) Is a value (see Section 3.3.3) specifying The irq number to wait on (0-7). If rel is present, then the
actual irq number used is calculating by replacing the low two bits of the irq number (irq_num10) with
the low two bits of the sum (irq_num10 + sm_num10) where sm_num10 is the state machine number
3.4.4. IN
3.4.4.1. Encoding
Bit: 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0
3.4.4.2. Operation
Shift Bit count bits from Source into the Input Shift Register (ISR). Shift direction is configured for each state machine by
SHIFTCTRL_IN_SHIFTDIR. Additionally, increase the input shift count by Bit count, saturating at 32.
• Source:
◦ 000: PINS
◦ 001: X (scratch register X)
◦ 010: Y (scratch register Y)
◦ 011: NULL (all zeroes)
◦ 100: Reserved
◦ 101: Reserved
◦ 110: ISR
◦ 111: OSR
• Bit count: How many bits to shift into the ISR. 1…32 bits, 32 is encoded as 00000.
If automatic push is enabled, IN will also push the ISR contents to the RX FIFO if the push threshold is reached
(SHIFTCTRL_PUSH_THRESH). IN still executes in one cycle, whether an automatic push takes place or not. The state machine
will stall if the RX FIFO is full when an automatic push occurs. An automatic push clears the ISR contents to all-zeroes,
and clears the input shift count.
IN always uses the least significant Bit count bits of the source data. For example, if PINCTRL_IN_BASE is set to 5, the
instruction IN 3, PINS will take the values of pins 5, 6 and 7, and shift these into the ISR. First the ISR is shifted to the left
or right to make room for the new input data, then the input data is copied into the gap this leaves. The bit order of the
input data is not dependent on the shift direction.
NULL can be used for shifting the ISR’s contents. For example, UARTs receive the LSB first, so must shift to the right. After
8 IN PINS, 1 instructions, the input serial data will occupy bits 31…24 of the ISR. An IN NULL, 24 instruction will shift in 24
zero bits, aligning the input data at ISR bits 7…0. Alternatively, the processor or DMA could perform a byte read from FIFO
address + 3, which would take bits 31…24 of the FIFO contents.
in <source>, <bit_count>
where:
<bit_count> Is a value (see Section 3.3.3) specifying the number of bits to shift (valid range 1-32)
3.4.5. OUT
3.4.5.1. Encoding
Bit: 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0
3.4.5.2. Operation
Shift Bit count bits out of the Output Shift Register (OSR), and write those bits to Destination. Additionally, increase the
output shift count by Bit count, saturating at 32.
• Destination:
◦ 000: PINS
◦ 001: X (scratch register X)
◦ 010: Y (scratch register Y)
◦ 011: NULL (discard data)
◦ 100: PINDIRS
◦ 101: PC
If automatic pull is enabled, the OSR is automatically refilled from the TX FIFO if the pull threshold, SHIFTCTRL_PULL_THRESH,
is reached. The output shift count is simultaneously cleared to 0. In this case, the OUT will stall if the TX FIFO is empty, but
otherwise still executes in one cycle.
OUT EXEC allows instructions to be included inline in the FIFO datastream. The OUT itself executes on one cycle, and the
instruction from the OSR is executed on the next cycle. There are no restrictions on the types of instructions which can be
executed by this mechanism. Delay cycles on the initial OUT are ignored, but the executee may insert delay cycles as
normal.
OUT PC behaves as an unconditional jump to an address shifted out from the OSR.
where:
<bit_count> Is a value (see Section 3.3.3) specifying the number of bits to shift (valid range 1-32)
3.4.6. PUSH
3.4.6.1. Encoding
Bit: 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0
3.4.6.2. Operation
Push the contents of the ISR into the RX FIFO, as a single 32-bit word. Clear ISR to all-zeroes.
• IfFull: If 1, do nothing unless the total input shift count has reached its threshold, SHIFTCTRL_PUSH_THRESH (the same as
for autopush).
push ( iffull )
where:
iffull Is equivalent to IfFull == 1 above. i.e. the default if this is not specified is IfFull == 0
block Is equivalent to Block == 1 above. This is the default if neither block nor noblock are specified
3.4.7. PULL
3.4.7.1. Encoding
Bit: 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0
3.4.7.2. Operation
• IfEmpty: If 1, do nothing unless the total output shift count has reached its threshold, SHIFTCTRL_PULL_THRESH (the same
as for autopull).
• Block: If 1, stall if TX FIFO is empty. If 0, pulling from an empty FIFO copies scratch X to OSR.
Some peripherals (UART, SPI…) should halt when no data is available, and pick it up as it comes in; others (I2S) should
clock continuously, and it is better to output placeholder or repeated data than to stop clocking. This can be achieved with
the Block parameter.
A nonblocking PULL on an empty FIFO has the same effect as MOV OSR, X. The program can either preload scratch register
X with a suitable default, or execute a MOV X, OSR after each PULL NOBLOCK, so that the last valid FIFO word will be recycled
until new data is available.
PULL IFEMPTY is useful if an OUT with autopull would stall in an inappropriate location when the TX FIFO is empty. For
example, a UART transmitter should not stall immediately after asserting the start bit. IfEmpty permits some of the same
program simplifications as autopull, but the stall occurs at a controlled point in the program.
NOTE
When autopull is enabled, any PULL instruction is a no-op when the OSR is full, so that the PULL instruction behaves as a
barrier. OUT NULL, 32 can be used to explicitly discard the OSR contents. See the RP2040 Datasheet for more detail on
autopull.
pull ( ifempty )
where:
ifempty Is equivalent to IfEmpty == 1 above. i.e. the default if this is not specified is IfEmpty == 0
block Is equivalent to Block == 1 above. This is the default if neither block nor noblock are specified
3.4.8. MOV
3.4.8.1. Encoding
Bit: 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0
3.4.8.2. Operation
• Destination:
◦ 000: PINS (Uses same pin mapping as OUT)
◦ 001: X (Scratch register X)
◦ 010: Y (Scratch register Y)
◦ 011: Reserved
◦ 100: EXEC (Execute data as instruction)
◦ 101: PC
◦ 110: ISR (Input shift counter is reset to 0 by this operation, i.e. empty)
◦ 111: OSR (Output shift counter is reset to 0 by this operation, i.e. full)
• Operation:
◦ 00: None
◦ 01: Invert (bitwise complement)
◦ 10: Bit-reverse
◦ 11: Reserved
• Source:
◦ 000: PINS (Uses same pin mapping as IN)
◦ 001: X
◦ 010: Y
◦ 011: NULL
◦ 100: Reserved
◦ 101: STATUS
◦ 110: ISR
◦ 111: OSR
MOV PC causes an unconditional jump. MOV EXEC has the same behaviour as OUT EXEC (Section 3.4.5), and allows register
contents to be executed as an instruction. The MOV itself executes in 1 cycle, and the instruction in Source on the next cycle.
Delay cycles on MOV EXEC are ignored, but the executee may insert delay cycles as normal.
The STATUS source has a value of all-ones or all-zeroes, depending on some state machine status such as FIFO full/empty,
configured by EXECCTRL_STATUS_SEL.
MOV can manipulate the transferred data in limited ways, specified by the Operation argument. Invert sets each bit in
Destination to the logical NOT of the corresponding bit in Source, i.e. 1 bits become 0 bits, and vice versa. Bit reverse sets
each bit n in Destination to bit 31 - n in Source, assuming the bits are numbered 0 to 31.
where:
3.4.9. IRQ
3.4.9.1. Encoding
Bit: 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0
3.4.9.2. Operation
• Clear: if 1, clear the flag selected by Index, instead of raising it. If Clear is set, the Wait bit has no effect.
• Wait: if 1, halt until the raised flag is lowered again, e.g. if a system interrupt handler has acknowledged the flag.
• Index:
◦ The 3 LSBs specify an IRQ index from 0-7. This IRQ flag will be set/cleared depending on the Clear bit.
◦ If the MSB is set, the state machine ID (0…3) is added to the IRQ index, by way of modulo-4 addition on the two
LSBs. For example, state machine 2 with a flag value of 0x11 will raise flag 3, and a flag value of 0x13 will raise
flag 1.
IRQ flags 4-7 are visible only to the state machines; IRQ flags 0-3 can be routed out to system level interrupts, on either of
the PIO’s two external interrupt request lines, configured by IRQ0_INTE and IRQ1_INTE.
The modulo addition bit allows relative addressing of 'IRQ' and 'WAIT' instructions, for synchronising state machines
which are running the same program. Bit 2 (the third LSB) is unaffected by this addition.
If Wait is set, Delay cycles do not begin until after the wait period elapses.
where:
<irq_num> ( rel ) Is a value (see Section 3.3.3) specifying The irq number to wait on (0-7). If rel is present, then the
actual irq number used is calculating by replacing the low two bits of the irq number (irq_num10) with
the low two bits of the sum (irq_num10 + sm_num10) where sm_num10 is the state machine number
irq wait Means set the IRQ and wait for it to be cleared before proceeding
3.4.10. SET
3.4.10.1. Encoding
Bit: 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0
3.4.10.2. Operation
• Destination:
• 000: PINS
• 001: X (scratch register X) 5 LSBs are set to Data, all others cleared to 0.
• 010: Y (scratch register Y) 5 LSBs are set to Data, all others cleared to 0.
• 011: Reserved
• 100: PINDIRS
• 101: Reserved
• 110: Reserved
• 111: Reserved
• Data: 5-bit immediate value to drive to pins or register.
This can be used to assert control signals such as a clock or chip select, or to initialise loop counters. As Data is 5 bits in
size, scratch registers can be SET to values from 0-31, which is sufficient for a 32-iteration loop.
The mapping of SET and OUT onto pins is configured independently. They may be mapped to distinct locations, for example
if one pin is to be used as a clock signal, and another for data. They may also be overlapping ranges of pins: a UART
transmitter might use SET to assert start and stop bits, and OUT instructions to shift out FIFO data to the same pins.
where:
<value> The value (see Section 3.3.3) to set (valid range 0-31)
hardware_base Low-level types and (atomic) accessors for memory-mapped hardware registers.
hardware_sync Low level hardware spin-lock, barrier and processor event API.
4.1.1. hardware_adc
Analog to Digital Converter (ADC) API.
The RP2040 has an internal analogue-digital converter (ADC) with the following features:
• SAR ADC
• 500 kS/s (Using an independent 48MHz clock)
• 12 bit (9.5 ENOB)
• 5 input mux:
• 4 inputs that are available on package pins shared with GPIO[29:26]
• 1 input is dedicated to the internal temperature sensor
• 4 element receive sample FIFO
• Interrupt generation
• DMA interface
Although there is only one ADC you can specify the input to it using the adc_select_input() function. In round robin mode
(adc_rrobin()) will use that input and move to the next one after a read.
User ADC inputs are on 0-3 (GPIO 26-29), the temperature sensor is on input 4.
T = 27 - (ADC_Voltage - 0.706)/0.001721
Example
1 #include <stdio.h>
2 #include "pico/stdlib.h"
3 #include "hardware/gpio.h"
4 #include "hardware/adc.h"
5
6 int main() {
7 stdio_init_all();
8 printf("ADC Example, measuring GPIO26\n");
9
10 adc_init();
11
12 // Make sure GPIO is high-impedance, no pullups etc
13 adc_gpio_init(26);
14 // Select ADC input 0 (GPIO26)
15 adc_select_input(0);
16
17 while (1) {
18 // 12-bit conversion, assume max value == ADC_VREF == 3.3 V
19 const float conversion_factor = 3.3f / (1 << 12);
20 uint16_t result = adc_read();
21 printf("Raw value: 0x%03x, voltage: %f V\n", result, result * conversion_factor);
22 sleep_ms(500);
23 }
24 }
4.1.1.2.1. adc_fifo_drain
Will wait for any conversion to complete then drain the FIFO discarding any results.
4.1.1.2.2. adc_fifo_get
4.1.1.2.3. adc_fifo_get_blocking
4.1.1.2.4. adc_fifo_get_level
The ADC FIFO is 4 entries long. This function will return how many samples are currently present.
4.1.1.2.5. adc_fifo_is_empty
Returns
4.1.1.2.6. adc_fifo_setup
FIFO is 4 samples long, if a conversion is completed and the FIFO is full the result is dropped.
Parameters
4.1.1.2.7. adc_gpio_init
Prepare a GPIO for use with ADC, by disabling all digital functions.
Parameters
• gpio The GPIO number to use. Allowable GPIO numbers are 26 to 29 inclusive.
4.1.1.2.8. adc_init
4.1.1.2.9. adc_irq_set_enabled
Parameters
4.1.1.2.10. adc_read
Performs an ADC conversion, waits for the result, and then returns it.
Returns
4.1.1.2.11. adc_run
Parameters
4.1.1.2.12. adc_select_input
Select an ADC input. 0…3 are GPIOs 26…29 respectively. Input 4 is the onboard temperature sensor.
Parameters
4.1.1.2.13. adc_set_clkdiv
Period of samples will be (1 + div) cycles on average. Note it takes 96 cycles to perform a conversion, so any period less
than that will be clamped to 96.
Parameters
• clkdiv If non-zero, conversion will be started at intervals rather than back to back.
4.1.1.2.14. adc_set_round_robin
This function sets which inputs are to be run through in round robin mode. Value between 0 and 0x1f (bit 0 to bit 4 for
GPIO 26 to 29 and temperature sensor input respectively)
Parameters
• input_mask A bit pattern indicating which of the 5 inputs are to be sampled. Write a value of 0 to disable round robin
sampling.
4.1.1.2.15. adc_set_temp_sensor_enabled
Parameters
• enable Set true to power on the onboard temperature sensor, false to power off.
4.1.2. hardware_base
Low-level types and (atomic) accessors for memory-mapped hardware registers.
hardware_base defines the low level types and access functions for memory mapped hardware registers. It is included by
default by all other hardware libraries.
The following register access typedefs codify the access type (read/write) and the bus size (8/16/32) of the hardware
register. The register type names are formed by concatenating one from each of the 3 parts A, B, C
A B C Meaning
When dealing with these types, you will always use a pointer, i.e. io_rw_32 *some_reg is a pointer to a read/write 32 bit
register that you can write with *some_reg = value, or read with value = *some_reg.
RP2040 hardware is also aliased to provide atomic setting, clear or flipping of a subset of the bits within a hardware
register so that concurrent access by two cores is always consistent with one atomic operation being performed first,
followed by the second.
See hw_set_bits(), hw_clear_bits() and hw_xor_bits() provide for atomic access via a pointer to a 32 bit register
Additionally given a pointer to a structure representing a piece of hardware (e.g. dma_hw_t *dma_hw for the DMA
controller), you can get an alias to the entire structure such that writing any member (register) within the structure is
equivalent to an atomic operation via hw_set_alias(), hw_clear_alias() or hw_xor_alias()…
For example hw_set_alias(dma_hw)→inte1 = 0x80; will set bit 7 of the INTE1 register of the DMA controller, leaving the
other bits unchanged.
4.1.2.2.1. hw_clear_bits
Parameters
4.1.2.2.2. hw_set_bits
Parameters
4.1.2.2.3. hw_write_masked
Sets destination bits to values specified in values, if and only if corresponding bit in write_mask is set
Note: this method allows safe concurrent modification of bits of a register, but multiple concurrent access to the same
bits is still unsafe.
Parameters
4.1.2.2.4. hw_xor_bits
Parameters
4.1.3. hardware_claim
Lightweight hardware resource management.
This API is usually called by other hardware specific claiming APIs and provides simple multi-core safe methods to
manipulate compact bit-sets representing hardware resources.
This API allows any other library to cooperatively participate in a scheme by which both compile time and runtime
allocation of resources can co-exist, and conflicts can be avoided or detected (depending on the use case) without the
libraries having any other knowledge of each other.
4.1.3.2.1. hw_claim_clear
Parameters
4.1.3.2.2. hw_claim_lock
uint32_t hw_claim_lock ()
Acquire the runtime mutual exclusion lock provided by the hardware_claim library.
This method is called automatically by the other hw_claim_ methods, however it is provided as a convenience to code that
might want to protect other hardware initialization code from concurrent use.
Returns
4.1.3.2.3. hw_claim_or_assert
Parameters
4.1.3.2.4. hw_claim_unlock
Release the runtime mutual exclusion lock provided by the hardware_claim library.
Parameters
4.1.3.2.5. hw_claim_unused_from_range
Atomically claim one resource out of a range of resources, optionally asserting if none are free.
Parameters
• the bit index representing the claimed or -1 if none are available in the range, and required = false
4.1.3.2.6. hw_is_claimed
Parameters
4.1.4. hardware_clocks
Clock Management API.
The clocks block provides independent clocks to on-chip and external components. It takes inputs from a variety of clock
sources allowing the user to trade off performance against cost, board area and power consumption. From these sources
it uses multiple clock generators to provide the required clocks. This architecture allows the user flexibility to start and
stop clocks independently and to vary some clock frequencies whilst maintaining others at their optimum frequencies
Please refer to the datasheet for more details on the RP2040 clocks.
The clock source depends on which clock you are attempting to configure. The first table below shows main clock
sources. If you are not setting the Reference clock or the System clock, or you are specifying that one of those two will be
using an auxiliary clock source, then you will need to use one of the entries from the subsequent tables.
ROSC CLOCKS_CLK_REF_CTRL_SRC_VALUE
_ROSC_CLKSRC_PH
XOSC CLOCKS_CLK_REF_CTRL_SRC_VALUE
_XOSC_CLKSRC
Reference CLOCKS_CLK_SYS_CTRL_SRC_VALUE
_CLK_REF
The auxiliary clock sources available for use in the configure function depend on which clock is being configured. The
following table describes the available values that can be used. Note that for clk_gpout[x], x can be 0-3.
GPIO in 0 CLOCKS_CLK_RTC_CTRL_AUXSRC_VALUE_CLKSRC_GPIN
0
GPIO in 1 CLOCKS_CLK_RTC_CTRL_AUXSRC_VALUE_CLKSRC_GPIN
1
ROSC CLOCKS_CLK_RTC_CTRL_AUXSRC_VALUE_ROSC_CLKSRC
_PH
XOSC CLOCKS_CLK_RTC_CTRL_AUXSRC_VALUE_XOSC_CLKSRC
Example// hello_48MHz.c
1 #include <stdio.h>
2 #include "pico/stdlib.h"
3 #include "hardware/pll.h"
4 #include "hardware/clocks.h"
5 #include "hardware/structs/pll.h"
6 #include "hardware/structs/clocks.h"
7
8 void measure_freqs(void) {
9 uint f_pll_sys = frequency_count_khz(CLOCKS_FC0_SRC_VALUE_PLL_SYS_CLKSRC_PRIMARY);
10 uint f_pll_usb = frequency_count_khz(CLOCKS_FC0_SRC_VALUE_PLL_USB_CLKSRC_PRIMARY);
11 uint f_rosc = frequency_count_khz(CLOCKS_FC0_SRC_VALUE_ROSC_CLKSRC);
12 uint f_clk_sys = frequency_count_khz(CLOCKS_FC0_SRC_VALUE_CLK_SYS);
13 uint f_clk_peri = frequency_count_khz(CLOCKS_FC0_SRC_VALUE_CLK_PERI);
14 uint f_clk_usb = frequency_count_khz(CLOCKS_FC0_SRC_VALUE_CLK_USB);
15 uint f_clk_adc = frequency_count_khz(CLOCKS_FC0_SRC_VALUE_CLK_ADC);
16 uint f_clk_rtc = frequency_count_khz(CLOCKS_FC0_SRC_VALUE_CLK_RTC);
17
18 printf("pll_sys = %dkHz\n", f_pll_sys);
19 printf("pll_usb = %dkHz\n", f_pll_usb);
20 printf("rosc = %dkHz\n", f_rosc);
21 printf("clk_sys = %dkHz\n", f_clk_sys);
22 printf("clk_peri = %dkHz\n", f_clk_peri);
23 printf("clk_usb = %dkHz\n", f_clk_usb);
24 printf("clk_adc = %dkHz\n", f_clk_adc);
25 printf("clk_rtc = %dkHz\n", f_clk_rtc);
26
27 // Can't measure clk_ref / xosc as it is the ref
28 }
29
30 int main() {
31 stdio_init_all();
32
33 printf("Hello, world!\n");
34
35 measure_freqs();
36
37 // Change clk_sys to be 48MHz. The simplest way is to take this from PLL_USB
38 // which has a source frequency of 48MHz
39 clock_configure(clk_sys,
40 CLOCKS_CLK_SYS_CTRL_SRC_VALUE_CLKSRC_CLK_SYS_AUX,
41 CLOCKS_CLK_SYS_CTRL_AUXSRC_VALUE_CLKSRC_PLL_USB,
42 48 * MHZ,
43 48 * MHZ);
44
45 // Turn off PLL sys for good measure
46 pll_deinit(pll_sys);
47
48 // CLK peri is clocked from clk_sys so need to change clk_peri's freq
49 clock_configure(clk_peri,
50 0,
51 CLOCKS_CLK_PERI_CTRL_AUXSRC_VALUE_CLK_SYS,
52 48 * MHZ,
53 48 * MHZ);
54
55 // Re init uart now that clk_peri has changed
56 stdio_init_all();
57
58 measure_freqs();
59 printf("Hello, 48MHz");
60
61 return 0;
62 }
4.1.4.1. Enumerations
• enum clock_index { clk_gpout0 = 0, clk_gpout1, clk_gpout2, clk_gpout3, clk_ref, clk_sys, clk_peri, clk_usb, clk_adc,
clk_rtc, CLK_COUNT }
Enumeration identifying a hardware clock.
4.1.4.2. Typedefs
• void clocks_init ()
• bool clock_configure (enum clock_index clk_index, uint32_t src, uint32_t auxsrc, uint32_t src_freq, uint32_t freq)
• void clock_stop (enum clock_index clk_index)
• uint32_t clock_get_hz (enum clock_index clk_index)
• uint32_t frequency_count_khz (uint src)
• void clock_set_reported_hz (enum clock_index clk_index, uint hz)
• void clocks_enable_resus (resus_callback_t resus_callback)
• void clock_gpio_init (uint gpio, uint src, uint div)
• bool clock_configure_gpin (enum clock_index clk_index, uint gpio, uint32_t src_freq, uint32_t freq)
4.1.4.4.1. clock_configure
See the tables in the description for details on the possible values for clock sources.
Parameters
4.1.4.4.2. clock_configure_gpin
Parameters
4.1.4.4.3. clock_get_hz
Parameters
• clk_index Clock
Returns
• Clock frequency in Hz
4.1.4.4.4. clock_gpio_init
Parameters
• gpio The GPIO pin to output the clock to. Valid GPIOs are: 21, 23, 24, 26. These GPIOs are connected to the GPOUT0-
3 clock generators.
• src The source clock. See the register field CLOCKS_CLK_GPOUT0_CTRL_AUXSRC for a full list. The list is the same
for each GPOUT clock generator.
• div The amount to divide the source clock by. This is useful to not overwhelm the GPIO pin with a fast clock.
4.1.4.4.5. clock_set_reported_hz
Set the "current frequency" of the clock as reported by clock_get_hz without actually changing the clock.
See also
• clock_get_hz
4.1.4.4.6. clock_stop
Parameters
4.1.4.4.7. clocks_enable_resus
The resuscitate function will restart the system clock if it falls below a certain speed (or stops). This could happen if the
clock source the system clock is running from stops. For example if a PLL is stopped.
Parameters
• resus_callback a function pointer provided by the user to call if a resus event happens.
4.1.4.4.8. clocks_init
void clocks_init ()
4.1.4.4.9. frequency_count_khz
Uses the inbuilt frequency counter to measure the specified clocks frequency. Currently, this function is accurate to +-
1KHz. See the datasheet for more details.
4.1.5. hardware_divider
Low-level hardware-divider access.
The SIO contains an 8-cycle signed/unsigned divide/modulo circuit, per core. Calculation is started by writing a dividend
and divisor to the two argument registers, DIVIDEND and DIVISOR. The divider calculates the quotient / and remainder %
of this division over the next 8 cycles, and on the 9th cycle the results can be read from the two result registers
DIV_QUOTIENT and DIV_REMAINDER. A 'ready' bit in register DIV_CSR can be polled to wait for the calculation to
complete, or software can insert a fixed 8-cycle delay
This header provides low level macros and inline functions for accessing the hardware dividers directly, and perhaps most
usefully performing asynchronous divides. These functions however do not follow the regular SDK conventions for
saving/restoring the divider state, so are not generally safe to call from interrupt handlers
The pico_divider library provides a more user friendly set of APIs over the divider (and support for 64 bit divides), and of
course by default regular C language integer divisions are redirected through that library, meaning you can just use C level
/ and % operators and gain the benefits of the fast hardware divider.
See also
• pico_divider
Example
1 #include <stdio.h>
2 #include "pico/stdlib.h"
3 #include "hardware/divider.h"
4
5 int main() {
6 stdio_init_all();
7 printf("Hello, divider!\n");
8
9 // This is the basic hardware divider function
4.1.5.2.1. hw_divider_divmod_s32
Divide a by b, wait for calculation to complete, return result as a fixed point 32p32 value.
Parameters
• a The dividend
• b The divisor
Returns
4.1.5.2.2. hw_divider_divmod_s32_start
Start a divide of the specified signed parameters. You should wait for 8 cycles (__div_pause()) or wait for the ready bit to
be set (hw_divider_wait_ready()) prior to reading the results.
Parameters
• a The dividend
• b The divisor
4.1.5.2.3. hw_divider_divmod_u32
Divide a by b, wait for calculation to complete, return result as a fixed point 32p32 value.
Parameters
• a The dividend
• b The divisor
Returns
4.1.5.2.4. hw_divider_divmod_u32_start
Start a divide of the specified unsigned parameters. You should wait for 8 cycles (__div_pause()) or wait for the ready bit
to be set (hw_divider_wait_ready()) prior to reading the results.
Parameters
• a The dividend
• b The divisor
4.1.5.2.5. hw_divider_pause
Pause for exact amount of time needed for a asynchronous divide to complete.
4.1.5.2.6. hw_divider_quotient_s32
Parameters
• a The dividend
• b The divisor
Returns
4.1.5.2.7. hw_divider_remainder_s32
Parameters
• a The dividend
• b The divisor
Returns
4.1.5.2.8. hw_divider_restore_state
Load a saved hardware divider state into the current core’s hardware divider.
Copy the passed hardware divider state into the hardware divider.
Parameters
4.1.5.2.9. hw_divider_result_nowait
Returns
• Current result. Most significant 32 bits are the remainder, lower 32 bits are the quotient.
4.1.5.2.10. hw_divider_result_wait
Returns
• Current result. Most significant 32 bits are the remainder, lower 32 bits are the quotient.
4.1.5.2.11. hw_divider_s32_quotient_inlined
Parameters
• a The dividend
• b The divisor
Returns
4.1.5.2.12. hw_divider_s32_quotient_wait
Returns
4.1.5.2.13. hw_divider_s32_remainder_inlined
Parameters
• a The dividend
• b The divisor
Returns
4.1.5.2.14. hw_divider_s32_remainder_wait
Returns
4.1.5.2.15. hw_divider_save_state
Copy the current core’s hardware divider state into the provided structure. This method waits for the divider results to be
stable, then copies them to memory. They can be restored via hw_divider_restore_state()
Parameters
4.1.5.2.16. hw_divider_u32_quotient
Parameters
• a The dividend
• b The divisor
Returns
4.1.5.2.17. hw_divider_u32_quotient_inlined
Parameters
• a The dividend
• b The divisor
Returns
4.1.5.2.18. hw_divider_u32_quotient_wait
Returns
4.1.5.2.19. hw_divider_u32_remainder
Parameters
• a The dividend
• b The divisor
Returns
4.1.5.2.20. hw_divider_u32_remainder_inlined
Parameters
• a The dividend
• b The divisor
Returns
4.1.5.2.21. hw_divider_u32_remainder_wait
Returns
4.1.5.2.22. hw_divider_wait_ready
4.1.5.2.23. to_quotient_s32
Parameters
Returns
• Unsigned quotient
4.1.5.2.24. to_quotient_u32
Parameters
• Unsigned quotient
4.1.5.2.25. to_remainder_s32
Parameters
• Signed remainder
4.1.5.2.26. to_remainder_u32
Parameters
• Unsigned remainder
4.1.6. hardware_dma
DMA Controller API.
The RP2040 Direct Memory Access (DMA) master performs bulk data transfers on a processor’s behalf. This leaves
processors free to attend to other tasks, or enter low-power sleep states. The data throughput of the DMA is also
significantly higher than one of RP2040’s processors.
The DMA can perform one read access and one write access, up to 32 bits in size, every clock cycle. There are 12
independent channels, which each supervise a sequence of bus transfers, usually in one of the following scenarios:
• Memory to peripheral
• Peripheral to memory
• Memory to memory
4.1.6.1. Modules
• channel_config
DMA channel configuration.
4.1.6.2. Enumerations
4.1.6.4.1. dma_channel_abort
Parameters
4.1.6.4.2. dma_channel_claim
Method for cooperative claiming of hardware. Will cause a panic if the channel is already claimed. Use of this method by
libraries detects accidental configurations that would fail in unpredictable ways.
Parameters
4.1.6.4.3. dma_channel_configure
Parameters
4.1.6.4.4. dma_channel_is_busy
Parameters
4.1.6.4.5. dma_channel_set_config
Parameters
4.1.6.4.6. dma_channel_set_irq0_enabled
Parameters
4.1.6.4.7. dma_channel_set_irq1_enabled
Parameters
4.1.6.4.8. dma_channel_set_read_addr
Parameters
4.1.6.4.9. dma_channel_set_trans_count
Parameters
4.1.6.4.10. dma_channel_set_write_addr
Parameters
4.1.6.4.11. dma_channel_start
Parameters
4.1.6.4.12. dma_channel_transfer_from_buffer_now
Parameters
4.1.6.4.13. dma_channel_transfer_to_buffer_now
Parameters
4.1.6.4.14. dma_channel_unclaim
Parameters
4.1.6.4.15. dma_channel_wait_for_finish_blocking
Parameters
4.1.6.4.16. dma_claim_mask
Method for cooperative claiming of hardware. Will cause a panic if any of the channels are already claimed. Use of this
method by libraries detects accidental configurations that would fail in unpredictable ways.
Parameters
• channel_mask Bitfield of all required channels to claim (bit 0 == channel 0, bit 1 == channel 1 etc)
4.1.6.4.17. dma_claim_unused_channel
Parameters
• the dma channel number or -1 if required was false, and none were free
4.1.6.4.18. dma_set_irq0_channel_mask_enabled
Parameters
• channel_mask Bitmask of all the channels to enable/disable. Channel 0 = bit 0, channel 1 = bit 1 etc.
• enabled true to enable all the interrupts specified in the mask, false to disable all the interrupts specified in the mask.
4.1.6.4.19. dma_set_irq1_channel_mask_enabled
Parameters
• channel_mask Bitmask of all the channels to enable/disable. Channel 0 = bit 0, channel 1 = bit 1 etc.
• enabled true to enable all the interrupts specified in the mask, false to disable all the interrupts specified in the mask.
4.1.6.4.20. dma_sniffer_disable
4.1.6.4.21. dma_sniffer_enable
Mode Function
Parameters
4.1.6.4.22. dma_sniffer_set_byte_swap_enabled
Locally perform a byte reverse on the sniffed data, before feeding into checksum.
Note that the sniff hardware is downstream of the DMA channel byteswap performed in the read master: if
channel_config_set_bswap() and dma_sniffer_set_byte_swap_enabled() are both enabled, their effects cancel from the sniffer’s
point of view.
Parameters
4.1.6.4.23. dma_start_channel_mask
Parameters
• chan_mask Bitmask of all the channels requiring starting. Channel 0 = bit 0, channel 1 = bit 1 etc.
4.1.7. channel_config
DMA channel configuration.
A DMA channel needs to be configured, these functions provide handy helpers to set up configuration structures. See
dma_channel_config
4.1.7.2.1. channel_config_get_ctrl_value
Parameters
• Register content
4.1.7.2.2. channel_config_set_bswap
No effect for byte data, for halfword data, the two bytes of each halfword are swapped. For word data, the four bytes of
each word are swapped to reverse their order.
Parameters
4.1.7.2.3. channel_config_set_chain_to
When this channel completes, it will trigger the channel indicated by chain_to. Disable by setting chain_to to itself (the
same channel)
Parameters
4.1.7.2.4. channel_config_set_dreq
The channel uses the transfer request signal to pace its data transfer rate. Sources for TREQ signals are internal
(TIMERS) or external (DREQ, a Data Request from the system). 0x0 to 0x3a → select DREQ n as TREQ 0x3b → Select
Timer 0 as TREQ 0x3c → Select Timer 1 as TREQ 0x3d → Select Timer 2 as TREQ (Optional) 0x3e → Select Timer 3 as
TREQ (Optional) 0x3f → Permanent request, for unpaced transfers.
Parameters
4.1.7.2.5. channel_config_set_enable
When false, the channel will ignore triggers, stop issuing transfers, and pause the current transfer sequence (i.e. BUSY will
remain high if already high)
Parameters
4.1.7.2.6. channel_config_set_irq_quiet
In QUIET mode, the channel does not generate IRQs at the end of every transfer block. Instead, an IRQ is raised when
NULL is written to a trigger register, indicating the end of a control block chain.
Parameters
4.1.7.2.7. channel_config_set_read_increment
Parameters
4.1.7.2.8. channel_config_set_ring
Size of address wrap region. If 0, don’t wrap. For values n > 0, only the lower n bits of the address will change. This wraps
the address on a (1 << n) byte boundary, facilitating access to naturally-aligned ring buffers. Ring sizes between 2 and
32768 bytes are possible (size_bits from 1 - 15)
0x0 → No wrapping.
Parameters
4.1.7.2.9. channel_config_set_sniff_enable
Parameters
4.1.7.2.10. channel_config_set_transfer_data_size
Set the size of each bus transfer (byte/halfword/word). The read and write addresses advance by the specific amount
(1/2/4 bytes) with each transfer.
Parameters
4.1.7.2.11. channel_config_set_write_increment
Parameters
4.1.7.2.12. dma_channel_get_default_config
Setting Default
DReq DREQ_FORCE
Chain to self
Parameters
4.1.7.2.13. dma_get_channel_config
Parameters
4.1.8. hardware_flash
Low level flash programming and erase API.
Note these functions are unsafe if you have two cores concurrently executing from flash. In this case you must perform
your own synchronisation to make sure no XIP accesses take place during flash programming.
Likewise they are unsafe if you have interrupt handlers or an interrupt vector table in flash, so you must disable interrupts
before calling in this case.
If PICO_NO_FLASH=1 is not defined (i.e. if the program is built to run from flash) then these functions will make a static
copy of the second stage bootloader in SRAM, and use this to reenter execute-in-place mode after programming or
erasing flash, so that they can safely be called from flash-resident code.
Example
1 #include <stdio.h>
2 #include <stdlib.h>
3
4 #include "pico/stdlib.h"
5 #include "hardware/flash.h"
6
7 // We're going to erase and reprogram a region 256k from the start of flash.
8 // Once done, we can access this at XIP_BASE + 256k.
9 #define FLASH_TARGET_OFFSET (256 * 1024)
10
11 const uint8_t *flash_target_contents = (const uint8_t *) (XIP_BASE + FLASH_TARGET_OFFSET);
12
13 void print_buf(const uint8_t *buf, size_t len) {
14 for (size_t i = 0; i < len; ++i) {
15 printf("%02x", buf[i]);
16 if (i % 16 == 15)
17 printf("\n");
18 else
19 printf(" ");
20 }
21 }
22
23 int main() {
24 stdio_init_all();
25 uint8_t random_data[FLASH_PAGE_SIZE];
26 for (int i = 0; i < FLASH_PAGE_SIZE; ++i)
27 random_data[i] = rand() >> 16;
28
29 printf("Generated random data:\n");
30 print_buf(random_data, FLASH_PAGE_SIZE);
31
4.1.8.2.1. flash_get_unique_id
Use a standard 4Bh RUID instruction to retrieve the 64 bit unique identifier from a flash device attached to the QSPI
interface. Since there is a 1:1 association between the MCU and this flash, this also serves as a unique identifier for the
board.
Parameters
4.1.8.2.2. flash_range_erase
Parameters
• flash_offs Offset into flash, in bytes, to start the erase. Must be aligned to a 4096-byte flash sector.
• count Number of bytes to be erased. Must be a multiple of 4096 bytes (one sector).
4.1.8.2.3. flash_range_program
Program flash.
Parameters
• flash_offs Flash address of the first byte to be programmed. Must be aligned to a 256-byte flash page.
• data Pointer to the data to program into flash
• count Number of bytes to program. Must be a multiple of 256 bytes (one page).
4.1.9. hardware_gpio
General Purpose Input/Output (GPIO) API.
RP2040 has 36 multi-functional General Purpose Input / Output (GPIO) pins, divided into two banks. In a typical use case,
the pins in the QSPI bank (QSPI_SS, QSPI_SCLK and QSPI_SD0 to QSPI_SD3) are used to execute code from an external
flash device, leaving the User bank (GPIO0 to GPIO29) for the programmer to use. All GPIOs support digital input and
output, but GPIO26 to GPIO29 can also be used as inputs to the chip’s Analogue to Digital Converter (ADC). Each GPIO
can be controlled directly by software running on the processors, or by a number of other functional blocks.
The function allocated to each GPIO is selected by calling the gpio_set_function function. Not all functions are available on
all pins.
Each GPIO can have one function selected at a time. Likewise, each peripheral input (e.g. UART0 RX) should only be
selected on one GPIO at a time. If the same peripheral input is connected to multiple GPIOs, the peripheral sees the logical
OR of these GPIO inputs. Please refer to the datasheet for more information on GPIO function select.
1 SPI0 CSn UART0 RX I2C0 SCL PWM0 B SIO PIO0 PIO1 USB VBUS
DET
2 SPI0 SCK UART0 I2C1 SDA PWM1 A SIO PIO0 PIO1 USB VBUS
CTS EN
4 SPI0 RX UART1 TX I2C0 SDA PWM2 A SIO PIO0 PIO1 USB VBUS
DET
5 SPI0 CSn UART1 RX I2C0 SCL PWM2 B SIO PIO0 PIO1 USB VBUS
EN
6 SPI0 SCK UART1 I2C1 SDA PWM3 A SIO PIO0 PIO1 USB
CTS OVCUR
DET
7 SPI0 TX UART1 I2C1 SCL PWM3 B SIO PIO0 PIO1 USB VBUS
RTS DET
8 SPI1 RX UART1 TX I2C0 SDA PWM4 A SIO PIO0 PIO1 USB VBUS
EN
GPIO F1 F2 F3 F4 F5 F6 F7 F8 F9
9 SPI1 CSn UART1 RX I2C0 SCL PWM4 B SIO PIO0 PIO1 USB
OVCUR
DET
10 SPI1 SCK UART1 I2C1 SDA PWM5 A SIO PIO0 PIO1 USB VBUS
CTS DET
11 SPI1 TX UART1 I2C1 SCL PWM5 B SIO PIO0 PIO1 USB VBUS
RTS EN
13 SPI1 CSn UART0 RX I2C0 SCL PWM6 B SIO PIO0 PIO1 USB VBUS
DET
14 SPI1 SCK UART0 I2C1 SDA PWM7 A SIO PIO0 PIO1 USB VBUS
CTS EN
16 SPI0 RX UART0 TX I2C0 SDA PWM0 A SIO PIO0 PIO1 USB VBUS
DET
17 SPI0 CSn UART0 RX I2C0 SCL PWM0 B SIO PIO0 PIO1 USB VBUS
EN
18 SPI0 SCK UART0 I2C1 SDA PWM1 A SIO PIO0 PIO1 USB
CTS OVCUR
DET
19 SPI0 TX UART0 I2C1 SCL PWM1 B SIO PIO0 PIO1 USB VBUS
RTS DET
20 SPI0 RX UART1 TX I2C0 SDA PWM2 A SIO PIO0 PIO1 CLOCK USB VBUS
GPIN0 EN
21 SPI0 CSn UART1 RX I2C0 SCL PWM2 B SIO PIO0 PIO1 CLOCK USB
GPOUT0 OVCUR
DET
22 SPI0 SCK UART1 I2C1 SDA PWM3 A SIO PIO0 PIO1 CLOCK USB VBUS
CTS GPIN1 DET
23 SPI0 TX UART1 I2C1 SCL PWM3 B SIO PIO0 PIO1 CLOCK USB VBUS
RTS GPOUT1 EN
24 SPI1 RX UART1 TX I2C0 SDA PWM4 A SIO PIO0 PIO1 CLOCK USB
GPOUT2 OVCUR
DET
25 SPI1 CSn UART1 RX I2C0 SCL PWM4 B SIO PIO0 PIO1 CLOCK USB VBUS
GPOUT3 DET
26 SPI1 SCK UART1 I2C1 SDA PWM5 A SIO PIO0 PIO1 USB VBUS
CTS EN
GPIO F1 F2 F3 F4 F5 F6 F7 F8 F9
28 SPI1 RX UART0 TX I2C0 SDA PWM6 A SIO PIO0 PIO1 USB VBUS
DET
29 SPI1 CSn UART0 RX I2C0 SCL PWM6 B SIO PIO0 PIO1 USB VBUS
EN
4.1.9.1. Enumerations
4.1.9.3.1. gpio_acknowledge_irq
Parameters
4.1.9.3.2. gpio_clr_mask
Parameters
4.1.9.3.3. gpio_disable_pulls
Parameters
4.1.9.3.4. gpio_get
Parameters
4.1.9.3.5. gpio_get_all
Returns
4.1.9.3.6. gpio_get_dir
Parameters
4.1.9.3.7. gpio_init
Clear the output enable (i.e. set to input) Clear any output value.
Parameters
4.1.9.3.8. gpio_init_mask
Clear the output enable (i.e. set to input) Clear any output value.
Parameters
4.1.9.3.9. gpio_is_dir_out
Parameters
4.1.9.3.10. gpio_is_pulled_down
Parameters
4.1.9.3.11. gpio_is_pulled_up
Parameters
4.1.9.3.12. gpio_pull_down
Parameters
4.1.9.3.13. gpio_pull_up
Parameters
4.1.9.3.14. gpio_put
Parameters
4.1.9.3.15. gpio_put_all
Parameters
4.1.9.3.16. gpio_put_masked
For each 1 bit in mask, drive that pin to the value given by corresponding bit in value, leaving other pins unchanged. Since
this uses the TOGL alias, it is concurrency-safe with e.g. an IRQ bashing different pins from the same core.
Parameters
4.1.9.3.17. gpio_set_dir
Parameters
4.1.9.3.18. gpio_set_dir_all_bits
Parameters
• values individual settings for each gpio; for GPIO N, bit N is 1 for out, 0 for in
4.1.9.3.19. gpio_set_dir_in_masked
Parameters
4.1.9.3.20. gpio_set_dir_masked
For each 1 bit in "mask", switch that pin to the direction given by corresponding bit in "value", leaving other pins
unchanged. E.g. gpio_set_dir_masked(0x3, 0x2); → set pin 0 to input, pin 1 to output, simultaneously.
Parameters
4.1.9.3.21. gpio_set_dir_out_masked
Parameters
4.1.9.3.22. gpio_set_dormant_irq_enabled
This configures IRQs to restart the XOSC or ROSC when they are disabled in dormant mode
Parameters
4.1.9.3.23. gpio_set_function
Parameters
4.1.9.3.24. gpio_set_inover
Parameters
4.1.9.3.25. gpio_set_input_enabled
Parameters
4.1.9.3.26. gpio_set_irq_enabled
bit interrupt
0 Low level
1 High level
2 Edge low
3 Edge high
Parameters
4.1.9.3.27. gpio_set_irq_enabled_with_callback
Parameters
4.1.9.3.28. gpio_set_mask
Parameters
4.1.9.3.29. gpio_set_oeover
Parameters
4.1.9.3.30. gpio_set_outover
Parameters
4.1.9.3.31. gpio_set_pulls
Parameters
4.1.9.3.32. gpio_xor_mask
Parameters
4.1.10. hardware_i2c
I2C Controller API.
The I2C bus is a two-wire serial interface, consisting of a serial data line SDA and a serial clock SCL. These wires carry
information between the devices connected to the bus. Each device is recognized by a unique address and can operate as
either a “transmitter” or “receiver”, depending on the function of the device. Devices can also be considered as masters or
slaves when performing data transfers. A master is a device that initiates a data transfer on the bus and generates the
clock signals to permit that transfer. At that time, any device addressed is considered a slave.
This API allows the controller to be set up as a master or a slave using the i2c_set_slave_mode function.
The external pins of each controller are connected to GPIO pins as defined in the GPIO muxing table in the datasheet. The
muxing options give some IO flexibility, but each controller external pin should be connected to only one GPIO.
Note that the controller does NOT support High speed mode or Ultra-fast speed mode, the fastest operation being fast
mode plus at up to 1000Kb/s.
See the datasheet for more information on the I2C controller and its usage.
Example
1 // Sweep through all 7-bit I2C addresses, to see if any slaves are present on
2 // the I2C bus. Print out a table that looks like this:
3 //
4 // I2C Bus Scan
5 // 0 1 2 3 4 5 6 7 8 9 A B C D E F
6 // 0
7 // 1 @
8 // 2
9 // 3 @
10 // 4
11 // 5
12 // 6
13 // 7
14 //
15 // E.g. if slave addresses 0x12 and 0x34 were acknowledged.
16
17 #include <stdio.h>
18 #include "pico/stdlib.h"
19 #include "hardware/i2c.h"
20
21 // I2C reserves some addresses for special purposes. We exclude these from the scan.
22 // These are any addresses of the form 000 0xxx or 111 1xxx
23 bool reserved_addr(uint8_t addr) {
24 return (addr & 0x78) == 0 || (addr & 0x78) == 0x78;
25 }
26
27 int main() {
28 // Enable UART so we can print status output
29 stdio_init_all();
30
31 // This example will use I2C0 on GPIO4 (SDA) and GPIO5 (SCL)
32 i2c_init(i2c0, 100 * 1000);
33 gpio_set_function(4, GPIO_FUNC_I2C);
34 gpio_set_function(5, GPIO_FUNC_I2C);
35 gpio_pull_up(4);
36 gpio_pull_up(5);
37
38 printf("\nI2C Bus Scan\n");
39 printf(" 0 1 2 3 4 5 6 7 8 9 A B C D E F\n");
40
41 for (int addr = 0; addr < (1 << 7); ++addr) {
42 if (addr % 16 == 0) {
43 printf("%02x ", addr);
44 }
45
46 // Perform a 1-byte dummy read from the probe address. If a slave
47 // acknowledges this address, the function returns the number of bytes
48 // transferred. If the address byte is ignored, the function returns
49 // -1.
50
51 // Skip over any reserved addresses.
52 int ret;
53 uint8_t rxdata;
54 if (reserved_addr(addr))
55 ret = PICO_ERROR_GENERIC;
56 else
57 ret = i2c_read_blocking(i2c0, addr, &rxdata, 1, false);
58
59 printf(ret < 0 ? "." : "@");
60 printf(addr % 16 == 15 ? "\n" : " ");
61 }
62 printf("Done.\n");
63 return 0;
64 }
4.1.10.1. Variables
• i2c_inst_t i2c0_inst
• int i2c_read_blocking_until (i2c_inst_t *i2c, uint8_t addr, uint8_t *dst, size_t len, bool nostop, absolute_time_t
until)
• static int i2c_write_timeout_us (i2c_inst_t *i2c, uint8_t addr, const uint8_t *src, size_t len, bool nostop, uint
timeout_us)
• static int i2c_read_timeout_us (i2c_inst_t *i2c, uint8_t addr, uint8_t *dst, size_t len, bool nostop, uint
timeout_us)
• int i2c_write_blocking (i2c_inst_t *i2c, uint8_t addr, const uint8_t *src, size_t len, bool nostop)
• int i2c_read_blocking (i2c_inst_t *i2c, uint8_t addr, uint8_t *dst, size_t len, bool nostop)
• static size_t i2c_get_write_available (i2c_inst_t *i2c)
• static size_t i2c_get_read_available (i2c_inst_t *i2c)
• static void i2c_write_raw_blocking (i2c_inst_t *i2c, const uint8_t *src, size_t len)
• static void i2c_read_raw_blocking (i2c_inst_t *i2c, uint8_t *dst, size_t len)
4.1.10.3.1. i2c_deinit
Disable the I2C again if it is no longer used. Must be reinitialised before being used again.
Parameters
4.1.10.3.2. i2c_get_read_available
Parameters
• 0 if no data available, if return is nonzero at least that many bytes can be read without blocking.
4.1.10.3.3. i2c_get_write_available
Parameters
• 0 if no space is available in the I2C to write more data. If return is nonzero, at least that many bytes can be written
without blocking.
4.1.10.3.4. i2c_hw_index
Parameters
• Number of UART, 0 or 1.
4.1.10.3.5. i2c_init
Put the I2C hardware into a known state, and enable it. Must be called before other functions. By default, the I2C is
configured to operate as a master.
The I2C bus frequency is set as close as possible to requested, and the return actual rate set is returned
Parameters
4.1.10.3.6. i2c_read_blocking
Parameters
Returns
4.1.10.3.7. i2c_read_blocking_until
Attempt to read specified number of bytes from address, blocking until the specified absolute time is reached.
Parameters
• until The absolute time that the block will wait until the entire transaction is complete.
Returns
4.1.10.3.8. i2c_read_raw_blocking
Reads directly from the I2C RX FIFO which us mainly useful for slave-mode operation.
Parameters
4.1.10.3.9. i2c_read_timeout_us
Parameters
• timeout_us The time that the function will wait for the entire transaction to complete
Returns
4.1.10.3.10. i2c_set_baudrate
Set I2C bus frequency as close as possible to requested, and return actual rate set. Baudrate may not be as exactly
requested due to clocking limitations.
Parameters
4.1.10.3.11. i2c_set_slave_mode
Parameters
4.1.10.3.12. i2c_write_blocking
Parameters
Returns
4.1.10.3.13. i2c_write_blocking_until
Attempt to write specified number of bytes to address, blocking until the specified absolute time is reached.
Parameters
• until The absolute time that the block will wait until the entire transaction is complete. Note, an individual timeout of
this value divided by the length of data is applied for each byte transfer, so if the first or subsequent bytes fails to
transfer within that sub timeout, the function will return with an error.
Returns
4.1.10.3.14. i2c_write_raw_blocking
Writes directly to the to I2C TX FIFO which us mainly useful for slave-mode operation.
Parameters
4.1.10.3.15. i2c_write_timeout_us
Parameters
• timeout_us The time that the function will wait for the entire transaction to complete. Note, an individual timeout of
this value divided by the length of data is applied for each byte transfer, so if the first or subsequent bytes fails to
transfer within that sub timeout, the function will return with an error.
Returns
4.1.11. hardware_interp
Hardware Interpolator API.
Each core is equipped with two interpolators (INTERP0 and INTERP1) which can be used to accelerate tasks by
combining certain pre-configured simple operations into a single processor cycle. Intended for cases where the pre-
configured operation is repeated a large number of times, this results in code which uses both fewer CPU cycles and
fewer CPU registers in the time critical sections of the code.
The interpolators are used heavily to accelerate audio operations within the SDK, but their flexible configuration make it
possible to optimise many other tasks such as quantization and dithering, table lookup address generation, affine texture
mapping, decompression and linear feedback.
Please refer to the RP2040 datasheet for more information on the HW interpolators and how they work.
4.1.11.1. Modules
• interp_config
Interpolator configuration.
4.1.11.3.1. interp_add_accumulater
Add to accumulator.
Atomically add the specified value to the accumulator on the specified lane
Parameters
4.1.11.3.2. interp_claim_lane
Use this function to claim exclusive access to the specified interpolator lane.
Parameters
4.1.11.3.3. interp_claim_lane_mask
Parameters
4.1.11.3.4. interp_get_accumulator
Parameters
4.1.11.3.5. interp_get_base
Parameters
4.1.11.3.6. interp_get_raw
Returns the raw shift and mask value from the specified lane, BASE0 is NOT added
Parameters
4.1.11.3.7. interp_peek_full_result
Parameters
4.1.11.3.8. interp_peek_lane_result
Parameters
4.1.11.3.9. interp_pop_full_result
Read lane result, and write lane results to both accumulators to update the interpolator.
Parameters
4.1.11.3.10. interp_pop_lane_result
Read lane result, and write lane results to both accumulators to update the interpolator.
Parameters
4.1.11.3.11. interp_restore
Parameters
4.1.11.3.12. interp_save
Can be used to save state if you need an interpolator for another purpose, state can then be recovered afterwards and
continue from that point
Parameters
4.1.11.3.13. interp_set_accumulator
Parameters
4.1.11.3.14. interp_set_base
Parameters
4.1.11.3.15. interp_set_base_both
The lower 16 bits go to BASE0, upper bits to BASE1 simultaneously. Each half is sign-extended to 32 bits if that lane’s
SIGNED flag is set.
Parameters
4.1.11.3.16. interp_set_force_bits
These bits are ORed into bits 29:28 of the lane result presented to the processor on the bus. There is no effect on the
internal 32-bit datapath.
Useful for using a lane to generate sequence of pointers into flash or SRAM, saving a subsequent OR or add operation.
Parameters
4.1.11.3.17. interp_unclaim_lane
Parameters
4.1.12. interp_config
Interpolator configuration.
Each interpolator needs to be configured, these functions provide handy helpers to set up configuration structures.
4.1.12.2.1. interp_config_set_add_raw
When enabled, mask + shift is bypassed for LANE0 result. This does not affect the FULL result.
Parameters
4.1.12.2.2. interp_config_set_blend
If enabled, LANE1 result is a linear interpolation between BASE0 and BASE1, controlled by the 8 LSBs of lane 1 shift and
mask value (a fractional number between 0 and 255/256ths)
LANE0 result does not have BASE0 added (yields only the 8 LSBs of lane 1 shift+mask value)
FULL result does not have lane 1 shift+mask value added (BASE2 + lane 0 shift+mask)
Parameters
4.1.12.2.3. interp_config_set_clamp
Parameters
4.1.12.2.4. interp_config_set_cross_input
Allows feeding of the accumulator content from the other lane back in to this lanes shift+mask hardware. This will take
effect even if the interp_config_set_add_raw option is set as the cross input mux is before the shift+mask bypass
Parameters
4.1.12.2.5. interp_config_set_cross_result
Allows feeding of the other lane’s result into this lane’s accumulator on a POP operation.
Parameters
4.1.12.2.6. interp_config_set_force_bits
ORed into bits 29:28 of the lane result presented to the processor on the bus.
No effect on the internal 32-bit datapath. Handy for using a lane to generate sequence of pointers into flash or SRAM
Parameters
4.1.12.2.7. interp_config_set_mask
Sets the range of bits (least to most) that are allowed to pass through the interpolator
Parameters
4.1.12.2.8. interp_config_set_shift
Sets the number of bits the accumulator is shifted before masking, on each iteration.
Parameters
4.1.12.2.9. interp_config_set_signed
Enables signed mode, where the shifted and masked accumulator value is sign-extended to 32 bits before adding to
BASE1, and LANE1 PEEK/POP results appear extended to 32 bits when read by processor.
Parameters
4.1.12.2.10. interp_default_config
Returns
4.1.12.2.11. interp_set_config
If an invalid configuration is specified (ie a lane specific item is set on wrong lane), depending on setup this function can
panic.
Parameters
4.1.13. hardware_irq
Hardware interrupt handling.
The RP2040 uses the standard ARM nested vectored interrupt controller (NVIC).
On the RP2040, only the lower 26 IRQ signals are connected on the NVIC; IRQs 26 to 31 are tied to zero (never firing).
There is one NVIC per core, and each core’s NVIC has the same hardware interrupt lines routed to it, with the exception of
the IO interrupts where there is one IO interrupt per bank, per core. These are completely independent, so for example,
processor 0 can be interrupted by GPIO 0 in bank 0, and processor 1 by GPIO 1 in the same bank.
That all IRQ APIs affect the executing core only (i.e. the core calling the function).
You should not enable the same (shared) IRQ number on both cores, as this will lead to race conditions or starvation of
one of the cores. Additionally don’t forget that disabling interrupts on one core does not disable interrupts on the other
core.
• Calling irq_add_shared_handler() at runtime to add a handler for a multiplexed interrupt (e.g. GPIO bank) on the
current core. Each handler, should check and clear the relevant hardware interrupt source
• Calling irq_set_exclusive_handler() at runtime to install a single handler for the interrupt on the current core
• Defining the interrupt handler explicitly in your application (e.g. by defining void isr_dma_0 will make that function the
handler for the DMA_IRQ_0 on core 0, and you will not be able to change it using the above APIs at runtime). Using
this method can cause link conflicts at runtime, and offers no runtime performance benefit (i.e, it should not
generally be used).
If an IRQ is enabled and fires with no handler installed, a breakpoint will be hit and the IRQ number will be in r0.
Interrupt NumbersInterrupts are numbered as follows, a set of defines is available (intctrl.h) with these names to avoid
using the numbers directly.
0 TIMER_IRQ_0
1 TIMER_IRQ_1
2 TIMER_IRQ_2
3 TIMER_IRQ_3
4 PWM_IRQ_WRAP
5 USBCTRL_IRQ
6 XIP_IRQ
7 PIO0_IRQ_0
8 PIO0_IRQ_1
9 PIO1_IRQ_0
10 PIO1_IRQ_1
11 DMA_IRQ_0
12 DMA_IRQ_1
13 IO_IRQ_BANK0
14 IO_IRQ_QSPI
15 SIO_IRQ_PROC0
16 SIO_IRQ_PROC1
17 CLOCKS_IRQ
18 SPI0_IRQ
19 SPI1_IRQ
20 UART0_IRQ
21 UART1_IRQ
22 ADC0_IRQ_FIFO
23 I2C0_IRQ
24 I2C1_IRQ
25 RTC_IRQ
4.1.13.1. Typedefs
4.1.13.3.1. irq_add_shared_handler
Use this method to add a handler on an irq number shared between multiple distinct hardware sources (e.g. GPIO, DMA or
PIO IRQs). Handlers added by this method will all be called in sequence from highest order_priority to lowest. The
irq_set_exclusive_handler() method should be used instead if you know there will or should only ever be one handler for
the interrupt.
This method will assert if there is an exclusive interrupt handler set for this irq number on this core, or if the (total across
all IRQs on both cores) maximum (configurable via PICO_MAX_SHARED_IRQ_HANDLERS) number of shared handlers
would be exceeded.
Parameters
See also
• irq_set_exclusive_handler
4.1.13.3.2. irq_clear
Parameters
4.1.13.3.3. irq_get_exclusive_handler
Get the exclusive interrupt handler for an interrupt on the executing core.
This method will return an exclusive IRQ handler set on this core by irq_set_exclusive_handler if there is one.
Parameters
• handler The handler if an exclusive handler is set for the IRQ, NULL if no handler is set or shared/shareable handlers
are installed
See also
• irq_set_exclusive_handler
4.1.13.3.4. irq_get_vtable_handler
Get the current IRQ handler for the specified IRQ from the currently installed hardware vector table (VTOR) of the
execution core.
Parameters
• the address stored in the VTABLE for the given irq number
4.1.13.3.5. irq_is_enabled
Parameters
4.1.13.3.6. irq_remove_handler
Remove a specific interrupt handler for the given irq number on the executing core.
This method may be used to remove an irq set via either irq_set_exclusive_handler() or irq_add_shared_handler(), and will
assert if the handler is not currently installed for the given IRQ number
Parameters
• irq_set_exclusive_handler
• irq_add_shared_handler
4.1.13.3.7. irq_set_enabled
Parameters
4.1.13.3.8. irq_set_exclusive_handler
Use this method to set a handler for single IRQ source interrupts, or when your code, use case or performance
requirements dictate that there should no other handlers for the interrupt.
This method will assert if there is already any sort of interrupt handler installed for the specified irq number.
Parameters
• irq_add_shared_handler
4.1.13.3.9. irq_set_mask_enabled
Parameters
• mask 32-bit mask with one bits set for the interrupts to enable/disable
• enabled true to enable the interrupts, false to disable them.
4.1.13.3.10. irq_set_pending
Parameters
4.1.13.3.11. irq_set_priority
Parameters
4.1.14. hardware_pio
Programmable I/O (PIO) API.
A programmable input/output block (PIO) is a versatile hardware interface which can support a number of different IO
standards. There are two PIO blocks in the RP2040
Each PIO is programmable in the same sense as a processor: the four state machines independently execute short,
sequential programs, to manipulate GPIOs and transfer data. Unlike a general purpose processor, PIO state machines are
highly specialised for IO, with a focus on determinism, precise timing, and close integration with fixed-function hardware.
Each state machine is equipped with:
4.1.14.1. Modules
• sm_config
PIO state machine configuration.
4.1.14.2. Enumerations
4.1.14.3. Macros
4.1.14.4. Macros
• static void pio_sm_set_config (PIO pio, uint sm, const pio_sm_config *config)
• static uint pio_get_index (PIO pio)
• static void pio_gpio_init (PIO pio, uint pin)
• static uint pio_get_dreq (PIO pio, uint sm, bool is_tx)
• bool pio_can_add_program (PIO pio, const pio_program_t *program)
• bool pio_can_add_program_at_offset (PIO pio, const pio_program_t *program, uint offset)
• uint pio_add_program (PIO pio, const pio_program_t *program)
• void pio_add_program_at_offset (PIO pio, const pio_program_t *program, uint offset)
• void pio_remove_program (PIO pio, const pio_program_t *program, uint loaded_offset)
• void pio_clear_instruction_memory (PIO pio)
• void pio_sm_init (PIO pio, uint sm, uint initial_pc, const pio_sm_config *config)
• static void pio_sm_set_enabled (PIO pio, uint sm, bool enabled)
• static void pio_set_sm_mask_enabled (PIO pio, uint32_t mask, bool enabled)
• static void pio_sm_restart (PIO pio, uint sm)
• static void pio_restart_sm_mask (PIO pio, uint32_t mask)
4.1.14.6.1. pio_add_program
Parameters
4.1.14.6.2. pio_add_program_at_offset
Attempt to load the program at the specified instruction memory offset, panicking if not possible.
Parameters
4.1.14.6.3. pio_can_add_program
Determine whether the given program can (at the time of the call) be loaded onto the PIO instance.
Parameters
• true if the program can be loaded; false if there is not suitable space in the instruction memory
4.1.14.6.4. pio_can_add_program_at_offset
Determine whether the given program can (at the time of the call) be loaded onto the PIO instance starting at a particular
location.
Parameters
• true if the program can be loaded at that location; false if there is not space in the instruction memory
4.1.14.6.5. pio_claim_sm_mask
Method for cooperative claiming of hardware. Will cause a panic if any of the state machines are already claimed. Use of
this method by libraries detects accidental configurations that would fail in unpredictable ways.
Parameters
4.1.14.6.6. pio_claim_unused_sm
Parameters
• the state machine index or -1 if required was false, and none were free
4.1.14.6.7. pio_clear_instruction_memory
Parameters
4.1.14.6.8. pio_clkdiv_restart_sm_mask
Restart multiple state machines' clock dividers (resetting the fractional count)
This method can be used to guarantee that multiple state machines with fractional clock dividers are exactly in sync
Parameters
4.1.14.6.9. pio_enable_sm_mask_in_sync
Parameters
4.1.14.6.10. pio_get_dreq
Return the DREQ to use for pacing transfers to a particular state machine.
Parameters
4.1.14.6.11. pio_get_index
Parameters
4.1.14.6.12. pio_gpio_init
Setup the function select for a GPIO to use output from the given PIO instance.
Parameters
4.1.14.6.13. pio_remove_program
Parameters
4.1.14.6.14. pio_restart_sm_mask
This method clears the ISR, shift counters, clock divider counter pin write flags, delay counter, latched EXEC instruction,
and IRQ wait condition.
Parameters
4.1.14.6.15. pio_set_sm_mask_enabled
Note that this method just sets the enabled state of the state machine; if now enabled they continue exactly from where
they left off.
Parameters
• pio_enable_sm_mask_in_sync if you wish to enable multiple state machines and ensure their clock dividers are in
sync.
4.1.14.6.16. pio_sm_claim
Method for cooperative claiming of hardware. Will cause a panic if the state machine is already claimed. Use of this
method by libraries detects accidental configurations that would fail in unpredictable ways.
Parameters
4.1.14.6.17. pio_sm_clear_fifos
Parameters
4.1.14.6.18. pio_sm_clkdiv_restart
Parameters
4.1.14.6.19. pio_sm_drain_tx_fifo
This method executes pull instructions on the state machine until the TX FIFO is empty
Parameters
4.1.14.6.20. pio_sm_exec
This instruction is executed instead of the next instruction in the normal control flow on the state machine. Subsequent
calls to this method replace the previous executed instruction if it is still running.
Parameters
• pio_sm_is_exec_stalled to see if an executed instruction is still running (i.e. it is stalled on some condition)
4.1.14.6.21. pio_sm_exec_wait_blocking
This instruction is executed instead of the next instruction in the normal control flow on the state machine. Subsequent
calls to this method replace the previous executed instruction if it is still running.
Parameters
• pio_sm_is_exec_stalled to see if an executed instruction is still running (i.e. it is stalled on some condition)
4.1.14.6.22. pio_sm_get
Parameters
4.1.14.6.23. pio_sm_get_blocking
Read a word of data from a state machine’s RX FIFO, blocking if the FIFO is empty.
Parameters
4.1.14.6.24. pio_sm_get_pc
Parameters
4.1.14.6.25. pio_sm_get_rx_fifo_level
Parameters
4.1.14.6.26. pio_sm_get_tx_fifo_level
Parameters
4.1.14.6.27. pio_sm_init
This method:
Parameters
4.1.14.6.28. pio_sm_is_exec_stalled
Parameters
4.1.14.6.29. pio_sm_is_rx_fifo_empty
Parameters
4.1.14.6.30. pio_sm_is_rx_fifo_full
Parameters
4.1.14.6.31. pio_sm_is_tx_fifo_empty
Parameters
4.1.14.6.32. pio_sm_is_tx_fifo_full
Parameters
4.1.14.6.33. pio_sm_put
Parameters
4.1.14.6.34. pio_sm_put_blocking
Write a word of data to a state machine’s TX FIFO, blocking if the FIFO is full.
Parameters
4.1.14.6.35. pio_sm_restart
This method clears the ISR, shift counters, clock divider counter pin write flags, delay counter, latched EXEC instruction,
and IRQ wait condition.
Parameters
4.1.14.6.36. pio_sm_set_clkdiv
Parameters
4.1.14.6.37. pio_sm_set_clkdiv_int_frac
set the current clock divider for a state machine using a 16:8 fraction
Parameters
4.1.14.6.38. pio_sm_set_config
Parameters
4.1.14.6.39. pio_sm_set_consecutive_pindirs
Use a state machine to set the same pin direction for multiple consecutive pins for the PIO instance.
This method repeatedly reconfigures the target state machine’s pin configuration and executes 'set' instructions to set the
pin direction on consecutive pins, before restoring the state machine’s pin configuration to what it was.
This method is provided as a convenience to set initial pin directions, and should not be used against a state machine that
is enabled.
Parameters
4.1.14.6.40. pio_sm_set_enabled
Parameters
4.1.14.6.41. pio_sm_set_pindirs_with_mask
Use a state machine to set the pin directions for multiple pins for the PIO instance.
This method repeatedly reconfigures the target state machine’s pin configuration and executes 'set' instructions to set pin
directions on up to 32 pins, before restoring the state machine’s pin configuration to what it was.
This method is provided as a convenience to set initial pin directions, and should not be used against a state machine that
is enabled.
Parameters
4.1.14.6.42. pio_sm_set_pins
Use a state machine to set a value on all pins for the PIO instance.
This method repeatedly reconfigures the target state machine’s pin configuration and executes 'set' instructions to set
values on all 32 pins, before restoring the state machine’s pin configuration to what it was.
This method is provided as a convenience to set initial pin states, and should not be used against a state machine that is
enabled.
Parameters
4.1.14.6.43. pio_sm_set_pins_with_mask
Use a state machine to set a value on multiple pins for the PIO instance.
This method repeatedly reconfigures the target state machine’s pin configuration and executes 'set' instructions to set
values on up to 32 pins, before restoring the state machine’s pin configuration to what it was.
This method is provided as a convenience to set initial pin states, and should not be used against a state machine that is
enabled.
Parameters
4.1.14.6.44. pio_sm_set_wrap
Parameters
4.1.14.6.45. pio_sm_unclaim
Parameters
4.1.15. sm_config
PIO state machine configuration.
A PIO block needs to be configured, these functions provide helpers to set up configuration structures. See
pio_sm_set_config
• struct pio_sm_config
PIO Configuration structure.
4.1.15.3.1. pio_get_default_sm_config
Setting Default
In Pins (base) 0
Jmp Pin 0
Setting Default
Returns
4.1.15.3.2. pio_sm_set_in_pins
Parameters
4.1.15.3.3. pio_sm_set_out_pins
Parameters
4.1.15.3.4. pio_sm_set_set_pins
Parameters
4.1.15.3.5. pio_sm_set_sideset_pins
Parameters
4.1.15.3.6. sm_config_set_clkdiv
Set the state machine clock divider (from a floating point value) in a state machine configuration.
The clock divider acts on the system clock to provide a clock for the state machine. See the datasheet for more details.
Parameters
4.1.15.3.7. sm_config_set_clkdiv_int_frac
Set the state machine clock divider (from integer and fractional parts - 16:8) in a state machine configuration.
The clock divider acts on the system clock to provide a clock for the state machine. See the datasheet for more details.
Parameters
• sm_config_set_clkdiv
4.1.15.3.8. sm_config_set_fifo_join
Parameters
• enum pio_fifo_join
4.1.15.3.9. sm_config_set_in_pins
Parameters
4.1.15.3.10. sm_config_set_in_shift
Parameters
4.1.15.3.11. sm_config_set_jmp_pin
Parameters
4.1.15.3.12. sm_config_set_mov_status
Parameters
4.1.15.3.13. sm_config_set_out_pins
Parameters
4.1.15.3.14. sm_config_set_out_shift
Parameters
4.1.15.3.15. sm_config_set_out_special
Parameters
4.1.15.3.16. sm_config_set_set_pins
Parameters
4.1.15.3.17. sm_config_set_sideset
Parameters
4.1.15.3.18. sm_config_set_sideset_pins
Parameters
4.1.15.3.19. sm_config_set_wrap
Parameters
4.1.16. hardware_pll
Phase Locked Loop control APIs.
• void pll_init (PLL pll, uint32_t ref_div, uint32_t vco_freq, uint32_t post_div1, uint8_t post_div2)
• void pll_deinit (PLL pll)
4.1.16.2.1. pll_deinit
This will turn off the power to the specified PLL. Note this function does not currently check if the PLL is in use before
powering it off so should be used with care.
Parameters
4.1.16.2.2. pll_init
Parameters
4.1.17. hardware_pwm
Hardware Pulse Width Modulation (PWM) API.
The RP2040 PWM block has 8 identical slices. Each slice can drive two PWM output signals, or measure the frequency or
duty cycle of an input signal. This gives a total of up to 16 controllable PWM outputs. All 30 GPIOs can be driven by the
PWM block
The PWM hardware functions by continuously comparing the input value to a free-running counter. This produces a
toggling output where the amount of time spent at the high output level is proportional to the input value. The fraction of
time spent at the high signal level is known as the duty cycle of the signal.
The default behaviour of a PWM slice is to count upward until the wrap value (pwm_config_set_wrap) is reached, and then
immediately wrap to 0. PWM slices also offer a phase-correct mode, where the counter starts to count downward after
reaching TOP, until it reaches 0 again.
Example
4.1.17.1. Enumerations
4.1.17.3.1. pwm_advance_count
Parameters
4.1.17.3.2. pwm_clear_irq
Parameters
4.1.17.3.3. pwm_config_set_clkdiv
If the divide mode is free-running, the PWM counter runs at clk_sys / div. Otherwise, the divider reduces the rate of events
seen on the B pin input (level or edge) before passing them on to the PWM counter.
Parameters
4.1.17.3.4. pwm_config_set_clkdiv_int
If the divide mode is free-running, the PWM counter runs at clk_sys / div. Otherwise, the divider reduces the rate of events
seen on the B pin input (level or edge) before passing them on to the PWM counter.
Parameters
4.1.17.3.5. pwm_config_set_clkdiv_mode
Configure which event gates the operation of the fractional divider. The default is always-on (free-running PWM). Can also
be configured to count on high level, rising edge or falling edge of the B pin input.
Parameters
4.1.17.3.6. pwm_config_set_output_polarity
Parameters
4.1.17.3.7. pwm_config_set_phase_correct
Setting phase control to true means that instead of wrapping back to zero when the wrap point is reached, the PWM
starts counting back down. The output frequency is halved when phase-correct mode is enabled.
Parameters
4.1.17.3.8. pwm_config_set_wrap
Set the highest value the counter will reach before returning to 0. Also known as TOP.
Parameters
4.1.17.3.9. pwm_force_irq
Parameters
4.1.17.3.10. pwm_get_counter
Parameters
4.1.17.3.11. pwm_get_default_config
PWM config is free running at system clock speed, no phase correction, wrapping at 0xffff, with standard polarities for
channels A and B.
Returns
4.1.17.3.12. pwm_get_irq_status_mask
Returns
4.1.17.3.13. pwm_gpio_to_channel
Returns
4.1.17.3.14. pwm_gpio_to_slice_num
Returns
4.1.17.3.15. pwm_init
Use the pwm_get_default_config() function to initialise a config structure, make changes as needed using the pwm_config_*
functions, then call this function to set up the PWM.
Parameters
4.1.17.3.16. pwm_retard_count
Parameters
4.1.17.3.17. pwm_set_both_levels
Parameters
4.1.17.3.18. pwm_set_chan_level
Set the current PWM counter compare value for one channel.
Set the value of the PWM counter compare value, for either channel A or channel B
Parameters
4.1.17.3.19. pwm_set_clkdiv
Set the clock divider. Counter increment will be on sysclock divided by this value, taking in to account the gating.
Parameters
4.1.17.3.20. pwm_set_clkdiv_int_frac
Set the clock divider. Counter increment will be on sysclock divided by this value, taking in to account the gating.
Parameters
4.1.17.3.21. pwm_set_clkdiv_mode
Parameters
4.1.17.3.22. pwm_set_counter
Parameters
4.1.17.3.23. pwm_set_enabled
Enable/Disable PWM.
Parameters
4.1.17.3.24. pwm_set_gpio_level
Helper function to set the PWM level for the slice and channel associated with a GPIO.
Look up the correct slice (0 to 7) and channel (A or B) for a given GPIO, and update the corresponding counter-compare
field.
This PWM slice should already have been configured and set running. Also be careful of multiple GPIOs mapping to the
same slice and channel (if GPIOs have a difference of 16).
Parameters
4.1.17.3.25. pwm_set_irq_enabled
Parameters
4.1.17.3.26. pwm_set_irq_mask_enabled
Parameters
• slice_mask Bitmask of all the blocks to enable/disable. Channel 0 = bit 0, channel 1 = bit 1 etc.
• enabled true to enable, false to disable
4.1.17.3.27. pwm_set_mask_enabled
Parameters
4.1.17.3.28. pwm_set_output_polarity
Parameters
4.1.17.3.29. pwm_set_phase_correct
Setting phase control to true means that instead of wrapping back to zero when the wrap point is reached, the PWM
starts counting back down. The output frequency is halved when phase-correct mode is enabled.
Parameters
4.1.17.3.30. pwm_set_wrap
Set the highest value the counter will reach before returning to 0. Also known as TOP.
Parameters
4.1.18. hardware_resets
Hardware Reset API.
The reset controller allows software control of the resets to all of the peripherals that are not critical to boot the processor
in the RP2040.
reset_bitmask
USB 24
UART 1 23
UART 0 22
Timer 21
TB Manager 20
SysInfo 19
System Config 18
SPI 1 17
SPI 0 16
RTC 15
PWM 14
PLL USB 13
PLL System 12
PIO 1 11
PIO 0 10
Pads - QSPI 9
Pads - bank 0 8
JTAG 7
IO Bank 1 6
IO Bank 0 5
I2C 1 4
I2C 0 3
DMA 2
Bus Control 1
ADC 0 0
Example
1 #include <stdio.h>
2 #include "pico/stdlib.h"
3 #include "hardware/resets.h"
4
5 int main() {
6 stdio_init_all();
7
8 printf("Hello, reset!\n");
9
10 // Put the PWM block into reset
11 reset_block(RESETS_RESET_PWM_BITS);
12
13 // And bring it out
14 unreset_block_wait(RESETS_RESET_PWM_BITS);
15
16 // Put the PWM and RTC block into reset
17 reset_block(RESETS_RESET_PWM_BITS | RESETS_RESET_RTC_BITS);
18
19 // Wait for both to come out of reset
20 unreset_block_wait(RESETS_RESET_PWM_BITS | RESETS_RESET_RTC_BITS);
21
22 return 0;
23 }
4.1.18.2.1. reset_block
Parameters
4.1.18.2.2. unreset_block
Parameters
4.1.18.2.3. unreset_block_wait
Parameters
4.1.19. hardware_rtc
Hardware Real Time Clock API.
The RTC keeps track of time in human readable format and generates events when the time is equal to a preset value.
Think of a digital clock, not epoch time used by most computers. There are seven fields, one each for year (12 bit), month
(4 bit), day (5 bit), day of the week (3 bit), hour (5 bit) minute (6 bit) and second (6 bit), storing the data in binary format.
See also
• datetime_t
Example
1 #include <stdio.h>
2 #include "hardware/rtc.h"
3 #include "pico/stdlib.h"
4 #include "pico/util/datetime.h"
5
6 int main() {
7 stdio_init_all();
8 printf("Hello RTC!\n");
9
10 char datetime_buf[256];
11 char *datetime_str = &datetime_buf[0];
12
13 // Start on Friday 5th of June 2020 15:45:00
14 datetime_t t = {
15 .year = 2020,
16 .month = 06,
17 .day = 05,
18 .dotw = 5, // 0 is Sunday, so 5 is Friday
19 .hour = 15,
20 .min = 45,
21 .sec = 00
22 };
23
24 // Start the RTC
25 rtc_init();
26 rtc_set_datetime(&t);
27
28 // Print the time
29 while (true) {
30 rtc_get_datetime(&t);
4.1.19.2.1. rtc_disable_alarm
4.1.19.2.2. rtc_get_datetime
Parameters
4.1.19.2.3. rtc_init
4.1.19.2.4. rtc_running
4.1.19.2.5. rtc_set_alarm
Set a time in the future for the RTC to call a user provided callback.
Parameters
• t Pointer to a datetime_t structure containing a time in the future to fire the alarm. Any values set to -1 will not be
matched on.
4.1.19.2.6. rtc_set_datetime
Parameters
4.1.20. hardware_spi
Hardware SPI API.
RP2040 has 2 identical instances of the Serial Peripheral Interface (SPI) controller.
The PrimeCell SSP is a master or slave interface for synchronous serial communication with peripheral devices that have
Motorola SPI, National Semiconductor Microwire, or Texas Instruments synchronous serial interfaces.
Each controller can be connected to a number of GPIO pins, see the datasheet GPIO function selection table for more
information.
4.1.20.1. Macros
• int spi_write_read_blocking (spi_inst_t *spi, const uint8_t *src, uint8_t *dst, size_t len)
• int spi_write_blocking (spi_inst_t *spi, const uint8_t *src, size_t len)
• int spi_read_blocking (spi_inst_t *spi, uint8_t repeated_tx_data, uint8_t *dst, size_t len)
• int spi_write16_read16_blocking (spi_inst_t *spi, const uint16_t *src, uint16_t *dst, size_t len)
• int spi_write16_blocking (spi_inst_t *spi, const uint16_t *src, size_t len)
• int spi_read16_blocking (spi_inst_t *spi, uint16_t repeated_tx_data, uint16_t *dst, size_t len)
4.1.20.3.1. spi_deinit
Puts the SPI into a disabled state. Init will need to be called to reenable the device functions.
Parameters
4.1.20.3.2. spi_get_index
Parameters
• Number of SPI, 0 or 1.
4.1.20.3.3. spi_init
Puts the SPI into a known state, and enable it. Must be called before other functions.
Parameters
4.1.20.3.4. spi_is_readable
Parameters
4.1.20.3.5. spi_is_writable
Parameters
4.1.20.3.6. spi_read16_blocking
Read len halfwords from SPI to dst. Blocks until all data is transferred. No timeout, as SPI hardware always transfers at a
known data rate. repeated_tx_data is output repeatedly on TX as data is read in from RX. Generally this can be 0, but some
devices require a specific value here, e.g. SD cards expect 0xff
Parameters
4.1.20.3.7. spi_read_blocking
Read len bytes from SPI to dst. Blocks until all data is transferred. No timeout, as SPI hardware always transfers at a
known data rate. repeated_tx_data is output repeatedly on TX as data is read in from RX. Generally this can be 0, but some
devices require a specific value here, e.g. SD cards expect 0xff
Parameters
4.1.20.3.8. spi_set_baudrate
Set SPI frequency as close as possible to baudrate, and return the actual achieved rate.
Parameters
Returns
4.1.20.3.9. spi_set_format
Configure SPI.
Configure how the SPI serialises and deserialises data on the wire
Parameters
4.1.20.3.10. spi_set_slave
Configure the SPI for master- or slave-mode operation. By default, spi_init() sets master-mode.
Parameters
4.1.20.3.11. spi_write16_blocking
Write len halfwords from src to SPI. Discard any data received back. Blocks until all data is transferred. No timeout, as SPI
hardware always transfers at a known data rate.
Parameters
4.1.20.3.12. spi_write16_read16_blocking
Write len halfwords from src to SPI. Simultaneously read len halfwords from SPI to dst. Blocks until all data is transferred.
No timeout, as SPI hardware always transfers at a known data rate.
Parameters
4.1.20.3.13. spi_write_blocking
Write len bytes from src to SPI, and discard any data received back Blocks until all data is transferred. No timeout, as SPI
hardware always transfers at a known data rate.
Parameters
4.1.20.3.14. spi_write_read_blocking
Write len bytes from src to SPI. Simultaneously read len bytes from SPI to dst. Blocks until all data is transferred. No
timeout, as SPI hardware always transfers at a known data rate.
Parameters
4.1.21. hardware_sync
Low level hardware spin-lock, barrier and processor event API.
The RP2040 provides 32 hardware spin locks, which can be used to manage mutually-exclusive access to shared
software resources.
spin locks 0-15 are currently reserved for fixed uses by the SDK - i.e. if you use them other functionality may break or not
function optimally
4.1.21.1. Typedefs
4.1.21.3.1. __dmb
The DMB (data memory barrier) acts as a memory barrier, all memory accesses prior to this instruction will be observed
before any explicit access after the instruction.
4.1.21.3.2. __isb
ISB acts as an instruction synchronization barrier. It flushes the pipeline of the processor, so that all instructions following
the ISB are fetched from cache or memory again, after the ISB instruction has been completed.
4.1.21.3.3. __mem_fence_acquire
4.1.21.3.4. __mem_fence_release
4.1.21.3.5. __sev
4.1.21.3.6. __wfe
The WFE (wait for event) instruction waits until one of a number of events occurs, including events signalled by the SEV
instruction on either core.
4.1.21.3.7. __wfi
The WFI (wait for interrupt) instruction waits for a interrupt to wake up the core.
4.1.21.3.8. get_core_num
Returns
4.1.21.3.9. is_spin_locked
Parameters
4.1.21.3.10. restore_interrupts
Parameters
4.1.21.3.11. save_and_disable_interrupts
Returns
• The prior interrupt enable status for restoration later via restore_interrupts()
4.1.21.3.12. spin_lock_blocking
Parameters
4.1.21.3.13. spin_lock_claim
Method for cooperative claiming of hardware. Will cause a panic if the spin lock is already claimed. Use of this method by
libraries detects accidental configurations that would fail in unpredictable ways.
Parameters
4.1.21.3.14. spin_lock_claim_mask
Method for cooperative claiming of hardware. Will cause a panic if any of the spin locks are already claimed. Use of this
method by libraries detects accidental configurations that would fail in unpredictable ways.
Parameters
• lock_num_mask Bitfield of all required spin locks to claim (bit 0 == spin lock 0, bit 1 == spin lock 1 etc)
4.1.21.3.15. spin_lock_claim_unused
Parameters
• the spin lock number or -1 if required was false, and none were free
4.1.21.3.16. spin_lock_get_num
Parameters
• The Spinlock ID
4.1.21.3.17. spin_lock_init
Parameters
4.1.21.3.18. spin_lock_instance
Parameters
• lock_num Spinlock ID
Returns
4.1.21.3.19. spin_lock_unclaim
Parameters
4.1.21.3.20. spin_lock_unsafe_blocking
Parameters
4.1.21.3.21. spin_locks_reset
4.1.21.3.22. spin_unlock
Parameters
• spin_lock_blocking()
4.1.21.3.23. spin_unlock_unsafe
Parameters
4.1.22. hardware_timer
Low-level hardware timer API.
This API provides medium level access to the timer HW. See also pico_time which provides higher levels functionality
using the hardware timer.
The timer has 4 alarms, and can output a separate interrupt for each alarm. The alarms match on the lower 32 bits of the
64 bit counter which means they can be fired a maximum of 2^32 microseconds into the future. This is equivalent to:
Example
1 #include <stdio.h>
2 #include "pico/stdlib.h"
3
4 volatile bool timer_fired = false;
5
6 int64_t alarm_callback(alarm_id_t id, void *user_data) {
7 printf("Timer %d fired!\n", (int) id);
8 timer_fired = true;
9 // Can return a value here in us to fire in the future
10 return 0;
11 }
12
13 bool repeating_timer_callback(struct repeating_timer *t) {
See also
• pico_time
4.1.22.1. Typedefs
4.1.22.3.1. busy_wait_until
Parameters
4.1.22.3.2. busy_wait_us
Busy wait wasting cycles for the given (64 bit) number of microseconds.
Parameters
4.1.22.3.3. busy_wait_us_32
Busy wait wasting cycles for the given (32 bit) number of microseconds.
Busy wait wasting cycles for the given (32 bit) number of microseconds.
Parameters
4.1.22.3.4. hardware_alarm_claim
Parameters
• hardware_claiming
4.1.22.3.5. hardware_alarm_set_callback
This method enables/disables the alarm IRQ for the specified hardware alarm on the calling core, and set the specified
This callback will be used for the timeout set via hardware_alarm_set_target
Parameters
• hardware_alarm_set_target
4.1.22.3.6. hardware_alarm_unclaim
Parameters
• hardware_claiming
4.1.22.3.7. time_reached
Parameters
4.1.22.3.8. time_us_32
Returns
4.1.22.3.9. time_us_64
uint64_t time_us_64 ()
Returns the full 64 bits of the hardware timer. The pico_time and other functions rely on the fact that this value
monotonically increases from power up. As such it is expected that this value counts upwards and never wraps (we
apologize for introducing a potential year 5851444 bug).
Returns
4.1.23. hardware_uart
Hardware UART API.
RP2040 has 2 identical instances of a UART peripheral, based on the ARM PL011. Each UART can be connected to a
number of GPIO pins as defined in the GPIO muxing.
Only the TX, RX, RTS, and CTS signals are connected, meaning that the modem mode and IrDA mode of the PL011 are not
supported.
Example
1 int main() {
2
3 // Initialise UART 0
4 uart_init(uart0, 115200);
5
6 // Set the GPIO pin mux to the UART - 0 is TX, 1 is RX
7 gpio_set_function(0, GPIO_FUNC_UART);
8 gpio_set_function(1, GPIO_FUNC_UART);
9
10 uart_puts(uart0, "Hello world!");
11 }
4.1.23.1. Enumerations
4.1.23.2. Macros
4.1.23.4.1. uart_default_tx_wait_blocking
4.1.23.4.2. uart_deinit
DeInitialise a UART.
Disable the UART if it is no longer used. Must be reinitialised before being used again.
Parameters
4.1.23.4.3. uart_get_index
Parameters
• Number of UART, 0 or 1.
4.1.23.4.4. uart_getc
This function will block until the character has been read
Parameters
4.1.23.4.5. uart_init
Initialise a UART.
Put the UART into a known state, and enable it. Must be called before other functions.
Parameters
4.1.23.4.6. uart_is_enabled
Parameters
4.1.23.4.7. uart_is_readable
Parameters
• 0 if no data available, otherwise the number of bytes, at least, that can be read
4.1.23.4.8. uart_is_readable_within_us
Wait for up to a certain number of microseconds for the RX FIFO to be non empty.
Parameters
• true if the RX FIFO became non empty before the timeout, false otherwise
4.1.23.4.9. uart_is_writable
Parameters
4.1.23.4.10. uart_putc
Write single character to UART for transmission, with optional CR/LF conversions.
This function will block until the character has been sent
Parameters
4.1.23.4.11. uart_putc_raw
This function will block until all the character has been sent
Parameters
4.1.23.4.12. uart_puts
This function will block until the entire string has been sent
Parameters
4.1.23.4.13. uart_read_blocking
This function will block until all the data has been received from the UART
Parameters
4.1.23.4.14. uart_set_baudrate
Set baud rate as close as possible to requested, and return actual rate selected.
Parameters
4.1.23.4.15. uart_set_break
Parameters
4.1.23.4.16. uart_set_fifo_enabled
Parameters
4.1.23.4.17. uart_set_format
Parameters
4.1.23.4.18. uart_set_hw_flow
Parameters
4.1.23.4.19. uart_set_irq_enables
Enable the UART’s interrupt output. An interrupt handler will need to be installed prior to calling this function.
Parameters
4.1.23.4.20. uart_set_translate_crlf
Parameters
4.1.23.4.21. uart_tx_wait_blocking
Parameters
4.1.23.4.22. uart_write_blocking
This function will block until all the data has been sent to the UART
Parameters
4.1.24. hardware_vreg
Voltage Regulation API.
4.1.24.2.1. vreg_set_voltage
Set voltage.
Parameters
• voltage The voltage (from enumeration vreg_voltage) to apply to the voltage regulator
4.1.25. hardware_watchdog
Hardware Watchdog Timer API.
The RP2040 has a built in HW watchdog Timer. This is a countdown timer that can restart parts of the chip if it reaches
zero. For example, this can be used to restart the processor if the software running on it gets stuck in an infinite loop or
similar. The programmer has to periodically write a value to the watchdog to stop it reaching zero.
Example
1 #include <stdio.h>
2 #include "pico/stdlib.h"
3 #include "hardware/watchdog.h"
4
5 int main() {
6 stdio_init_all();
7
8 if (watchdog_caused_reboot()) {
9 printf("Rebooted by Watchdog!\n");
10 return 0;
11 } else {
12 printf("Clean boot\n");
13 }
14
15 // Enable the watchdog, requiring the watchdog to be updated every 100ms or the chip will
reboot
16 // second arg is pause on debug which means the watchdog will pause when stepping through
code
17 watchdog_enable(100, 1);
18
19 for (uint i = 0; i < 5; i++) {
20 printf("Updating watchdog %d\n", i);
21 watchdog_update();
22 }
23
24 // Wait in an infinite loop and don't update the watchdog so it reboots us
25 printf("Waiting to be rebooted by watchdog\n");
26 while(1);
27 }
4.1.25.2.1. watchdog_caused_reboot
Returns
• true if the watchdog timer or a watchdog force caused the last reboot
• false there has been no watchdog reboot since run has been
4.1.25.2.2. watchdog_enable
By default the SDK assumes a 12MHz XOSC and sets the watchdog_start_tick appropriately.
Parameters
• delay_ms Number of milliseconds before watchdog will reboot without watchdog_update being called. Maximum of
0x7fffff, which is approximately 8.3 seconds
• pause_on_debug If the watchdog should be paused when the debugger is stepping through code
4.1.25.2.3. watchdog_get_count
Returns the number of microseconds before the watchdog will reboot the chip.
Returns
• The number of microseconds before the watchdog will reboot the chip.
4.1.25.2.4. watchdog_reboot
By default the SDK assumes a 12MHz XOSC and sets the watchdog_start_tick appropriately.
Parameters
• pc If Zero, a standard boot will be performed, if non-zero this is the program counter to jump to on reset.
• sp If pc is non-zero, this will be the stack pointer used.
• delay_ms Initial load value. Maximum value 0x7fffff, approximately 8.3s.
4.1.25.2.5. watchdog_start_tick
Parameters
• cycles This needs to be a divider that when applied to the XOSC input, produces a 1MHz clock. So if the XOSC is
12MHz, this will need to be 12.
4.1.25.2.6. watchdog_update
Reload the watchdog counter with the amount of time set in watchdog_enable.
4.1.26. hardware_xosc
Crystal Oscillator (XOSC) API.
4.1.26.2.1. xosc_disable
Turns off the crystal oscillator source, and waits for it to become unstable
4.1.26.2.2. xosc_dormant
Turns off the crystal oscillator until it is woken by an interrupt. This will block and hence the entire system will stop, until
an interrupt wakes it up. This function will continue to block until the oscillator becomes stable after its wakeup.
4.1.26.2.3. xosc_init
This function will block until the crystal oscillator has stabilised.
pico_multicore Adds support for running code on the second processor core (core1)
pico_stdlib Aggregation of a core subset of Raspberry Pi Pico SDK libraries used by most executables along
with some additional utility methods. Including pico_stdlib gives you everything you need to get
a basic program running which prints to stdout or flashes a LED.
critical_section Critical Section API for short-lived mutual exclusion safe for IRQ and multi-core.
mutex Mutex API for non IRQ mutual exclusion between cores.
pico_time API for accurate timestamps, sleeping, and time based callbacks.
timestamp Timestamp functions relating to points in time (including the current time)
4.2.1. pico_multicore
Adds support for running code on the second processor core (core1)
Example
1 #include <stdio.h>
2 #include "pico/stdlib.h"
3 #include "pico/multicore.h"
4
5 #define FLAG_VALUE 123
6
7 void core1_entry() {
8
9 multicore_fifo_push_blocking(FLAG_VALUE);
10
11 uint32_t g = multicore_fifo_pop_blocking();
12
13 if (g != FLAG_VALUE)
14 printf("Hmm, that's not right on core 1!\n");
15 else
16 printf("Its all gone well on core 1!");
17
18 while (1)
19 tight_loop_contents();
20 }
21
22 int main() {
23 stdio_init_all();
24 printf("Hello, multicore!\n");
25
26
27 multicore_launch_core1(core1_entry);
28
29 // Wait for it to start up
30
31 uint32_t g = multicore_fifo_pop_blocking();
32
33 if (g != FLAG_VALUE)
34 printf("Hmm, that's not right on core 0!\n");
35 else {
36 multicore_fifo_push_blocking(FLAG_VALUE);
37 printf("It's all gone well on core 0!");
38 }
39
40 }
4.2.1.1. Modules
• fifo
Functions for inter-core FIFO.
• void multicore_reset_core1 ()
• void multicore_launch_core1 (void(*entry)(void))
• void multicore_launch_core1_with_stack (void(*entry)(void), uint32_t *stack_bottom, size_t stack_size_bytes)
• void multicore_sleep_core1 ()
• void multicore_launch_core1_raw (void(*entry)(void), uint32_t *sp, uint32_t vector_table)
4.2.1.3.1. multicore_launch_core1
Reset core1 and enter the given function on core 1 using the default core 1 stack (below core 0 stack)
Parameters
4.2.1.3.2. multicore_launch_core1_raw
Reset core1 and enter the given function using the passed sp as the initial stack pointer. This is a bare bones functions
that does not provide a stack guard even if USE_STACK_GUARDS is defined
4.2.1.3.3. multicore_launch_core1_with_stack
Reset core1 and enter the given function on core 1 using the passed stack for core 1
4.2.1.3.4. multicore_reset_core1
void multicore_reset_core1 ()
Reset Core 1.
4.2.1.3.5. multicore_sleep_core1
void multicore_sleep_core1 ()
4.2.2. fifo
Functions for inter-core FIFO.
The RP2040 contains two FIFOs for passing data, messages or ordered events between the two cores. Each FIFO is 32
bits wide, and 8 entries deep. One of the FIFOs can only be written by core 0, and read by core 1. The other can only be
written by core 1, and read by core 0.
4.2.2.2.1. multicore_fifo_clear_irq
4.2.2.2.2. multicore_fifo_drain
4.2.2.2.3. multicore_fifo_get_status
Bit Description
Bit Description
Returns
4.2.2.2.4. multicore_fifo_pop_blocking
uint32_t multicore_fifo_pop_blocking ()
This function will block until there is data ready to be read Use multicore_fifo_rvalid() to check if data is ready to be read
if you don’t want to block.
Returns
4.2.2.2.5. multicore_fifo_push_blocking
This function will block until there is space for the data to be sent. Use multicore_fifo_wready() to check if it is possible to
write to the FIFO if you don’t want to block.
Parameters
4.2.2.2.6. multicore_fifo_rvalid
Returns
4.2.2.2.7. multicore_fifo_wready
Returns
4.2.3. pico_stdlib
Aggregation of a core subset of Raspberry Pi Pico SDK libraries used by most executables along with some additional
utility methods. Including pico_stdlib gives you everything you need to get a basic program running which prints to stdout
or flashes a LED.
• hardware_uart
• hardware_gpio
• pico_binary_info
• pico_runtime
• pico_platform
• pico_printf
• pico_stdio
• pico_standard_link
• pico_util
There are some basic default values used by these functions that will default to usable values, however, they can be
customised in a board definition header via config.h or similar
• void setup_default_uart ()
• void set_sys_clock_48mhz ()
• void set_sys_clock_pll (uint32_t vco_freq, uint post_div1, uint post_div2)
• bool check_sys_clock_khz (uint32_t freq_khz, uint *vco_freq_out, uint *post_div1_out, uint *post_div2_out)
• static bool set_sys_clock_khz (uint32_t freq_khz, bool required)
4.2.3.2.1. check_sys_clock_khz
Parameters
• true if the frequency is possible and the output parameters have been written.
4.2.3.2.2. set_sys_clock_48mhz
void set_sys_clock_48mhz ()
Set the system clock to 48MHz, and set the peripheral clock to match.
4.2.3.2.3. set_sys_clock_khz
Note that not all clock frequencies are possible; it is preferred that you use
src/rp2_common/hardware_clocks/scripts/vcocalc.py to calculate the parameters for use with set_sys_clock_pll
Parameters
4.2.3.2.4. set_sys_clock_pll
See the PLL documentation in the datasheet for details of driving the PLLs.
Parameters
• vco_freq The voltage controller oscillator frequency to be used by the SYS PLL
• post_div1 The first post divider for the SYS PLL
• post_div2 The second post divider for the SYS PLL.
4.2.3.2.5. setup_default_uart
void setup_default_uart ()
By default this will use UART 0, with TX to pin GPIO 0, RX to pin GPIO 1, and the baudrate to 115200
Calling this method also initializes stdin/stdout over UART if the pico_stdio_uart library is linked.
4.2.4. pico_sync
Synchronization primitives and mutual exclusion.
4.2.4.1. Modules
• critical_section
Critical Section API for short-lived mutual exclusion safe for IRQ and multi-core.
• mutex
Mutex API for non IRQ mutual exclusion between cores.
• sem
Semaphore API for restricting access to a resource.
4.2.5. critical_section
Critical Section API for short-lived mutual exclusion safe for IRQ and multi-core.
A critical section is non-reentrant, and provides mutual exclusion using a spin-lock to prevent access from the other core,
and from (higher priority) interrupts on the same core. It does the former using a spin lock and the latter by disabling
interrupts on the calling core.
Because interrupts are disabled by this function, uses of the critical_section should be as short as possible.
4.2.5.2.1. critical_section_enter_blocking
Enter a critical_section.
If the spin lock associated with this critical section is in use, then this method will block until it is released.
Parameters
4.2.5.2.2. critical_section_exit
Release a critical_section.
Parameters
4.2.5.2.3. critical_section_init
Initialise a critical_section structure allowing the system to assign a spin lock number.
The critical section is initialized ready for use, and will use a (possibly shared) spin lock number assigned by the system.
Note that in general it is unlikely that you would be nesting critical sections, however if you do so you use
critical_section_init_with_lock_num to ensure that the spin lock’s used are different.
Parameters
4.2.5.2.4. critical_section_init_with_lock_num
Parameters
4.2.6. mutex
Mutex API for non IRQ mutual exclusion between cores.
Mutexes are application level locks usually used protecting data structures that might be used by multiple cores. Unlike
critical sections, the mutex protected code is not necessarily required/expected to complete quickly, as no other
sytemwide locks are held on account of a locked mutex.
Because they are not re-entrant on the same core, blocking on a mutex should never be done in an IRQ handler. It is valid
to call mutex_try_enter from within an IRQ handler, if the operation that would be conducted under lock can be skipped if
the mutex is locked (at least by the same core).
See critical_section.h for protecting access between multiple cores AND IRQ handlers
4.2.6.1. Macros
4.2.6.3.1. mutex_enter_block_until
Wait until the specific time to take ownership of the mutex. If the calling core can take ownership of the mutex before the
timeout expires, then true will be returned and the calling core will own the mutex, otherwise false will be returned and the
calling core will own the mutex.
Parameters
Returns
• true if mutex now owned, false if timeout occurred before mutex became available
4.2.6.3.2. mutex_enter_blocking
This function will block until the calling core can claim ownership of the mutex. On return the caller core owns the mutex
Parameters
4.2.6.3.3. mutex_enter_timeout_ms
Wait for up to the specific time to take ownership of the mutex. If the calling core can take ownership of the mutex before
the timeout expires, then true will be returned and the calling core will own the mutex, otherwise false will be returned and
the calling core will own the mutex.
Parameters
• true if mutex now owned, false if timeout occurred before mutex became available
4.2.6.3.4. mutex_exit
Parameters
4.2.6.3.5. mutex_init
Parameters
4.2.6.3.6. mutex_is_initialzed
Parameters
4.2.6.3.7. mutex_try_enter
Parameters
4.2.7. sem
Semaphore API for restricting access to a resource.
A semaphore holds a number of available permits. sem_acquire methods will acquire a permit if available (reducing the
available count by 1) or block if the number of available permits is 0. sem_release() increases the number of available
permits by one potentially unblocking a sem_acquire method.
Note that sem_release() may be called an arbitrary number of times, however the number of available permits is capped
to the max_permit value specified during semaphore initialization.
Although these semaphore related functions can be used from IRQ handlers, it is obviously preferable to only release
semaphores from within an IRQ handler (i.e. avoid blocking)
4.2.7.2.1. sem_acquire_blocking
Parameters
4.2.7.2.2. sem_acquire_timeout_ms
This function will block and wait if no permits are available, until the defined timeout has been reached. If the timeout is
reached the function will return false, otherwise it will return true.
Parameters
4.2.7.2.3. sem_available
Parameters
4.2.7.2.4. sem_init
Parameters
4.2.7.2.5. sem_release
Increases the number of permits by one (unless the number of permits is already at the maximum). A blocked sem_acquire
will be released if the number of permits is increased.
Parameters
4.2.7.2.6. sem_reset
Reset value should be from 0 to the max_permits specified in the init function
Parameters
4.2.8. pico_time
API for accurate timestamps, sleeping, and time based callbacks.
The functions defined here provide a much more powerful and user friendly wrapping around the low level hardware timer
functionality. For these functions (and any other SDK functionality e.g. timeouts, that relies on them) to work correctly, the
hardware timer should not be modified. i.e. it is expected to be monotonically increasing once per microsecond.
Fortunately there is no need to modify the hardware timer as any functionality you can think of that isn’t already covered
here can easily be modelled by adding or subtracting a constant value from the unmodified hardware timer.
• hardware_timer
4.2.8.1. Modules
• timestamp
Timestamp functions relating to points in time (including the current time)
• sleep
Sleep functions for delaying execution in a lower power state.
• alarm
Alarm functions for scheduling future execution.
• repeating_timer
Repeating Timer functions for simple scheduling of repeated execution.
4.2.9. timestamp
Timestamp functions relating to points in time (including the current time)
These are functions for dealing with timestamps (i.e. instants in time) represented by the type absolute_time_t. This
opaque type is provided to help prevent accidental mixing of timestamps and relative time values.
4.2.9.2.1. absolute_time_diff_us
Parameters
• the number of microseconds between the two timestamps (positive if to is after from except in case of overflow)
4.2.9.2.2. delayed_by_ms
Parameters
4.2.9.2.3. delayed_by_us
Parameters
4.2.9.2.4. get_absolute_time
Returns an opaque high fidelity representation of the current time sampled during the call.
Returns
• absolute_time_t
• sleep_until()
• time_us_64()
4.2.9.2.5. is_nil_time
Parameters
• t the timestamp
Returns
• nil_time()
4.2.9.2.6. make_timeout_time_ms
Convenience method to get the timestamp a number of milliseconds from the current time.
Parameters
4.2.9.2.7. make_timeout_time_us
Convenience method to get the timestamp a number of microseconds from the current time.
Parameters
4.2.9.2.8. to_ms_since_boot
fn to_ms_since_boot
Parameters
• to_us_since_boot
4.2.10. sleep
Sleep functions for delaying execution in a lower power state.
These functions allow the calling core to sleep. This is a lower powered sleep; waking and re-checking time on every
processor event (WFE)
Lower powered sleep requires use of the default alarm pool which may be disabled by the
PICO_TIME_DEFAULT_ALARM_POOL_DISABLED define or currently full in which case these functions become busy waits
instead.
Whilst sleep_ functions are preferable to busy_wait functions from a power perspective, the busy_wait equivalent function
may return slightly sooner after the target is reached.
• busy_wait_until()
• busy_wait_us()
• busy_wait_us_32()
4.2.10.2.1. best_effort_wfe_or_timeout
This method will return in response to a an event (as per __wfe) or when the target time is reached, or at any point before.
This method can be used to implement a lower power polling loop waiting on some condition signalled by an event
(__sev()).
This is called because under certain circumstances (notably the default timer pool being disabled or full) the best effort is
simply to return immediately without a __wfe, thus turning the calling code into a busy wait.
Example usage:
Parameters
4.2.10.2.2. sleep_ms
Parameters
4.2.10.2.3. sleep_until
Parameters
• sleep_us()
• busy_wait_until()
4.2.10.2.4. sleep_us
Parameters
• busy_wait_us()
4.2.11. alarm
Alarm functions for scheduling future execution.
Alarms are added to alarm pools, which may hold a certain fixed number of active alarms. Each alarm pool utilizes one of
four underlying hardware alarms, thus you may have up to four alarm pools. An alarm pool calls (except when the
callback would happen before or during being set) the callback on the core from which the alarm pool was created.
Callbacks are called from the hardware alarm IRQ handler, so care must be taken in their implementation.
See also
• struct alarm_pool
• hardware_timer
4.2.11.1. Macros
• #define PICO_TIME_DEFAULT_ALARM_POOL_DISABLED 0
If 1 then the default alarm pool is disabled (so no hardware alarm is claimed for the pool)
• #define PICO_TIME_DEFAULT_ALARM_POOL_HARDWARE_ALARM_NUM 3
Selects which hardware alarm is used for the default alarm pool.
• #define PICO_TIME_DEFAULT_ALARM_POOL_MAX_TIMERS 16
Selects the maximum number of concurrent timers in the default alarm pool.
4.2.11.2. Typedefs
• void alarm_pool_init_default ()
• alarm_pool_t * alarm_pool_get_default ()
• alarm_pool_t * alarm_pool_create (uint hardware_alarm_num, uint max_timers)
• uint alarm_pool_hardware_alarm_num (alarm_pool_t *pool)
• void alarm_pool_destroy (alarm_pool_t *pool)
• alarm_id_t alarm_pool_add_alarm_at (alarm_pool_t *pool, absolute_time_t time, alarm_callback_t callback, void
*user_data, bool fire_if_past)
• static alarm_id_t alarm_pool_add_alarm_in_us (alarm_pool_t *pool, uint64_t us, alarm_callback_t callback, void
*user_data, bool fire_if_past)
• static alarm_id_t alarm_pool_add_alarm_in_ms (alarm_pool_t *pool, uint32_t ms, alarm_callback_t callback, void
*user_data, bool fire_if_past)
4.2.11.4.1. add_alarm_at
Generally the callback is called as soon as possible after the time specified from an IRQ handler on the core of the default
alarm pool (generally core 0). If the callback is in the past or happens before the alarm setup could be completed, then
this method will optionally call the callback itself and then return a return code to indicate that the target time has passed.
Parameters
• time the timestamp when (after which) the callback should fire
• callback the callback function
• user_data user data to pass to the callback function
• fire_if_past if true, this method will call the callback itself before returning 0 if the timestamp happens before or
during this method call
Returns
4.2.11.4.2. add_alarm_in_ms
Generally the callback is called as soon as possible after the time specified from an IRQ handler on the core of the default
alarm pool (generally core 0). If the callback is in the past or happens before the alarm setup could be completed, then
this method will optionally call the callback itself and then return a return code to indicate that the target time has passed.
Parameters
• ms the delay (from now) in milliseconds when (after which) the callback should fire
• callback the callback function
• user_data user data to pass to the callback function
• fire_if_past if true, this method will call the callback itself before returning 0 if the timestamp happens before or
during this method call
Returns
4.2.11.4.3. add_alarm_in_us
Generally the callback is called as soon as possible after the time specified from an IRQ handler on the core of the default
alarm pool (generally core 0). If the callback is in the past or happens before the alarm setup could be completed, then
this method will optionally call the callback itself and then return a return code to indicate that the target time has passed.
Parameters
• us the delay (from now) in microseconds when (after which) the callback should fire
• callback the callback function
• user_data user data to pass to the callback function
• fire_if_past if true, this method will call the callback itself before returning 0 if the timestamp happens before or
during this method call
Returns
4.2.11.4.4. alarm_pool_add_alarm_at
Generally the callback is called as soon as possible after the time specified from an IRQ handler on the core the alarm
pool was created on. If the callback is in the past or happens before the alarm setup could be completed, then this
method will optionally call the callback itself and then return a return code to indicate that the target time has passed.
Parameters
• pool the alarm pool to use for scheduling the callback (this determines which hardware alarm is used, and which
core calls the callback)
• time the timestamp when (after which) the callback should fire
• callback the callback function
• user_data user data to pass to the callback function
• fire_if_past if true, this method will call the callback itself before returning 0 if the timestamp happens before or
during this method call
Returns
4.2.11.4.5. alarm_pool_add_alarm_in_ms
Generally the callback is called as soon as possible after the time specified from an IRQ handler on the core the alarm
pool was created on. If the callback is in the past or happens before the alarm setup could be completed, then this
method will optionally call the callback itself and then return a return code to indicate that the target time has passed.
Parameters
• pool the alarm pool to use for scheduling the callback (this determines which hardware alarm is used, and which
core calls the callback)
• ms the delay (from now) in milliseconds when (after which) the callback should fire
• callback the callback function
• user_data user data to pass to the callback function
• fire_if_past if true, this method will call the callback itself before returning 0 if the timestamp happens before or
during this method call
Returns
4.2.11.4.6. alarm_pool_add_alarm_in_us
Generally the callback is called as soon as possible after the time specified from an IRQ handler on the core the alarm
pool was created on. If the callback is in the past or happens before the alarm setup could be completed, then this
method will optionally call the callback itself and then return a return code to indicate that the target time has passed.
Parameters
• pool the alarm pool to use for scheduling the callback (this determines which hardware alarm is used, and which
core calls the callback)
• us the delay (from now) in microseconds when (after which) the callback should fire
• callback the callback function
• user_data user data to pass to the callback function
• fire_if_past if true, this method will call the callback itself before returning 0 if the timestamp happens before or
during this method call
Returns
4.2.11.4.7. alarm_pool_cancel_alarm
Cancel an alarm.
Parameters
4.2.11.4.8. alarm_pool_create
The alarm pool will call callbacks from an alarm IRQ Handler on the core of this function is called from.
In many situations there is never any need for anything other than the default alarm pool, however you might want to
create another if you want alarm callbacks on core 1 or require alarm pools of different priority (IRQ priority based
preemption of callbacks)
Parameters
• alarm_pool_get_default()
• hardware_claiming
4.2.11.4.9. alarm_pool_destroy
Destroy the alarm pool, cancelling all alarms and freeing up the underlying hardware alarm.
Parameters
4.2.11.4.10. alarm_pool_get_default
alarm_pool_t* alarm_pool_get_default ()
The default alarm pool used when alarms are added without specifying an alarm pool, and also used by the SDK to
support lower power sleeps and timeouts.
See also
• PICO_TIME_DEFAULT_ALARM_POOL_HARDWARE_ALARM_NUM
4.2.11.4.11. alarm_pool_hardware_alarm_num
Parameters
4.2.11.4.12. alarm_pool_init_default
void alarm_pool_init_default ()
Create the default alarm pool (if not already created or disabled)
4.2.11.4.13. cancel_alarm
Parameters
4.2.12. repeating_timer
Repeating Timer functions for simple scheduling of repeated execution.
The regular alarm_ functionality can be used to make repeating alarms (by return non zero from the callback), however
these methods abstract that further (at the cost of a user structure to store the repeat delay in (which the alarm
framework does not have space for).
• struct repeating_timer
Information about a repeating timer.
4.2.12.2. Typedefs
4.2.12.4.1. add_repeating_timer_ms
Add a repeating timer that is called repeatedly at the specified interval in milliseconds.
Generally the callback is called as soon as possible after the time specified from an IRQ handler on the core of the default
alarm pool (generally core 0). If the callback is in the past or happens before the alarm setup could be completed, then
this method will optionally call the callback itself and then return a return code to indicate that the target time has passed.
Parameters
• delay_ms the repeat delay in milliseconds; if >0 then this is the delay between one callback ending and the next
starting; if <0 then this is the negative of the time between the starts of the callbacks. The value of 0 is treated as 1
microsecond
Returns
• false if there were no alarm slots available to create the timer, true otherwise.
4.2.12.4.2. add_repeating_timer_us
Add a repeating timer that is called repeatedly at the specified interval in microseconds.
Generally the callback is called as soon as possible after the time specified from an IRQ handler on the core of the default
alarm pool (generally core 0). If the callback is in the past or happens before the alarm setup could be completed, then
this method will optionally call the callback itself and then return a return code to indicate that the target time has passed.
Parameters
• delay_us the repeat delay in microseconds; if >0 then this is the delay between one callback ending and the next
starting; if <0 then this is the negative of the time between the starts of the callbacks. The value of 0 is treated as 1
• out the pointer to the user owned structure to store the repeating timer info in. BEWARE this storage location must
outlive the repeating timer, so be careful of using stack space
Returns
• false if there were no alarm slots available to create the timer, true otherwise.
4.2.12.4.3. alarm_pool_add_repeating_timer_ms
Add a repeating timer that is called repeatedly at the specified interval in milliseconds.
Generally the callback is called as soon as possible after the time specified from an IRQ handler on the core the alarm
pool was created on. If the callback is in the past or happens before the alarm setup could be completed, then this
method will optionally call the callback itself and then return a return code to indicate that the target time has passed.
Parameters
• pool the alarm pool to use for scheduling the repeating timer (this determines which hardware alarm is used, and
which core calls the callback)
• delay_ms the repeat delay in milliseconds; if >0 then this is the delay between one callback ending and the next
starting; if <0 then this is the negative of the time between the starts of the callbacks. The value of 0 is treated as 1
microsecond
Returns
• false if there were no alarm slots available to create the timer, true otherwise.
4.2.12.4.4. alarm_pool_add_repeating_timer_us
Add a repeating timer that is called repeatedly at the specified interval in microseconds.
Generally the callback is called as soon as possible after the time specified from an IRQ handler on the core the alarm
pool was created on. If the callback is in the past or happens before the alarm setup could be completed, then this
method will optionally call the callback itself and then return a return code to indicate that the target time has passed.
Parameters
• pool the alarm pool to use for scheduling the repeating timer (this determines which hardware alarm is used, and
which core calls the callback)
• delay_us the repeat delay in microseconds; if >0 then this is the delay between one callback ending and the next
starting; if <0 then this is the negative of the time between the starts of the callbacks. The value of 0 is treated as 1
• user_data user data to pass to store in the repeating_timer structure for use by the callback.
• out the pointer to the user owned structure to store the repeating timer info in. BEWARE this storage location must
outlive the repeating timer, so be careful of using stack space
Returns
• false if there were no alarm slots available to create the timer, true otherwise.
4.2.12.4.5. cancel_repeating_timer
Parameters
4.2.13. pico_unique_id
Unique device ID access API.
RP2040 does not have an on-board unique identifier (all instances of RP2040 silicon are identical and have no persistent
state). However, RP2040 boots from serial NOR flash devices which have a 64-bit unique ID as a standard feature, and
there is a 1:1 association between RP2040 and flash, so this is suitable for use as a unique identifier for an RP2040-based
board.
This library injects a call to the flash_get_unique_id function from the hardware_flash library, to run before main, and
stores the result in a static location which can safely be accessed at any time via pico_get_unique_id().
This avoids some pitfalls of the hardware_flash API, which requires any flash-resident interrupt routines to be disabled
when called into.
• struct pico_unique_board_id_t
Unique board identifier.
4.2.13.3.1. pico_get_unique_board_id
Get the unique 64-bit device identifier which was retrieved from the external NOR flash device at boot.
Parameters
4.2.14. pico_util
Useful data structures and utility functions.
4.2.14.1. Modules
• datetime
Date/Time formatting.
• pheap
Pairing Heap Implementation.
• queue
Multi-core and IRQ safe queue implementation.
4.2.15. datetime
Date/Time formatting.
• struct datetime_t
Structure containing date and time information.
4.2.15.3.1. datetime_to_str
Parameters
4.2.16. pheap
Pairing Heap Implementation.
pheap defines a simple pairing heap. the implementation simply tracks array indexes, it is up to the user to provide
storage for heap entries and a comparison function.
NOTE
this class is not safe for concurrent usage. It should be externally protected. Furthermore if used concurrently, the
caller needs to protect around their use of the returned id. for example, ph_remove_head returns the id of an element
that is no longer in the heap.
The user can still use this to look at the data in their companion array, however obviously further operations on the heap
may cause them to overwrite that data as the id may be reused on subsequent operations
4.2.17. queue
Multi-core and IRQ safe queue implementation.
Note that this queue stores values of a specified size, and pushed values are copied into the queue
• void queue_init_with_spinlock (queue_t *q, uint element_size, uint element_count, uint spinlock_num)
• static void queue_init (queue_t *q, uint element_size, uint element_count)
• void queue_free (queue_t *q)
• static uint queue_get_level_unsafe (queue_t *q)
• static uint queue_get_level (queue_t *q)
• static bool queue_is_empty (queue_t *q)
• static bool queue_is_full (queue_t *q)
• bool queue_try_add (queue_t *q, void *data)
• bool queue_try_remove (queue_t *q, void *data)
• bool queue_try_peek (queue_t *q, void *data)
• void queue_add_blocking (queue_t *q, void *data)
• void queue_remove_blocking (queue_t *q, void *data)
• void queue_peek_blocking (queue_t *q, void *data)
4.2.17.2.1. queue_add_blocking
If the queue is full this function will block, until a removal happens on the queue
Parameters
4.2.17.2.2. queue_free
Parameters
4.2.17.2.3. queue_get_level
Parameters
4.2.17.2.4. queue_get_level_unsafe
This does not use the spinlock, so may return incorrect results if the spin lock is not externally locked
Parameters
4.2.17.2.5. queue_init
Parameters
4.2.17.2.6. queue_init_with_spinlock
Parameters
4.2.17.2.7. queue_is_empty
Parameters
4.2.17.2.8. queue_is_full
Parameters
4.2.17.2.9. queue_peek_blocking
Parameters
4.2.17.2.10. queue_remove_blocking
If the queue is empty this function will block until a value is added.
Parameters
4.2.17.2.11. queue_try_add
If the queue is full this function will return immediately with false, otherwise the data is copied into a new value added to
the queue, and this function will return true.
Parameters
4.2.17.2.12. queue_try_peek
If the queue is not empty this function will return immediately with true with the peeked entry copied into the location
specified by the data parameter, otherwise the function will return false.
Parameters
4.2.17.2.13. queue_try_remove
If the queue is not empty function will copy the removed value into the location provided and return immediately with true,
otherwise the function will return immediately with false.
Parameters
4.3.1. tinyusb_device
TinyUSB Device-mode support for the RP2040
4.3.2. tinyusb_host
TinyUSB Host-mode support for the RP2040
boot_stage2 Second stage boot loaders responsible for setting up external flash.
pico_base Core types and macros for the Raspberry Pi Pico SDK. This header is intended to be included by
all source code.
pico_bit_ops Optimized bit manipulation functions. Additionally provides replacement implementations of the
compiler built-ins builtin_popcount, builtin_clz and __bulitin_ctz.
pico_divider Optimized 32 and 64 bit division functions accelerated by the RP2040 hardware divider.
Additionally provides integration with the C / and % operators.
pico_mem_ops Provides optimized replacement implementations of the compiler built-in memcpy, memset and
related functions:
pico_stdio Customized stdio support allowing for input and output from UART, USB, semi-hosting etc.
pico_standard_link Standard link step providing the basics for creating a runnable binary.
4.4.1. boot_stage2
Second stage boot loaders responsible for setting up external flash.
4.4.2. pico_base
Core types and macros for the Raspberry Pi Pico SDK. This header is intended to be included by all source code.
4.4.3. pico_bit_ops
Optimized bit manipulation functions. Additionally provides replacement implementations of the compiler built-ins
builtin_popcount, builtin_clz and __bulitin_ctz.
4.4.3.2.1. __rev
Parameters
4.4.3.2.2. __revll
Parameters
4.4.4. pico_bootrom
Access to functions and data in the RP2040 bootrom.
4.4.4.2.1. reset_usb_boot
This function reboots the device into the BOOTSEL mode ('usb boot").
Facilities are provided to enable an "activity light" via GPIO attached LED for the USB Mass Storage Device, and to limit the
USB interfaces exposed.
Parameters
• usb_activity_gpio_pin_mask 0 No pins are used as per a cold boot. Otherwise a single bit set indicating which GPIO pin
should be set to output and raised whenever there is mass storage activity from the host.
4.4.4.2.2. rom_data_lookup
Parameters
• a pointer to the data, or NULL if the code does not match any bootrom function
4.4.4.2.3. rom_func_lookup
Parameters
• a pointer to the function, or NULL if the code does not match any bootrom function
4.4.4.2.4. rom_funcs_lookup
This method looks up the 'codes' in the table, and convert each table entry to the looked up function pointer, if there is a
function for that code in the bootrom.
Parameters
• table an IN/OUT array, elements are codes on input, function pointers on success.
• count the number of elements in the table
Returns
• true if all the codes were found, and converted to function pointers, false otherwise
4.4.4.2.5. rom_table_code
These codes are uses to lookup data or function addresses in the bootrom
Parameters
4.4.5. pico_cxx_options
non-code library controlling C++ related compile options
4.4.6. pico_divider
Optimized 32 and 64 bit division functions accelerated by the RP2040 hardware divider. Additionally provides integration
with the C / and % operators.
4.4.6.2.1. div_s32s32
Parameters
• a Dividend
• b Divisor
Returns
• quotient
4.4.6.2.2. div_s32s32_unsafe
Parameters
• a Dividend
• b Divisor
Returns
• quotient
4.4.6.2.3. div_s64s64
Parameters
• a Dividend
• b Divisor
Returns
• Quotient
4.4.6.2.4. div_s64s64_unsafe
Parameters
• a Dividend
• b Divisor
Returns
• Quotient
4.4.6.2.5. div_u32u32
Parameters
• a Dividend
• b Divisor
Returns
• Quotient
4.4.6.2.6. div_u32u32_unsafe
Parameters
• a Dividend
• b Divisor
Returns
• Quotient
4.4.6.2.7. div_u64u64
Parameters
• a Dividend
• b Divisor
Returns
• Quotient
4.4.6.2.8. div_u64u64_unsafe
Parameters
• a Dividend
• b Divisor
Returns
• Quotient
4.4.6.2.9. divmod_s32s32
Parameters
• a Dividend
• b Divisor
Returns
4.4.6.2.10. divmod_s32s32_rem
Parameters
• a Dividend
• b Divisor
• rem The remainder of dividend/divisor
Returns
4.4.6.2.11. divmod_s32s32_rem_unsafe
Parameters
• a Dividend
• b Divisor
• rem The remainder of dividend/divisor
Returns
4.4.6.2.12. divmod_s32s32_unsafe
Parameters
• a Dividend
• b Divisor
Returns
4.4.6.2.13. divmod_s64s64
Parameters
• a Dividend
• b Divisor
Returns
4.4.6.2.14. divmod_s64s64_rem
Parameters
• a Dividend
• b Divisor
• rem The remainder of dividend/divisor
Returns
4.4.6.2.15. divmod_s64s64_rem_unsafe
Parameters
• a Dividend
• b Divisor
• rem The remainder of dividend/divisor
Returns
4.4.6.2.16. divmod_s64s64_unsafe
Parameters
• a Dividend
• b Divisor
Returns
4.4.6.2.17. divmod_u32u32
Parameters
• a Dividend
• b Divisor
Returns
4.4.6.2.18. divmod_u32u32_rem
Parameters
• a Dividend
• b Divisor
• rem The remainder of dividend/divisor
Returns
4.4.6.2.19. divmod_u32u32_rem_unsafe
Parameters
• a Dividend
• b Divisor
• rem The remainder of dividend/divisor
Returns
4.4.6.2.20. divmod_u32u32_unsafe
Parameters
• a Dividend
• b Divisor
Returns
4.4.6.2.21. divmod_u64u64
Parameters
• a Dividend
• b Divisor
Returns
4.4.6.2.22. divmod_u64u64_rem
Parameters
• a Dividend
• b Divisor
• rem The remainder of dividend/divisor
Returns
4.4.6.2.23. divmod_u64u64_rem_unsafe
Parameters
• a Dividend
• b Divisor
• rem The remainder of dividend/divisor
Returns
4.4.6.2.24. divmod_u64u64_unsafe
Parameters
• a Dividend
• b Divisor
Returns
4.4.7. pico_double
Optimized double-precision floating point functions.
(Replacement) optimized implementations are provided of the following compiler built-ins and math library functions:
• sqrt, cos, sin, tan, atan2, exp, log, ldexp, copysign, trunc, floor, ceil, round, asin, acos, atan, sinh, cosh, tanh, asinh,
acosh, atanh, exp2, log2, exp10, log10, pow,, hypot, cbrt, fmod, drem, remainder, remquo, expm1, log1p, fma
4.4.8. pico_float
Optimized single-precision floating point functions.
(Replacement) optimized implementations are provided of the following compiler built-ins and math library functions:
• ldexpf, copysignf, truncf, floorf, ceilf, roundf, asinf, acosf, atanf, sinhf, coshf, tanhf, asinhf, acoshf, atanhf, exp2f, log2f,
exp10f, log10f, powf, hypotf, cbrtf, fmodf, dremf, remainderf, remquof, expm1f, log1pf, fmaf
• fix2float, ufix2float, fix642float, ufix642float, float2fix, float2ufix, float2fix64, float2ufix64, float2int, float2int64,
float2int_z, float2int64_z
4.4.9. pico_int64_ops
Optimized replacement implementations of the compiler built-in 64 bit multiplication.
4.4.10. pico_malloc
Multi-core safety for malloc, calloc and free.
4.4.11. pico_mem_ops
Provides optimized replacement implementations of the compiler built-in memcpy, memset and related functions:
• memset, memcpy
• aeabi_memset, aeabi_memset4, aeabi_memset8, aeabi_memcpy, aeabi_memcpy4, aeabi_memcpy8
This library does not provide any additional functions
4.4.12. pico_platform
Compiler definitions for the selected PICO_PLATFORM.
4.4.13. pico_printf
Compact replacement for printf by Marco Paland ([email protected])
4.4.14. pico_runtime
Aggregate runtime support including pico_bit_ops, pico_divider, pico_double, pico_int64_ops, pico_float, pico_malloc,
pico_mem_ops and pico_standard_link.
4.4.15. pico_stdio
Customized stdio support allowing for input and output from UART, USB, semi-hosting etc.
Note the API for adding additional input output devices is not yet considered stable
4.4.15.1. Modules
• pico_stdio_semihosting
Experimental support for stdout using RAM semihosting.
• pico_stdio_uart
Support for stdin/stdout using UART.
• pico_stdio_usb
Support for stdin/stdout over USB serial (CDC)
• void stdio_init_all ()
• void stdio_flush ()
• int getchar_timeout_us (uint32_t timeout_us)
• void stdio_set_driver_enabled (stdio_driver_t *driver, bool enabled)
• void stdio_filter_driver (stdio_driver_t *driver)
• void stdio_set_translate_crlf (stdio_driver_t *driver, bool translate)
4.4.15.3.1. getchar_timeout_us
Parameters
• timeout_us the timeout in microseconds, or 0 to not wait for a character if none available.
Returns
4.4.15.3.2. stdio_filter_driver
Parameters
• driver if non-null then output only that driver will be used for input/output (assuming it is in the list of enabled
drivers). if NULL then all enabled drivers will be used
4.4.15.3.3. stdio_flush
void stdio_flush ()
Initialize all of the present standard stdio types that are linked into the binary.
Call this method once you have set up your clocks to enable the stdio support for UART, USB and semihosting based on
the presence of the respective librariess in the binary.
See also
4.4.15.3.4. stdio_init_all
void stdio_init_all ()
Initialize all of the present standard stdio types that are linked into the binary.
Call this method once you have set up your clocks to enable the stdio support for UART, USB and semihosting based on
the presence of the respective librariess in the binary.
See also
4.4.15.3.5. stdio_set_driver_enabled
Adds or removes a driver from the list of active drivers used for input/output.
Parameters
4.4.15.3.6. stdio_set_translate_crlf
Parameters
4.4.16. pico_stdio_semihosting
Experimental support for stdout using RAM semihosting.
Linking this library or calling pico_enable_stdio_semihosting(TARGET) in the CMake (which achieves the same thing) will
add semihosting to the drivers used for standard output
• void stdio_semihosting_init ()
4.4.16.2.1. stdio_semihosting_init
void stdio_semihosting_init ()
Explicitly initialize stdout over semihosting and add it to the current set of stdout targets.
4.4.17. pico_stdio_uart
Support for stdin/stdout using UART.
Linking this library or calling pico_enable_stdio_uart(TARGET) in the CMake (which achieves the same thing) will add
UART to the drivers used for standard output
• void stdio_uart_init ()
• void stdout_uart_init ()
• void stdin_uart_init ()
• void stdio_uart_init_full (uart_inst_t *uart, uint baud_rate, int tx_pin, int rx_pin)
• bool stdio_usb_init ()
4.4.17.2.1. stdin_uart_init
void stdin_uart_init ()
Explicitly initialize stdin only (no stdout) over UART and add it to the current set of stdin drivers.
This method sets up PICO_DEFAULT_UART_RX_PIN for UART input (if defined) , and configures the baud rate as
PICO_DEFAULT_UART_BAUD_RATE
4.4.17.2.2. stdio_uart_init
void stdio_uart_init ()
Explicitly initialize stdin/stdout over UART and add it to the current set of stdin/stdout drivers.
This method sets up PICO_DEFAULT_UART_TX_PIN for UART output (if defined), PICO_DEFAULT_UART_RX_PIN for input
(if defined) and configures the baud rate as PICO_DEFAULT_UART_BAUD_RATE.
4.4.17.2.3. stdio_uart_init_full
Perform custom initialization initialize stdin/stdout over UART and add it to the current set of stdin/stdout drivers.
Parameters
4.4.17.2.4. stdio_usb_init
bool stdio_usb_init ()
Explicitly initialize USB stdio and add it to the current set of stdin drivers.
4.4.17.2.5. stdout_uart_init
void stdout_uart_init ()
Explicitly initialize stdout only (no stdin) over UART and add it to the current set of stdout drivers.
This method sets up PICO_DEFAULT_UART_TX_PIN for UART output (if defined) , and configures the baud rate as
PICO_DEFAULT_UART_BAUD_RATE
4.4.18. pico_stdio_usb
Support for stdin/stdout over USB serial (CDC)
Linking this library or calling pico_enable_stdio_usb(TARGET) in the CMake (which achieves the same thing) will add USB
CDC to the drivers used for standard output
Note this library is a developer convenience. It is not applicable in all cases; for one it takes full control of the USB device
precluding your use of the USB in device or host mode. For this reason, this library will automatically disengage if you try
to using it alongside tinyusb_device or tinyusb_host. It also takes control of a lower level IRQ and sets up a periodic
background task.
4.4.19. pico_standard_link
Standard link step providing the basics for creating a runnable binary.
This includes
• C runtime initialization
• Linker scripts for 'default', 'no_flash', 'blocked_ram' and 'copy_to_ram' binaries
• 'Binary Information' support
• Linker option control
boot_picoboot Header file for the PICOBOOT USB interface exposed by an RP2040 in BOOTSEL mode.
boot_uf2 Header file for the UF2 format supported by an RP2040 in BOOTSEL mode.
4.5.1. boot_picoboot
Header file for the PICOBOOT USB interface exposed by an RP2040 in BOOTSEL mode.
4.5.2. boot_uf2
Header file for the UF2 format supported by an RP2040 in BOOTSEL mode.
Wiring information
Our 7 Segment display has pins as follows.
--A--
F B
--G--
E C
--D--
By default we are allocating GPIO 2 to A, 3 to B etc. So, connect GPIO 2 to pin A on the 7 segment LED display and so on.
You will need the appropriate resistors (68 ohm should be fine) for each segment. The LED device used here is common
anode, so the anode pin is connected to the 3.3v supply, and the GPIO’s need to pull low (to ground) to complete the
circuit. The pull direction of the GPIO’s is specified in the code itself.
Connect the switch to connect on pressing. One side should be connected to ground, the other to GPIO 9.
Figure 8. Wiring
Diagram for 7
segment LED.
List of Files
CMakeLists.txt
CMake file to incorporate the example in to the examples build tree.
1 add_executable(hello_7segment
2 hello_7segment.c
3 )
4
5 # Pull in our pico_stdlib which pulls in commonly used features
6 target_link_libraries(hello_7segment pico_stdlib)
7
8 # create map/bin/hex file etc.
9 pico_add_extra_outputs(hello_7segment)
10
11 # add url via pico_set_program_url
12 example_auto_set_url(hello_7segment)
hello_7segment.c
The example code.
1 /**
2 * Copyright (c) 2020 Raspberry Pi (Trading) Ltd.
3 *
4 * SPDX-License-Identifier: BSD-3-Clause
5 */
6
7 #include <stdio.h>
8 #include "pico/stdlib.h"
9 #include "hardware/gpio.h"
10
11 /*
12 Our 7 Segment display has pins as follows:
13
14 --A--
15 F B
16 --G--
17 E C
18 --D--
19
20 By default we are allocating GPIO 2 to A, 3 to B etc.
21 So, connect GOIP 2 to pin A on the 7 segment LED display etc. Don't forget
22 the appropriate resistors, best to use one for each segment!
23
24 Connect button so that pressing the switch connects the GPIO 9 (default) to
25 ground (pull down)
26 */
27
28 #define FIRST_GPIO 2
29 #define BUTTON_GPIO (FIRST_GPIO+7)
30
31 // This array converts a number 0-9 to a bit pattern to send to the GPIO's
32 int bits[10] = {
33 0x3f, // 0
34 0x06, // 1
35 0x5b, // 2
36 0x4f, // 3
37 0x66, // 4
38 0x6d, // 5
39 0x7d, // 6
40 0x07, // 7
41 0x7f, // 8
42 0x67 // 9
43 };
44
45 /// \tag::hello_gpio[]
46 int main() {
47 stdio_init_all();
48 printf("Hello, 7segment - press button to count down!\n");
49
50 // We could use gpio_set_dir_out_masked() here
51 for (int gpio = FIRST_GPIO; gpio < FIRST_GPIO + 7; gpio++) {
52 gpio_init(gpio);
53 gpio_set_dir(gpio, GPIO_OUT);
54 // Our bitmap above has a bit set where we need an LED on, BUT, we are pulling low to
light
55 // so invert our output
56 gpio_set_outover(gpio, GPIO_OVERRIDE_INVERT);
57 }
58
59 gpio_init(BUTTON_GPIO);
60 gpio_set_dir(BUTTON_GPIO, GPIO_IN);
61 // We are using the button to pull down to 0v when pressed, so ensure that when
62 // unpressed, it uses internal pull ups. Otherwise when unpressed, the input will
63 // be floating.
64 gpio_pull_up(BUTTON_GPIO);
65
66 int val = 0;
67 while (true) {
68 // Count upwards or downwards depending on button input
69 // We are pulling down on switch active, so invert the get to make
70 // a press count downwards
71 if (!gpio_get(BUTTON_GPIO)) {
72 if (val == 9) {
73 val = 0;
74 } else {
75 val++;
76 }
77 } else if (val == 0) {
78 val = 9;
79 } else {
80 val--;
81 }
82
83 // We are starting with GPIO 2, our bitmap starts at bit 0 so shift to start at 2.
84 int32_t mask = bits[val] << FIRST_GPIO;
85
86 // Set all our GPIO's in one go!
87 // If something else is using GPIO, we might want to use gpio_put_masked()
88 gpio_set_mask(mask);
89 sleep_ms(250);
90 gpio_clr_mask(mask);
91 }
92
93 return 0;
94 }
95 /// \end::hello_gpio[]
Bill of Materials
Table 11. A list of
Item Quantity Details
materials required for
the example
Breadboard 1 generic part
NOTE
The DHT-11 and DHT-22 sensors are the most common. They use the same protocol but have different
characteristics, the DHT-22 has better accuracy, and has a larger sensor range than the DHT-11. The sensor is
available from a number of retailers.
Wiring information
See Figure 9 for wiring instructions.
NOTE
One of the pins (pin 3) on the DHT sensor will not be connected, it is not used.
You will want to place a 10 kΩ resistor between VCC and the data pin, to act as a medium-strength pull up on the data line.
Connecting UART0 of Pico to Raspberry Pi as in Figure 9 and you should see something similar to Figure 10 in minicom
when connected to /dev/serial0 on the Raspberry Pi.
List of Files
A list of files with descriptions of their function;
CMakeLists.txt
Make file to incorporate the example in to the examples build tree.
1 add_executable(dht
2 dht.c
3 )
4
5 target_link_libraries(dht pico_stdlib)
6
7 pico_add_extra_outputs(dht)
8
9 # add url via pico_set_program_url
10 example_auto_set_url(dht)
dht.c
The example code.
1 /**
2 * Copyright (c) 2020 Raspberry Pi (Trading) Ltd.
3 *
4 * SPDX-License-Identifier: BSD-3-Clause
5 **/
6
7 #include <stdio.h>
8 #include <math.h>
9 #include "pico/stdlib.h"
10 #include "hardware/gpio.h"
11
12 const uint LED_PIN = PICO_DEFAULT_LED_PIN;
13 const uint DHT_PIN = 15;
14 const uint MAX_TIMINGS = 85;
15
16 typedef struct {
17 float humidity;
18 float temp_celsius;
19 } dht_reading;
20
21 void read_from_dht(dht_reading *result);
22
23 int main() {
24 stdio_init_all();
25 gpio_init(LED_PIN);
26 gpio_init(DHT_PIN);
27 gpio_set_dir(LED_PIN, GPIO_OUT);
28 while (1) {
29 dht_reading reading;
30 read_from_dht(&reading);
31 float fahrenheit = (reading.temp_celsius * 9 / 5) + 32;
32 printf("Humidity = %.1f%%, Temperature = %.1fC (%.1fF)\n",
33 reading.humidity, reading.temp_celsius, fahrenheit);
34
35 sleep_ms(2000);
36 }
37 }
38
39 void read_from_dht(dht_reading *result) {
40 int data[5] = {0, 0, 0, 0, 0};
41 uint last = 1;
42 uint j = 0;
43
44 gpio_set_dir(DHT_PIN, GPIO_OUT);
45 gpio_put(DHT_PIN, 0);
46 sleep_ms(20);
47 gpio_set_dir(DHT_PIN, GPIO_IN);
48
49 gpio_put(LED_PIN, 1);
50 for (uint i = 0; i < MAX_TIMINGS; i++) {
51 uint count = 0;
52 while (gpio_get(DHT_PIN) == last) {
53 count++;
54 sleep_us(1);
55 if (count == 255) break;
56 }
57 last = gpio_get(DHT_PIN);
58 if (count == 255) break;
59
60 if ((i >= 4) && (i % 2 == 0)) {
61 data[j / 8] <<= 1;
62 if (count > 16) data[j / 8] |= 1;
63 j++;
64 }
65 }
66 gpio_put(LED_PIN, 0);
67
68 if ((j >= 40) && (data[4] == ((data[0] + data[1] + data[2] + data[3]) & 0xFF))) {
69 result->humidity = (float) ((data[0] << 8) + data[1]) / 10;
70 if (result->humidity > 100) {
71 result->humidity = data[0];
72 }
73 result->temp_celsius = (float) (((data[2] & 0x7F) << 8) + data[3]) / 10;
74 if (result->temp_celsius > 125) {
75 result->temp_celsius = data[2];
76 }
77 if (data[2] & 0x80) {
78 result->temp_celsius = -result->temp_celsius;
79 }
80 } else {
81 printf("Bad data\n");
82 }
83 }
Bill of Materials
Table 12. A list of
Item Quantity Details
materials required for
the example
Breadboard 1 generic part
This examples reads the data from the sensor, and runs it through the appropriate compensation routines (see the chip
datasheet for details https://www.bosch-sensortec.com/media/boschsensortec/downloads/datasheets/bst-bme280-
ds002.pdf). At startup the compensation parameters required by the compensation routines are read from the chip. )
Wiring information
Wiring up the device requires 6 jumpers as follows:
NOTE
There are many different manufacturers who sell boards with the BME280. Whilst they all appear slightly different,
they all have, at least, the same 6 pins required to power and communicate. When wiring up a board that is different to
the one in the diagram, ensure you connect up as described in the previous paragraph.
List of Files
CMakeLists.txt
CMake file to incorporate the example in to the examples build tree.
1 add_executable(bme280_spi
2 bme280_spi.c
3 )
4
5 # Pull in our (to be renamed) simple get you started dependencies
6 target_link_libraries(bme280_spi pico_stdlib hardware_spi)
7
8 # create map/bin/hex file etc.
9 pico_add_extra_outputs(bme280_spi)
10
11 # add url via pico_set_program_url
12 example_auto_set_url(bme280_spi)
bme280_spi.c
The example code.
1 /**
2 * Copyright (c) 2020 Raspberry Pi (Trading) Ltd.
3 *
4 * SPDX-License-Identifier: BSD-3-Clause
5 */
6
7 #include <stdio.h>
8 #include <string.h>
9 #include "pico/stdlib.h"
10 #include "hardware/spi.h"
11
12 /* Example code to talk to a bme280 humidity/temperature/pressure sensor.
13
14 NOTE: Ensure the device is capable of being driven at 3.3v NOT 5v. The Pico
15 GPIO (and therefor SPI) cannot be used at 5v.
16
17 You will need to use a level shifter on the SPI lines if you want to run the
18 board at 5v.
19
20 Connections on Raspberry Pi Pico board and a generic bme280 board, other
21 boards may vary.
22
23 GPIO 16 (pin 21) MISO/spi0_rx-> SDO/SDO on bme280 board
24 GPIO 17 (pin 22) Chip select -> CSB/!CS on bme280 board
25 GPIO 18 (pin 24) SCK/spi0_sclk -> SCL/SCK on bme280 board
26 GPIO 19 (pin 25) MOSI/spi0_tx -> SDA/SDI on bme280 board
27 3.3v (pin 3;6) -> VCC on bme280 board
28 GND (pin 38) -> GND on bme280 board
29
30 Note: SPI devices can have a number of different naming schemes for pins. See
31 the Wikipedia page at https://en.wikipedia.org/wiki/Serial_Peripheral_Interface
32 for variations.
33
34 This code uses a bunch of register definitions, and some compensation code derived
35 from the Bosch datasheet which can be found here.
36 https://www.bosch-sensortec.com/media/boschsensortec/downloads/datasheets/bst-bme280-
ds002.pdf
37 */
38
39 #define PIN_MISO 16
40 #define PIN_CS 17
41 #define PIN_SCK 18
42 #define PIN_MOSI 19
43
44 #define SPI_PORT spi0
45 #define READ_BIT 0x80
46
47 int32_t t_fine;
48
49 uint16_t dig_T1;
50 int16_t dig_T2, dig_T3;
51 uint16_t dig_P1;
52 int16_t dig_P2, dig_P3, dig_P4, dig_P5, dig_P6, dig_P7, dig_P8, dig_P9;
53 uint8_t dig_H1, dig_H3;
54 int8_t dig_H6;
55 int16_t dig_H2, dig_H4, dig_H5;
56
57 /* The following compensation functions are required to convert from the raw ADC
58 data from the chip to something usable. Each chip has a different set of
59 compensation parameters stored on the chip at point of manufacture, which are
60 read from the chip at startup and used inthese routines.
61 */
62 int32_t compensate_temp(int32_t adc_T) {
63 int32_t var1, var2, T;
64 var1 = ((((adc_T >> 3) - ((int32_t) dig_T1 << 1))) * ((int32_t) dig_T2)) >> 11;
65 var2 = (((((adc_T >> 4) - ((int32_t) dig_T1)) * ((adc_T >> 4) - ((int32_t) dig_T1))) >>
12) * ((int32_t) dig_T3))
66 >> 14;
67
68 t_fine = var1 + var2;
69 T = (t_fine * 5 + 128) >> 8;
70 return T;
71 }
72
73 uint32_t compensate_pressure(int32_t adc_P) {
74 int32_t var1, var2;
75 uint32_t p;
76 var1 = (((int32_t) t_fine) >> 1) - (int32_t) 64000;
77 var2 = (((var1 >> 2) * (var1 >> 2)) >> 11) * ((int32_t) dig_P6);
78 var2 = var2 + ((var1 * ((int32_t) dig_P5)) << 1);
79 var2 = (var2 >> 2) + (((int32_t) dig_P4) << 16);
80 var1 = (((dig_P3 * (((var1 >> 2) * (var1 >> 2)) >> 13)) >> 3) + ((((int32_t) dig_P2) *
var1) >> 1)) >> 18;
81 var1 = ((((32768 + var1)) * ((int32_t) dig_P1)) >> 15);
82 if (var1 == 0)
83 return 0;
84
85 p = (((uint32_t) (((int32_t) 1048576) - adc_P) - (var2 >> 12))) * 3125;
86 if (p < 0x80000000)
87 p = (p << 1) / ((uint32_t) var1);
88 else
89 p = (p / (uint32_t) var1) * 2;
90
91 var1 = (((int32_t) dig_P9) * ((int32_t) (((p >> 3) * (p >> 3)) >> 13))) >> 12;
92 var2 = (((int32_t) (p >> 2)) * ((int32_t) dig_P8)) >> 13;
93 p = (uint32_t) ((int32_t) p + ((var1 + var2 + dig_P7) >> 4));
94
95 return p;
96 }
97
98 uint32_t compensate_humidity(int32_t adc_H) {
99 int32_t v_x1_u32r;
100 v_x1_u32r = (t_fine - ((int32_t) 76800));
101 v_x1_u32r = (((((adc_H << 14) - (((int32_t) dig_H4) << 20) - (((int32_t) dig_H5) *
v_x1_u32r)) +
102 ((int32_t) 16384)) >> 15) * (((((((v_x1_u32r * ((int32_t) dig_H6)) >>
10) * (((v_x1_u32r *
103
((int32_t) dig_H3))
104 >> 11) + ((int32_t) 32768))) >> 10) + ((int32_t) 2097152)) *
105 ((int32_t) dig_H2) + 8192) >> 14));
106 v_x1_u32r = (v_x1_u32r - (((((v_x1_u32r >> 15) * (v_x1_u32r >> 15)) >> 7) * ((int32_t)
dig_H1)) >> 4));
107 v_x1_u32r = (v_x1_u32r < 0 ? 0 : v_x1_u32r);
108 v_x1_u32r = (v_x1_u32r > 419430400 ? 419430400 : v_x1_u32r);
109
110 return (uint32_t) (v_x1_u32r >> 12);
111 }
112
113 static inline void cs_select() {
114 asm volatile("nop \n nop \n nop");
115 gpio_put(PIN_CS, 0); // Active low
116 asm volatile("nop \n nop \n nop");
117 }
118
119 static inline void cs_deselect() {
120 asm volatile("nop \n nop \n nop");
121 gpio_put(PIN_CS, 1);
122 asm volatile("nop \n nop \n nop");
123 }
124
125 static void write_register(uint8_t reg, uint8_t data) {
126 uint8_t buf[2];
127 buf[0] = reg & 0x7f; // remove read bit as this is a write
128 buf[1] = data;
129 cs_select();
130 spi_write_blocking(SPI_PORT, buf, 2);
131 cs_deselect();
132 sleep_ms(10);
133 }
134
Bill of Materials
Table 13. A list of
Item Quantity Details
materials required for
the example
Breadboard 1 generic part
NOTE
This is a very basic example, and only recovers raw data from the sensor. There are various calibration options
available that should be used to ensure that the final results are accurate. It is also possible to wire up the interrupt pin
to a GPIO and read data only when it is ready, rather than using the polling approach in the example.
Wiring information
Wiring up the device requires 6 jumpers as follows:
NOTE
There are many different manufacturers who sell boards with the MPU9250. Whilst they all appear slightly different,
they all have, at least, the same 6 pins required to power and communicate. When wiring up a board that is different to
the one in the diagram, ensure you connect up as described in the previous paragraph.
List of Files
CMakeLists.txt
CMake file to incorporate the example in to the examples build tree.
1 add_executable(mpu9250_spi
2 mpu9250_spi.c
3 )
4
5 # Pull in our (to be renamed) simple get you started dependencies
6 target_link_libraries(mpu9250_spi pico_stdlib hardware_spi)
7
8 # create map/bin/hex file etc.
9 pico_add_extra_outputs(mpu9250_spi)
10
mpu9250_spi.c
The example code.
1 /**
2 * Copyright (c) 2020 Raspberry Pi (Trading) Ltd.
3 *
4 * SPDX-License-Identifier: BSD-3-Clause
5 */
6
7 #include <stdio.h>
8 #include <string.h>
9 #include "pico/stdlib.h"
10 #include "hardware/spi.h"
11
12 /* Example code to talk to a MPU9250 MEMS accelerometer and gyroscope.
13 Ignores the magnetometer, that is left as a exercise for the reader.
14
15 This is taking to simple approach of simply reading registers. It's perfectly
16 possible to link up an interrupt line and set things up to read from the
17 inbuilt FIFO to make it more useful.
18
19 NOTE: Ensure the device is capable of being driven at 3.3v NOT 5v. The Pico
20 GPIO (and therefor SPI) cannot be used at 5v.
21
22 You will need to use a level shifter on the I2C lines if you want to run the
23 board at 5v.
24
25 Connections on Raspberry Pi Pico board and a generic MPU9250 board, other
26 boards may vary.
27
28 GPIO 4 (pin 6) MISO/spi0_rx-> ADO on MPU9250 board
29 GPIO 5 (pin 7) Chip select -> NCS on MPU9250 board
30 GPIO 6 (pin 9) SCK/spi0_sclk -> SCL on MPU9250 board
31 GPIO 7 (pin 10) MOSI/spi0_tx -> SDA on MPU9250 board
32 3.3v (pin 36) -> VCC on MPU9250 board
33 GND (pin 38) -> GND on MPU9250 board
34
35 Note: SPI devices can have a number of different naming schemes for pins. See
36 the Wikipedia page at https://en.wikipedia.org/wiki/Serial_Peripheral_Interface
37 for variations.
38 The particular device used here uses the same pins for I2C and SPI, hence the
39 using of I2C names
40 */
41
42 #define PIN_MISO 4
43 #define PIN_CS 5
44 #define PIN_SCK 6
45 #define PIN_MOSI 7
46
47 #define SPI_PORT spi0
48 #define READ_BIT 0x80
49
50 static inline void cs_select() {
51 asm volatile("nop \n nop \n nop");
52 gpio_put(PIN_CS, 0); // Active low
53 asm volatile("nop \n nop \n nop");
54 }
55
56 static inline void cs_deselect() {
57 asm volatile("nop \n nop \n nop");
58 gpio_put(PIN_CS, 1);
59 asm volatile("nop \n nop \n nop");
60 }
61
62 static void mpu9250_reset() {
63 // Two byte reset. First byte register, second byte data
64 // There are a load more options to set up the device in different ways that could be
added here
65 uint8_t buf[] = {0x6B, 0x00};
66 cs_select();
67 spi_write_blocking(SPI_PORT, buf, 2);
68 cs_deselect();
69 }
70
71
72 static void read_registers(uint8_t reg, uint8_t *buf, uint16_t len) {
73 // For this particular device, we send the device the register we want to read
74 // first, then subsequently read from the device. The register is auto incrementing
75 // so we don't need to keep sending the register we want, just the first.
76
77 reg |= READ_BIT;
78 cs_select();
79 spi_write_blocking(SPI_PORT, ®, 1);
80 sleep_ms(10);
81 spi_read_blocking(SPI_PORT, 0, buf, len);
82 cs_deselect();
83 sleep_ms(10);
84 }
85
86
87 static void mpu9250_read_raw(int16_t accel[3], int16_t gyro[3], int16_t *temp) {
88 uint8_t buffer[6];
89
90 // Start reading acceleration registers from register 0x3B for 6 bytes
91 read_registers(0x3B, buffer, 6);
92
93 for (int i = 0; i < 3; i++) {
94 accel[i] = (buffer[i * 2] << 8 | buffer[(i * 2) + 1]);
95 }
96
97 // Now gyro data from reg 0x43 for 6 bytes
98 read_registers(0x43, buffer, 6);
99
100 for (int i = 0; i < 3; i++) {
101 gyro[i] = (buffer[i * 2] << 8 | buffer[(i * 2) + 1]);;
102 }
103
104 // Now temperature from reg 0x41 for 2 bytes
105 read_registers(0x41, buffer, 2);
106
107 *temp = buffer[0] << 8 | buffer[1];
108 }
109
110 int main() {
111 stdio_init_all();
112
113 printf("Hello, MPU9250! Reading raw data from registers via SPI...\n");
114
115 // This example will use SPI0 at 0.5MHz.
Bill of Materials
Table 14. A list of
Item Quantity Details
materials required for
the example
Breadboard 1 generic part
NOTE
This is a very basic example, and only recovers raw data from the sensor. There are various calibration options
available that should be used to ensure that the final results are accurate. It is also possible to wire up the interrupt pin
to a GPIO and read data only when it is ready, rather than using the polling approach in the example.
Wiring information
Wiring up the device requires 4 jumpers, to connect VCC (3.3v), GND, SDA and SCL. The example here uses I2C port 0,
which is assigned to GPIO 4 (SDA) and 5 (SCL) in software. Power is supplied from the 3.3V pin.
NOTE
There are many different manufacturers who sell boards with the MPU6050. Whilst they all appear slightly different,
they all have, at least, the same 4 pins required to power and communicate. When wiring up a board that is different to
the one in the diagram, ensure you connect up as described in the previous paragraph.
List of Files
CMakeLists.txt
CMake file to incorporate the example in to the examples build tree.
1 add_executable(mpu6050_i2c
2 mpu6050_i2c.c
3 )
4
5 # Pull in our (to be renamed) simple get you started dependencies
6 target_link_libraries(mpu6050_i2c pico_stdlib hardware_i2c)
7
8 # create map/bin/hex file etc.
9 pico_add_extra_outputs(mpu6050_i2c)
10
11 # add url via pico_set_program_url
12 example_auto_set_url(mpu6050_i2c)
mpu6050_i2c.c
The example code.
1 /**
Bill of Materials
Table 15. A list of
Item Quantity Details
materials required for
the example
Breadboard 1 generic part
NOTE
These LCD displays can also be driven directly using GPIO without the use of an adapter board. That is beyond the
scope of this example.
Wiring information
Wiring up the device requires 4 jumpers, to connect VCC (3.3v), GND, SDA and SCL. The example here uses I2C port 0,
which is assigned to GPIO 4 (SDA) and 5 (SCL) in software. Power is supplied from the 3.3V pin.
WARNING
Many displays of this type are 5v. If you wish to use a 5v display you will need to use level shifters on the SDA and SCL
lines to convert from the 3.3V used by the RP2040. Whilst a 5v display will just about work at 3.3v, the display will be
dim.
List of Files
CMakeLists.txt
CMake file to incorporate the example in to the examples build tree.
1 add_executable(lcd_1602_i2c
2 lcd_1602_i2c.c
3 )
4
5 # Pull in our (to be renamed) simple get you started dependencies
6 target_link_libraries(lcd_1602_i2c pico_stdlib hardware_i2c)
7
8 # create map/bin/hex file etc.
9 pico_add_extra_outputs(lcd_1602_i2c)
10
11 # add url via pico_set_program_url
12 example_auto_set_url(lcd_1602_i2c)
lcd_1602_i2c.c
The example code.
1 /**
2 * Copyright (c) 2020 Raspberry Pi (Trading) Ltd.
3 *
4 * SPDX-License-Identifier: BSD-3-Clause
5 */
6
7 #include <stdio.h>
8 #include <string.h>
9 #include "pico/stdlib.h"
10 #include "hardware/i2c.h"
11 #include "pico/binary_info.h"
12
13 /* Example code to drive a 16x2 LCD panel via a I2C bridge chip (e.g. PCF8574)
14
15 NOTE: The panel must be capable of being driven at 3.3v NOT 5v. The Pico
16 GPIO (and therefor I2C) cannot be used at 5v.
17
18 You will need to use a level shifter on the I2C lines if you want to run the
19 board at 5v.
20
21 Connections on Raspberry Pi Pico board, other boards may vary.
22
23 GPIO 4 (pin 6)-> SDA on LCD bridge board
24 GPIO 5 (pin 7)-> SCL on LCD bridge board
25 3.3v (pin 36) -> VCC on LCD bridge board
26 GND (pin 38) -> GND on LCD bridge board
27 */
28 // commands
29 const int LCD_CLEARDISPLAY = 0x01;
30 const int LCD_RETURNHOME = 0x02;
31 const int LCD_ENTRYMODESET = 0x04;
32 const int LCD_DISPLAYCONTROL = 0x08;
33 const int LCD_CURSORSHIFT = 0x10;
34 const int LCD_FUNCTIONSET = 0x20;
35 const int LCD_SETCGRAMADDR = 0x40;
36 const int LCD_SETDDRAMADDR = 0x80;
37
38 // flags for display entry mode
39 const int LCD_ENTRYSHIFTINCREMENT = 0x01;
40 const int LCD_ENTRYLEFT = 0x02;
41
42 // flags for display and cursor control
43 const int LCD_BLINKON = 0x01;
44 const int LCD_CURSORON = 0x02;
45 const int LCD_DISPLAYON = 0x04;
46
47 // flags for display and cursor shift
48 const int LCD_MOVERIGHT = 0x04;
49 const int LCD_DISPLAYMOVE = 0x08;
50
51 // flags for function set
52 const int LCD_5x10DOTS = 0x04;
53 const int LCD_2LINE = 0x08;
54 const int LCD_8BITMODE = 0x10;
55
56 // flag for backlight control
57 const int LCD_BACKLIGHT = 0x08;
58
59 const int LCD_ENABLE_BIT = 0x04;
60
61 #define I2C_PORT i2c0
62 // By default these LCD display drivers are on bus address 0x27
63 static int addr = 0x27;
64
65 // Modes for lcd_send_byte
66 #define LCD_CHARACTER 1
67 #define LCD_COMMAND 0
68
69 #define MAX_LINES 2
70 #define MAX_CHARS 16
71
72 /* Quick helper function for single byte transfers */
73 void i2c_write_byte(uint8_t val) {
74 i2c_write_blocking(I2C_PORT, addr, &val, 1, false);
75 }
76
77 void lcd_toggle_enable(uint8_t val) {
78 // Toggle enable pin on LCD display
79 // We cannot do this too quickly or things don't work
80 #define DELAY_US 600
81 sleep_us(DELAY_US);
82 i2c_write_byte(val | LCD_ENABLE_BIT);
83 sleep_us(DELAY_US);
84 i2c_write_byte(val & ~LCD_ENABLE_BIT);
85 sleep_us(DELAY_US);
86 }
87
88 // The display is sent a byte as two separate nibble transfers
89 void lcd_send_byte(uint8_t val, int mode) {
90 uint8_t high = mode | (val & 0xF0) | LCD_BACKLIGHT;
91 uint8_t low = mode | ((val << 4) & 0xF0) | LCD_BACKLIGHT;
92
93 i2c_write_byte(high);
94 lcd_toggle_enable(high);
95 i2c_write_byte(low);
96 lcd_toggle_enable(low);
97 }
98
99 void lcd_clear(void) {
100 lcd_send_byte(LCD_CLEARDISPLAY, LCD_COMMAND);
101 }
102
103 // go to location on LCD
104 void lcd_set_cursor(int line, int position) {
105 int val = (line == 0) ? 0x80 + position : 0xC0 + position;
106 lcd_send_byte(val, LCD_COMMAND);
107 }
108
109 static void inline lcd_char(char val) {
110 lcd_send_byte(val, LCD_CHARACTER);
111 }
112
113 void lcd_string(const char *s) {
114 while (*s) {
115 lcd_char(*s++);
116 }
117 }
118
119 void lcd_init() {
120 lcd_send_byte(0x03, LCD_COMMAND);
121 lcd_send_byte(0x03, LCD_COMMAND);
122 lcd_send_byte(0x03, LCD_COMMAND);
123 lcd_send_byte(0x02, LCD_COMMAND);
124
125 lcd_send_byte(LCD_ENTRYMODESET | LCD_ENTRYLEFT, LCD_COMMAND);
126 lcd_send_byte(LCD_FUNCTIONSET | LCD_2LINE, LCD_COMMAND);
127 lcd_send_byte(LCD_DISPLAYCONTROL | LCD_DISPLAYON, LCD_COMMAND);
128 lcd_clear();
129 }
130
131 int main() {
132 // This example will use I2C0 on GPIO4 (SDA) and GPIO5 (SCL)
133 i2c_init(I2C_PORT, 100 * 1000);
134 gpio_set_function(4, GPIO_FUNC_I2C);
135 gpio_set_function(5, GPIO_FUNC_I2C);
136 gpio_pull_up(4);
137 gpio_pull_up(5);
138 // Make the I2C pins available to picotool
139 bi_decl( bi_2pins_with_func(4, 5, GPIO_FUNC_I2C));
140
141 lcd_init();
142
143 static uint8_t *message[] =
144 {
145 "RP2040 by", "Raspberry Pi",
146 "A brand new", "microcontroller",
147 "Twin core M0", "Full C SDK",
148 "More power in", "your product",
149 "More beans", "than Heinz!"
150 };
151
152 while (1) {
153 for (int m = 0; m < sizeof(message) / sizeof(message[0]); m += MAX_LINES) {
154 for (int line = 0; line < MAX_LINES; line++) {
155 lcd_set_cursor(line, (MAX_CHARS / 2) - strlen(message[m + line]) / 2);
156 lcd_string(message[m + line]);
157 }
158 sleep_ms(2000);
159 lcd_clear();
160 }
161 }
162
163 return 0;
164 }
Bill of Materials
Table 16. A list of
Item Quantity Details
materials required for
the example
Breadboard 1 generic part
Configuration is done by setting various predefined values in header files in your code. These will override the default
values from the SDK itself.
So for example, if you wanted to change the default pins used by the UART, you would add the following to your project
header files, before any SDK includes.
#define PICO_DEFAULT_UART_TX_PIN 16
#define PICO_DEFAULT_UART_RX_PIN 17
Configuration Parameters
Table 17. SDK and
Parameter name Defined in Default Description
Board Configuration
Parameters
PARAM_ASSERTIONS_DISABLE_ALL assert.h 0 Global assert disable
Configuration Parameters
Table 18. CMake
Parameter name Defined in Default Description
Configuration
Variables
PICO_BARE_METAL CMakeLists.txt 0 Flag to exclude anything except base
headers from the build
PICO_BOARD board_setup.cmak rp2040 The board name being built for. This is
e overridable from the user environment
PICO_DEFAULT_BINARY_TYPE default The default is flash binaries which are stored in and run from flash.
no_flash This option selects a RAM only binaries, that does not require any
flash. Note: this type of binary must be loaded on each device
reboot via a UF2 file or from the debugger.
copy_to_ram This option selects binaries which are stored in flash, but copy
themselves to RAM before executing.
blocked_ram
TIP
The binary type can be set on a per executable target (as created by add_executable) basis by calling
pico_set_binary_type(target type) where type is the same as for PICO_DEFAULT_BINARY_TYPE
Board Configuration
Board configuration is the process of customising the SDK to run on a specific board design. The SDK comes some
predefined configurations for boards produced by Raspberry Pi, the main (and default) example being the Raspberry Pi
Pico.
Configurations specify a number of parameters that could vary between hardware designs. For example, default UART
ports, on board LED locations and flash capacities etc.
This chapter will go through where these configurations files are, how to make changes and set parameters, and how to
build your SDK using CMake with your customisations.
Board specific configuration files are stored in the SDK source tree, at …/src/boards/include/boards/<boardname>.h. The
default configuration file is that for the Raspberry Pi Pico, and at the time of writing is:
<sdk_path>/src/boards/include/boards/pico.h
This relatively short file contains overrides from default of a small number of parameters used by the SDK when building
code.
1 /*
2 * Copyright (c) 2020 Raspberry Pi (Trading) Ltd.
3 *
4 * SPDX-License-Identifier: BSD-3-Clause
5 */
6
7 // -----------------------------------------------------
8 // NOTE: THIS HEADER IS ALSO INCLUDED BY ASSEMBLER SO
9 // SHOULD ONLY CONSIST OF PREPROCESSOR DIRECTIVES
10 // -----------------------------------------------------
11
12 // This header may be included by other board headers as "boards/pico.h"
13
14 #ifndef _BOARDS_PICO_H
15 #define _BOARDS_PICO_H
16
17 #ifndef PICO_DEFAULT_UART
18 #define PICO_DEFAULT_UART 0
19 #endif
20
21 #ifndef PICO_DEFAULT_UART_TX_PIN
22 #define PICO_DEFAULT_UART_TX_PIN 0
23 #endif
24
25 #ifndef PICO_DEFAULT_UART_RX_PIN
26 #define PICO_DEFAULT_UART_RX_PIN 1
27 #endif
28
29 #ifndef PICO_DEFAULT_LED_PIN
30 #define PICO_DEFAULT_LED_PIN 25
31 #endif
32
33 #ifndef PICO_FLASH_SPI_CLKDIV
34 #define PICO_FLASH_SPI_CLKDIV 2
35 #endif
36
37 #ifndef PICO_FLASH_SIZE_BYTES
38 #define PICO_FLASH_SIZE_BYTES (2 * 1024 * 1024)
39 #endif
40
41 // Drive high to force power supply into PWM mode (lower ripple on 3V3 at light loads)
42 #define PICO_SMPS_MODE_PIN 23
43
44 #ifndef PICO_FLOAT_SUPPORT_ROM_V1
45 #define PICO_FLOAT_SUPPORT_ROM_V1 1
46 #endif
47
48 #ifndef PICO_DOUBLE_SUPPORT_ROM_V1
49 #define PICO_DOUBLE_SUPPORT_ROM_V1 1
50 #endif
51
52 #endif
As can be seen, it sets up the default UART to uart0, the GPIO pins to be used for that UART, the GPIO pin used for the
onboard LED, and the flash size.
To create your own configuration file, create a file in the board ../source/folder with the name of your board, for example,
myboard.h. Enter your board specific parameters in this file.
To create a new build based on a new board configuration (we will use the myboard example from the previous section)
first create a new build folder under your project folder. For our example we will use the pico-examples folder.
$ cd pico-examples
$ mkdir myboard_build
$ cd myboard_build
cmake -D"PICO_BOARD=myboard" ..
This will set up the system ready to build so you can simply type make in the myboard_build folder and the examples will be
built for your new board configuration.
$ cd ~/
$ mkdir pico
$ cd pico
$ git clone -b master https://github.com/raspberrypi/pico-sdk.git
$ cd pico-sdk
$ git submodule update --init --recursive
$ cd ..
$ git clone -b master https://github.com/raspberrypi/pico-examples.git
and then afterwards you can go ahead and build the documentation.
$ cd pico-sdk
$ mkdir build
$ cd build
$ cmake ..
$ make docs
The API documentation will be built and can be found in the pico-sdk/build/docs/doxygen/html directory, see Figure 15.
When building the documentation cmake will go ahead and clone the pico-examples repo into the build directory directly
from Github. This can be over ridden on the command line if you have a local copy of the examples you want to use
instead,
$ cmake -DPICO_EXAMPLES_PATH=../../pico-examples ..
$ export PICO_EXAMPLES_PATH=../../pico-examples
$ cmake ..