diff --git a/Makefile b/Makefile index 88e12672..149ad532 100644 --- a/Makefile +++ b/Makefile @@ -595,11 +595,13 @@ endif @$(MD5SUM) test.hex $(TINYGO) build -size short -o test.hex -target=feather-m4 examples/pwm @$(MD5SUM) test.hex - # test usbhid + # test usb $(TINYGO) build -size short -o test.hex -target=feather-nrf52840 examples/hid-keyboard @$(MD5SUM) test.hex $(TINYGO) build -size short -o test.hex -target=circuitplay-express examples/hid-keyboard @$(MD5SUM) test.hex + $(TINYGO) build -size short -o test.hex -target=feather-nrf52840 examples/usb-midi + @$(MD5SUM) test.hex ifneq ($(STM32), 0) $(TINYGO) build -size short -o test.hex -target=bluepill examples/blinky1 @$(MD5SUM) test.hex diff --git a/src/examples/usb-midi/main.go b/src/examples/usb-midi/main.go new file mode 100644 index 00000000..75e89a23 --- /dev/null +++ b/src/examples/usb-midi/main.go @@ -0,0 +1,54 @@ +package main + +import ( + "fmt" + "machine" + "machine/usb/midi" + "time" +) + +func main() { + led := machine.LED + led.Configure(machine.PinConfig{Mode: machine.PinOutput}) + + button := machine.BUTTON + button.Configure(machine.PinConfig{Mode: machine.PinInputPullup}) + + m := midi.New() + m.SetCallback(func(b []byte) { + led.Set(!led.Get()) + fmt.Printf("% X\r\n", b) + m.Write(b) + }) + + prev := true + chords := []struct { + name string + keys []byte + }{ + {name: "C ", keys: []byte{60, 64, 67}}, + {name: "G ", keys: []byte{55, 59, 62}}, + {name: "Am", keys: []byte{57, 60, 64}}, + {name: "F ", keys: []byte{53, 57, 60}}, + } + index := 0 + + for { + current := button.Get() + if prev != current { + led.Set(current) + if current { + for _, c := range chords[index].keys { + m.Write([]byte{0x08, 0x80, c, 0x40}) + } + index = (index + 1) % len(chords) + } else { + for _, c := range chords[index].keys { + m.Write([]byte{0x09, 0x90, c, 0x40}) + } + } + prev = current + } + time.Sleep(10 * time.Millisecond) + } +} diff --git a/src/machine/usb.go b/src/machine/usb.go index 55eb8e1c..794c4b66 100644 --- a/src/machine/usb.go +++ b/src/machine/usb.go @@ -32,6 +32,7 @@ var usbDescriptor = descriptorCDC const ( usbDescriptorConfigCDC = 1 << iota usbDescriptorConfigHID + usbDescriptorConfigMIDI ) var usbDescriptorConfig uint8 = usbDescriptorConfigCDC @@ -137,11 +138,13 @@ const ( usb_HID_INTERFACE = 2 // HID // Endpoint - usb_CONTROL_ENDPOINT = 0 - usb_CDC_ENDPOINT_ACM = 1 - usb_CDC_ENDPOINT_OUT = 2 - usb_CDC_ENDPOINT_IN = 3 - usb_HID_ENDPOINT_IN = 4 + usb_CONTROL_ENDPOINT = 0 + usb_CDC_ENDPOINT_ACM = 1 + usb_CDC_ENDPOINT_OUT = 2 + usb_CDC_ENDPOINT_IN = 3 + usb_HID_ENDPOINT_IN = 4 + usb_MIDI_ENDPOINT_OUT = 5 + usb_MIDI_ENDPOINT_IN = 6 // bmRequestType usb_REQUEST_HOSTTODEVICE = 0x00 @@ -170,11 +173,13 @@ var ( usbSetupHandler [numberOfInterfaces]func(USBSetup) bool endPoints = []uint32{ - usb_CONTROL_ENDPOINT: usb_ENDPOINT_TYPE_CONTROL, - usb_CDC_ENDPOINT_ACM: (usb_ENDPOINT_TYPE_INTERRUPT | usbEndpointIn), - usb_CDC_ENDPOINT_OUT: (usb_ENDPOINT_TYPE_BULK | usbEndpointOut), - usb_CDC_ENDPOINT_IN: (usb_ENDPOINT_TYPE_BULK | usbEndpointIn), - usb_HID_ENDPOINT_IN: (usb_ENDPOINT_TYPE_DISABLE), // Interrupt In + usb_CONTROL_ENDPOINT: usb_ENDPOINT_TYPE_CONTROL, + usb_CDC_ENDPOINT_ACM: (usb_ENDPOINT_TYPE_INTERRUPT | usbEndpointIn), + usb_CDC_ENDPOINT_OUT: (usb_ENDPOINT_TYPE_BULK | usbEndpointOut), + usb_CDC_ENDPOINT_IN: (usb_ENDPOINT_TYPE_BULK | usbEndpointIn), + usb_HID_ENDPOINT_IN: (usb_ENDPOINT_TYPE_DISABLE), // Interrupt In + usb_MIDI_ENDPOINT_OUT: (usb_ENDPOINT_TYPE_DISABLE), // Bulk Out + usb_MIDI_ENDPOINT_IN: (usb_ENDPOINT_TYPE_DISABLE), // Bulk In } ) @@ -222,6 +227,8 @@ func sendDescriptor(setup USBSetup) { // composite descriptor if (usbDescriptorConfig & usbDescriptorConfigHID) > 0 { usbDescriptor = descriptorCDCHID + } else if (usbDescriptorConfig & usbDescriptorConfigMIDI) > 0 { + usbDescriptor = descriptorCDCMIDI } else { usbDescriptor = descriptorCDC } @@ -360,3 +367,12 @@ func EnableHID(txHandler func(), rxHandler func([]byte), setupHandler func(USBSe usbTxHandler[usb_HID_ENDPOINT_IN] = txHandler usbSetupHandler[usb_HID_INTERFACE] = setupHandler // 0x03 (HID - Human Interface Device) } + +// EnableMIDI enables MIDI. This function must be executed from the init(). +func EnableMIDI(txHandler func(), rxHandler func([]byte), setupHandler func(USBSetup) bool) { + usbDescriptorConfig |= usbDescriptorConfigMIDI + endPoints[usb_MIDI_ENDPOINT_OUT] = (usb_ENDPOINT_TYPE_BULK | usbEndpointOut) + endPoints[usb_MIDI_ENDPOINT_IN] = (usb_ENDPOINT_TYPE_BULK | usbEndpointIn) + usbRxHandler[usb_MIDI_ENDPOINT_OUT] = rxHandler + usbTxHandler[usb_MIDI_ENDPOINT_IN] = txHandler +} diff --git a/src/machine/usb/midi/buffer.go b/src/machine/usb/midi/buffer.go new file mode 100644 index 00000000..39cab2ca --- /dev/null +++ b/src/machine/usb/midi/buffer.go @@ -0,0 +1,52 @@ +package midi + +import ( + "runtime/volatile" +) + +const bufferSize = 128 + +// RingBuffer is ring buffer implementation inspired by post at +// https://www.embeddedrelated.com/showthread/comp.arch.embedded/77084-1.php +type RingBuffer struct { + rxbuffer [bufferSize][4]byte + head volatile.Register8 + tail volatile.Register8 +} + +// NewRingBuffer returns a new ring buffer. +func NewRingBuffer() *RingBuffer { + return &RingBuffer{} +} + +// Used returns how many bytes in buffer have been used. +func (rb *RingBuffer) Used() uint8 { + return uint8(rb.head.Get() - rb.tail.Get()) +} + +// Put stores a byte in the buffer. If the buffer is already +// full, the method will return false. +func (rb *RingBuffer) Put(val []byte) bool { + if rb.Used() != bufferSize { + rb.head.Set(rb.head.Get() + 1) + copy(rb.rxbuffer[rb.head.Get()%bufferSize][:], val) + return true + } + return false +} + +// Get returns a byte from the buffer. If the buffer is empty, +// the method will return a false as the second value. +func (rb *RingBuffer) Get() ([]byte, bool) { + if rb.Used() != 0 { + rb.tail.Set(rb.tail.Get() + 1) + return rb.rxbuffer[rb.tail.Get()%bufferSize][:], true + } + return nil, false +} + +// Clear resets the head and tail pointer to zero. +func (rb *RingBuffer) Clear() { + rb.head.Set(0) + rb.tail.Set(0) +} diff --git a/src/machine/usb/midi/midi.go b/src/machine/usb/midi/midi.go new file mode 100644 index 00000000..afcee53c --- /dev/null +++ b/src/machine/usb/midi/midi.go @@ -0,0 +1,79 @@ +package midi + +import ( + "machine" +) + +const ( + midiEndpointOut = 5 // from PC + midiEndpointIn = 6 // to PC +) + +var Midi *midi + +type midi struct { + buf *RingBuffer + callbackFuncRx func([]byte) + waitTxc bool +} + +func init() { + if Midi == nil { + Midi = newMidi() + } +} + +// New returns hid-mouse. +func New() *midi { + return Midi +} + +func newMidi() *midi { + m := &midi{ + buf: NewRingBuffer(), + } + machine.EnableMIDI(m.Callback, m.CallbackRx, nil) + return m +} + +func (m *midi) SetCallback(callbackRx func([]byte)) { + m.callbackFuncRx = callbackRx +} + +func (m *midi) Write(b []byte) (n int, err error) { + i := 0 + for i = 0; i < len(b); i += 4 { + m.tx(b[i : i+4]) + } + return i, nil +} + +// sendUSBPacket sends a MIDIPacket. +func (m *midi) sendUSBPacket(b []byte) { + machine.SendUSBInPacket(midiEndpointIn, b) +} + +// from BulkIn +func (m *midi) Callback() { + m.waitTxc = false + if b, ok := m.buf.Get(); ok { + m.waitTxc = true + m.sendUSBPacket(b) + } +} + +func (m *midi) tx(b []byte) { + if m.waitTxc { + m.buf.Put(b) + } else { + m.waitTxc = true + m.sendUSBPacket(b) + } +} + +// from BulkOut +func (m *midi) CallbackRx(b []byte) { + if m.callbackFuncRx != nil { + m.callbackFuncRx(b) + } +} diff --git a/src/machine/usb_descriptor.go b/src/machine/usb_descriptor.go index b6acb9b6..744d8703 100644 --- a/src/machine/usb_descriptor.go +++ b/src/machine/usb_descriptor.go @@ -71,3 +71,35 @@ var descriptorCDCHID = USBDescriptor{ }, }, } + +var descriptorCDCMIDI = USBDescriptor{ + Device: []byte{ + 0x12, 0x01, 0x00, 0x02, 0xef, 0x02, 0x01, 0x40, 0x86, 0x28, 0x2d, 0x80, 0x00, 0x01, 0x01, 0x02, 0x03, 0x01, + }, + Configuration: []byte{ + 0x09, 0x02, 0xaf, 0x00, 0x04, 0x01, 0x00, 0xa0, 0x32, + 0x08, 0x0b, 0x00, 0x02, 0x02, 0x02, 0x00, 0x00, + 0x09, 0x04, 0x00, 0x00, 0x01, 0x02, 0x02, 0x00, 0x00, + 0x05, 0x24, 0x00, 0x10, 0x01, + 0x04, 0x24, 0x02, 0x06, + 0x05, 0x24, 0x06, 0x00, 0x01, + 0x05, 0x24, 0x01, 0x01, 0x01, + 0x07, 0x05, 0x81, 0x03, 0x10, 0x00, 0x10, + 0x09, 0x04, 0x01, 0x00, 0x02, 0x0a, 0x00, 0x00, 0x00, + 0x07, 0x05, 0x02, 0x02, 0x40, 0x00, 0x00, + 0x07, 0x05, 0x83, 0x02, 0x40, 0x00, 0x00, + 0x08, 0x0b, 0x02, 0x02, 0x01, 0x01, 0x00, 0x00, + 0x09, 0x04, 0x02, 0x00, 0x00, 0x01, 0x01, 0x00, 0x00, + 0x09, 0x24, 0x01, 0x00, 0x01, 0x09, 0x00, 0x01, 0x03, + 0x09, 0x04, 0x03, 0x00, 0x02, 0x01, 0x03, 0x00, 0x00, + 0x07, 0x24, 0x01, 0x00, 0x01, 0x41, 0x00, + 0x06, 0x24, 0x02, 0x01, 0x01, 0x00, + 0x06, 0x24, 0x02, 0x02, 0x02, 0x00, + 0x09, 0x24, 0x03, 0x01, 0x03, 0x01, 0x02, 0x01, 0x00, + 0x09, 0x24, 0x03, 0x02, 0x04, 0x01, 0x01, 0x01, 0x00, + 0x09, 0x05, 0x05, 0x02, 0x40, 0x00, 0x00, 0x00, 0x00, + 0x05, 0x25, 0x01, 0x01, 0x01, + 0x09, 0x05, 0x86, 0x02, 0x40, 0x00, 0x00, 0x00, 0x00, + 0x05, 0x25, 0x01, 0x01, 0x03, + }, +}