Categories
My Designs Pico Midi

PICO MIDI – Expression Pedal

Let’s continue working on Pico MIDI – a Raspberry Pi Pico based MIDI pedal. In my previous posts I covered how to send MIDI commands and how to support footswitch and LED.

Today, I’ll use an expression pedal to control Digitech Whammy. In earlier posts I also used RC-500 Boss looper, so make sure you check that out.

In this post, I used M-Audio E-xp pedal, super cheap and pretty cheerful 😅. The approach is the same for any expression pedal, and this will probably work with most volume pedals too.

As usual – you can skip directly to the video and come back later for more details if you wish so.

Here’s the ToC:

Expression Pedal

I dismantled my expression pedal to see what’s in it. Please don’t do it … that will void your warranty … and you can easily measure resistance on the pedal plug so why go through that. Anyway, here’s what we’re dealing with:

Schematic of an expression pedal
An expression pedal

There isn’t much to it, it’s just a glorified 10K linear potentiometer. There’s an additional 50K pot to control minimal value, and there’s a switch that changes how the wiper is wired up. In one position it’s wired to the tip and in the other to the ring.

The sleeve is wired to one end of the pot, but it is meant to be wired to the ground because it’s screwed onto a metal base plate. There’s an additional current limiting resistor (R1) – just in case you use wrong switch position.

Actually, I’m wondering if anyone created a 3D model for the “rocking” pedal already. I’m pretty sure this would be possible to print on a 3D printer. If anyone has come across it please share in the comments.

Analog to Digital Converter

Yep, that’s ADC. RP2040 has 4 ADC pins, but Pico exposes only 3 on pins GP26-28 (check it out on the Pico pinout). It’s simple, we wire up our pedal as voltage divider, and we convert voltage at wiper to a digital value using ADC. We could try this with a normal pot first:

Schematic of how to wire a pot to read it's wiper position using an ADC
Wiring up a pot to ADC

We’re using pot to get our feet wet with ADC. And for this we’ll use, more or less, example from getting started with pico. Remember, if we can’t make simple work, we can’t make complicated work.

If you remember from last time, I went through explaining how to use uasyncio library and Micropython Async. Turns out there’s a very simple example already – and with a slight adaptation, here it is:

import uasyncio as asyncio
from machine import Pin, ADC
from primitives import AADC

aadc = AADC(ADC(Pin(28)))
async def main_loop():
    while True:
        value = await aadc(512)  # Trigger if value changes by 512
        print(value)

asyncio.run(main_loop())

That pretty simple. We’re attaching our pot’s wiper to pin 28 of our Pico. In the main loop our async implementation of ADC returns a value if it changed by at least 512.

Now, ADC returns unsigned 16bit integer for it’s value – that’s all values from 0-65535. Hold on a minute, the ADC is 12bit – that’s just 4096 values??? I’m guessing the value is extrapolated to the whole range of unsigned 16bit integer.

In the end, that’s totally irrelevant, our change command supports values from 0-127 anyway.

Analog Ground

A quick detour to cover pin 33 on Pico labeled with AGND. That’s analog ground. The microcontroller itself does not have AGND pin. The datasheet for Pico board states that there’s a separate analog ground plane.

Essentially – there’s a separate ground plane for digital signals to keep noise from them switching and doing their digital stuff away from ADC pins and AGND. For this to work fully there’s ADC_VREF pin to provide less noisy reference for all ADCs, but I’m not using that.

For the rest of the schematics assume I used AGND anyway for ground, but I’m doubtful it was supper effective when I used 3.3V power supply used by all digital circuitry. Keep in mind that we’re not building a rocket ship here 😋

Updated Schematic

Let’s expand on the circuit we previously had and add support for expression pedal:

Schematic excerpt showing how to wire up jack to support expression pedal
Jack for expression pedal

Again, nothing revolutionary, just a jack. The code, as you can imagine is pretty similar to what we covered in previous post, and MIDI commands we have already seen in the very first post in the series.

from machine import UART, Pin, ADC
import uasyncio as asyncio
from primitives import Pushbutton, AADC

# Whammy 2 octaves up ON/OFF
pc_1 = b'\xc0\x00'
pc_22 = b'\xc0\x15'
# CC#11 - toe up/down
cc_toe_up = b'\xb0\x0b\x00'
cc_toe_down = b'\xb0\x0b\x7f'
cc_partial = b'\xb0\x0b'

def toggle(led, midi):
    led.toggle()
    if led.value():
        midi.write(bytearray(pc_1))
        print('ON')
    else:
        midi.write(bytearray(pc_22))
        print('OFF')

def express(aadc, midi):
    while True:
        value = await aadc(512)
        cmd = bytearray(cc_partial)
        cc_value = int(value/512)
        cmd.append(cc_value)
        print(cmd, cc_value)
        midi.write(cmd)
    

