From c2da84cd447bad54d82bddd5d1efcd74838efe76 Mon Sep 17 00:00:00 2001 From: Chris I-B Date: Wed, 8 Jan 2025 15:26:41 -0500 Subject: [PATCH 1/3] Add support for Raspberry Pi Pico 2, Pico 2 W (#377) * Change the CPU_FREQ europi_config setting to a string instead of the actual frequency, add "pico2" as a new value for PICO_MODEL * Add additional docs for setting up the Pico 2. Untested so far, since I'm waiting for mine to ship and this is all considered placeholder for now. * Disable formatting checks to keep code legibly aligned, fix some other linter warnings * Update BOM with note about RP2350 * Implement a simplified, single-threaded version of Lutra so it doesn't hang on the RP2350. Hopefully this can be reverted with a future RP2350 Micropython implementation (breaks in 1.24.0-preview.201) * Disable the temperature sensor in the diagnostics program for the Pico 2; it's not currently usable and causes an unhandled exception when used * Use the standard europi GPIO pin wrapper instances instead of creating new ones. Ask for 5 and 10V for the simple calibration, but support skipping if the rack can't supply one or the other (most cases should be able to produce at least one). Allow skipping voltages during advanced calibration too * Remove unused functions & imports * Copy the temperature & USB connection changes from the calibration update branch into here, since they're needed for Pico2 support * Add Pico H as an option for the Pico model * Add "underclocked" key for CPU frequency; sets CPU frequency to 75MHz; any slower than this and CPU-intensive scripts (e.g. Pam's) may start having performance issues. * Add constants for the pico models, keep a space between pico & the variant (pico2 was the exception before). Add Pico 2W to the models (treated identically to Pico 2) * Fix default input voltage * Update the configuration docs with the pico 2w option --- hardware/EuroPi/bill_of_materials.md | 2 +- software/CONFIGURATION.md | 16 ++++- software/contrib/diagnostic.py | 5 +- software/contrib/lutra.py | 13 ++-- software/firmware/calibrate.py | 26 ++------ software/firmware/europi.py | 78 ++++++++++++++++++++-- software/firmware/europi_config.py | 97 ++++++++++++++++++++++------ software/programming_instructions.md | 23 ++++++- software/tests/mocks/machine.py | 13 ++++ 9 files changed, 210 insertions(+), 63 deletions(-) diff --git a/hardware/EuroPi/bill_of_materials.md b/hardware/EuroPi/bill_of_materials.md index de4e67db3..84cce0937 100644 --- a/hardware/EuroPi/bill_of_materials.md +++ b/hardware/EuroPi/bill_of_materials.md @@ -37,7 +37,7 @@ If buying in bulk there are likely other suppliers that are cheaper - this BOM i | | 2 | T18 Shaft | Knobs | [Thonk](https://www.thonk.co.uk/shop/1900h-t18/)
**NOTE: If you buy D-Shaft potentiometers, the knobs must be 'Reverse-D-Shaft' type.
Any knobs which fit may be chosen, but Mouser does not stock the standard suggestion sold by Thonk** | | 1 | 10 - 16 Pin | Eurorack Power Cable | [Thonk](https://www.thonk.co.uk/shop/eurorack-power-cables/)
**Note: Mouser does not stock the correct cable at time of writing. Other suppliers will provide the correct part, but always double check that the polarity is correct (red stripe is always -12V)** | | 1 | | Micro USB Cable (Capable of Data Transfer) | [CPC](https://cpc.farnell.com/pro-signal/psg91562/lead-usb-a-male-micro-b-male-black/dp/CS32732), [The Pi Hut](https://thepihut.com/products/usb-to-micro-usb-cable-0-5m)
[Mouser](https://www.mouser.co.uk/ProductDetail/Teltonika/PR2US08M?qs=9vOqFld9vZXl90mLcdqZXQ%3D%3D) -| | 1 | | Raspberry Pi Pico | [The Pi Hut](https://thepihut.com/products/raspberry-pi-pico)
[CPC](https://cpc.farnell.com/raspberry-pi/raspberry-pi-pico/raspberry-pi-pico-rp2040-mcu-board/dp/SC17106)
[Mouser](https://www.mouser.co.uk/ProductDetail/Raspberry-Pi/SC0915?qs=T%252BzbugeAwjgnLi4azxXVFA%3D%3D)
**Note: Any official version of the Raspberry Pi Pico will work (W, H, or WH). Third party RP2040 boards may work, but there is no guarantee - always double check the pinout** +| | 1 | | Raspberry Pi Pico | [The Pi Hut](https://thepihut.com/products/raspberry-pi-pico)
[CPC](https://cpc.farnell.com/raspberry-pi/raspberry-pi-pico/raspberry-pi-pico-rp2040-mcu-board/dp/SC17106)
[Mouser](https://www.mouser.co.uk/ProductDetail/Raspberry-Pi/SC0915?qs=T%252BzbugeAwjgnLi4azxXVFA%3D%3D)
The Raspberry Pi Pico 2 is also usable [with additional configuration](/software/CONFIGURATION.md)
**Note: Any official version of the Raspberry Pi Pico will work (W, H, or WH). Third party RP2040 or RP2350 boards may work, but there is no guarantee - always double check the pinout. RP2350 boards should be [configured like the Pico 2](/software/CONFIGURATION.md)** #### Note about OLED The OLED has two suppliers listed, each with different pin configurations. The module supports either of these two configurations (the most common), but no others, so make sure that the one you buy, wherever you source it, has one of these two configurations. diff --git a/software/CONFIGURATION.md b/software/CONFIGURATION.md index 0f5f1328d..e92a834ad 100644 --- a/software/CONFIGURATION.md +++ b/software/CONFIGURATION.md @@ -8,7 +8,7 @@ default configuration: { "EUROPI_MODEL": "europi", "PICO_MODEL": "pico", - "CPU_FREQ": 250000000, + "CPU_FREQ": "overclocked", "ROTATE_DISPLAY": false, "DISPLAY_WIDTH": 128, "DISPLAY_HEIGHT": 32, @@ -29,8 +29,15 @@ default configuration: CPU & Pico options: - `EUROPI_MODEL` specifies the type of EuroPi module. Currently only `"europi"` is supported. Default: `"europi"` -- `PICO_MODEL` must be one of `"pico"` or `"pico w"`. Default: `"pico"` -- `CPU_FREQ` must be one of `250000000` or `125000000`. Default: `"250000000"` +- `PICO_MODEL` must be one of + - `"pico"`, + - `"pico h"`, + - `"pico w"`, + - `"pico 2"`, or + - `"pico 2w"`. + Default: `"pico"`. +- `CPU_FREQ` specifies whether or not the CPU should be overclocked. Must be one of `"overclocked"` or `"normal"`. + Default: `"overclocked"` Display options: - `ROTATE_DISPLAY` must be one of `false` or `true`. Default: `false` @@ -59,6 +66,9 @@ Power options: - `MENU_AFTER_POWER_ON` is a boolean indicating whether or not the module should always return to the main menu when it powers on. By default the EuroPi will re-launch the last-used program instead of returning to the main menu. Default: `false` +If you assembled your module with the Raspberry Pi Pico 2 (or a clone featuring the RP2350 microcontroller) make sure to +set the `PICO_MODEL` setting to `"pico2"`. + # Experimental configuration diff --git a/software/contrib/diagnostic.py b/software/contrib/diagnostic.py index 240f79d32..7d69d9e20 100644 --- a/software/contrib/diagnostic.py +++ b/software/contrib/diagnostic.py @@ -17,6 +17,8 @@ k1, k2, oled, + europi_config, + thermometer, ) from europi_script import EuroPiScript import configuration @@ -40,7 +42,6 @@ class Diagnostic(EuroPiScript): def __init__(self): super().__init__() - self.temp_sensor = ADC(4) self.voltages = [ 0, # min 0.5, # not 0 but still below DI's threshold @@ -58,7 +59,7 @@ def config_points(cls): def calc_temp(self): # see the pico's datasheet for the details of this calculation - t = 27 - ((self.temp_sensor.read_u16() * TEMP_CONV_FACTOR) - 0.706) / 0.001721 + t = thermometer.read_temperature() if self.use_fahrenheit: t = (t * 1.8) + 32 return t diff --git a/software/contrib/lutra.py b/software/contrib/lutra.py index 7d80f1eaf..4a85619ca 100644 --- a/software/contrib/lutra.py +++ b/software/contrib/lutra.py @@ -269,7 +269,12 @@ def gui_render_thread(self): """A thread function that handles drawing the GUI """ SHOW_WAVE_TIMEOUT = 3000 - while True: + + # To prevent the module locking up when we connect the USB for e.g. debugging, kill this thread + # if the USB state changes. Otherwise the second core will continue being busy, which makes connecting + # to the Python terminal impossible + usb_connected_at_start = usb_connected.value() + while usb_connected.value() == usb_connected_at_start: now = time.ticks_ms() oled.fill(0) with self.pixel_lock: @@ -283,11 +288,9 @@ def gui_render_thread(self): def wave_generation_thread(self): """A thread function that handles the underlying math of generating the waveforms """ - usb_connected_at_start = usb_connected.value() - # To prevent the module locking up when we connect the USB for e.g. debugging, kill this thread - # if the USB state changes. Otherwise the second core will continue being busy, which makes connecting - # to the Python terminal impossible + # if the USB state changes + usb_connected_at_start = usb_connected.value() while usb_connected.value() == usb_connected_at_start: # Read the digital inputs self.digital_input_state.update() diff --git a/software/firmware/calibrate.py b/software/firmware/calibrate.py index 6b1177733..af1d840ea 100644 --- a/software/firmware/calibrate.py +++ b/software/firmware/calibrate.py @@ -1,6 +1,5 @@ -from machine import Pin, ADC, PWM, freq from time import sleep -from europi import oled, b1, b2, PIN_USB_CONNECTED, PIN_CV1, PIN_AIN +from europi import oled, b1, b2, ain, cv1, usb_connected from europi_script import EuroPiScript from os import stat, mkdir @@ -12,14 +11,10 @@ def display_name(cls): return "~Calibrate" def main(self): - ain = ADC(Pin(PIN_AIN, Pin.IN, Pin.PULL_DOWN)) - cv1 = PWM(Pin(PIN_CV1)) - usb = Pin(PIN_USB_CONNECTED, Pin.IN) - def sample(): readings = [] for reading in range(256): - readings.append(ain.read_u16()) + readings.append(ain.pin.read_u16()) return round(sum(readings) / 256) def wait_for_voltage(voltage): @@ -38,17 +33,6 @@ def text_wait(text, wait): oled.centre_text(text) sleep(wait) - def fill_show(colour): - oled.fill(colour) - oled.show() - - def flash(flashes, period): - for flash in range(flashes): - fill_show(1) - sleep(period / 2) - fill_show(0) - sleep(period / 2) - def wait_for_b1(value): while b1.value() != value: sleep(0.05) @@ -62,7 +46,7 @@ def wait_for_b1(value): # Calibration start - if usb.value() == 1: + 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) @@ -109,11 +93,11 @@ def wait_for_b1(value): output_duties = [0] duty = 0 - cv1.duty_u16(duty) + 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.duty_u16(duty) + cv1.pin.duty_u16(duty) duty += 10 reading = sample() output_duties.append(duty) diff --git a/software/firmware/europi.py b/software/firmware/europi.py index 7ec9778cc..dedf47592 100644 --- a/software/firmware/europi.py +++ b/software/firmware/europi.py @@ -23,7 +23,7 @@ from machine import PWM from machine import Pin from machine import freq - +from machine import mem32 from ssd1306 import SSD1306_I2C @@ -31,7 +31,7 @@ from configuration import ConfigSettings from framebuf import FrameBuffer, MONO_HLSB -from europi_config import load_europi_config +from europi_config import load_europi_config, CPU_FREQS from experimental.experimental_config import load_experimental_config if sys.implementation.name == "micropython": @@ -107,7 +107,7 @@ PIN_CV4 = 17 PIN_CV5 = 18 PIN_CV6 = 19 -PIN_USB_CONNECTED = 24 +PIN_USB_CONNECTED = 24 # Does not work on Pico 2 # Helper functions. @@ -622,6 +622,64 @@ 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 with some older + # uPython firmwares 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) @@ -639,6 +697,12 @@ def value(self, value): cv6 = Output(PIN_CV6) cvs = [cv1, cv2, cv3, cv4, cv5, cv6] +# Helper object to detect if the USB cable is connected or not +usb_connected = UsbConnection() + +# Helper object for reading the onboard temperature sensor +thermometer = Thermometer() + # External I2C external_i2c = I2C( europi_config.EXTERNAL_I2C_CHANNEL, @@ -648,10 +712,10 @@ def value(self, value): timeout=europi_config.EXTERNAL_I2C_TIMEOUT, ) -usb_connected = DigitalReader(PIN_USB_CONNECTED, 0) - -# Overclock the Pico for improved performance. -freq(europi_config.CPU_FREQ) +# Set the desired clock speed according to the configuration +# By default this will overclock the CPU, but some users may not want to +# e.g. to lower power consumption on a very power-constrained system +freq(CPU_FREQS[europi_config.PICO_MODEL][europi_config.CPU_FREQ]) # Reset the module state upon import. reset_state() diff --git a/software/firmware/europi_config.py b/software/firmware/europi_config.py index 13621f647..bb815b653 100644 --- a/software/firmware/europi_config.py +++ b/software/firmware/europi_config.py @@ -1,10 +1,55 @@ import configuration from configuration import ConfigFile, ConfigSpec -# Pico machine CPU freq. -# Default pico CPU freq is 125_000_000 (125mHz) -PICO_DEFAULT_CPU_FREQ = 125_000_000 -OVERCLOCKED_CPU_FREQ = 250_000_000 + +# sub-key constants for CPU_FREQS dict (see below) +# the Europi default is to overclock, so to avoid confusion about the default +# not being "default" just use a different word +# fmt: off +DEFAULT_FREQ = "normal" +OVERCLOCKED_FREQ = "overclocked" +UNDERCLOCKED_FREQ = "underclocked" +# fmt: on + +# Supported Pico model types +MODEL_PICO = "pico" +MODEL_PICO_H = "pico h" +MODEL_PICO_W = "pico w" +MODEL_PICO_2 = "pico 2" +MODEL_PICO_2W = "pico 2w" + +# Default & overclocked CPU frequencies for supported boards +# Key: board type (corresponds to EUROPI_MODEL setting) +# Sub-key: "default" or "overclocked" or "underclocked" +# fmt: off +CPU_FREQS = { + MODEL_PICO: { + DEFAULT_FREQ: 125_000_000, # Pico default frequency is 125MHz + OVERCLOCKED_FREQ: 250_000_000, # Overclocked frequency is 250MHz + UNDERCLOCKED_FREQ: 75_000_000 # Underclock to 75MHz + }, + MODEL_PICO_2: { + DEFAULT_FREQ: 150_000_000, # Pico 2 default frequency is 150MHz + OVERCLOCKED_FREQ: 300_000_000, # Overclocked frequency is 300MHz + UNDERCLOCKED_FREQ: 75_000_000, # Underclock to 75MHz + }, + MODEL_PICO_2W: { + DEFAULT_FREQ: 150_000_000, # Pico 2 W default frequency is 150MHz + OVERCLOCKED_FREQ: 300_000_000, # Overclocked frequency is 300MHz + UNDERCLOCKED_FREQ: 75_000_000, # Underclock to 75MHz + }, + MODEL_PICO_H: { + DEFAULT_FREQ: 125_000_000, # Pico H default frequency is 125MHz + OVERCLOCKED_FREQ: 250_000_000, # Overclocked frequency is 250MHz + UNDERCLOCKED_FREQ: 75_000_000, # Underclock to 75MHz + }, + MODEL_PICO_W: { + DEFAULT_FREQ: 125_000_000, # Pico W default frequency is 125MHz + OVERCLOCKED_FREQ: 250_000_000, # Overclocked frequency is 250MHz + UNDERCLOCKED_FREQ: 75_000_000, # Underclock to 75MHz + } +} +# fmt: on class EuroPiConfig: @@ -27,37 +72,47 @@ def config_points(cls): configuration.choice( name="EUROPI_MODEL", choices = ["europi"], - default="europi" + default="europi", ), # CPU & board settings configuration.choice( name="PICO_MODEL", - choices=["pico", "pico w"], - default="pico" + choices=[ + MODEL_PICO, + MODEL_PICO_W, + MODEL_PICO_H, + MODEL_PICO_2, + MODEL_PICO_2W, + ], + default=MODEL_PICO, ), configuration.choice( name="CPU_FREQ", - choices=[PICO_DEFAULT_CPU_FREQ, OVERCLOCKED_CPU_FREQ], - default=OVERCLOCKED_CPU_FREQ, + choices=[ + DEFAULT_FREQ, + OVERCLOCKED_FREQ, + UNDERCLOCKED_FREQ, + ], + default=OVERCLOCKED_FREQ, ), # Display settings configuration.boolean( name="ROTATE_DISPLAY", - default=False + default=False, ), configuration.integer( name="DISPLAY_WIDTH", minimum=8, maximum=1024, - default=128 + default=128, ), configuration.integer( name="DISPLAY_HEIGHT", minimum=8, maximum=1024, - default=32 + default=32, ), configuration.choice( name="DISPLAY_SDA", @@ -72,13 +127,13 @@ def config_points(cls): configuration.choice( name="DISPLAY_CHANNEL", choices=[0, 1], - default=0 + default=0, ), configuration.integer( name="DISPLAY_FREQUENCY", minimum=0, maximum=1000000, - default=400000 + default=400000, ), # External I2C connection (header between Pico & power connector) @@ -95,19 +150,19 @@ def config_points(cls): configuration.choice( name="EXTERNAL_I2C_CHANNEL", choices=[0, 1], - default=1 + default=1, ), configuration.integer( name="EXTERNAL_I2C_FREQUENCY", minimum=0, maximum=1000000, # 1M max - default=100000 # 100k default + default=100000, # 100k default ), configuration.integer( name="EXTERNAL_I2C_TIMEOUT", minimum=0, maximum=100000, - default=50000 + default=50000, ), # I/O voltage settings @@ -115,25 +170,25 @@ def config_points(cls): name="MAX_OUTPUT_VOLTAGE", minimum=1.0, maximum=10.0, - default=10.0 + default=10.0, ), configuration.floatingPoint( name="MAX_INPUT_VOLTAGE", minimum=1.0, maximum=12.0, - default=10.0 + default=10.0, ), configuration.floatingPoint( name="GATE_VOLTAGE", minimum=1.0, maximum=10.0, - default=5.0 + default=5.0, ), # Menu settings configuration.boolean( name="MENU_AFTER_POWER_ON", - default=False + default=False, ), ] # fmt: on diff --git a/software/programming_instructions.md b/software/programming_instructions.md index ae1460081..b63fd40e7 100644 --- a/software/programming_instructions.md +++ b/software/programming_instructions.md @@ -26,7 +26,10 @@ This document will take you through the steps to get your module ready to progra The quickest way to get your EuroPi flashed with the latest firmware is to head over to the [releases](https://github.com/Allen-Synthesis/EuroPi/releases) page and download the latest `europi-vX.Y.Z.uf2` file. Then follow the 'BOOTSEL' instructions above to flash the EuroPi firmware to your pico. -## Setting Up +## Pico Setup + +This section assumes you are using the Raspberry Pi Pico (or a compatible clone) featuring the RP2040 processor. If you +are using the newer Raspberry Pi Pico 2, with the RP2350 processor, see [below](#pico-2-setup). ### Downloading Thonny To start with, you'll need to download the [Thonny IDE](https://thonny.org/). This is what you will use to program and debug the module. @@ -34,7 +37,7 @@ To start with, you'll need to download the [Thonny IDE](https://thonny.org/). Th ![Thonny](https://i.imgur.com/UX4uQDO.jpg) ### Installing the firmware -1. Download the [most recent firmware](https://micropython.org/download/rp2-pico/) from the MicroPython website. The latest supported version is `1.22.2`. +1. Download the [most recent firmware](https://micropython.org/download/rp2-pico/) from the MicroPython website. The latest supported version is `1.23.0`. 2. Holding down the white button labeled 'BOOTSEL' on the Raspberry Pi Pico, connect the module to your computer via the USB cable. ![_DSC2400](https://user-images.githubusercontent.com/79809962/148647201-52b0d279-fc1e-4615-9e65-e51543605e15.jpg) @@ -89,7 +92,21 @@ The EuroPi Contrib library will make user-contributed software available on your ![Screenshot from 2023-07-14 03-02-02](https://github.com/Allen-Synthesis/EuroPi/assets/5189714/6690e1e3-56e1-49d6-8701-6f5912d10ba1) 1. Click 'Install'. -1. You will now see a 'contrib' folder inside the 'lib' folder which contains several software options with the extension `.py`. +1. You will now see a `contrib` folder inside the `lib` folder which contains several software options with the extension `.py`. + +## Pico 2 Setup + +If you are using the Raspberry Pi Pico 2, or a compatible clone, with the RP2350 processor, you must download the +MicroPython firmware for that board: +- [Download here](https://micropython.org/download/RPI_PICO2/). At the time of writing, the latest supported version is 1.24.0 + +Once the firmware is installed, continue installing the rest of the software: +1. [Installing the OLED library](#installing-the-oled-library) +2. [Installing the EuroPi library](#installing-the-oled-library) +3. [Installing the EuroPi Contrib library](#optional-installing-the-europi-contrib-library) + +Once the software is installed, you will need to [configure the software](/software/CONFIGURATION.md) to finish setting up +the Pico 2. ## Next Steps diff --git a/software/tests/mocks/machine.py b/software/tests/mocks/machine.py index f4d94a39a..77941d51c 100644 --- a/software/tests/mocks/machine.py +++ b/software/tests/mocks/machine.py @@ -54,3 +54,16 @@ def deinit(self): def freq(_): pass + + +class mem: + """ + Fakes underlying machine registers. + Shouldn't be used directly, but any registers (e.g. mem32) can be instantiated with this + """ + + def __getitem__(self, _): + return 0x00 + + +mem32 = mem() From 6eb6a68f0d84f46a4cccd347cdd60333dfdbe51d Mon Sep 17 00:00:00 2001 From: Chris I-B Date: Wed, 8 Jan 2025 15:28:29 -0500 Subject: [PATCH 2/3] Prevent ConfigPoints from crashing if the maximum voltages are lower-than-default (#393) * Fix a possible crash with the ConfigPoints if the global maximum output voltage has been lowered. Any channels whose default voltages are greater than the specified maximum will now be zero instead * Fix a possible crash with the ConfigPoints in Bezier if the maximum input voltage has been lowered from the default --- software/contrib/bezier.py | 52 +++++++++++++++++++++++++++++++++----- software/contrib/volts.py | 50 +++++++++++++++++++++++++++++++----- 2 files changed, 89 insertions(+), 13 deletions(-) diff --git a/software/contrib/bezier.py b/software/contrib/bezier.py index c5a296e4f..eb48f3af1 100644 --- a/software/contrib/bezier.py +++ b/software/contrib/bezier.py @@ -405,14 +405,52 @@ def on_b2_release(): def config_points(cls): """Return the static configuration options for this class """ + def restrict_input_voltage(v): + if v > europi_config.MAX_INPUT_VOLTAGE: + return europi_config.MAX_INPUT_VOLTAGE + return v + return [ - configuration.floatingPoint(name="MAX_INPUT_VOLTAGE", minimum=0.0, maximum=europi_config.MAX_INPUT_VOLTAGE, default=10.0), - configuration.floatingPoint(name="MIN_VOLTAGE", minimum=0.0, maximum=europi_config.MAX_OUTPUT_VOLTAGE, default=0.0), - configuration.floatingPoint(name="MAX_VOLTAGE", minimum=0.0, maximum=europi_config.MAX_OUTPUT_VOLTAGE, default=europi_config.MAX_OUTPUT_VOLTAGE), - configuration.floatingPoint(name="MIN_FREQUENCY", minimum=0.001, maximum=10.0, default=0.01), - configuration.floatingPoint(name="MAX_FREQUENCY", minimum=0.001, maximum=10.0, default=1.0), - configuration.choice(name="AIN_MODE", choices=["frequency", "curve"], default="frequency"), - configuration.choice(name="LOGIC_MODE", choices=["and", "or", "xor", "nand", "nor", "xnor"], default="xor") + configuration.floatingPoint( + name="MAX_INPUT_VOLTAGE", + minimum=0.0, + maximum=europi_config.MAX_INPUT_VOLTAGE, + default=restrict_input_voltage(10.0) + ), + configuration.floatingPoint( + name="MIN_VOLTAGE", + minimum=0.0, + maximum=europi_config.MAX_OUTPUT_VOLTAGE, + default=0.0 + ), + configuration.floatingPoint( + name="MAX_VOLTAGE", + minimum=0.0, + maximum=europi_config.MAX_OUTPUT_VOLTAGE, + default=europi_config.MAX_OUTPUT_VOLTAGE + ), + configuration.floatingPoint( + name="MIN_FREQUENCY", + minimum=0.001, + maximum=10.0, + default=0.01 + ), + configuration.floatingPoint( + name="MAX_FREQUENCY", + minimum=0.001, + maximum=10.0, + default=1.0 + ), + configuration.choice( + name="AIN_MODE", + choices=["frequency", "curve"], + default="frequency" + ), + configuration.choice( + name="LOGIC_MODE", + choices=["and", "or", "xor", "nand", "nor", "xnor"], + default="xor" + ) ] def save(self): diff --git a/software/contrib/volts.py b/software/contrib/volts.py index a3986ab59..8eb08a6a6 100644 --- a/software/contrib/volts.py +++ b/software/contrib/volts.py @@ -15,17 +15,55 @@ class OffsetVoltages(EuroPiScript): def __init__(self): super().__init__() + @classmethod def config_points(cls): """Return the static configuration options for this class """ + def restrict_voltage(v): + """If the max output voltage has been lowered, disable the too-high output + """ + if v > europi_config.MAX_OUTPUT_VOLTAGE: + return 0.0 + return v + return [ - configuration.floatingPoint(name="CV1", minimum=0.0, maximum=europi_config.MAX_OUTPUT_VOLTAGE, default=0.5), - configuration.floatingPoint(name="CV2", minimum=0.0, maximum=europi_config.MAX_OUTPUT_VOLTAGE, default=1.0), - configuration.floatingPoint(name="CV3", minimum=0.0, maximum=europi_config.MAX_OUTPUT_VOLTAGE, default=2.0), - configuration.floatingPoint(name="CV4", minimum=0.0, maximum=europi_config.MAX_OUTPUT_VOLTAGE, default=2.5), - configuration.floatingPoint(name="CV5", minimum=0.0, maximum=europi_config.MAX_OUTPUT_VOLTAGE, default=5.0), - configuration.floatingPoint(name="CV6", minimum=0.0, maximum=europi_config.MAX_OUTPUT_VOLTAGE, default=10.0), + configuration.floatingPoint( + name="CV1", + minimum=0.0, + maximum=europi_config.MAX_OUTPUT_VOLTAGE, + default=restrict_voltage(0.5) + ), + configuration.floatingPoint( + name="CV2", + minimum=0.0, + maximum=europi_config.MAX_OUTPUT_VOLTAGE, + default=restrict_voltage(1.0) + ), + configuration.floatingPoint( + name="CV3", + minimum=0.0, + maximum=europi_config.MAX_OUTPUT_VOLTAGE, + default=restrict_voltage(2.0) + ), + configuration.floatingPoint( + name="CV4", + minimum=0.0, + maximum=europi_config.MAX_OUTPUT_VOLTAGE, + default=restrict_voltage(2.5) + ), + configuration.floatingPoint( + name="CV5", + minimum=0.0, + maximum=europi_config.MAX_OUTPUT_VOLTAGE, + default=restrict_voltage(5.0) + ), + configuration.floatingPoint( + name="CV6", + minimum=0.0, + maximum=europi_config.MAX_OUTPUT_VOLTAGE, + default=restrict_voltage(10.0) + ), ] def main(self): From 8924ba2cc90ad88f1bf17424b96c67d15cde016d Mon Sep 17 00:00:00 2001 From: Chris I-B Date: Wed, 8 Jan 2025 15:29:46 -0500 Subject: [PATCH 3/3] Give the ValueErrors raised by the euclidean generator messages so there's better indication what's going wrong (#396) --- software/firmware/experimental/euclid.py | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/software/firmware/experimental/euclid.py b/software/firmware/experimental/euclid.py index 5067d7513..18c7bc2a6 100644 --- a/software/firmware/experimental/euclid.py +++ b/software/firmware/experimental/euclid.py @@ -36,10 +36,14 @@ def generate_euclidean_pattern(steps, pulses, rot=0): steps = int(steps) pulses = int(pulses) rot = int(rot) - if pulses > steps or pulses < 0: - raise ValueError - if rot > steps or steps < 0: - raise ValueError + if pulses > steps: + raise ValueError("Pulses cannot be greater than steps") + if pulses < 0: + raise ValueError("Pulses must be positive") + if rot > steps: + raise ValueError("Rotation cannot be greater than steps") + if steps < 0: + raise ValueError("Steps must be positive") if steps == 0: return [] if pulses == 0: