Skip to content

Commit

Permalink
Calibration update (#379)
Browse files Browse the repository at this point in the history
* Overhauls the calibration tool to add additional new modes of operation:

* * 5V-only calibration (useful if you don't have a 10V voltage source, as many Eurorack offset generators can easily produce 5V)

* * 1-10V, 6-channel calibration (should be more precise than the CV1-only calibration, but takes ~6x longer)

* Modifies the output calibration procedure to a 3-stange, increasingly-fine adjustment of the PW, rather than simply going up in fixed increments

* Moves calibration and diagnostic tools into `firmware/tools`

* Put the system tools in alphabetical order in `menu.py`

* Adds `About` to `firmware/tools` to show basic build & configuration information

* Add new tools directory to the UF2 builder script

* Implement a new class for checking the USB connection; Pin 24 doesn't appear to work on the Pico 2 (previously cherry-picked into the pico2 branch merged previously)

* Add `class mem` and `mem32` to mocks/machine

* Add a hardware mods file to keep track of unofficial changes to the hardware
chrisib authored Jan 18, 2025

Verified

This commit was signed with the committer’s verified signature.
Adam- Adam
1 parent db3d7ab commit 42256d5
Showing 14 changed files with 582 additions and 156 deletions.
33 changes: 33 additions & 0 deletions hardware/EuroPi/hardware_mods.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
# Hardware Mods

This file documents some common hardware modifications to EuroPi. These modifications are wholly
at your own risk; if performed wrong they could cause damage to your module!

## Alternative to OLED jumper wires

Instead of [soldering jumper wires](/hardware/EuroPi/build_guide.md#oled-configuration) to configure
the OLED, you can instead install a 2x4 bank of headers and use 4 jumpers. This makes it easy to
reconfigure the OLED connection, which may be useful if you ever need to replace the display.

<img src="https://github.com/user-attachments/assets/a308995c-de30-41ae-a7e0-2f714e3f8513" width="420">

_Header pins and jumpers used in the CPC orientation_

## Reducing analogue input noise

The original analogue input stage, as designed by Émilie Gillet (of Mutable Instruments fame) includes
a 1nF capacitor located in parallel with the final resistor:

<img src="https://github.com/user-attachments/assets/02dbf7f8-5e39-422a-82a9-104fbe0589e7" width="600">

_The input stage of Mutable Instruments Braids. Note the `1n` capacitor in the upper-right._

If you find your EuroPi's V/Oct outputs are incorrect, or are seeing an undesirable amount of jitter on
`AIN`, you can add a 1nF capacitor in parallel with `R23`. The easiest way to do this is to _carefully_
solder the 1nF capacitor directly to the back-side of `R23`, as shown below:

<img src="https://github.com/user-attachments/assets/2d7d6dcc-7dc1-433d-98c0-6ff8be978cc7" width="360">

_A 1nF capacitor soldered to the back-side of the EuroPi PCB, in parallel with `R23`_

After soldering the 1nF capactor in place, you should [recalibrate EuroPi](/software/firmware/tools/calibrate.md).
9 changes: 0 additions & 9 deletions software/contrib/README.md
Original file line number Diff line number Diff line change
@@ -279,15 +279,6 @@ Using the threshold knob and analogue input, users can determine whether a 1 or
<i>Author: [awonak](https://github.com/awonak)</i>
<br><i>Labels: Clock, Random, CV Generation</i>

### Diagnostic \[ [documentation](/software/contrib/diagnostic.md) | [script](/software/contrib/diagnostic.py) \]
Test the hardware of the module

The values of all inputs are shown on the display, and the outputs are set to fixed voltages.
Users can rotate the outputs to ensure they each output the same voltage when sent the same instruction from the script

<i>Author: [mjaskula](https://github.com/mjaskula)</i>
<br><i>Labels: utility</i>

### Hello World \[ [script](/software/contrib/hello_world.py) \]
An example script for the menu system

10 changes: 6 additions & 4 deletions software/contrib/menu.py
Original file line number Diff line number Diff line change
@@ -30,7 +30,6 @@
["Conway", "contrib.conway.Conway"],
["CVecorder", "contrib.cvecorder.CVecorder"],
["DCSN-2", "contrib.dscn2.Dcsn2"],
["Diagnostic", "contrib.diagnostic.Diagnostic"],
["EgressusMelodiam", "contrib.egressus_melodiam.EgressusMelodiam"],
["EnvelopeGen", "contrib.envelope_generator.EnvelopeGenerator"],
["Euclid", "contrib.euclid.EuclideanRhythms"],
@@ -64,11 +63,14 @@
["Turing Machine", "contrib.turing_machine.EuroPiTuringMachine"],
["Volts", "contrib.volts.OffsetVoltages"],

# System tools
["_Calibrate", "calibrate.Calibrate"],
# System tools, in alphabetical order with a _ prefix

["_About", "tools.about.About"],
["_BootloaderMode", "bootloader_mode.BootloaderMode"],
["_Calibrate", "tools.calibrate.Calibrate"],
["_Config Editor", "tools.conf_edit.ConfigurationEditor"],
["_Diagnostic", "tools.diagnostic.Diagnostic"],
["_Exp Cfg Editor", "tools.experimental_conf_edit.ExperimentalConfigurationEditor"],
["_BootloaderMode", "bootloader_mode.BootloaderMode"],
])
# fmt: on

114 changes: 0 additions & 114 deletions software/firmware/calibrate.py

This file was deleted.

55 changes: 42 additions & 13 deletions software/firmware/europi.py
Original file line number Diff line number Diff line change
@@ -36,14 +36,23 @@

from experimental.experimental_config import load_experimental_config


if sys.implementation.name == "micropython":
TEST_ENV = False # We're in micropython, so we can assume access to real hardware
else:
TEST_ENV = True # This var is set when we don't have any real hardware, for example in a test or doc generation setting

# How many CV outputs are there?
# On EuroPi this 6, but future versions (e.g. EuroPi X may have more)
NUM_CVS = 6

# Import the calibration values
# These are generated by tools/calibrate.py, but do not exist by default
try:
from calibration_values import INPUT_CALIBRATION_VALUES, OUTPUT_CALIBRATION_VALUES
except ImportError:
# Note: run calibrate.py to get a more precise calibration.
# Default calibration values are close-enough to reasonable performance, but aren't great
INPUT_CALIBRATION_VALUES = [384, 44634]
OUTPUT_CALIBRATION_VALUES = [
0,
@@ -58,6 +67,12 @@
56950,
63475,
]
# Legacy calibration using only CV1; apply the same calibration values to each output
if type(OUTPUT_CALIBRATION_VALUES[0]) is int:
cv1_values = OUTPUT_CALIBRATION_VALUES
OUTPUT_CALIBRATION_VALUES = []
for i in range(NUM_CVS):
OUTPUT_CALIBRATION_VALUES.append(cv1_values)


# Initialize EuroPi global singleton instance variables
@@ -104,7 +119,9 @@
PIN_CV4 = 17
PIN_CV5 = 18
PIN_CV6 = 19
PIN_USB_CONNECTED = 24 # Does not work on Pico 2
PIN_USB_CONNECTED = 24
PIN_TEMPERATURE = 4


# Helper functions.

@@ -493,17 +510,24 @@ class Output:
calibration is important if you want to be able to output precise voltages.
"""

def __init__(self, pin, min_voltage=MIN_OUTPUT_VOLTAGE, max_voltage=MAX_OUTPUT_VOLTAGE):
def __init__(
self,
pin,
min_voltage=MIN_OUTPUT_VOLTAGE,
max_voltage=MAX_OUTPUT_VOLTAGE,
calibration_values=OUTPUT_CALIBRATION_VALUES[0],
):
self.pin = PWM(Pin(pin))
self.pin.freq(PWM_FREQ)
self._duty = 0
self.MIN_VOLTAGE = min_voltage
self.MAX_VOLTAGE = max_voltage
self.gate_voltage = clamp(europi_config.GATE_VOLTAGE, self.MIN_VOLTAGE, self.MAX_VOLTAGE)

self._calibration_values = calibration_values
self._duty = 0
self._gradients = []
for index, value in enumerate(OUTPUT_CALIBRATION_VALUES[:-1]):
self._gradients.append(OUTPUT_CALIBRATION_VALUES[index + 1] - value)
for index, value in enumerate(self._calibration_values[:-1]):
self._gradients.append(self._calibration_values[index + 1] - value)
self._gradients.append(self._gradients[-1])

def _set_duty(self, cycle):
@@ -517,7 +541,7 @@ def voltage(self, voltage=None):
return self._duty / MAX_UINT16
voltage = clamp(voltage, self.MIN_VOLTAGE, self.MAX_VOLTAGE)
index = int(voltage // 1)
self._set_duty(OUTPUT_CALIBRATION_VALUES[index] + (self._gradients[index] * (voltage % 1)))
self._set_duty(self._calibration_values[index] + (self._gradients[index] * (voltage % 1)))

def on(self):
"""Set the voltage HIGH at 5 volts."""
@@ -545,7 +569,9 @@ def value(self, value):
class Thermometer:
"""
Wrapper for the temperature sensor connected to Pin 4
Reports the module's current temperature in Celsius.
If the module's temperature sensor is not working correctly, the temperature will always be reported as None
"""

@@ -564,6 +590,7 @@ def __init__(self):
def read_temperature(self):
"""
Read the ADC and return the current temperature
@return The current temperature in Celsius, or None if the hardware did not initialze properly
"""
if self.pin:
@@ -576,6 +603,7 @@ def read_temperature(self):
class UsbConnection:
"""
Checks the USB terminal is connected or not
On the original Pico we can check Pin 24, but on the Pico 2 this does not work. In that case
check the SIE_STATUS register and check bit 16
"""
@@ -594,6 +622,7 @@ def value(self):
# see https://forum.micropython.org/viewtopic.php?t=10814#p59545
SIE_STATUS = 0x50110000 + 0x50
BIT_CONNECTED = 1 << 16

if mem32[SIE_STATUS] & BIT_CONNECTED:
return 1
else:
@@ -633,13 +662,13 @@ def value(self):
height=europi_config.DISPLAY_HEIGHT,
)


cv1 = Output(PIN_CV1)
cv2 = Output(PIN_CV2)
cv3 = Output(PIN_CV3)
cv4 = Output(PIN_CV4)
cv5 = Output(PIN_CV5)
cv6 = Output(PIN_CV6)
# Output CVs
cv1 = Output(PIN_CV1, calibration_values=OUTPUT_CALIBRATION_VALUES[0])
cv2 = Output(PIN_CV2, calibration_values=OUTPUT_CALIBRATION_VALUES[1])
cv3 = Output(PIN_CV3, calibration_values=OUTPUT_CALIBRATION_VALUES[2])
cv4 = Output(PIN_CV4, calibration_values=OUTPUT_CALIBRATION_VALUES[3])
cv5 = Output(PIN_CV5, calibration_values=OUTPUT_CALIBRATION_VALUES[4])
cv6 = Output(PIN_CV6, calibration_values=OUTPUT_CALIBRATION_VALUES[5])
cvs = [cv1, cv2, cv3, cv4, cv5, cv6]

# Helper object to detect if the USB cable is connected or not
3 changes: 3 additions & 0 deletions software/firmware/tools/about.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# About

Displays version & other information about the software
25 changes: 25 additions & 0 deletions software/firmware/tools/about.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
from europi import *
from europi_script import EuroPiScript
from time import sleep
from version import __version__


class About(EuroPiScript):
def __init__(self):
super().__init__()

def main(self):
turn_off_all_cvs()

oled.centre_text(
f"""EuroPi v{__version__}
{europi_config.EUROPI_MODEL}/{europi_config.PICO_MODEL}
{europi_config.CPU_FREQ}"""
)

while True:
sleep(1)


if __name__ == "__main__":
About().main()
35 changes: 35 additions & 0 deletions software/firmware/tools/calibrate.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
# Calibration

Input & output calibration too for EuroPi.

## Required Equipment

Calibration requires the following physical setup:
1. EuroPi _must_ be connected to rack power
2. For low-accuracy calibration you need either another EuroRack module or an external voltage source capable of
generating precise voltages of either 5V or 10V
3. For high-accuracy calibration you need another EuroRack module or adjustable external voltage source capable of
generating precise voltages of 1.0, 2.0, 3.0, ..., 9.0, 10.0V.

## Usage

Calibration is interactive with instructions displayed on-screen. These instructions are summarized below:
1. Make sure the module is powered from your Eurorack power supply.
2. Select input calibration mode by turning `K2`. Press `B2` when ready
a. low-accuracy with 10V input
b. low-accuracy with 5V input
c. high-accuracy with variable 0-10V input
3. Disconnect all patch cables from the module. Press `B1`
4. Connect your voltage source to `AIN`. Press `B1` when instructed.
a. low-accuracy with 10V input: connect 10V to `AIN`
b. low-accuracy with 5V input: connect 5V to `AIN`
c. high-accuracy: read the on-screen instructions and connect the specified voltages when required.
5. Connect `CV1` directly to `AIN`. Press `B1`. Wait for the module to perform the output calibration.
6. Repeat step 5 for `CV2`, `CV3`, etc... until all CV outputs are calibrated
7. Reboot the module when prompted. The new calibration will be applied automatically.

Calibration data is saved to `/lib/calibration_values.py`. DO NOT delete this file; if you delete it you will have to
complete the calibration again.

After calibrating and rebooting the module it is recommended to run the `Diagnostic` program to verify that the
inputs and outputs have been properly calibrated.
429 changes: 429 additions & 0 deletions software/firmware/tools/calibrate.py

Large diffs are not rendered by default.

File renamed without changes.
Original file line number Diff line number Diff line change
@@ -36,8 +36,6 @@
- cvX: output a constant voltage, one of [0, 0.5, 1, 2.5, 5, 10]
"""

TEMP_CONV_FACTOR = 3.3 / 65535


class Diagnostic(EuroPiScript):
def __init__(self):
@@ -58,8 +56,10 @@ def config_points(cls):
return [configuration.choice(name="TEMP_UNITS", choices=["C", "F"], default="C")]

def calc_temp(self):
# see the pico's datasheet for the details of this calculation
t = thermometer.read_temperature()
if t is None:
return 0

if self.use_fahrenheit:
t = (t * 1.8) + 32
return t
@@ -90,7 +90,7 @@ def main(self):
formatted_temp = f"{int(t)}{self.temp_units}"

# display the input values
oled.text(f"ain: {ain.read_voltage():5.2f}v {formatted_temp}", 2, 3, 1)
oled.text(f"ain: {ain.read_voltage(samples=512):5.2f}v {formatted_temp}", 2, 3, 1)
oled.text(f"k1: {k1.read_position():2} k2: {k2.read_position():2}", 2, 13, 1)
oled.text(f"din:{din.value()} b1:{b1.value()} b2:{b2.value()}", 2, 23, 1)

13 changes: 2 additions & 11 deletions software/programming_instructions.md
Original file line number Diff line number Diff line change
@@ -175,19 +175,10 @@ If you do not wish to calibrate the module and don't mind your voltages being sl
> **Note**
> If you have just installed the menu, simply run the calibration script and skip to step 2.
1. To begin, you need to choose the `calibrate.py` file and save it as `main.py` in the root directory, as we did in [Option 2](#copy-someone-elses-program-to-run-on-your-module) above. You can obtain the `calibrate.py` file from either the `lib` directory on your Pico, or from [the firmware directory in the repository](/software/firmware/calibrate.py).
1. To begin, you need to choose the `calibrate.py` file and save it as `main.py` in the root directory, as we did in [Option 2](#copy-someone-elses-program-to-run-on-your-module) above. You can obtain the `calibrate.py` file from either the `lib/tools` directory on your Pico, or from [the firmware directory in the repository](/software/firmware/tools/calibrate.py).
2. Make sure your module is connected to rack power for the calibration process. It doesn't matter if it connected to USB as well, however if it is it will give an extra warning to turn on rack power which you need to skip using button 1.
3. Turn on the rack power supply, and the screen will display 'Calibration Mode'. If it doesn't, try [troubleshooting](../troubleshooting.md).
4. There are 2 options for calibration:
- Low Accuracy: Only a single 10V supply is required
- High Accuracy: A variable supply is required to produce voltages from 0-10V
Press button 1 to choose Low Accuracy mode, or button 2 to choose High Accuracy mode.
5. Connect your voltage source to the analogue input, and input each voltage displayed on the screen. To take a reading (once your voltage is connected), press button 1.
6. Once all the required voltages have been input, you now need to disconnect the analogue input from your voltage source, and instead connect it to CV output 1.
7. Once you have connected the analogue input to CV output 1, press button 1
8. Wait for each voltage up to 10V to complete. The module will tell you once it has completed.
9. NOTE: Skip this final step if you are running the calibration script from the menu system.
The calibration process is now complete! You now need to rename or delete the 'calibrate.py' program, however DO NOT delete the new file created called 'calibration_values.py'. This file is where the calibration values are stored, and if you delete it you will have to complete the calibration again.
4. See [calibration](/software/firmware/tools/calibrate.md) for details on the calibration process.


# Programming Limitations
1 change: 1 addition & 0 deletions software/tests/mocks/machine.py
Original file line number Diff line number Diff line change
@@ -59,6 +59,7 @@ def freq(_):
class mem:
"""
Fakes underlying machine registers.
Shouldn't be used directly, but any registers (e.g. mem32) can be instantiated with this
"""

3 changes: 2 additions & 1 deletion software/uf2_build/copy_and_compile.sh
Original file line number Diff line number Diff line change
@@ -4,6 +4,7 @@ set -e
echo "Copying EuroPi firmware and scripts to container..."
mkdir micropython/ports/rp2/modules/contrib
mkdir micropython/ports/rp2/modules/experimental
mkdir micropython/ports/rp2/modules/tools
cp -r europi/software/firmware/*.py micropython/ports/rp2/modules
cp -r europi/software/firmware/experimental/*.py micropython/ports/rp2/modules/experimental
cp -r europi/software/firmware/tools/*.py micropython/ports/rp2/modules/tools
@@ -14,4 +15,4 @@ cd micropython/ports/rp2
make

echo "Copying firmware file to /europi/software/uf2_build/europi-dev.uf2"
cp build-PICO/firmware.uf2 /europi/software/uf2_build/europi-dev.uf2
cp build-RPI_PICO/firmware.uf2 /europi/software/uf2_build/europi-dev.uf2

0 comments on commit 42256d5

Please sign in to comment.