🌀 Leuze Encoders Now Available at Smidmart!
Looking for precision? Find Leuze rotary encoders in stock at Smidmart – your one-stop shop for industrial automation components.

📦 Ready to ship
📍 Only at: www.smidmart.com?utm_source=mastodon&utm_medium=Zoho+Social
📞 Enquire now: 8956167814

#LeuzeEncoder #Smidmart #IndustrialAutomation #RotaryEncoder #MadeForMachines #AutomationSolutions

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

#dac #dx7 #i2c #midi #picodexed #raspberryPiPico #rotaryEncoder #synthDexed

Raspberry Pi Pico Synth_Dexed?

For a while I’ve wanted to get Synth_Dexed up and running on a Raspberry Pi Pico. This is the library written by Holger Wirtz for use with the Teensy microcontroller, and the core synth engin…

Simple DIY Electronic Music Projects

Duppa I2C MIDI Controller – Part 4

This is revisiting my Duppa I2C MIDI Controller this time using a Waveshare Zero format device.

  • Part 1 – getting to know the devices and testing them out.
  • Part 2 – adding MIDI to the LED ring and I2C encoder.
  • Part 3 – adding normal encoder, plain potentiometer, and endless potentiometer control.
  • Part 4 – revisits the idea with Waveshare Zero format devices and adds USB MIDI.

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 Arduino, see the Getting Started pages.

Parts list

  • Waveshare Zero ESP32-S3 or RP2040.
  • DuPPa small RGB LED Ring.
  • 10K potentiometer.
  • Bespoke hook-up wires (available from duppa.net).
  • Optional: 3V3 MIDI Interface.
  • Breadboard and jumper wires.

Waveshare Zero, Duppa LED Ring, Potentiometers

I’m planning on being able to use any Waveshare Zero format board that I have (so that includes ESP32-S3, ESP32-C3 and RP2040) with the Duppa Ring so that means finding common pins to support I2C.

From the various pinouts (see Waveshare Zero, Pimoroni Tiny, and Neopixels) I can see I can use two pins in the bottom right-hand (with the USB connector at the top) corner of the board.

I’ll also need an analog connection and potentially connecting RX/TX to MIDI.

The various pins I’ll be using are as follows:

PinFunctionESP32-S3ESP32-C3RP204015V2GND33V34ADCGP1GP0GP29/A313SCLGP10GP9GP514SDAGP11GP10GP417RXGP44GP20GP118TXGP43GP21GP0

Note, I’m not using physical pins 11 and 12, even though they also support I2C, as for the RP2040, these are on I2C bus 1, not 0 (see note later).

As the Pimoroni Tiny2040 is largely compatible too, that could also be used, but it will be physical pins 11 and 12, corresponding to GP5 and GP4, and 15 and 16 for GP1, GP0 (RX,TX).

The LEDs on the LED ring are powered from 5V, which comes directly off the Waveshare Zero USB port. The logic “VIO” is powered from 3V3.

The Code

I2C LED Ring

As the I2C pins to be used are configurable, this means changing the Duppa example code (and any other Arduino code) to initialise the I2C bus on specific pins as follows:

Wire.begin(11,10); // SDA, SCL for ESP32-S3
Wire.begin(10,9); // SDA, SCL for ESP32-C3

Using the ESP32 Arduino Core, there is a specific board entry for the Waveshare Zero ESP32-S3. There isn’t one for the ESP32-C3 so I just used “ESP32C3 Dev Module”.

I used the Arduino core for RP2040 from here rather than the official core: https://github.com/earlephilhower/arduino-pico

But the I2C initialisation is a little different.

Wire.setSDA(4);
Wire.setSCL(5);
Wire.begin();

If I’d have been using GP6 and GP7, then these would have required the initialisation of Wire1 rather than Wire with the RP2040 core.

Note: to use the serial port once a sketch has been loaded onto the board, requires the following to be set (via the Arduino Tools menu):

USB CDC On Boot -> Enabled

Once again, I’ve soldered the jumpers on the LED ring to enable pull-ups and set the address for S1 and S5, so that has to be changed in the demo code too.

Analog Potentiometer

In terms of analog read, the ESP32 has a resolution of 0..4095 compared to the Arduino’s 0..1023, so that has to be taken into account when calculating the MIDI CC values.

To do this, the reading has to be divided by the ratio of Pot Range / 128.

int newpot = algpot.avgeAnalogRead(PIN_ALG) / ((MAX_POT_VALUE+1)/128);

Serial MIDI

For these boards, the serial port has to be specified. There are different options depending on the board being used (more here).

To use the pins nominally designated as RX/TX on all of these boards, use:

// ESP32-S3 GP43,GP44 or ESP32-C3 GP20,GP21
MIDI_CREATE_INSTANCE(HardwareSerial, Serial0, MIDI);
// RP2040 GP1,GP0
MIDI_CREATE_INSTANCE(HardwareSerial, Serial1, MIDI);

It is a quirk of the RP2040 Arduino core that UART0 appears on Serial1 and UART1 on Serial2. Serial0 does not exist but USB is Serial (more here).

Also, for the RP2040 the pins can be changed prior to calling MIDI.begin() if required as follows:

Serial1.setRX(rxpin);
Serial1.setTX(txpin);
MIDI.begin();

MIDI USB

I want to make this a fairly stand-alone MIDI USB device, so for the ESP32 and RP2040 this means using the TinyUSB stack. There is an Adafruit library for Arduino that supports both and also works with the Arduino MIDI Library. References:

I’ve cribbed most of the code from: https://github.com/adafruit/Adafruit_TinyUSB_Arduino/blob/master/examples/MIDI/midi_test/midi_test.ino

And added the appropriate parts to my own midiSetup() and midiLoop() functions.

MIDI USB – ESP32-S3

For this to work on the ESP32-S3, the board settings (via the Arduino Tools menu) need to be changed as follows:

USB CDC On Boot -> Enabled
USB Mode -> USB-OTG (TinyUSB)
USB Firmware MSC On Boot=Disabled
USB DFU On Boot=Disabled

Naturally this means USB can’t be used for serial output anymore.

It also means that automatic sketch reset and download often didn’t work for me. It was quite normal to now have to use the BOOT and RESET buttons to get the ESP32 back into listening for a new sketch – not always, but also not uncommon. But this might be some serial port remapping weirdness that often occurs when the USB stack is running on the same microprocessor as the main code…

MIDI USB – RP2040

For the RP2040, the USB stack needs to be changed from the Pico SDK to TinyUSB, so in the Tools menu:

USB Stack -> Adafruit TinyUSB

There are some other RP2040 specific notes here, but I don’t believe they apply unless one is interested in rebuilding the core, default TinyUSB support directly.

USB MIDI – ESP32-C3

I don’t believe USB MIDI works on the ESP32-C3

Adafruit TinyUSB doesn’t seem to anyway and I haven’t looked into what the options might be yet.

Other Notes

I’ve left in all the conditional compilation from Duppa I2C MIDI Controller – Part 3 but for now am just working with potentiometer control.

Pretty much everything is configurable, but the most important config option is to specify the board at the top:

