Search
406 results for “circuitpython”
-
All - in case you didn't see, @adafruit released #CircuiPython 8.1.0. This version fully supports the Fig Pi boards, as well as Mini SAM M4. Upgrading the firmware is as easy as booting into BOOT mode, and dragging the new firmware from https://circuitpython.org/board/bwshockley_figpi/ onto the drive that shows up. That's it!
-
If you want to make one for yourself, I have published the 3d printable files for my Birb Air Quality sensor on Printables. https://www.printables.com/model/450447-birb-the-canary-shaped-air-quality-sensor (code coming soon!).
#canairi #3dprinted #3dprinting #airquality #homeassistant #mqtt #sensor #electronics #maker #Adafruit #CircuitPython #PythonOnHardware #UnexpectedMaker
-
Arduino and SP0256A-AL2 – Part 3
Following on from using an Arduino as a variable clock in Arduino and SP0256A-AL2 – Part 2, I have some ideas for a few options, but this post looks in detail at using a Raspberry Pi Pico as the clock source.
Spoilers: it kind of works, but isn’t quite the answer I need yet…
- Part 1 – Basic introduction and getting started
- Part 2 – Arduino programmable clock
- Part 3 – Using a Raspberry Pi Pico as a programmable clock
- Part 4 – Using a HC4046 PLL as the clock
- Part 5 – Using an I2C SI5351 programmable clock
- Part 6 – Adding MIDI
https://makertube.net/w/bxBYCqHrZvQLwLuwYa5Z9r
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.
Using a RPi Pico
The RP2040 can be overclocked quite a bit, so generating a variable square wave up in the few MHz range should presumably be relatively straight forward. Using the built-in PIO state machines for a square wave is fairly simple and it can be done from Circuitpython or Micropython too.
This is a complete square wave generator for GP2 that steps down from 4MHz to 2MHz in steps of 100kHz. It can optionally overclock the RPi to 250 MHz too if required.
import time
import microcontroller
import board
import rp2pio
import adafruit_pioasm
square = adafruit_pioasm.assemble ("""
.program square
set pins, 1
set pins, 0
""")
RP2040Freq = 125_000_000
#RP2040Freq = 250_000_000
print ("RP2040 Frequency = ", microcontroller.cpu.frequency)
microcontroller.cpu.frequency = RP2040Freq
time.sleep(1)
print ("New RP2040 Frequency = ", microcontroller.cpu.frequency)
while True:
for freq in range (4000000, 2000000, -100000):
print("\nReqd frequency = ", freq*2)
print("Sq frequency = ", freq)
sm = rp2pio.StateMachine(
square,
frequency=freq*2,
first_set_pin=board.GP2,
)
print("Actual freq = {:d}".format(sm.frequency))
print("Actual sq freq = {:d}".format(int(sm.frequency/2)))
time.sleep(5)
sm.deinit()The PIO program itself has two instruction steps, so takes two cycles to complete, so the running frequency has to be twice the desired frequency of the square wave. It automatically keeps looping, so no additional instructions are required there.
The state machine will run at the system speed with a 16.8 fixed point fractional clock divider. Full details can be found in section 3.5.5 “Clock Dividers” in the RP2040 datasheet.
For certain values there might be some jitter:
If the system clock is faster though, the amount of jitter will be less I suspect, so it is advantageous to overclock the Pico for more accurate frequencies.
The problem with this approach is that whilst I get a nice accurate clock source with quite a good resolution across its range, every time the state machine is recreated to change the frequency, there is a “blip” in the audio from the SP0256A-AL2 whilst its clock temporarily disappears!
An alternative approach is to use a fixed state machine frequency but include a counter in the PIO program to allow for a configurable number of steps per scan of the PIO without having to stop and restart the clock.
The problem with this is that I am limited to a unit of the instruction time for the PIO state machine which gives a fixed overhead, in terms of the instructions required for a minimal loop, and a flexible overhead, in terms of the counter I can pass in.
The upshot of this is that I’m tied to a certain resolution of frequency change.
I have the following PIO code:
.program blink
.side_set 1
.wrap_target
pull noblock
mov x, osr
mov y, x
set pins, 1
lp1:
jmp y-- lp1
nop
nop
mov y, x
set pins, 0
lp2:
jmp y-- lp2
.wrapThe “pull” will update the output shift register (OSR) either with any new value written to the state machine or the last value of the X register. This value gets copied to Y to use as a counter. This happens twice, once for when the pin is set at 1 and once for when the pin is set at 0.
There are two nops whilst the pin is set at 1 to balance for the original pull and mov instructions at the end of the pin set to 0 cycle.
As the Y counter value is used twice, the flexible overhead of the timing is essentially proportional to count * 2. It counts for the pin being HIGH and then for the pin being LOW.
The fixed overhead is the cost of the original pull, two moves, the pin sets, and a single jmp per pin state – so by using the two nops to ensure the HIGH and LOW times are the same, that is 10 instruction cycles.
I was hoping to use the “side” instruction to eliminate the two set instructions, but so far I’ve not managed to get that to work. I still don’t understand PIO…
So for now the timing of the PIO routine is = 10 + 2 * count and the unit is the time for a single instruction, which is 1 / frequency of the PIO execution, up to a maximum frequency of the Pico’s system clock frequency.
Using an overclocked Pico at 250MHz, the frequency range would start at the following:
- Execution freq = Pico Sys Clock / (10 + 2 * count)
- So when count = 0; execution freq = 250MHZ / 10 = 25 MHz
That is far too fast for the SP0256A-AL5. In fact, I’ve found that anything over around 5MHz causes the chip problems.
For this reason, I’m using a minimum count of 20:
- Max execution freq = 250MHz / (10 + 2 * 20) = 5 MHz
Plotting execution frequency per “count” value (starting from 20) gives the following:
We can see the limits of the resolution at the top-end, and in fact, the first few equivalent frequencies in that range are as follows:
CountEquivalent Frequency205,000,000214,807,692224,629,629234,464,285244,310,344That is giving me something like a 150-200kHz jump each time, which isn’t great, but is probably the best I can do. I would be larger if I wasn’t overclocking the Pico. It does get smaller as the count increases, but it is only really worth going down to a count value of around 120, which is around 1MHz for the resulting clock. Anything lower than that and the SP0256A-AL2 isn’t particularly useful.
Here is the full Circuitpython code which attaches a pot to GP26 to control the frequency in the range of around 900kHz up to 5MHz. Note the scaling of the pot value (0 to 65535) by 600 prior to its use to add to the count.
import array
import time
import board
import rp2pio
import microcontroller
import adafruit_pioasm
from analogio import AnalogIn
algin = AnalogIn(board.GP26) # ADC0
blink = adafruit_pioasm.assemble(
"""
.program blink
.side_set 1
.wrap_target
pull noblock
mov x, osr
mov y, x
set pins, 1
lp1:
jmp y-- lp1
nop
nop
mov y, x
set pins, 0
lp2:
jmp y-- lp2
.wrap
"""
)
RP2040Freq = 250_000_000
microcontroller.cpu.frequency = RP2040Freq
time.sleep(1)
oldalgval = 0
sm = rp2pio.StateMachine(
blink,
frequency=RP2040Freq,
first_set_pin=board.GP2
)
sm.write(bytes(16))
while True:
algval = algin.value
if (algval != oldalgval):
oldalgval = algval
count = 20 + int(algval / 600)
freq = int (RP2040Freq / (10 + count*2))
data = array.array("I", [count])
sm.write(data)
time.sleep(0.2)One problem will be the 3V3 operating levels of the Pico. The SP0256A-AL2 datasheet states the following:
So whilst a “high logic” value for the oscillator has a minimum level of 2.5V, it also states that a minimum of 3.9V is required if driven from an external source.
If required, something like a 74HCT14, powered by 5V, can be used to level shift the 3V3 output of the Pico to a 5V signal for use with the SP0256A-AL2.
But in practice, I was finding the Pico worked fine as is. It is important to ensure both the Pico, Arduino and SP0256A-AL2 all have their grounds connected.
A this point I’m just using the Pico as a programmable clock, but if I was to go this route, then it would make sense to have the Pico drive the SP0256A-AL2 too and forgo the Arduino.
Closing Thoughts
So I have two choices if I want to use a Raspberry Pi Pico:
- Go for smooth changes of frequency, but with less resolution, especially at the higher frequencies.
- Go for more accurate resolution across the range but accept there will be blips when the clock changes which will be heard in the audio.
Neither is a perfect solution, but it shows the principles are valid. Also, using two microcontrollers is a bit over the top, so if I was to move to using a Pico, I’d probably want to find a way to drive the SP0256A from the Pico directly too and skip using an Arduino.
One benefit of that would be that I can time the frequency changes to coincide with silence in the speaking should I wish to, avoiding the possibility of major audio blips.
But I also have a few other options to try, which I’ll come back to in a future post.
Kevin
-
Small Microcontroller Displays
I found myself wanting several small displays connected to a microcontroller, so was doing a bit of a trade-off between various options, and was starting to lose track, so this post collects some of those thoughts together.
It is not meant to be a comprehensive discussion of choosing small displays for projects, but more of a reflection on the displays I already have kicking around in my parts boxes!
Most displays tend to be categorised by the driver chip they use. And then by the bus type used for their connection. So that is how I’ve grouped them here.
A really good reference for many of the displays shown here is: https://www.lcdwiki.com/Main_Page
I2C SSD1306 OLED
This is usually my “go to” set of small displays. They are generally well supported and pretty easy to use.
I generally have two variants to choose from:
On the left is the 0.91″ 128×32 OLED SSD1306 and on the right is the 0.96″ 128×54 OLED SSD1306. Common properties of both displays:
- Monochrome – white or blue.
- I2C interface.
- Usually 5V powered, but some include level shifters to allow 3V3 logic.
- Usually includes I2C pull-ups. Might be to VCC level so be wary if using a 3V3 MCU but powering from 5V – always check the voltage level prior to connecting.
- 128×32 usually a fixed I2C address (0x3C). 128×64 usually allows selection between 0x3C and 0x3D.
- There are variants with VCC and GND swapped, and other variants with SDA and SCL swapped.
Software support:
- The “go to” library for Arduino is the Adafruit_SSD1306 library, which assumes the use of the Adafruit_GFX library too.
- But there are lots of alternative libraries too, for example u8glib.
- Well supported in Circuitpython or Micropython.
- More here: Arduino Nano Every I2C and SPI Displays.
Typical Connections:
- VCC/GND (see note below re VCC vs logic levels).
- SCL/SDA – I2C pins on the MCU.
Known “gotchas”:
- Requires a chunk of memory allocated on start-up on Arduino, which can fail if there isn’t enough dynamic memory left and make a sketch hang.
- Can’t be written to from an interrupt routine (e.g. a timer).
- Low-level I2C Wire library on Arduino is blocking.
- Can sometimes be a little slow compared to alternatives.
- Limited I2C address options, so multiple display use is limited (and also increase the memory issues).
- As already mentioned, any I2C pull-ups may be pulled up to the VCC level or might be level shifted, so it is always worth checking if planning to use with a 3V3 microcontroller.
Summary:
- Cheap, pretty easy to use, and fairly universal if you want a single, small, monochrome display for simple outputs.
Other I2C Variants
There are some variants of the SSD1306 that sometimes pops up too for slightly larger displays:
- SSD1315 – apparently can simply be treated as a SSD1306 and mostly it works ok.
- SH1106 – very similar niche to SSD1306 but requires it owns driver support.
SPI ST7735/89/96 TFT
Whereas the SSD1306 I2C is pretty ubiquitous for monochrome displays, I’ve tended to find that SPI ST77xx displays fill a similar niche for small, full colour, non-touch, TFT displays. And there are loads of variations on the theme when it comes to these displays.
The 7735 supports lower resolution, smaller displays, typically up to 170×320, with the 7789 for those of 240×240 or 240×320 and similar. There is also a ST7796 which I believe uses the same driver libraries for a higher 320×480 display.
Two 7735 Displays:
These two ST7735 displays that I have are labelled:
- TFT 0.96″ 80×160 SPI ST7735
- TFT 1.8″ 128×160 SPI ST7735
These ST7789 display I have is labelled:
- TFT 1.3″ 240×240 SPI ST7789
I also have a display that was bought as a ST7789 labelled “TFT 2.8″ 240×320 SPI” which comes with a touch screen, but I can’t get this to work.
Common properties:
- SPI interface: data (SDA/MOSI/COPI), clock (SCK/SCLK), chip select (CS), data/command (SR/DC), possibly a reset.
- Typically 65536 colours, usually encoded as 5-6-5 bit RGB patterns.
- Have to check for 3V3 or 5V operation depending on the datasheet of the driver chip and design of the module.
Software support:
- Adafruit SST77x Library supports ST7735 and ST7789, once again also requiring Adafruit_GFX.
- Arduino_GFX Library supports a whole range of displays and microcontrollers, including the ST77xx devices.
- TFT_eSPI for Raspberry Pi Pico, ESP32 and others.
- MCUFRIEND_kbv for Arduino and “MCUFRIEND” type displays. Wide range of TFT display drivers supported.
- Tiny TFT Library for ATTiny85 and Compact TFT Library for a range of MCUs by David Johnson-Davies (technoblogy)
- https://github.com/bitbank2/bb_spi_lcd. Standalone library for ST7735, ST7789 and others, with a set of pre-configured “named” displays.
- More here: Arduino and a Cheap TFT Display.
Typical Connections:
- VCC/GND
- CLK/SDA – SPI Clock and Data (Data OUT from MCU – i.e. MOSI/COPI)
- RES – Reset
- DC, RS – Data/Command Register Select
- CS – Chip Select
Known Gotchas:
- Working out if RS means reset of the data/command pin; and not mixing up SCL/SDA with I2C!
- Some might include a backlight control pin too for dimming or turning it off. With this not connected the display was a maximum brightness.
- I’ve also seen talk that many of these modules themselves run at 3V3, so whilst they may include a regulator for 5V to 3V3 for power, they don’t always include level shifting for the signal pins. It seems unclear (to me at least at the moment) if it is ok to use these with a 5V microcontroller (although I have done…).
- Some of these displays are “inverted” colour wise. The use 16-bit 5-6-5 format colours, but some are RGB, some are BGR and there might be other variants too.
- Sometimes the initialiser for the library requires a SPI_MODE setting. My ST7789 240×240 required the Adafruit ST7789 initialisation as follows:
#include <Adafruit_GFX.h>
#include <Adafruit_ST7789.h>
#include <SPI.h>
#define TFT_CS 10 // (not used)
#define TFT_RST 9
#define TFT_DC 8
Adafruit_ST7789 tft = Adafruit_ST7789(TFT_CS, TFT_DC, TFT_RST);
void setup() {
tft.init(240, 240, SPI_MODE2);
}Note, unlike the monochrome displays, these have their own pixel framebuffer so memory use is much more efficient, even when used as a full colour display.
Summary:
- Cheap, pretty easy to use once the voltage levels and the pin labelling are worked out. Well supported by a number of libraries; but does require more pins for 4-wire SPI. Might need some messing around to get the right colour definitions. Otherwise a good choice if you just want a cheap, colour display with no touch.
OLED SH1122 SPI
There are actually both I2C and SPI versions of SH1122 displays, but I’m considering the SPI version here. The display I have is a 256×64, monochrome OLED display.
Software Support:
- U8G2 library:https://github.com/olikraus/u8g2
- Use the SH1122 constructors: https://github.com/olikraus/u8g2/wiki/u8g2setupcpp#sh1122-256×64
Example:
U8G2_SH1122_256X64_1_4W_HW_SPI u8g2(U8G2_R0, /* cs=*/ 10, /* dc=*/ 9, /* reset=*/ 8);
Typical Connections:
- VCC/GND – mine states it can support either 3V or 5V power
- CLK/SDA – SPI Clock and Data (Data OUT from MCU – i.e. MOSI/COPI)
- RES – Reset
- DC, RS – Data/Command Register Select
- CS – Chip Select
The _F_ constructor requires a full frame-buffer so is unlikely to work on resource constrained devices (e.g. the Arduino Uno/Nano). The _1_ constructor provides a pageable interface which allows for the updating of the display in pages, which takes longer but allows it to be used with more devices.
There are also software SPI versions that allow the use of any GPIO pins.
Summary:
- A pretty neat board if these physical dimensions match what is required.
- There are a whole pile of example sketches in the U8G2/page_buffer directory.
ILI9341/9488 TFT
Cheap larger displays are often driven by one of the ILI9341 or ILI9488 chips. The former supports 240×320 in full (16-bit) colour whereas the 9488 tends to support larger displays of up to 320×480 in full (24-bit) colour. Both support either a parallel (at least 8, 9, 16-bit) or serial (3 or 4 wire SPI) bus interface.
I don’t have a lot of detailed information for this post yet, but instead will refer to:
The ILI9341 is well supported by the Adafruit graphic libraries, but the ILI9488 is likely to require something else, as described in the above post.
These displays are often used with touch support and will often expect to run at 3V3 logic levels.
LCD 1602 HD44780
This is another very common monochrome, but text only, display. They have a 4 or 8-bit parallel interface, but it is also quite common to use the with an I2C “backpack” based on the PCF8574 I2C IO expander. Boards can be cheaply bought with or without a backpack, and the backpacks are available separately too for retro fitting to displays without them.
They are often called “1602” displays as they are two rows of 16 characters. By using custom blocks it is possible to have some simple graphics. There are LCD2004 modules too with four rows of 20 characters.
They often come with a choice of backlight colours. White or red are particularly striking! There are some variants that come with an additional I2C controller chip built in to control the backlight and some even come with a full RGB backlight capability.
Typical Connections:
- VCC/GND
- Data – either 4 or 8 bit modes support
- E, RW, RS – enable, read/write, register select.
- Backlight V+/GND – level is often fixed using a resistor.
There are core Arduino libraries to support the most basic versions of these displays:
- LiquidCrystal
- LiquidCrystal_I2C
Typical Gotchas:
- The I2C backpacks often include pull-ups to VCC, yet many of these displays require 5V even if used with a 3V3 microcontroller. One option is to remove the pull-ups and add external pull-ups to 3V3.
- If there are only blocks on the display then the communications isn’t working properly – check SDA/SCL or the control lines.
- If there is nothing on the display or the text is obscured by blocks behind it, then the contrast is either too low or too high. I2C backpacks have a potentiometer to adjust the contrast.
- More complex versions require an additional I2C setup phase, e.g. to turn on the backlight, which isn’t supported by the standard libraries.
Summary:
- Very useful if a large, high/adjustable contrast, text-only display is required.
Others
So I don’t forget when considering the above, I also have:
- GC9A01 based 240×240 1.28″ circular SPI display like this one: https://www.waveshare.com/wiki/1.28inch_LCD_Module. Note: cheaper versions of this won’t include level shifting and will expect to run at 3V3.
- ESP32 boards with built-in displays: ESP32-C3 0.42 OLED
- ESP32 “Cheap Yellow Display” boards, details here: https://github.com/witnessmenow/ESP32-Cheap-Yellow-Display
- Range of small displays from Pimoroni, Waveshare and others for use with the Raspberry Pi Pico.
- Waveshare small HDMI displays for use with a Raspberry Pi.
I’ll add to the list for my own reference as I remember other odds and ends.
Kevin
#hd44780 #ili9341 #ili9488 #include #lcd1602 #oled #ssd1306 #st7735 #st7789 #tft
-
Small Microcontroller Displays
I found myself wanting several small displays connected to a microcontroller, so was doing a bit of a trade-off between various options, and was starting to lose track, so this post collects some of those thoughts together.
It is not meant to be a comprehensive discussion of choosing small displays for projects, but more of a reflection on the displays I already have kicking around in my parts boxes!
Most displays tend to be categorised by the driver chip they use. And then by the bus type used for their connection. So that is how I’ve grouped them here.
A really good reference for many of the displays shown here is: https://www.lcdwiki.com/Main_Page
I2C SSD1306 OLED
This is usually my “go to” set of small displays. They are generally well supported and pretty easy to use.
I generally have two variants to choose from:
On the left is the 0.91″ 128×32 OLED SSD1306 and on the right is the 0.96″ 128×54 OLED SSD1306. Common properties of both displays:
- Monochrome – white or blue.
- I2C interface.
- Usually 5V powered, but some include level shifters to allow 3V3 logic.
- Usually includes I2C pull-ups. Might be to VCC level so be wary if using a 3V3 MCU but powering from 5V – always check the voltage level prior to connecting.
- 128×32 usually a fixed I2C address (0x3C). 128×64 usually allows selection between 0x3C and 0x3D.
- There are variants with VCC and GND swapped, and other variants with SDA and SCL swapped.
Software support:
- The “go to” library for Arduino is the Adafruit_SSD1306 library, which assumes the use of the Adafruit_GFX library too.
- But there are lots of alternative libraries too, for example u8glib.
- Well supported in Circuitpython or Micropython.
- More here: Arduino Nano Every I2C and SPI Displays.
Typical Connections:
- VCC/GND (see note below re VCC vs logic levels).
- SCL/SDA – I2C pins on the MCU.
Known “gotchas”:
- Requires a chunk of memory allocated on start-up on Arduino, which can fail if there isn’t enough dynamic memory left and make a sketch hang.
- Can’t be written to from an interrupt routine (e.g. a timer).
- Low-level I2C Wire library on Arduino is blocking.
- Can sometimes be a little slow compared to alternatives.
- Limited I2C address options, so multiple display use is limited (and also increase the memory issues).
- As already mentioned, any I2C pull-ups may be pulled up to the VCC level or might be level shifted, so it is always worth checking if planning to use with a 3V3 microcontroller.
Summary:
- Cheap, pretty easy to use, and fairly universal if you want a single, small, monochrome display for simple outputs.
Other I2C Variants
There are some variants of the SSD1306 that sometimes pops up too for slightly larger displays:
- SSD1315 – apparently can simply be treated as a SSD1306 and mostly it works ok.
- SH1106 – very similar niche to SSD1306 but requires it owns driver support.
SPI ST7735/89/96 TFT
Whereas the SSD1306 I2C is pretty ubiquitous for monochrome displays, I’ve tended to find that SPI ST77xx displays fill a similar niche for small, full colour, non-touch, TFT displays. And there are loads of variations on the theme when it comes to these displays.
The 7735 supports lower resolution, smaller displays, typically up to 170×320, with the 7789 for those of 240×240 or 240×320 and similar. There is also a ST7796 which I believe uses the same driver libraries for a higher 320×480 display.
Two 7735 Displays:
These two ST7735 displays that I have are labelled:
- TFT 0.96″ 80×160 SPI ST7735
- TFT 1.8″ 128×160 SPI ST7735
These ST7789 display I have is labelled:
- TFT 1.3″ 240×240 SPI ST7789
I also have a display that was bought as a ST7789 labelled “TFT 2.8″ 240×320 SPI” which comes with a touch screen, but I can’t get this to work.
Common properties:
- SPI interface: data (SDA/MOSI/COPI), clock (SCK/SCLK), chip select (CS), data/command (SR/DC), possibly a reset.
- Typically 65536 colours, usually encoded as 5-6-5 bit RGB patterns.
- Have to check for 3V3 or 5V operation depending on the datasheet of the driver chip and design of the module.
Software support:
- Adafruit SST77x Library supports ST7735 and ST7789, once again also requiring Adafruit_GFX.
- Arduino_GFX Library supports a whole range of displays and microcontrollers, including the ST77xx devices.
- TFT_eSPI for Raspberry Pi Pico, ESP32 and others.
- MCUFRIEND_kbv for Arduino and “MCUFRIEND” type displays. Wide range of TFT display drivers supported.
- Tiny TFT Library for ATTiny85 and Compact TFT Library for a range of MCUs by David Johnson-Davies (technoblogy)
- https://github.com/bitbank2/bb_spi_lcd. Standalone library for ST7735, ST7789 and others, with a set of pre-configured “named” displays.
- More here: Arduino and a Cheap TFT Display.
Typical Connections:
- VCC/GND
- CLK/SDA – SPI Clock and Data (Data OUT from MCU – i.e. MOSI/COPI)
- RES – Reset
- DC, RS – Data/Command Register Select
- CS – Chip Select
Known Gotchas:
- Working out if RS means reset of the data/command pin; and not mixing up SCL/SDA with I2C!
- Some might include a backlight control pin too for dimming or turning it off. With this not connected the display was a maximum brightness.
- I’ve also seen talk that many of these modules themselves run at 3V3, so whilst they may include a regulator for 5V to 3V3 for power, they don’t always include level shifting for the signal pins. It seems unclear (to me at least at the moment) if it is ok to use these with a 5V microcontroller (although I have done…).
- Some of these displays are “inverted” colour wise. The use 16-bit 5-6-5 format colours, but some are RGB, some are BGR and there might be other variants too.
- Sometimes the initialiser for the library requires a SPI_MODE setting. My ST7789 240×240 required the Adafruit ST7789 initialisation as follows:
#include <Adafruit_GFX.h>
#include <Adafruit_ST7789.h>
#include <SPI.h>
#define TFT_CS 10 // (not used)
#define TFT_RST 9
#define TFT_DC 8
Adafruit_ST7789 tft = Adafruit_ST7789(TFT_CS, TFT_DC, TFT_RST);
void setup() {
tft.init(240, 240, SPI_MODE2);
}Note, unlike the monochrome displays, these have their own pixel framebuffer so memory use is much more efficient, even when used as a full colour display.
Summary:
- Cheap, pretty easy to use once the voltage levels and the pin labelling are worked out. Well supported by a number of libraries; but does require more pins for 4-wire SPI. Might need some messing around to get the right colour definitions. Otherwise a good choice if you just want a cheap, colour display with no touch.
OLED SH1122 SPI
There are actually both I2C and SPI versions of SH1122 displays, but I’m considering the SPI version here. The display I have is a 256×64, monochrome OLED display.
Software Support:
- U8G2 library:https://github.com/olikraus/u8g2
- Use the SH1122 constructors: https://github.com/olikraus/u8g2/wiki/u8g2setupcpp#sh1122-256×64
Example:
U8G2_SH1122_256X64_1_4W_HW_SPI u8g2(U8G2_R0, /* cs=*/ 10, /* dc=*/ 9, /* reset=*/ 8);
Typical Connections:
- VCC/GND – mine states it can support either 3V or 5V power
- CLK/SDA – SPI Clock and Data (Data OUT from MCU – i.e. MOSI/COPI)
- RES – Reset
- DC, RS – Data/Command Register Select
- CS – Chip Select
The _F_ constructor requires a full frame-buffer so is unlikely to work on resource constrained devices (e.g. the Arduino Uno/Nano). The _1_ constructor provides a pageable interface which allows for the updating of the display in pages, which takes longer but allows it to be used with more devices.
There are also software SPI versions that allow the use of any GPIO pins.
Summary:
- A pretty neat board if these physical dimensions match what is required.
- There are a whole pile of example sketches in the U8G2/page_buffer directory.
ILI9341/9488 TFT
Cheap larger displays are often driven by one of the ILI9341 or ILI9488 chips. The former supports 240×320 in full (16-bit) colour whereas the 9488 tends to support larger displays of up to 320×480 in full (24-bit) colour. Both support either a parallel (at least 8, 9, 16-bit) or serial (3 or 4 wire SPI) bus interface.
I don’t have a lot of detailed information for this post yet, but instead will refer to:
The ILI9341 is well supported by the Adafruit graphic libraries, but the ILI9488 is likely to require something else, as described in the above post.
These displays are often used with touch support and will often expect to run at 3V3 logic levels.
LCD 1602 HD44780
This is another very common monochrome, but text only, display. They have a 4 or 8-bit parallel interface, but it is also quite common to use the with an I2C “backpack” based on the PCF8574 I2C IO expander. Boards can be cheaply bought with or without a backpack, and the backpacks are available separately too for retro fitting to displays without them.
They are often called “1602” displays as they are two rows of 16 characters. By using custom blocks it is possible to have some simple graphics. There are LCD2004 modules too with four rows of 20 characters.
They often come with a choice of backlight colours. White or red are particularly striking! There are some variants that come with an additional I2C controller chip built in to control the backlight and some even come with a full RGB backlight capability.
Typical Connections:
- VCC/GND
- Data – either 4 or 8 bit modes support
- E, RW, RS – enable, read/write, register select.
- Backlight V+/GND – level is often fixed using a resistor.
There are core Arduino libraries to support the most basic versions of these displays:
- LiquidCrystal
- LiquidCrystal_I2C
Typical Gotchas:
- The I2C backpacks often include pull-ups to VCC, yet many of these displays require 5V even if used with a 3V3 microcontroller. One option is to remove the pull-ups and add external pull-ups to 3V3.
- If there are only blocks on the display then the communications isn’t working properly – check SDA/SCL or the control lines.
- If there is nothing on the display or the text is obscured by blocks behind it, then the contrast is either too low or too high. I2C backpacks have a potentiometer to adjust the contrast.
- More complex versions require an additional I2C setup phase, e.g. to turn on the backlight, which isn’t supported by the standard libraries.
Summary:
- Very useful if a large, high/adjustable contrast, text-only display is required.
Others
So I don’t forget when considering the above, I also have:
- GC9A01 based 240×240 1.28″ circular SPI display like this one: https://www.waveshare.com/wiki/1.28inch_LCD_Module. Note: cheaper versions of this won’t include level shifting and will expect to run at 3V3.
- ESP32 boards with built-in displays: ESP32-C3 0.42 OLED
- ESP32 “Cheap Yellow Display” boards, details here: https://github.com/witnessmenow/ESP32-Cheap-Yellow-Display
- Range of small displays from Pimoroni, Waveshare and others for use with the Raspberry Pi Pico.
- Waveshare small HDMI displays for use with a Raspberry Pi.
I’ll add to the list for my own reference as I remember other odds and ends.
Kevin
#hd44780 #ili9341 #ili9488 #include #lcd1602 #oled #ssd1306 #st7735 #st7789 #tft
-
Welcome to KAMPi! My Self-Built Digital Camera
Hello everyone! I have always wanted to build my own digital camera. But not just any digital camera, one I would actually use like a regular point-and-shoot camera. I wanted something that would give me traditional feel, but still shoot in 4K. A tall order for sure, and to top it all off, I wanted to build everything myself. Well the time has come. . . Please join me in welcoming KAMPi! Check out my build below.
Why KAMPi?
KAMPi is short for “Kampay” which is Tagalog slang for “Kanpai” the Japanese word for cheers. It also sounds like CAMPi another way of saying Pi Cam, which is exactly what it is – a digital camera built using a Raspberry Pi mini computer.
What’s Inside?
I built KAMPi from the ground up: selected, wired, and soldered the hardware electronics, 3D printed the parts, and prepared the software.
For the computer internals, I chose to use a Raspberry Pi Compute Module 5 which rides on a Waveshare Nano A. The camera imager and lens are from Raspberry Pi as well, the Raspberry Pi High Quality Camera with the 16mm Telephoto Lens. For the display I used a Pimoroni HyperPixel 4.0″ Hi-Res Display. The display is connected to the Waveshare Nano A via GPIO pins (the header of the Nano I soldered myself). Everything is packed together in a tight package shown above.
For the power electronics I chose to use a Pimoroni LiPo SHIM I purchased from Adafruit. I wired it up with a on/off switch and connected it to a USB-C plug.
For the trigger mechanism I chose to use Adafruit’s KB 2040 electronic board due to it’s small form factor, and also that it works over USB-C / USB serial. I also chose to use the KB 2040 because the HyperPixel 4.0 uses all the GPIO pins of the Waveshare Nano A and USB-C / USB serial seemed like a more straightforward alternative. Alternatively I could have used I2C, via the HyperPixel 4.0, but I didn’t have enough time to go that direction before the convention. The trigger itself is an illuminated pushbutton switch also from Adafruit.
I’m no expert in 3D printing and I originally wanted the form factor to be smaller. But since the camera cable stuck out from the top, I needed to make extend the base.
So I designed and 3D printed a cap portion to hold it all together.
I even added a hinge and latch lock to for easy access.
For the software, I wanted something simple. I’m running python script to take the photos. One thing to note is that the camera does not have any autofocus (which is exactly how I wanted it). That meant I needed to see what I’m shooting before taking the photo. I added a preview in the software, so I could focus the lens, and then take the shot.
The desktop above shows the camera python script and the folder where the photos are saved. You can also see the Circuitpython mounted “disk” of the KB 2040 on the desktop. I’m also a space nerd so I chose a James Webb galaxy image as a backdrop to show off the beautiful Pimironi display. I included a fun logo and added a nice rectangle so I could easily see the program icons on the desktop too.
KAMPi in Action
KAMPi is so new, I haven’t been able to test it in the wild yet. But here are some raw unprocessed photos from my home test shots.
https://www.instagram.com/reel/DMR6OegOWSn/?utm_source=ig_web_copy_link&igsh=MzRlODBiNWFlZA==
More to come from Opensaucelive!*
Tomorrow is Opensaucelive 2025 and I thought what better place to test and share my build there. Wow, I’m so excited to share KAMPi with everyone at Opensauce. If you see me, please do say hello. I’ll also upload some photos from KAMPi at Opensauce below:
[*UPDATED July 20, 2025] See above sample photos I took at Opensaucelive 2025 using KAMPi. I chose the sharpest in focus images to share. Since it was my first time shooting with it, many of the photos came out blurry – which was exactly what I was expecting! I wanted KAMPi to emulate the feel of a film camera, capturing the moment. And KAMPi did just that. I’m also sure I’ll get better at taking photos with KAMPi with a litter more practice 🙂
[*UPDATED Sept 20, 2025] Updated the description of the KB 2040 to provide additional info on why I decided to use it over I2C.
Kampay (Cheers) for now!
Did you like my build? Would you like to learn more about it? Let me know at the comments below!
If you enjoyed reading this post please be sure to like, and follow us here at SKKAW.BLOG (IG: @skkaw) for more geek and pop-culture goodness.
#Adafruit #camera #CamPi #ComputeModule5 #digitalCamera #DIY #DIYCamera #DIYDigitalCamera #KAMPi #Opensauce #OpenSauceLive #PiCam #RaspberryPi #RaspberryPiCamera #RaspberryPiComputeModule5 #RaspberryPiComputeModule5Camera #SelfMade #selfMadeDigitalCamera #selfBuilt #SelfMade #Waveshare
-
Forbidden Planet “Krell” Display PCB Build Guide
Here are the build notes for my Forbidden Planet “Krell” Display PCB. This post just looks at building the PCB for standalone use.
Further posts will explore other uses for this PCB:
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 and electronics, see the Getting Started pages.
Bill of Materials
- Forbidden Planet “Krell” Display PCB (GitHub link below)
- Waveshare Zero format board (more here).
- 10x APA-106 through-hole programmable RGB LEDs pinout: IN-VCC-GND-OUT.
- 1x 500uF electrolytic capacitor (or thereabouts).
- 1x 47uF electrolytic capacitor.
- Optional: 2x 9-way header sockets (full or low-profile – see notes).
- Pin headers
For the MIDI circuit:
- 1x H11L1 optoisolator.
- 1x 1N4148 or 1N914 signal diode.
- Resistors: 1×10Ω, 1×33Ω, 1×220Ω, 1×470Ω.
- 1x 100nF ceramic capacitor.
- 2x 3.5mm stereo TRS sockets – pcb mount (see photo and PCB for footprint).
- Optional: 1x 6-way DIP socket.
For potentiometer circuit:
- 1 or 2 x 10K pcb-mount potentiometer (see photo and PCB for footprint).
- 1 or 2x 100nF ceramic capactiors.
For the CV input:
- 1x Thonkiconn style mono PCB mount jack socket.
- Resistors: 1x22K, 1x33K.
- 2x BAT43 Schottky diodes.
Build Steps
This posts describes a standalone module with two potentiometer controls and a MIDI circuit. For a EuroRack-style module with CV inputs refer to: Forbidden Planet “Krell” Display EuroRack Module.
Taking a typical “low to high” soldering approach, this is the suggested order of assembly:
- All diodes and resistors.
- DIP socket (if used) and TRS sockets.
- Disc capacitors.
- LEDs on rear of the board.
- 9-way headers (if used).
- Additional pin headers (if used).
- Electrolytic capacitors.
- Potentiometers on rear of the board.
Here are some build photos.
When it comes to adding the LEDs it is critical to get them in the correct pin order. These boards are designed for LEDs with two long and two shorter legs, with the pins in the order:
- Short: IN
- Short: VCC
- Long: GND
- Long: OUT
The pins need to be slightly bent to fit in the staggered footprint which means it isn’t possible to push the LEDs flush with the PCB. It is worth taking a little care to get them all to approximately the same height and vertically aligned.
Hopefully it goes without saying to be careful of rubbing the hot soldering iron tip on any of the existing plastic components.
As the footprint for the Waveshare Zero is 2.54mm too wide, it is advantageous to use a Waveshare Zero format board to help angle-in the pin headers prior to soldering.
If using full height headers there will probably be enough flex to do this afterwards. If using low-profile headers then it will be necessary to get the angle correct prior to soldering.
In the following note how the large capacitor has been bent over to lie flat.
Also, I didn’t have a 500uF or higher, so used a 470uF in a 10mm diameter package.
Testing
I recommend performing the general tests described here: PCBs.
Here is some test CircuitPython code that will check the functionality of the board with a Waveshare Zero type device. This was used with a Pimoroni Tiny2040 (which has two less pins to the Waveshare Zero devices).
Analog Input
This tests the potentiometers:
import time
import board
from analogio import AnalogIn
analog_in1 = AnalogIn(board.A2)
analog_in2 = AnalogIn(board.A3)
while True:
print(analog_in1.value,"\t",analog_in2.value)
time.sleep(0.1)On turning each of the potentiometers a value between 0 and 65536 should be printed to the serial console. Note: Mine never seems to get below 256…
LEDs
This can be used to test the LEDs. Requires the following libraries from the Adafruit Circuitpython Library Bundle:
- neopixel.mpy
- adafruit_pioasm.mpy (presumably only required on RP2040 based boards)
- adafruit_pixelbuf.mpy
import time
import board
import neopixel
pixel_pin1 = board.GP2
pixel_pin2 = board.GP3
num_pixels = 5
pixels1 = neopixel.NeoPixel(pixel_pin1, num_pixels, brightness=0.3, auto_write=False, pixel_order=neopixel.RGB)
pixels2 = neopixel.NeoPixel(pixel_pin2, num_pixels, brightness=0.3, auto_write=False, pixel_order=neopixel.RGB)
while True:
for col in [(255,0,0),(0,255,0),(0,0,255),(0,0,0)]:
for pix in range(5):
pixels1[pix] = col
pixels1.show()
time.sleep(0.5)
pixels2[pix] = col
pixels2.show()
time.sleep(0.5)
time.sleep(3)This will light each LED in turn alternating between the upper and lower sets of LEDs and then leave them off for three seconds.
MIDI IN and OUT
This requires the Adafruit MIDI library, which requires the following directory from the Adafruit Circuitpython Library Bundle:
- adafruit_midi/*
import board
import digitalio
import busio
import adafruit_midi
from adafruit_midi.note_off import NoteOff
from adafruit_midi.note_on import NoteOn
uart = busio.UART(tx=board.GP0, rx=board.GP1, baudrate=31250, timeout=0.001)
midi = adafruit_midi.MIDI(midi_in=uart, midi_out=uart)
while True:
msg = midi.receive()
if (msg is not None):
if (isinstance(msg, NoteOn)):
print (msg)
print ("Note On: \t",msg.note,"\t",msg.velocity)
midi.send(msg)
if (isinstance(msg, NoteOff)):
print ("Note Off:\t",msg.note,"\t",msg.velocity)
midi.send(msg)This will print out any received NoteOn and NoteOff messages (and only those) on the MIDI IN port and send them back out over the MIDI OUT port.
PCB Errata
There are the following issues with this PCB:
- The aforementioned Waveshare Zero footprint error.
Enhancements:
- It might have been useful to position the Waveshare board so that the USB connector could be presented to the edge of the board and thus left exposed when used with a case.
Closing Thoughts
That is the basics of the board covered. Next will be a discussion of the alternative EuroRack supporting configuration and the physical builds for both versions.
Kevin
-
Forbidden Planet “Krell” Display PCB Build Guide
Here are the build notes for my Forbidden Planet “Krell” Display PCB. This post just looks at building the PCB for standalone use.
Further posts will explore other uses for this PCB:
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 and electronics, see the Getting Started pages.
Bill of Materials
- Forbidden Planet “Krell” Display PCB (GitHub link below)
- Waveshare Zero format board (more here).
- 10x APA-106 through-hole programmable RGB LEDs pinout: IN-VCC-GND-OUT.
- 1x 500uF electrolytic capacitor (or thereabouts).
- 1x 47uF electrolytic capacitor.
- Optional: 2x 9-way header sockets (full or low-profile – see notes).
- Pin headers
For the MIDI circuit:
- 1x H11L1 optoisolator.
- 1x 1N4148 or 1N914 signal diode.
- Resistors: 1×10Ω, 1×33Ω, 1×220Ω, 1×470Ω.
- 1x 100nF ceramic capacitor.
- 2x 3.5mm stereo TRS sockets – pcb mount (see photo and PCB for footprint).
- Optional: 1x 6-way DIP socket.
For potentiometer circuit:
- 1 or 2 x 10K pcb-mount potentiometer (see photo and PCB for footprint).
- 1 or 2x 100nF ceramic capactiors.
For the CV input:
- 1x Thonkiconn style mono PCB mount jack socket.
- Resistors: 1x22K, 1x33K.
- 2x BAT43 Schottky diodes.
Build Steps
This posts describes a standalone module with two potentiometer controls and a MIDI circuit. For a EuroRack-style module with CV inputs refer to: Forbidden Planet “Krell” Display EuroRack Module.
Taking a typical “low to high” soldering approach, this is the suggested order of assembly:
- All diodes and resistors.
- DIP socket (if used) and TRS sockets.
- Disc capacitors.
- LEDs on rear of the board.
- 9-way headers (if used).
- Additional pin headers (if used).
- Electrolytic capacitors.
- Potentiometers on rear of the board.
Here are some build photos.
When it comes to adding the LEDs it is critical to get them in the correct pin order. These boards are designed for LEDs with two long and two shorter legs, with the pins in the order:
- Short: IN
- Short: VCC
- Long: GND
- Long: OUT
The pins need to be slightly bent to fit in the staggered footprint which means it isn’t possible to push the LEDs flush with the PCB. It is worth taking a little care to get them all to approximately the same height and vertically aligned.
Hopefully it goes without saying to be careful of rubbing the hot soldering iron tip on any of the existing plastic components.
As the footprint for the Waveshare Zero is 2.54mm too wide, it is advantageous to use a Waveshare Zero format board to help angle-in the pin headers prior to soldering.
If using full height headers there will probably be enough flex to do this afterwards. If using low-profile headers then it will be necessary to get the angle correct prior to soldering.
In the following note how the large capacitor has been bent over to lie flat.
Also, I didn’t have a 500uF or higher, so used a 470uF in a 10mm diameter package.
Testing
I recommend performing the general tests described here: PCBs.
Here is some test CircuitPython code that will check the functionality of the board with a Waveshare Zero type device. This was used with a Pimoroni Tiny2040 (which has two less pins to the Waveshare Zero devices).
Analog Input
This tests the potentiometers:
import time
import board
from analogio import AnalogIn
analog_in1 = AnalogIn(board.A2)
analog_in2 = AnalogIn(board.A3)
while True:
print(analog_in1.value,"\t",analog_in2.value)
time.sleep(0.1)On turning each of the potentiometers a value between 0 and 65536 should be printed to the serial console. Note: Mine never seems to get below 256…
LEDs
This can be used to test the LEDs. Requires the following libraries from the Adafruit Circuitpython Library Bundle:
- neopixel.mpy
- adafruit_pioasm.mpy (presumably only required on RP2040 based boards)
- adafruit_pixelbuf.mpy
import time
import board
import neopixel
pixel_pin1 = board.GP2
pixel_pin2 = board.GP3
num_pixels = 5
pixels1 = neopixel.NeoPixel(pixel_pin1, num_pixels, brightness=0.3, auto_write=False, pixel_order=neopixel.RGB)
pixels2 = neopixel.NeoPixel(pixel_pin2, num_pixels, brightness=0.3, auto_write=False, pixel_order=neopixel.RGB)
while True:
for col in [(255,0,0),(0,255,0),(0,0,255),(0,0,0)]:
for pix in range(5):
pixels1[pix] = col
pixels1.show()
time.sleep(0.5)
pixels2[pix] = col
pixels2.show()
time.sleep(0.5)
time.sleep(3)This will light each LED in turn alternating between the upper and lower sets of LEDs and then leave them off for three seconds.
MIDI IN and OUT
This requires the Adafruit MIDI library, which requires the following directory from the Adafruit Circuitpython Library Bundle:
- adafruit_midi/*
import board
import digitalio
import busio
import adafruit_midi
from adafruit_midi.note_off import NoteOff
from adafruit_midi.note_on import NoteOn
uart = busio.UART(tx=board.GP0, rx=board.GP1, baudrate=31250, timeout=0.001)
midi = adafruit_midi.MIDI(midi_in=uart, midi_out=uart)
while True:
msg = midi.receive()
if (msg is not None):
if (isinstance(msg, NoteOn)):
print (msg)
print ("Note On: \t",msg.note,"\t",msg.velocity)
midi.send(msg)
if (isinstance(msg, NoteOff)):
print ("Note Off:\t",msg.note,"\t",msg.velocity)
midi.send(msg)This will print out any received NoteOn and NoteOff messages (and only those) on the MIDI IN port and send them back out over the MIDI OUT port.
PCB Errata
There are the following issues with this PCB:
- The aforementioned Waveshare Zero footprint error.
Enhancements:
- It might have been useful to position the Waveshare board so that the USB connector could be presented to the edge of the board and thus left exposed when used with a case.
Closing Thoughts
That is the basics of the board covered. Next will be a discussion of the alternative EuroRack supporting configuration and the physical builds for both versions.
Kevin
-
One last thing I definitely wanted for my PicoDexed was the option for PWM output. This post is by way of a short coda detailing how to do PWM on a Raspberry Pi Pico.
- Part 1 where I work out how to build Synth_Dexed using the Pico SDK and get some sounds coming out.
- Part 2 where I take a detailed look at the performance with a diversion into the workings of the pico_audio library and floating point maths on the pico, on the way.
- Part 3 where I managed to get up to 16-note polyphony, by overclocking, and some basic serial MIDI support.
- Part 4 for the full MIDI implementation, voice loading, SysEx control and USB-MIDI.
- Part 5 details different options for building hardware to run PicoDexed.
The latest code can be found on GitHub here: https://github.com/diyelectromusic/picodexed
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.
PWM Output Circuit
The official “Hardware Design with the RP2040” guide includes a PWM circuit as follows (see section 3.4.1).
Several others have used a slightly simplified version of this circuit, essentially omitting the buffer stage (U3) and perhaps only implementing a single channel. See for example – Raspberry Pi Pico PWM Audio Output Circuit (Jeremy S. Cook) and “pico-pwm-audio” (Robin Grosset).
An alternative is this circuit used by Tod Kurt for his Circuitpython-tricks – “The output circuitry to get line-out levels is a simple 10:1 voltage-divider and a 1uF capacitor to recenter the signal around 0 volts”:
I’m going with Jeremy S. Cook and Robin Grosset and using the following (diagram from here):
This is my solderless breadboard version of the above:
Raspberry Pi Pico Audio PWM
There appears to be two ways of getting audio style PWM signals out of a Pico:
- Use the PWM peripherals.
- Use the PIO.
It would appear that Raspberry Pi’s pico_audio library in the pico-extras repository uses PIO. I haven’t found a clear explanation as to why the built-in PWM peripherals haven’t been used.
Here are some other resources that show alternative descriptions of PWM for audion on a Pico:
- Greg Chadwick’s “Playing with the Pico” series: Part 3 – “PWM Audio” – walks through the basics of using PWM and DMA.
- Ben Everard’s PIO tutorial in Hackspace magazine – a simplified PIO PWM driver.
- chipfire’s rppico-pwm-sound synthesizer – uses the Pico SDK and DMA to the PWM peripheral.
- Pico Playground sine wave example – uses the Pico Extra’s pico_audio library and PIO PWM.
- Robin Grosset’s Raspberry Pi Pico PWM Audio Project – uses the Pico SDK and the PWM peripheral.
As I’m using the pico_audio library for I2S audio, I’m going to use the default PWM pico_audio library too.
To initialise the pico_audio PWM library requires the following:
const audio_pwm_channel_config_t config =
{
.core = {
.base_pin = base_pin,
.dma_channel = dma_ch,
.pio_sm = pio_sm
},
.pattern = 3,
};
const struct audio_format *output_format;
output_format = audio_pwm_setup(pAudioFormat, -1, &config);
bool status = audio_pwm_default_connect(pBufferPool, false);
audio_pwm_set_enabled(true);Everything else is the same as for I2S.
At present, overclocking the Pico causes the PWM frequencies to be messed up, so for now the recommended configuration is 8-note polyphony, 24000 sample rate and no overclocking.
The build uses GPIO 20 for PWM.
Closing Thoughts
I’ve uploaded a prototype PWM UF2 file to GitHub too now in case anyone wants to give that a go too: https://github.com/diyelectromusic/picodexed
Hopefully there is enough information in the above to get something up and running.
Kevin
https://diyelectromusic.wordpress.com/2024/02/19/raspberry-pi-pico-synth_dexed-part-6/
-
One last thing I definitely wanted for my PicoDexed was the option for PWM output. This post is by way of a short coda detailing how to do PWM on a Raspberry Pi Pico.
- Part 1 where I work out how to build Synth_Dexed using the Pico SDK and get some sounds coming out.
- Part 2 where I take a detailed look at the performance with a diversion into the workings of the pico_audio library and floating point maths on the pico, on the way.
- Part 3 where I managed to get up to 16-note polyphony, by overclocking, and some basic serial MIDI support.
- Part 4 for the full MIDI implementation, voice loading, SysEx control and USB-MIDI.
- Part 5 details different options for building hardware to run PicoDexed.
The latest code can be found on GitHub here: https://github.com/diyelectromusic/picodexed
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.
PWM Output Circuit
The official “Hardware Design with the RP2040” guide includes a PWM circuit as follows (see section 3.4.1).
Several others have used a slightly simplified version of this circuit, essentially omitting the buffer stage (U3) and perhaps only implementing a single channel. See for example – Raspberry Pi Pico PWM Audio Output Circuit (Jeremy S. Cook) and “pico-pwm-audio” (Robin Grosset).
An alternative is this circuit used by Tod Kurt for his Circuitpython-tricks – “The output circuitry to get line-out levels is a simple 10:1 voltage-divider and a 1uF capacitor to recenter the signal around 0 volts”:
I’m going with Jeremy S. Cook and Robin Grosset and using the following (diagram from here):
This is my solderless breadboard version of the above:
Raspberry Pi Pico Audio PWM
There appears to be two ways of getting audio style PWM signals out of a Pico:
- Use the PWM peripherals.
- Use the PIO.
It would appear that Raspberry Pi’s pico_audio library in the pico-extras repository uses PIO. I haven’t found a clear explanation as to why the built-in PWM peripherals haven’t been used.
Here are some other resources that show alternative descriptions of PWM for audion on a Pico:
- Greg Chadwick’s “Playing with the Pico” series: Part 3 – “PWM Audio” – walks through the basics of using PWM and DMA.
- Ben Everard’s PIO tutorial in Hackspace magazine – a simplified PIO PWM driver.
- chipfire’s rppico-pwm-sound synthesizer – uses the Pico SDK and DMA to the PWM peripheral.
- Pico Playground sine wave example – uses the Pico Extra’s pico_audio library and PIO PWM.
- Robin Grosset’s Raspberry Pi Pico PWM Audio Project – uses the Pico SDK and the PWM peripheral.
As I’m using the pico_audio library for I2S audio, I’m going to use the default PWM pico_audio library too.
To initialise the pico_audio PWM library requires the following:
const audio_pwm_channel_config_t config =
{
.core = {
.base_pin = base_pin,
.dma_channel = dma_ch,
.pio_sm = pio_sm
},
.pattern = 3,
};
const struct audio_format *output_format;
output_format = audio_pwm_setup(pAudioFormat, -1, &config);
bool status = audio_pwm_default_connect(pBufferPool, false);
audio_pwm_set_enabled(true);Everything else is the same as for I2S.
At present, overclocking the Pico causes the PWM frequencies to be messed up, so for now the recommended configuration is 8-note polyphony, 24000 sample rate and no overclocking.
The build uses GPIO 20 for PWM.
Closing Thoughts
I’ve uploaded a prototype PWM UF2 file to GitHub too now in case anyone wants to give that a go too: https://github.com/diyelectromusic/picodexed
Hopefully there is enough information in the above to get something up and running.
Kevin
https://diyelectromusic.wordpress.com/2024/02/19/raspberry-pi-pico-synth_dexed-part-6/
-
One last thing I definitely wanted for my PicoDexed was the option for PWM output. This post is by way of a short coda detailing how to do PWM on a Raspberry Pi Pico.
- Part 1 where I work out how to build Synth_Dexed using the Pico SDK and get some sounds coming out.
- Part 2 where I take a detailed look at the performance with a diversion into the workings of the pico_audio library and floating point maths on the pico, on the way.
- Part 3 where I managed to get up to 16-note polyphony, by overclocking, and some basic serial MIDI support.
- Part 4 for the full MIDI implementation, voice loading, SysEx control and USB-MIDI.
- Part 5 details different options for building hardware to run PicoDexed.
The latest code can be found on GitHub here: https://github.com/diyelectromusic/picodexed
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.
PWM Output Circuit
The official “Hardware Design with the RP2040” guide includes a PWM circuit as follows (see section 3.4.1).
Several others have used a slightly simplified version of this circuit, essentially omitting the buffer stage (U3) and perhaps only implementing a single channel. See for example – Raspberry Pi Pico PWM Audio Output Circuit (Jeremy S. Cook) and “pico-pwm-audio” (Robin Grosset).
An alternative is this circuit used by Tod Kurt for his Circuitpython-tricks – “The output circuitry to get line-out levels is a simple 10:1 voltage-divider and a 1uF capacitor to recenter the signal around 0 volts”:
I’m going with Jeremy S. Cook and Robin Grosset and using the following (diagram from here):
This is my solderless breadboard version of the above:
Raspberry Pi Pico Audio PWM
There appears to be two ways of getting audio style PWM signals out of a Pico:
- Use the PWM peripherals.
- Use the PIO.
It would appear that Raspberry Pi’s pico_audio library in the pico-extras repository uses PIO. I haven’t found a clear explanation as to why the built-in PWM peripherals haven’t been used.
Here are some other resources that show alternative descriptions of PWM for audion on a Pico:
- Greg Chadwick’s “Playing with the Pico” series: Part 3 – “PWM Audio” – walks through the basics of using PWM and DMA.
- Ben Everard’s PIO tutorial in Hackspace magazine – a simplified PIO PWM driver.
- chipfire’s rppico-pwm-sound synthesizer – uses the Pico SDK and DMA to the PWM peripheral.
- Pico Playground sine wave example – uses the Pico Extra’s pico_audio library and PIO PWM.
- Robin Grosset’s Raspberry Pi Pico PWM Audio Project – uses the Pico SDK and the PWM peripheral.
As I’m using the pico_audio library for I2S audio, I’m going to use the default PWM pico_audio library too.
To initialise the pico_audio PWM library requires the following:
const audio_pwm_channel_config_t config =
{
.core = {
.base_pin = base_pin,
.dma_channel = dma_ch,
.pio_sm = pio_sm
},
.pattern = 3,
};
const struct audio_format *output_format;
output_format = audio_pwm_setup(pAudioFormat, -1, &config);
bool status = audio_pwm_default_connect(pBufferPool, false);
audio_pwm_set_enabled(true);Everything else is the same as for I2S.
At present, overclocking the Pico causes the PWM frequencies to be messed up, so for now the recommended configuration is 8-note polyphony, 24000 sample rate and no overclocking.
The build uses GPIO 20 for PWM.
Closing Thoughts
I’ve uploaded a prototype PWM UF2 file to GitHub too now in case anyone wants to give that a go too: https://github.com/diyelectromusic/picodexed
Hopefully there is enough information in the above to get something up and running.
Kevin
https://diyelectromusic.wordpress.com/2024/02/19/raspberry-pi-pico-synth_dexed-part-6/
-
I was browsing some of the newer ATtiny chips the other day and started to make a note of some of their properties and it made me realise I actually have quite a few different microcontrollers at my disposal and many more I could be having a look at.
But having committed to not attempting to get hold of every variant of every device to put a MIDI interface on it, I thought it would still be worth a post summarising some of the features to make selecting them in the future a little easier.
There are many comparison charts and tables online, but this is my own summary of the things that are important to me right now in terms of using them for musical purposes.
Note: I think the data is correct at the time of writing. Feel free to let me know of any mistakes. Also feel free to let me know what microcontrollers you use for music, and why, in the comments.
8-bit Microcontrollers
MCUFreqPWRGPIOADCPWMDACCommsRAMFlashATmega328P16MHz2.7-5.5V236/860UART, I2C, SPI2K32KATmega32U416MHz2.7-5.5V261280UART, I2C, SPI, USB2.5K32KATtiny858/20MHz2.7-5.5V6460USI5128KATtiny8812MHz2.7-5.5V28620I2C, SPI5128KATtiny21(2|4)
ATtiny41(2|4|6)20MHz1.8-5.5V6|12
6|12|186|10
6|10|1261UART, I2C, SPI128
2562K
4K32-bit Microcontrollers
MCUFreqPWRFPUGPIOADCPWMDACCommsRAMFlashSAMD21 (M0+)48MHz1.6-3.6VN30/381430?1SERCOM, I2S, USB4-32K32-256KSAMD51 (M4)120MHz1.6-3.6VY513237?1SERCOM, I2S, USB128-256256-1024KRP2040 (2xM0+)133MHz3.3VN304110UART, I2C, SPI, USB, PIO264KexternalESP32 (LX6)160MHz3.0-3.6VY3418162UART, I2C, SPI, I2S, Wi-Fi, BT0-2M0-4MESP32-S2 (LX7)240MHz2.8-3.6VN?432080UART, I2C, SPI, I2S, Wi-Fi0-2M0-2-4MESP32-S3 (2xLX7)240MHz3.0-3.6VY452080UART, I2C, SPI, I2S, Wi-Fi, BT0-2-8-16M0-4-8MESP32-A1S (2xLX6)240MHx3.0-3.6VY?14??2UART, I2C, SPI, I2S520K+4M0?Points of Note
- The ATmega and ATtiny devices are all 8-bit AVR architecture and might be either 3V3 or 5V operation depending on the device. Whereas the others are all 32-bit, 3V3 operation, and either ARM or Tensilica Xtensa architectures.
- The SAMD51, ESP32 and ESP32-S3 are all interesting as they include a floating point unit, which might be useful if I get into requiring mathematical synthesis.
- ATtiny2xx, ATtiny4xx, SAMD21, SAMD51, ESP32 all include a DAC which would be really useful for generating control voltages.
- ATmega32U4, SAMD21, SAMD51, RP2040 all support USB directly.
- The last one is an interesting device. The ESP32-A1S is a single module that includes an ESP32 and a CODEC module. More recent versions use the ES8388 and support two audio in/out channels. There is an Espressif Audio Development Framework for use with all ESP32-based devices.
Other MCUs of possible interest might include some of the newer RISC-V devices (e.g. ESP32-C3), the STM32 device range (the higher performing devices include floating point support, for example), the Teensy boards (which have a strong following for audio applications), and even running with the broadcom devices used on the various Raspberry Pis in “bare metal” mode.
A key tradeoff already would be choosing between a more powerful, probably 32-bit, 3V3 logic devices or a less capable 5V device.
Development Boards
I’m unlikely to be working with a microcontroller directly though, given my own level of knowledge, so I’m probably going to be looking at some kind of development board.
The following could all be possibilities if I’m happy running at 3V3.
Note, many of the form-factors, e.g. Adafruit’s QT Py or Feather, support most of the architectures – but not all are listed – just those I have or might consider. I’ve also added in some other boards that I know are often used (or shout about being used) for audio applications.
Prices are approximate at time of writing (Feb 2024).
BoardMCUArchSpeedRAM/FlashFPUGPIOADCPWMI2SDACCostRPi PicoRP20402xM0+133MHz264K/2MN27316PIO0£4XIAOSAMD21M0+48MHz32K/256KN14111111£6XIAORP20402xM0+133MHz264K/2MN11411PIO0£6XIAOESP32-S32xLX7240MHz8M/8MY1191110£8XIAOESP32-C3RISC-V160MHz4K/4M?114110£6QT PySAMD21M0+48MHz32K/256KN119911£9QT PyRP20402xM0+125MHz264K/8MN13413PIO0£10QT PyESP32-S32xLX7240MHz512K+2M/4MY13101310£15TrinketSAMD21M0+48MHz32K/256KN55211£9ItsyBitsySAMD21M0+48MHz32K/256KN23111311£12ItsyBitsySAMD51M4120MHz192K/512K+2MN2371812£15FeatherSAMD21M0+48MHz32K/256KN2062011£19FeatherSAMD51M4120MHz192K/512K+2MY2161612£23FeatherRP20402xM0+125MHz264K/8MN21416PIO0£12FeatherESP32-S32xLX7240MHz2M/4MY2162110£17BananaPicoWESP32-S32xLX7240MHz512K/2M+8MY2718810£4WROOM32ESP322xLX6<240MHz500K/448K+4MY34152512£3Teensy 3.6MK66FX1M4F180MHz256K/1MY64252212N/ATeensy 4.0IMXRT1062M7600MHz1M/2MY40143120£26Teensy 4.1IMXRT1062M7600MHz1M/8MY55183520£30Arduino MKR ZeroSAMD21M0+48MHz32K/256KN2271311£30Arduino Giga R1STM32H747XM7
M4480MHz
240MHz1M/2M?761413?2£70It is interesting to note which boards support a DAC and which support I2S, both very useful for audio applications and the number of ADCs is relevant too.
Boards specifically designed for audio processing, which I’ve no direct experience of, include:
- Pico ADK – A RP2040 based “audio development kit” with 8 ADCs and SPI DAC.
- Daisy Seed – an ARM Cortex-M7 with audio IO designed for DSP and audio applications (£35)
- Bela and Bela Mini – designed for use with Beaglebone for real-time, low-latency audio processing (~£130-£160).
And it is worth noting that the Teensy has many features well suited to audio processing, including a dedicated software audio toolkit (see below).
Software Audio Frameworks
There are a number of software frameworks for use with some of the above for audio processing:
- Phil Schatzmann’s Arduino Audio Tools.
- Espressif’s Audio Development Framework.
- The Arduino Sound Library.
- Marcel-Licence’s ML Synth Tools for ESP32.
- Teensy Audio Library.
- Arduino Giga R1 Advanced ADCDAC Applications.
- Adafruit’s CircuitPython synthio.
There is a bit of discussion about these here: Arduino Audio and MIDI Frameworks.
Closing Thoughts
I expect this page will evolve with new information, but it will be good to have a single post to refer back to.
Kevin
https://diyelectromusic.wordpress.com/2024/05/07/selecting-microcontrollers-for-music/
#adafruit #arduino #attiny #esp32 #raspberryPi #raspberryPiPico #samd21 #samd51 #teensy
-
The story so far…
- In Part 1 I work out how to build Synth_Dexed using the Pico SDK and get some sounds coming out.
- In Part 2 I take a detailed look at the performance with a diversion into the workings of the pico_audio library and floating point maths on the pico, on the way.
This post describes how I’ve set things up for some further development and the decisions I’ve made to get to the point where it can receive MIDI and actually be somewhat playable within the limitations of 10 note polyphony, a 24000 sample rate, and a single voice only on MIDI channel 1!
Update: By overclocking the Pico to 250MHz I can do 16 note polyphony at 24000 or 8 note polyphony at 48000!
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.
Optimised Dexed->getSamples
I left things in part 2 noting that Dexed itself is essentially a fully integer-implemented synth engine, so why did I need the floating point calculations. I concluded it is all due to the filter that has been added, which is based on the LP filter code from https://obxd.wordpress.com/ which was added in Dexed, but wasn’t in the original “music synthesizer for Android“.
So I’ve decided not to bother with it. If I feel like it is really missing out, then I have stumbled across the following which looks promising: https://beammyselfintothefuture.wordpress.com/2015/02/16/simple-c-code-for-resonant-lpf-hpf-filters-and-high-low-shelving-eqs/
So, here is my integer-only version of Dexed->getSamples.
void Dexed::getSamples(int16_t* buffer, uint16_t n_samples)
{
if (refreshVoice)
{
for (uint8_t i = 0; i < max_notes; i++)
{
if ( voices[i].live )
voices[i].dx7_note->update(data, voices[i].midi_note, voices[i].velocity, voices[i].porta, &controllers);
}
lfo.reset(data + 137);
refreshVoice = false;
}
for (uint16_t i = 0; i < n_samples; ++i)
{
buffer[i] = 0;
}
for (uint16_t i = 0; i < n_samples; i += _N_)
{
AlignedBuf<int32_t, _N_> audiobuf;
for (uint8_t j = 0; j < _N_; ++j)
{
audiobuf.get()[j] = 0;
}
int32_t lfovalue = lfo.getsample();
int32_t lfodelay = lfo.getdelay();
for (uint8_t note = 0; note < max_notes; note++)
{
if (voices[note].live)
{
voices[note].dx7_note->compute(audiobuf.get(), lfovalue, lfodelay, &controllers);
for (uint8_t j = 0; j < _N_; ++j)
{
int16_t tmp = audiobuf.get()[j] >> 16;
buffer[i + j] += tmp;
audiobuf.get()[j] = 0;
}
}
}
}
}With this in place, I appear to be able to comfortably cope with 8-note polyphony. At least with my test chords.
Debug Output
Now before I go too far, I want a simple way to get some output out of the device. The Pico Getting Started documentation gives an example of how to get some standard output (stdio) working. There are two options for this output (see chapter 4 “Saying “Hello World” in C”):
- Using the built-in USB serial port.
- Outputting to the UART serial port.
To use USB requires building in TinyUSB, but I’m planning on using that later. It also adds quite a lot of overhead apparently, so the default is to output to the serial port via GP0 (TX) and GP1 (RX). All that is required is to find a way to connect this up to a computer or terminal device.
There are several options: some kind of 3V3 supporting USB<->Serial converter – there are several, based on the CH240 of FTDI devices for example, although not many of the cheap ones are 3V3 compatible (don’t use a 5V board it could damage the Pico!); or using a native Raspberry Pi development environment, then simply using GPIO directly to connect the Pico to the Pi’s UART.
It is also possible to use the picoprobe firmware running on another Pico I believe, but I haven’t tried that. It wasn’t totally clear to me if that supports the USB to serial link, although it is strongly implied. The official Raspberry Pi Debug Probe definitely does however, but I haven’t got one of those at the moment.
I initially opted to use another Pico as a serial to USB gateway by running Circuitpython and the following script on boot by saving it as code.py:
import board
import busio
import digitalio
uart = busio.UART(tx=board.GP0, rx=board.GP1, baudrate=115200, timeout=0.1)
while True:
readbytes = uart.read()
if readbytes != None:
print (''.join([chr(b) for b in readbytes]))Now this just needs connected to the Pico running PicoDexed as follows:
PicoDexed Debug Pico
GP0 <----> GP1
GND <----> GNDAs this is running Circuitpython it means I also get the CIRCUITPY virtual drive appear and mounted too which isn’t ideal but not really a big issue.
Then I had a rummage in my Pico drawer looking for a neater solution and found a Waveshare RP2040-One that I’d forgotten I had! This is perfect as it has a USB plug at one end (via a shaped PCB) and GPIO at the other, including pins connected to UART 0.
I dropped Micropython onto the board this time, with the following script.
import time
from machine import UART, Pin
# Use one of the GPIO as a GND pin for the serial
gndpin = Pin(11, Pin.OUT)
gndpin.value(0)
print ("Initialising UART 0 on pins gnd=11, tx=12, rx=13...")
uart = UART(0, baudrate=115200, tx=Pin(12), rx=Pin(13))
print ("Ready")
while True:
# Read raw data version
rxdata = bytes()
while uart.any() > 0:
rxdata += uart.read(1)
time.sleep_ms(10)
if rxdata:
print(rxdata.decode('utf-8'))To keep the connections simple, I used GPIO 11 as an additional GND pin as there is only one on the board and it isn’t so convenient.
PicoDexed RP2040-One
GP0 <----> GP13
GND <----> GP11To ensure the code can output text just needs something like the following:
#include <stdio.h>
void main () {
stdio_init_all();
printf("PicoDexed...");
}Then with both devices connected to my virtual Ubuntu Linux machine, I can run minicom (once installed – it isn’t installed by default):
$ sudo minicom -b 115200 -D /dev/ttyACM0
Here is the output.
Welcome to minicom 2.8
OPTIONS: I18n
Port /dev/ttyACM0, 13:27:28
Press CTRL-A Z for help on special keys
PicoDexed...
Connecting PIO I2S audio
Copying mono to mono at 24000 HzNote, to exit minicom use CTRL-A then X.
Alternatively I could use PuTTY on Windows on the COM port associated with the “debugging” Pico.
At some point I’ll probably need to set up proper SWD debugging, but this should do for the time being.
I might also need to switch UARTs if I want to use UART 0 for MIDI, but apparently there are some defines that can be changed in the CMakeLists.txt file:
target_compile_definitions(picodexed PRIVATE
PICO_DEFAULT_UART=0
PICO_DEFAULT_UART_TX_PIN=0
PICO_DEFAULT_UART_RX_PIN=1
)PicoDexed design
It is time to start thinking seriously if I can turn this into something interesting or not, so borrowing from some of the design principles encapsulated in MiniDexed, I’ve now got a project that looks as follows:
- main.cpp -> Basic IO, initialisation and main update loop.
- picodexed.cpp -> The core synthesizer wrapper with the following key interface:
- CPicoDexed::Init -> perform all the required synthesizer initialisation.
- CPicoDexed::Process -> perform a single “tick” of the synthesizer functions, including updating the sample buffers from Dexed.
- mididevice.cpp, serialmidi.cpp, usbmidi.cpp -> placeholder classes that will eventually support MIDI message passing and a serial and USB MIDI interface. This borrows heavily from the way it is done in MiniDexed. These classes will support the following interface:
- CSerialMIDI::Init -> Initialise the hardware (same for USB).
- CSerialMIDI::Process -> poll the hardware (same for USB).
- CMIDIDevice::MIDIMessageHandler -> will be called by the lower-level devices when a MIDI message is ready to be processed. Once parsed, it will trigger calls into the PicoDexed main synthesizer to update its state.
- soundevice.cpp -> Encapsulating the interface to the pico_audio library to use I2S audio, with the following key interface:
- CSoundDevice::Init -> Set the sample rate and I2S interface pins.
- CSoundDevice::Update -> Fill the sample buffer using the provided callback, which will be a call to the Dexed->getSamples code above.
- config.h -> contains some system-wide configuration items, such as sample rate and polyphony.
PicoDexed will include the functions required to control the synthesizer. Examples include keydown and keyup functions for when MIDI NoteOn and NoteOff messages, and so on. It also includes a means to set the MIDI channel and to load a voice.
I don’t know yet if the MIDI handling will be interrupt driven or polled. I need to read up on how the Pico SDK handles USB and serial data, but I suspect a polled interface should be fine for my purposes as long as it doesn’t hold up the sample calculations, buffer filling, and sample playing.
With my first pass of this code, there is no external interface – it is still playing a test chord only. But at least most of the structure is now in place to hook it up to MIDI.
The “to do” list so far:
- Ideally find a way to better manage the Synth_Dexed changes. I should submit a PR to Holger, the creator of Synth_Dexed and discuss some conditional compilation steps.
- Hook up USB MIDI so that the Pico can act as a MIDI device and play the synth that way.
- Hook up serial MIDI too.
- Implement volume. Without the filter there is currently no volume changing.
- Implement some basic voice and bank loading.
- Connect up some more core MIDI functionality for program change, BANKSEL, channel volume, master volume, and so on.
- Think about how best to utilise the second core – in theory it should be possible to expand it to 16-note polyphony by using both cores. Or an alternative might be two instances of Synth_Dexed running, so making a second tone generator.
MIDI/Serial Handling
Rather than jump into USB, I’ve opted to get serial MIDI working first. The serial port handling I’ve implemented in serialmidi.cpp borrows heavily from the “advanced” UART example: https://github.com/raspberrypi/pico-examples/tree/master/uart/uart_advanced
It is interrupt driven and shares a simple circular buffer with the main Read function based on the implementation described here: https://embedjournal.com/implementing-circular-buffer-embedded-c/.
The basic design of the serial MIDI interface is as follows:
Interrupt Handler:
Empty the serial hardware of data writing it to the circular buffer
Init function:
Initialise the UART as per the uart_advanced example
Install the interrupt handler and enable interrupts
Process function:
Call the MIDI device data parser to piece together any MIDI messages
Call the MIDI device msg handler to handle any complete MIDI messages
Read function:
Read the next byte out of the circular bufferThere is a common MIDI device that the serial MIDI device inherits from (and that I plan to also use with USB MIDI support when I get that far). This has the following essential functionality:
MIDIParser:
Read a byte from the transport interface (e.g. the serial MIDI Read)
IF starting a new message THEN
Initialise MIDI msg structures
IF a single byte message THEN
Fill in MIDI msg structures for single-byte message
return TRUE
IF there is a valid Running Status byte stored THEN
IF this now completes a valid two-byte msg THEN
Fill in MIDI msg structures for a two-byte message
return TRUE
Otherwise process as a two or three byte message
IF message now complete THEN
Fill in MIDI msg structures
return TRUE
return FALSE
MIDI Message Parser:
IF MIDI msg already processed THEN return
IF on correct channel or OMNI THEN
Based on received MIDI command:
Extract parameters
Call appropriate picoDexed function
Mark MIDI msg as processed.I had a fun bug where in the serial handling, I was writing to a byte one-out in the circular buffer which meant that the MIDI handling largely worked, but only when using a controller with ActiveSensing – basically the reception of the extra byte “pushed through” the previous message. But it was a bit unresponsive, and occasionally a note of a chord would sound after the others.
I spent the better part of a day instrumenting the code, attempting to work out where the delays might be coming from. Eventually I got so fed up with the active sensing reception clouding my analysis (and triggering my scope when I didn’t want it to) that I filtered it out in the serial interrupt routine – so as early as I could.
This the made the delay a whole pile worse! That was the point I realised it was continually essentially one message behind. As a consequence I had another look at the buffer handling and that was when I realised the mistake.
Multicore support
My initial thought on the above problem was that it was a performance issue – that the MIDI handling wasn’t responsive enough. So I pushed ahead and moved all the synthesis code over to the second core. This is something I wanted to do anyway as I always had the plan of splitting the functionality across the two cores.
To enable multicore support requires including pico_multicore in the list of libaries in the CMakeLists.txt file and then it should largely be a case of doing the following:
#include "pico/multicore.h"
void core1_entry (void)
{
// stuff to do to initialise core 1
while (1)
{
// Stuff to do repeatedly on core 1
}
}
// Rest of "normal" (core 0) initialisation code
multicore_launch_core1 (core1_entry);The question is where to enable this. Eventually I settled on implementing this in picoDexed itself to split out the ProcessSound function over to the second core. This required the following:
- PicoDexed::Init – initialise multi-core support and start the second core running.
- PicoDexed::Process – no longer calls ProcessSound.
- PicoDexed::core1_entry – now calls ProcessSound in an infinite loop.
In order to ensure that I don’t get Dexed into an inconsistent state, I’ve protected the calls into Dexed from the Dexed_Adaptor with spinlocks (mirroring what was happing in MiniDexed) as shown in the following extract:
class CDexedAdapter : public Dexed
{
public:
CDexedAdapter (uint8_t maxnotes, int rate)
: Dexed (maxnotes, rate)
{
spinlock_num = spin_lock_claim_unused(true);
spinlock = spin_lock_init(spinlock_num);
}
void getSamples (int16_t* buffer, uint16_t n_samples)
{
spin_lock_unsafe_blocking(spinlock);
Dexed::getSamples (buffer, n_samples);
spin_unlock_unsafe(spinlock);
}
private:
int spinlock_num;
spin_lock_t *spinlock;
}Spinlocks are described in chapter 4.1.19 of the RPi C/C++ SDK and are part of the hardware_sync library.
In order to ensure that the spin_locks are not held too long, and to allow things like keyup/down events to be registered in a timely manner and not hold up core 0 whilst core 1 is calculating samples, I’ve now reduced the sample buffer to 64 bytes.
As core 1 is essentially free-running calculating samples now, I figured it wouldn’t make much difference how many samples are calculated in each “chunk” but going to 64 from 256 gives four times the number of break points in the cycle where other events can be processed between the spin_locks.
Once consequence of running multi-core seems to be that I can now push the polyphony up to 10 simultaneous notes without any artefacts.
If I can find a way to keep some of the sound generation on core 0 too, I might be able to increase that even further, although getting 6 additional sound engines running to get up to the magic 16 note polyphony might be stretching things still. The trick will be finding a way to trigger and mix samples from the sound generators in the two cores, as all of that current happens within Dexed itself.
Overclocking the Pico
There have been a number of experiments already in seeing how far a Pico can be pushed. There is a standard API call to set the system clock: set_sys_clock_khz(), although not all values can be accurately configured. But general wisdom seems to be that running the Pico at 250MHz isn’t a big deal…
Of course, at this point it is running outside of the “normal” spec, so the long term effects may well reduce the life of the Pico…
But by doing this, the Pico is now running twice as fast and so can now easily cope with 16 note polyphony at a sample rate of 24000, or up the sample rate to 48000 and stick with 8 note polyphony.
It might even raise the possibility of running two tone generators, one on each core! It really does open up a wide range of possibilities!
Closing Thoughts
I’m really pleased with the progress so far. I was starting to think there wouldn’t be a usable combination possible, but 10-note polyphony at a sample rate of 24000 isn’t too bad for a 133MHz CPU with no FPU.
I think my basic design goal would be for something usable with a MIDI controller. I’m not looking to implement a UI like there is with MiniDexed as part of this build. But I do need a bit more MIDI functionality first and I would like to find a way to squeeze some sample calculations out of core 0 when it isn’t handling MIDI.
I also want to get USB MIDI up and running too. I’m not sure if I want to push for both device and host USB support though. I’ll see how complicated it all is!
In the video, I’ve used my Raspberry Pi Pico MIDI Proto Expander. It just needs the addition of the Pimoroni audio board and it is ready to go!
Of course the key question is: would I recommend this to anyone? Answer: no! No way – get yourself a Raspberry Pi Zero V2 and run a full-blown set of 8 DX7s using MiniDexed 🙂
Still for me, this is a bit of fun a really good excuse to do something that’s been on my “to do” list for ages – start getting to grips with the Raspberry Pi Pico C/C++ SDK and the RP2040.
Kevin
https://diyelectromusic.wordpress.com/2024/02/04/raspberry-pi-pico-synth_dexed-part-3/
-
Raspberry Pi RP2350 board offers NB-IoT cellular connectivity, GNSS, and Wi-Fi indoor location
-
ACEBOTT QD023 ESP32-based gesture control glove tracks finger movements with potentiometers
-
Wrapped up my latest custom #keyboard today. It’s a single handed keeb designed exclusively for playing #ArcRaiders . Designed in TinkerCad and #3dprinted on my #Prusa mk4. Then hand painted with Panduro Hobby Acrylics and dusted with weathering powders. Running #kmk on a #seeed Xiao #rp2040. #circuipython #kmk