Raspberry Pi Pico Synth_Dexed – Revisited
I thought it was time I took another look at my Raspberry Pi Pico Synth_Dexed. I’ve a couple of main aims with coming back to this project:
- Update the build for the latest Pico SDK and Synth_Dexed.
- See what is involved in getting it running on an RP2350.
- See if a simple I2C display can be added and possibly an encoder or other controls.
- Actually document the architecture!
The current hardware is described in detail in Raspberry Pi Pico Synth_Dexed? – Part 5 and supports serial and USB MIDI, I2S or PWM audio output, voice selection (over MIDI) and up to 16 note polyphony if using an overclocked (to 250MHz) RP2040 based Pico and lower sample rate.
https://makertube.net/w/tY1u9qFz85NprRYPmtdvEj
Warning! I strongly recommend using old or second hand equipment for your experiments. I am not responsible for any damage to expensive instruments!
If you are new to microcontrollers, see the Getting Started pages.
Core Updates
Since I first put the PicoDexed together, V2 of the Raspberry Pi Pico SDK has been released. There aren’t many changes, but as noted by okyeron, an additional include file is required in main.c.
But as I was updating things, I also found a number of changes to Synth_Dexed too that broke the build. The most irritating of which was including a definition of DEXED_SAMPLE_RATE which now clashes with my own definition. I’ve gone through and changed a number of DEXED_ definitions to PICODEXED_ to avoid that in the future too.
A few other changes have also been introduced, which have necessitated an update to my diff file for Synth_Dexed/src/Dexed.cpp.
But on the whole, there was nothing major in the end. This has all been uploaded to GitHub as a v0.02.
PicoDexed Architecture
I had to go back and attempt to reacquaint myself with what I’d done last time, so as part of that I’ve drawn out a rough architecture diagram as shown below.
This expands on the design notes I’ve already written up in Part 3.
The key principles are as follows:
- Core 0 is used for all IO and is largely driven by MIDI events received over serial or USB.
- Core 1 is used for all audio processing and is run continually using the Pico’s audio driver (either I2S or PWM) to use a callback function to fill a buffer with samples to be played. The samples come from Synth_Dexed.
- All synthesis parameters (including keyup/down events for playing notes) is handled within picoDexed in response to the MIDIMessageHandler receiving MIDI messages from the MIDIParser.
Looking at this, I should be able to include some additional IO handling in the main Process loop of picoDexed that runs on core 0.
I2C Displays
Hunting around for libraries to support an SSD1306 display with the Pico SDK, so far I’ve found four options:
- pico-examples – has an example app that can put simple text or an image on an SSD1306. Note this isn’t created as a library that could be used however – it is all in a single example.
- sharkis/pico-ssd1306 – what appears to be a couple of files that can be included in your own code. It is pretty low-level with minimal options for any text or graphics and there is no documentation or licensing information as far as I can see.
- daschr/pico-ssd1306 – a higher level interface that provides basic graphics primitives and simple font support. MIT license. Again a couple of files to include in your project.
- Harbys/pico-ssd1306 – the most complete support I’ve found so far. What I’d consider to be a “proper” library with what seems to be good documentation and a wide range of usage options.
Whilst tempted to go with the “proper” library, it might be a little over the top for what I really need right now, and I don’t want to include an additional third-party GitHub link in my code at this time.
So I’m going to try daschr’s pico-ssd1306 as I can just grab the files, maintain the license information, and include it directly into my code.
To include this code in the build I’ve done the following:
- Created a libs area and copied in the files: ssd1306.c, ssd1306.h, font.h
- Added the c file to the CMakeLists.txt file.
- Added libs to the include path.
- Added hardware_i2c as an included library.
My new CMakeLists.txt file now looks as follows.
cmake_minimum_required(VERSION 3.13)
include(pico_sdk_import.cmake)
include(pico_extras_import.cmake)
project(picodexed)
pico_sdk_init()
add_executable(picodexed)
target_sources(picodexed PUBLIC
${CMAKE_CURRENT_LIST_DIR}/src/main.cpp
${CMAKE_CURRENT_LIST_DIR}/src/pico_perf.cpp
${CMAKE_CURRENT_LIST_DIR}/src/mididevice.cpp
${CMAKE_CURRENT_LIST_DIR}/src/picodexed.cpp
${CMAKE_CURRENT_LIST_DIR}/src/serialmidi.cpp
${CMAKE_CURRENT_LIST_DIR}/src/sounddevice.cpp
${CMAKE_CURRENT_LIST_DIR}/src/usbmidi.cpp
${CMAKE_CURRENT_LIST_DIR}/src/usbtask.c
${CMAKE_CURRENT_LIST_DIR}/src/usb_descriptors.c
${CMAKE_CURRENT_LIST_DIR}/libs/ssd1306.c
)
add_subdirectory(synth_dexed)
target_include_directories(picodexed PUBLIC
${CMAKE_CURRENT_LIST_DIR}/src
${CMAKE_CURRENT_LIST_DIR}/libs
${CMAKE_CURRENT_LIST_DIR}/synth_dexed/Synth_Dexed/src
${CMAKE_CURRENT_LIST_DIR}/synth_dexed
)
target_link_libraries(picodexed PUBLIC synth_dexed pico_stdlib pico_multicore tinyusb_device tinyusb_board pico_audio_i2s pico_audio_pwm hardware_i2c)
target_compile_definitions(picodexed PRIVATE
PICO_AUDIO_I2S_MONO_OUTPUT=1
PICO_AUDIO_I2S_MONO_INPUT=1
USE_AUDIO_I2S=1
USE_AUDIO_PWM=1
)
pico_add_extra_outputs(picodexed)
The most basic usage is as follows:
static ssd1306_t disp;
i2c_init(i2c1, 400000);
gpio_set_function(2, GPIO_FUNC_I2C);
gpio_set_function(3, GPIO_FUNC_I2C);
gpio_pull_up(2);
gpio_pull_up(3);
disp.external_vcc=false;
ssd1306_init(&disp, 128, 32, 0x3c, i2c1);
ssd1306_clear(&disp);
ssd1306_draw_string(&disp, 8, 8, 2, "picoDexed");
ssd1306_show(&disp);
This initialises I2C bus 1 on GPIO 2 and 3 for device at address 0x3C.
Rotary Encoder
Taking the same approach with a rotary encoder, I’ve so far found the following as candidates for use:
The first is a more complete library, the second more “bare bones”. The last seems more of an example rather than a reusable object.
I was initially inclined towards the second which seemed likely to be easier to integrate into my own code, but as it was pretty low level and required a fair bit of glue around it.
Eventually I actually opted for something based on “GitJer”‘s example. There is a full explanation of how the code works here: https://github.com/GitJer/Some_RPI-Pico_stuff/tree/main/Rotary_encoder
I’ve had to change how voices are selected slightly to ensure the display, MIDI BANKSEL/PC and encoder can stay reasonably intuitive.
I’ve used the following logic:
- IF BANKSEL/PC both received then treat as Bank=0-7; Voice=0-31.
- IF PC on its own, then treat as Voice=0-127 across Banks 0 to 3.
- IF UI changes voices, then treat as Bank=0-7; Voice=0-31.
In each case the display shows the current bank and voice number in 0-7/0-31 format.
So just to be clear, MIDI Program Select on its own now (so 0..128) will always only select from the first four banks of 32 voices each. This is a change from the previous version which would allow PC to select based on the currently selected banks and the three following banks.
The encoder will automatically switch from one bank to the next, and wrap around at either end, so is able to select all voices across all installed banks.
Raspberry Pi Pico PIO Use
One issue to keep an eye on is PIO state-machine use.
I’m still getting my head around PIO (I’ve just not really devoted any significant time to figuring it out yet) but as I understand things, each PIO instance has a 32 instruction memory which is shared across four state machines.
So if there are several programmes to run and they all combined fit in the 32 instruction memory, then they could all use the same PIO instance whilst being attached to different state machines.
But if there are two vastly different programs to be running then it may be that they have to be split across the two PIO instances. But there can still be up to four instance of each program, one for each state machine in a PIO instance.
The I2S audio driver uses PIO to implement I2S. As far as I can see which PIO to use is determined by the following definitions:
PICO_AUDIO_PIO
PICO_AUDIO_I2S_PIO
PICO_AUDIO_PWM_PIO
If PICO_AUDIO_PIO is not defined then I2S_PIO and PWM_PIO are set to 0, which is then turned into “pio0” via the following in i2s_audio.c:
#define audio_pio __CONCAT(pio, PICO_AUDIO_I2S_PIO)
Which later on then uses the following to claim a free statemachine as part of audio_i2s_setup():
pio_sm_claim(audio_pio, sm);
I don’t know for certain, but it appears to me that the I2S PIO program is quite complete and so highly likely to fill the 32 word instruction memory.
On that basis, then the rotary encoder PIO program will have to use PIO instance 1 for its own code.
Summary of PIO use:
UsePIO InstanceNumber of State machinesI2SPIO 01EncoderPIO 11
Update GPIO Usage
Expanding now on the table from Part 5, updated with the above, now gives the following GPIO usage map.
GP0Debug UART TXGP1Debug UART RX (unused at present)GP2SDA (I2C bus 1) OLED displayGP3SCL (I2C bus 1) OLED displayGP4MIDI TX (unused at present)GP5MIDI RXGP6Rotary Encoder AGP7Rotary Encoder BGP8Rotary Encoder Switch (not used)GP9I2S Data (DATA, DIN)GP10I2S Bit Clock (BCK)GP11I2S “Left/Right” Clock (LCK, LRCK, LR_CLOCK)GP20Optional: PWM outputGP22Optional: Mute pin for the Pimoroni Audio Pack (not used)VSYS5V power to DAC and OLED display (if required)3V3_OUT3V3 power to MIDI IN and encoder (if required)GND
PicoDexed on a Breadboard
Closing Thoughts
I feel like this is really starting to get interesting now. I have a few choices to make now – do I attempt to go for a more complete menu system similar to MiniDexed, or do I stay with MIDI control and basic voice selection as it is now?
Future enhancements might include:
- It will be interesting to see what supporting the RP2350 could bring.
- It would be useful to be able to set the MIDI channel – either in menu, or perhaps more ideally in hardware somehow (e.g. switches, resistors on an ADC, etc).
- Act as a USB host for USB MIDI devices.
In the video I’ve hooked it up to a serial MIDI controller and am just cycling through some of the voices showing how the UI works.
Kevin