Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Calibration update #379

Open
wants to merge 22 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 0 additions & 9 deletions software/contrib/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -266,15 +266,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

Expand Down
5 changes: 3 additions & 2 deletions software/contrib/menu.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,6 @@
["Consequencer", "contrib.consequencer.Consequencer"],
["Conway", "contrib.conway.Conway"],
["CVecorder", "contrib.cvecorder.CVecorder"],
["Diagnostic", "contrib.diagnostic.Diagnostic"],
["EgressusMelodiam", "contrib.egressus_melodiam.EgressusMelodiam"],
["EnvelopeGen", "contrib.envelope_generator.EnvelopeGenerator"],
["Euclid", "contrib.euclid.EuclideanRhythms"],
Expand Down Expand Up @@ -62,7 +61,9 @@
["Turing Machine", "contrib.turing_machine.EuroPiTuringMachine"],
["Volts", "contrib.volts.OffsetVoltages"],

["_Calibrate", "calibrate.Calibrate"], # this one should always be second to last!
["_About", "tools.about.About"], # this one should always be fourth to last!
["_Diagnostic", "tools.diagnostic.Diagnostic"], # this one should always be third to last!
["_Calibrate", "tools.calibrate.Calibrate"], # this one should always be second to last!
["_BootloaderMode", "bootloader_mode.BootloaderMode"] # this one should always be last!
])
# fmt: on
Expand Down
130 changes: 0 additions & 130 deletions software/firmware/calibrate.py

This file was deleted.

113 changes: 100 additions & 13 deletions software/firmware/europi.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
from machine import PWM
from machine import Pin
from machine import freq
from machine import mem32


from ssd1306 import SSD1306_I2C
Expand All @@ -34,14 +35,23 @@
from europi_config import load_europi_config
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,
Expand All @@ -56,6 +66,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
Expand Down Expand Up @@ -107,7 +123,8 @@
PIN_CV4 = 17
PIN_CV5 = 18
PIN_CV6 = 19
PIN_USB_CONNECTED = 24
PIN_USB_CONNECTED = 24 # Does not work on Pico 2
PIN_TEMPERATURE = 4

# Helper functions.

Expand Down Expand Up @@ -573,17 +590,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):
Expand All @@ -597,7 +621,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."""
Expand All @@ -622,6 +646,68 @@ def value(self, value):
self.off()


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
"""

# Conversion factor for converting from the raw ADC reading to sensible units
# See Raspberry Pi Pico datasheet for details
TEMP_CONV_FACTOR = 3.3 / 65535

def __init__(self):
# The Raspberry Pi Pico 2's temperature sensor doesn't work reliably (yet)
# so do some basic exception handling
try:
self.pin = ADC(PIN_TEMPERATURE)
except:
self.pin = None

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:
# see the pico's datasheet for the details of this calculation
return 27 - ((self.pin.read_u16() * self.TEMP_CONV_FACTOR) - 0.706) / 0.001721
else:
return None


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
"""

def __init__(self):
if europi_config.PICO_MODEL == "pico2":
self.pin = None
else:
self.pin = DigitalReader(PIN_USB_CONNECTED)

def value(self):
"""Return 0 or 1, indicating if the USB connection is disconnected or connected"""
if self.pin:
return self.pin.value()
else:
# 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:
return 0


# Define all the I/O using the appropriate class and with the pins used
din = DigitalInput(PIN_DIN)
ain = AnalogueInput(PIN_AIN)
Expand All @@ -631,12 +717,12 @@ def value(self, value):
b2 = Button(PIN_B2)

oled = Display()
cv1 = Output(PIN_CV1)
cv2 = Output(PIN_CV2)
cv3 = Output(PIN_CV3)
cv4 = Output(PIN_CV4)
cv5 = Output(PIN_CV5)
cv6 = Output(PIN_CV6)
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]

# External I2C
Expand All @@ -648,7 +734,8 @@ def value(self, value):
timeout=europi_config.EXTERNAL_I2C_TIMEOUT,
)

usb_connected = DigitalReader(PIN_USB_CONNECTED, 0)
thermometer = Thermometer()
usb_connected = UsbConnection()

# Overclock the Pico for improved performance.
freq(europi_config.CPU_FREQ)
Expand Down
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()
Loading
Loading