//#define WAVESHARE_ESP32S3
//#define WAVESHARE_ESP32C3
#define WAVESHARE_RP2040

I could probably auto detect from the build settings but for now, this will do.

Other options include GPIO pins, whether to include serial or USB MIDI (or both), and whether to enable MIDI THRU or not.

Find it on GitHub here.

Closing Thoughts

This is the first go at getting my Duppa controller working on 3V3 Waveshare Zero format boards, and so far it looks pretty good.

For the ESP32-S3 and RP2040 being able to enable MIDI USB is particularly useful. I might see if I can support MIDI THRU across the interfaces, which might be handy for a built-in USB to serial MIDI converter, but for now MIDI THRU is on the same port only.

I’ve not tested this with encoders or the endless potentiometer, but in theory it ought to work. I’d have to add some conditional compilation for GPIO numbers if I want to keep the same physical pins again.

Kevin

#controlChange #duppa #endlessPotentiometer #esp32c3 #ESP32s3 #i2c #midi #potentiometer #rgbLed #rotaryEncoder #rp2040 #WaveshareZero

Duppa I2C MIDI Controller – Part 3

This is a follow up post to Part 2 expanding on the code and adding in some of the alternative control options I mentioned. Warning! I strongly recommend using old or second hand equipment for…

Simple DIY Electronic Music Projects

Duppa I2C MIDI Controller – Part 3

This is a follow up post to Part 2 expanding on the code and adding in some of the alternative control options I mentioned.

  • Part 1 – getting to know the devices and testing them out.
  • Part 2 – adding MIDI to the LED ring and I2C encoder.
  • Part 3 – adding normal encoder, plain potentiometer, and endless potentiometer control.
  • Part 4 – revisits the idea with Waveshare Zero format devices and adds USB MIDI.

https://makertube.net/w/ncLFMqBwCUcrrM4r3mHJwd

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 Arduino, see the Getting Started pages.

Parts list

  • Arduino Uno/Nano.
  • DuPPa small RGB LED Ring.
  • 10K potentiometer.
  • KY-040 rotary encoder module.
  • Bespoke hook-up wires (available from duppa.net).
  • Breadboard and jumper wires.

The Circuit

This circuit shows all three control options on the same diagram, but naturally use whichever one you require. There are the following three options:

  • Duppa I2C Encoder (from the previous project) on A4, A5 (SDA, SCL).
  • Plain potentiometer on A0.
  • Rotary encoder on D10, D11, D12.

In addition to the I2C LED Ring on A4, A5 (SDA, SCL) of course.

Here is a separate diagram for the endless potentiometer. I can be used alongside the above, but I’ve drawn it on its own for clarity.

Mine has the pins in the following order: Wiper A, GND, Wiper B, VCC.

The Code

This uses the code from Part 2 as a starting point and adds in some additional features.

The main aim is to add additional control options and then make them selectable in code. There are several definitions at the top of the file. One of them should be uncommented to enable that control option.

//#define CC_ENCODER
#define CC_POTENTIOMETER
//#define CC_I2CENCODER
//#define CC_ENDLESSPOT

Each control method will have some associated functions, but the interface will be hidden away behind these definitions and called from the main code, something like the following.

#ifdef CC_ENCODER
#include <librarycode.h>
... control specific definitions ....

void encSetup() {
// Control specific setup code
}

void encLoop() {
// Control specific loop code
}
#else
void encSetup() {}
void encLoop() {}
#endif

If that control option is not enabled, then it will just end up with the two empty functions.

Normal Potentiometer

This is fairly straightforward. I’m using the averaging technique I’ve mentioned before (details here) and include a counter so that the pot isn’t read on every scan, otherwise it slows down all other processing significantly.

The pot reading is scaled down to 7-bits from the default 10-bit value with a bitshift.

I’ve opted to have a jump if the CC value is updated over MIDI rather than anything more sophisticated, as that is probably the simplest.

All the same display options are available as used previously: one LED per CC; scaled to multiples of the ring size; or scaled to a single ring.

This works quite well with all of them, but probably makes most sense when the MIDI CC range is scaled to a single revolution of the LED ring.

CC_WRAP has no effect when used with a normal potentiometer, as the pot itself does not wrap around.

Rotary Encoder

This is using the same encoder library I’ve used before in my Arduino MIDI Rotary Encoder Controller. This makes the scanning code relatively straight forward.

I’ve refactored out the increment/decrement functions from the I2C encoder code into midiCCIncrement and midiCCDecrement, so these can now be used by both encoder options.

These encoder modules are often switched, but I’m not making use of the switch here.

Once again, all the same display options are available: one LED per CC; scaled to multiples of the ring size; or scaled to a single ring. CC_WRAP can be on or off.

Endless Potentiometer

There is a detailed discussion of how these work here: https://hackaday.io/project/171841-driveralgorithm-for-360-deg-endless-potentiometer
My initial thought was that I could just use one of the wipers, assuming it would go from 0 to full resistance and immediately back to zero, but they don’t – they gradually go from 0 to full resistance and then gradually back to 0 again. See the diagram in the above link.

This means that some processing is required to get a single reading out of them, so I ended up using a library from here:

Well actually, I ended up using the slight variant of the library as used on the “Ottopot” MIDI controller, which can be found here:

In my case I just dropping in the endlesspotentiometer.cpp/h files into my Arduino sketch (swapping any includes from <> to “” in the process). There was one reference to main.h that needed removing, and it required a definition of MAX_POT_VALUE which is 1023 for an Arduino Uno.

Then the code is fairly straight forward as the library is able to give an indication of direction and how much the pot has moved.

One thing to watch out for – I wanted this to be able to act on midiCC in a relative manner, replication the benefits of an encoder, but with a potentiometer, so I needed to know how much the pot had changed and then add that to the current midiCC value, rather than set it directly.

To do this I allowed midiCCIncrement/Decrement to take a parameter – how far to increase or decrease midiCC.

The core code for handling the endless pot was thus:

endpot.updateValues(epot1.avgeAnalogRead(PIN_EALG_1),
epot2.avgeAnalogRead(PIN_EALG_2));
if (endpot.isMoving) {
if (endpot.direction == endpot.CLOCKWISE) {
midiCCIncrement(endpot.valueChanged);
} else if (endpot.direction == endpot.COUNTER_CLOCKWISE) {
midiCCDecrement(endpot.valueChanged);
}
}

I also turned the potentiometer averaging code into a class of its own so I could also use it here for both analog readings of the endless pot.

It took a bit of experimentation with the sensitivity and threshold settings and how they interacted with the frequency of reading, but I’ve ended up with something that seems to work ok for me.

Summary

Although at the start I said that one of the options should be commented out to select it, in reality, if the devices are all on separate IO pins, then actually they can all be enabled at once.

And it does seem to work pretty well, with all four methods: I2C encoder, plain encoder, potentiometer – all interacting as you might expect they would.

Find it on GitHub here.

Closing Thoughts

I was quite surprised how usable everything was with all four input methods enabled. I probably wouldn’t recommend it for typical use, but it was a bit of fun.

