Introduction to the Raspberry Pi Pico
The Raspberry Pi Pico represents a significant departure from the Raspberry Pi Foundation's traditional offerings. Unlike its predecessors, which were complete single-board computers capable of running full operating systems, the Pico is a microcontroller board designed for physical computing projects. Released in January 2021, the Pico brought something entirely new to the table: the RP2040, the Raspberry Pi Foundation's first custom-designed silicon chip.
This microcontroller board has quickly gained popularity among hobbyists, educators, and professional developers due to its remarkable capabilities, low cost, and the strong ecosystem support that comes with the Raspberry Pi name. Whether you're a beginner just starting with electronics or an experienced developer looking for a powerful yet affordable microcontroller for your next project, the Raspberry Pi Pico offers a compelling platform that deserves consideration.
In this comprehensive guide, we'll explore everything you need to know about incorporating the Raspberry Pi Pico into your projects. From understanding its technical specifications to setting up your development environment, from basic programming concepts to advanced applications, this article aims to be your go-to resource for making the most of this versatile microcontroller.
Understanding the Raspberry Pi Pico Hardware
Technical Specifications
The Raspberry Pi Pico is built around the RP2040 microcontroller chip, which offers impressive specifications that make it suitable for a wide range of applications:
Feature | Specification |
---|---|
Processor | Dual-core Arm Cortex-M0+ @ 133MHz |
Memory | 264KB on-chip SRAM |
Storage | 2MB on-board QSPI Flash |
Connectivity | 26x multi-function GPIO pins |
USB | USB 1.1 with device and host support |
Low Power | Sleep and dormant modes |
Unique Features | 8x Programmable I/O (PIO) state machines |
Peripherals | 2x UARTs, 2x SPI controllers, 2x I2C controllers, 16x PWM channels |
Analog | 3x 12-bit ADC channels |
Clock | Accurate clock and timer on-chip |
Temperature | On-chip temperature sensor |
Form Factor | 51 x 21 mm with 40 pins |
Price | Approximately $4 USD |
Board Layout and GPIO Pins
The Pico features a compact design with 40 pins in a dual inline package (DIP) format, making it breadboard-friendly. These pins provide access to the various capabilities of the RP2040 chip:
- 26 GPIO pins for digital input/output
- 3 analog inputs (ADC)
- 16 PWM channels
- 2 UART, 2 I2C, and 2 SPI interfaces
- 8 Programmable I/O (PIO) state machines
The GPIO pins are arranged along both sides of the board, with additional system pins for power, ground, and special functions. The board includes a micro-USB port for power and programming, a BOOTSEL button for entering programming mode, and an on-board LED connected to GPIO pin 25.
Power Options
The Pico is designed with flexibility in mind regarding power supply options:
Power Source | Voltage Range | Connection Method |
---|---|---|
Micro USB | 5V | USB connector |
External Power | 1.8V to 5.5V | VSYS pin |
Battery | 1.8V to 5.5V | VBUS pin |
3.3V Direct | 3.3V | 3V3 pin (bypasses regulator) |
This flexibility allows the Pico to be used in various scenarios, from USB-powered desktop projects to battery-powered portable applications.
The RP2040 Chip: A Closer Look
The heart of the Raspberry Pi Pico is the RP2040 microcontroller, designed in-house by the Raspberry Pi Foundation. This chip includes several notable features:
- Dual-core Arm Cortex-M0+ Processor: Running at 133MHz, these cores can operate independently or cooperatively to handle multiple tasks simultaneously.
- Programmable I/O (PIO): Perhaps the most innovative feature of the RP2040 is its PIO system, which consists of eight state machines that can be programmed to implement custom digital interfaces. This allows the Pico to interface with a wide range of hardware, from simple LED strips to complex communication protocols.
- USB 1.1 Controller: Supports both device and host modes, allowing the Pico to act as either a USB device connected to a computer or a host controlling other USB devices.
- Advanced Timer System: Includes a 64-bit timer and real-time counter, enabling precise timing for applications like motor control or sensor reading.
The RP2040's architecture is designed to provide a balance between processing power, I/O capabilities, and power efficiency, making it suitable for a diverse range of embedded applications.
Getting Started with the Raspberry Pi Pico
Setting Up Your Development Environment
Before you can start programming the Pico, you'll need to set up your development environment. The Raspberry Pi Foundation supports multiple programming languages and development approaches:
MicroPython Setup
MicroPython is an implementation of Python 3 optimized to run on microcontrollers. It's an excellent choice for beginners or those familiar with Python.
- Download the MicroPython UF2 file: Visit the official to download the latest MicroPython UF2 file.
- Connect the Pico in bootloader mode:
- Hold down the BOOTSEL button on the Pico
- While holding BOOTSEL, connect the Pico to your computer via USB
- Release BOOTSEL once connected
- Flash MicroPython:
- The Pico should appear as a USB mass storage device
- Drag and drop the MicroPython UF2 file onto this drive
- The Pico will automatically reboot with MicroPython installed
- Install an IDE: Thonny is recommended for MicroPython development with the Pico:
- Download and install Thonny from
- In Thonny, go to Tools > Options > Interpreter
- Select "MicroPython (Raspberry Pi Pico)" from the dropdown menu
- Configure the port (usually auto-detected)
C/C++ Setup
For more performance-critical applications or if you prefer C/C++, the Pico SDK (Software Development Kit) provides a comprehensive development environment:
Development Environment | Operating System | Notes |
---|---|---|
Raspberry Pi OS | Raspberry Pi | Pre-configured with necessary tools. Recommended for beginners. |
Windows | Windows 10/11 | Requires installation of Visual Studio, CMake, and build tools. |
macOS | macOS 10.15+ | Requires installation of Homebrew, CMake, and ARM toolchain. |
Linux | Ubuntu, Debian | Requires installation of development tools via apt or equivalent. |
The basic setup process for C/C++ development involves:
- Install prerequisites: Git, CMake, Python 3, and the ARM GCC compiler
- Clone the Pico SDK repository:
bash
cd pico-sdk git submodule update --init
- git clone https://github.com/raspberrypi/pico-sdk.git
- Set the PICO_SDK_PATH environment variable:
bash
- export PICO_SDK_PATH=/path/to/pico-sdk
- Clone the Pico examples repository:
bash
- git clone https://github.com/raspberrypi/pico-examples.git
- Build the examples:
bash
mkdir build cd build cmake .. make
- cd pico-examples
Your First Pico Program
Let's create the traditional "Hello, World!" for microcontrollers - blinking an LED.
MicroPython Blink Example
pythonfrom machine import Pinimport time # The Pico has an on-board LED connected to GPIO pin 25 led = Pin(25, Pin.OUT) # Blink the LED in an infinite loop while True: led.value(1) # Turn on the LED time.sleep(0.5) # Wait for 0.5 seconds led.value(0) # Turn off the LED time.sleep(0.5) # Wait for 0.5 seconds
C/C++ Blink Example
c#include "pico/stdlib.h"int main() { // Initialize the GPIO pin connected to the on-board LED const uint LED_PIN = 25; gpio_init(LED_PIN); gpio_set_dir(LED_PIN, GPIO_OUT); // Blink the LED in an infinite loop while (true) { gpio_put(LED_PIN, 1); // Turn on the LED sleep_ms(500); // Wait for 0.5 seconds gpio_put(LED_PIN, 0); // Turn off the LED sleep_ms(500); // Wait for 0.5 seconds } return 0; // This line will never be reached }
Basic Input and Output Operations
Beyond blinking an LED, let's explore some basic I/O operations with the Pico:
Digital I/O
The Pico's GPIO pins can be configured as either inputs or outputs:
MicroPython Digital I/O Example:
pythonfrom machine import Pinimport time # Configure GPIO 15 as output and GPIO 14 as input with pull-up resistor led = Pin(15, Pin.OUT) button = Pin(14, Pin.IN, Pin.PULL_UP) # Light the LED when the button is pressed (button connects pin to ground when pressed) while True: if button.value() == 0: # Button is pressed led.value(1) # Turn on LED else: led.value(0) # Turn off LED time.sleep(0.01) # Small delay to avoid CPU hogging
C/C++ Digital I/O Example:
#include "pico/stdlib.h"int main() { // Configure GPIO 15 as output and GPIO 14 as input with pull-up resistor const uint LED_PIN = 15; const uint BUTTON_PIN = 14; gpio_init(LED_PIN); gpio_set_dir(LED_PIN, GPIO_OUT); gpio_init(BUTTON_PIN); gpio_set_dir(BUTTON_PIN, GPIO_IN); gpio_pull_up(BUTTON_PIN); // Light the LED when the button is pressed while (true) { if (gpio_get(BUTTON_PIN) == 0) { // Button is pressed gpio_put(LED_PIN, 1); // Turn on LED } else { gpio_put(LED_PIN, 0); // Turn off LED } sleep_ms(10); // Small delay to avoid CPU hogging } return 0; }
Analog Input (ADC)
The Pico features three analog-to-digital converter (ADC) pins that can read voltage levels:
MicroPython ADC Example:
pythonfrom machine import ADC, Pinimport time # Initialize ADC on GPIO 26 (ADC0) adc = ADC(26) # Read and print analog values while True: # Read ADC value (0-65535) and convert to voltage (0-3.3V) raw_value = adc.read_u16() voltage = raw_value * (3.3 / 65535) print(f"ADC Raw Value: {raw_value}, Voltage: {voltage:.2f}V") time.sleep(1)
C/C++ ADC Example:
#include "pico/stdlib.h"#include "hardware/adc.h" #include <stdio.h> int main() { stdio_init_all(); // Initialize standard I/O for printf // Initialize ADC adc_init(); adc_gpio_init(26); // Make GPIO26 an ADC input adc_select_input(0); // GPIO26 is ADC0 // Read and print analog values while (true) { // Read ADC value (0-4095) and convert to voltage (0-3.3V) uint16_t raw_value = adc_read(); float voltage = raw_value * (3.3f / 4095.0f); printf("ADC Raw Value: %d, Voltage: %.2fV\n", raw_value, voltage); sleep_ms(1000); } return 0; }
PWM Output
Pulse Width Modulation (PWM) is useful for controlling the brightness of LEDs, motor speed, or generating analog-like signals:
MicroPython PWM Example:
pythonfrom machine import Pin, PWMimport time # Initialize PWM on GPIO 16 pwm = PWM(Pin(16)) pwm.freq(1000) # Set frequency to 1 kHz # Gradually vary the duty cycle (LED brightness) while True: for duty in range(0, 65535, 1000): # From 0% to 100% brightness pwm.duty_u16(duty) # Set duty cycle time.sleep(0.01) for duty in range(65535, 0, -1000): # From 100% to 0% brightness pwm.duty_u16(duty) # Set duty cycle time.sleep(0.01)
C/C++ PWM Example:
#include "pico/stdlib.h"#include "hardware/pwm.h" int main() { // Initialize PWM on GPIO 16 const uint LED_PIN = 16; gpio_set_function(LED_PIN, GPIO_FUNC_PWM); // Find out which PWM slice is connected to GPIO 16 uint slice_num = pwm_gpio_to_slice_num(LED_PIN); uint channel = pwm_gpio_to_channel(LED_PIN); // Set frequency (125MHz / (wrap + 1) = 1kHz) pwm_set_wrap(slice_num, 125000 - 1); // Enable PWM pwm_set_enabled(slice_num, true); // Gradually vary the duty cycle (LED brightness) while (true) { for (int i = 0; i <= 125000; i += 1000) { pwm_set_chan_level(slice_num, channel, i); sleep_ms(10); } for (int i = 125000; i >= 0; i -= 1000) { pwm_set_chan_level(slice_num, channel, i); sleep_ms(10); } } return 0; }
Communication Protocols with Raspberry Pi Pico
One of the key strengths of the Raspberry Pi Pico is its support for various communication protocols, allowing it to interface with a wide range of sensors, displays, and other peripherals.
UART Communication
UART (Universal Asynchronous Receiver/Transmitter) is used for serial communication between devices:
MicroPython UART Example:
pythonfrom machine import UART, Pinimport time # Initialize UART0 with baud rate of 9600 uart = UART(0, baudrate=9600, tx=Pin(0), rx=Pin(1)) # Send and receive data while True: # Send a message uart.write("Hello from Pico!\r\n") # Check if any data is available to read if uart.any(): # Read and print received data data = uart.read() print("Received:", data) time.sleep(1)
C/C++ UART Example:
#include "pico/stdlib.h"#include "hardware/uart.h" #include <stdio.h> #define UART_ID uart0 #define BAUD_RATE 9600 #define UART_TX_PIN 0 #define UART_RX_PIN 1 int main() { // Initialize UART uart_init(UART_ID, BAUD_RATE); gpio_set_function(UART_TX_PIN, GPIO_FUNC_UART); gpio_set_function(UART_RX_PIN, GPIO_FUNC_UART); // Set UART flow control CTS/RTS (optional) // uart_set_hw_flow(UART_ID, false, false); // Set data format (optional) uart_set_format(UART_ID, 8, 1, UART_PARITY_NONE); // Send and receive data while (true) { // Send a message uart_puts(UART_ID, "Hello from Pico!\r\n"); // Check if any data is available to read while (uart_is_readable(UART_ID)) { // Read and print received data char ch = uart_getc(UART_ID); printf("Received: %c\n", ch); } sleep_ms(1000); } return 0; }
I2C Communication
I2C (Inter-Integrated Circuit) is commonly used to connect to sensors, displays, and other peripherals that require a simple 2-wire interface:
MicroPython I2C Example with an OLED Display:
pythonfrom machine import Pin, I2Cimport time from ssd1306 import SSD1306_I2C # Requires the ssd1306 library # Initialize I2C with pins i2c = I2C(0, scl=Pin(9), sda=Pin(8), freq=400000) # Scan for I2C devices devices = i2c.scan() print("I2C devices found:", [hex(device) for device in devices]) # Initialize OLED display (assuming 128x64 pixels) if len(devices) > 0: oled = SSD1306_I2C(128, 64, i2c) # Display text oled.text("Raspberry Pi", 0, 0) oled.text("Pico", 0, 10) oled.text("with OLED", 0, 20) oled.show() # Animation loop counter = 0 while True: oled.fill(0) # Clear display oled.text("Counter:", 0, 0) oled.text(str(counter), 0, 10) oled.show() counter += 1 time.sleep(0.5)
C/C++ I2C Example with an OLED Display:
#include "pico/stdlib.h"#include "hardware/i2c.h" #include <stdio.h> // Define I2C pins and instance #define I2C_INSTANCE i2c0 #define I2C_SDA_PIN 8 #define I2C_SCL_PIN 9 #define I2C_FREQ 400000 // SSD1306 OLED display definitions #define OLED_ADDR 0x3C #define OLED_WIDTH 128 #define OLED_HEIGHT 64 // Function declarations for OLED (simplified) void oled_init(); void oled_clear(); void oled_send_cmd(uint8_t cmd); void oled_send_data(uint8_t data); void oled_set_cursor(uint8_t x, uint8_t y); void oled_print(const char* str); int main() { stdio_init_all(); // Initialize I2C i2c_init(I2C_INSTANCE, I2C_FREQ); gpio_set_function(I2C_SDA_PIN, GPIO_FUNC_I2C); gpio_set_function(I2C_SCL_PIN, GPIO_FUNC_I2C); gpio_pull_up(I2C_SDA_PIN); gpio_pull_up(I2C_SCL_PIN); // Initialize OLED oled_init(); // Display static text oled_clear(); oled_set_cursor(0, 0); oled_print("Raspberry Pi"); oled_set_cursor(0, 1); oled_print("Pico"); oled_set_cursor(0, 2); oled_print("with OLED"); // Animation loop with counter int counter = 0; char buffer[20]; while (true) { // Update counter oled_clear(); oled_set_cursor(0, 0); oled_print("Counter:"); // Convert counter to string sprintf(buffer, "%d", counter); oled_set_cursor(0, 1); oled_print(buffer); counter++; sleep_ms(500); } return 0; } // Note: The actual OLED functions would be more complex // and require the SSD1306 driver implementation
SPI Communication
SPI (Serial Peripheral Interface) provides high-speed communication with devices like SD cards, displays, and some sensors:
MicroPython SPI Example with an SD Card:
thon
from machine import Pin, SPI import sdcard import os # Initialize SPI with pins spi = SPI(0, baudrate=1000000, polarity=0, phase=0, bits=8, firstbit=SPI.MSB, sck=Pin(18), mosi=Pin(19), miso=Pin(16)) # Initialize SD card sd_cs = Pin(17, Pin.OUT) sd = sdcard.SDCard(spi, sd_cs) # Mount the SD card vfs = os.VfsFat(sd) os.mount(vfs, "/sd") # Write to a file on the SD card with open("/sd/test.txt", "w") as file: file.write("Hello from Raspberry Pi Pico!\n") file.write("This is an SD card test.") # Read from the file with open("/sd/test.txt", "r") as file: content = file.read() print(content) # List files on the SD card print("Files on SD card:") print(os.listdir("/sd"))
C/C++ SPI Example with an SD Card:
#include "pico/stdlib.h"#include "hardware/spi.h" #include "ff.h" // FatFs library #include <stdio.h> // SPI configuration #define SPI_INSTANCE spi0 #define SPI_SCK_PIN 18 #define SPI_MOSI_PIN 19 #define SPI_MISO_PIN 16 #define SPI_CS_PIN 17 // FatFs variables static FATFS fs; static FIL fil; static FRESULT fr; int main() { stdio_init_all(); // Initialize SPI spi_init(SPI_INSTANCE, 1000000); gpio_set_function(SPI_SCK_PIN, GPIO_FUNC_SPI); gpio_set_function(SPI_MOSI_PIN, GPIO_FUNC_SPI); gpio_set_function(SPI_MISO_PIN, GPIO_FUNC_SPI); // Chip select pin gpio_init(SPI_CS_PIN); gpio_set_dir(SPI_CS_PIN, GPIO_OUT); gpio_put(SPI_CS_PIN, 1); // Deselect SD card // Initialize SD card with FatFs fr = f_mount(&fs, "", 1); if (fr != FR_OK) { printf("Failed to mount SD card: %d\n", fr); return 1; } // Write to a file fr = f_open(&fil, "test.txt", FA_WRITE | FA_CREATE_ALWAYS); if (fr == FR_OK) { UINT bw; f_write(&fil, "Hello from Raspberry Pi Pico!\r\nThis is an SD card test.", 56, &bw); f_close(&fil); printf("File written successfully\n"); } else { printf("Failed to open file for writing: %d\n", fr); } // Read from the file fr = f_open(&fil, "test.txt", FA_READ); if (fr == FR_OK) { UINT br; char buffer[100]; f_read(&fil, buffer, sizeof(buffer) - 1, &br); buffer[br] = '\0'; // Null-terminate the string printf("File content: %s\n", buffer); f_close(&fil); } else { printf("Failed to open file for reading: %d\n", fr); } // Unmount f_mount(NULL, "", 0); while (true) { sleep_ms(1000); } return 0; }
Advanced Features of the Raspberry Pi Pico
Dual-Core Programming
One of the standout features of the RP2040 chip is its dual-core architecture. By utilizing both cores, you can achieve true parallel execution of tasks:
MicroPython Dual-Core Example:
pythonimport _threadfrom machine import Pin import time # LED pins for each core led_core0 = Pin(25, Pin.OUT) # Onboard LED led_core1 = Pin(15, Pin.OUT) # External LED # Function to run on second core def core1_task(): while True: led_core1.value(1) time.sleep(0.2) led_core1.value(0) time.sleep(0.2) # Start the second core _thread.start_new_thread(core1_task, ()) # Main code runs on core 0 while True: led_core0.value(1) time.sleep(0.5) led_core0.value(0) time.sleep(0.5)
C/C++ Dual-Core Example:
#include "pico/stdlib.h"#include "pico/multicore.h" // LED pins for each core const uint LED_CORE0_PIN = 25; // Onboard LED const uint LED_CORE1_PIN = 15; // External LED // Function to run on core 1 void core1_entry() { gpio_init(LED_CORE1_PIN); gpio_set_dir(LED_CORE1_PIN, GPIO_OUT); while (true) { gpio_put(LED_CORE1_PIN, 1); sleep_ms(200); gpio_put(LED_CORE1_PIN, 0); sleep_ms(200); } } int main() { // Initialize core 0 LED gpio_init(LED_CORE0_PIN); gpio_set_dir(LED_CORE0_PIN, GPIO_OUT); // Launch core 1 multicore_launch_core1(core1_entry); // Main loop on core 0 while (true) { gpio_put(LED_CORE0_PIN, 1); sleep_ms(500); gpio_put(LED_CORE0_PIN, 0); sleep_ms(500); } return 0; }
Programmable I/O (PIO)
The RP2040's PIO state machines provide hardware-level I/O control, allowing implementation of custom communication protocols with precise timing:
MicroPython PIO Example for WS2812B LED Strip:
pythofrom machine import Pinimport rp2 import time # WS2812B (NeoPixel) PIO program @rp2.asm_pio(sideset_init=rp2.PIO.OUT_LOW, out_shiftdir=rp2.PIO.SHIFT_LEFT, autopull=True, pull_thresh=24) def ws2812(): T1 = 2 T2 = 5 T3 = 3 wrap_target() label("bitloop") out(x, 1) .side(0) [T3 - 1] jmp(not_x, "do_zero") .side(1) [T1 - 1] jmp("bitloop") .side(1) [T2 - 1] label("do_zero") nop() .side(0) [T2 - 1] wrap() # Configure the PIO state machine sm = rp2.StateMachine(0, ws2812, freq=8_000_000, sideset_base=Pin(22)) sm.active(1) # Function to generate colors def wheel(pos): # Input a value 0 to 255 to get a color value. if pos < 85: return (pos * 3, 255 - pos * 3, 0) elif pos < 170: pos -= 85 return (255 - pos * 3, 0, pos * 3) else: pos -= 170 return (0, pos * 3, 255 - pos * 3) # Number of LEDs in the strip NUM_LEDS = 8 # Rainbow animation while True: for j in range(256): for i in range(NUM_LEDS): # Calculate the color rc, gc, bc = wheel(((i * 256 // NUM_LEDS) + j) & 255) # Convert RGB to GRB (WS2812B format) and combine into 32-bit value # The upper 8 bits are ignored pixel_value = (gc << 16) | (rc << 8) | bc # Send to PIO state machine sm.put(pixel_value) # Wait for data to be transmitted time.sleep_ms(20)
C/C++ PIO Example for WS2812B LED Strip:
#include "pico/stdlib.h"#include "hardware/pio.h" #include "hardware/clocks.h" #include "ws2812.pio.h" // Generated from WS2812 PIO program #define WS2812_PIN 22 #define NUM_LEDS 8 // Function to generate colors void wheel(uint8_t pos, uint8_t *r, uint8_t *g, uint8_t *b) { if (pos < 85) { *r = pos * 3; *g = 255 - pos * 3; *b = 0; } else if (pos < 170) { pos -= 85; *r = 255 - pos * 3; *g = 0; *b = pos * 3; } else { pos -= 170; *r = 0; *g = pos * 3; *b = 255 - pos * 3; } } int main() { // Initialize PIO PIO pio = pio0; int sm = 0; uint offset = pio_add_program(
No comments:
Post a Comment