allen-synthesis / europi Goto Github PK
View Code? Open in Web Editor NEWEuroPi: A reprogrammable Eurorack module based on the Raspberry Pi Pico
License: Creative Commons Zero v1.0 Universal
EuroPi: A reprogrammable Eurorack module based on the Raspberry Pi Pico
License: Creative Commons Zero v1.0 Universal
Testing the Output
class, I am unable to get the output voltage above 9.12v.
To Reproduce
Steps to reproduce the behavior:
From a MicroPython shell running on the pico, I am testing setting the cv1 output duty cycle to the maximum value and I am only seeing 9.12V max.
> cv1.pin.duty_u16(65535)
On my oscilloscope I am reading 9.12v.
Expected behavior
I would expect to see a maximum output of 10v.
Improve "OSError: [Errno 5] EIO" error handling with more user friendly message.
Describe your suggestion or idea
The troubleshooting guide provides helpful tips for handling "OSError: [Errno 5] EIO". This error message can be improved by checking the I2C ready state before allowing the exception to be thrown.
In europi.py lines 11-13, the oled I2C device is registered and immediately used. In the MicroPython documentation, the I2C class offers a "scan" method which allows you to scan for peripheral deices on the given bus. If no devices are found, you can gracefully report that error to the user instead of allowing the unintuitive "OSError: EIO" error to be thrown.
An example implementation would look like this:
oled_bus = I2C(0, sda=Pin(0), scl=Pin(1), freq=400000)
if len(oled_bus.scan()) == 0:
raise SystemError("No I2C devices found. Ensure OLED device properly installed.")
oled = SSD1306_I2C(WIDTH, HEIGHT, oled_bus)
oled.fill(0)
oled.show()
Images
If applicable, add screenshots or photos to help explain your problem.
Describe the bug
This could potentially be a problem with the hardware or perhaps the firmware button debounce method.
On some occasions a b(n).handler_falling interrupt can be triggered without a physical press of a button. However, it is quite difficult to reproduce and seems to happen mostly randomly. The bug was identified after logging was added to the CVecorder script.
To Reproduce
In some cases this can be produced by starting the module with nothing connected, then connecting CV to ain followed by a clock into din. However, this problem does not happen all the time and appears to happen rather randomly.
Expected behavior
A b(n).handler_falling interrupt should not be triggered unless a button is pressed and then released.
Additional context
This bug has been identified so far only when using the CVecorder script - as a button press can trigger the deletion of recorded CV, so it is therefore obvious when it happens. However, I believe this issue could manifest in all scripts.
Instead of having separate functions to add custom behavior to the OLED instance variable, you can subclass the SSD1306_I2C library and add your custom functions there.
Describe your suggestion or idea
For example:
class EuroPiDisplay(SSD1306_I2C):
def __init__(self, sda, sc, channel=0, freq=FREQ):
i2c = I2C(channel, sda=Pin(sda), scl=Pin(scl), freq=freq)
if len(i2c.scan()) == 0:
raise SystemError("No I2C devices found. Ensure OLED device properly installed.")
# super() calls the init method on the SSD1306_I2C base class
super().__init__(WIDTH, HEIGHT, i2c)
def centre_text(self, text):
"""Takes a string of up to 3 lines separated by '\n', and displays them centred vertically and horizontally."""
...
self.text(...)
def clear(self):
self.fill(0)
# Sample usage:
oled = EuroPiDisplay(SDA_PIN, SCL_PIN)
oled.clear()
oled.show()
# default SSD1306_I2C method
oled.text("default text")
# custom method
oled.centre_text("centred text")
This will give users a consistent interface for working with the OLED display. Custom functions should still be documented as such in the OLED Tips page, but this will give users a consistent interface when coding up their scripts. This also helps avoid the function centre_text
from relying on the globally defined oled
variable.
Images
If applicable, add screenshots or photos to help explain your problem.
I noticed the banner on the Allen Synthesis order confirmation page is 10MB which causes significant delays (50 seconds when not cached) in loading the page. The image should be resized to the view dimensions on the website.
Describe the bug
A clear and concise description of what the bug is.
To Reproduce
page:
https://allensynthesis.square.site/store/status/<CONFIRMATION_CODE>/confirmation
Link to unoptimized banner file: https://allensynthesis.square.site/uploads/b/927185487825331d77a55900406e125776a9e27e353ead17e3df7a79b6afc647/banner_1639401480.png
Expected behavior
The file size shouldn't exceed 1mb and can probably be optimized down to a few kilobytes.
There are a couple broken links in the docs, which are both pointing to https://allensynthesis.co.uk/europi/europi-programming.html this page doesn't seem to exist. I assume that it has been replaced with https://github.com/Allen-Synthesis/EuroPi/blob/main/software/programming_instructions.md
In addition most of the links in the documentation are hardcoded to point to the main branch of the main EuroPi repo. This means that these links become incorrect when viewing the docs on branches and forks.
When the EuroPi isn't connected to a serial output and an exception is raised, it's not obvious what happened.
Describe your suggestion or idea
It would be helpful to have a EuroPi specific error class that displays the error message to OLED when an exception is thrown.
This can be useful to explicitly throw:
# europi.py
def choice(self, values, samples=None):
if not isinstance(values, list):
raise EuroPiError(f"choice expects a list, got: {values}")
The expectation would be that the error message fits as much of the exception text onto the oled display as possible. This makes it clear an error has occurred and helps hint at what the error was.
A more tricky case is when there is an unexpected error thrown. If it's not caught, then the oled wont show the error message.
One way to address this is to have a main entry point into the script and wrap that in a try/except/finally block. For example:
# coin_toss.py
try:
coin_toss = CoinToss()
coin_toss.main()
except BaseException as err:
raise EuroPiError(e)
Images
If applicable, add screenshots or photos to help explain your problem.
Additional observation from @t-schreibs
Ran into an odd issue the other day. Probably not a support thing, but idk where else to drop this, and I'd be interested to hear what y'all think. I was feeding CV values from one EuroPi into another, where the second one was running Poly Square, and the output I was getting was bizarre, like almost FM-y. It was like the input analog value was super noisy. However, feeding those CV values into another oscillator (was testing w Beads w no audio input) sounded crystal clear, and any other CV value into Poly Square was also crystal clear. I'm trying to figure out where to start debugging this. Any chance it's a weird hardware thing?
Further observations from @t-schreibs
I have one EuroPi which, I believe, is still running 0.3.0 of the firmware, and the rest are up to 0.5.0. I'm testing w RadioScanner into Poly Square just to get an easily controllable steady CV value. While the output from 0.3.0 is not perfect by any means (I'm getting audible pops & crackles), the pitch center is much more stable. The output from 0.5.0 wanders quite a bit
Yep, I've confirmed there's something up with the latest version - I brought the other EuroPi up to 0.5.0 and now it's demonstrating the same problems as the other ones
Root cause suspected to be the change in PWM freq:
f0ae072
Oscilloscope testing between versions:
https://imgur.com/a/C5h0YYn
Update the Output
class to hide the concept of “duty” and only work with voltage, since that's what users care about. Any setting/reading of duty should be abstracted away and public API methods should only get/return 0 or 1 for digital output and a floating range of 0.0 to 10.0 for analogue output.
Describe the bug
Some clocks appear to be too short to trigger the din handler.
To Reproduce
Send a short clock to din. Some clocks trigger the din handler, others do not.
Expected behavior
It is expected that a clock input to din will always trigger the din.handler.
Images
n/a
Additional context
Tested so far:
The goal is to reorganize the EuroPi software folder in such a way that it is 1) Easy to transfer to the Pico via Thonny 2) Easy for users to navigate, read and contribute 3) Logically organized in a way that can map 1:1 with MicroPython's expected file structure 4) (stretch goal) Leading to better facilitate a Program Selection bootloader #37.
.
├── LICENSE
├── README.md
├── docs
├── hardware
└── software # EuroPi library files and scripts.
├── firmware
│ ├── europi.py
│ ├── calibrate.py
│ ├── helpers.py
│ ├── tests.py # EuroPi library files should be tested.
│ └── contrib # User contributed scripts, unofficially supported.
│ ├── coin_toss.py
│ ├── diagnostics.py
│ ├── harmonic_lfos.py
│ └── radio_scanner.py
└── tests
├── test_calibrate.py
├── test_europi.py
└── test_helpers.py
Describe your suggestion or idea
This would allow us to use Thonny to copy the firmware
directory to the pico with right click "Upload to /lib". Additionally, rshell
users could do something similar like the following:
$ rshell cp -r ./software/firmware /pyboard/lib
Then users can copy scripts from scripts
or contrib
to main.py
on the root directory of the pico to choose which script they want to use.
Note: these changes are independent of the Program Selection bootloader work, but would be a prerequisite.
There are a few small improvements we can add to the DigitalReader
base class for buttons and digital input:
Provide an accessor method for last_pressed
.
Basically, whenever the handler is called, we capture the timestamp of the event and use that to calculate the debounce. This is also useful for user scripts to do things like executing some behavior for a duration after a button press (e.g. show menu header) or for measuring the time between events for calculating BPM from an external clock.
Provide a handler for IRQ Falling
We currently have the option to assign a callback function on the "rising" edge of a digital event. User scripts may also benefit from being able to attach a callback to the "falling" edge event too. Some examples might include enhanced gate behavior like clock divisions or detecting a long press via button release time.
The default samples
value should be set by the class instance and allow script override any time with a set_samples(samples: int)
method. All AnalogueReader methods should take an optional samples value or use current instance default.
This will let us change the default samples value once in the script if there is a need to override it and remove the need to pass samples in each analogue read call.
For example:
# Configure EuroPi options to improve performance.
# Used with a large range of values and needs high accuracy
k1.set_samples(256)
# Used to choose from a range of a few values and doesn't need high accuracy
k2.set_samples(1)
Describe the bug
The function Display.clear()
calls show()
internally, as such it is of limited usefulness and requires detailed documentation in order to describe when best to use it. It is not currently in use by and contrib scripts, only in europi.py
.
We should remove the function entirely and document the fill(0)
method of clearing the screen.
Describe the bug
Initializing the EuroPi module and installing the firmware for the first time throws an error because calibration.txt
does not exist.
To Reproduce
Steps to reproduce the behavior:
Expected behavior
When running europi.py firmware for the first time, the firmware should not crash when calibration.txt
is not present.
Additional context
>>> %Run -c $EDITOR_CONTENT
Traceback (most recent call last):
File "<stdin>", line 188, in <module>
File "<stdin>", line 126, in __init__
File "<stdin>", line 29, in get_input_calibration_data
OSError: [Errno 2] ENOENT
Describe the issue or suggestion
The two extra diodes for power supply protection are not present on the schematic.
The pin numbers for the op-amps also don't match the actual pinouts of the ICs
Update the europi.py firmware file to document public api methods with proper Python docstrings.
This will then enable us to be able to automated documentation generation tools like Sphinx and readthedocs.
To further simplify the setup process for EuroPi users, create a upip distribution package for the EuroPi firmware.
This would allow Thonny users to "install" the EuroPi firmware using the same Tools > Manage Packages process to install a "EuroPi firmware" library.
One missing requirement is defining a version scheme for differentiating between releases.
Official documentation:
https://docs.micropython.org/en/latest/reference/packages.html#distribution-packages-package-management-and-deploying-applications
https://packaging.python.org/en/latest/guides/distributing-packages-using-setuptools/
Practical example:
https://github.com/stlehmann/micropython-ssd1306
Describe your suggestion or idea
Currently the module needs to be plugged in via the USB on the Pico, but this means sometimes cramming the cable in between your rails, and you don't have any easy access to either bootsel or reset without removing the module.
It would be nice to have front access USB and potentially buttons for reset and bootsel.
Would it be possible to use this kit to run a C++ program as a module? I'm looking for a way to get started porting some audio plugins I've written to modules. They have JUCE as a dependency in C++ -- is it possible to connect the audio inputs from this board into a C++ app on the PI, and then back to the output?
Appreciate your time sharing your thoughts on it!
Describe the bug
The EuroPi library checks for the existence of the micropython
module to determine if it is in a micropython environment (running on hardware) or a cpython environment (running the unit tests). This was possible because no script's tests used the micropython library. Recently a change was made to import all scripts included in the menu as a part of menu unit test. This ensures that the menu can load, as it imports all of the scripts. The end result of this is that scripts that are included in the menu cause this test to fail if they import the micropython module.
To Reproduce
Steps to reproduce the behavior:
import micropython
2. run the test suite
Expected behavior
Scripts can import micropython and their tests still pass.
Additional context
This issue was discovered by the CVecorder script. It currently has its use of micropython commented out. As a part of resolving this issue those 3 lines should be uncommented.
As suggested by @awonak. We don't own or maintain flashnuke.uf2, so it shouldn't be in our repo.
Remove flashnuke.uf2 from the repo and instead provide a URL to the official source of the file, and update any references in our docs
Hey EuroPi friends!
I've noticed there's no code style rules set. Would you be interested in having that?
I'm thinking Black as the formatter (with a line length of say 120, since the default 88 is a bit restrictive?) and pylint and flake8 for linting.
In addition to formatting/linting the current codebase we could add the same tooling to github actions so everything stays consistent going forward.
Let me know what you think, if this seems like a good idea I can draft a PR
As you can see below, this PR would introduce a lot of stylistic changes initially but after that the mental effort put towards code style will drop significantly, which might be useful as the number of contributors goes up.
We've seen the initial implementation of save / load state behavior with the Menu Bootloader, which hints at the ability for scripts to save state when it changes and to load state from disk when the EuroPi loads the script. This behavior should be abstracted and well documented for users to easily implement in their scripts.
Considerations
Interface for SaveState
save_state
- take state in persistence format as a string or bytes (indicated with flag or type checking) and write to disk. Save should be responsible for checking / handling the situation where there is not enough space left on disk to save state.load_state
- when called, check disk for saved state, if it exists, return the raw state value.get_state
- (optional) gather the current values of instance variables that represent state of the script and convert into the persistence format.set_state
- (optional) given state in the persistence format, parse and update the instance variables.Persistence Formats
String
JSON
Struct
Describe the bug
When attempting to display more than 3 lines of text on the OLED, the text is cut off at the 3 line mark which is unhelpful if you don't know that is the limit of the display size.
To Reproduce
Expected behavior
A helpful error message to explain that the display can't display more than 3 lines
As we add user contributed scripts we will (hopefully) end up with a large enough collection that the module will be useful 'out of the box' and not require that the user write their own scripts. In this case it would be nice to be able to switch amongst these scripts without connecting a computer and re-writing main.py
.
I think that a proper menu system will require that we design two major pieces:
I believe that some work had already be done towards this, at least with the prototype module (https://github.com/awonak/EuroPiAlt). I suggest that we use this issue to focus these efforts and gather input on what this menu should be and how it should behave.
The module regulates its own supply using a linear regulator rather than using the 5V rail which would be cleaner
We need to update the calibration process for components that read analog voltage.
Describe your suggestion or idea
The new calibration process should support both low precision and high precision (gradient between each 10v steps).
Documentation needs to be updated to explain how to run and use the calibration.
Additionally we should revisit the way AnalogueInput handles the calibration values because read_voltage
is duplicating work from percent
.
#class AnalogueInput(AnalogueReader):
def percent(self, samples=None):
"""Current voltage as a relative percentage of the component's range."""
# Determine the percent value from the max calibration value.
reading = self._sample_adc(samples)
max_value = max(reading, CALIBRATION_VALUES[-1])
return reading / max_value
def read_voltage(self, samples=None):
reading = self._sample_adc(samples)
max_value = max(reading, CALIBRATION_VALUES[-1])
percent = reading / max_value
# low precision vs. high precision
if len(self._gradients) == 2:
cv = 10 * (reading / CALIBRATION_VALUES[-1])
else:
index = int(percent * (len(CALIBRATION_VALUES) - 1))
cv = index + (self._gradients[index] *
(reading - CALIBRATION_VALUES[index]))
return clamp(cv, self.MIN_VOLTAGE, self.MAX_VOLTAGE)
Describe the issue or suggestion
https://github.com/Allen-Synthesis/EuroPi/blob/main/hardware/build_guide.md
"Solder the 100nF capacitors to the back (C8, C9)."
They are numbered C7 and C8 on the board, not C8 and C9
If a script is left running for a long period of time, it can burn that screen into the oled and leave ghost images. We should update the docs with suggestions 1) do not leave the EuroPi screen on for long periods of time with with something on the display, and 2) Update programming instructions to either let the display "sleep" after a period of time or provide some interaction to clear the display at rest.
Describe the bug
The cutoff of the RC filter used to filter the PWM is very low, so you any changes in voltage have a slight slew which isn't audible, but results in a reduced volume and smoothed output when running at high frequencies
Describe the bug
radio_scanner is out of date. It appears that it didn't get updated with the removal of the duty methods.
To Reproduce
Steps to reproduce the behavior:
Expected behavior
It runs
Additional context
Also, radio_scanner (and maybe digital_noise) should be moved to the contrib dir.
Describe the issue or suggestion
In the BOM the transistor is described as PNP, but the link goes to an NPN transistor.
Additional context
I don't have enough electronics savvy to know if this is ok, or not. But even if it is, in the interest of clear documentation, it should be consistent.
Describe the issue or suggestion
The schematic shows 4.7uF capacitors on the output stages but that value of capacitor does not appear in the build guide or BOM. I didn't check the values of other components.
Ask your question
On the build I did the LEDs are very bright (used what I had on hand rather than ordering to spec). I was thinking I should be able to tone them down by replacing the 4.7k resisters leading to each LED with something closer to 33k.
Is this a reasonable approach or would you recommend something else?
MicroPython supports static type checking (at least up to Python version 3.6). The EuroPi firmware code should add type hinting to help improve static analysis and documentation for IDEs that support it.
Describe your suggestion or idea
By adding type hinting, this will make writing scripts easier by providing the expected type of parameters of the API.
There are no negative side effects for IDEs like Thonny that don't use it.
Describe the bug
When in external clock mode, coin_toss is triggered on the falling slope, rather than the rise
To Reproduce
Steps to reproduce the behavior:
Expected behavior
It's expected that the tosses would be triggered when the rising slope is first detected
Solution
The problem appears to be here:
def wait(self):
"""Pause script execution waiting for next quarter note in the clock cycle."""
if self.internal_clock:
while True:
if ticks_diff(self._deadline, ticks_ms()) <= 0:
self._deadline = self.get_next_deadline()
return
else: # External clock
# Loop until digital in goes high (clock pulse received).
while not self.internal_clock:
if din.value() != self._prev_clock:
# We've detected a new clock value.
self._prev_clock = 1 if self._prev_clock == 0 else 0
# If the previous value is 0 then we are seeing a high
# value for the first time, break wait and return.
if self._prev_clock == 0:
return
The second to last line should be if self._prev_clock == 1:
since the self._prev_clock
value was just changed.
We should explore the extent to which we can write automated tests for the main Europi libraries. Including:
The 2x4 pin male and female headers for joining the pcbs are not listed correctly in BOM. The description is wrong, they are listed as 2x8 pin, the male link is to a 2x8, and there is no female header link.
Describe the bug
When running the europi.py script for the first time, the calibration.txt file is not yet generated so it causes an error when trying to read
To Reproduce
Expected behavior
No error and a new file is generated
Additional context
n/a
Describe your suggestion or idea
The current PCBs only support one pin layout for the I2C bus of the OLED display, meaning that alternative displays using the same driver which may be more accessible to other customers cannot be used.
The PCB could contain some hardware to allow the pins to be switched for alternative layouts.
CPC OLED (theoretically compatible but alternate pin configuration)
PR #139 added debug logging and log file rolling support to the cvrecorder script. We should consider adding logging support to the EuroPi library so that scrips do not need to implement this.
The primary needs of this feature would be:
Document the existing test framework including:
Describe the bug
The scaling that happens to the CV input before the Pico ADC will translate +12V into only around 2.64V, meaning only 80% of the ADC is being used, and that's assuming a 0-12V input which is quite unlikely.
In the event of using a more common range of 0-8V, the Pico would only see 1.76V at the ADC, meaning only 53% of the ADC is being used, and thus decreasing the resolution from a theoretical maximum of 1.9mV/step to 3.6mV/step.
Does anyone have Kicad versions of the board/schematic files?
It might be a plus to add those to the project at some point, as Kicad is free and available for all major OSes, where DesignSpark seems to be windows only unless I'm missing something?
Another upside would be automated interactive BOM creation due to this wonderful plugin: https://github.com/openscopeproject/InteractiveHtmlBom - this could be a help for future builders!
See https://openscopeproject.org/InteractiveHtmlBomDemo/html/pic_programmer.html for an example of an interactive BOM extracted using the plugin.
From Nik (@gamecat69) via discord:
I think we need a better way of merging code from other developers into the upstream branch BTW. Observations so far:
Positives:
- Pro-tips provided by admins really help people write better code
Cons:
- There are no rules published anywhere, other than rolling rules which are revealed by admins when a PR is created
- Developers must maintain their own branch exactly the same as upstream, rather then being able to specify particular files to push upstream. There may be some better git-foo to be learned here, but there is no guidance published anywere in the main repo to help people
- Sometimes coding preferences as comments by admins can be misunderstood as "things that need to be done to get this PR through"
contributing.md
should be updated to address the above concerns. Additionally, it should include a section on how PR reviewers should participate in PRs as well.
I think it would be neat to add a hook into our workflow to have a Discord bot automatically announce when a new version of the EuroPi firmware or contrib gets published to PiPy.
Our workflows are defined here:
https://github.com/Allen-Synthesis/EuroPi/tree/main/.github/workflows
I'm sure something like this must exist already, so hopefully this should be a low-lift contribution!
Create AnalogueReader
and DigitalReader
base classes so inputs can share methods. This gives a consistent interface with components and shares code.
AnalogReaders
should have a consistent interface for reading cv values, for example:
AnalogInput
should still have the method read_voltage and Knob
should still have read_position to remain backwards compatible and useful.
DigitalReaders
can provide value by allowing child classes to define component specific init values, such as pin initialization and debounce delay time.
As written by CestBallot in the Discord:
If you use the menu.py and if you start a calibration without any tools, it can be a problem.
The calibration tools don't have any return/ cancel button ( like the button 2 who can do it) and if I press button 1 it calibrate with No input so bad calibration... and after a reboot my module don't up the oled screen.
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.