Back to Hardware

MIDI over Bluetooth

18 Jun 2016
Progress: Work-in-progress

Wireless MIDI has obvious benefits for someone who plays the keytar.

The problem is, there is no – as far as I can tell – MIDI class / Bluetooth profile. USB's MIDI streaming class means that you can plug a USB-MIDI device into any USB host regardless of operating system and it will work without having to install anything. This is doubly annoying since Bluetooth HID is just a thin encapsulation of USB HID and works in much the same way (keyboards, mice, etc), and audio streaming is obviously a major use of Bluetooth, but they didn't bother to create a MIDI class for us. Shucks.

But we needn't despair since Bluetooth Serial Port Profile (SPP) is available and MIDI is at its core just serial data. If we can cope with having to use driver software on the host / receiver, MIDI over bluetooth should be perfectly doable.

So I thought I'd try it out.

Bluetooth SPP

This is the HC-05 bluetooth serial module, broken out.

The datasheet for it is written in awful english but everything we need to know is in there. In short: there are two modes in can enter when it boots up, one of which turns it into an automatic serial transceiver, the other listens to AT commands. To get it into AT mode you turn it on holding the tiny button down (which pulls pin 34 high).

(There are actually a bunch of different modules all using the same chip (BC417) from CSR, the only difference in their behaviour is the firmware. The HC-06 is the same as the HC-05 but only does slave mode. Also, some people have managed to swap the firmware for one from a more expensive module, and managed to get it to behave as Bluetooth HID.)

The setup:

Using an FTDI USB serial converter. It has a jumper to set it to 3.3 volts (although the HC-05 breakout has level conversion onboard). Tx → Rx and Rx → Tx. The default settings were 38400bps, 8N1.

AT commands are used for all sorts of modems and are pretty simple, and described in the datasheet. I set the device name to be "midi" (AT+NAME=midi) and then played with the UART settings.

Since MIDI is just UART at 31250bps, 8N1 it would be swell if the module could just listen to this directly and send it. Unfortunately it only supports a fixed set of baud rates. There's easily enough processing power on that chip to do the conversion for us, even to bit bang the whole thing, but working with an unfamiliar architecture, coupled to the fact the dev tools and toolchain for the chip are all proprietary and expensive, by far the easiest thing for me to do is add another chip to do the baudrate conversion.

The possible speeds of the module's UART are: 4800, 9600, 19200, 38400, 57600, 115200, 230400, 460800, 921600 and 1382400. These are pretty standard speeds. So long as we pick one that's faster than MIDI there should be no problem, and no need for buffers or anything. In fact the overall latency will partly depend on this relaying, so we may as well go as fast as possible.

I took an atmel chip from my drawer which had a UART (ATtiny2313, still have a few of them left). Its baud rate settings are based on divisors from the master clock frequency which seem to clash perfectly with the fast rates we're trying to get. Its internal RC oscillator is 8MHz, and setting the divisor as small as possible, using the double-speed setting, we can get exactly 1Mbps, which is pretty far from the nearest available setting (921.6kbps). Not to worry though, since these ATtiny chips allow you to calibrate their internal oscillators. We can set to OSCCAL and flatten the master clock frequency to bend the UART speed down to 921.6kbps.

This then means there's no easy divisor to get the 31.25kbps, but that's not a problem since it's so much slower anyway. Besides, UART's stop bits take account of quite big variations, you can get away with well over a 5% speed difference.

So the chip bit-bangs the UART input at MIDI speed, then sends it to the really fast UART output. I looked at the graph of OSCCAL values and chose dead centre, 64, and it worked first time so I left it. There's probably better values to get the timing more accurate or maybe I was just lucky. The UART bit-bang input is lifted straight from my synth cable, but I shortened the timings by approximately the same percentage that the RC oscillator had been flattened (again, mostly a guess but it worked first time).

.include "tn2313adef.inc"

    ldi r16,64
    out OSCCAL, r16

    nop
    nop
    nop
    nop
    nop

    ldi r16,1<<PD1
    out DDRD,r16

    ldi r16,0
    out UBRRL,r16
    out UBRRH,r16
    ldi r16,1<<U2X
    out UCSRA,r16
    ldi r16,1<<TXEN
    out UCSRB,r16