async def my_app():
    pin = Pin(14, Pin.IN, Pin.PULL_UP)
    red = Pin(15, Pin.OUT)
    midi = UART(0, baudrate=31250, bits=8, parity=None, stop=1, tx=Pin(16), rx=Pin(17), invert=0)
    aadc = AADC(ADC(Pin(28)))
    pb = Pushbutton(pin)
    pb.press_func(toggle, (red, midi, ))
    asyncio.create_task(express(aadc, midi))
    while True:
        await asyncio.sleep(60)

asyncio.run(my_app())  # Run main application code

Most of the code is just copy-pasted from the previous post. It supports a footswitch and an LED. When LED is ON, PC#1 is sent to turn on 2 Octave option on the Whammy, when LED is OFF, PC#22 to bypass it. The footswitch just toggles that. Pretty much the same as in the previous post, I’m just sending different commands.

In my_app I’m defining how my ADC is wired up and I create a task that loops and reads our pedal position – express function. CC#11 is sent – this is pedal position command for Whammy. Value for that command is read from expression pedal position/ADC. To get value from 0-127 I’m dividing output of ADC by 512. Simple, I hope. If you don’t believe this code works see the video 😉.

Full Control

The above code is still relatively simple, but we have full control of the expression pedal. We can complicate the code, but for a good cause. We can make it behave whichever way we want. If we use something like a piecewise linear function, we can transform our linear pot in the expression pedal to any response we like.

For example, audio pot is not a perfect exponential function (it’s log taper but it’s response is exponential function … I find this confusing to be honest, but anyway, let’s continue). It’s made out of three or more sections that are linear but made to roughly correspond to an exponential function:

Diagram showing audio and linear pot responses
That’s an excerpt from Alps datasheet

If you look at the diagram above the resistance for the audio pot (15A) is made out of 5 different linear sections: (0.0,0.0)-(0.23,0.03)-(0.65,0.25)-(0.72,0.4)-(0.95,0.99)-(1.0,1.0). So from 0% to 23% of the rotation of the pot, resistance goes from 0% to 3%. Then from 23%-65% of rotation, the resistance goes from 3%-25% etc.

This is roughly how these pots are made, better quality pots, I suppose, have more linear sections. But using piecewise linear functions you can approximate nearly any response you want. Here’s code to demonstrate how this could be done:

from machine import UART, Pin, ADC
import uasyncio as asyncio
from primitives import Pushbutton, AADC

# Whammy 2 octaves up ON/OFF
pc_1 = b'\xc0\x00'
pc_22 = b'\xc0\x15'
# CC#11 - toe up/down
cc_toe_up = b'\xb0\x0b\x00'
cc_toe_down = b'\xb0\x0b\x7f'
cc_partial = b'\xb0\x0b'

def translate_pot_value(val):
    #piecewise linear approximation of an audio pot
    #(x2, y2) = (1.0,1.0)
    #taper = [(0.95,0.99),(0.72,0.4),(0.65,0.25),(0.23,0.03),(0.0,0.0)]
    (x2, y2) = (65_535.0, 65_535.0)
    taper = [(62_258.0, 64_880.0), (47_185.0, 26_214.0), (42_598.0, 16_384.0), (15_073.0,1_966.0), (0.0,0.0)]
    
    for (x1, y1) in taper:
        if val >= x1: #detect piecewise linear function
            piecewise_portion = (val-x1)/(x2-x1) #portion of that piecewise function
            piecewise_value = (y2-y1)*piecewise_portion + y1
            return piecewise_value
        
        (x2, y2) = (x1, y1)
    
    return 0.0

def toggle(led, midi):
    led.toggle()
    if led.value():
        midi.write(bytearray(pc_1))
        print('ON')
    else:
        midi.write(bytearray(pc_22))
        print('OFF')

def express(aadc, midi):
    while True:
        value = await aadc(512)
        cmd = bytearray(cc_partial)
        adc_value = int(translate_pot_value(value)/512)
        cmd.append(adc_value)
        print(adc_value)
        midi.write(cmd)
    

async def my_app():
    pin = Pin(14, Pin.IN, Pin.PULL_UP)
    red = Pin(15, Pin.OUT)
    midi = UART(0, baudrate=31250, bits=8, parity=None, stop=1, tx=Pin(16), rx=Pin(17), invert=0)
    aadc = AADC(ADC(Pin(28)))
    pb = Pushbutton(pin)
    pb.press_func(toggle, (red, midi, ))
    asyncio.create_task(express(aadc, midi))
    while True:
        await asyncio.sleep(60)

asyncio.run(my_app())  # Run main application code

The only new addition here is function: translate_pot_value. I pre-calculated values so we don’t waste our CPU time on on-the-fly calculation, and the code is simpler.

Scary Math Ahead

All the code does is – finds the linear segment to use, and then determines where on that linear segment are we, and how much is that worth in resistance value. All the amounts below are in ADC money.

So, say, if our ADC reads 30,000 – that’s roughly 46% of the rotation of our linear pot. That is 4.6K roughly of resistance – for a linear pot. If we look at the above diagram, that is roughly 1.6K for audio pot.