It is particularly satisfying to sweep through the entire range using the pot with “one LED per CC” enabled, even though scaling a single ring to the potentiometers range makes more sense (to me).

At some point I will try to get several controls up and running.

Kevin

#arduinoNano #duppa #endlessPotentiometer #i2c #ifdef #midi #potentiometer #rgbLed #rotaryEncoder

Duppa I2C MIDI Controller – Part 2

This is a follow up post to Part 1 where I’m starting to look at MIDI applications and a range of control options. Part 1 – getting to know the devices and testing them out. P…

Simple DIY Electronic Music Projects

Duppa I2C MIDI Controller – Part 2

This is a follow up post to Part 1 where I’m starting to look at MIDI applications and a range of control options.

  • Part 1 – getting to know the devices and testing them out.
  • Part 2 – adding MIDI to the LED ring and I2C encoder.
  • Part 3 – adding normal encoder, plain potentiometer, and endless potentiometer control.
  • Part 4 – revisits the idea with Waveshare Zero format devices and adds USB MIDI.

Since posting the first part, I’ve stumbled across a rather excellent DIY MIDI controller that uses 8 of these Duppa LED rings and 8 endless potentiometers (which I hadn’t realised was even a thing!). It is a fantastic build, based on a Teensy and PlatformIO and I totally recommend going and taking a look. Read about it here: https://gerotakke.de/ottopot/.

https://makertube.net/w/2oKgvpZ29L2oExanSaUXjE

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 Arduino, see the Getting Started pages.

Parts list

  • Arduino Uno/Nano.
  • DuPPa small RGB LED Ring.
  • Mini I2C Encoder.
  • Bespoke hook-up wires (available from duppa.net).
  • Breadboard and jumper wires.

The Circuit

I eventually want to include various options for controlling the ring and MIDI:

  • Duppa I2C rotary encoder.
  • Plain rotary encoder.
  • Simple potentiometer.
  • Endless potentiometer.

I don’t have any endless potentiometers yet, they are something I’ve only recently found exist, but I’ll post again when I get a chance to try them!

The required connections, naturally, are quite different for each case:

  • Duppa I2C encoder: connects to SDA/SCL.
  • Plain rotary encoder: requires two digital IO pins.
  • Simple potentiometer: requires a single analog input.
  • Endless potentiometer: requires two analog inputs.

In this first post, I’m just looking at the same Duppa I2C Encoder and LED Ring that I used in Part 1 but am adding a 5V compatible MIDI module.

The Code

Once again, this is using the Duppa Arduino library: https://github.com/Fattoresaimon/ArduinoDuPPaLib/

CC, Pots, Encoders…

The biggest issue I find with attempting to develop a MIDI CC controller is where is the authoritative definition of what the CC value actually is? What do I mean by that? Well we have the value in the synthesizer, defined on power up or via the on-board controls. And then we have the setting in the external MIDI controller. Until the MIDI controller sends an updated value to the synth, these will be different. And if the value on the synth changes locally, e.g. using knobs or controls on the synth, then they will be out of sync.

I’ve not found an easy answer to this, but what I’m planning is having the CC controller receive MIDI CC messages as well as send them. This means that if the CC value is changed directly on the synth, if the synth transmits that over MIDI, then it will be received by the external controller which can update its own value accordingly.

One problem with this is that there are two types of hardware controller: relative or absolute.

A rotary encoder is a relative controller – it turns in a direction and the value will increase or decrease accordingly. If the internal knowledge of the CC value changes, the encoder will just continue to increase of decrease from that new value instead.

A potentiometer is (usually) an absolute controller – it turns and has a value. If the internal knowledge of the CC value changes, then unless you have a motorised potentiometer, it will still be in the same place so on turning there will be a jump in value from the stored value to the new setting of the potentiometer.

One option to deal with absolute values is to have the new position value only become relevant once the turning “catches up” with the internal value and the starts adjusting it from that point onwards. But this can create a disjoint between the user experience of turning the knob and it actually changing anything. But on the plus side, absolute values are “remembered” when powered off – providing the knobs are left in the same place.

I’m hoping to use the encoders as a pseudo potentiometer. But it isn’t going to be possible to have a complete rotation considered the same as a full sweep of a potentiometer, as that will be down to the number of “detents” per rotation and that won’t be anything like enough to support 128 MIDI CC values. But I do plan to indicate the value by LEDS, and use those to indicate the position in the full sweep. This will allow the starting point to change if a CC message is received.

One solution to this problem, and that used by the Ottopot controller mentioned at the start, is to use an endless potentiometer. This not-only allows a variable starting position, but it also represents a full-sweep of values with a single turn as per a simple potentiometer.

So when I get hold of some of those I’ll come back to revisit this code.

For this first version there is code for the I2C encoder implemented using the following functions:

  • i2cEncSetup()
  • i2cEncLoop()
  • i2cEncIncrement()
  • i2cEncDecrement()

These are based on the code I used in Part 1. The increment and decrement functions act on a global “midiCC” directly, which stores the CC value to use using the range of a single MIDI “byte” 0 to 127. There is a compilation option to allow wrapping around (between 0 and 127) or not.

MIDI Handler

The Arduino MIDI library is used for both send and receive and all MIDI functionality is wrapped up in the following functions:

  • midiSetup()
  • midiLoop()
  • midiControlChange()

There are a few additional functions to give an optional LED indication of MIDI activity. Within the MIDI loop the midiCC value is checked and if it has changed then a MIDI control change message is transmitted:

void midiLoop() {
MIDI.read();
if (lastMidiCC != midiCC) {
MIDI.sendControlChange(MIDI_CC, midiCC, MIDI_CHANNEL);
}
}

There is an option to have software MIDI THRU enabled and this is handled as part of the MIDI.read() call. On setup midiControlChange() is installed as a handler function for MIDI CC messages and if the correct CC message is received on the correct MIDI channel then the midiCC value is updated directly.

One consequence of using midiCC directly and it being changed by either the encoder or from MIDI is that any change will also trigger the sending of the CC value out too.

This means that if MIDI THRU is enabled and a MIDI CC value is sent to the Arduino then it will almost certainly be sent back over MIDI twice – once as part of the THRU handling, and once as a consequence of it having changed the Arduino’s stored midiCC value.

LED Ring Indicator

The simplest implementation will be to scale the 24 LEDs of the ring to the 128 MIDI values and light up the LED that best represents the chosen value.

An alternative is to use one LED per MIDI CC value and allow the ring to loop round, possibly in a different colour. For 128 values across 24 LEDs this means there will be five full circles of the ring plus 8 more.

I’ve also provided the option to scale the MIDI CC values to a multiple of the LED ring so that the full MIDI 0..127 range wraps around back to 0 back at the start of the ring.

In the following, scalemax is the largest multiple of NUM_LEDS that will fit in 128, then the midiCC value is scaled to that new range and then used in the rest of the LED calculation.