receiveByte:
    sbic PIND, 0
    rjmp receiveByte
    cli

    ldi r16,30
rbWait1:
    nop
    dec r16
    brne rbWait1

    ldi r17,8
    ldi r18,0

rbBit:
    ldi r16,57
rbWait2:
    clc
    dec r16
    brne rbWait2
    
    nop
    nop

    sbic PIND, 0
    sec
    ror r18
    
    dec r17
    brne rbBit

rbEnd:
    sbis PIND, 0
    rjmp rbEnd


transmit:
    sbis UCSRA,UDRE
    rjmp transmit

    out UDR,r18

    rjmp receiveByte

84 bytes total.

Green board is an ISP adapter to program the chip. FTDI board now sends at 31250bps which gets upscaled to 921600 by the ATtiny2313 and transmitted by the HC-05.

Brilliant. Midi data goes into the chip, superfast UART comes out and goes into the HC-05. That was the easy bit.

Connecting, installing, pairing, oh boy. To get it to work on windows. I had enormous difficulty, it seems that every time it unpaired it would never pair again. Almost certainly driver issues. The only method I could come up with of reliably pairing it was to completely remove the device, disable and re-enable the bluetooth adapter then pair it from scratch again.

It would also sometimes only half-connect, or perhaps this is again some windows virtual COM port thing where it would appear to be paired but the COM port would be totally unresponsive.

Anyway, saving the reader any more of this pain, it was possible to get the module connected and a terminal emulator receiving data from it. Then I unplugged the FTDI board and crocodile-clipped the Rx onto an actual MIDI cable, and using the newly created baudrate converter chip, we had midi arriving at the terminal over the wireless connection. Woohoo!

The FE bytes are active sense bytes. This is an old MIDI spec thing to alert a receiver that although there's nothing to send, the line is not dead. Something I noticed quite quickly with the Bluetooth SPP connection is that after about 20 seconds of idle time, it goes to sleep and the next byte has a delay of about half a second. After that it seemed pretty responsive, until it went idle again. For that reason I deliberately used a keyboard with active sense enabled so as to keep the line responsive. (Thinking ahead here, it may be needed in a final device to add active sense bytes into the stream if the keyboard doesn't send them ordinarily. This may play havoc with running status...)

Next step. To evaluate what the serial data coming in is, and clump it back into MIDI messages which then get sent to a virtual MIDI input port. I was working quite quickly at this point as I really, really wanted to know if this was going to be worth it. As soon as we had note-on and note-off going through we could get a sense of whether the latency is acceptable or terrible.

So I wrote a tiny python script that uses the rtmidi module. Unfortunately (another Windows issue) I couldn't get virtual ports working. I even changed python version, back to 2.7 since that's what the rtmidi module is intended to support, but no luck. Not the end of the world as we can use Midi Yoke to emulate virtual cables, but it's yet another step, yet another failure point, yet another source of lag.

Here is the python code that I wrote (but it's probably glitchy and doesn't support sysex and so on, but again, speed is everything!)

import rtmidi
import serial

midiout = rtmidi.MidiOut()
midiout.open_port(1) # MIDI Yoke 1

midiIn = serial.Serial('COM7')

status = 0x80

while (1):
    a = ord(midiIn.read())
    if (a == 0xFE):
        continue
    if (a & 0x80):
        status = a
        b = ord(midiIn.read())
    else:
        b = a
    c = ord(midiIn.read())

    midiout.send_message([status,b,c])

At last, MIDI over Bluetooth SPP was working. And it's not terrible! In fact so long as the active sense bytes are sent, the latency is almost unnoticeable. I unfortunately don't have any way of scientifically measuring the lag, since it's working with the laptop's built in bluetooth module. But it is most definitely within the usable range. This is a Good Thing.

Taking a break from the receiving end, this whole thing is still operating on a breadboard with an external power source. Having to use a battery (separate battery) would suck. A big question is, can it be powered by MIDI?