Well, let’s do what the function would do, the pedestrian way. 30,000 falls in the segment between 42,598 and 15,073. The whole segment’s rotation value is: 42,598-15,073 = 27,525. We’ve covered 30,000 – 15,073 = 14,927 of the segment, or, roughly 54% of it. The whole resistance value of the segment is: 16,384 – 1,966 = 14,418. That means 14,418 * 54% = 7,786 in the new money. The previous segment ended with 1,966, so our full translated value is 7,786 + 1,966 = 9,752. That’s 9,752/65,535 = 14.88% of our resistance. That’s 1.49K … not too far off from my “print a very small diagram and use a ruler” method.

I’m not sure what was the point of the above, just trust me on this 😂. And watch the video, it clearly shows that the pedal response is different.

Wiring up Different Pedals

If all you want to support is the one pedal you have, you can stop reading now. But if you want to support more than just the one, well, we can’t just keep the wiring as we had. Even if I flip the switch on my pedal, it does not work correctly any longer.

I don’t think there is really a standard how to wire these pedals, so some may have the wiper wired on the tip, some may have it wired on the ring, you can only hope that you’re OK to wire the sleeve to ground … because that’s where we’ll keep it.

Software is easy to manipulate, but hardware, once it’s soldered, that’s it. That is, unless you’re willing to wield your soldering iron every once in a while. If we slightly rewire our switch like this (I’m showing a pot here for simplicity):

Schematic excerpt showing an alternative way to wire up a pot for ADC
Alternative wiring

If I use one of the GPIO pins to supply 3.3V for our pot and the other for ADC read-out, we can re-configure it in software on the fly.

NOTE: We should be safe to do so, even if we short the wires between GPIOs 26 and 27. This is because we’re using ADC and it is slightly differently wired than a GPIO. But we could cause some damage otherwise if the configuration is incorrect and we have no current limiters. In the next post I’ll cover this in more depth, but we’re OK for now.

This leads to the:

Final Schematic … for Today

Schematic of a midi pedal with footswitch, LED and Expression pedal support
Midi Pedal with Footswitch, LED & Expression Pedal support

That’s it, the whole long post for a very simple change. Here’s the final code for today:

from machine import UART, Pin, ADC
import uasyncio as asyncio
from primitives import Pushbutton, AADC

# Whammy 2 octaves up ON/OFF
pc_1 = b'\xc0\x00'
pc_22 = b'\xc0\x15'
# CC#11 - toe up/down
cc_toe_up = b'\xb0\x0b\x00'
cc_toe_down = b'\xb0\x0b\x7f'
cc_partial = b'\xb0\x0b'

#linear
def translate_pot_value(val):
    (x2, y2) = (65_535.0, 65_535.0)
    taper = [(0.0,0.0)]
    
    for (x1, y1) in taper:
        if val >= x1: #detect piecewise linear function
            piecewise_portion = (val-x1)/(x2-x1) #portion of that piecewise function
            piecewise_value = (y2-y1)*piecewise_portion + y1
            return piecewise_value
        
        (x2, y2) = (x1, y1)
    
    return 0.0

def toggle(led, midi):
    led.toggle()
    if led.value():
        midi.write(bytearray(pc_1))
    else:
        midi.write(bytearray(pc_22))

def express(aadc, midi):
    while True:
        value = await aadc(512)
        cmd = bytearray(cc_partial)
        adc_value = int(translate_pot_value(value)/512)
        cmd.append(adc_value)
        print(adc_value)
        out_len = midi.write(cmd) #setting out_len so count is not printed out
    

async def my_app():
    pin = Pin(14, Pin.IN, Pin.PULL_UP)
    red = Pin(15, Pin.OUT)
    midi = UART(0, baudrate=31250, bits=8, parity=None, stop=1, tx=Pin(16), rx=Pin(17), invert=0)
    aadc = AADC(ADC(Pin(27)))
    driver = Pin(26, Pin.OUT)
    driver.on()
    pb = Pushbutton(pin)
    pb.press_func(toggle, (red, midi, ))
    asyncio.create_task(express(aadc, midi))
    while True:
        await asyncio.sleep(60)

asyncio.run(my_app())  # Run main application code

The only difference is:

    aadc = AADC(ADC(Pin(27)))
    driver = Pin(26, Pin.OUT)
    driver.on()

If you want to change the wiring, just set that aadc is on pin 26 and the driver is on pin 27 instead, and you support both wiring options. Watch the video for the demo. The important part is that we’re not rewiring our hardware, but we can configure it on the fly with software. Awesome! 😎

Action

Here’s a (not so) quick demo of what I covered here:

Lights, camera …. action!

Things are getting more complicated by the day. We still have a couple of things to cover before we have completed our hardware design for the pedal. Next time I’ll cover external switches. Subscribe so you don’t miss out 😉

One reply on “PICO MIDI – Expression Pedal”

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.