int scalemax = 128 - 128 % NUM_LEDS;
int midiCC_scaled = (midiCC * scalemax / 128);
nextLed = midiCC_scaled % NUM_LEDS;
uint32_t col=0;
if (midiCC_scaled < NUM_LEDS) {
col = 0x00003F;
} else if (midiCC_scaled < NUM_LEDS*2) {
col = 0x003F3F;
} else if (midiCC_scaled < NUM_LEDS*3) {
col = 0x003F00;
} else if (midiCC_scaled < NUM_LEDS*4) {
col = 0x3F3F00;
} else if (midiCC_scaled < NUM_LEDS*5) {
col = 0x3F0000;
} else {
col = 0x3F003F;
}

One quirk to note is that the LEDs are numbered anti-clockwise, so at some point I’ll have to reverse the LED number when it comes to presenting an increasing CC value as a clockwise changing display.

I’d also like to have a bit of a lazy, dragging LED change, so I want to implement something that fades illuminated LEDs out as the value changes, leaving some kind of “tail” as the LED moves.

To do this, I’ve used an array to store the colour value used for any illuminated LEDs and then at regular intervals that colour is updated to fade back to OFF.

I’ve implemented a relatively simple fade – basically each of the R, G and B components of the colour is bit-shifted to the right by 1 on each “scan”. This has the effect of continually dividing the colour value by 2 until it reaches 0. The only thing to watch out for is that I don’t do this for the current illuminated LED.

Also note that I only pull out the most significant 7 bits of each 8 bit value (by & 0xFE) so that the shift doesn’t map the least significant bit of one value down into the next colour.

for (int i=0; i<NUM_LEDS; i++) {
if (i != nextLed && ledMap[i] > 0) {
uint32_t newcol = ((ledMap[i] & 0xFE0000) >> 1)
+ ((ledMap[i] & 0x00FE00) >> 1)
+ ((ledMap[i] & 0x0000FE) >> 1);
LEDRingSmall.LEDRingSmall_Set_RGB(i, newcol);
ledMap[i] = newcol;
}
}

All the LED handling is wrapped up in the following functions:

  • ledSetup()
  • ledLoop()
  • ledIndex()

The last function is responsible for swapping the LED numbers around to make them go clockwise. It isn’t as simple as doing NUMLEDS – led as I still want the first LED to be at “12 o’clock”, hence the function to return the correct index.

Find it on GitHub here.

Closing Thoughts

I am so pleased with that lazy LED change effect.

Having so many turns of the encoder isn’t particularly practical at the moment, but it does work. It is certainly good to have a few configuration options – especially the option to wrap around, as it takes quite a few turns to get from 0 to 127.

In a follow up post I’ll take a look at some of the other options, and when I get my endless encoders in the post that will definitely get a mention too.

I also want to wrap up the existing code somehow to allow for several LED CC controls if required and some kind of box might also be nice.

Kevin

#arduinoNano #duppa #i2c #midi #rgbLed #rotaryEncoder

Duppa I2C MIDI Controller – Part 1

I’ve had my eye on Duppa’s rather neat looking I2C LED rings and rotary encoders for some time and finally pushed “go” on getting a couple. I want to use them as the basis f…

Simple DIY Electronic Music Projects

Duppa I2C MIDI Controller – Part 1

I’ve had my eye on Duppa’s rather neat looking I2C LED rings and rotary encoders for some time and finally pushed “go” on getting a couple. I want to use them as the basis for a MIDI controller but this post is just my “getting to know them” post.

  • Part 1 – getting to know the devices and testing them out.
  • Part 2 – adding MIDI to the LED ring and I2C encoder.
  • Part 3 – adding normal encoder, plain potentiometer, and endless potentiometer control.
  • Part 4 – revisits the idea with Waveshare Zero format devices and adds USB MIDI.

A follow-up will build on this knowledge to get them going with MIDI.

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 Arduino, see the Getting Started pages.

Parts list

  • Arduino Uno/Nano
  • DuPPa small RGB LED Ring and mini I2C Encoder.
  • Bespoke hook-up wires (available from duppa.net).
  • Breadboard and jumper wires

Duppa I2C Devices

There are several interesting devices, two variants of LED ring, and two variants of I2C connected rotary encoders. They are all pretty interesting devices, and the cascading nature of, especially the I2C encoders, has massive potential for a range of interesting MIDI controllers!

I have the following devices:

A couple of points to note about these:

Duppa LED Rings

LED Ring SmallLED Ring24 RGB LEDs48 RGB LEDsIS31FL3746A controllerIS31FL3745 controllerCascade up to 15 ringsCascade up to 16 rings

Both LED rings support the following (from the product pages):

  • Individual 256 PWM control steps
  • Individual 256 DC current steps
  • Global 256 step current setting
  • 29kHz PWM frequency
  • Individual open and short error detect function
  • 1MHz I2C-compatible interface
  • Supply voltage range: 2.7V to 5.5V

And they share a cable interface using a Molex “Picoblade” connector at 1.25mm pitch, with the following pinout:

VCCLED supply voltage2.7V – 5.5VGNDGround0VVIOLogic supply voltage2.7 – 5.5VSDAI2C dataSame as VIOSCLI2C clockSame as VIO

Note, at first glance, especially as these are I2C devices, it is easy to imagine that these are compatible with standards such as Qwicc, Stemma, Grove, and so on. They are not (more here).

These are designed so that the LEDs can be powered independently from the logic to allow for higher current if required. This includes using 5V for LEDs and 3V3 for the logic. Apparently both rings state a “all LEDs on” current of 450mA. I don’t know if this is a typo…

The connectors are paired so it is possible to have an “in” and “out” connection allowing for up to 15 or 16 rings to be cascaded, providing different I2C addresses are set via the configuration jumpers. The address range is 0x60 to 0x7E (in steps of two).

Naturally using more rings will definitely require an independent LED power supply that can provide the maximum required current.

Duppa I2C Encoders

There are two variants of I2C encoder (actually there are also some older versions of them too, which are not compatible, but I’m ignoring those).

I2CEncoder MiniI2C EncoderProgrammable I2C addressSolder jumper I2C addressATtiny 402PIC16F18345Cascade in one dimensionCascade in two-dimensional matrixRGB LED support3 GPIO pins

Some common features:

  • 3V3 to 5V operation.
  • Available with 2.54mm pin headers or JST-XH sockets.
  • 400 kHz I2C.
  • Interrupt line.
  • Optional I2C pull-ups via solder jumper.

Both share a common 2.54mm pitch connector and has options for pin headers or JST-XH sockets fitted. The pinout is slightly different to the LED rings, as follows:

GNDGroundVCC3V3 to 5V powerSDAI2C DataSCLI2C ClockINTOpen drain interrupt signal.

Once again these might superficially appear compatible with the likes of Grove and other I2C common options, but they are not.

Also, pretty critically, it should be noted that GND and VCC are the opposite way round to the LED rings, and that SDA/SCL are in different places in the five pins.

The I2C Address for the Mini encoder has to be set in software. The default is 0x20, but it would appear that any of the 128 addresses (apart from 0) could be used. A utility sketch is provided on GitHub and as part of the support library (see below), to set the address.

Note: the full LED ring has 7 address jumpers which will set the 7-bit address in binary directly in hardware.

