-
-
Notifications
You must be signed in to change notification settings - Fork 1
PikoPD manual
PikoPD project automates building PD patches (.pd) into a UF2 firmware using hvcc compiler and Raspberry Pi Pico C/C++ SDK.
The goal of this project is to develop an interface between the Raspberry Pi Pico, its peripherals (such as knobs, buttons, sensors), and Pure Data, providing an interactive workflow for creating embedded audio and MIDI tools.
PikoPD supports hvcc-compatible vanilla PD objects and heavylib objects, such as hv.osc~ and hv.lfo~.
- Toolchain Setup
- Hardware Configuration
- Project Configuration
- Polyphonic Input
- MIDI
- Sample Loading
- Serial Monitor
- Web Control and OSC
- Web Config Tool
- Useful Links
- jinja2
brew install cmake
brew install git
xcode-select --install
brew install arm-none-eabi-gccIf you encounter nosys.specs error after installation of the arm-none-eabi-gcc homebrew version:
brew uninstall --force arm-none-eabi-gcc
brew uninstall --force arm-none-eabi-binutils
brew install gcc-arm-embeddedI recommend using the official ARM toolchain.
Download:
https://developer.arm.com/downloads/-/a ... -downloads
Then add this to the PATH:
echo 'export PATH="/Applications/ArmGNUToolchain/14.3.rel1/arm-none-eabi/bin:$PATH"' >> ~/.bash_profile && source ~/.bash_profilesudo apt install cmake git python3 build-essential gcc-arm-none-eabi libnewlib-arm-none-eabi libstdc++-arm-none-eabi- newlibpython3 -m venv venv
source venv/bin/activate
git clone https://github.com/Wasted-Audio/hvcc.git
cd hvcc/
pip3 install -e . git clone https://github.com/raspberrypi/pico-sdk.git
cd pico-sdk
git submodule update --init Set pico-sdk path environment variable:
export PICO_SDK_PATH=/your_path/pico-sdkMust be placed inside the pico-sdk folder.
cd pico-sdk
git clone https://github.com/raspberrypi/pico-extras.git
cd pico-extras
git submodule update --init brew install picotoolgit clone https://github.com/raspberrypi/picotool
cd picotool
mkdir build && cd build
cmake .. -DPICO_SDK_PATH=$PICO_SDK_PATH
make -j8
sudo make installHardware configuration is done by adjusting the board.json file.
This file defines how the board hardware (LEDs, inputs, joystick, etc.) is mapped to GPIO pins and how it behaves.
Set in board.json:
- board (pico, pico_w, zero, pico2)
- core frequency
- sample rate
- audio mode (I2S, PWM) and pins
- voice count
- led (pwm, rgb and mode)
- adc pins (knob, cv_in)
- rotary encoder
- gate in/out (gate or trigger)
- button (bang, toggle, switch)
- joystick and range (regular or midi 1-127)
- midi mode (uart, usb, host)
- uart (pins tx 0, rx 1 )
- debug console
- sensors
- cny70
- mpr121
- hc-sr04
- masterfx (delay, reverb, limiter)
- I2S (PCM5102)
"audio_mode": "I2S",
"sample_rate": 48000,
"channel": 2,
"buffer_size": 64,
"i2s_data_pin": 9,
"i2s_bclk_pin": 10LRCK pin is asigned automatically as next after the BCLK.
- PWM
"audio_mode": "PWM",
"sample_rate": 48000,
"buffer_size": 64,
"pwm_pin": 10"buttons": [
{ "name": "btn1", "pin": 23, "mode": "toggle" },
{ "name": "btn2", "pin": 11, "mode": "switch" },
{ "name": "btn3", "pin": 14, "mode": "bang" }
]TOGGLE: Latch (On/Off)
SWITCH: Press & Release (Momentary)
BANG: Trigger, Sends 1.0, then 0.0 after 50ms (can be adjusted)
"adc_pins": [
{ "name": "knob", "pin": 26, "type": "knob" },
{ "name": "cv1", "pin": 27, "type": "cv_in" }
]| Type | Description |
|---|---|
knob |
Analog control such as a potentiometer (smoothed) |
cv_in |
Control voltage input for external analog signals (0–3.3V) |
Raspberry Pico can't sample audio so PD [adc] object will not work without an external adc.
PikoPD boards support 4 LED modes.
-
pd – Maps a Pure Data
[send]object directly to the LED. - status – LED turns on when the board is powered and working correctly.
- midi – LED blinks in response to incoming MIDI messages.
- clock – LED blinks in response to incoming MIDI clock.
"leds": [
{ "name": "led1", "pin": 25, "mode": "pd" },
{ "name": "status", "pin": 24, "mode": "status" },
{ "name": "clock", "pin": 23, "mode": "clock" },
{ "name": "ledRGB", "pin": 16, "is_rgb": true, "mode": "midi" }
]Builtin LED pins
| Board | Pin | Notes |
|---|---|---|
| Pico | 25 | Single-color LED |
| Pico W | 25 | Single-color LED |
| Pico 2 | 25 | Single-color LED |
| Pico Zero | 16 | RGB NeoPixel LED (is_rgb: true) |
Code supports up to 12 different led connection.
RGB led in PD accepts 1 value (intensity) or 2 values (hue and intensity) in range f0.0-1.0.
Use [pack f f] object before [s ledRGB] to send 2 values.
PikoPD supports 2 joystick connection (each uses 2 adc pins), which can output values in either regular or MIDI 1–127 range.
"joystick": [
{ "name": "joy", "joy_x": 26, "joy_y": 27, "midi_range": true }
]PikoPD supports 4 incremental rotary encoders
"encoders": [
{ "name": "enc", "pin_a": 5, "pin_b": 6 }
]Use this construct in your patch from encoder.pd:
-
[r enc @hv_param]receives incremental encoder changes -1 / +1 - The value is accumulated using
[f]and[+] -
[mod]wraps the value into a fixed range - Adjust
modto set the number of encoder steps (e.g.,mod 8,mod 16)
"sensors": {
"mpr121": [
{ "name": "mpr1", "i2c_bus": "i2c0", "sda": 4, "scl": 5, "irq": 6, "addr_index": 0 },
{ "name": "mpr2", "i2c_bus": "i2c1", "sda": 6, "scl": 7, "irq": 8, "addr_index": 0 }
]
}PikoPD supports up to 4 MPR121 capacitive touch sensor devices on each of the i2c buses. To use two or more MPR121 on the same i2c bus you will have to physically change it's adress and set addr_index:
- 0x5A,
- 0x5B,
- 0x5C,
- 0x5D
IRQ pin is used by default to make processing more efficient.
To use this sensor in the PD patch create [r pad1 @hv_param] object for each pad in numerical order. Script will automatically asign pad objects to each of the devices (0-12, 13-24...) set in board.json.
"sensors": {
"hc-sr04": [
{ "name": "distance", "trigger": 2, "echo": 3}
]
}PikoPD supports multiple HC-SR04 ultrasonic distance sensors. Use object [r distance @hv_param].
"sensors": {
"cny70": [
{ "name": "cny", "adc_pin": 28 }
]
}The CNY70 is a short-range reflective optical sensor (often called an optoisolator or phototransistor).
Sensor contains two main parts inside its square plastic housing:
- Infrared (IR) Emitter – An LED that continuously emits invisible infrared light
- Phototransistor (Receiver) – A light-sensitive component that detects reflected IR light
When you place a finger or an object in front of the sensor (within a few millimeters), the IR light reflects off the object and hits the receiver. The sensor then outputs a voltage based on how much light is reflected.
Because there are multiple manufacturers of these sensors, the pin layouts and wiring can differ. Refer to this 3.3V common wiring scheme for Raspberry Pi Pico boards. Note that because infrared light is invisible to the human eye, you will need to look at the LED through a smartphone camera lens to verify that it is turned on and glowing.
To use this sensor in a PD patch, connect its output to an ADC pin and add [r cny @hv_param] object.
"display": {
"enabled": true,
"driver": "ssd1306",
"i2c_bus": "i2c0",
"sda": 4,
"scl": 5,
"width": 128,
"height": 64,
"mode": "console"
},PikoPD supports SSD1306 display. There are 2 modes which can be set in board.json:
- console - outputs adjusted parameter [print] object.
- pd - outputs first 4 [print] objects as dashboard.
- PikoPD supports hvcc-compatible vanilla PD objects and heavylib objects, such as hv.osc~ and hv.lfo~.
- Check PD patch examples in the folder.
- The
[s @hv_param]and[r @hv_param]object names must exactly match (case-sensitive) names defined in the config file. - The script automatically includes objects present in the patch and ignores unconnected.
- Debug console, when enabled, will also output PD
[print]objects. Use it moderately, because it can crash the device. - If you change board and MIDI mode or encounter compile-time errors remove the project folder or rename it to rebuild files.
- Tested on macOS.
workspace/
├── pico-sdk/
│ └── pico-extras/
├── picotool/
├── pikoPD/
│ ├── docs/
│ ├── lib/
│ │ └── heavylib/
│ ├── patches/ # pd patches folder
│ ├── src/ # hardware config source files
│ ├── templates/
│ │ └── main.cpp # template
│ ├── project/
│ │ ├── build/ # build folder (uf2 file here)
│ │ ├── hvcc/ # hvcc compiler generated files
│ │ ├── src/
│ │ └── CMakeLists.txt
│ ├── board.json # user config file
│ └── pikopd.py # pikopd script
pikopd.py
- Converts Pure Data (
.pd) patch to C code via hvcc compiler - Copies config files into project folder from
/src - Configures hardware using
board.json - Uses main.cpp as a project template
- Builds firmware using CMake in a
build/folder - Checks for device in BOOTSEL mode
- Flashes UF2 firmware to PICO board and restarts device
Enter bootloader mode by holding device boot button
python3 pikopd.py patches/heavy.pd project_name
optional arguments:
-h, --help Show help message and exit
-b, --board Path to custom json configuration file
-f, --flash Flash UF2 to Pico (BOOTSEL mode required)
-s, --serial Open serial console after reboot
-x, --skip-hvcc Disable hvcc file regeneration for manual editing
-v, --verbose Enable verbose compiler console debug output
The Pure Data [poly] object works with [notein] on PICO, but it is resource-intensive.
To make MIDI note processing lightweight, a custom voice allocation system with oldest voice stealing was implemented using [r NOTE] objects.
To use the custom system:
- Set voice count to 2 or more in
board.json. - Add
[NOTE1, [NOTE2]...objects for each voice. - Use
[unpack]to extract note, velocity, and channel in the PD patch.
Check example in the patch folder.
"midi_mode": "usb" // Usb midi device "PikoPD"
"midi_mode": "uart" // Default pins tx 0, rx 1
"midi_mode": "host" // Use otg cable to power pico and usb midi deviceMidi clock and start/stop messages work with PD [midirealtimein] object.
| CC Number | Parameter |
|---|---|
| 7 | Master Volume |
| 8 | Limiter Bypass |
| 90 | Delay Time |
| 91 | Delay Send Level |
| 92 | Delay Feedback Amount |
| 93 | Delay Bypass |
| 94 | Reverb Mix |
| 95 | Reverb Room Size |
| 96 | Reverb Damping |
| 97 | Reverb Width |
| 98 | Reverb Pre-delay |
| 99 | Reverb Bypass |
| 120 | Debug Toggle |
You can enable the masterFX in the board.json. To use safe volume it is recomended to keep limiter on.
Sample loading works despite the limitations. Here is a tutorial for a sample loading using Plugdata.
By design, hvcc-generated code stores samples in float arrays in RAM. PikoPD applies a patch to store them in flash memory, making it possible to load more.
"console": trueDebug console will also output PD [print] objects, which are parsed automatically. Use it moderately, because it can crash the device.
"web": {
"enabled": true,
"active_mode": 0,
"ap": {
"mode_id": 0,
"ssid": "pikoPD",
"password": "12345678"
},
"sta": {
"mode_id": 1,
"ssid": "",
"password": ""
},
"mdns_name": "pikopd",
"http_port": 80,
"osc_enabled": false,
"osc_port": 8000
}PikoPD supports WEB and OSC protocols for the PICO boards with WIFI chips.
See patches/web.pd
- To receive values from WEB ui /cgi_handler use PD objects with keyword WEB - [r web @hv_param]
At this test stage only one slider is enabled in WEB UI which sends values to web1 object inside PD.
After device connects to wifi open pikopd.local in the browser.
For creating custom WEB UI use the library /lib/pico-w-webserver. Edit index.shtml and than run makefsdata.py.
After that put generated htmldata.c inside /src/web and rebuild.
- To receive OSC messages use PD objects with keyword OSC - [r osc @hv_param]
- To send messages to the device use [s osc @hv_param]
To test OSC use the PD patch oscNetsendReceive.pd in /tools.
Select your board model (Raspberry Pico, Pico W, Zero or Pico 2).
Upload your .pd patch to see available parameters or load board.json configuration file.
Click a pin on the board and add a component, or drag a parameter tag directly onto a pin.
Export the board.json and place it in the pikoPD folder.
- About hvcc compiler
https://wasted-audio.github.io/hvcc/ - Hvcc supported PD objects
https://github.com/Wasted-Audio/hvcc/blob/develop/docs/reference/objects/supported.md