My initial tests came up with the answer 'no'. The module runs at >3.3v and the MIDI data line struggles to provide more than ~2.9v. A lot of power is being lost by the voltage regulator, even if it is a low dropout one. First I tried to pump the MIDI voltage in by bypassing the regulator with a bit of wire.

No luck. Just for a sanity check, pumping 3.3v in from another supply powers it up just fine. Perhaps the regulator is draining it somehow just by being connected? Also that LED is drawing a few mA.

I decided to remove the castellated board from its breakout. Luckily it's only soldered in place at three corners and can be removed with a soldering iron, razor blade and some patience.

Who needs a breakout? Just stick some stiff wire to the three pins that we need: power, ground, Rx.

Breadboardable again... and yet still no luck. The module just refused to power up from the midi source. Worse, after powering up it up from the ordinary supply it would no longer pair with the laptop again – this time with yet another variation of failures.

With windows drivers it seems the fun never stops. Worried that I'd fried the module somehow, I actually then soldered it back onto the breakout board which didn't fix the issue. In fact it turned out to be entirely software based on the laptop.

The development process would be a lot quicker if I didn't have to waste ten minutes every time I tried to connect it. Coupled with the lack of cross-platform support, clearly we are heading in the wrong direction on the side of the master device.

I think the best set-up would be a separate, dedicated bluetooth dongle that identifies as a USB MIDI device. This would eliminate the cross platform issues and the need for drivers. This would also hopefully be more reliable in terms of pairing correctly. The HC-05 module can behave as a master, unfortunately I only have the one at the moment. I've ordered another, I will update this page when it arrives.

A dedicated bluetooth USB-MIDI dongle would definitely improve the system as well as the development process. Can the transmitter be powered by MIDI? I'm sure it's possible, since I'm fairly sure the actual bluetooth chip uses 1.8v in its core. It only needs the 3.3v for its USB peripheral, which isn't used. Whether or not there's some internal register that needs setting to disable its 3.3v systems is a question I'm unlikely to be able to answer, since delving into the chip's firmware is beyond my effort threshold at this point.

In conclusion, for now, I've convinced myself that MIDI over Bluetooth SPP is viable but there are many bumps to smooth out. Stay tuned.


Minor Update
OK so reading the BC417 datasheet it seems that breaking into its firmware would definitely be the best route, if we can cope with it. I can get hold of the SDK but it uses a parallel port interface to program the chip. This will definitely be a stumbling block.

First of all we could dispense with the baud rate conversion, although whether we can do this without having to re-write all the SPP interfacing I'm not sure. Possibly we could disassemble the current firmware and search for the PSKEY_UART_BAUD_RATE (0x204) register access and overwrite it with our own value. Reading the datasheet, UART baud rate is equal to PSKEY_UART_BAUD_RATE / 0.004096 so a value of 128 would give exactly 31250.

The second advantage of jumping into the firmware is the hardware USB support. For the master dongle this would both simplify the circuit and reduce latency slightly. If we can flash it with the report descriptors for the midi streaming class we could build our entire bluetooth midi link using essentially unmodified hardware. Which would be awesome.

It seems that the program code is stored entirely on the external flash on board (I think) in which case it may be possible to reprogram the firmware by writing directly to the flash. This uses the JEDEC Common Flash Interface. But physically soldering to this might be a challenge. I shall look into this some more.


Post-Minor Update
I've now made up a programming cable and I'm able to dump/reflash the firmware of the HC-05, I'll talk more about this in a later project. One of the tools from CSR is the PSKey tool which lets you modify individual keys, so assuming the firmware doesn't rewrite this on boot up, we could potentially set the unusual baud rate there and not need the conversion chip.

The biggest problem with the HC-05 is its power consumption. I just can't foresee getting this to work when powered by the midi cable. I even bought this cute little boost converter, which will take an input as low as 0.7V and boost it up to 3.3V:

But the HC-05 draws about 18mA when idle, and up to 50mA when transmitting. Given that the MIDI spec allows for a 5mA current loop, it's unlikely this will ever work. We're left with two options:

My, aha, current line of thinking is to go with a different wireless protocol that hopefully uses less power. If it's being received by a dedicated dongle, it doesn't need to be bluetooth at all. The investigation continues... stay tuned...