There are datasheets for both encoders in the respective GitHub repositories. Here is the one for the I2CEncoder Mini.

Getting Started

My plan is to use an LED ring and encoder as a pair, on the I2C bus as a MIDI controller. This means deciding on I2C addresses, pull-up configuration, and connections.

All Duppa devices have options for buying compatible cables, so I would advise getting a range for experimentation. My first task was to crimp some jumper header pins onto each of the cables.

For my initial experiments I’ve gone with the following configuration

  • Enable I2C pull-ups on the LED ring.
  • Use LED ring base address of 0x60 (solder S1, S5).
  • Use Encoder default base address of 0x20.

This requires the I2C pull-up, S1, and S5 solder bridges making on the LED ring, but means I can use the encoder “as is” (for now).

The following shows the connections to an Arduino Nano.

Note that this is powering the LED ring directly from the Nano’s 5V, which itself is powered from the USB socket. Most standard USB sockets would be good for around 500mA I believe, so this should be ok, especially if all LEDs are not on at the same time at full brightness.

Other things to note:

  • For the 5-wire links to the devices, I’ve used the colours that came with the wires I’d ordered from Duppa.net.
  • Notice how 5V/GND are swapped for the two devices.
  • I’ve used 5V for both the LED VCC and logic VIO for the LED ring.
  • In both cases SDA/SCL are cascaded up to the Nanos A4 (SDA), A5 (SCL).
  • The encoders interrupt line is connected to A3.

A note on pull-ups

As already mentioned, I’ve enabled hardware pull-up resistors on the I2C bus by enalbing the solder bridge on the LED ring to connect both on-board I2C 4K7 pull-ups to VCC. Another option would have been to add external 4K7 resistors on the solderless breadboard between both SDA and SCL and VCC (5V).

Another option would have been to solder bridge the I2C pull-ups on the encoder instead.

Generally speaking I2C needs pull-ups somewhere and there are several approaches:

  • Use a microcontroller with hardware pull-ups on SDA and SCL (internal pull-ups aren’t usually “strong” enough for I2C as a rule);
  • Add pull-ups on one of the devices connected to the bus;
  • Use external pull-ups.

There are pros/cons of each. The Arduino doesn’t include additional pull-ups on I2C, but the Raspberry Pi does. Adafruit devices often include an option for enabling pull-ups on each device. It might be possible to use the internal pull-ups on a microcontroller, but they are often considered to be “weak” pull-ups (on an ATMega328 internal pull-ups around ~20-50k) and may not be suitable for the higher speeds of the I2C bus.

Only one set of pull-ups is typically required on the bus. Enabling more may work, but essentially puts the pull-ups in parallel with each other, which lowers the resistance used. I2C seems pretty tolerant of a wide range of pull-up values, so it might be fine for many purposes, but eventually it could lead to current draw issues one presumes.

There is also the issue of what a pull-up should pull up to. If a device is powered using 5V, but the I2C bus is running at 3V3 because it is interfacing to a 3V3 logic microcontroller, then pull-ups need to be 3V3 too.

Some references for pull-up resistors:

The interrupt signal for the I2CEncoder should also include a pull-up resistor, but in this case, as it is not part of the high-speed I2C bus, the internal resistor of either the microcontroller or encoder should suffice.

There is a configuration option for the encoder to tell it to enable the pull-up on the interrupt line, so this is used.

The Code

There is an Arduino library available for all of the Duppa products here: https://github.com/Fattoresaimon/ArduinoDuPPaLib/

This has to be downloaded as a ZIP file of the whole repository and then loaded into the Arduino environment using the “Include Library”->”Add ZIP Library” option.

To verify what I2C addresses are being used, a simple I2C scanner sketch can be used: https://playground.arduino.cc/Main/I2cScanner/

In my case this returns:

I2C Scanner
Scanning...
I2C device found at address 0x20 !
I2C device found at address 0x60 !
done

As already mentioned if the I2C address of the I2CEncoder Mini needs to be changed there is a sketch in the DuPPa library examples to do this:

  • Examples -> DuPPa Library -> I2CEncoderMini -> Change_Address.

It is worth ensuring the provided examples work before going further.

LED Ring Demo

  • Examples -> DuPPa Library -> RGB LED Ring Small -> LEDRingSmall_Demo

The I2C address can be set at the start as follows:

LEDRingSmall LEDRingSmall(ISSI3746_SJ1 | ISSI3746_SJ5);

I also limited the “global current” by changing 0xFF (255) in two places near the end of the loop() to 0x20 (32):

for (i = 0x20; i > 0; i--) { // Was 0xFF
LEDRingSmall.LEDRingSmall_GlobalCurrent(i);
delay(20);
}
LEDRingSmall.LEDRingSmall_ClearAll();
LEDRingSmall.LEDRingSmall_GlobalCurrent(0x20); // Was 0xFF

There are a few other calls to LEDRingSmall_GlobalCurrent() which the library API notes sets the Global Current Control Register in the IS31FL3746A according to Table 10 in the datasheet.

I must admit it isn’t totally clear to me exactly how this works, but various descriptions describe a limiting function on the global current used in 256 steps with 0 being the lowest and 255 (0xFF) being the highest/brightest. The datasheet says that the default is 0, so presumably if this call isn’t used then there will be no output. One section of the demo turns on all LEDs and steps through the global current values from 255 down to 0. I changed this to 32 (0x20) down to 0.

In fact this is a common feature of the library, it appears to be a fairly shallow shim over the direct registers of the IS31FL3746A, so some knowledge of the datasheet is required to use it properly. Although I suspect that for many cases, just starting with the default demo program and fiddling about will probably go quite a long way.

The main LED functions, once the setup is complete, take a LED number and will either set a RGB colour or can set the individual colour levels of red, green or blue from 0 to 255.

led = 0..23
rgb = 0x000000 .. 0xFFFFFF
LEDRingSmall_Set_RGB(led, rgb);

led = 0..23
level = 0 .. 255
LEDRingSmall_Set_RED(led, level);
LEDRingSmall_Set_GREEN(led, level);
LEDRingSmall_Set_BLUE(led, level);

I2C Encoder Demo

  • Examples -> DuPPa Library -> I2CEncoderMini -> Basic_with_Callbacks

The I2C address and Interrupt pin are required:

const int IntPin = A3;
i2cEncoderMiniLib Encoder(0x20);

The demo code uses callbacks from the library to perform various actions depending on the state of the encoder. The following events are tracked (and reported via the serial console):

onIncrement
onDecrement
onMax
onMin
onButtonPush
onButtonRelease
onButtonDoublePush
onButtonLongPush

The interrupt pin is enabled using autoconfigInterrupt() and also relies on the internal pull-up being enabled on the encoder. This is part of the configuration provided on initialisation.

i2cEncoderMiniLib Encoder(0x20);

Encoder.reset();
Encoder.begin( i2cEncoderMiniLib::WRAP_DISABLE
| i2cEncoderMiniLib::DIRE_LEFT
| i2cEncoderMiniLib::IPUP_ENABLE
| i2cEncoderMiniLib::RMOD_X1
);

