From d034cf269b332763739652d9dddba9ef5b274cf0 Mon Sep 17 00:00:00 2001 From: Chris I-B Date: Thu, 9 Jan 2025 23:51:14 -0500 Subject: [PATCH 01/30] Add missing temperature pin --- software/firmware/europi.py | 1 + 1 file changed, 1 insertion(+) diff --git a/software/firmware/europi.py b/software/firmware/europi.py index a0d100e42..81b2052ef 100644 --- a/software/firmware/europi.py +++ b/software/firmware/europi.py @@ -105,6 +105,7 @@ PIN_CV5 = 18 PIN_CV6 = 19 PIN_USB_CONNECTED = 24 # Does not work on Pico 2 +PIN_TEMPERATURE = 4 # Helper functions. From 80882c9f182330e486bc392a8eabbdb6542e8ae2 Mon Sep 17 00:00:00 2001 From: Chris I-B Date: Wed, 11 Sep 2024 14:16:54 -0400 Subject: [PATCH 02/30] Rewrite the calibration program to support 5V low-accuracy calibration (5V voltage sources seem way more common in EuroRack modules than 10V). Move diagnistic out of contrib, into new firmware/tools namespace. Put calibration here too. Update README, programming instructions, menu accordingly. Add calibration.md with more detailed description of calibration process --- software/contrib/README.md | 9 - software/contrib/menu.py | 4 +- software/firmware/calibrate.py | 114 ------- software/firmware/tools/calibrate.md | 35 ++ software/firmware/tools/calibrate.py | 319 ++++++++++++++++++ .../{contrib => firmware/tools}/diagnostic.md | 0 .../{contrib => firmware/tools}/diagnostic.py | 0 software/programming_instructions.md | 13 +- 8 files changed, 358 insertions(+), 136 deletions(-) delete mode 100644 software/firmware/calibrate.py create mode 100644 software/firmware/tools/calibrate.md create mode 100644 software/firmware/tools/calibrate.py rename software/{contrib => firmware/tools}/diagnostic.md (100%) rename software/{contrib => firmware/tools}/diagnostic.py (100%) diff --git a/software/contrib/README.md b/software/contrib/README.md index d8105e55f..4d3500cd3 100644 --- a/software/contrib/README.md +++ b/software/contrib/README.md @@ -279,15 +279,6 @@ Using the threshold knob and analogue input, users can determine whether a 1 or Author: [awonak](https://github.com/awonak)
Labels: Clock, Random, CV Generation -### 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 - -Author: [mjaskula](https://github.com/mjaskula) -
Labels: utility - ### Hello World \[ [script](/software/contrib/hello_world.py) \] An example script for the menu system diff --git a/software/contrib/menu.py b/software/contrib/menu.py index ae8d93fc7..004231a63 100644 --- a/software/contrib/menu.py +++ b/software/contrib/menu.py @@ -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,7 +63,8 @@ ["Turing Machine", "contrib.turing_machine.EuroPiTuringMachine"], ["Volts", "contrib.volts.OffsetVoltages"], - ["_Calibrate", "calibrate.Calibrate"], # this one should always be second 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 diff --git a/software/firmware/calibrate.py b/software/firmware/calibrate.py deleted file mode 100644 index af1d840ea..000000000 --- a/software/firmware/calibrate.py +++ /dev/null @@ -1,114 +0,0 @@ -from time import sleep -from europi import oled, b1, b2, ain, cv1, usb_connected -from europi_script import EuroPiScript -from os import stat, mkdir - - -class Calibrate(EuroPiScript): - @classmethod - def display_name(cls): - """Push this script to the end of the menu.""" - return "~Calibrate" - - def main(self): - def sample(): - readings = [] - for reading in range(256): - readings.append(ain.pin.read_u16()) - return round(sum(readings) / 256) - - def wait_for_voltage(voltage): - wait_for_b1(0) - if voltage != 0: - oled.centre_text(f"Plug in {voltage}V\n\nDone: Button 1") - wait_for_b1(1) - else: - oled.centre_text(f"Unplug all\n\nDone: Button 1") - wait_for_b1(1) - oled.centre_text("Calibrating...") - sleep(1.5) - return sample() - - def text_wait(text, wait): - oled.centre_text(text) - sleep(wait) - - def wait_for_b1(value): - while b1.value() != value: - sleep(0.05) - - # Test if /lib exists. If not: Create it - - try: - stat("/lib") - except OSError: - mkdir("/lib") - - # Calibration start - - if usb_connected.value() == 1: - oled.centre_text("Make sure rack\npower is on\nDone: Button 1") - wait_for_b1(1) - wait_for_b1(0) - - text_wait("Calibration\nMode", 3) - - oled.centre_text("Choose Process\n\n1:LOW 2:HIGH") - while True: - if b1.value() == 1: - chosen_process = 1 - break - elif b2.value() == 1: - chosen_process = 2 - break - - # Input calibration - - readings = [] - if chosen_process == 1: - readings.append(wait_for_voltage(0)) - readings.append(wait_for_voltage(10)) - else: - for voltage in range(11): - readings.append(wait_for_voltage(voltage)) - - with open(f"lib/calibration_values.py", "w") as file: - values = ", ".join(map(str, readings)) - file.write(f"INPUT_CALIBRATION_VALUES=[{values}]") - - # Output Calibration - - oled.centre_text(f"Plug CV1 into\nanalogue in\nDone: Button 1") - wait_for_b1(1) - oled.centre_text("Calibrating...") - - if chosen_process == 1: - new_readings = [readings[0]] - m = (readings[1] - readings[0]) / 10 - c = readings[0] - for x in range(1, 10): - new_readings.append(round((m * x) + c)) - new_readings.append(readings[1]) - readings = new_readings - - output_duties = [0] - duty = 0 - cv1.pin.duty_u16(duty) - reading = sample() - for index, expected_reading in enumerate(readings[1:]): - while abs(reading - expected_reading) > 0.002 and reading < expected_reading: - cv1.pin.duty_u16(duty) - duty += 10 - reading = sample() - output_duties.append(duty) - oled.centre_text(f"Calibrating...\n{index+1}V") - - with open(f"lib/calibration_values.py", "a+") as file: - values = ", ".join(map(str, output_duties)) - file.write(f"\nOUTPUT_CALIBRATION_VALUES=[{values}]") - - oled.centre_text("Calibration\nComplete!") - - -if __name__ == "__main__": - Calibrate().main() diff --git a/software/firmware/tools/calibrate.md b/software/firmware/tools/calibrate.md new file mode 100644 index 000000000..b2f4c1a8f --- /dev/null +++ b/software/firmware/tools/calibrate.md @@ -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` +6. Wait for the module to perform the output calibration. +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. diff --git a/software/firmware/tools/calibrate.py b/software/firmware/tools/calibrate.py new file mode 100644 index 000000000..1695804ab --- /dev/null +++ b/software/firmware/tools/calibrate.py @@ -0,0 +1,319 @@ +from time import sleep +from europi import oled, b1, b2, k2, ain, cv1, usb_connected +from europi_script import EuroPiScript +from os import stat, mkdir + + +class CalibrationValues: + """Wrapper class for the input & output calibration values used for analogue inputs & outputs + + In low-accuracy mode, input_calibration_values is a length 2 array with the raw samples taken at 0V and 10V + + In high-accuracy mode, input_calibration_values is a length 11 array with the raw samples taken at 0-10V, in 1V + increments. + + In either mode, output_calibration_values is a length 11 array with the raw samples taken at 0-10V, in 1V + increments. + """ + MODE_UNKNOWN = "unk" + MODE_LOW_10V = "low10" + MODE_LOW_5V = "low5" + MODE_HIGH = "high" + + mode = "unk" + + input_calibration_values = [] + output_calibration_values = [] + + def __init__(self, mode): + """Create the calibration values, specifying the mode we're operating in + + @param mode The calibration mode, one of MODE_LOW_10, MODE_LOW_5, or MODE_HIGH + """ + self.mode = mode + + def save(self): + """Save the calibration readings to /lib/calibration_values.py + + Note: this will overwrite all previous calibrations + """ + with open(f"lib/calibration_values.py", "w") as file: + values = ", ".join(map(str, self.input_calibration_values)) + file.write(f"INPUT_CALIBRATION_VALUES=[{values}]\n") + + values = ", ".join(map(str, self.output_calibration_values)) + file.write(f"OUTPUT_CALIBRATION_VALUES=[{values}]\n") + + file.write(f"CALIBRATION_MODE = '{self.mode}'\n") + + +class Calibrate(EuroPiScript): + """ + A script to interactively calibrate the module + + General flow: + 1. Sanity check (rack power, necessary file structure exists) + 2. Input calibration. One of: + a. low-accuracy 10V in + b. low-accuracy 5V in + c. high-accuracy 0-10V in + 3. Output calibration + 4. Save calibration + 5. Idle for reboot + + Output calibration only applies to CV1; all other outputs will + use the same calibration values. + """ + + # The initial state; prompt the user to select their calibration mode using K2 + B2 + STATE_STARTUP = 0 + + # Asking the user to select their calibration mode + STATE_MODE_SELECT = 1 + + # Initial states for each input calibration mode: + # - low-accuracy w/ 10V input + # - low-accuracy w/ 5V input + # - high-accuracy w/ variable 0-10V input + STATE_START_LOW_10 = 2 + STATE_START_LOW_5 = 3 + STATE_START_HIGH = 4 + + # Start output calibration + STATE_START_OUTPUT = 5 + + # Flags indicating we're waiting for the user to press B1 + STATE_WAITING_B1 = -1 + STATE_B1_PRESSED = -2 + + # As above, but for B2 + STATE_WAITING_B2 = -3 + STATE_B7_PRESSED = -4 + + # The current state of the program + # The state progresses pretty linearly with minimal branching, so mostly + # this is used for debugging. + # It _is_ necessary for button press detection + state = 0 + + @classmethod + def display_name(cls): + """Push this script to the end of the menu.""" + return "~Calibrate" + + def text_wait(self, text, duration): + """ + Display text on the screen and block for the specified duration + + @param text The text to display + @param duration The duration to wait in seconds + """ + oled.centre_text(text) + sleep(duration) + + def read_sample(self): + """ + Read from the raw ain pin and return the average across several readings + + @return The average across 256 distinct readings from the pin + """ + N_READINGS = 256 + readings = [] + for reading in range(N_READINGS): + readings.append(ain.pin.read_u16()) + return round(sum(readings) / N_READINGS) + + def wait_for_voltage(self, voltage): + """ + Wait for the user to connect the desired voltage to ain & press b1 + + @param voltage The voltage to instruct the user to connect. Used for display only + + @return The average samples read from ain (see @read_sample) + """ + if voltage == 0: + oled.centre_text(f"Unplug all\n\nDone: Button 1") + else: + oled.centre_text(f"Plug in {voltage:0.1f}V\n\nDone: Button 1") + self.wait_for_b1() + return self.read_sample() + + def wait_for_b1(self, wait_fn=None): + """ + Wait for the user to press B1, returning to the original state when they have + + @param wait_fn A function to execute while waiting (e.g. refresh the UI) + """ + if wait_fn is None: + wait_fn = lambda: sleep(0.01) + + prev_state = self.state + self.state = self.STATE_WAITING_B1 + while self.state == self.STATE_WAITING_B1: + wait_fn() + self.state = prev_state + + def wait_for_b2(self, wait_fn=None): + """ + Wait for the user to press B2, returning to the original state when they have + + @param wait_fn A function to execute while waiting (e.g. refresh the UI) + """ + if wait_fn is None: + wait_fn = lambda: sleep(0.01) + + prev_state = self.state + self.state = self.STATE_WAITING_B2 + while self.state == self.STATE_WAITING_B2: + wait_fn() + self.state = prev_state + + def check_directory(self): + """Check if /lib exists. If it does not, create it""" + try: + stat("/lib") + except OSError: + mkdir("/lib") + + def check_rack_power(self): + if usb_connected.value() == 1: + oled.centre_text("Make sure rack\npower is on\nDone: Button 1") + self.wait_for_b1() + + def input_calibration_low10(self): + """ + Low-accuracy, 10V input calibration + + Prompt the user for 0 and 10V inputs only + + @return The sample readings for 0 and 10V + """ + self.state = self.STATE_START_LOW_10 + readings = [ + self.wait_for_voltage(0), + self.wait_for_voltage(10), + ] + return readings + + def input_calibration_low5(self): + """ + Low-accuracy, 5V input calibration + + Prompt the user for 0 and 5V inputs only. The 5V reading is extrapolated to 10V. + + @return The sample readings for 0 and 10V + """ + self.state = self.STATE_START_LOW_5 + readings = [ + self.wait_for_voltage(0), + self.wait_for_voltage(5), + ] + + # Extrapolate from 5V input to 10V, assuming a linear progression + samples_per_volt = round((readings[-1] - readings[0]) / 5) + readings[-1] = samples_per_volt * 10 + readings[0] + + return readings + + def input_calibration_high(self): + """ + High accuracy input calibration + + User is prompted to connect 0, 1, 2, ..., 9, 10V + + @return The sample readings for 0 to 10V (inclusive) + """ + self.state = self.STATE_START_HIGH + readings = [] + for i in range(11): + readings.append(self.wait_for_voltage(i)) + return readings + + def main(self): + # Button handlers + @b1.handler + def on_b1_press(): + if self.state == self.STATE_WAITING_B1: + self.state = self.STATE_B1_PRESSED + + @b2.handler + def on_b2_press(): + if self.state == self.STATE_WAITING_B2: + self.state = self.STATE_B2_PRESSED + + # The available calibration modes (selected by k2) + CALIBRATION_MODES = [ + "Low (10V in)", + "Low (5V in)", + "High (0-10V in)", + ] + + self.text_wait("Calibration\nMode", 3) + + # Initial hardware & software checks + self.check_directory() + self.check_rack_power() + + # Calibration start + self.state = self.STATE_MODE_SELECT + refresh_ui = lambda: oled.centre_text(f"Choose mode (k2)\n{k2.choice(CALIBRATION_MODES)}\nDone: Button 2") + refresh_ui() + self.wait_for_b2(refresh_ui) + + # Peform the requested input calibration + choice = k2.choice(CALIBRATION_MODES) + if choice == CALIBRATION_MODES[0]: + calibration_values = CalibrationValues(CalibrationValues.MODE_LOW_10V) + calibration_values.input_calibration_values = self.input_calibration_low10() + elif choice == CALIBRATION_MODES[1]: + calibration_values = CalibrationValues(CalibrationValues.MODE_LOW_5V) + calibration_values.input_calibration_values = self.input_calibration_low5() + else: + calibration_values = CalibrationValues(CalibrationValues.MODE_HIGH) + calibration_values.input_calibration_values = self.input_calibration_high() + + # Output calibration + self.state = self.STATE_START_OUTPUT + oled.centre_text(f"Plug CV1 into\nanalogue in\nDone: Button 1") + self.wait_for_b1() + oled.centre_text("Calibrating...") + + if calibration_values.mode == CalibrationValues.MODE_HIGH: + readings_in = calibration_values.input_calibration_values + else: + # expand the raw calibration values if we were in a low-accuracy mode such that + # we have an expected reading for every volt from 0-10 + readings_in = [calibration_values.input_calibration_values[0]] + m = (calibration_values.input_calibration_values[1] - calibration_values.input_calibration_values[0]) / 10 + c = calibration_values.input_calibration_values[0] + for x in range(1, 10): + readings_in.append(round((m * x) + c)) + readings_in.append(calibration_values.input_calibration_values[-1]) + + # Output 1-10V on CV1 & read the result on AIN + # Adjust the duty cycle of CV1 until the input is within an acceptable range + calibration_values.output_calibration_values = [0] + duty = 0 + cv1.pin.duty_u16(duty) + reading = self.read_sample() + for index, expected_reading in enumerate(readings_in[1:]): + while abs(reading - expected_reading) > 0.002 and reading < expected_reading: + cv1.pin.duty_u16(duty) + duty += 10 + reading = self.read_sample() + calibration_values.output_calibration_values.append(duty) + oled.centre_text(f"Calibrating...\n{index+1}V") + + # Save the result + oled.centre_text("Saving\ncalibration...") + calibration_values.save() + + # Prompt the user to reboot to apply the new calibration + # Spin here forever if necessary + while True: + self.text_wait("Calibration\bcomplete", 3) + self.text_wait("Reboot\nto apply\nnew calibration", 3) + + +if __name__ == "__main__": + Calibrate().main() diff --git a/software/contrib/diagnostic.md b/software/firmware/tools/diagnostic.md similarity index 100% rename from software/contrib/diagnostic.md rename to software/firmware/tools/diagnostic.md diff --git a/software/contrib/diagnostic.py b/software/firmware/tools/diagnostic.py similarity index 100% rename from software/contrib/diagnostic.py rename to software/firmware/tools/diagnostic.py diff --git a/software/programming_instructions.md b/software/programming_instructions.md index b63fd40e7..82feeb9f7 100644 --- a/software/programming_instructions.md +++ b/software/programming_instructions.md @@ -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 From 8df3da5baaa90ecb1fd58ada8e4ce1bbc7f107b7 Mon Sep 17 00:00:00 2001 From: Chris I-B Date: Wed, 11 Sep 2024 14:18:37 -0400 Subject: [PATCH 03/30] Implement a new Thermometer class to wrap the temperature sensor. Add necessary exception handling for Pico 2's (currently) unsupported temperature sensor --- software/firmware/europi.py | 8 ++++++-- software/firmware/tools/diagnostic.py | 9 +++++++-- 2 files changed, 13 insertions(+), 4 deletions(-) diff --git a/software/firmware/europi.py b/software/firmware/europi.py index 81b2052ef..2ace4605f 100644 --- a/software/firmware/europi.py +++ b/software/firmware/europi.py @@ -104,9 +104,10 @@ 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. @@ -546,7 +547,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 """ @@ -565,10 +568,11 @@ 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: - # See the Pico's datasheet for the details of this calculation + # 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 diff --git a/software/firmware/tools/diagnostic.py b/software/firmware/tools/diagnostic.py index 7d69d9e20..986c676fc 100644 --- a/software/firmware/tools/diagnostic.py +++ b/software/firmware/tools/diagnostic.py @@ -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,15 @@ def config_points(cls): return [configuration.choice(name="TEMP_UNITS", choices=["C", "F"], default="C")] def calc_temp(self): +<<<<<<< HEAD # see the pico's datasheet for the details of this calculation t = thermometer.read_temperature() +======= + t = thermometer.get_temperature() + if t is None: + return 0 + +>>>>>>> 891824e (Implement a new Thermometer class to wrap the temperature sensor. Add necessary exception handling for Pico 2's (currently) unsupported temperature sensor) if self.use_fahrenheit: t = (t * 1.8) + 32 return t From 7b8e97bbc06932198075f34920faf8453ac7e3b9 Mon Sep 17 00:00:00 2001 From: Chris I-B Date: Wed, 11 Sep 2024 14:29:13 -0400 Subject: [PATCH 04/30] Linting --- software/firmware/tools/calibrate.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/software/firmware/tools/calibrate.py b/software/firmware/tools/calibrate.py index 1695804ab..2211f797e 100644 --- a/software/firmware/tools/calibrate.py +++ b/software/firmware/tools/calibrate.py @@ -15,6 +15,7 @@ class CalibrationValues: In either mode, output_calibration_values is a length 11 array with the raw samples taken at 0-10V, in 1V increments. """ + MODE_UNKNOWN = "unk" MODE_LOW_10V = "low10" MODE_LOW_5V = "low5" @@ -256,7 +257,9 @@ def on_b2_press(): # Calibration start self.state = self.STATE_MODE_SELECT - refresh_ui = lambda: oled.centre_text(f"Choose mode (k2)\n{k2.choice(CALIBRATION_MODES)}\nDone: Button 2") + refresh_ui = lambda: oled.centre_text( + f"Choose mode (k2)\n{k2.choice(CALIBRATION_MODES)}\nDone: Button 2" + ) refresh_ui() self.wait_for_b2(refresh_ui) @@ -284,7 +287,9 @@ def on_b2_press(): # expand the raw calibration values if we were in a low-accuracy mode such that # we have an expected reading for every volt from 0-10 readings_in = [calibration_values.input_calibration_values[0]] - m = (calibration_values.input_calibration_values[1] - calibration_values.input_calibration_values[0]) / 10 + m = ( + calibration_values.input_calibration_values[1] - calibration_values.input_calibration_values[0] + ) / 10 c = calibration_values.input_calibration_values[0] for x in range(1, 10): readings_in.append(round((m * x) + c)) From 81fb866a7f538effbc372d17f197ed385fbfbd2e Mon Sep 17 00:00:00 2001 From: Chris I-B Date: Wed, 11 Sep 2024 14:30:18 -0400 Subject: [PATCH 05/30] Linting --- software/firmware/tools/calibrate.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/software/firmware/tools/calibrate.py b/software/firmware/tools/calibrate.py index 2211f797e..151d0a4be 100644 --- a/software/firmware/tools/calibrate.py +++ b/software/firmware/tools/calibrate.py @@ -288,7 +288,8 @@ def on_b2_press(): # we have an expected reading for every volt from 0-10 readings_in = [calibration_values.input_calibration_values[0]] m = ( - calibration_values.input_calibration_values[1] - calibration_values.input_calibration_values[0] + calibration_values.input_calibration_values[1] + - calibration_values.input_calibration_values[0] ) / 10 c = calibration_values.input_calibration_values[0] for x in range(1, 10): From ebfc0befd3f78680eb246012be23e4d5a0b57c9f Mon Sep 17 00:00:00 2001 From: Chris I-B Date: Wed, 11 Sep 2024 20:13:47 -0400 Subject: [PATCH 06/30] get_temperature -> read_temperature() --- software/firmware/tools/diagnostic.py | 5 ----- 1 file changed, 5 deletions(-) diff --git a/software/firmware/tools/diagnostic.py b/software/firmware/tools/diagnostic.py index 986c676fc..ca951d23d 100644 --- a/software/firmware/tools/diagnostic.py +++ b/software/firmware/tools/diagnostic.py @@ -56,15 +56,10 @@ def config_points(cls): return [configuration.choice(name="TEMP_UNITS", choices=["C", "F"], default="C")] def calc_temp(self): -<<<<<<< HEAD - # see the pico's datasheet for the details of this calculation t = thermometer.read_temperature() -======= - t = thermometer.get_temperature() if t is None: return 0 ->>>>>>> 891824e (Implement a new Thermometer class to wrap the temperature sensor. Add necessary exception handling for Pico 2's (currently) unsupported temperature sensor) if self.use_fahrenheit: t = (t * 1.8) + 32 return t From 21f5cb6a2b4f18f530735b7a3cf5a859b9fd01b4 Mon Sep 17 00:00:00 2001 From: Chris I-B Date: Wed, 11 Sep 2024 20:35:47 -0400 Subject: [PATCH 07/30] Implement a new class for checking the USB connection; Pin 24 doesn't appear to work on the Pico 2 --- software/firmware/europi.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/software/firmware/europi.py b/software/firmware/europi.py index 2ace4605f..109db3f42 100644 --- a/software/firmware/europi.py +++ b/software/firmware/europi.py @@ -581,6 +581,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 """ @@ -599,6 +600,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: From c44ecb68aeab2aaf7d3f73343fac91039d49f31a Mon Sep 17 00:00:00 2001 From: Chris I-B Date: Wed, 11 Sep 2024 20:36:20 -0400 Subject: [PATCH 08/30] Fix typos --- software/firmware/tools/calibrate.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/software/firmware/tools/calibrate.py b/software/firmware/tools/calibrate.py index 151d0a4be..b4a38bf0c 100644 --- a/software/firmware/tools/calibrate.py +++ b/software/firmware/tools/calibrate.py @@ -89,7 +89,7 @@ class Calibrate(EuroPiScript): # As above, but for B2 STATE_WAITING_B2 = -3 - STATE_B7_PRESSED = -4 + STATE_B2_PRESSED = -4 # The current state of the program # The state progresses pretty linearly with minimal branching, so mostly @@ -317,7 +317,7 @@ def on_b2_press(): # Prompt the user to reboot to apply the new calibration # Spin here forever if necessary while True: - self.text_wait("Calibration\bcomplete", 3) + self.text_wait("Calibration\ncomplete", 3) self.text_wait("Reboot\nto apply\nnew calibration", 3) From 69a7889f7a34ce49cc724d26fb6737e4f52775b3 Mon Sep 17 00:00:00 2001 From: Chris I-B Date: Wed, 11 Sep 2024 20:52:49 -0400 Subject: [PATCH 09/30] Add `class mem` and `mem32` to mocks/machine --- software/tests/mocks/machine.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/software/tests/mocks/machine.py b/software/tests/mocks/machine.py index 77941d51c..5b95e6022 100644 --- a/software/tests/mocks/machine.py +++ b/software/tests/mocks/machine.py @@ -59,7 +59,8 @@ def freq(_): class mem: """ Fakes underlying machine registers. - Shouldn't be used directly, but any registers (e.g. mem32) can be instantiated with this + + Shouldn't be used directly, but any registers (e.g. mem32) can be isntantiated with this """ def __getitem__(self, _): From a4f1adeb48a0b9a1442757596e253d3fd9e7476d Mon Sep 17 00:00:00 2001 From: Chris I-B Date: Wed, 11 Sep 2024 21:13:57 -0400 Subject: [PATCH 10/30] Typo in docstring --- software/tests/mocks/machine.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/software/tests/mocks/machine.py b/software/tests/mocks/machine.py index 5b95e6022..a1ec18e22 100644 --- a/software/tests/mocks/machine.py +++ b/software/tests/mocks/machine.py @@ -60,7 +60,7 @@ class mem: """ Fakes underlying machine registers. - Shouldn't be used directly, but any registers (e.g. mem32) can be isntantiated with this + Shouldn't be used directly, but any registers (e.g. mem32) can be instantiated with this """ def __getitem__(self, _): From 6d017359530eab15c88e8e8f6a7e580290e1ee5f Mon Sep 17 00:00:00 2001 From: Chris I-B Date: Sun, 15 Sep 2024 16:33:06 -0400 Subject: [PATCH 11/30] Add per-output calibration instead of just CV1 --- software/firmware/europi.py | 41 ++++++++++++++------ software/firmware/tools/calibrate.md | 4 +- software/firmware/tools/calibrate.py | 56 ++++++++++++++++++---------- 3 files changed, 68 insertions(+), 33 deletions(-) diff --git a/software/firmware/europi.py b/software/firmware/europi.py index 109db3f42..dc38619f6 100644 --- a/software/firmware/europi.py +++ b/software/firmware/europi.py @@ -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 @@ -495,7 +510,11 @@ 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.calibration_values = calibration_values self.pin = PWM(Pin(pin)) self.pin.freq(PWM_FREQ) self._duty = 0 @@ -504,8 +523,8 @@ def __init__(self, pin, min_voltage=MIN_OUTPUT_VOLTAGE, max_voltage=MAX_OUTPUT_V self.gate_voltage = clamp(europi_config.GATE_VOLTAGE, self.MIN_VOLTAGE, self.MAX_VOLTAGE) 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): @@ -519,7 +538,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.""" @@ -640,13 +659,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 diff --git a/software/firmware/tools/calibrate.md b/software/firmware/tools/calibrate.md index b2f4c1a8f..7d62b5c33 100644 --- a/software/firmware/tools/calibrate.md +++ b/software/firmware/tools/calibrate.md @@ -24,8 +24,8 @@ Calibration is interactive with instructions displayed on-screen. These instruct 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` -6. Wait for the module to perform the output calibration. +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 diff --git a/software/firmware/tools/calibrate.py b/software/firmware/tools/calibrate.py index b4a38bf0c..9ad970cc3 100644 --- a/software/firmware/tools/calibrate.py +++ b/software/firmware/tools/calibrate.py @@ -1,5 +1,5 @@ from time import sleep -from europi import oled, b1, b2, k2, ain, cv1, usb_connected +from europi import oled, b1, b2, k2, ain, cvs, usb_connected, turn_off_all_cvs from europi_script import EuroPiScript from os import stat, mkdir @@ -251,10 +251,14 @@ def on_b2_press(): self.text_wait("Calibration\nMode", 3) + ###################################################################################################### + # Initial hardware & software checks self.check_directory() self.check_rack_power() + ###################################################################################################### + # Calibration start self.state = self.STATE_MODE_SELECT refresh_ui = lambda: oled.centre_text( @@ -263,6 +267,8 @@ def on_b2_press(): refresh_ui() self.wait_for_b2(refresh_ui) + ###################################################################################################### + # Peform the requested input calibration choice = k2.choice(CALIBRATION_MODES) if choice == CALIBRATION_MODES[0]: @@ -275,12 +281,6 @@ def on_b2_press(): calibration_values = CalibrationValues(CalibrationValues.MODE_HIGH) calibration_values.input_calibration_values = self.input_calibration_high() - # Output calibration - self.state = self.STATE_START_OUTPUT - oled.centre_text(f"Plug CV1 into\nanalogue in\nDone: Button 1") - self.wait_for_b1() - oled.centre_text("Calibrating...") - if calibration_values.mode == CalibrationValues.MODE_HIGH: readings_in = calibration_values.input_calibration_values else: @@ -296,19 +296,35 @@ def on_b2_press(): readings_in.append(round((m * x) + c)) readings_in.append(calibration_values.input_calibration_values[-1]) - # Output 1-10V on CV1 & read the result on AIN - # Adjust the duty cycle of CV1 until the input is within an acceptable range - calibration_values.output_calibration_values = [0] - duty = 0 - cv1.pin.duty_u16(duty) - reading = self.read_sample() - for index, expected_reading in enumerate(readings_in[1:]): - while abs(reading - expected_reading) > 0.002 and reading < expected_reading: - cv1.pin.duty_u16(duty) - duty += 10 - reading = self.read_sample() - calibration_values.output_calibration_values.append(duty) - oled.centre_text(f"Calibrating...\n{index+1}V") + ###################################################################################################### + + # Output calibration + self.state = self.STATE_START_OUTPUT + calibration_values.output_calibration_values = [] + + turn_off_all_cvs() + for i in range(len(cvs)): + oled.centre_text(f"Plug CV{i+1} into\nanalogue in\nDone: Button 1") + self.wait_for_b1() + oled.centre_text(f"Calibrating\nCV{i+1}...") + + # always 0 duty for 0V out + calibration_values.output_calibration_values.append([0]) + + # Output 1-10V on each CV output & read the result on AIN + # Adjust the duty cycle of the CV output until the input is within an acceptable range + duty = 0 + cvs[i].pin.duty_u16(duty) + reading = self.read_sample() + for index, expected_reading in enumerate(readings_in[1:]): + while abs(reading - expected_reading) > 0.002 and reading < expected_reading: + cvs[i].pin.duty_u16(duty) + duty += 10 + reading = self.read_sample() + calibration_values.output_calibration_values[-1].append(duty) + oled.centre_text(f"Calibrating...\n{index+1}V") + + cvs[i].off() # Save the result oled.centre_text("Saving\ncalibration...") From d94c2ebc79a3b93831e3b0594d2b3ae34e33b7ce Mon Sep 17 00:00:00 2001 From: Chris I-B Date: Sun, 15 Sep 2024 16:38:31 -0400 Subject: [PATCH 12/30] Indentation cleanup --- software/firmware/europi.py | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/software/firmware/europi.py b/software/firmware/europi.py index dc38619f6..222c73407 100644 --- a/software/firmware/europi.py +++ b/software/firmware/europi.py @@ -510,10 +510,13 @@ 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, - calibration_values=OUTPUT_CALIBRATION_VALUES[0]): + def __init__( + self, + pin, + min_voltage=MIN_OUTPUT_VOLTAGE, + max_voltage=MAX_OUTPUT_VOLTAGE, + calibration_values=OUTPUT_CALIBRATION_VALUES[0] + ): self.calibration_values = calibration_values self.pin = PWM(Pin(pin)) self.pin.freq(PWM_FREQ) From 133a2b0b5b663c26c6caba80dacbb30d0d79cca5 Mon Sep 17 00:00:00 2001 From: Chris I-B Date: Sun, 15 Sep 2024 16:40:33 -0400 Subject: [PATCH 13/30] Trailing comma --- software/firmware/europi.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/software/firmware/europi.py b/software/firmware/europi.py index 222c73407..e267dd9e3 100644 --- a/software/firmware/europi.py +++ b/software/firmware/europi.py @@ -515,7 +515,7 @@ def __init__( pin, min_voltage=MIN_OUTPUT_VOLTAGE, max_voltage=MAX_OUTPUT_VOLTAGE, - calibration_values=OUTPUT_CALIBRATION_VALUES[0] + calibration_values=OUTPUT_CALIBRATION_VALUES[0], ): self.calibration_values = calibration_values self.pin = PWM(Pin(pin)) From 139643a7aae887748861c496446101b8750d5b58 Mon Sep 17 00:00:00 2001 From: Chris I-B Date: Sun, 15 Sep 2024 16:51:38 -0400 Subject: [PATCH 14/30] Rename Output.calibration_values -> Output._calibration_values --- software/firmware/europi.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/software/firmware/europi.py b/software/firmware/europi.py index e267dd9e3..4431227ef 100644 --- a/software/firmware/europi.py +++ b/software/firmware/europi.py @@ -517,17 +517,17 @@ def __init__( max_voltage=MAX_OUTPUT_VOLTAGE, calibration_values=OUTPUT_CALIBRATION_VALUES[0], ): - self.calibration_values = calibration_values 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(self.calibration_values[:-1]): - self._gradients.append(self.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): @@ -541,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(self.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.""" From e38bc60ded5a66073cebf3a2472157a26509dd3b Mon Sep 17 00:00:00 2001 From: Chris I-B Date: Wed, 9 Oct 2024 10:44:56 -0400 Subject: [PATCH 15/30] Add new tools directory to the UF2 builder script --- software/uf2_build/copy_and_compile.sh | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/software/uf2_build/copy_and_compile.sh b/software/uf2_build/copy_and_compile.sh index 459adb1d7..9b1ac4f8c 100644 --- a/software/uf2_build/copy_and_compile.sh +++ b/software/uf2_build/copy_and_compile.sh @@ -4,8 +4,10 @@ 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 cp -r europi/software/contrib/*.py micropython/ports/rp2/modules/contrib echo "Compiling micropython and firmware modules..." @@ -13,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 From 4ba118d2bf3cc002d463465719b6c7b32cb1521d Mon Sep 17 00:00:00 2001 From: Chris I-B Date: Mon, 11 Nov 2024 16:13:13 -0500 Subject: [PATCH 16/30] Add tools.about for easily checking the version, CPU clocking, & configured EuroPi & Pico models --- software/contrib/menu.py | 1 + software/firmware/tools/about.md | 3 +++ software/firmware/tools/about.py | 21 +++++++++++++++++++++ 3 files changed, 25 insertions(+) create mode 100644 software/firmware/tools/about.md create mode 100644 software/firmware/tools/about.py diff --git a/software/contrib/menu.py b/software/contrib/menu.py index 004231a63..cbd83d205 100644 --- a/software/contrib/menu.py +++ b/software/contrib/menu.py @@ -63,6 +63,7 @@ ["Turing Machine", "contrib.turing_machine.EuroPiTuringMachine"], ["Volts", "contrib.volts.OffsetVoltages"], + ["_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! diff --git a/software/firmware/tools/about.md b/software/firmware/tools/about.md new file mode 100644 index 000000000..2fa5c0c7e --- /dev/null +++ b/software/firmware/tools/about.md @@ -0,0 +1,3 @@ +# About + +Displays version & other information about the software diff --git a/software/firmware/tools/about.py b/software/firmware/tools/about.py new file mode 100644 index 000000000..4d700b73b --- /dev/null +++ b/software/firmware/tools/about.py @@ -0,0 +1,21 @@ +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() From 9f7ce744a41473cdd193971434eb117a77d6b097 Mon Sep 17 00:00:00 2001 From: Chris I-B Date: Mon, 11 Nov 2024 16:19:09 -0500 Subject: [PATCH 17/30] Linting --- software/firmware/tools/about.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/software/firmware/tools/about.py b/software/firmware/tools/about.py index 4d700b73b..1c3c2c39d 100644 --- a/software/firmware/tools/about.py +++ b/software/firmware/tools/about.py @@ -3,6 +3,7 @@ from time import sleep from version import __version__ + class About(EuroPiScript): def __init__(self): super().__init__() @@ -12,10 +13,12 @@ def main(self): oled.centre_text(f"""EuroPi v{__version__} {europi_config.EUROPI_MODEL}/{europi_config.PICO_MODEL} -{europi_config.CPU_FREQ}""") +{europi_config.CPU_FREQ}""" + ) while True: sleep(1) + if __name__ == "__main__": About().main() From a4d3ed5b603c36331beedc97ce04cef9ae7c88dc Mon Sep 17 00:00:00 2001 From: Chris I-B Date: Mon, 11 Nov 2024 16:20:09 -0500 Subject: [PATCH 18/30] Linting --- software/firmware/tools/about.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/software/firmware/tools/about.py b/software/firmware/tools/about.py index 1c3c2c39d..02ed77777 100644 --- a/software/firmware/tools/about.py +++ b/software/firmware/tools/about.py @@ -11,7 +11,8 @@ def __init__(self): def main(self): turn_off_all_cvs() - oled.centre_text(f"""EuroPi v{__version__} + oled.centre_text( + f"""EuroPi v{__version__} {europi_config.EUROPI_MODEL}/{europi_config.PICO_MODEL} {europi_config.CPU_FREQ}""" ) From 5ade4db27869e1d1dff7ee3eba4e603b0aab9285 Mon Sep 17 00:00:00 2001 From: Chris I-B Date: Tue, 19 Nov 2024 20:57:22 -0500 Subject: [PATCH 19/30] Split the output calibration into two stages for improved accuracy --- software/firmware/tools/calibrate.py | 27 +++++++++++++++++++++++---- 1 file changed, 23 insertions(+), 4 deletions(-) diff --git a/software/firmware/tools/calibrate.py b/software/firmware/tools/calibrate.py index 9ad970cc3..287a1287f 100644 --- a/software/firmware/tools/calibrate.py +++ b/software/firmware/tools/calibrate.py @@ -306,7 +306,6 @@ def on_b2_press(): for i in range(len(cvs)): oled.centre_text(f"Plug CV{i+1} into\nanalogue in\nDone: Button 1") self.wait_for_b1() - oled.centre_text(f"Calibrating\nCV{i+1}...") # always 0 duty for 0V out calibration_values.output_calibration_values.append([0]) @@ -316,13 +315,33 @@ def on_b2_press(): duty = 0 cvs[i].pin.duty_u16(duty) reading = self.read_sample() - for index, expected_reading in enumerate(readings_in[1:]): + COARSE_STEP = 10 + FINE_STEP = 1 + for volts, expected_reading in enumerate(readings_in[1:]): + oled.centre_text(f"Calibrating...\n CV{i+1} @ {volts+1}V") + + # Step 1: coarse calibration + # increase the duty in large steps until we get within 0.002V of teh expected reading while abs(reading - expected_reading) > 0.002 and reading < expected_reading: + duty += COARSE_STEP + cvs[i].pin.duty_u16(duty) + reading = self.read_sample() + + # Step 2: fine calibration + # increase or decrease the duty in much smaller increments + count = 0 + while abs(reading - expected_reading) > 0.001 and count <= COARSE_STEP*2: + count+= 1 + if reading < expected_reading: + duty += FINE_STEP + elif reading > expected_reading: + duty -= FINE_STEP + cvs[i].pin.duty_u16(duty) - duty += 10 + sleep(0.05) reading = self.read_sample() + calibration_values.output_calibration_values[-1].append(duty) - oled.centre_text(f"Calibrating...\n{index+1}V") cvs[i].off() From 8066662f28f5bb46cdb96fb6d2cc76146a153cbd Mon Sep 17 00:00:00 2001 From: Chris I-B Date: Tue, 19 Nov 2024 21:08:13 -0500 Subject: [PATCH 20/30] Record twice as many samples, but discard the highest and lowest 1/4 to remove outliers --- software/firmware/tools/calibrate.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/software/firmware/tools/calibrate.py b/software/firmware/tools/calibrate.py index 287a1287f..fd9c14d39 100644 --- a/software/firmware/tools/calibrate.py +++ b/software/firmware/tools/calibrate.py @@ -118,10 +118,14 @@ def read_sample(self): @return The average across 256 distinct readings from the pin """ - N_READINGS = 256 + N_READINGS = 512 readings = [] for reading in range(N_READINGS): readings.append(ain.pin.read_u16()) + + # discard the lowest & highest 1/4 of the readings as outliers + readings.sort() + readings = readings[N_READINGS//4 : 3*N_READINGS//4] return round(sum(readings) / N_READINGS) def wait_for_voltage(self, voltage): From 1336de5aa5f8fae5dde5523394c0233be27d174d Mon Sep 17 00:00:00 2001 From: Chris I-B Date: Tue, 19 Nov 2024 22:07:12 -0500 Subject: [PATCH 21/30] Linting --- software/firmware/tools/calibrate.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/software/firmware/tools/calibrate.py b/software/firmware/tools/calibrate.py index fd9c14d39..78abb7dbe 100644 --- a/software/firmware/tools/calibrate.py +++ b/software/firmware/tools/calibrate.py @@ -125,7 +125,7 @@ def read_sample(self): # discard the lowest & highest 1/4 of the readings as outliers readings.sort() - readings = readings[N_READINGS//4 : 3*N_READINGS//4] + readings = readings[N_READINGS // 4 : 3 * N_READINGS // 4] return round(sum(readings) / N_READINGS) def wait_for_voltage(self, voltage): @@ -334,8 +334,8 @@ def on_b2_press(): # Step 2: fine calibration # increase or decrease the duty in much smaller increments count = 0 - while abs(reading - expected_reading) > 0.001 and count <= COARSE_STEP*2: - count+= 1 + while abs(reading - expected_reading) > 0.001 and count <= COARSE_STEP * 2: + count += 1 if reading < expected_reading: duty += FINE_STEP elif reading > expected_reading: From 585a93a0e7294de9426b11f2b515e1bbb1c1186d Mon Sep 17 00:00:00 2001 From: Chris I-B Date: Wed, 20 Nov 2024 12:06:25 -0500 Subject: [PATCH 22/30] Increase the sleep during the fine-calibration step, sleep before reading the input calibrations, fix averaging the input calibration readings --- software/firmware/tools/calibrate.py | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/software/firmware/tools/calibrate.py b/software/firmware/tools/calibrate.py index 78abb7dbe..a719ed31a 100644 --- a/software/firmware/tools/calibrate.py +++ b/software/firmware/tools/calibrate.py @@ -2,6 +2,7 @@ from europi import oled, b1, b2, k2, ain, cvs, usb_connected, turn_off_all_cvs from europi_script import EuroPiScript from os import stat, mkdir +from experimental.math_extras import mean class CalibrationValues: @@ -126,7 +127,7 @@ def read_sample(self): # discard the lowest & highest 1/4 of the readings as outliers readings.sort() readings = readings[N_READINGS // 4 : 3 * N_READINGS // 4] - return round(sum(readings) / N_READINGS) + return round(mean(readings)) def wait_for_voltage(self, voltage): """ @@ -141,6 +142,8 @@ def wait_for_voltage(self, voltage): else: oled.centre_text(f"Plug in {voltage:0.1f}V\n\nDone: Button 1") self.wait_for_b1() + oled.centre_text(f"Calibrating...\nAIN @ {voltage:0.1f}V") + sleep(1) return self.read_sample() def wait_for_b1(self, wait_fn=None): @@ -285,8 +288,10 @@ def on_b2_press(): calibration_values = CalibrationValues(CalibrationValues.MODE_HIGH) calibration_values.input_calibration_values = self.input_calibration_high() + # make a local copy of the analogue-in readings, extrapolated if necessary, + # that we can use to perform the output calibration if calibration_values.mode == CalibrationValues.MODE_HIGH: - readings_in = calibration_values.input_calibration_values + readings_in = list(calibration_values.input_calibration_values) else: # expand the raw calibration values if we were in a low-accuracy mode such that # we have an expected reading for every volt from 0-10 @@ -342,7 +347,7 @@ def on_b2_press(): duty -= FINE_STEP cvs[i].pin.duty_u16(duty) - sleep(0.05) + sleep(0.1) reading = self.read_sample() calibration_values.output_calibration_values[-1].append(duty) From 47f08fb8b8e4637f2c897d914ecdc58421eced3d Mon Sep 17 00:00:00 2001 From: Chris I-B Date: Wed, 20 Nov 2024 16:51:26 -0500 Subject: [PATCH 23/30] Increase samples in diagnostic, refactor the calibration to target the duty cycle instead of voltage --- software/firmware/tools/calibrate.py | 129 +++++++++++++++++--------- software/firmware/tools/diagnostic.py | 2 +- 2 files changed, 86 insertions(+), 45 deletions(-) diff --git a/software/firmware/tools/calibrate.py b/software/firmware/tools/calibrate.py index a719ed31a..07c114e7c 100644 --- a/software/firmware/tools/calibrate.py +++ b/software/firmware/tools/calibrate.py @@ -98,6 +98,12 @@ class Calibrate(EuroPiScript): # It _is_ necessary for button press detection state = 0 + # How much we adjust the duty cycle during coarse calibration + COARSE_STEP = 10 + + # How much we adjust the duty cycle during fine calibration + FINE_STEP = 1 + @classmethod def display_name(cls): """Push this script to the end of the menu.""" @@ -117,11 +123,11 @@ def read_sample(self): """ Read from the raw ain pin and return the average across several readings - @return The average across 256 distinct readings from the pin + @return The average across several distinct readings from the pin """ - N_READINGS = 512 + N_READINGS = 1024 readings = [] - for reading in range(N_READINGS): + for i in range(N_READINGS): readings.append(ain.pin.read_u16()) # discard the lowest & highest 1/4 of the readings as outliers @@ -138,7 +144,7 @@ def wait_for_voltage(self, voltage): @return The average samples read from ain (see @read_sample) """ if voltage == 0: - oled.centre_text(f"Unplug all\n\nDone: Button 1") + oled.centre_text("Unplug all\n\nDone: Button 1") else: oled.centre_text(f"Plug in {voltage:0.1f}V\n\nDone: Button 1") self.wait_for_b1() @@ -237,6 +243,79 @@ def input_calibration_high(self): readings.append(self.wait_for_voltage(i)) return readings + def calibrate_output(self, cv_n, calibration_values, input_readings): + """ + Send volts from CVx to AIN, adjusting the duty cycle so the output is correct. + + @param cv_n A value 0 <= cv_n < len(cvs) indicating which CV output we're calibrating + @param calibration_values The array of calibration values we append our results to + @param input_readings The duty cycles of AIN corresponding to 0, 1, 2, ..., 9, 10 volts + """ + oled.centre_text(f"Plug CV{cv_n+1} into\nanalogue in\nDone: Button 1") + self.wait_for_b1() + + # always 0 duty for 0V out + calibration_values.output_calibration_values.append([0]) + + # Output 1-10V on each CV output & read the result on AIN + # Adjust the duty cycle of the CV output until the input is within an acceptable range + duty = 0 + cvs[cv_n].pin.duty_u16(duty) + for volts, expected_reading in enumerate(input_readings[1:]): + oled.centre_text(f"Calibrating...\n CV{cv_n+1} @ {volts+1}V") + + sleep(1) + + duty = self.coarse_output_calibration(cvs[cv_n], expected_reading, duty) + duty = self.fine_output_calibration(cvs[cv_n], expected_reading, duty) + + calibration_values.output_calibration_values[-1].append(duty) + + cvs[cv_n].off() + + def coarse_output_calibration(self, cv, goal_duty, start_duty): + """ + Perform a fast, coarse calibration to bring the CV output's duty cycle somewhere close + + @param cv The CV output pin we're adjusting + @param goal_duty The AIN duty cycle we're expecting to read + @param start_duty The CVx duty cycle we're applying to the output initially + + @return The adjusted output duty cycle + """ + read_duty = self.read_sample() + duty = start_duty + while abs(read_duty - goal_duty) > self.COARSE_STEP and read_duty < goal_duty: + duty += self.COARSE_STEP + cv.pin.duty_u16(duty) + read_duty = self.read_sample() + + return duty + + def fine_output_calibration(self, cv, goal_duty, start_duty): + """ + Perform a slower, fine calibration to bring the CV output's duty cycle towards the goal + + @param cv The CV output pin we're adjusting + @param goal_duty The AIN duty cycle we're expecting to read + @param start_duty The CVx duty cycle we're applying to the output initially + + @return The adjusted output duty cycle + """ + count = 0 + read_duty = self.read_sample() + duty = start_duty + while abs(read_duty - goal_duty) >= 2 * self.FINE_STEP and count < 2 * self.COARSE_STEP: + if read_duty < goal_duty: + duty += self.FINE_STEP + elif read_duty < goal_duty: + duty -= self.FINE_STEP + cv.pin.duty_u16(duty) + sleep(0.1) + read_duty = self.read_sample() + + return duty + def main(self): # Button handlers @b1.handler @@ -310,49 +389,11 @@ def on_b2_press(): # Output calibration self.state = self.STATE_START_OUTPUT calibration_values.output_calibration_values = [] - turn_off_all_cvs() for i in range(len(cvs)): - oled.centre_text(f"Plug CV{i+1} into\nanalogue in\nDone: Button 1") - self.wait_for_b1() + self.calibrate_output(i, calibration_values, readings_in) - # always 0 duty for 0V out - calibration_values.output_calibration_values.append([0]) - - # Output 1-10V on each CV output & read the result on AIN - # Adjust the duty cycle of the CV output until the input is within an acceptable range - duty = 0 - cvs[i].pin.duty_u16(duty) - reading = self.read_sample() - COARSE_STEP = 10 - FINE_STEP = 1 - for volts, expected_reading in enumerate(readings_in[1:]): - oled.centre_text(f"Calibrating...\n CV{i+1} @ {volts+1}V") - - # Step 1: coarse calibration - # increase the duty in large steps until we get within 0.002V of teh expected reading - while abs(reading - expected_reading) > 0.002 and reading < expected_reading: - duty += COARSE_STEP - cvs[i].pin.duty_u16(duty) - reading = self.read_sample() - - # Step 2: fine calibration - # increase or decrease the duty in much smaller increments - count = 0 - while abs(reading - expected_reading) > 0.001 and count <= COARSE_STEP * 2: - count += 1 - if reading < expected_reading: - duty += FINE_STEP - elif reading > expected_reading: - duty -= FINE_STEP - - cvs[i].pin.duty_u16(duty) - sleep(0.1) - reading = self.read_sample() - - calibration_values.output_calibration_values[-1].append(duty) - - cvs[i].off() + ###################################################################################################### # Save the result oled.centre_text("Saving\ncalibration...") diff --git a/software/firmware/tools/diagnostic.py b/software/firmware/tools/diagnostic.py index ca951d23d..4441ce4c1 100644 --- a/software/firmware/tools/diagnostic.py +++ b/software/firmware/tools/diagnostic.py @@ -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) From 904a845b8ff4e656848d909bcda4e1474013f48d Mon Sep 17 00:00:00 2001 From: Chris I-B Date: Wed, 20 Nov 2024 19:08:59 -0500 Subject: [PATCH 24/30] Reduce the samples back to 512, refactor the coarse/fine calibration to add another intermediate step so we adjust in increments of 50/5/1. This appears to speed things up without sacrificing accuracy. Tweak the UI to add a progress indication to the output calibration --- software/firmware/tools/calibrate.py | 51 ++++++++++++++++++---------- 1 file changed, 33 insertions(+), 18 deletions(-) diff --git a/software/firmware/tools/calibrate.py b/software/firmware/tools/calibrate.py index 07c114e7c..221c7d678 100644 --- a/software/firmware/tools/calibrate.py +++ b/software/firmware/tools/calibrate.py @@ -98,11 +98,6 @@ class Calibrate(EuroPiScript): # It _is_ necessary for button press detection state = 0 - # How much we adjust the duty cycle during coarse calibration - COARSE_STEP = 10 - - # How much we adjust the duty cycle during fine calibration - FINE_STEP = 1 @classmethod def display_name(cls): @@ -125,7 +120,7 @@ def read_sample(self): @return The average across several distinct readings from the pin """ - N_READINGS = 1024 + N_READINGS = 512 readings = [] for i in range(N_READINGS): readings.append(ain.pin.read_u16()) @@ -254,6 +249,15 @@ def calibrate_output(self, cv_n, calibration_values, input_readings): oled.centre_text(f"Plug CV{cv_n+1} into\nanalogue in\nDone: Button 1") self.wait_for_b1() + # Initial calibration in steps of 50, rapidly to get close to the goal + COARSE_STEP = 50 + + # Intermediate calibration in steps of 5 + INTERMEDIATE_STEP = 5 + + # Final fine calibration in steps of 1 + FINE_STEP = 1 + # always 0 duty for 0V out calibration_values.output_calibration_values.append([0]) @@ -262,54 +266,65 @@ def calibrate_output(self, cv_n, calibration_values, input_readings): duty = 0 cvs[cv_n].pin.duty_u16(duty) for volts, expected_reading in enumerate(input_readings[1:]): - oled.centre_text(f"Calibrating...\n CV{cv_n+1} @ {volts+1}V") - + oled.centre_text(f"Calibrating...\n CV{cv_n+1} @ {volts+1}V\n1/3") sleep(1) - - duty = self.coarse_output_calibration(cvs[cv_n], expected_reading, duty) - duty = self.fine_output_calibration(cvs[cv_n], expected_reading, duty) + duty = self.coarse_output_calibration(cvs[cv_n], expected_reading, duty, COARSE_STEP) + oled.centre_text(f"Calibrating...\n CV{cv_n+1} @ {volts+1}V\n2/3") + duty = self.fine_output_calibration(cvs[cv_n], expected_reading, duty, INTERMEDIATE_STEP, COARSE_STEP) + oled.centre_text(f"Calibrating...\n CV{cv_n+1} @ {volts+1}V\n3/3") + duty = self.fine_output_calibration(cvs[cv_n], expected_reading, duty, FINE_STEP, INTERMEDIATE_STEP) calibration_values.output_calibration_values[-1].append(duty) cvs[cv_n].off() - def coarse_output_calibration(self, cv, goal_duty, start_duty): + def coarse_output_calibration(self, cv, goal_duty, start_duty, step_size): """ Perform a fast, coarse calibration to bring the CV output's duty cycle somewhere close + This will exit if either the calibration is within +/- the step_size OR if the measured duty + cycle is higher than the goal (i.e. we've over-shot the goal) + @param cv The CV output pin we're adjusting @param goal_duty The AIN duty cycle we're expecting to read @param start_duty The CVx duty cycle we're applying to the output initially + @param step_size The amount by which we adjust the duty cycle up to reach the goal @return The adjusted output duty cycle """ read_duty = self.read_sample() duty = start_duty - while abs(read_duty - goal_duty) > self.COARSE_STEP and read_duty < goal_duty: - duty += self.COARSE_STEP + while abs(read_duty - goal_duty) > step_size and read_duty < goal_duty: + duty += step_size cv.pin.duty_u16(duty) read_duty = self.read_sample() return duty - def fine_output_calibration(self, cv, goal_duty, start_duty): + def fine_output_calibration(self, cv, goal_duty, start_duty, step_size, prev_step_size): """ Perform a slower, fine calibration to bring the CV output's duty cycle towards the goal + This exits if the measured duty cycle is within +/- 2*step_size OR if we make + 2*prev_step_size adjustments + @param cv The CV output pin we're adjusting @param goal_duty The AIN duty cycle we're expecting to read @param start_duty The CVx duty cycle we're applying to the output initially + @param step_size The amount by which we adjust the duty cycle up/down to reach the goal + @param prev_step_size The previous iteration's step size, used to limit how many adjustments we make @return The adjusted output duty cycle """ + MAX_COUNT = 2 * prev_step_size count = 0 read_duty = self.read_sample() duty = start_duty - while abs(read_duty - goal_duty) >= 2 * self.FINE_STEP and count < 2 * self.COARSE_STEP: + while abs(read_duty - goal_duty) >= 2 * step_size and count < MAX_COUNT: if read_duty < goal_duty: - duty += self.FINE_STEP + duty += step_size elif read_duty < goal_duty: - duty -= self.FINE_STEP + duty -= step_size cv.pin.duty_u16(duty) sleep(0.1) read_duty = self.read_sample() From e9ed48056782628e3abc7746a43e0428a12b30b3 Mon Sep 17 00:00:00 2001 From: Chris I-B Date: Wed, 20 Nov 2024 19:13:37 -0500 Subject: [PATCH 25/30] Linting --- software/firmware/tools/calibrate.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/software/firmware/tools/calibrate.py b/software/firmware/tools/calibrate.py index 221c7d678..3b720cea4 100644 --- a/software/firmware/tools/calibrate.py +++ b/software/firmware/tools/calibrate.py @@ -98,7 +98,6 @@ class Calibrate(EuroPiScript): # It _is_ necessary for button press detection state = 0 - @classmethod def display_name(cls): """Push this script to the end of the menu.""" @@ -270,9 +269,13 @@ def calibrate_output(self, cv_n, calibration_values, input_readings): sleep(1) duty = self.coarse_output_calibration(cvs[cv_n], expected_reading, duty, COARSE_STEP) oled.centre_text(f"Calibrating...\n CV{cv_n+1} @ {volts+1}V\n2/3") - duty = self.fine_output_calibration(cvs[cv_n], expected_reading, duty, INTERMEDIATE_STEP, COARSE_STEP) + duty = self.fine_output_calibration( + cvs[cv_n], expected_reading, duty, INTERMEDIATE_STEP, COARSE_STEP + ) oled.centre_text(f"Calibrating...\n CV{cv_n+1} @ {volts+1}V\n3/3") - duty = self.fine_output_calibration(cvs[cv_n], expected_reading, duty, FINE_STEP, INTERMEDIATE_STEP) + duty = self.fine_output_calibration( + cvs[cv_n], expected_reading, duty, FINE_STEP, INTERMEDIATE_STEP + ) calibration_values.output_calibration_values[-1].append(duty) From efaa964d5aa7f3e3f4cfd39455ed535a0d0bcc31 Mon Sep 17 00:00:00 2001 From: Chris I-B Date: Thu, 21 Nov 2024 08:17:43 -0500 Subject: [PATCH 26/30] Increment the counter, fix the elif condition in the fine calibration --- software/firmware/tools/calibrate.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/software/firmware/tools/calibrate.py b/software/firmware/tools/calibrate.py index 3b720cea4..40caa60e9 100644 --- a/software/firmware/tools/calibrate.py +++ b/software/firmware/tools/calibrate.py @@ -324,9 +324,10 @@ def fine_output_calibration(self, cv, goal_duty, start_duty, step_size, prev_ste read_duty = self.read_sample() duty = start_duty while abs(read_duty - goal_duty) >= 2 * step_size and count < MAX_COUNT: + count += 1 if read_duty < goal_duty: duty += step_size - elif read_duty < goal_duty: + elif read_duty > goal_duty: duty -= step_size cv.pin.duty_u16(duty) sleep(0.1) From 6ad499628edb70d694b088b4455201be17520e44 Mon Sep 17 00:00:00 2001 From: Chris I-B Date: Wed, 8 Jan 2025 19:51:05 -0500 Subject: [PATCH 27/30] Put the system tools in alphabetical order --- software/contrib/menu.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/software/contrib/menu.py b/software/contrib/menu.py index cbd83d205..1ffb561cd 100644 --- a/software/contrib/menu.py +++ b/software/contrib/menu.py @@ -63,10 +63,12 @@ ["Turing Machine", "contrib.turing_machine.EuroPiTuringMachine"], ["Volts", "contrib.volts.OffsetVoltages"], - ["_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! + # System tools, in alphabetical order with a _ prefix + + ["_About", "tools.about.About"], + ["_BootloaderMode", "bootloader_mode.BootloaderMode"], + ["_Calibrate", "tools.calibrate.Calibrate"], + ["_Diagnostic", "tools.diagnostic.Diagnostic"], ]) # fmt: on From 23017aa30dddafd2b2fc2dde9a4c73bc03a5d582 Mon Sep 17 00:00:00 2001 From: Chris I-B Date: Thu, 9 Jan 2025 23:46:19 -0500 Subject: [PATCH 28/30] Revert a change in a comment --- software/firmware/europi.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/software/firmware/europi.py b/software/firmware/europi.py index 4431227ef..35c1447f0 100644 --- a/software/firmware/europi.py +++ b/software/firmware/europi.py @@ -594,7 +594,7 @@ def read_temperature(self): @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 + # 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 From dac7f31442a8cd9d967ce9395af5c2e6b9360651 Mon Sep 17 00:00:00 2001 From: Chris I-B Date: Fri, 10 Jan 2025 13:02:26 -0500 Subject: [PATCH 29/30] Add a hardware mods file to keep track of unofficial changes to the hardware --- hardware/EuroPi/hardware_mods.md | 33 ++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) create mode 100644 hardware/EuroPi/hardware_mods.md diff --git a/hardware/EuroPi/hardware_mods.md b/hardware/EuroPi/hardware_mods.md new file mode 100644 index 000000000..583a7e4af --- /dev/null +++ b/hardware/EuroPi/hardware_mods.md @@ -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. + +TODO: picture here + +_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 1uF capacitor located in parallel with the final resistor: + + + +_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 1uF capacitor in parallel with `R23`. The easiest way to do this is to _carefully_ +solder the 1uF capacitor directly to the back-side of `R23`, as shown below: + + + +_A 1uF capacitor soldered to the back-side of the EuroPi PCB, in parallel with `R23`_ + +After soldering the 1uF capactor in place, you should [recalibrate EuroPi](/software/firmware/tools/calibrate.md). From fadccbcc3f880ed30662f78c7b06f4e4b5abbdb6 Mon Sep 17 00:00:00 2001 From: Chris I-B Date: Fri, 10 Jan 2025 13:09:29 -0500 Subject: [PATCH 30/30] 1uF -> 1nF, simplify image URLs --- hardware/EuroPi/hardware_mods.md | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/hardware/EuroPi/hardware_mods.md b/hardware/EuroPi/hardware_mods.md index 583a7e4af..54f45a691 100644 --- a/hardware/EuroPi/hardware_mods.md +++ b/hardware/EuroPi/hardware_mods.md @@ -9,25 +9,25 @@ Instead of [soldering jumper wires](/hardware/EuroPi/build_guide.md#oled-configu 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. -TODO: picture here + _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 1uF capacitor located in parallel with the final resistor: +a 1nF capacitor located in parallel with the final resistor: - + _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 1uF capacitor in parallel with `R23`. The easiest way to do this is to _carefully_ -solder the 1uF capacitor directly to the back-side of `R23`, as shown below: +`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: - + -_A 1uF capacitor soldered to the back-side of the EuroPi PCB, in parallel with `R23`_ +_A 1nF capacitor soldered to the back-side of the EuroPi PCB, in parallel with `R23`_ -After soldering the 1uF capactor in place, you should [recalibrate EuroPi](/software/firmware/tools/calibrate.md). +After soldering the 1nF capactor in place, you should [recalibrate EuroPi](/software/firmware/tools/calibrate.md).