Arduino-Pico Documentation
Release 4.4.3
Earle F. Philhower, III
Feb 08, 2025
CONTENTS:
1 Getting Help 3
2 Contributing and Porting to the Core 5
2.1 Contributing to the Core (Pull Requests) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5
2.2 Adding a New Board . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5
2.3 Porting Libraries and Applications to the Core . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6
3 Installation 9
3.1 Installing via Arduino Boards Manager . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 9
3.2 Installing via Arduino CLI . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 10
3.3 Installing via GIT . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 10
3.4 Installing both Arduino and CMake . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11
3.5 Uploading Sketches . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11
3.6 Uploading the First Sketch . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11
3.7 Windows 7 Driver Notes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11
3.8 Windows 7 Installation Problems . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 12
3.9 Uploading Filesystem Images . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 12
3.10 Uploading Sketches with Picotool . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 13
3.11 Uploading Sketches with Picoprobe . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 13
3.12 Uploading Sketches with OpenOCD . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 13
3.13 Debugging with Picoprobe/Debugprobe, OpenOCD, and GDB . . . . . . . . . . . . . . . . . . . . . 14
4 IDE Menus 15
4.1 Board . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 15
4.2 Flash Size . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 15
4.3 CPU Speed . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 15
4.4 Debug Port and Debug Level . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 15
4.5 Generic RP2040 Support . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 15
4.6 Boot Stage 2 Options for Generic RP2040 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 15
5 Using this core with PlatformIO 17
5.1 What is PlatformIO? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 17
5.2 Important steps for Windows users, before installing . . . . . . . . . . . . . . . . . . . . . . . . . . 19
5.3 Current state of development . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 20
5.4 Deprecation warnings . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 20
5.5 Selecting the new core . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 21
5.6 Flash size . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 21
5.7 PSRAM size . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 21
5.8 PSRAM chip select (CS) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 22
5.9 Boot2 Source . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 22
i
5.10 CPU Speed . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 22
5.11 Debug Port . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 22
5.12 Debug Level . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 23
5.13 C++ Exceptions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 23
5.14 Stack Protector . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 23
5.15 RTTI . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 23
5.16 USB Stack . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 24
5.17 USB Customization . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 24
5.18 IP Stack . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 24
5.19 Bluetooth Stack . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 24
5.20 Selecting a different core version . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 24
5.21 Selecting the CPU architecture . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 25
5.22 Examples . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 25
5.23 Debugging . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 25
5.24 Filesystem Uploading . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 27
6 Pin Assignments 29
6.1 I2S . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 29
6.2 Serial1 (UART0), Serial2 (UART1) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 29
6.3 SPI (SPI0), SPI1 (SPI1) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 29
6.4 Wire (I2C0), Wire1 (I2C1) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 29
7 RP2040 Helper Class 31
7.1 Core Internals . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 31
7.2 Hardware Watchdog . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 31
7.3 Memory Information . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 32
7.4 Hardware Identification . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 32
7.5 Bootloader . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 32
7.6 DMA-based MEMCPY . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 32
8 Analog I/O 35
8.1 Analog Input . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 35
8.2 Analog Outputs . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 35
8.3 Analog Output Restrictions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 35
9 Digital I/O 37
9.1 Board-Specific Pins . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 37
9.2 Pin Notation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 37
9.3 Input Modes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 37
9.4 Output Modes (Pad Strength) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 37
9.5 Tone/noTone . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 37
10 BOOTSEL Button 39
11 EEPROM Library 41
11.1 EEPROM Class API . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 41
11.2 EEPROM Examples . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 42
12 I2S (Digital Audio) Audio Library 43
12.1 I2S Class API . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 43
12.2 Sample Writing/Reading API . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 46
12.3 Note About 24-bit Samples . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 47
13 PWM Audio Library 49
13.1 PWM Class API . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 49
ii
14 ADC Input Library 51
14.1 ADC Input API . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 51
15 Serial Ports (USB and UART) 53
15.1 Inversion . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 53
15.2 RP2040 Specific SerialUSB methods . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 54
16 “SoftwareSerial” PIO-based UART 55
16.1 Inversion . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 55
17 SoftwareSerial Emulation 57
18 Servo Library 59
18.1 Pulse Width Defaults . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 59
19 SPI Master (Serial Peripheral Interface) 61
20 Software SPI (Master Only) 63
21 SPI Slave (SPISlave) 65
22 Asynchronous Operation 67
22.1 bool transferAsync(const void *send, void *recv, size_t bytes) . . . . . . . . . . . . . . . . . . . . . 67
22.2 bool finishedAsync() . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 67
22.3 void abortAsync() . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 67
23 Examples 69
24 Wire (I2C Master and Slave) 71
24.1 Asynchronous Operation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 71
25 File Systems 73
25.1 Flash Layout . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 73
25.2 Compatible Filesystem APIs . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 73
25.3 FatFS File System Caveats and Warnings . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 74
25.4 LittleFS File System Limitations . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 74
25.5 Uploading Files to the LittleFS File System . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 74
25.6 Downloading Files from a LittleFS System . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 75
25.7 SD Library Information . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 75
25.8 Using Second SPI port for SD . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 75
25.9 Enabling SDIO operation for SD . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 75
25.10 Using VFS (Virtual File System) for POSIX support . . . . . . . . . . . . . . . . . . . . . . . . . . 76
25.11 File system object (LittleFS/SD/SDFS/FatFS) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 76
25.12 Filesystem information structure . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 79
25.13 Directory object (Dir) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 80
25.14 File object . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 81
26 USB (Arduino and Adafruit_TinyUSB) 85
26.1 Pico SDK USB Support . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 85
26.2 HID Polling Interval . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 85
26.3 Adafruit TinyUSB Arduino Support . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 85
26.4 Adafruit TinyUSB Configuration and Quirks . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 86
27 Multicore Processing 87
27.1 Stack Sizes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 87
27.2 Pausing Cores . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 87
iii
27.3 Communicating Between Cores . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 88
28 Semihosting Support 89
28.1 Running Semihosting on the Development Host . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 89
28.2 SerialSemi - Serial over Semihosting . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 89
28.3 SemiFS - Host filesystem access through Semihosting . . . . . . . . . . . . . . . . . . . . . . . . . 89
29 Profiling Applications with GPROF 91
29.1 Enabling Profiling in an Application . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 91
29.2 Collecting and Analyzing Profile Data . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 92
30 RP2350 Specific Notes 93
30.1 ARM and RISC-V Modes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 93
30.2 P2350-E9 Errata (“Increased leakage current on Bank 0 GPIO when pad input is enabled”) . . . . . . 93
31 RP2350 PSRAM Support 95
31.1 Using PSRAM for regular variables . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 95
31.2 Using PSRAM for dynamic allocations . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 95
31.3 Checking on PSRAM space . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 96
32 Bluetooth on PicoW Support 97
32.1 Enabling Bluetooth . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 97
32.2 Included Bluetooth Libraries . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 97
32.3 Writing Custom Bluetooth Applications . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 97
33 Bluetooth HID Master 99
33.1 BTDeviceInfo Class . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 99
33.2 BluetoothHCI Class . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 99
33.3 BluetoothHIDMaster Operation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 99
33.4 Callback Event Handlers . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 100
33.5 BluetoothHIDMaster Class . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 101
34 Bluetooth Audio (A2DP Source and Sink) 103
34.1 A2DPSink . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 103
34.2 A2DPSource . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 103
35 SingleFileDrive 105
35.1 Callbacks, Interrupt Safety, and File Operations . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 105
35.2 Using SingleFileDrive . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 105
36 FatFSUSB 107
36.1 Callbacks, Interrupt Safety, and File Operations . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 107
37 FreeRTOS SMP 109
37.1 Enabling FreeRTOS . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 109
37.2 Configuration and Predefined Tasks . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 109
37.3 Caveats . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 109
37.4 More Information . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 109
38 WiFi (Raspberry Pi Pico W) Support 111
38.1 Supported Features . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 111
38.2 Important Information . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 111
38.3 Special Thanks . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 112
39 EthernetLWIP (Wired Ethernet) Support 113
39.1 Supported Wired Ethernet Modules . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 113
iv
39.2 Enabling Wired Ethernet . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 113
39.3 Adjusting LWIP Polling . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 114
39.4 Using Interrupt-Driven Handling . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 114
39.5 Adjusting SPI Speed . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 114
39.6 Using the WIZnet W5100S-EVB-Pico . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 115
39.7 Example Code . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 115
39.8 Caveats . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 115
39.9 Special Thanks . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 115
40 WiFiClient 117
40.1 flush and stop . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 117
40.2 setNoDelay . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 117
40.3 getNoDelay . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 118
40.4 setSync . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 118
40.5 getSync . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 118
40.6 setDefaultNoDelay and setDefaultSync . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 118
40.7 getDefaultNoDelay and getDefaultSync . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 118
40.8 Other Function Calls . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 118
41 Server Class 121
41.1 accept . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 121
41.2 available . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 121
41.3 write (write to all clients) not supported . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 121
41.4 setNoDelay . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 122
41.5 Other Function Calls . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 122
42 UDP Class 123
43 Network Time Protocol (NTP) 125
43.1 bool NTP.waitSet(uint32_t timeout) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 125
43.2 bool NTP.waitSet(void (*cb)(), uint32_t timeout) . . . . . . . . . . . . . . . . . . . . . . . . . . . . 126
44 BearSSL WiFi Classes 127
44.1 CPU Requirements . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 127
44.2 Memory Requirements . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 127
44.3 Object Lifetimes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 127
44.4 TLS and HTTPS Basics . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 128
44.5 Public and Private Keys . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 128
44.6 TLS Sessions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 128
44.7 X.509 Certificate(s) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 129
44.8 Certificate Stores . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 129
44.9 Supported Crypto . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 129
45 WiFiClientSecure Class 131
45.1 Validating X509 Certificates (Am I talking to the server I think I’m talking to?) . . . . . . . . . . . . 131
45.2 Client Certificates (Proving I’m who I say I am to the server) . . . . . . . . . . . . . . . . . . . . . . 132
45.3 MFLN or Maximum Fragment Length Negotiation (Saving RAM) . . . . . . . . . . . . . . . . . . . 132
45.4 Sessions (Resuming connections fast) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 133
45.5 Errors . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 133
45.6 Limiting Ciphers (New connections faster) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 133
45.7 Limiting TLS(SSL) Versions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 133
45.8 ESP32 Compatibility . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 133
46 WiFiServerSecure Class 135
46.1 setBufferSizes(int recv, int xmit) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 135
v
46.2 Setting Server Certificates . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 135
46.3 Client sessions (Resuming connections fast) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 135
46.4 Requiring Client Certificates . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 136
47 HTTPClient Library 137
48 OTA Updates 139
48.1 Introduction . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 139
48.2 Compression . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 142
48.3 Uploading from the Arduino IDE . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 143
48.4 Password Protection . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 143
48.5 Web Browser . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 144
48.6 HTTP Server . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 144
48.7 Stream Interface . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 145
49 Libraries Ported/Optimized for the RP2040 147
50 Using the Raspberry Pi Pico SDK (PICO-SDK) 149
50.1 Included SDK . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 149
50.2 Multicore (CORE1) Processing . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 149
50.3 PIOASM (Compiling for the PIO processors) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 149
51 Licensing and Credits 151
vi
Arduino-Pico Documentation, Release 4.4.3
This is the documentation for the Raspberry Pi Pico Arduino core, Arduino-Pico. Arduino-Pico is a community port of
Arduino to the RP2040 (Raspberry Pi Pico processor) and RP2350 (Raspberry Pi Pico 2 processor), intended to make
it easier and more fun to use and program the Raspberry Pi Pico / RP2040 / RP2350 based boards.
This Arduino core uses a custom toolset with GCC 14.2 and Newlib 4.3 and doesn’t require any system-installed
prerequisites.
For the latest version, always check https://github.com/earlephilhower/arduino-pico
CONTENTS: 1
Arduino-Pico Documentation, Release 4.4.3
2 CONTENTS:
CHAPTER
ONE
GETTING HELP
This is a community supported project and has multiple ways to get assistance. Posting complete details, in a polite
and organized way will get the best response.
For bugs in the Core, or to submit patches, please use the GitHub Issues or GitHub Pull Requests
For general questions/discussions use either GitHub Discussions or live-chat with gitter.im
3
Arduino-Pico Documentation, Release 4.4.3
4 Chapter 1. Getting Help
CHAPTER
TWO
CONTRIBUTING AND PORTING TO THE CORE
First of all, thank you for contributing to the project. It’s a lot of work keeping up with all the different uses of the
RP2040, so the more people working on the code, the better. Your assistance can help the project succeed.
2.1 Contributing to the Core (Pull Requests)
We use the standard GitHub Pull Request model. If you’re unfamiliar with it, this guide gives a simple overview of the
process.
All pull requests have to pass a set of Continuous Integration (CI) checks which help make sure the code compiles
under different configurations and has no spelling or style errors.
2.1.1 Tips for a Good Pull Request (PR)
All code in the core and libraries, except for INO sketches, uses a 4-space indent with cuddled brackets. When in
doubt, copy your formatting from the surrounding code. You should install astyle and run tests/restyle.sh on
your machine before committing and pushing any pull requests to ensure the formatting is correct.
Describe the change you’re proposing and why it’s important in your git commit message. If it fixes an open issue,
place Fixes #xxxx (where xxxx is the issue number) in the message to link the two.
Try and only change one thing per pull request. That makes it easier to review and prioritize. Opening up a separate
PR per change also helps keep track of them when release messages are generated.
2.2 Adding a New Board
Adding a new board requires:
• Updated tools/makeboards.py script
• Updated boards.txt file, generated by makeboard.py
• Updated package_pico_index.template.json file, generated by makeboard.py
• New tools/json/BOARD_NAME.json board file for Platform.IO
• New variants/BOARD_NAME/pins_arduino.h header defining the I/O pins
To add a new RP2040 board you will need to update the tools/makeboards.py script. Do NOT manually edit
boards.txt, that file is machine generated. You will need to add a MakeBoard call at the end of the file. Please
be sure to add your board so that it sorts alphabetically, starting with the company name and then the board name.
Otherwise it is hard to find a specific board in the menu.
Run python3 tools/makeboards.py to update the boards.txt file and generate a Platform.IO JSON file in the
tools/json directory.
5
Arduino-Pico Documentation, Release 4.4.3
Create a folder called variants/BOARD_NAME and place in a pins_arduino.h file in it that contains your default pin
name mapping (i.e. SPI0/1 pins, UART pins, LED_DEFAULT, etc.). Copying one of the existing ones as a template
can make this task much simpler.
In your git commit be sure to add the newly generated tools/json/XXX.json file as well as the modified
makeboards script and boards.txt, the new pins_arduino.h header you generated, and the Arduino packaging
JSON package/package_pico_index.template.json. You should also add a note in the README.md file listing
your new board.
Submit the updated commit as a PR and, if all goes well, your board will be in on the next core release.
2.3 Porting Libraries and Applications to the Core
We try and follow Arduino standards so, with luck, porting to this core should be relatively straightforward. The WiFi
library and associates support libraries like WebServer are modeled after the ESP32 and ESP8266 versions of those
libraries, combined with the “standard” Arduino WiFi one.
2.3.1 Compiler Defines for Porting
If you are adding RP2040 support to an existing library and need to isolate code that only runs on this core, use the
following define.
#if defined(ARDUINO_ARCH_RP2040) && !defined(__MBED__)
~~~ your changes ~~~
#endif
2.3.2 Identifying RP2040, RP2530A, or RP2350B
To check if a board is an original RP2040
#if defined(PICO_RP2040)
...OG Pico code...
#endif
For RP2350(A or B):
#if defined(PICO_RP2350)
...Pico 2 code...
#endif
For only RP2350A variants (using the compile options, not the onboard ID register):
#if defined(PICO_RP2350) && !defined(PICO_RP2350B)
...RP2350A only code...
#endif
For only RP2350B variants (again, at compile time as identified by the selected board and not the chip ID register):
#if defined(PICO_RP2350B)
...48-GPIO version code here
#endif
6 Chapter 2. Contributing and Porting to the Core
Arduino-Pico Documentation, Release 4.4.3
2.3.3 Library Architectures
After adding support in the code, libraries need their library.properties and library.json files updated to
indicate support, or the IDE will not know your new code is compatible here.
Add rp2040 to architectures (in library.properties) and "rp2040" to platforms[] (in library.json) to
let the tools know. Note that even the RP2350 is identified as rp2040 for this purpose.
2.3. Porting Libraries and Applications to the Core 7
Arduino-Pico Documentation, Release 4.4.3
8 Chapter 2. Contributing and Porting to the Core
CHAPTER
THREE
INSTALLATION
The Arduino-Pico core can be installed using the Arduino IDE Boards Manager or using git. If you want to simply
write programs for your RP2040 board, the Boards Manager installation will suffice, but if you want to try the latest
pre-release versions and submit improvements, you will need the git installation.
3.1 Installing via Arduino Boards Manager
1. Open up the Arduino IDE and go to File->Preferences.
2. In the dialog that pops up, enter the following URL in the “Additional Boards Manager URLs” field: https:
//github.com/earlephilhower/arduino-pico/releases/download/global/package_rp2040_index.json
3. Hit OK to close the dialog.
4. Go to Tools->Boards->Board Manager in the IDE
5. Type “pico” in the search box and select “Add”:
9
Arduino-Pico Documentation, Release 4.4.3
3.1.1 Arduino IDE Installation Warning
Note for Windows Users: Please do not use the Windows Store version of the actual Arduino application because it
has issues detecting attached Pico boards. Use the “Windows ZIP” or plain “Windows” executable (EXE) download
direct from https://arduino.cc. and allow it to install any device drivers it suggests. Otherwise the Pico board may not
be detected.
Note for Linux Users: If you installed the Arduino IDE using Flatpak, which is common in Pop!_OS, Fedora, and Mint,
among others, you may need to configure Flatpak to allow the IDE access to files outside your home folder. The RP2040
device is sometimes mounted as a folder in /opt or /media, which Flatpak will prevent the Arduino IDE from accessing.
For Arduino IDE V2, override the filesystem restriction using flatpak override --user --filesystem=host
cc.arduino.IDE2 . For For Arduino IDE < V2, use flatpak override --user --filesystem=host cc.
arduino.arduinoide.
3.2 Installing via Arduino CLI
To install using the Arduino command line tool (arduino-cli):
arduino-cli config add board_manager.additional_urls https://github.com/earlephilhower/
˓→arduino-pico/releases/download/global/package_rp2040_index.json
arduino-cli core update-index
arduino-cli core install rp2040:rp2040
To list the supported boards:
arduino-cli board listall | grep rp2040
3.3 Installing via GIT
To install via GIT (for latest and greatest versions):
mkdir -p ~/Arduino/hardware/pico
git clone https://github.com/earlephilhower/arduino-pico.git ~/Arduino/hardware/pico/
˓→rp2040
cd ~/Arduino/hardware/pico/rp2040
git submodule update --init
cd pico-sdk
git submodule update --init
cd ../tools
python3 ./get.py
10 Chapter 3. Installation
Arduino-Pico Documentation, Release 4.4.3
3.4 Installing both Arduino and CMake
Tom’s Hardware presented a very nice writeup on installing arduino-pico on both Windows and Linux, available at
Tom’s Hardware .
If you follow their step-by-step you will also have a fully functional CMake-based environment to build Pico apps on
if you outgrow the Arduino ecosystem.
3.5 Uploading Sketches
To upload your first sketch, you will need to hold the BOOTSEL button down while plugging in the Pico to your
computer. Then hit the upload button and the sketch should be transferred and start to run.
After the first upload, this should not be necessary as the arduino-pico core has auto-reset support. Select the appro-
priate serial port shown in the Arduino Tools->Port->Serial Port menu once (this setting will stick and does not need to
be touched for multiple uploads). This selection allows the auto-reset tool to identify the proper device to reset. Them
hit the upload button and your sketch should upload and run.
In some cases the Pico will encounter a hard hang and its USB port will not respond to the auto-reset request. Should
this happen, just follow the initial procedure of holding the BOOTSEL button down while plugging in the Pico to enter
the ROM bootloader.
3.6 Uploading the First Sketch
The first time you upload a sketch to a board, you’ll need to use the built-in ROM bootloader to handle the upload and
not a serial port.
1. Hold the BOOTSEL button while plugging in the board.
2. Select Tools->Port->UF2 Board from the menu.
3. Upload as normal.
4. After the board boots up, select the new serial port from the Tools->Port menu.
3.7 Windows 7 Driver Notes
Windows 10, Linux, and Mac will all support the Pico CDC/ACM USB serial port automatically. However, Windows
7 may not include the proper driver and therefore no detect the Pico for automatic uploads or the Serial Monitor.
For Windows 7, if this occurs, you can use Zadig <https://zadig.akeo.ie/> to install the appropriate driver. Select the
USB ID of 2E8A and use the USB Serial (CDC) driver.
3.4. Installing both Arduino and CMake 11
Arduino-Pico Documentation, Release 4.4.3
3.8 Windows 7 Installation Problems
When running MalwareBytes antivirus (or others) the scanner may lock the compiler or other toolchain executables,
causing installation or build failures. (Thanks to @Andy2No)
Symptoms include:
• Access denied during update in the boards manager - affects the .exe files, because MalwareBytes has locked
them.
• Access denied during compilation, to one of the .exe files - same reason.
• Can’t delete the .exe files - they’re locked by MalwareBytes.
A workaround is possible, involving setting the toolchain as an “excluded directory” and reinstalling.
1. In MalwareBytes Settings, click the Exclusions tab. Add an exclusion for the equivalent of this folder path:
C:\Users{YOUR_USERNAME_HERE}\AppData\Local\Arduino15\packages\rp2040\tools\pqt-gcc\1.1.
0-a-81a1771
2. Reboot to unlock the files.
3. Do the boards manager installation / upgrade again.
4. Set the board type, e.g. to Raspberry Pi Pico and check it can compile.
3.9 Uploading Filesystem Images
The onboard flash filesystem for the Pico, LittleFS, lets you upload a filesystem image from the sketch directory for
your sketch to use. Download the needed plugin from
• IDE 1.x: https://github.com/earlephilhower/arduino-pico-littlefs-plugin/releases
• IDE 2.x: https://github.com/earlephilhower/arduino-littlefs-upload/releases
To install, follow the directions in
• IDE 1.x: https://github.com/earlephilhower/arduino-pico-littlefs-plugin/blob/master/README.md
• IDE 2.x: https://github.com/earlephilhower/arduino-littlefs-upload/blob/main/README.md
For detailed usage information, please check the repo documentation available at
12 Chapter 3. Installation
Arduino-Pico Documentation, Release 4.4.3
• https://arduino-pico.readthedocs.io/en/latest/fs.html
3.10 Uploading Sketches with Picotool
Because the Picotool uses a custom device driver in the Pico to handle upload, when using the Upload
Method->Picotool mode custom code needs to be run on the Pico which is not included by default for compati-
bility and code savings.
So for the first sketch you will need to rebuild (with the Upload Method->Picotool selected in them menus) and
then manually hold down BOOTSEL and insert the Pico USB cable to enter the ROM bootloader.
After the initial upload, as long as the running binary was built using the Picotool upload method, then the normal
upload process should work.
For Ubuntu and other Linux operating systems you may need to add the following lines to a new udev config
file(99-picotool.rules) to allow normal users to access the special USB device the Pico exports:
echo 'SUBSYSTEM=="usb", ATTRS{idVendor}=="2e8a", ATTRS{idProduct}=="0003", MODE="660",␣
˓→GROUP="plugdev"' | sudo tee -a /etc/udev/rules.d/98-Picotool.rules
echo 'SUBSYSTEM=="usb", ATTRS{idVendor}=="2e8a", ATTRS{idProduct}=="000a", MODE="660",␣
˓→GROUP="plugdev"' | sudo tee -a /etc/udev/rules.d/98-Picotool.rules
sudo udevadm control --reload
3.11 Uploading Sketches with Picoprobe
If you have built a Raspberry Pi Picoprobe, you can use OpenOCD to handle your sketch uploads and for debugging
with GDB.
Under Windows a local admin user should be able to access the Picoprobe port automatically, but under Linux udev
must be told about the device and to allow normal users access.
To set up user-level access to Picoprobes on Ubuntu (and other OSes which use udev):
echo 'SUBSYSTEMS=="usb", ATTRS{idVendor}=="2e8a", ATTRS{idProduct}=="0004", GROUP="users
˓→", MODE="0666"' | sudo tee -a /etc/udev/rules.d/98-PicoProbe.rules
sudo udevadm control --reload
The first line creates a file with the USB vendor and ID of the Picoprobe and tells UDEV to give users full access to
it. The second causes udev to load this new rule. Note that you will need to unplug and re-plug in your device the first
time you create this file, to allow udev to make the device node properly.
Once Picoprobe permissions are set up properly, then select the board “Raspberry Pi Pico (Picoprobe)” in the Tools
menu and upload as normal.
3.12 Uploading Sketches with OpenOCD
Under Windows and macOS, any user should be able to access OpenOCD automatically, but under Linux udev must
be told about the device and to allow normal users access.
To set up user-level access to all CMSIS-DAP adapters on Ubuntu (and other OSes which use udev):
echo 'ATTRS{product}=="*CMSIS-DAP*", MODE="664", GROUP="plugdev"' | sudo tee -a /etc/
˓→udev/rules.d/98-CMSIS-DAP.rules
sudo udevadm control --reload
3.10. Uploading Sketches with Picotool 13
Arduino-Pico Documentation, Release 4.4.3
The first line creates a file that recognizes all CMSIS-DAP adapters and tells UDEV to give users full access to it. The
second causes udev to load this new rule. Note that you will need to unplug and re-plug in your device the first time
you create this file, to allow udev to make the device node properly.
Once CMSIS-DAP permissions are set up properly, then select the Upload Method “Picoprobe/Debugprobe (CMSIS-
DAP)” in the Tools menu.
3.13 Debugging with Picoprobe/Debugprobe, OpenOCD, and GDB
The installed tools include a version of OpenOCD (in the pqt-openocd directory) and GDB (in the pqt-gcc directory).
These may be used to run GDB in an interactive window as documented in the Pico Getting Started manuals from the
Raspberry Pi Foundation.
14 Chapter 3. Installation
CHAPTER
FOUR
IDE MENUS
4.1 Board
Use the boards menu to select your model of RP2040 board.
There is also a Generic RP2040 board which allows you to individually select things such as flash size or boot2 flash
type. Use this if your board isn’t yet fully supported and isn’t working with the normal Raspberry Pi Pico option.
4.2 Flash Size
Arduino-Pico supports onboard filesystems which will set aside some of the flash on your board for the filesystem,
shrinking the maximum code size allowed. Use this menu to select the desired ratio of filesystem to sketch.
4.3 CPU Speed
While it is unsupported, the Raspberry Pi Pico RP2040 can often run much faster than the stock 125MHz. Use the CPU
Speed menu to select a desired over or underclock speed. If the sketch fails at the higher speed, hold the BOOTSEL
while plugging it in to enter update mode and try a lower overclock.
4.4 Debug Port and Debug Level
Debug messages from printf and the Core can be printed to a Serial port to allow for easier debugging. Select the
desired port and verbosity. Selecting a port for debug output does not stop a sketch from using it for normal operations.
4.5 Generic RP2040 Support
If your RP2040 board isn’t in the menus you can still use it with the IDE by using the Board->Generic RP2040 menu
option. You will need to then set the flash size (see above) and tell the IDE how to communicate with the flash chip
using the Tools->Boot Stage 2 menu.
4.6 Boot Stage 2 Options for Generic RP2040
The Arduino Pico needs to set up its internal flash interface to talk to whatever flash chip is in the system. While all
flash chips support a basic (and slow) 1-bit operation using common timings, each different brand (and sometimes
model) of flash chip require custom timings to work in QSPI (4-bit) mode. The Boot Stage 2 menu lets you select from
the supported timings.
The options with /2 in them divide the system clock by 2 to drive the bus. Options with /4 divide the clock by 4 and so
are slower but more compatible.
15
Arduino-Pico Documentation, Release 4.4.3
If you can’t match a chip name in the menu to your flash chip, a simple test can be run to determine which is correct.
Simpily load the Blink example, select the first option in the Boot Stage 2 menu, and upload. If that works, note it and
continue. Iterate through the options and note which ones work. If an option doesn’t work, unplug the chip and hold
the BOOTSEL button down while re-inserting it to enter the ROM uploader mode. (The CPU and flash will not be
harmed if the test fails.)
If one of the custom bootloaders (not Generic SPI /2 or /4) worked, use that option to get best performance. If none
worked other than the Generic SPI /2 or /4 then use that. The /2 options of all models is preferred as it is faster, but
some boards do require /4 on the custom chip interfaces.
When in doubt, Generic SPI /4 should work with any flash chip but is slow.
16 Chapter 4. IDE Menus
CHAPTER
FIVE
USING THIS CORE WITH PLATFORMIO
5.1 What is PlatformIO?
PlatformIO is a free, open-source build-tool written in Python, which also integrates into VSCode code as an extension.
PlatformIO significantly simplifies writing embedded software by offering a unified build system, yet being able to
create project files for many different IDEs, including VSCode, Eclipse, CLion, etc. Through this, PlatformIO can
offer extensive features such as IntelliSense (autocomplete), debugging, unit testing etc., which not available in the
standard Arduino IDE.
The Arduino IDE experience:
17
Arduino-Pico Documentation, Release 4.4.3
The PlatformIO experience:
18 Chapter 5. Using this core with PlatformIO
Arduino-Pico Documentation, Release 4.4.3
Refer to the general documentation at https://docs.platformio.org/.
Especially useful is the Getting started with VSCode + PlatformIO, CLI reference and the platformio.ini options page.
Hereafter it is assumed that you have a basic understanding of PlatformIO in regards to project creation, project file
structure and building and uploading PlatformIO projects, through reading the above pages.
5.2 Important steps for Windows users, before installing
By default, Windows has a limited path length that is not long enough to fully clone the Pico-SDK’s tinyusb repository,
resulting in error messages like the one below while attempting to fetch the repository.
error: unable to create file '.....' : Filename too long
To work around this requires performing two steps and rebooting Windows once. These steps will enable longer file
paths at the Windows OS and the git level.
5.2.1 Step 1: Enabling long paths in git
Open up a Windows cmd or terminal window and execute the following command
git config --system core.longpaths true
5.2. Important steps for Windows users, before installing 19
Arduino-Pico Documentation, Release 4.4.3
5.2.2 Step 2: Enabling long paths in the Windows OS
(taken from https://www.microfocus.com/documentation/filr/filr-4/filr-desktop/t47bx2ogpfz7.html)
1. Click Window key and type gpedit.msc, then press the Enter key. This launches the Local Group Policy Editor.
2. Navigate to Local Computer Policy > Computer Configuration > Administrative Templates > System > Filesys-
tem.
3. Double click Enable NTFS/Win32 long paths and close the dialog.
5.2.3 Step 3: Reboot the computer
Once the two prior stages are complete, please do a full reboot or power cycle so that the new settings will take effect.
5.3 Current state of development
At the time of writing, PlatformIO integration for this core is a work-in-progress and not yet merged into mainline
PlatformIO. This is subject to change once this pull request is merged.
If you want to use the PlatformIO integration right now, make sure you first create a standard Raspberry Pi Pico +
Arduino project within PlatformIO. This will give you a project with the platformio.ini
[env:pico]
platform = raspberrypi
board = pico
framework = arduino
Here, you need to change the platform to take advantage of the features described hereunder and switch to the new core.
[env:pico]
platform = https://github.com/maxgerhardt/platform-raspberrypi.git
board = pico
framework = arduino
board_build.core = earlephilhower
When the support for this core has been merged into mainline PlatformIO, this notice will be removed and a standard
platformio.ini as shown above will work as a base.
5.4 Deprecation warnings
Previous versions of this documentation told users to inject the framework and toolchain package into the project by
using
; note that download link for toolchain is specific for OS. see https://github.com/
˓→earlephilhower/pico-quick-toolchain/releases.
(continues on next page)
20 Chapter 5. Using this core with PlatformIO
Arduino-Pico Documentation, Release 4.4.3
(continued from previous page)
platform_packages =
maxgerhardt/framework-arduinopico@https://github.com/earlephilhower/arduino-pico.git
maxgerhardt/toolchain-pico@https://github.com/earlephilhower/pico-quick-toolchain/
˓→releases/download/1.3.1-a/x86_64-w64-mingw32.arm-none-eabi-7855b0c.210706.zip
This is now deprecated and should not be done anymore. Users should delete these platform_packages lines and
update the platform integration by issuing the command
pio pkg update -g -p https://github.com/maxgerhardt/platform-raspberrypi.git
in the PlatformIO CLI. The same can be achieved by using the VSCode PIO Home -> Platforms -> Updates GUI.
The toolchain, which was also renamed to toolchain-rp2040-earlephilhower is downloaded automatically from
the registry. The same goes for the framework-arduinopico toolchain package, which points directly to the Arduino-
Pico Github repository. However, users can still select a custom fork or branch of the core if desired so, as detailed in
a chapter below.
5.5 Selecting the new core
Prerequisite for using this core is to tell PlatformIO to switch to it. There will be board definition files where the Earle-
Philhower core will be the default since it’s a board that only exists in this core (and not the other https://github.com/
arduino/ArduinoCore-mbed). To switch boards for which this is not the default core (which are only board = pico
and board = nanorp2040connect), the directive
board_build.core = earlephilhower
must be added to the platformio.ini. This controls the core switching logic.
When using Arduino-Pico-only boards like board = rpipico or board = adafruit_feather, this is not needed.
5.6 Flash size
Controlled via specifying the size allocated for the filesystem. Available sketch size is calculated accordingly
by using (as in makeboards.py) that number and the (constant) EEPROM size (4096 bytes) and the total flash
size as known to PlatformIO via the board definition file. The expression on the right can involve “b”,”k”,”m”
(bytes/kilobytes/megabytes) and floating point numbers. This makes it actually more flexible than in the Arduino
IDE where there is a finite list of choices. Calculations happen in the platform.
; in reference to a board = pico config (2MB flash)
; Flash Size: 2MB (Sketch: 1MB, FS:1MB)
board_build.filesystem_size = 1m
; Flash Size: 2MB (No FS)
board_build.filesystem_size = 0m
; Flash Size: 2MB (Sketch: 0.5MB, FS:1.5MB)
board_build.filesystem_size = 1.5m
5.7 PSRAM size
For RP2350 based boards, this controls how much PSRAM the firmware will think it has available in bytes, mapped at
starting address 0x11000000.
To learn more about PSRAM usage, see: RP2350 PSRAM Support
5.5. Selecting the new core 21
Arduino-Pico Documentation, Release 4.4.3
; PSRAM size: 1MB
board_upload.psram_length = 1048576
; PSRAM size: 2MB
board_upload.psram_length = 2097152
; PSRAM size: 4MB
board_upload.psram_length = 4194304
5.8 PSRAM chip select (CS)
For RP2350 based boards, this controls what chip-select (also called: slave-select / SS) pin to use when wanting to talk
to the PSRAM chip.
Note that it’s not needed to set this with a board that is known to have a PSRAM chip on-board, such as a “Sparkfun
Thing Plus 2350”. The pins_arduino.h of that variant already has the correct definition.
To learn more about PSRAM usage, see: RP2350 PSRAM Support
; PSRAM CS is at GP47
build_flags =
-DRP2350_PSRAM_CS=47
5.9 Boot2 Source
Boot2 is the second stage bootloader and predominantly used on the RP2040. Its main purpose is to configure the
communication with the Flash at the highest, safest speed it can. All known boards have their correct value already
configured. However, when choosing board = generic, you can freely configure the Boot2 to be for a different flash.
For possible Boot2 filenames, please see here.
; expect an ISSI IS25LP080 flash, SPI frequency = CPU frequency divided by 2
board_build.arduino.earlephilhower.boot2_source = boot2_is25lp080_2_padded_checksum.S
5.10 CPU Speed
As for all other PlatformIO platforms, the f_cpu macro value (which is passed to the core) can be changed as docu-
mented
; 133MHz
board_build.f_cpu = 133000000L
5.11 Debug Port
Via build_flags as done for many other cores (example).
; Debug Port: Serial
build_flags = -DDEBUG_RP2040_PORT=Serial
; Debug Port: Serial 1
build_flags = -DDEBUG_RP2040_PORT=Serial1
; Debug Port: Serial 2
build_flags = -DDEBUG_RP2040_PORT=Serial2
22 Chapter 5. Using this core with PlatformIO
Arduino-Pico Documentation, Release 4.4.3
5.12 Debug Level
Done again by directly adding the needed build flags. When wanting to define multiple build flags, they must be
accumulated in either a single line or a newline-separated expression.
; Debug level: Core
build_flags = -DDEBUG_RP2040_CORE
; Debug level: SPI
build_flags = -DDEBUG_RP2040_SPI
; Debug level: Wire
build_flags = -DDEBUG_RP2040_WIRE
; Debug level: All
build_flags = -DDEBUG_RP2040_WIRE -DDEBUG_RP2040_SPI -DDEBUG_RP2040_CORE
; Debug level: NDEBUG
build_flags = -DNDEBUG
; example: Debug port on serial 2 and all debug output
build_flags = -DDEBUG_RP2040_WIRE -DDEBUG_RP2040_SPI -DDEBUG_RP2040_CORE -DDEBUG_RP2040_
˓→PORT=Serial2
; equivalent to above
build_flags =
-DDEBUG_RP2040_WIRE
-DDEBUG_RP2040_SPI
-DDEBUG_RP2040_CORE
-DDEBUG_RP2040_PORT=Serial2
5.13 C++ Exceptions
Exceptions are disabled by default. To enable them, use
; Enable Exceptions
build_flags = -DPIO_FRAMEWORK_ARDUINO_ENABLE_EXCEPTIONS
5.14 Stack Protector
To enable GCC’s stack protection feature, use
; Enable Stack Protector
build_flags = -fstack-protector
5.15 RTTI
RTTI (run-time type information) is disabled by default. To enable it, use
; Enable RTTI
build_flags = -DPIO_FRAMEWORK_ARDUINO_ENABLE_RTTI
5.12. Debug Level 23
Arduino-Pico Documentation, Release 4.4.3
5.16 USB Stack
Not specifying any special build flags regarding this gives one the default Pico SDK USB stack. To change it, add
; Adafruit TinyUSB
build_flags = -DUSE_TINYUSB
; No USB stack
build_flags = -DPIO_FRAMEWORK_ARDUINO_NO_USB
Note that the special “No USB” setting is also supported, through the shortcut-define
PIO_FRAMEWORK_ARDUINO_NO_USB.
5.17 USB Customization
If you want to change the USB VID, PID, product or manufacturer name that the device will appear under, configure
them as follows:
board_build.arduino.earlephilhower.usb_manufacturer = Custom Manufacturer
board_build.arduino.earlephilhower.usb_product = Ultra Cool Product
board_build.arduino.earlephilhower.usb_vid = 0xABCD
board_build.arduino.earlephilhower.usb_pid = 0x1337
5.18 IP Stack
The lwIP stack can be configured to support only IPv4 (default) or additionally IPv6. To activate IPv6 support, add
; IPv6
build_flags = -DPIO_FRAMEWORK_ARDUINO_ENABLE_IPV6
to the platformio.ini.
5.19 Bluetooth Stack
The Bluetooth Classic (BTC) and Bluetooth Low Energy (BLE) stack can be activated by adding
; BTC and BLE
build_flags = -DPIO_FRAMEWORK_ARDUINO_ENABLE_BLUETOOTH
to the platformio.ini.
5.20 Selecting a different core version
If you wish to use a different version of the core, e.g., the latest git master version, you can use a platform_packages
directive to do so. Simply specify that the framework package (framework-arduinopico) comes from a different
source.
platform_packages =
framework-arduinopico@https://github.com/earlephilhower/arduino-pico.git#master
Whereas the #master can also be replaced by a #branchname or a #commithash. If left out, it will pull the default
branch, which is master.
24 Chapter 5. Using this core with PlatformIO
Arduino-Pico Documentation, Release 4.4.3
The file:// and symlink:// pseudo-protocols can also be used instead of https:// to point to a local copy of
the core (with e.g. some modifications) on disk (see documentation).
Note that this can only be done for versions that have the PlatformIO builder script it in, so versions before 1.9.2 are
not supported.
5.21 Selecting the CPU architecture
By default Platform.IO will build for the onboard ARM cores on the RP2350. To build RISC-V binaries adjust the
board_build.mcu option accordingly:
; RP2350 based (RISC-V)
[env:rpipico2-riscv]
board = rpipico2
board_build.mcu = rp2350-riscv
5.22 Examples
The following example platformio.ini can be used for a Raspberry Pi Pico and 0.5MByte filesystem.
[env:pico]
platform = https://github.com/maxgerhardt/platform-raspberrypi.git
board = pico
framework = arduino
; board can use both Arduino cores -- we select Arduino-Pico here
board_build.core = earlephilhower
board_build.filesystem_size = 0.5m
The initial project structure should be generated just creating a new project for the Pico and the Arduino framework,
after which the auto-generated platformio.ini can be adapted per above.
5.23 Debugging
With recent updates to the toolchain and OpenOCD, debugging firmwares is also possible.
To specify the debugging adapter, use debug_tool (documentation). Supported values are:
• picoprobe
• cmsis-dap
• jlink
• raspberrypi-swd
• blackmagic
These values can also be used in upload_protocol if you want PlatformIO to upload the regular firmware through
this method, which you likely want.
Especially the PicoProbe method is convenient when you have two Raspberry Pi Pico boards. One of them can be
flashed with the PicoProbe firmware (documentation) and is then connected to the target Raspberry Pi Pico board
(see documentation chapter “Picoprobe Wiring”). Remember that on Windows, you have to use Zadig to also load
“WinUSB” drivers for the “Picoprobe (Interface 2)” device so that OpenOCD can speak to it.
5.21. Selecting the CPU architecture 25
Arduino-Pico Documentation, Release 4.4.3
ò Note
Newer PicoProbe firmware versions have dropped the proprietary “PicoProbe” USB communication protocol and
emulate a CMSIS-DAP instead. Meaning, you have to use debug_tool = cmsis-dap for these newer firmwares,
such as those obtained from raspberrypi/picoprobe
With that set up, debugging can be started via the left debugging sidebar and works nicely: Setup breakpoints, inspect
the value of variables in the code, step through the code line by line. When a breakpoint is hit or execution is halted,
you can even see the execution state both Cortex-M0+ cores of the RP2040.
For further information on customizing debug options, like the initial breakpoint or debugging / SWD speed, consult
the documentation.
ò Note
For the BlackMagicProbe debugging probe (as can be e.g., created by simply flashing a STM32F103C8 “Bluepill”
board), you currently have to use the branch fix/rp2040-flash-reliability (or at least commit 1d001bc)
and use the official ARM provided toolchain.
You can obtain precompiled binaries from here. A flashing guide is available here. You then have to configure the
target serial port (“GDB port”) in your project per documentation.
26 Chapter 5. Using this core with PlatformIO
Arduino-Pico Documentation, Release 4.4.3
5.24 Filesystem Uploading
For the Arduino IDE, a plugin is available that enables a data folder to be packed as a LittleFS filesystem binary and
uploaded to the Pico.
This functionality is also built-in in the PlatformIO integration. Open the project tasks and expand the “Platform” tasks:
The files you want to upload should be placed in a folder called data inside the project. This can be customized if
needed.
The task “Build Filesystem Image” will take all files in the data directory and create a littlefs.bin file from it using
the mklittlefs tool.
The task “Upload Filesystem Image” will upload the filesystem image to the Pico via the specified upload_protocol.
ò Note
Set the space available for the filesystem in the platformio.ini using e.g., board_build.filesystem_size
= 0.5m, or filesystem creation will fail!
5.24. Filesystem Uploading 27
Arduino-Pico Documentation, Release 4.4.3
28 Chapter 5. Using this core with PlatformIO
CHAPTER
SIX
PIN ASSIGNMENTS
The Raspberry Pi Pico has an incredibly flexible I/O configuration and most built-in peripherals (except for the ADC)
can be used on multiple sets of pins. Note, however, that not all peripherals can use all I/Os. Refer to the RP2040
datasheet or an online pinout diagram for more details.
Additional methods have been added to allow you to select a peripheral’s I/O pins before calling ::begin. This is
especially helpful when using third party libraries: the library doesn’t need to be modified, only your own code in
setup() is needed to adjust pinouts.
6.1 I2S
::setBCLK(pin)
::setDOUT(pin)
6.2 Serial1 (UART0), Serial2 (UART1)
::setRX(pin)
::setTX(pin)
::setRTS(pin)
::setCTS(pin)
6.3 SPI (SPI0), SPI1 (SPI1)
::setSCK(pin)
::setCS(pin)
::setRX(pin)
::setTX(pin)
6.4 Wire (I2C0), Wire1 (I2C1)
::setSDA(pin)
::setSCL(pin)
For example, because the SD library uses the SPI library, we can make it use a non-default pinout with a simple call
void setup() {
SPI.setRX(4);
(continues on next page)
29
Arduino-Pico Documentation, Release 4.4.3
(continued from previous page)
SPI.setTX(7);
SPI.setSCK(6);
SPI.setCS(5);
SD.begin(5);
}
30 Chapter 6. Pin Assignments
CHAPTER
SEVEN
RP2040 HELPER CLASS
Some of the core functionality of the RP2040 chip powering the Raspberry Pi Pico is exposed in the RP2040 class
variable rp2040.
7.1 Core Internals
7.1.1 int rp2040.f_cpu()
Returns the current frequency of the core clock. This is read at runtime, versus the constant F_CPU macro that is also
available. This is useful in cases where your code changes the core clock (i.e. low power modes, etc.)
7.1.2 int rp2040.cpuid()
Returns the current core ID (0 or 1) of the executing task.
7.1.3 uint32_t rp2040.getCycleCount()
Returns a 32-bit cycle count from then the core started running. Because it is only 32-bits, and the Pico runs at 133MHz,
this value can loop around in a matter of seconds.
7.1.4 uint64_t rp2040.getCycleCount64()
Returns a 64-bit cycle count from then the core started running. This value should never loop around in normal mode
(at 133MHz it would take over 4,000 years to overflow).
7.1.5 uint32_t rp2040.hwrand32()
Returns a 32-bit value derived from the CPU cycle counter and the ROSC oscillator. Because the ROSC bit is not a
true random number generator, the values returned may not meet the most stringent random tests. If your application
needs absolute bulletproof random numbers, consider using dedicated external hardware.
7.1.6 void rp2040.reboot()
Forces a hardware reboot of the Pico.
7.2 Hardware Watchdog
7.2.1 void rp2040.wdt_begin(uint32_t delay_ms)
Enables the hardware watchdog timer with a delay value of delay_ms milliseconds. Note that on the RP2040, once this
function has called, the hardware watchdog can _not_ be disabled.
31
Arduino-Pico Documentation, Release 4.4.3
The maximum delay_ms allowed in this call is 8300, corresponding to 8.3 seconds. Any higher values will be trun-
cated by the hardware.
7.2.2 void rp2040.wdt_reset()
Reloads the watchdog’s counter with the amount of time set by wdt_begin.
7.2.3 RP2040::resetReason_t rp2040.getResetReason()
Returns the reason for the last reset, defined in enum RP2040::resetReason_t. See example `ResetReason` for
some details.
7.3 Memory Information
7.3.1 int rp2040.getFreeHeap()
Returns the number of bytes free for heap allocation (i.e. malloc, new). Note that because there is some overhead, and
there may be heap fragmentation, this number is an upper bound and you generally will only be able to allocate less
than this returned number.
7.3.2 int rp2040.getUsedHeap()
Returns the number of bytes allocated out of the heap.
7.3.3 int rp2040.getTotalHeap()
Returns the total heap that was available to this program at compile time (i.e. the Pico RAM size minus things like the
.data and .bss sections and other overhead).
7.4 Hardware Identification
7.4.1 bool rp2040.isPicoW()
Returns the core’s best guess if this code is running on a Raspberry Pi Pico W. This would let you, possibly, use the
same UF2 for Pico and PicoW by simply not doing any WiFi calls.
7.5 Bootloader
7.5.1 void rp2040.enableDoubleResetBootloader() (Pico/RP2040 only)
Add a call anywhere in the sketch to rp2040.enableDoubleResetBootloader() and the core will check for a
double-tap on reset, and if found will start the USB bootloader.
7.5.2 void rp2040.rebootToBootloader()
Will reboot the RP2040 into USB UF2 upload mode.
7.6 DMA-based MEMCPY
The onboard DMA engines can copy 4-byte aligned quantities faster than the CPU.
32 Chapter 7. RP2040 Helper Class
Arduino-Pico Documentation, Release 4.4.3
7.6.1 void *rp2040.memcpyDMA(void *dest, const void *src, size_t count);
Uses a DMA engine to transfer data from src to dest in 4-byte chunks without CPU intervention. If any arguments are
not 4-byte aligned, or if the count is not a multiple of 4, then it will fall back to CPU-managed memcpy.
7.6. DMA-based MEMCPY 33
Arduino-Pico Documentation, Release 4.4.3
34 Chapter 7. RP2040 Helper Class
CHAPTER
EIGHT
ANALOG I/O
8.1 Analog Input
For analog inputs, the RP2040 device has a 12-bit, 4-channel ADC + temperature sensor available on a fixed set of pins
(A0. . . A3). The standard Arduino calls can be used to read their values (with 3.3V nominally reading as 4095).
8.1.1 int analogRead(pin_size_t pin = A0..A3)
Returns a value from 0. . . 4095 correspionding to the ADC reading of the specific pin.
8.1.2 void analogReadResolution(int bits)
Determines the resolution (in bits) of the value returned by the analogRead() function. Default resolution is 10bit.
8.1.3 float analogReadTemp(float vref = 3.3f)
Returns the temperature, in Celsius, of the onboard thermal sensor. If you have a custom Vref for the ADC on your
RP2040 board, you can pass it in as a parameter. Calling with no parameters assumes the normal, 3.3V Vref. This
reading is not exceedingly accurate and of relatively low resolution, so it is not a replacement for an external temperature
sensor in many cases.
8.2 Analog Outputs
The RP2040 does not have any onboard DACs, so analog outputs are simulated using the standard method of using
pulse width modulation (PWM) using the RP2040’s hardware PWM units.
While up to 16 PWM channels can be generated, they are not independent and there are significant restrictions as to
allowed pins in parallel. See the RP2040 datasheet for full details.
8.3 Analog Output Restrictions
The PWM generator source clock restricts the legal combinations of frequency and ranges. At a CPU frequency
of 133MHz, the 16 bit maximum range decreases by 1 bit for every doubling of the default PWM frequency of 1
kHz. For example, at 1MHz only about 6 bits of range are possible. When you define an analogWriteFreq and
analogWriteRange that can’t be fulfilled by the hardware, the frequency will be preserved but the accuracy (range)
will be reduced automatically. Your code will still send in the range you specify, but the core itself will transparently
map it into the allowable PWN range.
35
Arduino-Pico Documentation, Release 4.4.3
8.3.1 void analogWriteFreq(uint32_t freq)
Sets the master PWM frequency used (i.e. how often the PWM output cycles). From 100Hz to 1MHz are supported.
8.3.2 void analogWriteRange(uint32_t range) and analogWriteResolution(int res)
These calls set the maximum PWM value (i.e. writing this value will result in a PWM duty cycle of 100%)/ either
explicitly (range) or as a power-of-two (res). A range of 16 to 65535 is supported.
8.3.3 void analogWrite(pin_size_t pin, int val)
Writes a PWM value to a specific pin. The PWM machine is enabled and set to the requested frequency and scale, and
the output is generated. This will continue until a digitalWrite or other digital output is performed.
36 Chapter 8. Analog I/O
CHAPTER
NINE
DIGITAL I/O
9.1 Board-Specific Pins
The Raspberry Pi Pico RP2040 chip supports up to 30 digital I/O pins, however not all boards provide access to all
pins.
9.2 Pin Notation
When using Analog or Digital I/Os, if you supply an integer it specifies the RP2040 GPIO pin to use. Using Dx or Ax
notation (for example, D4 or A3) may be necessary on boards without a direct PCB pin to GPIO mapping.
9.3 Input Modes
The Raspberry Pi Pico has 3 Input modes settings for use with pinMode: INPUT, INPUT_PULLUP and IN-
PUT_PULLDOWN
9.4 Output Modes (Pad Strength)
The Raspberry Pi Pico has the ability to set the current that a pin (actually the pad associated with it) is capable of
supplying. The current can be set to values of 2mA, 4mA, 8mA and 12mA. By default, on a reset, the setting is
4mA. A pinMode(x, OUTPUT), where x is the pin number, is also the default setting. 4 settings have been added for
use with pinMode: OUTPUT_2MA, OUTPUT_4MA, which has the same behavior as OUTPUT, OUTPUT_8MA and
OUTPUT_12MA.
9.5 Tone/noTone
Simple square wave tone generation is possible for up to 8 channels using Arduino standard tone calls. Because these
use the PIO to generate the waveform, they must share resources with other calls such as I2S or Servo objects.
37
Arduino-Pico Documentation, Release 4.4.3
38 Chapter 9. Digital I/O
CHAPTER
TEN
BOOTSEL BUTTON
The BOOTSEL button on the Pico is not connected to a standard GPIO, so it cannot be read using the usual
digitalRead function. It can, however, be read using a special (relatively slow) method.
The BOOTSEL object implements a simple way of reading the BOOTSEL button. Simply use the object BOOTSEL as a
boolean (as a conditional in an if or while, or assigning to a bool):
// Print "BEEP" if the BOOTSEL button is pressed
if (BOOTSEL) {
Serial.println("BEEP!");
// Wait until BOOTSEL is released
while (BOOTSEL) {
delay(1);
}
}
39
Arduino-Pico Documentation, Release 4.4.3
40 Chapter 10. BOOTSEL Button
CHAPTER
ELEVEN
EEPROM LIBRARY
While the Raspberry Pi Pico RP2040 does not come with an EEPROM onboard, we simulate one by using a single 4K
chunk of flash at the end of flash space.
Note that this is a simulated EEPROM and will only support the number of writes as the onboard flash chip,
not the 100,000 or so of a real EEPROM. Therefore, do not frequently update the EEPROM or you may prematurely
wear out the flash.
11.1 EEPROM Class API
11.1.1 EEPROM.begin(size=256. . . 4096)
Call before the first use of the EEPROM data for read or write. It makes a copy of the emulated EEPROM sector in
RAM to allow random update and access.
11.1.2 EEPROM.read(addr), EEPROM[addr]
Returns the data at a specific offset in the EEPROM. See EEPROM.get later for a more
11.1.3 EEPROM.write(addr, data), EEPROM[addr] = data
Writes a byte of data at the offset specified. Not persisted to flash until EEPROM.commit() is called.
11.1.4 EEPROM.commit()
Writes the updated data to flash, so next reboot it will be readable.
11.1.5 EEPROM.end()
EEPROM.commit() and frees all memory used. Need to call EEPROM.begin() before the EEPROM can be used again.
11.1.6 EEPROM.get(addr, val)
Copies the (potentially multi-byte) data in EEPROM at the specific byte offset into the returned value. Useful for
reading structures from EEPROM.
11.1.7 EEPROM.put(addr, val)
Copies the (potentially multi-byte) value into EEPROM a the byte offset supplied. Useful for storing struct in EEP-
ROM. Note that any pointers inside a written structure will not be valid, and that most C++ objects like String cannot
be written to EEPROM this way because of it.
41
Arduino-Pico Documentation, Release 4.4.3
11.1.8 EEPROM.length()
Returns the length of the EEPROM (i.e. the value specified in EEPROM.begin() ).
11.2 EEPROM Examples
Three EEPROM examples are included.
42 Chapter 11. EEPROM Library
CHAPTER
TWELVE
I2S (DIGITAL AUDIO) AUDIO LIBRARY
While the RP2040 chip on the Raspberry Pi Pico does not include a hardware I2S device, it is possible to use the PIO
(Programmable I/O) state machines to implement one dynamically.
Digital audio input and output are supported at 8, 16, 24, and 32 bits per sample.
Theoretically up to 6 I2S ports may be created, but in practice there may not be enough resources (DMA, PIO SM) to
actually create and use so many.
Create an I2S port by instantiating a variable of the I2S class specifying the direction. Configure it using API calls
below before using it.
12.1 I2S Class API
12.1.1 I2S(OUTPUT)
Creates an I2S output port. Needs to be connected up to the desired pins (see below) and started before any output can
happen.
12.1.2 I2S(INPUT)
Creates an I2S input port. Needs to be connected up to the desired pins (see below) and started before any input can
happen.
12.1.3 I2S(INPUT_PULLUP)
Creates a bi-directional I2S input and output port. Needs to be connected up to the desired pins (see below) and started
before any input or output can happen.
12.1.4 bool setBCLK(pin_size_t pin)
Sets the BCLK pin of the I2S device. The LRCLK/word clock will be pin + 1 due to limitations of the PIO state
machines. Call this before I2S::begin()
12.1.5 bool setDATA(pin_size_t pin)
Sets the DOUT or DIN pin of the I2S device. Any pin may be used. In bi-directional operation, must use
I2S::setDOUT() and I2S::setDIN instead. Call before I2S::begin()
43
Arduino-Pico Documentation, Release 4.4.3
12.1.6 bool setDOUT(pin_size_t pin)
Sets the DOUT pin of the I2S device. Any pin may be used. Call before I2S::begin()
12.1.7 bool setDIN(pin_size_t pin)
Sets the DIN pin of the I2S device. Any pin may be used. Call before I2S::begin()
12.1.8 bool setMCLK(pin_size_t pin)
Sets the MCLK pin of the I2S device and enables MCLK output. Any pin may be used. Call before I2S::begin()
12.1.9 bool setMCLKmult(int mult)
Sets the sample rate to MCLK multiplier value. Only multiples of 64 are valid. Call before I2S::begin()
12.1.10 bool setBitsPerSample(int bits)
Specify how many bits per audio sample to read or write. Note that for 24-bit samples, audio samples must be left-
aligned (i.e. bits 31. . . 8). Call before I2S::begin()
12.1.11 bool setBuffers(size_t buffers, size_t bufferWords, int32_t silenceSample =
0)
Set the number of DMA buffers and their size in 32-bit words as well as the word to fill when no data is available to
send to the I2S hardware. Call before I2S::begin().
12.1.12 bool setFrequency(long sampleRate)
Sets the word clock frequency, but does not start the I2S device if not already running. May be called after
I2S::begin() to change the sample rate on-the-fly.
12.1.13 bool setSysClk(int samplerate)
Changes the PICO system clock to optimise for the desired samplerate. The clock changes to 153.6 MHz for samplerates
that are a multiple of 8 kHz, and 135.6 MHz for multiples of 11.025 kHz. Note that using setSysClk() may affect the
timing of other sysclk-dependent functions. Should be called before any I2S functions and any other sysclk dependent
initialisations.
12.1.14 bool setLSBJFormat()
Enables LSB-J format for I2S output. In this mode the MSB comes out at the same time as the LRCLK changes, and
not the normal 1-cycle delay. Useful for DAC chips like the PT8211.
12.1.15 bool setTDMFormat()
Enabled TDM formatted multi-channel output. Be sure to set the number of channels to the expected value (8 normally)
and the bits per sample to 32.
12.1.16 bool setTDMChannels(int channels)
Sets the number of TDM channels between frame syncs. Generally should be set to 8.
44 Chapter 12. I2S (Digital Audio) Audio Library
Arduino-Pico Documentation, Release 4.4.3
12.1.17 bool swapClocks()
Certain boards are hardwired with the WCLK before the BCLK, instead of the normal way around. This call swaps the
WCLK and BCLK pins. Note that you still call setBCLK(x) with x being the lowest pin ID (i.e. in swapClocks mode
the setBCLK call actually sets LRCLK).
12.1.18 bool begin()/begin(long sampleRate)
Start the I2S device up with the given sample rate, or with the value set using the prior setFrequency call.
12.1.19 void end()
Stops the I2S device.
12.1.20 void flush()
Waits until all the I2S buffers have been output.
12.1.21 void getOverUnderflow()
Returns a flag indicating if the I2S system ran our of data to send on output, or had to throw away data on input.
12.1.22 void getOverflow()
Returns a flag indicating if the I2S system had to throw away data on input.
12.1.23 void getUnderflow()
Returns a flag indicating if the I2S system ran our of data to send on output.
12.1.24 size_t write(uint8_t/int8_t/int16_t/int32_t)
Writes a single sample of bitsPerSample to the buffer. It is up to the user to keep track of left/right channels. Note
this writes data equivalent to one channel’s data, not the size of the passed in variable (i.e. if you have a 16-bit sample
size and write((int8_t)-5); write((int8_t)5); you will have written 2 samples to the I2S buffer of whatever
the I2S size, not a single 16-bit sample.
This call will block (wait) until space is available to actually write the data.
12.1.25 size_t write(int32_t val, bool sync)
Writes 32 bits of data to the I2S buffer (regardless of the configured I2S bit size). When sync is true, it will not return
until the data has been writte. When sync is false, it will return 0 immediately if there is no space present in the I2S
buffer.
12.1.26 size_t write(const uint8_t *buffer, size_t size)
Transfers number of bytes from an application buffer to the I2S output buffer. Be aware that size is in bytes* and not
samples. Size must be a multiple of 4 bytes. Will not block, so check the return value to find out how many 32-bit
words were actually written.
12.1.27 int availableForWrite()
Returns the amount of bytes that can be written without potentially blocking.
12.1. I2S Class API 45
Arduino-Pico Documentation, Release 4.4.3
12.1.28 int read()
Reads a single sample of I2S data, whatever the I2S sample size is configured. Will not return until data is available.
12.1.29 int peek()
Returns the next sample to be read from the I2S buffer (without actually removing it).
12.1.30 size_t read(uint8_t *buffer, size_t size)
Transfers number of bytes from the I2S input buffer to an application buffer. Be aware that size is in bytes* and not
samples. Size must be a multiple of 4 bytes. Will not block, so check the return value to find out how many 32-bit
words were actually read.
12.1.31 void onTransmit(void (*fn)(void))
Sets a callback to be called when an I2S DMA buffer is fully transmitted. Will be in an interrupt context so the specified
function must operate quickly and not use blocking calls like delay() or write to the I2S.
12.1.32 void onReceive(void (*fn)(void))
Sets a callback to be called when an I2S DMA buffer is fully read in. Will be in an interrupt context so the specified
function must operate quickly and not use blocking calls like delay() or read from the I2S.
12.2 Sample Writing/Reading API
Because I2S streams consist of a natural left and right sample, it is often convenient to write or read both with a single
call. The following calls allow applications to read or write both samples at the same time, and explicitly indicate the
bit widths required (to avoid potential issues with type conversion on calls).
12.2.1 size_t write8(int8_t l, int8_t r)
Writes a left and right 8-bit sample to the I2S buffers. Blocks until space is available.
12.2.2 size_t write16(int16_t l, int16_t r)
Writes a left and right 16-bit sample to the I2S buffers. Blocks until space is available.
12.2.3 size_t write24(int32_t l, int32_t r)
Writes a left and right 24-bit sample to the I2S buffers. See note below about 24-bit mode. Blocks until space is
available.
12.2.4 size_t write32(int32_t l, int32_t r)
Writes a left and right 32-bit sample to the I2S buffers. Blocks until space is available.
12.2.5 bool read8(int8_t *l, int8_t *r)
Reads a left and right 8-bit sample and returns true on success. Will block until data is available.
46 Chapter 12. I2S (Digital Audio) Audio Library
Arduino-Pico Documentation, Release 4.4.3
12.2.6 bool read16(int16_t *l, int16_t *r)
Reads a left and right 16-bit sample and returns true on success. Will block until data is available.
12.2.7 bool read24(int32_t *l, int32_t *r)
Reads a left and right 24-bit sample and returns true on success. See note below about 24-bit mode. Will block until
data is available.
12.2.8 bool read32(int32_t *l, int32_t *r)
Reads a left and right 32-bit sample and returns true on success. Will block until data is available.
12.3 Note About 24-bit Samples
24-bit samples are stored as left-aligned 32-bit values with bits 7..0 ignored. Only the upper 24 bits 31. . . 8 will be
transmitted or received. The actual I2S protocol will only transmit or receive 24 bits in this mode, even though the data
is 32-bit packed.
12.3. Note About 24-bit Samples 47
Arduino-Pico Documentation, Release 4.4.3
48 Chapter 12. I2S (Digital Audio) Audio Library
CHAPTER
THIRTEEN
PWM AUDIO LIBRARY
Relatively good quality analog audio out can be generated by using the RP2040 onboard PWM hardware. It can drive
an amplifier for speaker output, or be decoupled using a capacitor to drive a headphone-level signal. Mono and stereo
signals can be generated.
All samples are sent to the PWMAudio library as signed 16 bits per sample. Due to frequency limitations of the PWM
hardware, at higher bit rates these 16-bits will automatically be reduced to the maximum the hardware can handle.
Multiple PWMAudio devices are supported, depending on availability of DMA channels.
The interface for the PWMAudio device is very similar to the I2S device, and most code can be ported simply by
instantiating a PWMAudio object in lieu of an I2S object.
13.1 PWM Class API
13.1.1 PWMAudio(pin)
Creates a mono PWM output port. Any pin can be used, but no analogWrite calls are allowed to any other pins using
that pin’s PWM slice hardware. See the RP2040 datasheet for more details about PWM slices.
13.1.2 PWMAudio(pin, true)
Creates a stereo PWM output port. Only even pins (left signal) can be used, the next odd pin will automatically be
assigned to the right channel (i.e. PWMAudio pwm(0, true); will make GP0 as the left channel, GP1 as the right
channel). The same restriction as in mono mode applies.
13.1.3 bool setBuffers(size_t buffers, size_t bufferWords)
Set the number of DMA buffers and their size in 32-bit words. Call before PWMAudio::begin().
When running at high sample rates, it is recommended to increase the bufferWords to 32 or higher (i.e. pwm.
setBuffers(4, 32); ).
13.1.4 bool setPin(pin_size_t pin)
Adjusts the pin to connect to the PWM audio output. Only legal before PWMAudio::begin().
13.1.5 bool setStereo(bool stereo)
Adjusts the mono/stereo setting of the PWM audio output. Only legal before PWMAudio::begin().
49
Arduino-Pico Documentation, Release 4.4.3
13.1.6 bool setFrequency(long sampleRate)
Sets the sample frequency, but does not start the PWM device (however if the device was already running, it will
wontinue to run at the new frequency).
13.1.7 bool begin()/begin(long sampleRate)
Start the PWM Audio device up with the given sample rate, or with the value set using the prior setFrequency call.
13.1.8 void end()
Stops the PWMAudio device.
13.1.9 void flush()
Waits until all the PWM Audio buffers have been output.
13.1.10 size_t write(int16_t sample, bool sync = true)
Writes a single 16-bit sample to the buffer. It is up to the user to keep track of left/right channels when in stereo mode.
In mono mode, one sample is written per timestep while in stereo mode two write() calls are required.
This call will block (wait) until space is available to actually write the data if sync is not specified or set to true.
13.1.11 size_t write(const uint8_t *buffer, size_t size)
Transfers number of bytes from an application buffer to the PWM Audio output buffer. Be aware that size is in bytes*
and not samples. Size must be a multiple of 4 bytes. Will not block, so check the return value to find out how many
bytes were actually written.
13.1.12 int availableForWrite()
Returns the number of samples that can be written without potentially blocking.
13.1.13 void onTransmit(void (*fn)(void))
Sets a callback to be called when a PWM Audio DMA buffer is fully transmitted. Will be in an interrupt context so the
specified function must operate quickly and not use blocking calls like delay() or write to the PWM Audio.
50 Chapter 13. PWM Audio Library
CHAPTER
FOURTEEN
ADC INPUT LIBRARY
The ADC pins can be sampled and recorded by an application using the same interface as the I2S or PWM Audio
libraries. This allows analog devices which need to be periodically sampled to be read by applications, easily, such as:
• Analog electret microphones
• Potentiometers
• Light dependent resistors (LDR), etc.
Up to 4 analog samples can be recorded by the hardware (A0 . . . A3), and all recording is done at 16-bit levels (but be
aware that the ADC in the Pico will only ever return values between 0. . . 4095).
The interface for the ADCInput device is very similar to the I2S input device, and most code can be ported simply by
instantiating a ADCInput object in lieu of an I2S input object and choosing the pins to record.
Since this uses the ADC hardware, no analogRead or analogReadTemp calls are allowed while in use.
14.1 ADC Input API
14.1.1 ADCInput(pin0 [, pin1, pin2, pin3])
Creates an ADC input object which will record the pins specified in the code. Only pins A0 . . . A3 can be used, and
they must be specified in increasing order (i.e. ADCInput(A0, A1); is valid, but ADCInput(A1, A0) is not.
14.1.2 bool setBuffers(size_t buffers, size_t bufferWords)
Set the number of DMA buffers and their size in 32-bit words. Call before ADCInput::begin().
When running at high sample rates, it is recommended to increase the bufferWords to 32 or higher (i.e. adcinput.
setBuffers(4, 32); ).
14.1.3 bool setPins(pin_size_t pin [, pin1, pin2, pin3])
Adjusts the pin to record. Only legal before ADCInput::begin().
14.1.4 bool setFrequency(long sampleRate)
Sets the ADC sampling frequency, but does not start recording (however if the device was already running, it will
continue to run at the new frequency). Note that every pin requested will be sampled at this frequency, one after the
other. That is, if you have code like this:
ADCInput adc(A0, A1);
adc.setFrequency(1000);
51
Arduino-Pico Documentation, Release 4.4.3
A0 will be sampled at 0ms, 1ms, 2ms, etc. and A1 will be sampled at 0.5ms 1.5ms, 2.5ms, etc. Each input is sampled
at the proper frequency but offset in time since there is only one active ADC at a time.
14.1.5 bool begin()/begin(long sampleRate)
Start the ADC input up with the given sample rate, or with the value set using the prior setFrequency call.
14.1.6 void end()
Stops the ADC Input device.
14.1.7 int read()
Reads a single sample of recorded ADC data, as a 16-bit value. When multiple pins are recorded the first read will be
pin 0, the second will be pin 1, etc. Applications need to keep track of which pin is being returned (normally by always
reading out all pins at once). Will not return until data is available.
14.1.8 int available()
Returns the number of samples that can be read without potentially blocking.
14.1.9 void onReceive(void (*fn)(void))
Sets a callback to be called when a ADC input DMA buffer is fully filled. Will be in an interrupt context so the specified
function must operate quickly and not use blocking calls like delay().
52 Chapter 14. ADC Input Library
CHAPTER
FIFTEEN
SERIAL PORTS (USB AND UART)
The Arduino-Pico core implements a software-based Serial-over-USB port using the USB ACM-CDC model to support
a wide variety of operating systems.
Serial is the USB serial port, and while Serial.begin() does allow specifying a baud rate, this rate is ignored since
it is USB-based. (Also be aware that this USB Serial port is responsible for resetting the RP2040 during the upload
process, following the Arduino standard of 1200bps = reset to bootloader).
The RP2040 provides two hardware-based UARTS with configurable pin selection.
Serial1 is UART0, and Serial2 is UART1.
Configure their pins using the setXXX calls prior to calling begin()
Serial1.setRX(pin);
Serial1.setTX(pin);
Serial1.begin(baud);
The size of the receive FIFO may also be adjusted from the default 32 bytes by using the setFIFOSize call prior to
calling begin()
Serial1.setFIFOSize(128);
Serial1.begin(baud);
The FIFO is normally handled via an interrupt, which reduced CPU load and makes it less likely to lose characters.
For applications where an IRQ driven serial port is not appropriate, use setPollingMode(true) before calling
begin()
Serial1.setPollingMode(true);
Serial1.begin(300)
For detailed information about the Serial ports, see the Arduino Serial Reference .
15.1 Inversion
Serial1 and Serial2 can both support inverted input and/or outputs via the methods Serial1/
2::setInvertRX(bool invert) and Serial1/2::setInvertTX(bool invert) and Serial1/
2::serInvertControl(bool invert).
53
Arduino-Pico Documentation, Release 4.4.3
15.2 RP2040 Specific SerialUSB methods
15.2.1 void Serial.ignoreFlowControl(bool ignore)
In some cases, the target application will not assert the DTR virtual line, thus preventing writing operations to succeed.
For this reason, the SerialUSB::ignoreFlowControl() method disables the connection’s state verification, enabling the
program to write on the port, even though the data might be lost.
15.2.2 bool Serial.dtr()
Returns the current state of the DTR virtual line. A USB CDC host (such as the Arduino serial monitor) typically raises
the DTR pin when opening the device, and may lower it when closing the device.
15.2.3 bool Serial.rts()
Returns the current state of the RTS virtual line.
54 Chapter 15. Serial Ports (USB and UART)
CHAPTER
SIXTEEN
“SOFTWARESERIAL” PIO-BASED UART
Equivalent to the Arduino SoftwareSerial library, an emulated UART using one or two PIO state machines is included
in the Arduino-Pico core. This allows for up to 4 bidirectional or up to 8 unidirectional serial ports to be run from the
RP2040 without requiring additional CPU resources.
Instantiate a SerialPIO(txpin, rxpin, fifosize) object in your sketch and then use it the same as any other
serial port. Even, odd, and no parity modes are supported, as well as data sizes from 5- to 8-bits. Fifosize, if not
specified, defaults to 32 bytes.
To instantiate only a serial transmit or receive unit, pass in SerialPIO::NOPIN as the txpin or rxpin.
For example, to make a transmit-only port on GP16
SerialPIO transmitter( 16, SerialPIO::NOPIN );
For detailed information about the Serial ports, see the Arduino Serial Reference .
16.1 Inversion
SoftwareSerial and SerialPIO can both support inverted input and/or outputs via the methods setInvertRX(bool
invert) and setInvertTX(bool invert).
55
Arduino-Pico Documentation, Release 4.4.3
56 Chapter 16. “SoftwareSerial” PIO-based UART
CHAPTER
SEVENTEEN
SOFTWARESERIAL EMULATION
A SoftwareSerial wrapper is included to provide plug-and-play compatibility with the Arduino Software Serial
library. Use the normal #include <SoftwareSerial.h> to include it. The following differences from the Arduino
standard are present:
• All ports are always listening
• listen call is a no-op
• isListening() always returns true
57
Arduino-Pico Documentation, Release 4.4.3
58 Chapter 17. SoftwareSerial Emulation
CHAPTER
EIGHTEEN
SERVO LIBRARY
A hardware-based servo controller is provided using the Servo library. It utilizes the PIO state machines and generates
the appropriate servo control pulses, glitch-free and jitter-free (within crystal limits).
Up to 8 Servos can be controlled in parallel assuming no other tasks require the use of a PIO machine.
See the Arduino standard Servo documentation for detailed usage instructions. There is also an included sweep exam-
ple.
18.1 Pulse Width Defaults
The defaults in the Servo library are conservatively set to avoid damage in the case of over-driving. The pulse widths
individual servos, especially the no-name or clones, occasionally need tweaking.
You can set the min and max servo pulse width in the attach command, with default values used in most Arduino cores
of 540/2400: `myServo.attach(D3, 540, 2400)`
59
Arduino-Pico Documentation, Release 4.4.3
60 Chapter 18. Servo Library
CHAPTER
NINETEEN
SPI MASTER (SERIAL PERIPHERAL INTERFACE)
The RP2040 has two hardware SPI interfaces, spi0 (SPI) and spi1 (SPI1). These interfaces are supported by the
SPI library in master mode.
SPI pinouts can be set before SPI.begin() using the following calls:
bool setRX(pin_size_t pin); // or setMISO()
bool setCS(pin_size_t pin);
bool setSCK(pin_size_t pin);
bool setTX(pin_size_t pin); // or setMOSI()
Note that the CS pin can be hardware or software controlled by the sketch. When software controlled, the setCS() call
is ignored.
The Arduino SPI documentation gives a detailed overview of the library, except for the following RP2040-specific
changes:
• SPI.begin(bool hwCS) can take an options hwCS parameter.
By passing in true for hwCS the sketch does not need to worry about asserting and deasserting the CS pin between
transactions. The default is false and requires the sketch to handle the CS pin itself, as is the standard way in Arduino.
• The interrupt calls (attachInterrupt, and detachInterrpt) are not implemented.
61
Arduino-Pico Documentation, Release 4.4.3
62 Chapter 19. SPI Master (Serial Peripheral Interface)
CHAPTER
TWENTY
SOFTWARE SPI (MASTER ONLY)
Similar to SoftwareSerial, SoftwareSPI creates a PIO based SPI interface that can be used in the same manner
as the hardware SPI devices. The constructor takes the pins desired, which can be any GPIO pins with the rule that if
hardware CS is used then it must be on pin SCK + 1. Construct a SoftwareSPI object in your code as follows and
use it as needed (i.e. pass it into SD.begin(_CS, softwareSPI);
#include <SoftwareSPI.h>
SoftwareSPI softSPI(_sck, _miso, _mosi); // no HW CS support, any selection of pins can␣
˓→be used
63
Arduino-Pico Documentation, Release 4.4.3
64 Chapter 20. Software SPI (Master Only)
CHAPTER
TWENTYONE
SPI SLAVE (SPISLAVE)
Slave mode operation is also supported on either SPI interface. Two callbacks are needed in your app, set through
SPISlave.onDataRecv and SPISlave.onDataSent, in order to consunme the received data and provide data to
transmit.
• The callbacks operate at IRQ time and may be called very frequently at high SPI frequencies. So, make then
small, fast, and with no memory allocations or locking.
65
Arduino-Pico Documentation, Release 4.4.3
66 Chapter 21. SPI Slave (SPISlave)
CHAPTER
TWENTYTWO
ASYNCHRONOUS OPERATION
Applications can use asynchronous SPI calls to allow for processing while long-running SPI transfers are being per-
formed. For example, a game could send a full screen update out over SPI and immediately start processing the next
frame without waiting for the first one to be sent. DMA is used to handle the transfer to/from the hardware freeing the
CPU from bit-banging or busy waiting.
Note that asynchronous operations can not be intersped with normal, synchronous ones. transferAsync should still
occur after a beginTransaction() and when finishedAsync() returns true then endTransaction() should
also be called.
All buffers need to be valid throughout the entire operation. Read data cannot be accessed until the transaction is
completed and can’t be “peeked” at while the operation is ongoing.
22.1 bool transferAsync(const void *send, void *recv, size_t bytes)
Begins an SPI asynchronous transaction. Either send or recv can be nullptr if data only needs to be transferred
in one direction. Check finishedAsync() to determine when the operation completes and conclude the transaction.
This operation needs to allocate a buffer from heap equal to bytes in size if LSBMODE is used.
22.2 bool finishedAsync()
Call to check if the asynchronous operations is completed and the buffer passed in can be either read or reused. Frees
the allocated memory and completes the asynchronous transaction.
22.3 void abortAsync()
Cancels the outstanding asynchronous transaction and frees any allocated memory.
67
Arduino-Pico Documentation, Release 4.4.3
68 Chapter 22. Asynchronous Operation
CHAPTER
TWENTYTHREE
EXAMPLES
See the SPItoMyself and SPItoMyselfAsync examples for a complete Master and Slave application.
69
Arduino-Pico Documentation, Release 4.4.3
70 Chapter 23. Examples
CHAPTER
TWENTYFOUR
WIRE (I2C MASTER AND SLAVE)
The RP2040 has two I2C devices, i2c0 (Wire) and i2c1 (Wire1).
The default pins for Wire and Wire1 vary depending on which board you’re using. (Here are the pinout diagrams for
Pico and Adafruit Feather.)
You may change these pins before calling Wire.begin() or Wire1.begin() using:
bool setSDA(pin_size_t sda);
bool setSCL(pin_size_t scl);
Be sure to use pins labeled I2C0 for Wire and I2C1 for Wire1 on the pinout diagram for your board, or it won’t work.
Other than that, the API is compatible with the Arduino standard. Both master and slave operation are supported.
Master transmissions are buffered (up to 256 bytes) and only performed on endTransmission, as is standard with
modern Arduino Wire implementations.
For more detailed information, check the Arduino Wire documentation .
24.1 Asynchronous Operation
Applications can use asynchronous I2C calls to allow for processing while long-running I2C operations are being
performed. For example, a game could send a full screen update out over I2C and immediately start processing the
next frame without waiting for the first one to be sent over I2C. DMA is used to handle the transfer to/from the I2C
hardware freeing the CPU from bit-banging or busy waiting.
Note that asynchronous operations can not be intersped with normal, synchronous ones. Fully complete or abort an
asynchronous operation before attempting to do a normal Wire.beginTransaction() or Wire.requestFrom.
24.1.1 bool writeReadAsync(uint8_t address, const void *wbuffer, size_t wbytes,
const void *rbuffer, size_t rbytes, bool sendStop)
Executes a master I2C asynchronous write/read transaction to I2C slave address. First wbytes from wbuffer are
written to the I2C slave, followed by an I2C restart, then rbytes are read from the I2C slave into rbuffer. The buffers
need to be valid throughout the entire asynchronous operation.
At the end of the transaction an I2C stop is sent if sendStop is true, and at the beginning of the transaction an I2C
start is sent if the previous write/read had sendStop set to true.
Check finishedAsync() to determine when the operation completes, or use onFinishedAsync() to set a callback.
Set rbytes to 0 to do a write-only operation, set wbytes to 0 to do a read-only operation. Or use:
bool writeAsync(uint8_t address, const void \*buffer, size_t bytes, bool sendStop)
bool readAsync(uint8_t address, void \*buffer, size_t bytes, bool sendStop)
71
Arduino-Pico Documentation, Release 4.4.3
The first call to an asynchronous write/read operation allocates the required DMA channels and internal buffer. If
desired, call end() to free these resources.
24.1.2 bool finishedAsync()
Call to check if the asynchronous operations is completed.
24.1.3 void onFinishedAsync(void(*function)(void))
Set a (optional) callback for async operation. The function will be called when the asynchronous operation finishes.
24.1.4 void abortAsync()
Cancels any outstanding asynchronous transaction.
72 Chapter 24. Wire (I2C Master and Slave)
CHAPTER
TWENTYFIVE
FILE SYSTEMS
The Arduino-Pico core supports using some of the onboard flash as a file system, useful for storing configuration data,
output strings, logging, and more. It also supports using SD cards as another (FAT32) filesystem, with an API that’s
compatible with the onboard flash file system.
25.1 Flash Layout
Even though file system is stored on the same flash chip as the program, programming new sketch will not modify file
system contents (or EEPROM data).
The following diagram shows the flash layout used in Arduino-Pico:
|----|---------------------|-------------|----|
^ ^ ^ ^
OTA Sketch File system EEPROM
The file system size is configurable via the IDE menus, from 64k up to 15MB (assuming you have an RP2040 board
with that much flash)
Note: to use any of file system functions in the sketch, add the following include to the sketch:
#include "LittleFS.h" // LittleFS is declared
// #include <SDFS.h>
// #include <SD.h>
// #include <FatFS.h>
25.2 Compatible Filesystem APIs
LittleFS is an onboard filesystem that sets aside some program flash for use as a filesystem without requiring any
external hardware.
SDFS is a filesystem for SD cards, based on [SdFat 2.0](https://github.com/earlephilhower/ESP8266SdFat). It supports
FAT16 and FAT32 formatted cards, and requires an external SD card reader.
SD is the Arduino-supported, somewhat old and limited SD card filesystem. It is recommended to use SDFS for new
applications instead of SD.
FatFS implements a wear-leveled, FTL-backed FAT filesystem in the onboard flash which can be easily accessed over
USB as a standard memory stick via FatFSUSB.
All of these filesystems can open and manipulate File and Dir objects with the same code because the implement a
common end-user filesystem API.
73
Arduino-Pico Documentation, Release 4.4.3
25.3 FatFS File System Caveats and Warnings
The FAT filesystem is ubiquitous, but it is also around 50 years old and ill suited to SPI flash memory due to having
“hot spots” like the FAT copies that are rewritten many times over. SPI flash allows a high, but limited, number of
writes before losing the ability to write safely. Applications like data loggers where many writes occur could end up
wearing out the SPI flash sector that holds the FAT years before coming close to the write limits of the data sectors.
To circumvent this issue, the FatFS implementation here uses a flash translation layer (FTL) developed for SPI flash on
embedded systems. This allows for the same LBA to be written over and over by the FAT filesystem, but use different
flash locations. For more information see [SPIFTL](https://github.com/earlephilhower/SPIFTL). In this mode the Pico
flash appears as a normal, 512-byte sector drive to the FAT.
What this means, practically, is that about 5KB of RAM per megabyte of flash is required for housekeeping. Writes
can also become very slow if most of the flash LBA range is used (i.e. if the FAT drive is 99% full) due to the need for
garbage collection processes to move data around and preserve the flash lifetime.
Alternatively, if an FTL is not desired or memory is tight, FatFS can use the raw flash directly. In this mode sectors are
4K in size and flash is mapped 1:1 to sectors, so things like the FAT table updates will all use the same physical flash
bits. For low-utilization operations this may be fine, but if significant writes are done (from the Pico or the PC host)
this may wear out portions of flash very quickly , rendering it unusable.
25.4 LittleFS File System Limitations
The LittleFS implementation for the RP2040 supports filenames of up to 254 characters + terminating zero (i.e. char
filename[255] or better char filename[LFS_NAME_MAX] ), and as many subdirectories as space permits.
Filenames are assumed to be in the root directory if no initial “/” is present.
Opening files in subdirectories requires specifying the complete path to the file (i.e. LittleFS.open("/sub/dir/
file.txt", "r");). Subdirectories are automatically created when you attempt to create a file in a subdirectory, and
when the last file in a subdirectory is removed the subdirectory itself is automatically deleted.
25.5 Uploading Files to the LittleFS File System
PicoLittleFS is a tool which integrates into the Arduino IDE. It adds a menu item to Tools menu for uploading the
contents of sketch data directory into a new LittleFS flash file system.
IDE 1.x
• Download the tool: https://github.com/earlephilhower/arduino-pico-littlefs-plugin/releases
• In your Arduino sketchbook directory, create tools directory if it doesn’t exist yet.
• Unpack the tool into tools directory (the path will look like <home_dir>/Arduino/tools/PicoLittleFS/
tool/picolittlefs.jar) If upgrading, overwrite the existing JAR file with the newer version.
• Restart Arduino IDE.
• Open a sketch (or create a new one and save it).
• Go to sketch directory (choose Sketch > Show Sketch Folder).
• Create a directory named data and any files you want in the file system there.
• Make sure you have selected a board, port, and closed Serial Monitor.
• Double check the Serial Monitor is closed. Uploads will fail if the Serial Monitor has control of the serial port.
• Select Tools > Pico LittleFS Data Upload. This should start uploading the files into the flash file system.
IDE 2.x
74 Chapter 25. File Systems
Arduino-Pico Documentation, Release 4.4.3
• Download the new tool: https://github.com/earlephilhower/arduino-littlefs-upload/releases
• Exit the IDE, if running
• Copy the VSIX file manually to (Linux/Mac) ~/.arduinoIDE/plugins/ (you may need to make this directory
yourself beforehand) or to (Windows) C:\Users\<username>\.arduinoIDE\
• Restart the IDE
• Double check the Serial Monitor is closed. Uploads will fail if the Serial Monitor has control of the serial port.
• Enter [Ctrl] + [Shift] + [P] to bring up the command palette, then select/type Upload LittleFS to
Pico/ESP8266
25.6 Downloading Files from a LittleFS System
Using gdb it is possible to dump the flash data making up the filesystem and then extract it using the mklittlefs tool.
A working OpenOCD setup, DebugProbe, and gdb are required. To download the raw filesystem, from within GDB run:
^C (break)
(gdb) dump binary memory littlefs.bin &_FS_start &_FS_end
It may take a few seconds as GDB reads out the flash to the file. Once the raw file is downloaded it can be extracted
using the mklittlefs tool from the BASH/Powershell/command line
$ <path-to-mklittlefs>/mklittlefs -u output-dir littlefs.bin
Directory <output-dir> does not exists. Try to create it.
gmon.out > <output-dir>/gmon.out size: 24518 Bytes
gmon.bak > <output-dir>/gmon.bak size: 1 Bytes
The defaults built into mklittlefs should be appropriate for normal LittleFS filesystems built on the device or using
the upload tool.
25.7 SD Library Information
The included SD library is the Arduino standard one. Please refer to the [Arduino SD reference](https://www.arduino.
cc/en/reference/SD) for more information.
25.8 Using Second SPI port for SD
The SD library begin() has been modified to allow you to use the second SPI port, SPI1. Just use the following call
in place of SD.begin(cspin)
SD.begin(cspin, SPI1);
25.9 Enabling SDIO operation for SD
SDIO support is available thanks to SdFat implementing a PIO-based SDIO controller. This mode can significantly
increase IO performance to SD cards but it requires that all 4 DAT0..DAT3 lines to be wired to the Pico (most SD
breakout boards only provide 1-but SPI mode of operation).
To enable SDIO mode, simply specify the SD_CLK, SD_CMD, and SD_DAT0 GPIO pins. The clock and command
pins can be any GPIO (not limited to legal SPI pins). The DAT0 pin can be any GPIO with remaining DAT1. . . 3 pins
consecutively connected.
25.6. Downloading Files from a LittleFS System 75
Arduino-Pico Documentation, Release 4.4.3
..code:: cpp
SD.begin(RP_CLK_GPIO, RP_CMD_GPIO, RP_DAT0_GPIO);
No other changes are required in the application to take advantage of this high performance mode.
25.10 Using VFS (Virtual File System) for POSIX support
The VFS library enables sketches to use standard POSIX file I/O operations using standard FILE * operations. Include
the VFS library in your application and add a call to map the VFS.root() to your filesystem. I.e.:
#include <VFS.h>
#include <LittleFS.h>
void setup() {
LittleFS.begin();
VFS.root(LittleFS);
FILE *fp = fopen("/thisfilelivesonflash.txt", "w");
fprintf(fp, "Hello!\n");
fclose(fp);
}
Multiple filesystems can be VFS.map() into the VFS namespace under different directory names. For example, the
following will make files on /sd reside on an externalSD card and files on /lfs live in internal flash.
#include <VFS.h>
#include <LittleFS.h>
#include <SDFS.h>
void setup() {
LittleFS.begin();
SDFS.begin();
VFS.map("/lfs", LittleFS);
VFS.map("/sd", SDFS);
FILE *onSD = fopen("/sd/thislivesonsd.txt", "wb");
....
}
See the examples in the VFS library for more information.
25.11 File system object (LittleFS/SD/SDFS/FatFS)
25.11.1 setConfig
LittleFSConfig cfg;
cfg.setAutoFormat(false);
LittleFS.setConfig(cfg);
SDFSConfig c2;
c2.setCSPin(12);
SDFS.setConfig(c2);
FatFSConfig c3;
(continues on next page)
76 Chapter 25. File Systems
Arduino-Pico Documentation, Release 4.4.3
(continued from previous page)
c3.setUseFTL(false); // Directly access flash memory
c3.setDirEntries(256); // We need 256 root directory entries on a format()
c3.setFATCopies(1); // Only 1 FAT to save 4K of space and extra writes
FatFS.setConfig(c3);
FatFS.format(); // Format using these settings, erasing everything
This method allows you to configure the parameters of a filesystem before mounting. All filesystems have their own
*Config (i.e. SDFSConfig or LittleFSConfig with their custom set of options. All filesystems allow explicitly
enabling/disabling formatting when mounts fail. If you do not call this setConfig method before perforing begin(),
you will get the filesystem’s default behavior and configuration. By default, LittleFS and FatFS will autoformat the
filesystem if it cannot mount it, while SDFS will not. FatFS will also use the built-in FTL to support 512 byte sectors
and higher write lifetime.
25.11.2 begin
SDFS.begin()
or LittleFS.begin()
This method mounts file system. It must be called before any other FS APIs are used. Returns true if file system was
mounted successfully, false otherwise.
Note that LittleFS will automatically format the filesystem if one is not detected. This is configurable via setConfig.
25.11.3 end
SDFS.end()
or LittleFS.end()
This method unmounts the file system.
25.11.4 format
SDFS.format()
or LittleFS.format()
Formats the file system. May be called either before or after calling begin. Returns true if formatting was successful.
25.11.5 open
SDFS.open(path, mode)
or LittleFS.open(path, mode)
Opens a file. path should be an absolute path starting with a slash (e.g. /dir/filename.txt). mode is a string
specifying access mode. It can be one of “r”, “w”, “a”, “r+”, “w+”, “a+”. The meaning of these modes is the same as
for the fopen C function.
r Open text file for reading. The stream is positioned at the
beginning of the file.
r+ Open for reading and writing. The stream is positioned at the
beginning of the file.
(continues on next page)
25.11. File system object (LittleFS/SD/SDFS/FatFS) 77
Arduino-Pico Documentation, Release 4.4.3
(continued from previous page)
w Truncate file to zero length or create text file for writing.
The stream is positioned at the beginning of the file.
w+ Open for reading and writing. The file is created if it does
not exist, otherwise it is truncated. The stream is
positioned at the beginning of the file.
a Open for appending (writing at end of file). The file is
created if it does not exist. The stream is positioned at the
end of the file.
a+ Open for reading and appending (writing at end of file). The
file is created if it does not exist. The initial file
position for reading is at the beginning of the file, but
output is always appended to the end of the file.
Returns File object. To check whether the file was opened successfully, use the boolean operator.
File f = LittleFS.open("/f.txt", "w");
if (!f) {
Serial.println("file open failed");
}
25.11.6 exists
SDFS.exists(path)
or LittleFS.exists(path)
Returns true if a file with given path exists, false otherwise.
25.11.7 mkdir
SDFS.mkdir(path)
or LittleFS.mkdir(path)
Returns true if the directory creation succeeded, false otherwise.
25.11.8 rmdir
SDFS.rmdir(path)
or LittleFS.rmdir(path)
Returns true if the directory was successfully removed, false otherwise.
25.11.9 openDir
SDFS.openDir(path)
or LittleFS.openDir(path)
Opens a directory given its absolute path. Returns a Dir object.
78 Chapter 25. File Systems
Arduino-Pico Documentation, Release 4.4.3
25.11.10 remove
SDFS.remove(path)
or LittleFS.remove(path)
Deletes the file given its absolute path. Returns true if file was deleted successfully.
25.11.11 rename
SDFS.rename(pathFrom, pathTo)
or LittleFS.rename(pathFrom, pathTo)
Renames file from pathFrom to pathTo. Paths must be absolute. Returns true if file was renamed successfully.
25.11.12 info
FSInfo fs_info;
or LittleFS.info(fs_info);
Fills FSInfo structure with information about the file system. Returns true if successful, false otherwise. ìnfo()
has been updated to support filesystems greater than 4GB and FSInfo64 and info64() have been discarded.
25.12 Filesystem information structure
struct FSInfo {
uint64_t totalBytes;
uint64_t usedBytes;
size_t blockSize;
size_t pageSize;
size_t maxOpenFiles;
size_t maxPathLength;
};
This is the structure which may be filled using FS::info method. - totalBytes — total size of useful data on the file
system - usedBytes — number of bytes used by files - blockSize — filesystem block size - pageSize — filesystem
logical page size - maxOpenFiles — max number of files which may be open simultaneously - maxPathLength —
max file name length (including one byte for zero termination)
25.12.1 setTimeCallback(time_t (*cb)(void))
time_t myTimeCallback() {
return 1455451200; // UNIX timestamp
}
void setup () {
LittleFS.setTimeCallback(myTimeCallback);
...
// Any files will now be made with Pris' incept date
}
The SD, SDFS, and LittleFS filesystems support a file timestamp, updated when the file is opened for writing. By de-
fault, the Pico will use the internal time returned from time(NULL) (i.e. local time, not UTC, to conform to the existing
FAT filesystem), but this can be overridden to GMT or any other standard you’d like by using setTimeCallback(). If
your app sets the system time using NTP before file operations, then you should not need to use this function. However,
25.12. Filesystem information structure 79
Arduino-Pico Documentation, Release 4.4.3
if you need to set a specific time for a file, or the system clock isn’t correct and you need to read the time from an
external RTC or use a fixed time, this call allows you do to so.
In general use, with a functioning time() call, user applications should not need to use this function.
25.13 Directory object (Dir)
The purpose of Dir object is to iterate over files inside a directory. It provides multiple access methods.
The following example shows how it should be used:
Dir dir = LittleFS.openDir("/data");
// or Dir dir = LittleFS.openDir("/data");
while (dir.next()) {
Serial.print(dir.fileName());
if(dir.fileSize()) {
File f = dir.openFile("r");
Serial.println(f.size());
}
}
25.13.1 next
Returns true while there are files in the directory to iterate over. It must be called before calling fileName(),
fileSize(), and openFile() functions.
25.13.2 fileName
Returns the name of the current file pointed to by the internal iterator.
25.13.3 fileSize
Returns the size of the current file pointed to by the internal iterator.
25.13.4 fileTime
Returns the time_t write time of the current file pointed to by the internal iterator.
25.13.5 fileCreationTime
Returns the time_t creation time of the current file pointed to by the internal iterator.
25.13.6 isFile
Returns true if the current file pointed to by the internal iterator is a File.
25.13.7 isDirectory
Returns true if the current file pointed to by the internal iterator is a Directory.
25.13.8 openFile
This method takes mode argument which has the same meaning as for SDFS/LittleFS.open() function.
80 Chapter 25. File Systems
Arduino-Pico Documentation, Release 4.4.3
25.13.9 rewind
Resets the internal pointer to the start of the directory.
25.13.10 setTimeCallback(time_t (*cb)(void))
Sets the time callback for any files accessed from this Dir object via openNextFile. Note that the SD and SDFS filesys-
tems only support a filesystem-wide callback and calls to Dir::setTimeCallback may produce unexpected behavior.
25.14 File object
SDFS/LittleFS.open() and dir.openFile() functions return a File object. This object supports all the functions
of Stream, so you can use readBytes, findUntil, parseInt, println, and all other Stream methods.
There are also some functions which are specific to File object.
25.14.1 seek
file.seek(offset, mode)
This function behaves like fseek C function. Depending on the value of mode, it moves current position in a file as
follows:
• if mode is SeekSet, position is set to offset bytes from the beginning.
• if mode is SeekCur, current position is moved by offset bytes.
• if mode is SeekEnd, position is set to offset bytes from the end of the file.
Returns true if position was set successfully.
25.14.2 position
file.position()
Returns the current position inside the file, in bytes.
25.14.3 size
file.size()
Returns file size, in bytes.
25.14.4 name
String name = file.name();
Returns short (no-path) file name, as const char*. Convert it to String for storage.
25.14.5 fullName
// Filesystem:
// testdir/
// file1
Dir d = LittleFS.openDir("testdir/");
(continues on next page)
25.14. File object 81
Arduino-Pico Documentation, Release 4.4.3
(continued from previous page)
File f = d.openFile("r");
// f.name() == "file1", f.fullName() == "testdir/file1"
Returns the full path file name as a const char*.
25.14.6 getLastWrite
Returns the file last write time, and only valid for files opened in read-only mode. If a file is opened for writing, the
returned time may be indeterminate.
25.14.7 getCreationTime
Returns the file creation time, if available.
25.14.8 isFile
bool amIAFile = file.isFile();
Returns true if this File points to a real file.
25.14.9 isDirectory
bool amIADir = file.isDir();
Returns true if this File points to a directory (used for emulation of the SD.* interfaces with the openNextFile method).
25.14.10 close
file.close()
Close the file. No other operations should be performed on File object after close function was called.
25.14.11 openNextFile (compatibility method, not recommended for new code)
File root = LittleFS.open("/");
File file1 = root.openNextFile();
File file2 = root.openNextFile();
Opens the next file in the directory pointed to by the File. Only valid when File.isDirectory() == true.
25.14.12 rewindDirectory (compatibility method, not recommended for new code)
File root = LittleFS.open("/");
File file1 = root.openNextFile();
file1.close();
root.rewindDirectory();
file1 = root.openNextFile(); // Opens first file in dir again
Resets the openNextFile pointer to the top of the directory. Only valid when File.isDirectory() == true.
82 Chapter 25. File Systems
Arduino-Pico Documentation, Release 4.4.3
25.14.13 setTimeCallback(time_t (*cb)(void))
Sets the time callback for this specific file. Note that the SD and SDFS filesystems only support a filesystem-wide
callback and calls to Dir::setTimeCallback may produce unexpected behavior.
25.14. File object 83
Arduino-Pico Documentation, Release 4.4.3
84 Chapter 25. File Systems
CHAPTER
TWENTYSIX
USB (ARDUINO AND ADAFRUIT_TINYUSB)
Two USB stacks are present in the core. Users can choose the simpler Pico-SDK version or the more powerful Adafruit
TinyUSB library. Use the Tools->USB Stack menu to select between the two.
26.1 Pico SDK USB Support
This is the default mode and automatically includes a USB-based serial port, Serial as well as supporting automatic
reset-to-upload from the IDE.
The Arduino-Pico core includes ported versions of the basic Arduino Keyboard, Mouse and Joystick libraries. These
libraries allow you to emulate a keyboard, a gamepad or mouse (or all together) with the Pico in your sketches. These
libraries only are available when using the built-in USB, not the Adafruit library.
See the examples and Arduino Reference at https://www.arduino.cc/reference/en/language/functions/usb/keyboard/
and https://www.arduino.cc/reference/en/language/functions/usb/mouse
26.2 HID Polling Interval
By default, HID devices will request to be polled every 10ms (i.e. 100x per second). If you have a higher performance
need, you can override this value by creating a global variable in your main application, set to the polling period:
int usb_hid_poll_interval = 1; // Set HID poll interval to 1ms (1kHz)
void setup() {
....
}
26.3 Adafruit TinyUSB Arduino Support
Examples are provided in the Adafruit_TinyUSB_Arduino for the more advanced USB stack.
To use Serial with TinyUSB, you must include the TinyUSB header in your sketch to avoid a compile error.
#include <Adafruit_TinyUSB.h>
If you need to be compatible with the other USB stack, you can use an ifdef:
#ifdef USE_TINYUSB
#include <Adafruit_TinyUSB.h>
#endif
85
Arduino-Pico Documentation, Release 4.4.3
Also, this stack requires sketches to manually call Serial.begin(115200) to enable the USB serial port and auto-
matic sketch upload from the IDE. If a sketch is run without this command in setup(), the user will need to use the
standard “hold BOOTSEL and plug in USB” method to enter program upload mode.
26.4 Adafruit TinyUSB Configuration and Quirks
The Adafruit TinyUSB’s configuration header for RP2040 devices is stored in libraries/
Adafruit_TinyUSB_Arduino/src/arduino/ports/rp2040/tusb_config_rp2040.h (here).
In some cases it is important to know what TinyUSB is configured with. For example, by having set
#define CFG_TUD_CDC 1
#define CFG_TUD_MSC 1
#define CFG_TUD_HID 1
#define CFG_TUD_MIDI 1
#define CFG_TUD_VENDOR 1
this configuration file defines the maximum number of USB CDC (serial) devices as 1. Hence, the example sketch
cdc_multi.ino that is delivered with the library will not work, it will only create one USB CDC device instead of two.
It will however work when the above CFG_TUD_CDC macro is defined to 2 instead of 1.
To do such a modification when using the Arduino IDE, the file can be locally modified in the Arduino core’s package
files. The base path can be found per this article, then navigate further to the packages/rp2040/hardware/rp2040/
<core version>/libraries/Adafruit_TinyUSB_Arduino folder to find the Adafruit TinyUSB library.
When using PlatformIO, one can also make use of the feature that TinyUSB allows redirecting the configuration file to
another one if a certain macro is set.
#ifdef CFG_TUSB_CONFIG_FILE
#include CFG_TUSB_CONFIG_FILE
#else
#include "tusb_config.h"
#endif
And as such, in the platformio.ini of the project, one can add
build_flags =
-DUSE_TINYUSB
-DCFG_TUSB_CONFIG_FILE=\"custom_tusb_config.h\"
-Iinclude/
and further add create the file include/custom_tusb_config.h as a copy of the original tusb_config_rp2040.h
but with the needed modifications.
Note: Some configuration file changes have no effect because upper levels of the library don’t properly support them.
In particular, even though the maximum number of HID devices can be set to 2, and two Adafruit_USBD_HID can be
created, it will not cause two HID devices to actually show up, because of code limitations.
86 Chapter 26. USB (Arduino and Adafruit_TinyUSB)
CHAPTER
TWENTYSEVEN
MULTICORE PROCESSING
The RP2040 chip has 2 cores that can run independently of each other, sharing peripherals and memory with each
other. Arduino code will normally execute only on core 0, with the 2nd core sitting idle in a low power state.
By adding a setup1() and loop1() function to your sketch you can make use of the second core. Anything called
from within the setup1() or loop1() routines will execute on the second core.
setup() and setup1() will be called at the same time, and the loop() or loop1() will be started as soon as the
core’s setup() completes (i.e. not necessarily simultaneously!).
See the Multicore.ino example in the rp2040 example directory for a quick introduction.
27.1 Stack Sizes
When the Pico is running in single core mode, core 0 has the full 8KB of stack space available to it. When using
multicore setup1/loop1 the 8KB is split into two 4K stacks, one per core. It is possible for core 0’s stack to overwrite
core 1’s stack in this case, if you go beyond the 4K limitation.
To allocate a separate 8K stack for core 1, resulting in 8K stacks being available for both cores, simply define the
following variable in your sketch and set it to true:
bool core1_separate_stack = true;
27.2 Pausing Cores
Sometimes an application needs to pause the other core on chip (i.e. it is writing to flash or needs to stop processing
while some other event occurs). In most cases, however, these calls are SHOULD NOT BE USED. To synchronize
cross-core operations use normal multiprocessor methods such as circular buffers, global volatile flags, mutexes,
and the like. Stopping a core has massive implications and can kill networking and USB communications if done too
long or too frequently.
27.2.1 void rp2040.idleOtherCore()
Sends a message to stop the other core (i.e. when called from core 0 it pauses core 1, and vice versa). Waits for the
other core to acknowledge before returning.
The other core will have its interrupts disabled and be busy-waiting in an RAM-based routine, so flash and other
peripherals can be accessed.
NOTE If you idle core 0 too long, then the USB port can become frozen. This is because core 0 manages the USB and
needs to service IRQs in a timely manner (which it can’t do when idled).
87
Arduino-Pico Documentation, Release 4.4.3
27.2.2 void rp2040.resumeOtherCore()
Resumes processing in the other core, where it left off.
27.2.3 void rp2040.restartCore1()
Hard resets Core1 from Core 0 and restarts its operation from setup1().
27.3 Communicating Between Cores
The RP2040 provides a hardware FIFO for communicating between cores, but it is used exclusively for the idle/resume
calls described above. Instead, please use the following functions to access a software-managed, multicore safe FIFO.
There are two FIFOs, one written to by core 0 and read by core 1, and the other written to by core 1 and read by core 0.
You can (and probably should) use shared memory (such as volatile globals) or other normal multiprocessor com-
munication algorithms to transfer data or work between cores, but for simple tasks these FIFO routines can suffice.
27.3.1 void rp2040.fifo.push(uint32_t)
Pushes a value to the other core. Will block if the FIFO is full.
27.3.2 bool rp2040.fifo.push_nb(uint32_t)
Pushes a value to the other core. If the FIFO is full, returns false immediately and doesn’t block. If the push is
successful, returns true.
27.3.3 uint32_t rp2040.fifo.pop()
Reads a value from this core’s FIFO. Blocks until one is available.
27.3.4 bool rp2040.fifo.pop_nb(uint32_t *dest)
Reads a value from this core’s FIFO and places it in dest. Will return true if successful, or false if the pop would
block.
27.3.5 int rp2040.fifo.available()
Returns the number of values available to read in this core’s FIFO.
88 Chapter 27. Multicore Processing
CHAPTER
TWENTYEIGHT
SEMIHOSTING SUPPORT
Using special debugger breakpoints and commands, the Pico can read and write to the debugging console as well as
read and write files on a development PC. The Semihosting library allows applications to use the semihosting support
as a normal filesystem or serial port.
NOTE Semihosting only works when connected to an OpenOCD + GDB debug session. Running an application
compiled for Semihosting without the debugger will cause a panic and hang the chip.
As of now, only ARM has support for Semihosting.
28.1 Running Semihosting on the Development Host
Start OpenOCD normally from inside a directory that you can read and write files within (i.e. do not run from C:\\
Program Files\\.. on Windows where general users aren’t allowed to write). The starting directory will be where
the Pico will read and write files using the SemiFS class. Be sure to keep the terminal window you ran OpenOCD in
open, because all SerialSemi input and output will go to that terminal and not gdb’s.
Start GDB normally and connect to the OpenOCD debugger and enable semihosting support
(gdb) target extended-remote localhost:3333
(gdb) monitor arm semihosting enable
At this point load and run your ELF application as normal. Again, all SerialSemi output will go to the OpenOCD
window, not GDB.
See the hellosemi example in the Semihosting library.
28.2 SerialSemi - Serial over Semihosting
Simply include <Semihosting.h> in your application and use SerialSemi as you would any other Serial port with
the following limitations:
• Baud rate, bit width, etc. are all ignored
• Input is limited because read may hang indefinitely in the host and available is not part of the spec
SerialSemi can also be selected as the debug output port in the IDE, in which case ::printf will write to the
debugger directly.
28.3 SemiFS - Host filesystem access through Semihosting
Use SemiFS the same way as any other file system. Note that only file creation and renaming are supported, with no
provision for iterating over directories or listing files. In most cases simply opening a File and writing out a debug
dump is all that’s needed:
89
Arduino-Pico Documentation, Release 4.4.3
SemiFS.begin();
File f = SemiFS.open("debug.dmp", "w");
f.write(buffer, size);
f.close();
SerialSemi.printf("Debug dump now available on host.\n");
90 Chapter 28. Semihosting Support
CHAPTER
TWENTYNINE
PROFILING APPLICATIONS WITH GPROF
Applications running on the Pico can be profiled using GNU GPROF to show where the CPU is using its time on
the device and how often certain functions are called. It does this by recompiling the application and adding a small
preamble to each function built to identify what functions call what others (and how frequently). It also uses the
SYSTICK exception timer to sample and record the PC 10,000 times per second. When an application is complete, the
recorded date can be dumped to the host PC as a gmon.,out file which can be processed by arm-none-eabi-gprof
into useful date.
s histogram of PCs and tally of function caller/callees can take a significant amount of RAM, from 100KB to 10000KB
depending on the size of the application. As such, while the RP2040 may be able to profile small applications, this
is only really recommended on the RP2350 with external PSRAM. The profiler will automatically use PSRAM when
available. Call rp2040.getProfileMemoryUsage() to get the memory allocated at runtime.
Profiling also adds processing overhead in terms of the periodic sampling and the function preambles. In most cases
there is no reason to enable (and many reasons to disable) profiling when an application is deployed to the field.
To transfer the GMON.OUT data from the Pico to the host HP can be done by having the application write it out to an SD
card or a LittleFS filesystem which is then manually dumped, but for ease of use semihosting can be used to allow the
Pico (under the control of OpenOCD and GDB) to write the gmon.out file directly on the host PC, ready for use.
NOTE Semihosting only works when connected to an OpenOCD + GDB debug session. Running an application
compiled for Semihosting without the debugger will cause a panic and hang the chip.
As of now, only ARM has support for Semihosting or GPROF.
29.1 Enabling Profiling in an Application
The Tools->Profiling->Enabled menu needs to be selected to enable profiling support in GCC. This will add the
necessary preamble to every function compiled (Note that the libpico and libc will not be instrumented because
they are pre-built so calls from them will not be fully instrumented. However, PC data will still be grabbed and decoded
from them at runtime.)
The application will automatically start collecting profiling data even before setup starts in this mode. It will continue
collecting data until you stop and write out the profiling data using rp2040.writeProfiling() to dump to the host,
a file, serial port, etc.
For example, an application which does all its processing in setup() might look like:
#include <SemiFS.h>
void setup() {
SerialSemi.printf("BEGIN\n");
do_some_work_that_takes_a_long_time_with_many_function_calls();
// Do lots of other work...
// Now all done...
(continues on next page)
91
Arduino-Pico Documentation, Release 4.4.3
(continued from previous page)
SerialSemi.printf("Writing GMON.OUT\n");
SemiFS.begin();
File gmon = SemiFS.open("gmon.out", "w");
rp2040.writeProfiling(&gmon);
gmon.close();
SerialSemi.printf("END\n");
}
void loop() {}
29.2 Collecting and Analyzing Profile Data
Running this application under semihosting GDB and OpenOCD generates a gmon.out file in the OpenOCD current
working directory. This file, combined with the ELF binary build in the IDE and loaded through GDB, can produce
profiler output using
$ /path/to/arm-none-eabi/bin/arm-none-eabi-gprof /path/to/sketch.ino.elf /path/to/gmon.
˓→out
See the rp2040/Profiling.ino example for more details.
92 Chapter 29. Profiling Applications with GPROF
CHAPTER
THIRTY
RP2350 SPECIFIC NOTES
The RP2350 chip (present on the Raspberry Pi Pico 2 board and many others) is supported by the core with some minor
caveats:
• PSRAM is supported via a new pmalloc call and PSRAM variable decorator.
• Both RP2350A and RP2350B (48 GPIOs) are supported.
30.1 ARM and RISC-V Modes
Either set of cores can be used on the RP2350, ARM Cortex-M33 or RISC-V Hazard3. Select the desired core from
the IDE menus under Tools->CPU Architecture. As of the initial release, all libraries should work under the new
RISC-V mode with the exception of FreeRTOS. If not, patches are always welcome.
30.2 P2350-E9 Errata (“Increased leakage current on Bank 0 GPIO
when pad input is enabled”)
Like all chips, the RP2350-A2 stepping has post-silicon chip errata covering certain bugs found after the chip was
manufactured. Probably the most (in)famous and concerning is RP2350-E9 which is noted as resulting in “Increased
leakage current on Bank 0 GPIO when pad input is enabled.” At a high level, this means that in some cases when an
input pin is being driven by a weak (high impedance) input, it may not read properly.
After discussion with the community and exploration of the issue by many users, this core has decided not to implement
any forced workarounds for this issue. Given the physical nature of the problem and how no one specific solution is
appropriate for all conditions.
For more information please read the errata yourself and check the Raspberry Pi Forums for the latest updates.
93
Arduino-Pico Documentation, Release 4.4.3
94 Chapter 30. RP2350 Specific Notes
CHAPTER
THIRTYONE
RP2350 PSRAM SUPPORT
The RP2350 chip in the Raspberry Pi Pico 2, and other RP2350 boards, supports an external interface to PSRAM.
When a PSRAM chip is attached to the processor (please note that there is none on the Pico 2 board, but iLabs and
SparkFun boards, among others, do have it), up to 16 megabytes of additional memory can be used by the chip.
While this external RAM is slower than the built-in SRAM, it is still able to be used in any place where normal RAM
would be used (other than for memory-mapped functions and statically initialized variables).
When present, PSRAM can be used in two ways: for specific instantiated variables, or through a malloc-like access
method. Both can be used in any single application.
31.1 Using PSRAM for regular variables
Similar to PROGMEM in the original Arduino AVR devices, the variable decorator PSRAM can be added to map a variable
into PSRAM. Simply add PSRAM to an array and it will be mapped into PSRAM:
...
float weights[4000] PSRAM; // Place an array of 4000 floats in PSRAM
char samplefile[1'000'000] PSRAM; // Allocate 1M for WAV samples in PSRAM
...
These variables can be used just like normal ones, no special handling is required. For example:
char buff[4 *1024 * 1024]; // 4MB array
void initBuff() {
bzero(buff, sizeof(buff));
for (int i = 0; i < 4 *1024 * 1024; i += 4096) {
buff[i] = rand();
}
}
The only restriction is that these variables may not be initialized statically. The following example will NOT work:
char buff[] = "This is illegal and will not function";
31.2 Using PSRAM for dynamic allocations
PSRAM can also be used as a heap for dynamic allocations using pmalloc and pcalloc. These calls function exactly
like normal malloc and calloc except they allocate space from PSRAM.
95
Arduino-Pico Documentation, Release 4.4.3
Simply replace a malloc or calloc with pmalloc or pcalloc to use the PSRAM heap. Other calls, such as free
and realloc “just work” and do not need to be modified (they check where the passed-in pointer resides and do the
right thing automatically).
For example, to create and modify large buffer in PSRAM:
void ex() {
int *buff;
// Ignoring OOM error conditions in example for brevity
buff = (int *)pmalloc(10000 * sizeof(*buff));
// Something happened and we need more space, so...
buff = (int *)realloc(buff, 20000 * sizeof(*buff)); // buff now has 20K elements
for (int i = 0; i < 20000; i++) {
buff[i] = i;
}
// Do some work, now we're done
free(buff);
}
C++ objects can be allocated in PSRAM using “placement new” constructors. Note that this will only place immediate
object data in PSRAM: if the object creates any other objects via new those objects will be placed in normal RAM
unless the object also uses placement new constructors.
31.3 Checking on PSRAM space
The rp2040 helper object has the following calls to return the state of the PSRAM heap with the following calls, similar
to the normal RAM heap:
31.3.1 int rp2040.getPSRAMSize()
Return the total size of the attached PSRAM chip. This is the RAW space and does not take into account any allocations
for static variables or dynamic allocations. (i.e. it will return 1, 2, 4, 8, or 16MV depending on the chip).
31.3.2 int rp2040.getTotalPSRAMHeap()
Returns the total PSRAM heap (free and used) available or used for pmalloc allocations.
31.3.3 int rp2040.getUsedPSRAMHeap()
Returns the total used bytes (including any overhead) of the PSRAM heap.
31.3.4 int getFreePSRAMHeap()
Returns the total free bytes in the PSRAM heap. (Note that this may include multiple non-contiguous chunks, so this
is not always the maximum block size that can be allocated.)
96 Chapter 31. RP2350 PSRAM Support
CHAPTER
THIRTYTWO
BLUETOOTH ON PICOW SUPPORT
As of the Pico-SDK version 1.5.0, the PicoW has BETA Bluetooth support.
32.1 Enabling Bluetooth
To enable Bluetooth (BT), use the Tools->IP/Bluetooth Stack menu. It requires around 80KB of flash and 20KB
of RAM when enabled.
Both Bluetooth Classic and BluetoothBLE are enabled in btstack_config.h.
32.2 Included Bluetooth Libraries
You may use the KeyboardBT, MouseBT, or JoystickBT to emulate a Bluetooth Classic HID device using the same
API as their USB versions.
You may use the KeyboardBLE, MouseBLE, or JoystickBLE to emulate a Bluetooth Low Energy (BLE) HID device
using the same API as their USB versions.
The SerialBT library implements a very simple SPP (Serial Port Profile) Serial-compatible port.
Connect and use Bluetooth peripherals with the PicoW using the BluetoothHIDMaster library.
BluetoothAudio (A2DP) is also supported, both sink and source.
32.3 Writing Custom Bluetooth Applications
You may also write full applications using the BTStack standard callback method, but please be aware that the
Raspberry Pi team has built an interrupt-driven version of the BT execute loop, so there is no need to actually call
btstack_run_loop_execute because the async_context handler will do it for you.
There is no need to call cyw43_arch_init in your code, either, as that is part of the PicoW variant booting process.
For many BTStack examples, you simply need call the included btstack_main() and make sure that
hci_power_control(HCI_POWER_ON); is called afterwards to start processing (in the background).
You will also need to acquire the BT async_context system lock before calling any BTStack APIs.
__lockBluetooth and unlockBluetooth are provided in the PicoW variant code.
Note that if you need to modify the system btstack_config.h file, do so in the tools/libpico directory and rebuild
the Pico SDK static library. Otherwise the change will not take effect in the precompiled code, leading to really bad
behavior.
97
Arduino-Pico Documentation, Release 4.4.3
98 Chapter 32. Bluetooth on PicoW Support
CHAPTER
THIRTYTHREE
BLUETOOTH HID MASTER
The PicoW can connect to a Bluetooth Classic or Bluetooth BLE keyboard, mouse, or joystick and receive input events
from it. As opposed to the Keyboard, Mouse, and Joystick libraries, which make the PicoW into a peripheral others
can use, this lets the PicoW use the same kinds of peripherals in a master rols.
33.1 BTDeviceInfo Class
The BluetoothHCI class implements a scanning function for classic and BLE devices and can return a std::vector
of discovered devices to an application. Iterate over the list using any of the STL iteration methods (i.e. for (auto e
: list)). The elements of this list are BTDeviceInfo objects which have the following member functions:
BTDeviceInfo::deviceClass() returns the Bluetooth BLE UUID or the Blustooth device class for the device. This
specifies the general class of the device (keyboard, mouse, etc.).
BTDeviceInfo::address() and BTDeviceInfo::addressString() return the Bluetooth address as a binary array
or a string that can be used to print.
BTDeviceInfo::addressType() returns whether the BLE address is random or not, and is not generally needed by
a user application.
BTDeviceInfo::rssi() returns an approximate dB level for the device. Less negative is stronger signal.
BTDeviceInfo::name() returns the advertised name of the device, if present. Some devices or scans do not return
names for valid devices.
33.2 BluetoothHCI Class
The BluetoothHCI class is responsible for scanning for devices and the lower-level housekeeping for a master-
mode Bluetooth application. Most applications will not need to access it directly, but it can be used to dis-
cover nearby BT devices. As part of the application Bluetooth setup, call BluetoothHCI::install() and then
BluetoothHCI::begin() to start BT processing. Your application is still responsible for all the non-default HCI
initialization and customization. See the BluetoothScanner.ino example for more info.
33.3 BluetoothHIDMaster Operation
Most applications will use the BluetoothHIDMaster class and, after connecting, receive callbacks from the Bluetooth
stack when input events (key presses, mouse moves, button mashes) occur.
Application flow will generally be: 1. Install the appropriate callbacks using the
BluetoothHIDMaster::onXXX methods 2. Start the Bluetooth processing with BluetoothHIDMaster::begin()
or BluetoothHIDMaster::beginBLE() 3. Connect to the first device found with
BluetoothHIDMaster::connectXXX() and start receiving callbacks. 4. Callbacks will come at interrupt
time and set global state variables, which the main loop() will process
99
Arduino-Pico Documentation, Release 4.4.3
33.4 Callback Event Handlers
The main application is informed of new inputs via a standard callback mechanism. These callbacks run at interrupt
time and should not do significant work, delay(), or allocate or free memory. The most common way of handling this
is setting global volatile flags and variables that the main loop() will poll and process.
33.4.1 Mouse Callbacks
The BluetoothHIDMaster::onMouseMove callback gets the delta X, Y, and wheel reported by the device. The
BluetoothHIDMaster::onMouseButton gets a button number and state (up or down) and will be called each time
an individual button is reported changed by the mouse.
void mouseMoveCB(void *cbdata, int dx, int dy, int dw) {
// Process the deltas, adjust global mouse state
}
void mouseButtonCB(void *cbdata, int butt, bool down) {
// Set the global button array with this new info
}
33.4.2 Meyboard Callbacks
The BluetoothHIDMaster::onKeyDown` callback receives the raw HID key (NOT ASCII) sent by the device on
a key press while BluetoothHIDMaster::onKeyUp` gets the same when a key is released. Note that up to 6
keys can be pressed at any one time. For media keys (“consumer keys” in the USB HID documentation) the
BluetoothHIDMaster::onConsumerKeyDown and BluetoothHIDMaster::onConsumerKeyUp perform the same
function (and receive the consumer key IDs defined by the USB HID spec and not ASCII).
To convert the key press and release (including SHIFT handling), use a HIDKeyStream object. Simply write the raw
HID key and the up/down state to the stream and read back the ASCII for use in an application.
HIDKeyStream keystream;
void keyDownCB(void *cbdata, int key) {
keystream.write((uint8_t )key);
keystream.write((uint8_t) true); // Keystream now has 1 ASCII character to read out␣
˓→and use
char ascii = keystream.read();
// ....
}
void keyUpCB(void *cbdata, int key) {
// Normally don't do anything on this, the character was read in the keyDownCB
}
void consumerKeyDownCB(void *cbdata, int key) {
// switch(key) and use cases from the USB Consumer Key page
}
void consumerKeyUpCB(void *cbdata, int key) {
// switch(key) and use cases from the USB Consumer Key page
}
100 Chapter 33. Bluetooth HID Master
Arduino-Pico Documentation, Release 4.4.3
33.4.3 Joystick Callbacks
A single BluetoothHIDMaster::onJoystick callback gets activated every time a report from a joystick is processed.
It receives (potentially, if supported by the device) 4 analog axes, one 8-way digital hat switch position, and up to 32
button states at a time.
void joystickCB(void *cbdata, int x, int y, int z, int rz, uint8_t hat, uint32_t␣
˓→buttons) {
// HAT 0 = UP and continues clockwise. If no hat direction it is set to 0x0f.
// Use "buttons & (1 << buttonNumber)" to look at the individual button states
// ...
}
33.4.4 PianoKeyboard Example
See the PianoKeyboard.ino and PianoKeyboardBLE.ino examples for more information on callback operation.
33.4.5 BluetoothHIDMaster::onXXX Callback Installers
void BluetoothHIDMaster::onMouseMove(void (*)(void *, int, int, int), void *cbData =␣
˓→nullptr);
void BluetoothHIDMaster::onMouseButton(void (*)(void *, int, bool), void *cbData =␣
˓→nullptr);
void BluetoothHIDMaster::onKeyDown(void (*)(void *, int), void *cbData = nullptr);
void BluetoothHIDMaster::onKeyUp(void (*)(void *, int), void *cbData = nullptr);
void BluetoothHIDMaster::onConsumerKeyDown(void (*)(void *, int), void *cbData =␣
˓→nullptr);
void BluetoothHIDMaster::onConsumerKeyUp(void (*)(void *, int), void *cbData = nullptr);
void BluetoothHIDMaster::onJoystick(void (*)(void *, int, int, int, int, uint8_t, uint32_
˓→t), void *cbData = nullptr);
33.5 BluetoothHIDMaster Class
33.5.1 bool BluetoothHIDMaster::begin()
Installs and configures the Bluetooth Classic stack and starts processing events. No connections are made at this point.
When running in Classic mode, no BLE devices can be detected or used.
33.5.2 bool BluetoothHIDMaster::begin(const char *BLEName)
Installs and configures the Bluetooth BLE stack and starts processing events. No connections are made at this point.
When running in BLE mode, no Classic devices can be detected or used.
33.5.3 bool BluetoothHIDMaster::connected()
Returns if the Bluetooth stack is up and running and a connection to a device is currently active.
33.5.4 void BluetoothHIDMaster::end()
Disables the Bluetooth stack. Note that with the current Bluetooth implementation restarting the stack (i.e. calling
begin() after end()) is not stable and will not work. Consider storing state and rebooting completely if this is
necessary.
33.5. BluetoothHIDMaster Class 101
Arduino-Pico Documentation, Release 4.4.3
33.5.5 bool BluetoothHIDMaster::running()
Returns if the Bluetooth stack is running at all. Does not indicate if there is an active connection or not.
33.5.6 bool BluetoothHIDMaster::hciRunning()
Returns if the Bluetooth stack has passed the initial HCI start up phase. Until this returns true no Bluetooth operations
can be performed.
33.5.7 std::vector<BTDeviceInfo> BluetoothHIDMaster::scan(uint32_t mask, int
scanTimeSec, bool async)
Passes through the BluetoothHCI::scan() function to manually scan for a list of nearby devices. If you want to
connect to the first found device, this is not needed.
33.5.8 bool BluetoothHIDMaster::connect(const uint8_t *addr)
Start the connection process to the Bluetooth Classic device with the given MAC. Note that this returns immediately,
but it may take several seconds until connected() reports that the connection has been established.
33.5.9 bool BluetoothHIDMaster::connectKeyboard(), connectMouse(), connec-
tJoystick(), connectAny()
Connect to the first found specified Bluetooth Classic device type (or any HID device) in pairing mode. No need to
call scan() or have an address.
33.5.10 bool BluetoothHIDMaster::connectBLE(const uint8_t *addr, int addrType)
Start the connection process to the Bluetooth BLE device with the given MAC. Note that this returns immediately, but
it may take several seconds until connected() reports that the connection has been established.
33.5.11 bool BluetoothHIDMaster::connectBLE()
Connect to the first found BLE device that has a HID service UUID (keyboard, mouse, or joystick)
33.5.12 bool BluetoothHIDMaster::disconnect()
Shuts down the connection to the currently connected device.
33.5.13 void BluetoothHIDMaster::clearPairing()
Erases all Bluetooth keys from memory. This effectively “forgets” all pairing between devices and can help avoid issues
with the beta Bluetooth stack in the SDK.
102 Chapter 33. Bluetooth HID Master
CHAPTER
THIRTYFOUR
BLUETOOTH AUDIO (A2DP SOURCE AND SINK)
The PicoW can be used as a Bluetooth Audio sink or source with the BluetoothAudio class. Operation is generally
handled “automatically” in the background so while the audio is playing or streaming the main application can perform
other operations (like displaying playback info, polling buttons for controls, etc.)
#include <BluetoothAudio.h>
...
Note about CPU usage: Bluetooth SBC audio is a compressed format. That means that it takes non-trivial amounts
of CPU to compress on send, or decompress on receive. Transmitting precompressed audio from, say, MP3 or AAC,
requires first decompressing the source file into raw PCM and then re-compressing them in the SBC format. You may
want to consider overclocking in this case to avoid underflow.
34.1 A2DPSink
This class implements slave sink-mode operation with player control (play, pause, etc.) and can play the received and
decoded SBC audio to PWMAudio, I2S, or a user-created BluetoothAudioConsumer` class.
The A2DPSink.ino example demonstrates turning a PicoW into a Bluetooth headset with PWMAudio.
34.2 A2DPSource
This class implements a master source-mode SBC Bluetooth A2DP audio connection which transmits audio using the
standard Stream interface (like I2S or PWMAudio. The main application connects to a Bluetooth speaker and then
writes samples into a buffer that’s automatically transmitted behind the scenes.
The A2DPSource.ino example shows how to connect to a Bluetooth speaker, transmit data, and respond to commands
from the speaker.
103
Arduino-Pico Documentation, Release 4.4.3
104 Chapter 34. Bluetooth Audio (A2DP Source and Sink)
CHAPTER
THIRTYFIVE
SINGLEFILEDRIVE
USB drive mode is supported through the SingleFileDrive class which allows the Pico to emulate a FAT-formatted
USB stick while preserving the onboard LittleFS filesystem. A single file can be exported this way without needing to
use FAT as the onboard filesystem (FAT is not appropriate for flash-based devices without complicated wear leveling
because of the update frequency of the FAT tables).
This emulation is very simple and only allows for the reading of the single file, and deleting it.
35.1 Callbacks, Interrupt Safety, and File Operations
The SingleFileDrive library allows your application to get a callback when a PC attempts to mount or unmount the
Pico as a drive. Your app can also get a callback if the user attempts to delete the file (but your sketch does not actually
need to delete the file, it’s up to you).
Note that when the USB drive is mounted by a PC it is not safe for your main sketch to make changes to the LittleFS
filesystem or the file being exported. So, normally, your onPlug callback will set a flag letting your application know
not to touch the filesystem, with the onUnplug callback clearing this flag.
Also, because the USB port can be connected at any time, it is important to disable interrupts using noInterrupts()
before writing to a file you will be exporting (and restoring them with interrupts() afterwards). It is also important
to close() the file after each update, or the on-flash version the SingleFileDrive will attempt to export may not be
up to date causing issues later on.
See the included DataLoggerUSB sketch for an example of working with these limitations.
35.2 Using SingleFileDrive
Implementing the drive requires including the header file, starting LittleFS, defining your callbacks, and telling the
library what file to export. No polling or other calls are required outside of your setup(). (Note that the callback
routines allow for a parameter to be passed to them, but in most cases this can be safely ignored.)
#include <LittleFS.h>
#include <SingleFileDrive.h>
void myPlugCB(uint32_t data) {
// Tell my app not to write to flash, we're connected
}
void myUnplugCB(uint32_t data) {
// I can start writing to flash again
}
(continues on next page)
105
Arduino-Pico Documentation, Release 4.4.3
(continued from previous page)
void myDeleteDB(uint32_t data) {
// Maybe LittleFS.remove("myfile.txt")? or do nothing
}
void setup() {
LittleFS.begin();
singleFileDrive.onPlug(myPlugCB);
singleFileDrive.onUnplug(myUnplugCB);
singleFileDrive.onDelete(myDeleteCB);
singleFileDrive.begin("littlefsfile.csv", "Data Recorder.csv");
// ... rest of setup ...
}
void loop() {
// Take some measurements, delay, etc.
if (okay-to-write) {
noInterrupts();
File f = LittleFS.open("littlefsfile.csv", "a");
f.printf("%d,%d,%d\n", data1, data2, data3);
f.close();
interrupts();
}
}
106 Chapter 35. SingleFileDrive
CHAPTER
THIRTYSIX
FATFSUSB
When the onboard flash memory is used as a FatFS filesystem, the FatFSUSB can be used to allow exporting it to a
PC as a standard memory stick. The PC can then access, add, and remove files as if the Pico was a USB memory stick,
and upon ejection the Pico can access any new files just as if it made them itself.
(Note, if you are using LittleFS then you need to use SingleFileDrive to export a single file, not this class, because
the PC does not understand the LittleFS disk format.)
36.1 Callbacks, Interrupt Safety, and File Operations
The FatFSUSB library allows your application to get a callback when a PC attempts to mount or unmount the Pico as
a FAT drive.
When the drive is being used by the Pico (i.e. any File is open for read or write, the FatFS is not end() -ed and still
mounted, etc.) the host may not access it. Conversely, while the host PC is connected to the drive no FatFS access by
the Pico is allowed.
Your driveReady callback will be called when the PC attempts to mount the drive. If you have any files open, then
this callback can report back that the drive is not yet ready. When you complete file processing, the PC can re-attempt
to mount the drive and your callback can return true .
The onPlug callback will generally FatFS.end() and set a global flag letting your application know not to touch the
filesystem until the flag is cleared by the onUnplug callback (which will also do a FatFS.begin() call).
Failing to close all files and FatFS.end() before granting the PC access to flash memory will result in corruption.
FAT does not allow multiple writers to access the same drive. Even mounting and only reading files from the PC may
cause hidden writes (things like access time, etc.) which would also cause corruption.
See the included Listfiles-USB sketch for an example of working with these limitations.
107
Arduino-Pico Documentation, Release 4.4.3
108 Chapter 36. FatFSUSB
CHAPTER
THIRTYSEVEN
FREERTOS SMP
The SMP (multicore) port of FreeRTOS is included with the core. This allows complex task operations and real
preemptive multithreading in your sketches. While the setup1 and loop1 way of multitasking is simplest for most
folks, FreeRTOS is much more powerful.
37.1 Enabling FreeRTOS
To enable FreeRTOS, simply add
#include <FreeRTOS.h>
to your sketch and it will be included and enabled automatically.
37.2 Configuration and Predefined Tasks
FreeRTOS is configured with 8 priority levels (0 through 7) and a process for setup()/loop(), setup1()/loop1(),
and the USB port will be created. The task quantum is 1 millisecond (i.e. 1,000 switches per second).
setup() and loop() are assigned to only run on core 0, while setup1() and loop1() only run in core 1 in this
mode, the same as the default multithreading mode.
You can launch and manage additional processes using the standard FreeRTOS routines.
delay() and yield() free the CPU for other tasks, while delayMicroseconds() does not.
37.3 Caveats
While the core now supports FreeRTOS, most (probably all) Arduino libraries were not written to support preemptive
multithreading. This means that all calls to a particular library should be made from a single task.
In particular, the LittleFS and SDFS libraries can not be called from different threads. Do all File operations from
a single thread or else undefined behavior (aka strange crashes or data corruption) can occur.
37.4 More Information
For full FreeRTOS documentation look at FreeRTOS.org and FreeRTOS SMP support.
109
Arduino-Pico Documentation, Release 4.4.3
110 Chapter 37. FreeRTOS SMP
CHAPTER
THIRTYEIGHT
WIFI (RASPBERRY PI PICO W) SUPPORT
WiFi is supported on the Raspberry Pi Pico W by selecting the “Raspberry Pi Pico W” board in the Boards Manager.
It is generally compatible with the Arduino WiFi library and the ESP8266 Arduino WiFi library.
Enable WiFi support by selecting the Raspberry Pi Pico W board in the IDE and adding #include <WiFi.h> in your
sketch.
38.1 Supported Features
• WiFi connection (Open, WPA/WPA2)
– Static IP or dynamic DHCP supported
– Station Mode (STA, connects to an existing network)
– Access Point Mode (AP, creates its own wireless network) with 4 clients
• WiFi Scanning and Reporting
– See the ScanNetworks.ino example to better understand the process.
38.2 Important Information
Please note that WiFi on the Pico W is a work-in-progress and there are some important caveats:
• Adding WiFi increases flash usage by over 220KB
– There is a 220KB binary firmware blob for the WiFi chip (CYW43-series) which the Pico W uses, even to
control the onboard LED.
• Adding WiFi increases RAM usage by ~40KB.
– LWIP, the TCP/IP driver, requires preallocated buffers to allow it to run in non-polling mode (i.e. packets
can be sent and received in the background without the application needing to explicitly do anything).
• The WiFi driver is a little limited as of now, but fully functional for sending and receiving data
– Extensible Authentication Protocol (EAP) is not supported
– Combined STA/AP mode is not supported
• Multicore is supported, but only core 0 may run WiFi related code.
– FreeRTOS is supported only on core 0 and from within setup and loop, not tasks, due to the requirement
for a very different LWIP implementation. PRs always appreciated!
– LEAmDNS (MDNS) is not supported in FreeRTOS due to internal IRQ-time memory allocations. Instead,
use the SimpleMDNS library ( #include <SimpleMDNS.h> ) which has no such allocations.
111
Arduino-Pico Documentation, Release 4.4.3
The WiFi library borrows much work from the ESP8266 Arduino Core , especially the WiFiClient and WiFiServer
classes.
38.3 Special Thanks
Special thanks to:
• @todbot for donating one of his Pico W boards to the effort
• @d-a-v for much patient explanation about LWIP internals
• The whole ESP8266 Arduino team for their network classes
• Adafruit Industries for their kind donation
112 Chapter 38. WiFi (Raspberry Pi Pico W) Support
CHAPTER
THIRTYNINE
ETHERNETLWIP (WIRED ETHERNET) SUPPORT
Wired Ethernet interfaces are supported for all the internal networking libraries (WiFiClient, WiFiClientSecure,
WiFiServer, WiFiServerSecure, WiFiUDP, WebServer, Updater, HTTPClient, etc.).
Using these wired interfaces is very similar to using the Pico-W WiFi so most examples in the core only require minor
modifications to use a wired interface.
39.1 Supported Wired Ethernet Modules
• Wiznet W5100(s)
• Wiznet W5500
• ENC28J60
39.2 Enabling Wired Ethernet
Simply replace the WiFi include at the top with:
#include <W5500lwIP.h> // Or W5100lwIP.h or ENC28J60.h
And add a global Ethernet object of the same type:
Wiznet5500lwIP eth(1); // Parameter is the Chip Select pin
In your setup() you may adjust the SPI pins you’re using to match your hardware (be sure they are legal for the
RP2040!), or skip this if you’re using the default ones:
void setup() {
SPI.setRX(0);
SPI.setCS(1);
SPI.setSCK(2);
SPI.setTX(3);
....
}
And finally replace the WiFi.begin() and WiFi.connected() calls with eth.begin() and eth.connected():
void setup() {
....
// WiFi.begin(SSID, PASS)
eth.begin();
(continues on next page)
113
Arduino-Pico Documentation, Release 4.4.3
(continued from previous page)
//while (!WiFi.connected()) {
while (!eth.connected()) {
Serial.print(".");
}
Serial.print("IP address: ");
//Serial.println(WiFi.localIP());
Serial.println(eth.localIP());
....
}
39.3 Adjusting LWIP Polling
LWIP operates in a polling mode for the wired Ethernet devices. By default it will run every 20ms, meaning that on
average it will take half that time (10ms) before a packet received in the Ethernet module is received and operated upon
by the Pico. This gives very low CPU utilization but in some cases this latency can affect performance.
Adding a call to lwipPollingPeriod(XXX) (where XXXX is the polling period in milliseconds) can adjust this setting
on the fly. Note that if you set it too low, the Pico may not have enough time to service the Ethernet port before the
timer fires again, leading to a lock up and hang.
39.4 Using Interrupt-Driven Handling
The WizNet and ENC28J60 devices support generating an interrupt when a packet is received, removing the need
for polling and decreasing latency. Simply specify the SPI object to use and the interrupt pin when instantiating the
Ethernet object:
#include <W5100lwIP.h>
Wiznet5100lwIP eth(SS /* Chip Select*/, SPI /* SPI interface */, 17 /* Interrupt GPIO */␣
˓→);
39.5 Adjusting SPI Speed
By default a 4MHz clock will be used to clock data into and out of the Ethernet module. Depending on the module
and your wiring, a higher SPI clock may increase performance (but too high of a clock will cause communications
problems or hangs).
This value may be adjusted using the eth.setSPISpeed(hz) call before starting the device. (You may also use
custom SPISettings instead via eth.setSPISettings(spis)`)
For example, to set the W5500 to use a 30MHZ clock:
#include <W5500lwIP.h>
Wiznet5500lwIP eth(1);
void setup() {
eth.setSPISpeed(30000000);
lwipPollingPeriod(3);
...
(continues on next page)
114 Chapter 39. EthernetLWIP (Wired Ethernet) Support
Arduino-Pico Documentation, Release 4.4.3
(continued from previous page)
eth.begin();
...
}
39.6 Using the WIZnet W5100S-EVB-Pico
You can use the onboard Ethernet chip with these drivers, in interrupt mode, by utilizing the following options:
#include <W5100lwIP.h>
Wiznet5100lwIP eth(17, SPI, 21); // Note chip select is **17**
void setup() {
// Set SPI to the onboard Wiznet chip
SPI.setRX(16);
SPI.setCS(17);
SPI.setSCK(18);
SPI.setTX(19);
...
eth.begin();
...
}
39.7 Example Code
The following examples allow switching between WiFi and Ethernet:
• WebServer/AdvancedWebServer
• HTTPClient/BasicHTTPSClient
39.8 Caveats
The same restrictions for WiFi apply to these Ethernet classes, namely:
• Only core 0 may run any networking related code.
• In FreeRTOS, only the setup and loop task can call networking libraries, not any tasks.
39.9 Special Thanks
• LWIPEthernet classes come from the ESP8266 Arduino team
• Individual Ethernet drivers were written by Nicholas Humfrey
39.6. Using the WIZnet W5100S-EVB-Pico 115
Arduino-Pico Documentation, Release 4.4.3
116 Chapter 39. EthernetLWIP (Wired Ethernet) Support
CHAPTER
FORTY
WIFICLIENT
Methods documented for Client in Arduino
1. WiFiClient()
2. connected()
3. connect()
4. write()
5. print()
6. println()
7. available()
8. read()
9. flush()
10. stop()
Methods and properties described further down are specific to ESP8266. They are not covered in Arduino WiFi library
documentation. Before they are fully documented please refer to information below.
40.1 flush and stop
flush(timeoutMs) and stop(timeoutMs) both have now an optional argument: timeout in millisecond, and both
return a boolean.
Default input value 0 means that effective value is left at the discretion of the implementer.
flush() returning true indicates that output data have effectively been sent, and false that a timeout has occurred.
stop() returns false in case of an issue when closing the client (for instance a timed-out flush). Depending on
implementation, its parameter can be passed to flush().
40.2 setNoDelay
setNoDelay(nodelay)
With nodelay set to true, this function will to disable Nagle algorithm.
This algorithm is intended to reduce TCP/IP traffic of small packets sent over the network by combining a number
of small outgoing messages, and sending them all at once. The downside of such approach is effectively delaying
individual messages until a big enough packet is assembled.
Example:
117
Arduino-Pico Documentation, Release 4.4.3
client.setNoDelay(true);
40.3 getNoDelay
Returns whether NoDelay is enabled or not for the current connection.
40.4 setSync
This is an experimental API that will set the client in synchronized mode. In this mode, every write() is flushed. It
means that after a call to write(), data are ensured to be received where they went sent to (that is flush semantic).
When set to true in WiFiClient implementation,
• It slows down transfers, and implicitly disable the Nagle algorithm.
• It also allows to avoid a temporary copy of data that otherwise consumes at most TCP_SND_BUF = (2 * MSS) bytes
per connection,
40.5 getSync
Returns whether Sync is enabled or not for the current connection.
40.6 setDefaultNoDelay and setDefaultSync
These set the default value for both setSync and setNoDelay for every future instance of WiFiClient (including
those coming from WiFiServer.available() by default).
Default values are false for both NoDelay and Sync.
This means that Nagle is enabled by default for all new connections.
40.7 getDefaultNoDelay and getDefaultSync
Return the values to be used as default for NoDelay and Sync for all future connections.
40.8 Other Function Calls
uint8_t status ()
virtual size_t write (const uint8_t *buf, size_t size)
size_t write_P (PGM_P buf, size_t size)
size_t write (Stream &stream)
size_t write (Stream &stream, size_t unitSize) __attribute__((deprecated))
virtual int read (uint8_t *buf, size_t size)
virtual int peek ()
virtual size_t peekBytes (uint8_t *buffer, size_t length)
size_t peekBytes (char *buffer, size_t length)
virtual operator bool ()
IPAddress remoteIP ()
uint16_t remotePort ()
IPAddress localIP ()
uint16_t localPort ()
118 Chapter 40. WiFiClient
Arduino-Pico Documentation, Release 4.4.3
Documentation for the above functions is not yet available.
40.8. Other Function Calls 119
Arduino-Pico Documentation, Release 4.4.3
120 Chapter 40. WiFiClient
CHAPTER
FORTYONE
SERVER CLASS
Methods documented for the Server Class in Arduino
1. WiFiServer()
2. begin()
3. available()
4. write()
5. print()
6. println()
In ESP8266WiFi library the ArduinoWiFiServer class implements available and the write-to-all-clients function-
ality as described in the Arduino WiFi library reference. The PageServer example shows how available and the
write-to-all-clients works.
For most use cases the basic WiFiServer class of the ESP8266WiFi library is suitable.
Methods and properties described further down are specific to ESP8266. They are not covered in Arduino WiFi library
documentation. Before they are fully documented please refer to information below.
41.1 accept
Method accept() returns a waiting client connection. accept() is documented for the Arduino Ethernet library.
41.2 available
see accept
available in the ESP8266WiFi library’s WiFiServer class doesn’t work as documented for the Arduino WiFi library.
It works the same way as accept.
41.3 write (write to all clients) not supported
Please note that the write method on the WiFiServer object is not implemented and returns failure always. Use the
returned WiFiClient object from the WiFiServer::accept() method to communicate with individual clients. If
you need to send the exact same packets to a series of clients, your application must maintain a list of connected clients
and iterate over them manually.
121
Arduino-Pico Documentation, Release 4.4.3
41.4 setNoDelay
setNoDelay(nodelay)
With nodelay set to true, this function will to disable Nagle algorithm.
This algorithm is intended to reduce TCP/IP traffic of small packets sent over the network by combining a number
of small outgoing messages, and sending them all at once. The downside of such approach is effectively delaying
individual messages until a big enough packet is assembled.
Example:
server.begin();
server.setNoDelay(true);
By default, nodelay value will depends on global WiFiClient::getDefaultNoDelay() (currently false by default).
However, a call to wiFiServer.setNoDelay() will override NoDelay for all new WiFiClient provided by the
calling instance (wiFiServer).
41.5 Other Function Calls
bool hasClient ()
size_t hasClientData ()
bool hasMaxPendingClients ()
bool getNoDelay ()
virtual size_t write (const uint8_t *buf, size_t size)
uint8_t status ()
void close ()
void stop ()
Documentation for the above functions is not yet prepared.
122 Chapter 41. Server Class
CHAPTER
FORTYTWO
UDP CLASS
Methods documented for WiFiUDP Class in Arduino
1. begin()
2. available()
3. beginPacket()
4. endPacket()
5. write()
6. parsePacket()
7. peek()
8. read()
9. flush()
10. stop()
11. remoteIP()
12. remotePort()
123
Arduino-Pico Documentation, Release 4.4.3
124 Chapter 42. UDP Class
CHAPTER
FORTYTHREE
NETWORK TIME PROTOCOL (NTP)
NTP allows the Pico to set its internal clock using the internet, and is required for secure connections because the
certificates used have valid date stamps.
After WiFi.begin() use NTP.begin(s1) or NTP.begin(s1, s2) to use one or two NTP servers (common ones are
pool.ntp.org and time.nist.gov) .
WiFi.begin("ssid", "pass");
NTP.begin("pool.ntp.org", "time.nist.gov");
Either names or IPAddress may be used to identify the NTP server to use.
It may take seconds to minutes for the system time to be updated by NTP, depending on the server. It is often useful to
check that time(NULL) returns a sane value before continuing a sketch:
void setClock() {
NTP.begin("pool.ntp.org", "time.nist.gov");
Serial.print("Waiting for NTP time sync: ");
time_t now = time(nullptr);
while (now < 8 * 3600 * 2) {
delay(500);
Serial.print(".");
now = time(nullptr);
}
Serial.println("");
struct tm timeinfo;
gmtime_r(&now, &timeinfo);
Serial.print("Current time: ");
Serial.print(asctime(&timeinfo));
}
43.1 bool NTP.waitSet(uint32_t timeout)
This call will wait up to timeout milliseconds for the time to be set, and returns success or failure. It will also begin
NTP with a default “pool.ntp.org” server if it is not already running. Using this method, the above code becomes:
void setClock() {
NTP.begin("pool.ntp.org", "time.nist.gov");
NTP.waitSet();
time_t now = time(nullptr);
struct tm timeinfo;
(continues on next page)
125
Arduino-Pico Documentation, Release 4.4.3
(continued from previous page)
gmtime_r(&now, &timeinfo);
Serial.print("Current time: ");
Serial.print(asctime(&timeinfo));
}
43.2 bool NTP.waitSet(void (*cb)(), uint32_t timeout)
Allows for a callback that will be called every 1/10th of a second while waiting for NTP sync. For example, using
lambdas you can simply print “.”s:”
void setClock() {
NTP.begin("pool.ntp.org", "time.nist.gov");
NTP.waitSet([]() { Serial.print("."); });
time_t now = time(nullptr);
struct tm timeinfo;
gmtime_r(&now, &timeinfo);
Serial.print("Current time: ");
Serial.print(asctime(&timeinfo));
}
126 Chapter 43. Network Time Protocol (NTP)
CHAPTER
FORTYFOUR
BEARSSL WIFI CLASSES
Methods and properties described in this section are specific to the Raspberry Pi Pico W and the ESP8266. They are not
covered in Arduino WiFi library documentation. Before they are fully documented please refer to information below.
The BearSSL library (with modifications for ESP8266 compatibility and to use ROM tables whenever possible) is used
to perform all cryptography and TLS operations. The main ported repo is available on GitHub.
44.1 CPU Requirements
SSL operations take significant CPU cycles to run, so it will connect significantly slower than unprotected connections
on the Pico, but the actual data transfer rates once connected are similar.
See the section on sessions and limiting cryptographic negotiation for ways of ensuring faster modes are used.
44.2 Memory Requirements
BearSSL doesn’t perform memory allocations at runtime, but it does require allocation of memory at the beginning of
a connection. There are two memory chunks required: . A per-application secondary stack . A per-connection TLS
receive/transmit buffer plus overhead
The per-application secondary stack is approximately 7KB in size and is used for temporary variables during BearSSL
processing. Only one stack is required, and it will be allocated whenever any BearSSL::WiFiClientSecure or
BearSSL::WiFiServerSecure are instantiated. So, in the case of a global client or server, the memory will be allo-
cated before setup() is called.
The per-connection buffers are approximately 22KB in size, but in certain circumstances it can be reduced dramatically
by using MFLN or limiting message sizes. See the MLFN section below for more information.
44.3 Object Lifetimes
There are many configuration options that require passing in a pointer to an object (i.e. a pointer to a private key, or a
certificate list). In order to preserve memory, BearSSL does NOT copy the objects passed in via these pointers and as
such any pointer passed in to BearSSL needs to be preserved for the life of the client object. For example, the following
code is in error:
BearSSL::WiFiClientSecure client;
const char x509CA PROGMEM = ".......";
void setup() {
BearSSL::X509List x509(x509CA);
client.setTrustAnchor(&x509);
}
(continues on next page)
127
Arduino-Pico Documentation, Release 4.4.3
(continued from previous page)
void loop() {
client.connect("192.168.1.1", 443);
}
Because the pointer to the local object x509 no longer is valid after setup(), expect to crash in the main loop() where it
is accessed by the client object.
As a rule, either keep your objects global, use new to create them, or ensure that all objects needed live inside the same
scope as the client.
44.4 TLS and HTTPS Basics
The following discussion is only intended to give a rough idea of TLS/HTTPS(which is just HTTP over a TLS connec-
tion) and the components an application needs to manage to make a TLS connection. For more detailed information,
please check the relevant RFC 5246 and others.
TLS can be broken into two stages: verifying the identities of server (and potentially client), and then encrypting blocks
of data bidirectionally. Verifying the identity of the other partner is handled via keys encoded in X509 certificates,
optionally signed by a series of other entities.
44.5 Public and Private Keys
Cryptographic keys are required for many of the BearSSL functions. Both public and private keys are supported, with
either Elliptic Curve or RSA key support.
To generate a public or private key from an existing PEM (ASCII format) or DER (binary format), the simplest method
is to use the constructor:
BearSSL::PublicKey(const char *pemString)
... or ...
BearSSL::PublicKey(const uint8_t *derArray, size_t derLen)
Note that PROGMEM strings and arrays are natively supported by these constructors and no special *_P modes are
required. There are additional functions to identify the key type and access the underlying BearSSL proprietary types,
but they are not needed by user applications.
44.6 TLS Sessions
TLS supports the notion of a session (completely independent and different from HTTP sessions) which allow clients
to reconnect to a server without having to renegotiate encryption settings or validate X509 certificates. This can save
significant time (3-4 seconds in the case of EC keys) and can help save power by allowing the ESP8266 to sleep for a
long time, reconnect and transmit some samples using the SSL session, and then jump back to sleep quicker.
BearSSL::Session is an opaque class. Use the BearSSL::WiFiClientSecure.setSession(&BearSSLSession) method to
apply it before the first BearSSL::WiFiClientSecure.connect() and it will be updated with session parameters during the
operation of the connection. After the connection has had .close() called on it, serialize the BearSSL::Session object
to stable storage (EEPROM, RTC RAM, etc.) and restore it before trying to reconnect. See the BearSSL_Sessions
example for a detailed example.
Sessions contains additional information on the sessions API.
128 Chapter 44. BearSSL WiFi Classes
Arduino-Pico Documentation, Release 4.4.3
44.7 X.509 Certificate(s)
X509 certificates are used to identify peers in TLS connections. Normally only the server identifies itself, but the client
can also supply an X509 certificate if desired (this is often done in MQTT applications). The certificate contains many
fields, but the most interesting in our applications are the name, the public key, and potentially a chain of signing that
leads back to a trusted authority (like a global internet CA or a company-wide private certificate authority).
Any call that takes an X509 certificate can also take a list of X509 certificates, so there is no special X509 class, simply
BearSSL::X509List (which may only contain a single certificate).
Generating a certificate to be used to validate using the constructor
BearSSL::X509List(const char *pemX509);
...or...
BearSSL::X509List(const uint8_t *derCert, size_t derLen);
If you need to add additional certificates (unlikely in normal operation), the ::append() operation can be used.
44.8 Certificate Stores
The web browser you’re using to read this document keeps a list of 100s of certification authorities (CAs) worldwide
that it trusts to attest to the identity of websites.
In many cases your application will know the specific CA it needs to validate web or MQTT servers against (often just
a single, self-signing CA private to your institution). Simply load your private CA in a BearSSL::X509List and use that
as your trust anchor.
However, there are cases where you will not know beforehand which CA you will need (i.e. a user enters a website
through a keypad), and you need to keep the list of CAs just like your web browser. In those cases, you need to generate
a certificate bundle on the PC while compiling your application, upload the certs.ar bundle to LittleFS or SD when
uploading your application binary, and pass it to a BearSSL::CertStore() in order to validate TLS peers.
See the BearSSL_CertStore example for full details.
44.9 Supported Crypto
Please see the BearSSL website for detailed cryptographic information. In general, TLS 1.2, TLS 1.1, and TLS 1.0 are
supported with RSA and Elliptic Curve keys and a very rich set of hashing and symmetric encryption codes. Please
note that Elliptic Curve (EC) key operations take a significant amount of time.
44.7. X.509 Certificate(s) 129
Arduino-Pico Documentation, Release 4.4.3
130 Chapter 44. BearSSL WiFi Classes
CHAPTER
FORTYFIVE
WIFICLIENTSECURE CLASS
BearSSL::WiFiClientSecure is the object which actually handles TLS encrypted WiFi connections to a remote server
or client. It extends WiFiClient and so can be used with minimal changes to code that does unsecured communications.
45.1 Validating X509 Certificates (Am I talking to the server I think I’m
talking to?)
Prior to connecting to a server, the BearSSL::WiFiClientSecure needs to be told how to verify the identity of the other
machine. By default BearSSL will not validate any connections and will refuse to connect to any server.
There are multiple modes to tell BearSSL how to verify the identity of the remote server. See the BearSSL_Validation
example for real uses of the following methods:
45.1.1 setInsecure()
Don’t verify any X509 certificates. There is no guarantee that the server connected to is the one you think it is in this
case.
45.1.2 setKnownKey(const BearSSL::PublicKey *pk)
Assume the server is using the specific public key. This does not verify the identity of the server or the X509 certificate
it sends, it simply assumes that its public key is the one given. If the server updates its public key at a later point then
connections will fail.
45.1.3 setFingerprint(const uint8_t fp[20]) / setFingerprint(const char *fpStr)
Verify the SHA1 fingerprint of the certificate returned matches this one. If the server certificate changes, it will fail. If
an array of 20 bytes are sent in, it is assumed they are the binary SHA1 values. If a char* string is passed in, it is parsed
as a series of human-readable hex values separated by spaces or colons (e.g. setFingerprint(“00:01:02:03:. . . :1f”);)
This fingerprint is calculated on the raw X509 certificate served by the server. In very rare cases, these certificates have
certain encodings which should be normalized before taking a fingerprint (but in order to preserve memory BearSSL
does not do this normalization since it would need RAM for an entire copy of the cert), and the fingerprint BearSSL
calculates will not match the fingerprint OpenSSL calculates. In this case, you can enable SSL debugging and get
a dump of BearSSL’s calculated fingerprint and use that one in your code, or use full certificate validation. See the
original issue and debug here.
45.1.4 setTrustAnchors(BearSSL::X509List *ta)
Use the passed-in certificate(s) as a trust anchor, accepting remote certificates signed by any of these. If you have many
trust anchors it may make sense to use a BearSSL::CertStore because it will only require RAM for a single trust anchor
(while the setTrustAnchors call requires memory for all certificates in the list).
131
Arduino-Pico Documentation, Release 4.4.3
45.1.5 setX509Time(time_t now)
For setTrustAnchors and CertStore , the current time (set via SNTP) is used to verify the certificate against the list, so
SNTP must be enabled and functioning before the connection is attempted. If you cannot use SNTP for some reason,
you can manually set the “present time” that BearSSL will use to validate a certificate with this call where now is
standard UNIX time.
45.2 Client Certificates (Proving I’m who I say I am to the server)
TLS servers can request that a client identify themselves with an X509 certificate signed by a trust anchor it honors (i.e.
a global TA or a private CA). This is commonly done for applications like MQTT. By default the client doesn’t send a
certificate, and in cases where a certificate is required the server will disconnect and no connection will be possible.
45.2.1 setClientRSACert / setClientECCert
Sets a client certificate to send to a TLS server that requests one. It should be called before connect() to add a certificate
to the client in case the server requests it. Note that certificates include both a certificate and a private key. Both should
be provided to you by your certificate generator. Elliptic Curve (EC) keys require additional information, as shown in
the prototype.
45.3 MFLN or Maximum Fragment Length Negotiation (Saving RAM)
Because TLS was developed on systems with many megabytes of memory, they require by default a 16KB buffer for
receive and transmit. That’s enormous for the ESP8266, which has only around 40KB total heap available.
We can (and do) minimize the transmission buffer down to slightly more than 512 bytes to save memory, since BearSSL
can internally ensure transmissions larger than that are broken up into smaller chunks that do fit. But that still leaves
the 16KB receive buffer requirement since we cannot in general guarantee the TLS peer will send in smaller chunks.
TLS 1.2 added MFLN, which lets a client negotiate smaller buffers with a server and reduce the memory requirements
on the ESP8266. Unfortunately, BearSSL needs to know the buffer sizes before it begins connection, so applications
that want to use smaller buffers need to check the remote server’s support before connect() .
45.3.1 probeMaxFragmentLength(host, port, len)
Use one of these calls before connection to determine if a specific fragment length is supported (len must be a power
of two from 512 to 4096, per the specification). This does not initiate a SSL connection, it simply opens a TCP port
and performs a trial handshake to check support.
45.3.2 setBufferSizes(int recv, int xmit)
Once you have verified (or know beforehand) that MFLN is supported you can use this call to set the size of memory
buffers allocated by the connection object. This must be called before connect() or it will be ignored.
In certain applications where the TLS server does not support MFLN (not many do as of this writing as it is relatively
new to OpenSSL), but you control both the ESP8266 and the server to which it is communicating, you may still be able
to setBufferSizes() smaller if you guarantee no chunk of data will overflow those buffers.
45.3.3 bool getMFLNStatus()
After a successful connection, this method returns whether or not MFLN negotiation succeeded or not. If it did not
succeed, and you reduced the receive buffer with setBufferSizes then you may experience reception errors if the server
attempts to send messages larger than your receive buffer.
132 Chapter 45. WiFiClientSecure Class
Arduino-Pico Documentation, Release 4.4.3
45.4 Sessions (Resuming connections fast)
45.4.1 setSession(BearSSL::Session &sess)
If you are connecting to a server repeatedly in a fixed time period (usually 30 or 60 minutes, but normally configurable
at the server), a TLS session can be used to cache crypto settings and speed up connections significantly.
45.5 Errors
BearSSL can fail in many more unique and interesting ways. Use these calls to get more information when something
fails.
45.5.1 getLastSSLError(char *dest = NULL, size_t len = 0)
Returns the last BearSSL error code encountered and optionally set a user-allocated buffer to a human-readable form of
the error. To only get the last error integer code, just call without any parameters (int errCode = getLastSSLError();).
45.6 Limiting Ciphers (New connections faster)
There is very rarely reason to use these calls, but they are available.
45.6.1 setCiphers()
Takes an array (in PROGMEM is valid) or a std::vector of 16-bit BearSSL cipher identifiers and restricts BearSSL to
only use them. If the server requires a different cipher, then connection will fail. Generally this is not useful except in
cases where you want to connect to servers using a specific cipher. See the BearSSL headers for more information on
the supported ciphers.
45.6.2 setCiphersLessSecure()
Helper function which essentially limits BearSSL to less secure ciphers than it would natively choose, but they may be
helpful and faster if your server depended on specific crypto options.
45.7 Limiting TLS(SSL) Versions
By default, BearSSL will connect with TLS 1.0, TLS 1.1, or TLS 1.2 protocols (depending on the request of the remote
side). If you want to limit to a subset, use the following call:
45.7.1 setSSLVersion(uint32_t min, uint32_t max)
Valid values for min and max are BR_TLS10, BR_TLS11, BR_TLS12. Min and max may be set to the same value if
only a single TLS version is desired.
45.8 ESP32 Compatibility
Simple ESP32 WiFiClientSecure compatibility is built-in, allow for some sketches to run without any modification.
The following methods are implemented:
void setCACert(const char *rootCA);
void setCertificate(const char *client_ca);
void setPrivateKey(const char *private_key);
(continues on next page)
45.4. Sessions (Resuming connections fast) 133
Arduino-Pico Documentation, Release 4.4.3
(continued from previous page)
bool loadCACert(Stream& stream, size_t size);
bool loadCertificate(Stream& stream, size_t size);
bool loadPrivateKey(Stream& stream, size_t size);
int connect(IPAddress ip, uint16_t port, int32_t timeout);
int connect(const char *host, uint16_t port, int32_t timeout);
int connect(IPAddress ip, uint16_t port, const char *rootCABuff, const char *cli_cert,␣
˓→const char *cli_key);
int connect(const char *host, uint16_t port, const char *rootCABuff, const char *cli_
˓→cert, const char *cli_key);
Note that the SSL backend is very different between Arduino-Pico and ESP32-Arduino (BearSSL vs. mbedTLS). This
means that, for instance, the SSL connection will check valid dates of certificates (and hence require system time to be
set on the Pico, which is automatically done in this case).
TLS-Pre Shared Keys (PSK) is not supported by BearSSL, and hence not implemented here. Neither is ALPN.
For more advanced control, it is recommended to port to the native Pico calls which allows much more flexibility and
control.
134 Chapter 45. WiFiClientSecure Class
CHAPTER
FORTYSIX
WIFISERVERSECURE CLASS
Implements a TLS encrypted server with optional client certificate validation. See Server Class for general information
and BearSSL Secure Client Class for basic server and BearSSL concepts.
46.1 setBufferSizes(int recv, int xmit)
Similar to the BearSSL::WiFiClientSecure method, sets the receive and transmit buffer sizes. Note that servers cannot
request a buffer size from the client, so if these are shrunk and the client tries to send a chunk larger than the receive
buffer, it will always fail. Needs to be called before begin()
46.2 Setting Server Certificates
TLS servers require a certificate identifying itself and containing its public key, and a private key they will use to
encrypt information with. The application author is responsible for generating this certificate and key, either using
a self-signed generator or using a commercial certification authority. Do not reuse the certificates included in the
examples provided.
This example command will generate a RSA 2048-bit key and certificate:
openssl req -x509 -nodes -newkey rsa:2048 -keyout key.pem -out cert.pem -days 4096
Again, it is up to the application author to generate this certificate and key and keep the private key safe and private.
46.2.1 setRSACert(const BearSSL::X509List *chain, const BearSSL::PrivateKey
*sk)
Sets a RSA certificate and key to be used by the server when connections are received. Needs to be called before begin()
46.2.2 setECCert(const BearSSL::X509List *chain, unsigned cert_issuer_key_type,
const BearSSL::PrivateKey *sk)
Sets an elliptic curve certificate and key for the server. Needs to be called before begin().
46.3 Client sessions (Resuming connections fast)
The TLS handshake process takes a long time because of all the back and forth between the client and the server. You
can shorten it by caching the clients’ sessions which will skip a few steps in the TLS handshake. In order for this
to work, your client also needs to cache the session. BearSSL::WiFiClientSecure can do that as well as modern web
browsers.
135
Arduino-Pico Documentation, Release 4.4.3
Here are the kind of performance improvements that you’ll be able to see for TLS handshakes with an ESP8266 with
it’s clock set at 160MHz on a network with fairly low latency:
• With an EC key of 256 bits, a request taking ~360ms without caching takes ~60ms with caching.
• With an RSA key of 2048 bits, a request taking ~1850ms without caching takes ~70ms with caching.
46.3.1 setCache(BearSSL::ServerSessions *cache)
Sets the cache for the server’s sessions. When choosing the size of the cache, remember that each client session takes
100 bytes. If you setup a cache for 10 sessions, it will take 1000 bytes. Needs to be called before begin()
When creating the cache, you can use any of the 2 available constructors:
• BearSSL::ServerSessions(ServerSession *sessions, uint32_t size): Creates a cache with the given buffer and
number of sessions.
• BearSSL::ServerSessions(uint32_t size): Dynamically allocates a cache for the given number of sessions.
46.4 Requiring Client Certificates
TLS servers can request the client to identify itself by transmitting a certificate during handshake. If the client cannot
transmit the certificate, the connection will be dropped by the server.
46.4.1 setClientTrustAnchor(const BearSSL::X509List *client_CA_ta)
Sets the trust anchor (normally a self-signing CA) that all received certificates will be verified against. Needs to be
called before begin().
136 Chapter 46. WiFiServerSecure Class
CHAPTER
FORTYSEVEN
HTTPCLIENT LIBRARY
A simple HTTP requester that can handle both HTTP and HTTPS requests is included as the HTTPClient library.
Check the examples for use under HTTP and HTTPS configurations. In general, for HTTP connections (unsecured and
very uncommon on the internet today) simply passing in a URL and performiung a GET is sufficient to transfer data.
// Error checking is left as an exercise for the reader...
HTTPClient http;
if (http.begin("http://my.server/url")) {
if (http.GET() > 0) {
String data = http.getString();
}
http.end();
}
For HTTPS connections, simply add the appropriate WiFiClientSecure calls as needed (i.e. setInsecure(),
setTrustAnchor, etc.). See the WiFiClientSecure documentation for more details.
// Error checking is left as an exercise for the reader...
HTTPClient https;
https.setInsecure(); // Use certs, but do not check their authenticity
if (https.begin("https://my.secure.server/url")) {
if (https.GET() > 0) {
String data = https.getString();
}
https.end();
}
Unlike the ESP8266 and ESP32 HTTPClient implementations it is not necessary to create a WiFiClient or
WiFiClientSecure to pass in to the HTTPClient object.
137
Arduino-Pico Documentation, Release 4.4.3
138 Chapter 47. HTTPClient Library
CHAPTER
FORTYEIGHT
OTA UPDATES
48.1 Introduction
OTA (Over the Air) update is the process of uploading firmware to a Pico using a Wi-Fi, Ethernet, or other connection
rather than a serial port. This is especially useful for WiFi enabled Picos, like the Pico W, because it lets systems be
updated remotely, without needing physical access.
OTA may be done using:
• Arduino IDE
• Web Browser
• HTTP Server
• Any other method (ZModem receive over a UART port, etc.) by using the Updater object in your sketch
The Arduino IDE option is intended primarily for the software development phase. The other two options would be
more useful after deployment, to provide the module with application updates either manually with a web browser, or
automatically using an HTTP server.
In any case, the first firmware upload has to be done over a serial port. If the OTA routines are correctly implemented
in the sketch, then all subsequent uploads may be done over the air.
By default, there is no imposed security for the OTA process. It is up to the developer to ensure that updates are allowed
only from legitimate / trusted sources. Once the update is complete, the module restarts, and the new code is executed.
The developer should ensure that the application running on the module is shut down and restarted in a safe manner.
Chapters below provide additional information regarding security and safety of OTA updates.
48.1.1 OTA Requirements
OTA requires a LittleFS partition to store firmware upgrade files. Make sure that you configure the sketch with a
filesystem large enough to handle whatever size firmware binary you expect. Updates may be compressed, minimizing
the total space needed.
48.1.2 Power Fail Safety
The update commands are all stored in flash, so a power cycle during update (except if the OTA bootloader is being
changed) should not brick the device because when power is restored the OTA bootloader will begin the process from
scratch once again.
139
Arduino-Pico Documentation, Release 4.4.3
48.1.3 Security Disclaimer
No guarantees as to the level of security provided for your application by the following methods is implied. Please
refer to the GNU LGPL license associated for this project for full disclaimers. If you do find security weaknesses,
please don’t hesitate to contact the maintainers or supply pull requests with fixes. The MD5 verification and password
protection schemes are already known to supply a very weak level of security.
48.1.4 Basic Security
The module has to be exposed wirelessly to get it updated with a new sketch. That poses a risk of the module being
violently hacked and programmed with some other code. To reduce the likelihood of being hacked, consider protecting
your uploads with a password, selecting certain OTA port, etc.
Check functionality provided with the ArduinoOTA library that may improve security:
void setPort(uint16_t port);
void setHostname(const char* hostname);
void setPassword(const char* password);
Certain basic protection is already built in and does not require any additional coding by the developer. ArduinoOTA
and espota.py use Digest-MD5 to authenticate uploads. Integrity of transferred data is verified on the Pico side using
MD5 checksum.
Make your own risk analysis and, depending on the application, decide what library functions to implement. If required,
consider implementation of other means of protection from being hacked, like exposing modules for uploads only
according to a specific schedule, triggering OTA only when the user presses a dedicated “Update” button wired to the
Pico, etc.
48.1.5 Advanced Security - Signed Updates
While the above password-based security will dissuade casual hacking attempts, it is not highly secure. For applications
where a higher level of security is needed, cryptographically signed OTA updates can be required. This uses SHA256
hashing in place of MD5 (which is known to be cryptographically broken) and RSA-2048 bit level public-key encryption
to guarantee that only the holder of a cryptographic private key can produce signed updates accepted by the OTA update
mechanisms.
Signed updates are updates whose compiled binaries are signed with a private key (held by the developer) and verified
with a public key (stored in the application and available for all to see). The signing process computes a hash of
the binary code, encrypts the hash with the developer’s private key, and appends this encrypted hash (also called a
signature) to the binary that is uploaded (via OTA, web, or HTTP server). If the code is modified or replaced in any
way by anyone except the holder of the developer’s private key, the signature will not match and the Pico will reject the
upload.
Cryptographic signing only protects against tampering with binaries delivered via OTA. If someone has physical access,
they will always be able to flash the device over the serial port. Signing also does not encrypt anything but the hash (so
that it can’t be modified), so this does not protect code inside the device: if a user has physical access they can read out
your program.
Securing your private key is paramount. The same private/public key pair that was used with the original upload
must also be used to sign later binaries. Loss of the private key associated with a binary means that you will not
be able to OTA-update any of your devices in the field. Alternatively, if someone else copies the private key, then
they will be able to use it to sign binaries which will be accepted by the Pico.
140 Chapter 48. OTA Updates
Arduino-Pico Documentation, Release 4.4.3
Signed Binary Format
The format of a signed binary is compatible with the standard binary format, and can be uploaded to a non-signed Pico
via serial or OTA without any conditions. Note, however, that once an unsigned OTA app is overwritten by this signed
version, further updates will require signing.
As shown below, the signed hash is appended to the unsigned binary, followed by the total length of the signed hash
(i.e., if the signed hash was 64 bytes, then this uint32 data segment will contain 64). This format allows for extensibility
(such as adding a CA-based validation scheme allowing multiple signing keys all based on a trust anchor). Pull requests
are always welcome. (currently it uses SHA256 with RSASSA-PKCS1-V1_5-SIGN signature scheme from RSA PKCS
#1 v1.5)
NORMAL-BINARY <SIGNATURE> <uint32 LENGTH-OF-SIGNATURE>
Signed Binary Prerequisites
OpenSSL is required to run the standard signing steps, and should be available on any UNIX-like or Windows system.
As usual, the latest stable version of OpenSSL is recommended.
Signing requires the generation of an RSA-2048 key (other bit lengths are supported as well, but 2048 is a good selection
today) using any appropriate tool. The following shell commands will generate a new public/private key pair. Run them
in the sketch directory:
openssl genrsa -out private.key 2048
openssl rsa -in private.key -outform PEM -pubout -out public.key
Automatic Signing
The simplest way of implementing signing is to use the automatic mode, which presently is only possible on Linux and
Mac due to some of the tools not being available for Windows. This mode uses the IDE to configure the source code
to enable signing verification with a given public key, and signs binaries as part of the standard build process using a
given public key.
To enable this mode, just include private.key and public.key in the sketch .ino directory. The IDE will call a helper script
(tools/signing.py) before the build begins to create a header to enable key validation using the given public key, and to
actually do the signing after the build process, generating a sketch.bin.signed file. When OTA is enabled (ArduinoOTA,
Web, or HTTP), the binary will automatically only accept signed updates.
When the signing process starts, the message:
Enabling binary signing
will appear in the IDE window before a compile is launched. At the completion of the build, the signed binary file well
be displayed in the IDE build window as:
Signed binary: /full/path/to/sketch.bin.signed
If you receive either of the following messages in the IDE window, the signing was not completed and you will need
to verify the public.key and private.key:
Not enabling binary signing
... or ...
Not signing the generated binary
48.1. Introduction 141
Arduino-Pico Documentation, Release 4.4.3
Manual Signing of Binaries
Users may also manually sign executables and require the OTA process to verify their signature. In the main code,
before enabling any update methods, add the following declarations and function call:
<in globals>
BearSSL::PublicKey signPubKey( ... key contents ... );
BearSSL::HashSHA256 hash;
BearSSL::SigningVerifier sign( &signPubKey );
...
<in setup()>
Update.installSignature( &hash, &sign );
The above snippet creates a BearSSL public key and a SHA256 hash verifier, and tells the Update object to use them
to validate any updates it receives from any method.
Compile the sketch normally and, once a .bin file is available, sign it using the signer script:
<PicoArduinoPath>/tools/signing.py --mode sign --privatekey <path-to-private.key> --bin
˓→<path-to-unsigned-bin> --out <path-to-signed-binary>
48.2 Compression
The bootloader incorporates a GZIP decompressor, built for very low code requirements. For applications, this optional
decompression is completely transparent.
No changes to the application are required. The Updater class and bootloader (which performs actual application
overwriting on update) automatically search for the gzip header in the uploaded binary, and if found, handle it.
Compress an application .bin file or filesystem package using any gzip available, at any desired compression level (gzip
-9 is recommended because it provides the maximum compression and uncompresses as fast as any other compression
level). For example:
gzip -9 sketch.bin # Maximum compression, output sketch.bin.gz
<Upload the resultant sketch.bin.gz>
If signing is desired, sign the gzip compressed file after compression.
gzip -9 sketch.bin
<PicoPath>/tools/signing.py --mode sign --privatekey <path-to-private.key> --bin sketch.
˓→bin.gz --out sketch.bin.gz.signed
48.2.1 Safety
The OTA process consumes some of the Pico’s resources and bandwidth during upload. Then, the module is restarted
and a new sketch executed. Analyse and test how this affects the functionality of the existing and new sketches.
If the Pico is in a remote location and controlling some equipment, you should devote additional attention to what
happens if operation of this equipment is suddenly interrupted by the update process. Therefore, decide how to put this
equipment into a safe state before starting the update. For instance, your module may be controlling a garden watering
system in a sequence. If this sequence is not properly shut down and a water valve is left open, the garden may be
flooded.
The following functions are provided with the ArduinoOTA library and intended to handle functionality of your appli-
cation during specific stages of OTA, or on an OTA error:
142 Chapter 48. OTA Updates
Arduino-Pico Documentation, Release 4.4.3
void onStart(OTA_CALLBACK(fn));
void onEnd(OTA_CALLBACK(fn));
void onProgress(OTA_CALLBACK_PROGRESS(fn));
void onError(OTA_CALLBACK_ERROR (fn));
48.3 Uploading from the Arduino IDE
Uploading modules wirelessly from Arduino IDE is intended for the following typical scenarios:
• During firmware development as a quicker alternative to loading over a serial port,
• For updating a small number of modules,
• Only if modules are accessible on the same network as the computer with the Arduino IDE.
• For all IDE uploads, the Pico W and the computer must be connected to the same network.
To upload wirelessly from the IDE:
1. Build a sketch that starts WiFi and includes the appropriate calls to ArduinoOTA (see the examples for reference).
These include the ArduinoOTA.begin() call in setup() and periodically calling ArduinoOTA.handle();
from the loop()
2. Upload using standard USB connection the first time.
3. The Tools->Port should now list pico-###### under the Network Ports. Select it (you won’t be able to
use the serial monitor, of course).
4. Try another upload. It should display the OTA process in place of the serial port upload.
48.4 Password Protection
Protecting your OTA uploads with a password is really straightforward. All you need to do, is to include the following
statement in your code:
ArduinoOTA.setPassword((const char *)"123");
Where 123 is a sample password that you should replace with your own.
Before implementing it in your sketch, it is a good idea to check how it works using BasicOTA.ino sketch available
under File > Examples > ArduinoOTA. Go ahead, open BasicOTA.ino, uncomment the above statement that is already
there, and upload the sketch. To make troubleshooting easier, do not modify example sketch besides what is absolutely
required. This includes the original simple 123 OTA password. Then attempt to upload a sketch again (using OTA).
After compilation is complete, once upload is about to begin, you should see a prompt for password.
Enter the password and upload should be initiated as usual with the only difference being Authenticating...OK
message visible in the upload log.
You will not be prompted for a reentering the same password next time. Arduino IDE will remember it for you. You
will see a prompt for password only after reopening the IDE, or if you change it in your sketch, upload the sketch and
then try to upload it again.
Please note, it is possible to reveal password entered previously in Arduino IDE, if the IDE has not been closed since
last upload. This can be done by enabling Show verbose output during: upload in File > Preferences and attempting
to upload the module.
48.3. Uploading from the Arduino IDE 143
Arduino-Pico Documentation, Release 4.4.3
48.5 Web Browser
Updates described in this chapter are done with a web browser that can be useful in the following typical scenarios:
• after application deployment if loading directly from Arduino IDE is inconvenient or not possible,
• after deployment if user is unable to expose module for OTA from external update server,
• to provide updates after deployment to small quantity of modules when setting an update server is not practicable.
48.5.1 Requirements
• The Pico and the computer must be connected to the same network, or the IP of the Pico should be known if on
a different network.
48.5.2 Implementation Overview
Updates with a web browser are implemented using HTTPUpdateServer class together with WebServer and LEAmDNS
or SimpleMDNS classes. The following code is required to get it work:
setup()
MDNS.begin(host);
httpUpdater.setup(&httpServer);
httpServer.begin();
MDNS.addService("http", "tcp", 80);
loop()
httpServer.handleClient();
In case OTA update fails dead after entering modifications in your sketch, you can always recover module by loading
it over a serial port. Then diagnose the issue with sketch using Serial Monitor. Once the issue is fixed try OTA again.
48.6 HTTP Server
HTTPUpdate class can check for updates and download a binary file from HTTP web server. It is possible to download
updates from every IP or domain address on the network or Internet.
Note that by default this class closes all other connections except the one used by the update, this is because the update
method blocks. This means that if there’s another application receiving data then TCP packets will build up in the
buffer leading to out of memory errors causing the OTA update to fail. There’s also a limited number of receive buffers
available and all may be used up by other applications.
There are some cases where you know that you won’t be receiving any data but would still like to send progress updates.
It’s possible to disable the default behaviour (and keep connections open) by calling closeConnectionsOnUpdate(false).
48.6.1 Requirements
• web server
144 Chapter 48. OTA Updates
Arduino-Pico Documentation, Release 4.4.3
48.6.2 Arduino code
Simple updater
Simple updater downloads the file every time the function is called.
WiFiClient client;
HTTPUpdate.update(client, "192.168.0.2", 80, "/arduino.bin");
Advanced updater
It’s possible to point the update function to a script on the server. If a version string argument is given, it will be sent
to the server. The server side script can use this string to check whether an update should be performed.
The server-side script can respond as follows: - response code 200, and send the firmware image, - or response code
304 to notify Pico that no update is required.
WiFiClient client;
t_httpUpdate_return ret = HTTPUpdate.update(client, "192.168.0.2", 80, "/pico/update/
˓→arduino.php", "optional current version string here");
switch(ret) {
case HTTP_UPDATE_FAILED:
Serial.println("[update] Update failed.");
break;
case HTTP_UPDATE_NO_UPDATES:
Serial.println("[update] Update no Update.");
break;
case HTTP_UPDATE_OK:
Serial.println("[update] Update ok.");
break;
}
TLS updater
Please read and try the examples provided with the library.
48.6.3 Server request handling
Simple updater
For the simple updater the server only needs to deliver the binary file for update.
Advanced updater
For advanced update management a script (such as a PHP script) can run on the server side. It will receive the following
headers which it may use to choose a specific firmware file to serve:
::
[User-Agent] => Pico-HTTP-Update [x-Pico-STA-MAC] => 18:FE:AA:AA:AA:AA [x-Pico-AP-MAC] =>
1A:FE:AA:AA:AA:AA [x-Pico-Version] => DOOR-7-g14f53a19 [x-Pico-Mode] => sketch
48.7 Stream Interface
The Stream Interface is the base for all other update modes like OTA, HTTP Server / client. Given a Stream-class
variable streamVar providing byteCount bytes of firmware, it can store the firmware as follows:
48.7. Stream Interface 145
Arduino-Pico Documentation, Release 4.4.3
Update.begin(firmwareLengthInBytes);
Update.writeStream(streamVar);
Update.end();
48.7.1 OTA Bootloader and Memory Map
A firmware file is uploaded via any method (Ethernet, WiFi, serial ZModem, etc.) and stored on the LittleFS filesystem
as a normal file. The Updater class (or the underlying PicoOTA) will make a special “OTA command” file on the
filesystem, which will be read by the OTA bootloader. On a reboot, this OTA bootloader will check for an upgrade file,
verify its contents, and then perform the requested update and reboot. If no upgrade file is present, the OTA bootloader
simply jumps to the main sketch.
The ROM layout consists of:
[boot2.S] [OTA Bootloader] [0-pad] [OTA partition table] [Main sketch] [LittleFS␣
˓→filesystem] [EEPROM]
146 Chapter 48. OTA Updates
CHAPTER
FORTYNINE
LIBRARIES PORTED/OPTIMIZED FOR THE RP2040
Most Arduino libraries that work on modern 32-bit CPU based Arduino boards will run fine using Arduino-Pico.
The following libraries have undergone additional porting and optimizations specifically for the RP2040 and you should
consider using them instead of the generic versions available in the Library Manager
• Adafruit GFX Library by @Bodmer, 2-20x faster than the standard version on the Pico
• Adafruit ILI9341 Library again by @Bodmer
• ESP8266Audio ported to use the included I2S library
147
Arduino-Pico Documentation, Release 4.4.3
148 Chapter 49. Libraries Ported/Optimized for the RP2040
CHAPTER
FIFTY
USING THE RASPBERRY PI PICO SDK (PICO-SDK)
50.1 Included SDK
A complete copy of the Raspberry Pi Pico SDK is included with the Arduino core, and all functions in the core are
available inside the standard link libraries.
For simple programs wishing to call these functions, simply include the appropriate header as shown below
#include "pico/stdlib.h"
void setup() {
const uint LED_PIN = 25;
gpio_init(LED_PIN);
gpio_set_dir(LED_PIN, GPIO_OUT);
while (true) {
gpio_put(LED_PIN, 1);
sleep_ms(250);
gpio_put(LED_PIN, 0);
sleep_ms(250);
}
}
void loop() {}
Note: When you call SDK functions in your own app, the core and libraries are not aware of any changes to the Pico
you perform. So, you may break the functionality of certain libraries in doing so.
50.2 Multicore (CORE1) Processing
Warning: While you may spawn multicore applications on CORE1 using the SDK, the Arduino core may have issues
running properly with them. In particular, anything involving flash writes (i.e. EEPROM, filesystems) will probably
crash due to CORE1 attempting to read from flash while CORE0 is writing to it.
50.3 PIOASM (Compiling for the PIO processors)
A precompiled version of the PIOASM tool is included in the download package and can be run from the CLI.
There is also a fully online version of PIOASM that runs in a web browser without any CLI required, thanks to
@jake653: https://wokwi.com/tools/pioasm (GitHub source: https://github.com/wokwi/pioasm-wasm)
There is also Docker code available for the tool at: https://github.com/kahara/pioasm-docker
149
Arduino-Pico Documentation, Release 4.4.3
150 Chapter 50. Using the Raspberry Pi Pico SDK (PICO-SDK)
CHAPTER
FIFTYONE
LICENSING AND CREDITS
Arduino-Pico is licensed under the LGPL license as detailed in the included README.
In addition, it contains code from additional open source projects:
• The Arduino IDE and ArduinoCore-API are developed and maintained by the Arduino team. The IDE is licensed
under GPL.
• The RP2040 GCC-based toolchain is licensed under under the GPL.
• The Pico-SDK and Pico-Extras are by Raspberry Pi (Trading) Ltd. and licensed under the BSD 3-Clause license.
• Arduino-Pico core files are licenses under the LGPL.
• LittleFS library written by ARM Limited and released under the BSD 3-clause license .
• UF2CONV.PY is by Microsoft Corporation and licensed under the MIT license.
• Some filesystem code taken from the ESP8266 Arduino Core and licensed under the LGPL.
151