Close
0%
0%

Modular MIDI

Using a CAN bus to distribute MIDI messages in a modular synthesizer

Similar projects worth following
Modular MIDI (M2IDI) is a prototype for a bus connection to distribute MIDI messages in modular systems like eurorack.

This is a prototype MIDI transport with support for MIDI 2.0. These specifications may not be final.

M2IDI bus overview

This project contains:

USB module - Basic features complete (missing MIDI 2.0 features). Will act as a USB host or device. In device mode it transfers MIDI messages between the CAN bus and USB, and will also provide stereo audio in and out over USB. In host mode it will only connect to MIDI devices.

DIN module - Is feature complete. Transfers MIDI messages between the CAN bus and the old DIN transport.

Tangle module - Is feature complete (missing some MIDI 2.0 features). Is an analog multiplexer with 8 inputs and 8 outputs which can save mappings to MIDI program change messages.

CV16 module - Basic features complete (missing some MIDI 2.0 features).  Has 16 general purpose analog outputs.

Hardware specs:

  • 4-wire flat ribbon-cable
  • MicroMatch or WR-MM connector
  • Outermost pins of the connector are connected to an RC filter to reduce EMI/EMR
  • Selectable end-termination on every module
  • CAN FD controller and transceiver

End termination can be enabled on a module with a DPDT switch, or with jumpers. And the terminated position should be clearly marked on the PCB. Split termination is preferred to minimize EMR.

M2IDI Pinout

Communication specs:

  • Nominal bus speed is 1Mbps: TSEG1 = 700ns, TSEG2 = 200ns, Sync = 100ns.
  • Data bus speed can be 4Mbps: TSEG1 = 100ns, TSEG2 = 100ns, Sync = 50ns.
  • Payloads over 8 bytes should use the datarate switch, this is optional for smaller payloads.
  • 11-bit standard ID where the 4 MSb are the MIDI 2.0 group, the remaining bits should be a unique identifier for each device on the bus.
  • Extended IDs for groupless messages, using the same unique identifier as normal messages.
  • MIDI messages are sent over the bus using the UMP format
  • A CAN frame can contain multiple MIDI messages from the same MIDI group (up to 64 byte)

The ID field of the CAN frame contains an identifier of the transmitting device for arbitration purposes. These identifiers should be randomly generated. Also, the device should generate a new identifier if it receives a frame with its own identifier.

CV16_LED_divider-Part.stl

A divider for the CV16 led matrix to prevent bleed between the pixels

Standard Tesselated Geometry - 217.07 kB - 10/22/2021 at 12:52

