From 9d30a9db775eeece3434bef61cc6e07ed688080d Mon Sep 17 00:00:00 2001 From: Chris I-B Date: Fri, 9 Aug 2024 12:23:06 -0400 Subject: [PATCH 01/25] Change the CPU_FREQ europi_config setting to a string instead of the actual frequency, add "pico2" as a new value for PICO_MODEL --- software/CONFIGURATION.md | 5 ++-- software/firmware/europi.py | 11 ++++++--- software/firmware/europi_config.py | 37 ++++++++++++++++++++++++------ 3 files changed, 41 insertions(+), 12 deletions(-) diff --git a/software/CONFIGURATION.md b/software/CONFIGURATION.md index 5c81ffc12..550e05c61 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, @@ -24,7 +24,8 @@ default configuration: - `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"` +- `CPU_FREQ` specifies whether or not the CPU should be overclocked. Must be one of `"overclocked"` or `"normal"`. + Default: `"overclocked"` - `ROTATE_DISPLAY` must be one of `false` or `true`. Default: `false` - `DISPLAY_WIDTH` is the width of the screen in pixels. The standard EuroPi screen is 128 pixels wide. Default: `128` - `DISPLAY_HEIGHT` is the height of the screen in pixels. The standard EuroPi screen is 32 pixels tall. Default: `32` diff --git a/software/firmware/europi.py b/software/firmware/europi.py index c1b4fa576..16f53a5d8 100644 --- a/software/firmware/europi.py +++ b/software/firmware/europi.py @@ -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": @@ -639,10 +639,15 @@ 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 = 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 914fc346e..562f99210 100644 --- a/software/firmware/europi_config.py +++ b/software/firmware/europi_config.py @@ -1,10 +1,30 @@ 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) +OVERCLOCKED_FREQ = "overclocked" +DEFAULT_FREQ = "normal" # the Europi default is to overclock, so to avoid confusion about the default + # not being "default" just use a different word + + +# Default & overclocked CPU frequencies for supported boards +# Key: board type (corresponds to EUROPI_MODEL setting) +# Sub-key: "default" or "overclocked" +CPU_FREQS = { + "pico": { + DEFAULT_FREQ: 125_000_000, # Pico default frequency is 125MHz + OVERCLOCKED_FREQ: 250_000_000 # Overclocked frequency is 250MHz + }, + "pico2": { + DEFAULT_FREQ: 150_000_000, # Pico2 default frequency is 150MHz + OVERCLOCKED_FREQ: 300_000_000 # Overclocked frequency is 300MHz + }, + "pico w": { + DEFAULT_FREQ: 125_000_000, # Pico W default frequency is 125MHz + OVERCLOCKED_FREQ: 250_000_000 # Overclocked frequency is 250MHz + } +} class EuroPiConfig: @@ -33,13 +53,16 @@ def config_points(cls): # CPU & board settings configuration.choice( name="PICO_MODEL", - choices=["pico", "pico w"], + choices=["pico", "pico w", "pico2"], default="pico" ), configuration.choice( name="CPU_FREQ", - choices=[PICO_DEFAULT_CPU_FREQ, OVERCLOCKED_CPU_FREQ], - default=OVERCLOCKED_CPU_FREQ, + choices=[ + DEFAULT_FREQ, + OVERCLOCKED_FREQ + ], + default=OVERCLOCKED_FREQ, ), # Display settings From bd9e9fdd6c067be18ea27da453c2954b88daaa85 Mon Sep 17 00:00:00 2001 From: Chris I-B Date: Sat, 10 Aug 2024 21:03:59 -0400 Subject: [PATCH 02/25] 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. --- software/CONFIGURATION.md | 7 +++++-- software/programming_instructions.md | 21 +++++++++++++++++++-- 2 files changed, 24 insertions(+), 4 deletions(-) diff --git a/software/CONFIGURATION.md b/software/CONFIGURATION.md index 550e05c61..fbd839aa2 100644 --- a/software/CONFIGURATION.md +++ b/software/CONFIGURATION.md @@ -22,8 +22,8 @@ default configuration: } ``` -- `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"` +- `EUROPI_MODEL` specifies the type of EuroPi module. Currently only `"europi"` is supported. Default: `"europi"`. +- `PICO_MODEL` must be one of `"pico"`, `"pico2"`, or `"pico w"`. Default: `"pico"`. - `CPU_FREQ` specifies whether or not the CPU should be overclocked. Must be one of `"overclocked"` or `"normal"`. Default: `"overclocked"` - `ROTATE_DISPLAY` must be one of `false` or `true`. Default: `false` @@ -41,6 +41,9 @@ default configuration: - `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/programming_instructions.md b/software/programming_instructions.md index ae1460081..e0f26cc32 100644 --- a/software/programming_instructions.md +++ b/software/programming_instructions.md @@ -28,13 +28,16 @@ The quickest way to get your EuroPi flashed with the latest firmware is to head ## Setting Up +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. ![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 From 8602d5393f9cdf12bd22a939eacf1b363285486f Mon Sep 17 00:00:00 2001 From: Chris I-B Date: Tue, 27 Aug 2024 17:04:10 -0400 Subject: [PATCH 03/25] Disable formatting checks to keep code legibly aligned, fix some other linter warnings --- software/firmware/europi.py | 4 +--- software/firmware/europi_config.py | 5 ++++- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/software/firmware/europi.py b/software/firmware/europi.py index 16f53a5d8..88a1f4eea 100644 --- a/software/firmware/europi.py +++ b/software/firmware/europi.py @@ -645,9 +645,7 @@ def value(self, value): # 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] -) +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 562f99210..128320d50 100644 --- a/software/firmware/europi_config.py +++ b/software/firmware/europi_config.py @@ -3,14 +3,16 @@ # sub-key constants for CPU_FREQS dict (see below) +# fmt: off OVERCLOCKED_FREQ = "overclocked" DEFAULT_FREQ = "normal" # the Europi default is to overclock, so to avoid confusion about the default # not being "default" just use a different word - +# fmt: on # Default & overclocked CPU frequencies for supported boards # Key: board type (corresponds to EUROPI_MODEL setting) # Sub-key: "default" or "overclocked" +# fmt: off CPU_FREQS = { "pico": { DEFAULT_FREQ: 125_000_000, # Pico default frequency is 125MHz @@ -25,6 +27,7 @@ OVERCLOCKED_FREQ: 250_000_000 # Overclocked frequency is 250MHz } } +# fmt: on class EuroPiConfig: From d64d065c60a1ef1f50cbee4a534b1140c791e487 Mon Sep 17 00:00:00 2001 From: Chris I-B Date: Wed, 28 Aug 2024 10:42:58 -0400 Subject: [PATCH 04/25] Update BOM with note about RP2350 --- hardware/EuroPi/bill_of_materials.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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. From 3d0f5f3e6119de5a01ac1e46525b95980bfc2c9e Mon Sep 17 00:00:00 2001 From: Chris I-B Date: Fri, 30 Aug 2024 10:59:48 -0400 Subject: [PATCH 05/25] 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) --- software/contrib/lutra.py | 77 +++++++++++++++++++++++++++++++++++---- 1 file changed, 70 insertions(+), 7 deletions(-) diff --git a/software/contrib/lutra.py b/software/contrib/lutra.py index 7d80f1eaf..922190ced 100644 --- a/software/contrib/lutra.py +++ b/software/contrib/lutra.py @@ -269,7 +269,9 @@ def gui_render_thread(self): """A thread function that handles drawing the GUI """ SHOW_WAVE_TIMEOUT = 3000 - while True: + + 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: @@ -284,10 +286,6 @@ 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 while usb_connected.value() == usb_connected_at_start: # Read the digital inputs self.digital_input_state.update() @@ -338,9 +336,74 @@ def wave_generation_thread(self): if self.config_dirty: self.save() + def single_thread(self): + """Temporary work-around for RP2350 + + Using the secondary core on the Pico 2 seems to be unstable, + so as a work-around don't use it and render a simplified UI + """ + prev_speed = -1 + prev_spread = -1 + + while True: + # Read the digital inputs + self.digital_input_state.update() + + # Manually handle B1 and DIN rising & falling + # If either goes high, signal that we want to old the outputs low + # If both become low, signal that all outputs should reset & output normally + if self.digital_input_state.b1_rising or self.digital_input_state.din_rising: + self.on_digital_in_rising() + elif ( + (self.digital_input_state.b1_falling and not self.digital_input_state.din_high) or + (self.digital_input_state.din_falling and not self.digital_input_state.b1_pressed) + ): + self.on_digital_in_falling() + + # Read the CV inputs and apply them + # Round to 2 decimal places to reduce noise + ain_percent = round(ain.percent(), 2) + k1_percent = round(k1.percent(), 2) + k2_percent = round(k2.percent(), 2) + + if self.config.AIN_MODE == self.AIN_MODE_SPREAD: + k_speed = k1_percent + k_spread = clamp(k2_percent + ain_percent, 0, 1) + else: + k_speed = clamp(k1_percent + ain_percent, 0, 1) + k_spread = k2_percent + + base_ticks = int((1.0 - k_speed) * (self.MAX_CYCLE_TICKS - self.MIN_CYCLE_TICKS) + self.MIN_CYCLE_TICKS) + for i in range(len(cvs)): + base_tick_multiplier = rescale(k_spread, 0, 1, 1, self.MAX_SPEED_MULTIPLIERS[i]) + spread_ticks = int(base_ticks / base_tick_multiplier) + self.waves[i].change_cycle_length(spread_ticks) + + for i in range(len(cvs)): + pixel_height = OLED_HEIGHT - 1 + if self.hold_low: + cvs[i].off() + else: + self.waves[i].tick() + + if prev_speed != k_speed or prev_spread != k_spread or self.config_dirty: + # render the simplified GUI + oled.centre_text(f"Speed: {k_speed}\nSpread: {k_spread}", auto_show=False) + oled.blit(WaveGenerator.WAVE_SHAPE_IMAGES[self.waves[0].shape], 0, 0) + oled.show() + + prev_speed = k_speed + prev_spread = k_spread + + if self.config_dirty: + self.save() + def main(self): - gui_thread = _thread.start_new_thread(self.gui_render_thread, ()) - self.wave_generation_thread() + if europi_config.PICO_MODEL == "pico2": + self.single_thread() + else: + gui_thread = _thread.start_new_thread(self.gui_render_thread, ()) + self.wave_generation_thread() if __name__ == "__main__": Lutra().main() From 50be0f73aa5ff4a6bdead1e85a668beac058d8e3 Mon Sep 17 00:00:00 2001 From: Chris I-B Date: Sun, 1 Sep 2024 12:38:09 -0400 Subject: [PATCH 06/25] Disable the temperature sensor in the diagnostics program for the Pico 2; it's not currently usable and causes an unhandled exception when used --- software/contrib/diagnostic.py | 24 ++++++++++++++++++------ 1 file changed, 18 insertions(+), 6 deletions(-) diff --git a/software/contrib/diagnostic.py b/software/contrib/diagnostic.py index 240f79d32..69204b5bd 100644 --- a/software/contrib/diagnostic.py +++ b/software/contrib/diagnostic.py @@ -17,6 +17,7 @@ k1, k2, oled, + europi_config, ) from europi_script import EuroPiScript import configuration @@ -40,7 +41,13 @@ class Diagnostic(EuroPiScript): def __init__(self): super().__init__() - self.temp_sensor = ADC(4) + if europi_config.PICO_MODEL == "pico2": + # Pico 2 tempreature sensor is not currently working + # dissable for now + # see: https://github.com/micropython/micropython/issues/15687 + self.temp_sensor = None + else: + self.temp_sensor = ADC(4) self.voltages = [ 0, # min 0.5, # not 0 but still below DI's threshold @@ -57,11 +64,16 @@ def config_points(cls): return [configuration.choice(name="TEMP_UNITS", choices=["C", "F"], default="C")] def calc_temp(self): - # see the pico's datasheet for the details of this calculation - t = 27 - ((self.temp_sensor.read_u16() * TEMP_CONV_FACTOR) - 0.706) / 0.001721 - if self.use_fahrenheit: - t = (t * 1.8) + 32 - return t + if self.temp_sensor: + # 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 + if self.use_fahrenheit: + t = (t * 1.8) + 32 + return t + else: + # Temporary work-around for the Pico 2's temperature sensor not working + # See https://github.com/micropython/micropython/issues/15687 + return 0.0 def rotate_r(self): self.voltages = self.voltages[-1:] + self.voltages[:-1] From 440ee41ee4df0594994de81e1169dcba4107b89c Mon Sep 17 00:00:00 2001 From: Chris I-B Date: Wed, 4 Sep 2024 23:50:59 -0400 Subject: [PATCH 07/25] 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 --- software/firmware/calibrate.py | 55 ++++++++++++++++++++++++++++------ 1 file changed, 46 insertions(+), 9 deletions(-) diff --git a/software/firmware/calibrate.py b/software/firmware/calibrate.py index 6b1177733..4e6af0f3f 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,10 +11,6 @@ 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): @@ -25,8 +20,14 @@ def sample(): 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) + oled.centre_text(f"""Plug in {voltage} +Done Skip + B1 B2""") + pressed = wait_for_button(1) + if pressed == b2: + oled.centre_text("Skipping...") + sleep(1.5) + return None else: oled.centre_text(f"Unplug all\n\nDone: Button 1") wait_for_b1(1) @@ -49,7 +50,31 @@ def flash(flashes, period): fill_show(0) sleep(period / 2) + def wait_for_button()): + """ + Wait for either button to be pressed + + + @return b1 or b2, indicating what button was maniupulated + """ + pressed = None + b1_pressed = b1.value() != 0 + b2_pressed = b2.value() != 0 + while not b1_pressed and not b2_pressed: + sleep(0.05) + b1_pressed = b1.value() != 0 + b2_pressed = b2.value() != 0 + if b1_pressed: + return b1 + else: + return b2 + def wait_for_b1(value): + """ + Wait for b1 to be pressed or released + + @param value Either 0 or 1, indicating if we're waiting for a release or a press + """ while b1.value() != value: sleep(0.05) @@ -62,7 +87,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) @@ -82,12 +107,24 @@ def wait_for_b1(value): readings = [] if chosen_process == 1: + # Not every rack can generate both 5 and 10V, but most should have at least one + # Ask for both voltages, but we can skip one or the other readings.append(wait_for_voltage(0)) + readings.append(wait_for_voltage(5)) readings.append(wait_for_voltage(10)) else: for voltage in range(11): readings.append(wait_for_voltage(voltage)) + # remove Nones from skipped values in the readings + # + done = False + while not done: + try: + readings.remove(None) + except ValueError: + done = True + with open(f"lib/calibration_values.py", "w") as file: values = ", ".join(map(str, readings)) file.write(f"INPUT_CALIBRATION_VALUES=[{values}]") From a909b07fa09e8cd6fbfe36025aa762fe3c08aaee Mon Sep 17 00:00:00 2001 From: Chris I-B Date: Wed, 4 Sep 2024 23:59:59 -0400 Subject: [PATCH 08/25] Refactor the calibration script to use member functions, add some docstrings, remove unused functions. Print the desired voltage with 1 decimal place and units --- software/firmware/calibrate.py | 150 +++++++++++++++++---------------- 1 file changed, 76 insertions(+), 74 deletions(-) diff --git a/software/firmware/calibrate.py b/software/firmware/calibrate.py index 4e6af0f3f..371bad659 100644 --- a/software/firmware/calibrate.py +++ b/software/firmware/calibrate.py @@ -10,74 +10,76 @@ 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.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} + def sample(self): + """ + Read the raw ADC samples from ain over several attempts and return their average + + @return The average of the ADC samples, rounded to the nearest integer + """ + N_READINGS = 256 + readings = [] + for i 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 into ain. + + Pressing B1 confirms they've done it, pressing B2 will skip and return None + + @param voltage The requested voltage + @return The measured sample, or None if the user skipped this measurement + """ + self.wait_for_b1(0) + if voltage != 0: + oled.centre_text(f"""Plug in {voltage:0.1f}V Done Skip - B1 B2""") - pressed = wait_for_button(1) - if pressed == b2: - oled.centre_text("Skipping...") - sleep(1.5) - return None - 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 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_button()): - """ - Wait for either button to be pressed - - - @return b1 or b2, indicating what button was maniupulated - """ - pressed = None +B1 B2""") + pressed = self.wait_for_button(1) + if pressed == b2: + oled.centre_text("Skipping...") + sleep(1.5) + return None + else: + oled.centre_text(f"Unplug all\n\nDone: Button 1") + self.wait_for_b1(1) + oled.centre_text("Calibrating...") + sleep(1.5) + return self.sample() + + def text_wait(self, text, wait): + oled.centre_text(text) + sleep(wait) + + def wait_for_button(self): + """ + Wait for either button to be pressed + + + @return b1 or b2, indicating what button was maniupulated + """ + b1_pressed = b1.value() != 0 + b2_pressed = b2.value() != 0 + while not b1_pressed and not b2_pressed: + sleep(0.05) b1_pressed = b1.value() != 0 b2_pressed = b2.value() != 0 - while not b1_pressed and not b2_pressed: - sleep(0.05) - b1_pressed = b1.value() != 0 - b2_pressed = b2.value() != 0 - if b1_pressed: - return b1 - else: - return b2 - - def wait_for_b1(value): - """ - Wait for b1 to be pressed or released - - @param value Either 0 or 1, indicating if we're waiting for a release or a press - """ - while b1.value() != value: - sleep(0.05) + if b1_pressed: + return b1 + else: + return b2 + def wait_for_b1(self, value): + """ + Wait for b1 to be pressed or released + + @param value Either 0 or 1, indicating if we're waiting for a release or a press + """ + while b1.value() != value: + sleep(0.05) + + def main(self): # Test if /lib exists. If not: Create it try: @@ -89,10 +91,10 @@ def wait_for_b1(value): 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) + self.wait_for_b1(1) + self.wait_for_b1(0) - text_wait("Calibration\nMode", 3) + self.text_wait("Calibration\nMode", 3) oled.centre_text("Choose Process\n\n1:LOW 2:HIGH") while True: @@ -109,12 +111,12 @@ def wait_for_b1(value): if chosen_process == 1: # Not every rack can generate both 5 and 10V, but most should have at least one # Ask for both voltages, but we can skip one or the other - readings.append(wait_for_voltage(0)) - readings.append(wait_for_voltage(5)) - readings.append(wait_for_voltage(10)) + readings.append(self.wait_for_voltage(0)) + readings.append(self.wait_for_voltage(5)) + readings.append(self.wait_for_voltage(10)) else: for voltage in range(11): - readings.append(wait_for_voltage(voltage)) + readings.append(self.wait_for_voltage(voltage)) # remove Nones from skipped values in the readings # @@ -132,7 +134,7 @@ def wait_for_b1(value): # Output Calibration oled.centre_text(f"Plug CV1 into\nanalogue in\nDone: Button 1") - wait_for_b1(1) + self.wait_for_b1(1) oled.centre_text("Calibrating...") if chosen_process == 1: @@ -147,12 +149,12 @@ def wait_for_b1(value): output_duties = [0] duty = 0 cv1.duty_u16(duty) - reading = sample() + reading = self.sample() for index, expected_reading in enumerate(readings[1:]): while abs(reading - expected_reading) > 0.002 and reading < expected_reading: cv1.duty_u16(duty) duty += 10 - reading = sample() + reading = self.sample() output_duties.append(duty) oled.centre_text(f"Calibrating...\n{index+1}V") From 0268eadb1e77366f3ccfb26d77b69e0ef0184a8a Mon Sep 17 00:00:00 2001 From: Chris I-B Date: Thu, 5 Sep 2024 00:01:28 -0400 Subject: [PATCH 09/25] String formatting --- software/firmware/calibrate.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/software/firmware/calibrate.py b/software/firmware/calibrate.py index 371bad659..136898850 100644 --- a/software/firmware/calibrate.py +++ b/software/firmware/calibrate.py @@ -35,7 +35,7 @@ def wait_for_voltage(self, voltage): if voltage != 0: oled.centre_text(f"""Plug in {voltage:0.1f}V Done Skip -B1 B2""") + B1 B2""") pressed = self.wait_for_button(1) if pressed == b2: oled.centre_text("Skipping...") From bf9c4694897d0a01ef20d33577cd7dd6c8defdb6 Mon Sep 17 00:00:00 2001 From: Chris I-B Date: Thu, 5 Sep 2024 00:29:23 -0400 Subject: [PATCH 10/25] Revert to the original calibration, but don't re-construct the pins; just use the ones from europi.py --- software/firmware/calibrate.py | 140 ++++++++++++--------------------- 1 file changed, 49 insertions(+), 91 deletions(-) diff --git a/software/firmware/calibrate.py b/software/firmware/calibrate.py index 136898850..4bdf7861e 100644 --- a/software/firmware/calibrate.py +++ b/software/firmware/calibrate.py @@ -1,3 +1,4 @@ +from machine import Pin, ADC, PWM, freq from time import sleep from europi import oled, b1, b2, ain, cv1, usb_connected from europi_script import EuroPiScript @@ -10,76 +11,45 @@ def display_name(cls): """Push this script to the end of the menu.""" return "~Calibrate" - def sample(self): - """ - Read the raw ADC samples from ain over several attempts and return their average - - @return The average of the ADC samples, rounded to the nearest integer - """ - N_READINGS = 256 - readings = [] - for i 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 into ain. - - Pressing B1 confirms they've done it, pressing B2 will skip and return None - - @param voltage The requested voltage - @return The measured sample, or None if the user skipped this measurement - """ - self.wait_for_b1(0) - if voltage != 0: - oled.centre_text(f"""Plug in {voltage:0.1f}V -Done Skip - B1 B2""") - pressed = self.wait_for_button(1) - if pressed == b2: - oled.centre_text("Skipping...") - sleep(1.5) - return None - else: - oled.centre_text(f"Unplug all\n\nDone: Button 1") - self.wait_for_b1(1) - oled.centre_text("Calibrating...") - sleep(1.5) - return self.sample() - - def text_wait(self, text, wait): - oled.centre_text(text) - sleep(wait) - - def wait_for_button(self): - """ - Wait for either button to be pressed - - - @return b1 or b2, indicating what button was maniupulated - """ - b1_pressed = b1.value() != 0 - b2_pressed = b2.value() != 0 - while not b1_pressed and not b2_pressed: - sleep(0.05) - b1_pressed = b1.value() != 0 - b2_pressed = b2.value() != 0 - if b1_pressed: - return b1 - else: - return b2 - - def wait_for_b1(self, value): - """ - Wait for b1 to be pressed or released + def main(self): - @param value Either 0 or 1, indicating if we're waiting for a release or a press - """ - while b1.value() != value: - sleep(0.05) + 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 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) - def main(self): # Test if /lib exists. If not: Create it try: @@ -91,10 +61,10 @@ def main(self): if usb_connected.value() == 1: oled.centre_text("Make sure rack\npower is on\nDone: Button 1") - self.wait_for_b1(1) - self.wait_for_b1(0) + wait_for_b1(1) + wait_for_b1(0) - self.text_wait("Calibration\nMode", 3) + text_wait("Calibration\nMode", 3) oled.centre_text("Choose Process\n\n1:LOW 2:HIGH") while True: @@ -109,23 +79,11 @@ def main(self): readings = [] if chosen_process == 1: - # Not every rack can generate both 5 and 10V, but most should have at least one - # Ask for both voltages, but we can skip one or the other - readings.append(self.wait_for_voltage(0)) - readings.append(self.wait_for_voltage(5)) - readings.append(self.wait_for_voltage(10)) + readings.append(wait_for_voltage(0)) + readings.append(wait_for_voltage(10)) else: for voltage in range(11): - readings.append(self.wait_for_voltage(voltage)) - - # remove Nones from skipped values in the readings - # - done = False - while not done: - try: - readings.remove(None) - except ValueError: - done = True + readings.append(wait_for_voltage(voltage)) with open(f"lib/calibration_values.py", "w") as file: values = ", ".join(map(str, readings)) @@ -134,7 +92,7 @@ def main(self): # Output Calibration oled.centre_text(f"Plug CV1 into\nanalogue in\nDone: Button 1") - self.wait_for_b1(1) + wait_for_b1(1) oled.centre_text("Calibrating...") if chosen_process == 1: @@ -148,13 +106,13 @@ def main(self): output_duties = [0] duty = 0 - cv1.duty_u16(duty) - reading = self.sample() + 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 = self.sample() + reading = sample() output_duties.append(duty) oled.centre_text(f"Calibrating...\n{index+1}V") From 79d25d8f01a541395d5a44c9d7ff61bee879bded Mon Sep 17 00:00:00 2001 From: Chris I-B Date: Thu, 5 Sep 2024 00:30:05 -0400 Subject: [PATCH 11/25] Remove unused functions --- software/firmware/calibrate.py | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/software/firmware/calibrate.py b/software/firmware/calibrate.py index 4bdf7861e..51b3e38b8 100644 --- a/software/firmware/calibrate.py +++ b/software/firmware/calibrate.py @@ -35,17 +35,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) From 9521bc6f5ba18ff618d10a25b5f3dd5a2ae01191 Mon Sep 17 00:00:00 2001 From: Chris I-B Date: Thu, 5 Sep 2024 00:30:28 -0400 Subject: [PATCH 12/25] Remove unused imports --- software/firmware/calibrate.py | 1 - 1 file changed, 1 deletion(-) diff --git a/software/firmware/calibrate.py b/software/firmware/calibrate.py index 51b3e38b8..3241dbe49 100644 --- a/software/firmware/calibrate.py +++ b/software/firmware/calibrate.py @@ -1,4 +1,3 @@ -from machine import Pin, ADC, PWM, freq from time import sleep from europi import oled, b1, b2, ain, cv1, usb_connected from europi_script import EuroPiScript From b605113f18f0d86341045ae41edcf1b9f6b9b14b Mon Sep 17 00:00:00 2001 From: Chris I-B Date: Wed, 9 Oct 2024 11:59:38 -0400 Subject: [PATCH 13/25] Revert changes to de-thread Lutra; the latest Pico 2 firmware appears to resolve the issue with _thread we were previously encountering --- software/contrib/lutra.py | 74 ++++----------------------------------- 1 file changed, 7 insertions(+), 67 deletions(-) diff --git a/software/contrib/lutra.py b/software/contrib/lutra.py index 922190ced..4a85619ca 100644 --- a/software/contrib/lutra.py +++ b/software/contrib/lutra.py @@ -270,6 +270,9 @@ def gui_render_thread(self): """ SHOW_WAVE_TIMEOUT = 3000 + # 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() @@ -285,6 +288,8 @@ def gui_render_thread(self): def wave_generation_thread(self): """A thread function that handles the underlying math of generating the waveforms """ + # To prevent the module locking up when we connect the USB for e.g. debugging, kill this thread + # if the USB state changes usb_connected_at_start = usb_connected.value() while usb_connected.value() == usb_connected_at_start: # Read the digital inputs @@ -336,74 +341,9 @@ def wave_generation_thread(self): if self.config_dirty: self.save() - def single_thread(self): - """Temporary work-around for RP2350 - - Using the secondary core on the Pico 2 seems to be unstable, - so as a work-around don't use it and render a simplified UI - """ - prev_speed = -1 - prev_spread = -1 - - while True: - # Read the digital inputs - self.digital_input_state.update() - - # Manually handle B1 and DIN rising & falling - # If either goes high, signal that we want to old the outputs low - # If both become low, signal that all outputs should reset & output normally - if self.digital_input_state.b1_rising or self.digital_input_state.din_rising: - self.on_digital_in_rising() - elif ( - (self.digital_input_state.b1_falling and not self.digital_input_state.din_high) or - (self.digital_input_state.din_falling and not self.digital_input_state.b1_pressed) - ): - self.on_digital_in_falling() - - # Read the CV inputs and apply them - # Round to 2 decimal places to reduce noise - ain_percent = round(ain.percent(), 2) - k1_percent = round(k1.percent(), 2) - k2_percent = round(k2.percent(), 2) - - if self.config.AIN_MODE == self.AIN_MODE_SPREAD: - k_speed = k1_percent - k_spread = clamp(k2_percent + ain_percent, 0, 1) - else: - k_speed = clamp(k1_percent + ain_percent, 0, 1) - k_spread = k2_percent - - base_ticks = int((1.0 - k_speed) * (self.MAX_CYCLE_TICKS - self.MIN_CYCLE_TICKS) + self.MIN_CYCLE_TICKS) - for i in range(len(cvs)): - base_tick_multiplier = rescale(k_spread, 0, 1, 1, self.MAX_SPEED_MULTIPLIERS[i]) - spread_ticks = int(base_ticks / base_tick_multiplier) - self.waves[i].change_cycle_length(spread_ticks) - - for i in range(len(cvs)): - pixel_height = OLED_HEIGHT - 1 - if self.hold_low: - cvs[i].off() - else: - self.waves[i].tick() - - if prev_speed != k_speed or prev_spread != k_spread or self.config_dirty: - # render the simplified GUI - oled.centre_text(f"Speed: {k_speed}\nSpread: {k_spread}", auto_show=False) - oled.blit(WaveGenerator.WAVE_SHAPE_IMAGES[self.waves[0].shape], 0, 0) - oled.show() - - prev_speed = k_speed - prev_spread = k_spread - - if self.config_dirty: - self.save() - def main(self): - if europi_config.PICO_MODEL == "pico2": - self.single_thread() - else: - gui_thread = _thread.start_new_thread(self.gui_render_thread, ()) - self.wave_generation_thread() + gui_thread = _thread.start_new_thread(self.gui_render_thread, ()) + self.wave_generation_thread() if __name__ == "__main__": Lutra().main() From fb15cec27c50bd4028e0c94fff4f4e7d1e6e594c Mon Sep 17 00:00:00 2001 From: Chris I-B Date: Wed, 9 Oct 2024 12:03:21 -0400 Subject: [PATCH 14/25] Remove superfluous newline --- software/firmware/calibrate.py | 1 - 1 file changed, 1 deletion(-) diff --git a/software/firmware/calibrate.py b/software/firmware/calibrate.py index 3241dbe49..af1d840ea 100644 --- a/software/firmware/calibrate.py +++ b/software/firmware/calibrate.py @@ -11,7 +11,6 @@ def display_name(cls): return "~Calibrate" def main(self): - def sample(): readings = [] for reading in range(256): From b3237d8e0a5616d061ac2312a6a500a504711f67 Mon Sep 17 00:00:00 2001 From: Chris I-B Date: Sat, 9 Nov 2024 14:08:23 -0500 Subject: [PATCH 15/25] Copy the temperature & USB connection changes from the calibration update branch into here, since they're needed for Pico2 support --- software/contrib/diagnostic.py | 23 ++++-------- software/firmware/europi.py | 64 +++++++++++++++++++++++++++++++-- software/tests/mocks/machine.py | 13 +++++++ 3 files changed, 80 insertions(+), 20 deletions(-) diff --git a/software/contrib/diagnostic.py b/software/contrib/diagnostic.py index 69204b5bd..7d69d9e20 100644 --- a/software/contrib/diagnostic.py +++ b/software/contrib/diagnostic.py @@ -18,6 +18,7 @@ k2, oled, europi_config, + thermometer, ) from europi_script import EuroPiScript import configuration @@ -41,13 +42,6 @@ class Diagnostic(EuroPiScript): def __init__(self): super().__init__() - if europi_config.PICO_MODEL == "pico2": - # Pico 2 tempreature sensor is not currently working - # dissable for now - # see: https://github.com/micropython/micropython/issues/15687 - self.temp_sensor = None - else: - self.temp_sensor = ADC(4) self.voltages = [ 0, # min 0.5, # not 0 but still below DI's threshold @@ -64,16 +58,11 @@ def config_points(cls): return [configuration.choice(name="TEMP_UNITS", choices=["C", "F"], default="C")] def calc_temp(self): - if self.temp_sensor: - # 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 - if self.use_fahrenheit: - t = (t * 1.8) + 32 - return t - else: - # Temporary work-around for the Pico 2's temperature sensor not working - # See https://github.com/micropython/micropython/issues/15687 - return 0.0 + # see the pico's datasheet for the details of this calculation + t = thermometer.read_temperature() + if self.use_fahrenheit: + t = (t * 1.8) + 32 + return t def rotate_r(self): self.voltages = self.voltages[-1:] + self.voltages[:-1] diff --git a/software/firmware/europi.py b/software/firmware/europi.py index 88a1f4eea..e7271fb14 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 @@ -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 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) @@ -640,7 +698,7 @@ def value(self, value): cvs = [cv1, cv2, cv3, cv4, cv5, cv6] # Helper object to detect if the USB cable is connected or not -usb_connected = DigitalReader(PIN_USB_CONNECTED, 0) +usb_connected = UsbConnection() # Set the desired clock speed according to the configuration # By default this will overclock the CPU, but some users may not want to diff --git a/software/tests/mocks/machine.py b/software/tests/mocks/machine.py index d4234b874..2dab14cd6 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 17a162d40227a9a23d08af30e09a16c9980f4663 Mon Sep 17 00:00:00 2001 From: Chris I-B Date: Sat, 9 Nov 2024 14:11:09 -0500 Subject: [PATCH 16/25] Instantiate the new thermometer --- software/firmware/europi.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/software/firmware/europi.py b/software/firmware/europi.py index e7271fb14..0cb9ddc91 100644 --- a/software/firmware/europi.py +++ b/software/firmware/europi.py @@ -700,6 +700,9 @@ def value(self): # 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() + # 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 From 3ceadcdd050b4b485640c30d52b9e587a221b4b3 Mon Sep 17 00:00:00 2001 From: Chris I-B Date: Mon, 11 Nov 2024 11:10:49 -0500 Subject: [PATCH 17/25] Add Pico H as an option for the Pico model --- software/CONFIGURATION.md | 2 +- software/firmware/europi_config.py | 6 +++++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/software/CONFIGURATION.md b/software/CONFIGURATION.md index fbd839aa2..f77bbc014 100644 --- a/software/CONFIGURATION.md +++ b/software/CONFIGURATION.md @@ -23,7 +23,7 @@ default configuration: ``` - `EUROPI_MODEL` specifies the type of EuroPi module. Currently only `"europi"` is supported. Default: `"europi"`. -- `PICO_MODEL` must be one of `"pico"`, `"pico2"`, or `"pico w"`. Default: `"pico"`. +- `PICO_MODEL` must be one of `"pico"`, `"pico2"`, `pico h`, or `"pico w"`. Default: `"pico"`. - `CPU_FREQ` specifies whether or not the CPU should be overclocked. Must be one of `"overclocked"` or `"normal"`. Default: `"overclocked"` - `ROTATE_DISPLAY` must be one of `false` or `true`. Default: `false` diff --git a/software/firmware/europi_config.py b/software/firmware/europi_config.py index 128320d50..e9b90ac4b 100644 --- a/software/firmware/europi_config.py +++ b/software/firmware/europi_config.py @@ -22,6 +22,10 @@ DEFAULT_FREQ: 150_000_000, # Pico2 default frequency is 150MHz OVERCLOCKED_FREQ: 300_000_000 # Overclocked frequency is 300MHz }, + "pico h": { + DEFAULT_FREQ: 125_000_000, # Pico H default frequency is 125MHz + OVERCLOCKED_FREQ: 250_000_000 # Overclocked frequency is 250MHz + }, "pico w": { DEFAULT_FREQ: 125_000_000, # Pico W default frequency is 125MHz OVERCLOCKED_FREQ: 250_000_000 # Overclocked frequency is 250MHz @@ -56,7 +60,7 @@ def config_points(cls): # CPU & board settings configuration.choice( name="PICO_MODEL", - choices=["pico", "pico w", "pico2"], + choices=["pico", "pico w", "pico2", "pico h"], default="pico" ), configuration.choice( From 6e6178531315171d1f0157f5a7393990dd9f421f Mon Sep 17 00:00:00 2001 From: Chris I-B Date: Mon, 11 Nov 2024 11:12:47 -0500 Subject: [PATCH 18/25] Docs --- 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 0cb9ddc91..c26de9d5f 100644 --- a/software/firmware/europi.py +++ b/software/firmware/europi.py @@ -634,7 +634,7 @@ class Thermometer: TEMP_CONV_FACTOR = 3.3 / 65535 def __init__(self): - # The Raspberry Pi Pico 2's temperature sensor doesn't with some older + # 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) From e675ebe3d0495a911d540de1b9049584974ed962 Mon Sep 17 00:00:00 2001 From: Chris I-B Date: Mon, 11 Nov 2024 11:13:33 -0500 Subject: [PATCH 19/25] Docs --- 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 c26de9d5f..9444dc599 100644 --- a/software/firmware/europi.py +++ b/software/firmware/europi.py @@ -647,7 +647,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 1e11dd4f0d9490364545a29c138e35f7d6cbd075 Mon Sep 17 00:00:00 2001 From: Chris I-B Date: Mon, 11 Nov 2024 11:15:25 -0500 Subject: [PATCH 20/25] Setting Up -> Pico Setup for section header --- software/programming_instructions.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/software/programming_instructions.md b/software/programming_instructions.md index e0f26cc32..b63fd40e7 100644 --- a/software/programming_instructions.md +++ b/software/programming_instructions.md @@ -26,7 +26,7 @@ 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). From 71bef5dcf8e74bc40cbedf1a1c1aab9107e068d7 Mon Sep 17 00:00:00 2001 From: Chris I-B Date: Sat, 7 Dec 2024 22:22:41 -0500 Subject: [PATCH 21/25] 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. --- software/firmware/europi_config.py | 26 ++++++++++++++++---------- 1 file changed, 16 insertions(+), 10 deletions(-) diff --git a/software/firmware/europi_config.py b/software/firmware/europi_config.py index 8f24709f9..4845adbf9 100644 --- a/software/firmware/europi_config.py +++ b/software/firmware/europi_config.py @@ -5,30 +5,35 @@ # sub-key constants for CPU_FREQS dict (see below) # fmt: off OVERCLOCKED_FREQ = "overclocked" +UNDERCLOCKED_FREQ = "underclocked" DEFAULT_FREQ = "normal" # the Europi default is to overclock, so to avoid confusion about the default # not being "default" just use a different word # fmt: on # Default & overclocked CPU frequencies for supported boards # Key: board type (corresponds to EUROPI_MODEL setting) -# Sub-key: "default" or "overclocked" +# Sub-key: "default" or "overclocked" or "underclocked" # fmt: off CPU_FREQS = { "pico": { - DEFAULT_FREQ: 125_000_000, # Pico default frequency is 125MHz - OVERCLOCKED_FREQ: 250_000_000 # Overclocked frequency is 250MHz + 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 }, "pico2": { - DEFAULT_FREQ: 150_000_000, # Pico2 default frequency is 150MHz - OVERCLOCKED_FREQ: 300_000_000 # Overclocked frequency is 300MHz + DEFAULT_FREQ: 150_000_000, # Pico2 default frequency is 150MHz + OVERCLOCKED_FREQ: 300_000_000, # Overclocked frequency is 300MHz + UNDERCLOCKED_FREQ: 75_000_000, # Underclock to 75MHz }, "pico h": { - DEFAULT_FREQ: 125_000_000, # Pico H default frequency is 125MHz - OVERCLOCKED_FREQ: 250_000_000 # Overclocked frequency is 250MHz + 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 }, "pico w": { - DEFAULT_FREQ: 125_000_000, # Pico W default frequency is 125MHz - OVERCLOCKED_FREQ: 250_000_000 # Overclocked frequency is 250MHz + 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 @@ -67,7 +72,8 @@ def config_points(cls): name="CPU_FREQ", choices=[ DEFAULT_FREQ, - OVERCLOCKED_FREQ + OVERCLOCKED_FREQ, + UNDERCLOCKED_FREQ, ], default=OVERCLOCKED_FREQ, ), From 142b5b36027f58bba30450a234f01526fcf0e21b Mon Sep 17 00:00:00 2001 From: Chris I-B Date: Sat, 7 Dec 2024 22:32:50 -0500 Subject: [PATCH 22/25] 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) --- software/firmware/europi_config.py | 58 +++++++++++++++++++----------- 1 file changed, 38 insertions(+), 20 deletions(-) diff --git a/software/firmware/europi_config.py b/software/firmware/europi_config.py index 4845adbf9..775626534 100644 --- a/software/firmware/europi_config.py +++ b/software/firmware/europi_config.py @@ -10,27 +10,39 @@ # not being "default" just use a different word # 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 = { - "pico": { + 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 }, - "pico2": { - DEFAULT_FREQ: 150_000_000, # Pico2 default frequency is 150MHz + 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 }, - "pico h": { + 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 }, - "pico w": { + 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 @@ -59,14 +71,20 @@ 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", "pico2", "pico h"], - 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", @@ -81,19 +99,19 @@ def config_points(cls): # 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", @@ -108,13 +126,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) @@ -131,19 +149,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 @@ -151,25 +169,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=12.0 + default=12.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 From 9669a238538bf9d5aed3da75a4ce77a140e84d79 Mon Sep 17 00:00:00 2001 From: Chris I-B Date: Sat, 7 Dec 2024 22:34:59 -0500 Subject: [PATCH 23/25] Fix default input voltage --- software/firmware/europi_config.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/software/firmware/europi_config.py b/software/firmware/europi_config.py index 775626534..615c1b075 100644 --- a/software/firmware/europi_config.py +++ b/software/firmware/europi_config.py @@ -175,7 +175,7 @@ def config_points(cls): name="MAX_INPUT_VOLTAGE", minimum=1.0, maximum=12.0, - default=12.0, + default=10.0, ), configuration.floatingPoint( name="GATE_VOLTAGE", From faf3b8a300267e4b66009a2343c0529f235352f4 Mon Sep 17 00:00:00 2001 From: Chris I-B Date: Sat, 7 Dec 2024 22:37:45 -0500 Subject: [PATCH 24/25] Comment restructuring --- software/firmware/europi_config.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/software/firmware/europi_config.py b/software/firmware/europi_config.py index 615c1b075..bb815b653 100644 --- a/software/firmware/europi_config.py +++ b/software/firmware/europi_config.py @@ -3,11 +3,12 @@ # 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" -DEFAULT_FREQ = "normal" # the Europi default is to overclock, so to avoid confusion about the default - # not being "default" just use a different word # fmt: on # Supported Pico model types From 6711f2f3474fc5002d66257d55b633634685adc3 Mon Sep 17 00:00:00 2001 From: Chris I-B Date: Sat, 7 Dec 2024 22:45:45 -0500 Subject: [PATCH 25/25] Update the configuration docs with the pico 2w option --- software/CONFIGURATION.md | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/software/CONFIGURATION.md b/software/CONFIGURATION.md index 62563b5e8..e92a834ad 100644 --- a/software/CONFIGURATION.md +++ b/software/CONFIGURATION.md @@ -29,7 +29,13 @@ 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"`, `"pico2"`, `pico h`, or `"pico w"`. Default: `"pico"`. +- `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"`