Whilst the interrupt pin is enabled at the encoder end, it doesn’t actually cause an interrupt on the Arduino. The main loop just polls the relevant INPUT pin and uses it to decide if the encoder needs checking:

void loop() {
if (digitalRead(IntPin) == LOW) {
/* Check the status of the encoder and call the callback */
Encoder.updateStatus();
}
}

I’m guessing this is better than continually scanning the I2C bus to see if there is anything to do, assuming there is a spare GPIO pin to connect up.

Some other points of note for use of the library:

  • It is possible to enable/disable some of the interrupt sources using writeInterruptConfig(), but it can also be automatically configured by calling autoconfigInterrupt() once all the callbacks have been specified for just the sources that match the callbacks.
  • There are three types of encoder supported, representing by the X1, X2, X4 definitions.
  • There is a method for ChangeI2CAddress() but this should not be used in general purpose code. It only really makes sense in the utility sketch already mentioned for one-time setting of the I2C address of the encoder.
  • There is a block of EEPROM that can be accessed using readEEPROM()/writeEEPROM() which might come in handy.

Encoder driven LED Ring

Combining elements of both demos here is a short sketch that uses the encoder to rotate an LED pattern around the ring.

#include <Wire.h>
#include <i2cEncoderMiniLib.h>
#include "LEDRingSmall.h"

const int IntPin = A3;
i2cEncoderMiniLib Encoder(0x20);
LEDRingSmall LEDRingSmall(ISSI3746_SJ1 | ISSI3746_SJ5);

#define MAX_RGB 0x7F

int tim,last;
int dir;
int r, g, b, rg, br, bg;
#define INCR(x) (x)+=dir; if ((x)>=24) {(x)=0;} else if ((x)<0) {(x)=23;}

void encoder_increment(i2cEncoderMiniLib* obj) {
dir = 1;
tim ++;
}

void encoder_decrement(i2cEncoderMiniLib* obj) {
dir = -1;
tim--;
}

void setup() {
pinMode(IntPin, INPUT);
dir = 0;
tim = 0;
last = 1;

Wire.begin();
Wire.setClock(400000);

LEDRingSmall.LEDRingSmall_Reset();
delay(20);

LEDRingSmall.LEDRingSmall_Configuration(0x01); //Normal operation
LEDRingSmall.LEDRingSmall_PWMFrequencyEnable(1);
LEDRingSmall.LEDRingSmall_SpreadSpectrum(0b0010110);
LEDRingSmall.LEDRingSmall_GlobalCurrent(0x10);
LEDRingSmall.LEDRingSmall_SetScaling(0xFF);
LEDRingSmall.LEDRingSmall_PWM_MODE();

Encoder.reset();
Encoder.begin(i2cEncoderMiniLib::WRAP_DISABLE
| i2cEncoderMiniLib::DIRE_LEFT
| i2cEncoderMiniLib::IPUP_ENABLE
| i2cEncoderMiniLib::RMOD_X1 );

Encoder.writeCounter((int32_t) 0); /* Reset the counter value */
Encoder.writeStep((int32_t) 1); /* Set the step to 1*/

Encoder.onIncrement = encoder_increment;
Encoder.onDecrement = encoder_decrement;

Encoder.autoconfigInterrupt();
}

void loop() {
dir = 0;
if (digitalRead(IntPin) == LOW) {
Encoder.updateStatus();
}

if (tim != last) {

if ((tim % 1) == 0) {
LEDRingSmall.LEDRingSmall_Set_RED(r, 0);
INCR(r);
LEDRingSmall.LEDRingSmall_Set_RED(r, MAX_RGB);
}

if ((tim % 2) == 0) {
LEDRingSmall.LEDRingSmall_Set_GREEN(g, 0);
INCR(g);
LEDRingSmall.LEDRingSmall_Set_GREEN(g, MAX_RGB);
}

if ((tim % 3) == 0) {
LEDRingSmall.LEDRingSmall_Set_BLUE(b, 0);
INCR(b);
LEDRingSmall.LEDRingSmall_Set_BLUE(b, MAX_RGB);
}

if ((tim % 4) == 0) {
LEDRingSmall.LEDRingSmall_Set_BLUE(br, 0);
LEDRingSmall.LEDRingSmall_Set_RED(br, 0);
INCR(br);
LEDRingSmall.LEDRingSmall_Set_BLUE(br, MAX_RGB);
LEDRingSmall.LEDRingSmall_Set_RED(br, MAX_RGB);
}

if ((tim % 5) == 0) {
LEDRingSmall.LEDRingSmall_Set_BLUE(bg, 0);
LEDRingSmall.LEDRingSmall_Set_GREEN(bg, 0);
INCR(bg);
LEDRingSmall.LEDRingSmall_Set_BLUE(bg, MAX_RGB);
LEDRingSmall.LEDRingSmall_Set_GREEN(bg, MAX_RGB);
}

if ((tim % 6) == 0) {
LEDRingSmall.LEDRingSmall_Set_RED(rg, 0);
LEDRingSmall.LEDRingSmall_Set_GREEN(rg, 0);
INCR(rg);
LEDRingSmall.LEDRingSmall_Set_RED(rg, MAX_RGB);
LEDRingSmall.LEDRingSmall_Set_GREEN(rg, MAX_RGB);
}
}
last = tim;
}

This only uses the increment/decrement callbacks to set the direction (“dir”) and update the time (“tim”) that determines which direction the ring is moving.

It isn’t perfectly reversible – I liked the idea of winding the ring forward and back again – but it is pretty close. I suspect there is an off-by-one error somewhere that means it can get out of sync.

But it is plenty enough to show how it all works.

Closing Thoughts

These are great little boards. Having an I2C encoded in my case might be a bit over the top, but they are pretty easy to use.

The LED ring is awesome 🙂

Kevin

#duppa #i2c #include #is31fl3746a #rgbLed #rotaryEncoder

DuPPa

I'm designing a 5.25" front for the Gotek floppy emulator that will go in my #GeminiMicrocomputers. It's vertical, has a #rotaryencoder, a 1.3" OLED display and a big fat red 5mm LED. I think it's going to look sort of period correct, if you disregard the presence of the OLED.

And yeah, this is a test run so dimensions are minimal and color is whatever was on the #Prusa #3dprinter.

#vintagecomputing

In this posts I describe a version of the Arduino Euclidean Gate Sequencer that implements the same user interface and most of the features of HAGIWO’s brilliant build.

  • Part 1 covered all the theory and main functions of the code.
  • Part 2 included some hardware suggestions for connecting it to other devices.
  • Part 3 added a rotary encoder and I2C display and demonstrated by Arduino Clock Generator Shield PCB.
  • Part 4 reimplements HAGIWO’s original with a few tweaks and updates.

Also see the Arduino Clock Generator Shield PCB design.

https://makertube.net/w/vPXHnd69UifLhHyQangUr4

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 Arduino, see the Getting Started pages.

Parts List

In principle this could use the same hardware as described in Part 3 but I’m using my PCB as it makes everything a lot easier to test and use.

Introduction and Core Functionality