Download

  • Rounding up features

    David10/22/2024 at 18:50 0 comments

    A local maker faire happened recently (ish), which applied some time-pressure and made me reevaluate the project priorities. I easily spend a lot of time in the weeds of some random feature or refactoring code, having a temporary deadline was a nice change of perspective. Going forward I will focus on getting the MIDI 2.0 features working, so a lot of features will get put into "future work".

    My synth next to a video-synth on the maker faire stand

    The EEPROM storage is now implemented on the CV module. The formatting is a bit naive, so we could probably save space by saving the configurations of outputs separate from the complete config. However, that would require a better user interface or an improved menu structure. Maybe I'll think of something in the future. A random/white noise output mode was also added, as well as pulse width for the square output. This is probably good enough for a prototype.

    I have a couple of MIDI controllers which don't work with the USB module host mode, so I have been looking for bugs in the tinyusb library. Most of the bugs I've found already have open pull-requests, but I found one new bug:
    Some MIDI controllers fail enumeration because their descriptors report a wrong total length, so tinyusb tries reading past the buffered values and panics. I have made a fix for this, so I will probably make a pull-request. Another bug is that the device stack runs out of RAM when using USB Audio because some functionality in the library was not implemented for the RP2040. There is an active merge request for this so it should be fixed soon.

    The USB ID pin is supposed to detect the connection type so the module can know which mode it should be in, but it was giving me a lot of trouble. It somehow stopped working when the RP2040 entered device mode, which is strange because this pin is controlled by an external chip which should not be affected by the software, only the CC pins of the USB port should affect this.
    In the end I found a workaround by disconnecting and reconnecting the USB line pullups in 1 second intervals while the device is not mounted. So the mode switching is finally working.

    While implementing the I2S I found that getting nice audio streaming is more difficult with windows, while on linux it just worked. It seems like windows does not handle half the frames being empty, and the endpoint is probably not big enough for two millisecond of audio. In that case we would get gaps in the audio stream. This was fixed on the device by using a smaller sample buffer. Thus, audio streaming is working with the RP2040 as well.

    Currently the CAN frame can contain multiple messages of the same type to reduce the overhead a bit. However, turns out this is pretty annoying to implement, at least if we ensure the order of the messages stays the same. Perhaps it would be better if it was based on the MIDI group of the messages, then it would matter less if the messages were rearranged since they would go to different endpoints and the benefits should be more consistent.

    With that, it is finally time for the MIDI 2.0 features. I will consider this project done when the MIDI 2.0 function blocks are implemented, and I will try to add property exchange so parameters can be edited over MIDI. The first step is to implement the MIDI 2.0 handshake for device mode of the USB module.

  • CV module DAC

    David06/13/2024 at 16:47 0 comments

    The CV16 is probably the most complicated module in this project since it is supposed to be fairly flexible. The plan is to use core 1 for time-sensitive tasks like driving the led-matrix and outputs. While core 0 takes care of the MIDI processing, menu system, and anything else. The focus so far has been to restore old functionality and get the DAC working.

    My soldering was pretty shoddy this time, I had some short-circuits which led to the opamps and mux reaching 70C. They still reach the low 40s after fixing the shorts. It is better, but still a bit spicy.

    I made a couple of blunders when I re-designed this module. One SPI bus is busy just servicing the DAC, but the EEPROM is also connected to this bus. So those connections need to be changed. The DAC load is purely capacitive which leads to the initial current being high (likely higher than the max rating of the MUX) and causes ringing. I'm guessing this also contributes to the high temperature in these components.

    All the old code has been ported to the RP2040 now, I also started refactoring and optimizing some of it. The refresh rate on the LED matrix has been increased to 60Hz, and the module output rate has been increased as well. Otherwise, the only feature I have added is key-tracking oscillators, which means this module can generate sound directly.

    Timing issues with DAC multiplexing

    At first, the output voltages were only correct when the neighboring voltages were the same, and spikes could be seen when the MUX switched. Turns out the DAC settling time was longer than I expected. The DAC settling time from the LDAC edge is 7µs when the voltage change is 5V, which is longer than the multiplexing hold period at 44kHz (5.6µs). The settling time would be 5µs if the voltage range was halved, but doing that would increase the circuit complexity. Currently, the output rate is reduced to 22kHz to get around this, but the neighboring outputs were still affecting each other.

    SPI delay before and after optimization

    It finally turned out the timing issues were caused by a massive delay when writing values to the SPI. To reduce this delay I switched to PIO so it can handle the CS pin instead of software. I removed some division and modulo operations because they eat many CPU cycles. Then, set core1 as high priority on the bus fabric and used a setting to have these functions loaded in the RAM instead of flash to further reduce the delay. This resulted in a healthy margin between the end of writing and LDAC.

    440Hz triangle output

    There is still some noise when switching, but since the sample rate is above 20kHz it is less of an issue. The noise is not noticeable when the outputs are used to modulate pitch, and they can even be used directly as a DCO. It has some nice aliasing on the upper range of frequencies which can be heard at the end of this video:

    Making changes leads to lots of troubleshooting, but I believe most hardware changes on all modules have been troubleshot now. I will order a new revision of this main board, mostly to move the EEPROM to the other SPI bus. The EEPROM is working on the tangle module so I'm fairly confident it won't cause more issues.

    There is still more to do on this module, I'm going to finish refactoring the output handler and menu system. More waveshapes will be added like multiple kinds of noise, pulse waves, and maybe even supersaw. I also need to figure out how to organize the configurations in the EEPROM.

  • Finishing the Tangle module

    David03/25/2024 at 18:06 0 comments

    The work on the tangle module started with a bang. Something short-circuited on the -12V rail, which blew out my mini-PSU. It turns out the reverse polarity protection had failed, the gate terminal of the MOSFET shorted with the source. I replaced it with a beefier MOSFET and it is working now, so I assume it was underdimensioned. I also forgot to connect the CAN controller interrupt pin, but is any prototype complete without any bodging?

    The bodged connection

    The light pipes are working well, there is a small gap which I think is the source of some light bleed but I can live with it. The circuit is a bit overcomplicated though, because the peak detectors are not making a difference. With audio-rate signals, the color indicates the duty cycle of the signal with yellow being roughly 50%.

    Light pipes

    As for the code I have now implemented the EEPROM driver. For a while, I wondered how to organize the data. The configuration takes up three bytes, but the MIDI bank, channel, and program number would take another four. The main problem would be finding the correct configuration because there's not enough RAM to store an index of every stored config. In the end, I stored just the three-byte configs in a main section then the bank and channel in a header section. This gives enough space to store twenty full banks by using the memory location to determine which program number is where. Having 2500 configurations across 20 banks is probably plenty, maybe even overkill.

    I also switched to the midi library from midi2.dev. It caused some weird compilation error when the library was included where the ROM overflowed by more than twice the total size. Switching to the nano GCC instructions seems to fix this issue.

    The only thing missing now is some MIDI 2.0 features like the function block and maybe some implementation of property exchange. Next up I'm going to finish soldering the CV16 module and get started on some code.

  • USB host mode

    David02/01/2024 at 21:34 0 comments

    I have slowly been making progress on the USB module over the past months, but it has not been easy. This is my first time using the RP2040 and CMAKE so that is the source of most of these hurdles. 

    Even getting blinky working with the RP2040 took a long time. The USB bootloader worked as expected, I could upload code and verify the binary file. But when it exited the bootloader the device would be completely dead. I thought it was a problem with the crystal because it was not oscillating, so I spent some time resoldering components. But it turns out the RP2040 uses two bootloaders and the default second-stage bootloader is not compatible with the flash chip I am using. Blinky finally worked when I added the right flash settings to the board header file.

    One hardware issue I discovered on the new PCBs is that the new USB CC chip is host only... It handles CC to ID interpreting and has an integrated power switch for the connected device. BUT it does not have the pulldown resistors to advertise being a device, so it will not work for a dual-role port. I have switched it back to a TUSB321 and MIC2005 in the schematics. I will not order a PCB with this fix because I can get around the bug using a USB-A to USB-C cable, so I'll get the testing done anyway.

    The first feature I tried implementing was the USB host mode because it was the biggest feature missing in the last version. I used an existing MIDI host driver to handle the USB side of driving the devices. There was a pull request with this feature on the TinyUSB library, but it never got pulled. So this driver adds the functionality from that pull request. Host mode mostly works on this module now: It works with my MPK mini and modwave, even via a USB hub. However, it does not work with the beatstep pro and a USB-C hub with extra features like an SDCARD reader and HDMI port. There is some debugging to do there I guess.

    Then I started implementing code from the previous version. There were a couple SAMD specific commands in my libraries, and linking the libraries with CMAKE gave me so much trouble. But adapting the libraries for RP2040 was otherwise pretty simple. It is now at the point where it sends messages to other modules over the CAN bus. Device mode and I2S are still not working, and I haven't implemented the external EEPROM or RAM chips in the code yet.

    This week I found out that some great resources for MIDI 2.0 have become open-source. I will probably switch to their MIDI library because it has implemented capability inquiry and other features. There are finally tools I can test MIDI 2.0 functionality with, there is even a device driver for TinyUSB. But I want to get the two other modules working before I continue with the USB module because the non-linearity of the previous CV16 module makes it impossible to do a good demo and the tangle module is almost done.

  • Completing the DIN module

    David11/18/2023 at 18:05 0 comments

    Assembling and programming the DIN module was mostly pain-free, but I could not use the bootloader. I tried flashing the bootloader but was unable to upload the program because it got stuck when initializing the flash with the applet. I even tried recompiling the applet to make sure it had the correct memory configuration. Perhaps the SAMD21G15 doesn't have enough RAM for the bootloader. It's not a huge issue though, the MCU variants with more memory are pin-compatible and I have it working on a SAMD21G17.

    Flashing activity LEDs

    Several issues have been fixed in this module, as well as some general improvements: The output is confirmed to be working as expected now. The DIP switch to select a MIDI group has been implemented. The CAN controller now filters out message types. Randomization of the CAN ID and MUID was added. The module now translates more message types between DIN and M2IDI: realtime, MIDI 2.0 voice messages, and sysex messages.

    I have also been updating the MIDI driver during this time, to adhere to the latest UMP specification. So flex and stream messages are handled, and util messages are groupless. It should also insert the bank in program change messages when translating between MIDI 1.0 and MIDI 2.0. One thing that gave me a headache while diving into the spec is that the byte order is inconsistent. Some messages, like realtime, have the least significant byte first. Other messages, like program change, have the most significant byte first. But most of them are ambiguous, left to be decided by each transport. I ended up using most significant byte first because it seems to be the most consistent overall.

    This module is basically feature-complete now, the only thing missing may be implementing a function block for MIDI discovery. Next up is probably the USB module, since I already finished soldering it.

  • New PCBs

    David11/01/2023 at 21:41 0 comments

    I have slowly been assembling the new PCBs over the past months. I'm just about halfway through the soldering now, and have started programming some of the new features.

    The new PCBs

    New PCBs, some were partially pre-assembled

    There have been a couple of announcements around synths and MIDI lately. Tiptop Audio revealed their "ART" system which is similar to patch-able MIDI. That is a very interesting way to coordinate polyphony using multiple monophonic modules. With regular MIDI I think the best way would be to have one of the modules resend the data on different MIDI channels for each voice. Maybe I should make a converter module to connect ART and M2IDI someday.

    There was also an update to the MIDI 2.0 spec. It added something called functional blocks, which give a new way to discover capabilities and negotiate MIDI 2.0. It also added two new message types: Flex data, which lets us send a sysex message to all devices in a group, and stream messages which are used for discovery of functional blocks. The utility messages have been changed to be group-less, so all devices should receive them regardless of their group. There really is a lot of programming ahead.

  • Revised USB module

    David07/14/2023 at 21:19 0 comments

    Finally, the last module to be revised is the USB module. As with previous modules I have added bootloader and reset buttons, an SMD USB connector, EEPROM, and power rail protection.

    This module also changes the MCU to RP2040. It should be easier to implement USB host mode with this change since others already have added support for it in the library.

    There should be plenty of processing power left over for some bonus features, I don't think I will implement anything beyond the basic functionality, but maybe one day.

    I added a RAM chip and a button on the front to support delay/reverb effects and changing modes, just in case I get around to implementing this. However, since this module is only 4HP wide there's not much room for controlling the effects, so it will require some creativity or MIDI CC. This module should eventually get MIDI-CI so it can be mapped and configured with that. I spent some time wondering if it would be worth it to add on such effects as an afterthought. But if it ends up unused we can just not populate those components on the PCBs.

    The 5V rail protection

    The 5V rail protection needed to be different on this module because it can supply power to the connected USB devices. This circuit blocks power from the USB connection when the 12V rail is present, which is always there when the module is powered from the rack. When the USB power is not blocked Q5 will prevent power from being spread to other modules in the rack (which currently happens when the rack is powered-down).

    The new ADC input stage

    I changed the ADC input stage to reduce its distortion since the zener diodes added some soft-clipping within the normal range of voltages. It now uses hard clipping closer to the ADC to deal with overvoltage. I also removed the ring connection from one of the inputs, so only the left input accepts stereo audio if the other input is unused.

    The previous version of this module did not comply with the jitter limitations for USB hosts because it was running off of the internal oscillator. Therefore I made sure to use a crystal oscillator in this one.

    I could probably tweak these PCBs forever, so I'll just send them all to be manufactured now.

  • Revised CV module

    David05/19/2023 at 15:02 0 comments

    Most changes on the control voltage module were the same as in previous modules. It gets an SMD USB connector, EEPROM, power rail protection, a bootloader button, and a latching bus connector. But the remaining changes are more drastic.

    The biggest change in this module is switching the MCU for an RP2040. That's not an easy choice at this point in the project, as I already have written a lot of code. But the SAMD21 already stutters, the code could definitively be optimized, but it would probably limit the features that could be added. I have been trying to keep the low-level drivers separate from the higher-level programs like the menu system and the code generating the output values, so I suppose it will test the quality of my code.

    New core PCB
    The new core PCB with the RP2040

    The pico is more popular, so it may make a more appealing open-source project. Plus it has better support in libraries like TinyUSB. People have been implementing USB MIDI host support for the RP2040 in that library, so that's less work I would need to do on the USB module.

    I also added a quad 16-bit DAC to replace the filtered PWM used in the current version of this module. This was primarily to reduce the noise on the outputs while providing better resolution. A side-effect of the change is that the outputs can be a lot faster, so the DAC could potentially provide audio-rate signals to all outputs. Seeing how far it can be pushed will be interesting.

  • Revised Multiplexer module

    David04/19/2023 at 19:50 0 comments

    Next up is the multiplexer module's revision, there are no drastic changes on this one either. Like the DIN module, this gets an SMD USB connector, 5V rail protection, a latching bus connector, and bootloader buttons.

    I also added reverse voltage protection on the +-12V rails. It shouldn't be necessary since I'm using shrouded power connectors, but it's an open-source project so people may change the components when re-creating it.

    Something I discovered with this module was that unconnected inputs caused crossover noise, so signals would appear on other inputs. I fixed this by soldering the connector's tip switch to ground so the wires are grounded when no jack is present. This has now been added to the PCB.

    I added an external EEPROM which means the routings and settings finally can be saved between power cycles. Not needing to re-configure the module every time will make it much more convenient to use.

    This module was using every available pin on the MCU (even the SWDIO pin was used for a LED), and I needed some pins for the buttons and EEPROM. So I needed to add shift-registers to control the MUX selection. This reduced the number of pins needed to control the MUXes from 24 to 3.

    Output LED circuit
    The LED control circuit for the bi-color LEDs

    The bi-color LEDs were changed from a 2-pin to a 4-pin version so both colors can shine simultaneously. This should have a better effect for audio-rate signals. The LEDs should now be green for positive signals, red for negative, and yellow for audio rate (becoming brighter with higher frequencies). This is implemented with a peak-detector circuit for each LED. The opamp can't drive high capacitive loads though, as anything over 100pF may reduce the slew rate or cause instability. Therefore the capacitor is discharged via a transistor to amplify the current. This means the discharge time depends on the gain of the transistor. Using transistors also lowers the voltage needed to drive the LEDs, so the dead zone where neither LED shines will be smaller. I could not find a THT version of these LEDs, so it will use an SMD version with a light-pipe.

  • Revised DIN module

    David04/06/2023 at 10:11 0 comments

    I started this round of revisions with the simplest module: the DIN module. 

    All this time the DIN output has not been working, because it has been inverted. It happened because the first version of this module used another MCU with the ability to invert any pin function (and I misread the datasheet for the current one). It should now be fixed by adding another inverter stage.

    I have also complained about the lack of EEPROM in this MCU. But the only thing I can think of that needs changing in this module is the MIDI group, so I added a DIP switch instead. This DIP switch is piano style for side access since the faceplate of this module can't be removed.

    Buttons to enter bootloader mode have been added, one just resets the module, and the other selects prog mode (if it is held during reset). This should make uploading firmware easier. Although the SAM-BA bootloader program has too many steps in my opinion, maybe I'll try to make it easier sometime.

    I added some protection on the 5V power to prevent the USB connection from powering the rest of the rack. I had some issues with that on the USB module, but it can occur when updating the firmware over USB on this module. This is solved with a PMOS transistor which should result in a lower voltage drop than a diode would have.

    Power protection of the 5V rail, VBUS is from the USB connector

    I considered doing hardware control of the activity LEDs by using peak detectors on the UART lines. But the PCB is already tight, so I could not fit the extra components. It isn't necessary anyways, the CPU load on this module is pretty low.

    The rest of the changes are component and silkscreen changes. The USB connector has been switched to an SMD version with THT on the shield because the THT version I used before was not within the fab's capabilities, so I had to adjust it. Room has been made for a latching bus connector which may prolong how long it lasts. The SWD programming header is now horizontal so it can be accessed from the side. And I have added reminders for which pins are which.

View all 23 project logs

Enjoy this project?

Share

Discussions

Richard Hogben wrote 10/22/2024 at 19:53 point

Amazing project, have you attended any of the Supercons?

  Are you sure? yes | no

David wrote 10/24/2024 at 16:11 point

Thank you! I haven't attended any supercon so far

  Are you sure? yes | no

Similar Projects

Does this project spark your interest?

Become a member to follow this project and never miss any updates