There is a really good demonstration of HAGIWO’s build in this video: https://www.youtube.com/watch?v=lkoBfiq6KPY

HAGIWO’s screen is shown above and has the following features:

  • 6 channel outputs each with 16 steps.
  • 16 different Euclidean patterns, represented by polygons on the display.
  • Patterns can be offset by up to 16 steps.
  • Patterns can have the number of steps reduced or limited down to a single step.
  • Channels can be muted.
  • There is a random mode that sets all parameters in a random, but still musically useful, way.

The menus are navigated by means of a rotary encoder and switch and it expects an external clock signal, as it is designed for use in a Eurorack setup.

I’m not bothered about the random mode, but I want to implement a built-in clock source with its own tempo control, similar to that shown in Part 3.

The Code

HAGIWO’s code is implemented in a single loop function, using IO interrupts with the Enc rotary encoder library, which means the encoder has to be on pins 2 and 3 on an Uno.

I want to keep my timer-driven GATE outputs and will be polling the encoder using the library from Matthias Hertel (https://github.com/mathertel/RotaryEncoder) which means essentially I’m going to be doing a re-implementation rather than a port of HAGIWO’s code to my hardware.

I’ve reused all the user interface design elements though (apart from the random mode) even though the implementation has been tweaked slightly.

Once again I have two main threads of code:

  • A 10mS tick that handles the GATE outputs and timings subject to the current selected tempo. I will also be using this to poll the rotary encoder too.
  • The main Arduino Loop() function which will manage the user interface, update the display and adjust the sequencer parameters as required.

By keeping the GATE handling in an interrupt routine it will not be affected by the slower I2C updates of the display.

I did wonder about a third level – perhaps a multiple of the main tick, to give me a thread for the encoder and one for the GATES, but it seems to work fine using the same 10mS tick for both.

The core sequencer parameters are:

  • seqTempo – tempo in beats per minute.
  • seqPatterns[] – pattern number for each channel (gate).
  • seqOffsets[] – start offset for each channel.
  • seqMute[] – if TRUE then channel is muted.
  • seqLimits[] – end step for each channel.
  • seqCounter[] – current playing step for each channel.

Timer Functions:

  • timerSetup() – initialise the TimerOne library with a 10mS tick to call timerLoop().
  • timerLoop() – calls the sequencerLoop() and uiScan() each tick.

Sequencer Functions:

  • sequencerSetup() – sets up the first patterns, tempo and any sequencer parameters that aren’t pre-initialised in code.
  • sequencerLoop() – uses tempoTickCnt to decide if a sequencer step is to be enacted or not and output the GATEs as required. Called from the timerLoop().

Gate Functions (all called from the sequencerLoop()):

  • gateSetup() – initialses the IO for GATE outputs.
  • gateClear() – turns off all GATEs.
  • gateOutput() – turns on all active GATEs.
  • gateUpdate() – works out which GATEs should be on for a particular step in a pattern using patternStepOn().

Pattern Functions:

  • patternStepOn() – returns TRUE if the indicated step of the current active pattern shows a GATE should be on for this channel.
  • patternRefresh() – caches the current active patterns for each channel, taking into account any seqOffset[] setting.

User Interface and Menu Functions:

  • uiSetup() – initialise the OLED display and encoder.
  • uiScan() – high speed elements of the interface, called from the timer interrupt and used to scan the encoder.
  • uiLoop() – lower speed elements of the interface, called from the Arduino’s main loop and handles the updates to the sequencer parameters from the encoder by calling updateMenu().
  • uiRequest() – used to signal a display refresh is required. Used when something changes due to the encoder or the playing step display needs updating.
  • uiUpdate() – redraws the screen to represent the current state of the interface and sequencer parameters.
  • updateMenu() – handles the main menu logic using the encoder increment/decrement and switch functions.

The main menu logic from the updateMenu() function has to manage 7 selection states as follows:

  • State 0: channel update
  • State 1: pattern update
  • State 2: offset update
  • State 3: limit update
  • State 4: mute on/off
  • State 5: reset sequence back to starting step
  • State 6: tempo update

Most of the states will add the (positive or negative) increment to the appropriate sequencer parameter and bounds check the result.

The exceptions are mute and reset which act immediately – mute being a toggle, and reset simply setting all seqCounters[] back to 0.

uiLoop() will act on the encoder switch using it to choose between moving between menus and selecting the functionality of one of the menu items.

uiUpdate() is the function that closely mirrors HAGIWO’s original as it has to reproduce the interface on the display. As already mentioned, I’ve not implemented a random mode, but I have added a tempo indication which I show by adding a blocked-out rectangle across the centre of the display with the current bpm tempo value shown.

I’ve also added code so that the currently selected menu item has a solid triangle when in parameter changing mode. This is mostly as HAGIWO’s original used an independent switch, so the sub-menu mode was active as long as the switch was pressed. As I’m using a switched encoder, I want the switch to be a latched toggle between modes rather than only active when pressed.

There is one bug that I can’t quite see why it happens. When showing the tempo there is a block of interference on the bottom right-hand corner. I’m using display.print() at the coordinate point indicated for the tempo text, so I’m not sure what is causing that. Once the tempo display disappears as the menu selection moves on, it goes away…

This seems to be a result of setTextSize(2) and using print(). I’ve no idea why, but switching over to using drawChar() directly for the three digits seems to solve the issue! Still no real idea why to be honest but must be some weird buffer bug in the libraries somewhere I guess.

// Note: default font has 6x8 characters, but we're using size=2
if (seqTempo >= 100) {
display.drawChar (46,24,'0'+seqTempo/100, OLED_ON, OLED_OFF, 2);
}
display.drawChar (46+12,24,'0'+(seqTempo/10)%10, OLED_ON, OLED_OFF, 2);
display.drawChar (46+24,24,'0'+seqTempo%10, OLED_ON, OLED_OFF, 2);

Update – Nov 2024

I realised what the issue is likely to be. The SSD1306 library has to allocate a chunk of memory on startup, as part of the display.begin() call and if that fails, but the code continues on anyway, then its behaviour will be quite unpredictable.

It requires the following amount of memory to run (from Adafruit_SSD1306.cpp):

(WIDTH * ((HEIGHT + 7) / 8))

So for 128×64 that is 1136 bytes of memory. I would imagine there is some kind of overrun going on – if not then it is probably something related to that.

Either way, I’ve added a test on startup to ensure that the display.begin() calls succeeds before proceeding just in case – something I should have been doing anyway.

Apart from these changes, I believe the interface is working as to HAGIWO’s original.

Find it on GitHub here.

Closing Thoughts

Once again the video shows my Arduino Clock Generator Shield PCB running the above code, driving my Arduino Drum Trigger to MIDI Shield PCB which is hooked up to a Roland TR-505.

When I first started messing around with Euclidean rhythms and thinking about an encoder and screen-based interface, I did think I’d just pick up and use the code from HAGIWO’s project pretty much “as is”. But in the end it was just the interface design I reused and reimplemented everything else myself so I could tweak and adjust things in the way I wanted.

So I consider this is another wheel dutifully reinvented here.

As always though, major thanks to HAGIWO for the project that inspired this one and for publishing the designs and code online for others to learn from.

Kevin

https://diyelectromusic.com/2024/10/28/arduino-euclidean-gate-sequencer-part-4/

#arduinoUno #euclidean #gate #midi #oledDisplay #rotaryEncoder #ssd1306 #stepSequence

Arduino Euclidean Gate Sequencer

I’ve always liked the idea of mathematics, algorithms and their application to music, so a Euclidean Sequencer has been on my list of things “to do” for a while. This is my take o…

Simple DIY Electronic Music Projects

This is another variant of my Arduino Euclidean Gate Sequencer this time using a rotary encoder and I2C display rather than a potentiometer for control.

  • Part 1 covered all the theory and main functions of the code.
  • Part 2 included some hardware suggestions for connecting it to other devices.
  • Part 3 added a rotary encoder and I2C display and demonstrated by Arduino Clock Generator Shield PCB.
  • Part 4 reimplements HAGIWO’s original with a few tweaks and updates.

https://makertube.net/w/s1cMmrCzW7tGm16NQQKH9w

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 Arduino, see the Getting Started pages.

Parts List

  • Arduino Uno.
  • Switched Rotary Encoder – either a KY-040 module or an unmounted encoder.
  • 128×64 OLED I2C SSD1306 display.
  • Optional: LEDs and associated (e.g. 220Ω or 1K) resistors.
  • Breadboard and jumper wires.

The Circuit

The above diagram shows how to connect two variants of rotary encoder. On the left is a common KY-040 switched encoder module. On the right is a common switched encoder directly connected to solderless breadboard. Only one encoder should be connected to D4, D5 with the switch on D6.

Note if it turns out the encoder is incrementing/decrementing values in the opposite way to what is expected, then usually the simplest thing to do is swap the IO pin assignments for the A and B connections in the code.

The diagram also shows how to connect a 128×64 I2C OLED display. These are the very common SSD1306 based displays. Note that the pinouts on these can vary with GND/VCC swapped or SCL/SDA swapped, but they must be connected to 5V, GND and A4 (SDA) and A5 (SCL).

The clock generator outputs are on D8-D13. I’ve not added an LED to D13 as it is wired to the internal LED, but that can be added too if required.

I’ve put together an Arduino Clock Generator Shield PCB which includes buffered outputs, a rotary encoder and I2C display connections.

The Code

This expands on the original code that is fully described in part 1 here.

There are two main threads of code as follows:

  • 10mS “tick” interrupt routine to ensure accurate GATE timings.
  • Back “loop” handling the encoder and display.

In order to ensure a responsive encoder the display is only updated when something actually needs to change.

Once again I’ve opted for Matthias Hertel’s simple RotaryEncoder library which can be found here: https://github.com/mathertel/RotaryEncoder. A detailed explanation of how it works can be found here. I’ve found this seems to work best for me for the cheap encoders I tend to buy. One the correct encoder type has been found I’ve found this to be very reliable, especially when simply polling IO pins for an encoder. There is a little more on the rationale for choosing this library in a previous project here: Arduino MIDI Rotary Encoder Controller.

For the I2C display, I’m using the SSD1306 implementation of the Adafruit GFX library which can be found here:

All of these should be installable from the Arduino Library Manager.

The core principles of operation are as follows:

  • The display will show the tempo in beats per minute and which pattern is used for each of the 6 GATE outputs.
  • Clicking the encoder will change the focus between tempo and any of the six GATEs allowing them to be changed.
  • Note: although the tempo is in bpm the number of GATE pulses is actually running at 4 pulses per beat. This means that every 16-pulse sequence, regardless of pattern, will take 4 beats in time to run through.

This is the display I have. I’ve used a larger font for the tempo and underlining to show the active focus of the interface.

The display is managed by having lists of the coordinate points for “cursor” which map what I’ve used for positioning the text.

int cursor_x[GATES+1] = {10,30,50,70,90,110,20};
int cursor_y[GATES+1] = {60,60,60,60,60,60,35};
int cursor_l[GATES+1] = {12,12,12,12,12,12,54};

display.setTextSize(3);
display.setCursor(20,10);
display.print(tempo);

display.setTextSize(1);
for (int i=0; i<GATES ; i++) {
display.setCursor(10+20*i,50);
display.print(gatePatterns[i]);
}

display.writeFastHLine(cursor_x[uiFocus], cursor_y[uiFocus], cursor_l[uiFocus], WHITE);

Then when the encoder triggers a change, the main interface code essentially runs the following algorithm:

IF encoder signals UP THEN
IF focus is tempo THEN
Increment the tempo
ELSE
Increment the selected GATE pattern
ELSE IF encoder signals DOWN THEN
IF focus is tempo THEN
Decrement the tempo
ELSE
Decrement the selected GATE pattern

IF encoder switch is pressed
Move focus on between GATES or over to tempo

I did wonder if the encoder should be polled in its own timer-driver routine. For performance, many people might enable hardware pin interrupts, but these are only available on pins 2 and 3 on an ATMega328. But as the interface loop is essentially not doing much else, it will poll the encoder pretty quickly (at least compared to the speed a person can turn it) and performance is further enhanced by only writing out to the display if something has actually changed.

There are three #defines for the encoder IO pins. If the encoder seems to be doing the opposite to what is expected, just swap over the pins for ENC_A and ENC_B.

#define ENC_A 4
#define ENC_B 5
#define ENC_SW 6

The display is assumed to have an I2C address of 0x3C but that can be changed. It is also built for a 128×64 display but a 128×32 display could also be used if the coordinates for the text and cursors mentioned previously are changed accordingly.

#define OLED_ADDR 0x3C
#define OLED_W 128
#define OLED_H 64

There are two sets of IO pin definitions in the code. One provides a consecutive set of GATES on D8-D13 and one uses D2,D3,D10-D13 to match the requirements of my Arduino Clock Generator Shield PCB.

int gatePins[GATES] = {13,12,11,10,3,2}; // PCB variant
int gatePins[GATES] = {13,12,11,10,9,8}; // Consecutive pins

Find it on GitHub here.

Closing Thoughts

The video shows my Arduino Clock Generator Shield PCB running the above code, driving my Arduino Drum Trigger to MIDI Shield PCB which is hooked up to a Roland TR-505.

The only change required is for the GATE pins, which are slightly different for the PCB.

Kevin

https://diyelectromusic.com/2024/10/27/arduino-euclidean-gate-sequencer-part-3/

#arduinoUno #define #defines #euclidean #gate #midi #oledDisplay #rotaryEncoder #ssd1306 #stepSequence

Arduino Euclidean Gate Sequencer

I’ve always liked the idea of mathematics, algorithms and their application to music, so a Euclidean Sequencer has been on my list of things “to do” for a while. This is my take o…

Simple DIY Electronic Music Projects
Retro Inspired Cyberdeck Scrolls Around Cyberspace

It’s difficult to nail down exactly what counts as a “real” cyberdeck in this brave new era of bespoke computing. But at the minimum, most in the community would agree that a prop…

Hackaday