diff options
author | irieda <[email protected]> | 2023-01-08 06:30:40 +0900 |
---|---|---|
committer | GitHub <[email protected]> | 2023-01-07 22:30:40 +0100 |
commit | a7ff2731b91a83767a72da5f8ad196b45bc4c5de (patch) | |
tree | 158138c8c627c2e435d000edeb66a8c6be7a7ea3 /src/machine/usb | |
parent | 0566bbfeb4f09adecfa1658ad7c67ac652057dce (diff) | |
download | tinygo-a7ff2731b91a83767a72da5f8ad196b45bc4c5de.tar.gz tinygo-a7ff2731b91a83767a72da5f8ad196b45bc4c5de.zip |
Add USB HID joystick support (#3366)
machine/usb: add USB HID joystick support
Diffstat (limited to 'src/machine/usb')
-rw-r--r-- | src/machine/usb/descriptor.go | 51 | ||||
-rw-r--r-- | src/machine/usb/hid/hid.go | 9 | ||||
-rw-r--r-- | src/machine/usb/joystick/buffer.go | 52 | ||||
-rw-r--r-- | src/machine/usb/joystick/joystick.go | 95 | ||||
-rw-r--r-- | src/machine/usb/joystick/state.go | 213 | ||||
-rw-r--r-- | src/machine/usb/midi/midi.go | 5 | ||||
-rw-r--r-- | src/machine/usb/usb.go | 17 |
7 files changed, 430 insertions, 12 deletions
diff --git a/src/machine/usb/descriptor.go b/src/machine/usb/descriptor.go index 17bf07709..3acb2e9a7 100644 --- a/src/machine/usb/descriptor.go +++ b/src/machine/usb/descriptor.go @@ -109,9 +109,58 @@ var DescriptorCDCMIDI = Descriptor{ 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, + 0x09, 0x05, 0x07, 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, }, } + +var DescriptorCDCJoystick = Descriptor{ + Device: []byte{ + 0x12, 0x01, 0x00, 0x02, 0xef, 0x02, 0x01, 0x40, + 0x41, 0x23, 0x36, 0x80, 0x00, 0x01, 0x01, 0x02, + 0x03, 0x01, + }, + Configuration: []byte{ + // Configuration Descriptor Header + 0x09, 0x02, + 0x6b, 0x00, // Total Length: 0x006b(107) + 0x03, 0x01, 0x00, 0xa0, 0xfa, + // InterfaceAssociation Descriptoy + 0x08, 0x0b, 0x00, 0x02, 0x02, 0x02, 0x00, 0x00, + // InterfaceSescriptor(CDC-Ctrl) + 0x09, 0x04, 0x00, 0x00, 0x01, 0x02, 0x02, 0x00, 0x00, + // Communication Descriptor + 0x05, 0x24, 0x00, 0x10, 0x01, + // Communication Descriptor + 0x05, 0x24, 0x01, 0x01, 0x01, + // Communication Descriptor + 0x04, 0x24, 0x02, 0x06, + // Communication Descriptor + 0x05, 0x24, 0x06, 0x00, 0x01, + // ENDPOINT Descriptor + 0x07, 0x05, 0x81, 0x03, 0x10, 0x00, 0x40, + // InterfaceSescriptor(CDC-Data) + 0x09, 0x04, 0x01, 0x00, 0x02, 0x0a, 0x00, 0x00, 0x00, + // ENDPOINT Descriptor + 0x07, 0x05, 0x02, 0x02, 0x40, 0x00, 0x00, + // ENDPOINT Descriptor + 0x07, 0x05, 0x83, 0x02, 0x40, 0x00, 0x00, + // InterfaceSescriptor(HID) + 0x09, 0x04, 0x02, 0x00, 0x02, 0x03, 0x00, 0x00, 0x00, + // HID Descriptor + 0x09, // bLength: 9 + 0x21, // bDescriptorType: 0x21(HID) + 0x11, 0x01, // bcdHID: 0x111 + 0x00, // bCountryCode: Not Supported + 0x01, // bNumDescriptors: 1 + 0x22, // Type: HID Report + 0x00, 0x00, // Length + // ENDPOINT Descriptor + 0x07, 0x05, 0x84, 0x03, 0x40, 0x00, 0x01, + // ENDPOINT Descriptor + 0x07, 0x05, 0x05, 0x03, 0x40, 0x00, 0x01, + }, + HID: map[uint16][]byte{}, +} diff --git a/src/machine/usb/hid/hid.go b/src/machine/usb/hid/hid.go index 4f4072419..d0ad366f0 100644 --- a/src/machine/usb/hid/hid.go +++ b/src/machine/usb/hid/hid.go @@ -14,10 +14,11 @@ var ( ) const ( - hidEndpoint = 4 + hidEndpoint = usb.HID_ENDPOINT_IN - usb_SET_REPORT_TYPE = 33 - usb_SET_IDLE = 10 + REPORT_TYPE_INPUT = 1 + REPORT_TYPE_OUTPUT = 2 + REPORT_TYPE_FEATURE = 3 ) type hidDevicer interface { @@ -51,7 +52,7 @@ func handler() { func setupHandler(setup usb.Setup) bool { ok := false - if setup.BmRequestType == usb_SET_REPORT_TYPE && setup.BRequest == usb_SET_IDLE { + if setup.BmRequestType == usb.SET_REPORT_TYPE && setup.BRequest == usb.SET_IDLE { machine.SendZlp() ok = true } diff --git a/src/machine/usb/joystick/buffer.go b/src/machine/usb/joystick/buffer.go new file mode 100644 index 000000000..3610b52e3 --- /dev/null +++ b/src/machine/usb/joystick/buffer.go @@ -0,0 +1,52 @@ +package joystick + +import ( + "runtime/volatile" +) + +const bufferSize = 32 + +// 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][16]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/joystick/joystick.go b/src/machine/usb/joystick/joystick.go new file mode 100644 index 000000000..9c12997b9 --- /dev/null +++ b/src/machine/usb/joystick/joystick.go @@ -0,0 +1,95 @@ +package joystick + +import ( + "machine" + "machine/usb" +) + +const ( + jsEndpointOut = usb.HID_ENDPOINT_OUT // from PC + jsEndpointIn = usb.HID_ENDPOINT_IN // to PC +) + +var js *Joystick + +type Joystick struct { + State + buf *RingBuffer + waitTxc bool + rxHandlerFunc func(b []byte) + setupFunc func(setup usb.Setup) bool +} + +func Enable(def Definitions, rxHandlerFunc func(b []byte), + setupFunc func(setup usb.Setup) bool, hidDesc []byte) *Joystick { + m := &Joystick{ + buf: NewRingBuffer(), + State: def.NewState(), + } + m.State = def.NewState() + if setupFunc == nil { + setupFunc = m.setupFunc + } + machine.EnableJoystick(m.handler, rxHandlerFunc, setupFunc, hidDesc) + js = m + return m +} + +// Port returns the USB Joystick port. +func Port() *Joystick { + if js == nil { + def := DefaultDefinitions() + js = &Joystick{ + State: def.NewState(), + buf: NewRingBuffer(), + } + machine.EnableJoystick(js.handler, nil, js.setupFunc, def.Descriptor()) + } + return js +} + +func (m *Joystick) sendUSBPacket(b []byte) { + machine.SendUSBInPacket(jsEndpointIn, b) +} + +// from InterruptIn +func (m *Joystick) handler() { + m.waitTxc = false + if b, ok := m.buf.Get(); ok { + m.waitTxc = true + m.sendUSBPacket(b) + } +} + +func (m *Joystick) setup(setup usb.Setup) bool { + if setup.BmRequestType == usb.SET_REPORT_TYPE && setup.BRequest == usb.SET_IDLE { + machine.SendZlp() + return true + } + return false +} + +func (m *Joystick) rxHandler(b []byte) { + if m.rxHandlerFunc != nil { + m.rxHandlerFunc(b) + } +} + +func (m *Joystick) tx(b []byte) { + if m.waitTxc { + m.buf.Put(b) + } else { + m.waitTxc = true + m.sendUSBPacket(b) + } +} + +// to InterruptOut +func (m *Joystick) SendReport(reportID byte, b []byte) { + m.tx(append([]byte{reportID}, b...)) +} + +func (m *Joystick) SendState() { + b, _ := m.State.MarshalBinary() + m.tx(b) +} diff --git a/src/machine/usb/joystick/state.go b/src/machine/usb/joystick/state.go new file mode 100644 index 000000000..51b7e6c40 --- /dev/null +++ b/src/machine/usb/joystick/state.go @@ -0,0 +1,213 @@ +package joystick + +import "encoding/binary" + +type HatDirection uint8 + +const ( + HatUp HatDirection = iota + HatRightUp + HatRight + HatRightDown + HatDown + HatLeftDown + HatLeft + HatLeftUp + HatCenter +) + +type Constraint struct { + MinIn int + MaxIn int + MinOut int16 + MaxOut int16 +} + +type AxisValue struct { + constraint Constraint + Value int +} + +func fit(x, in_min, in_max int, out_min, out_max int16) int16 { + return int16((x-in_min)*(int(out_max)-int(out_min))/(in_max-in_min) + int(out_min)) +} + +func fitf(x, in_min, in_max, out_min, out_max float32) float32 { + return x - in_min*(out_max-out_min)/(in_max-in_min) + out_min +} + +func limit(v, max int) int { + if v > max { + v = max + } else if v < -max { + v = -max + } + return v +} + +type Definitions struct { + ReportID byte + ButtonCnt int + HatSwitchCnt int + AxisDefs []Constraint + descriptor []byte +} + +func (c Definitions) Descriptor() []byte { + if len(c.descriptor) > 0 { + return c.descriptor + } + // TODO: build hid descriptor + return nil +} + +func (c Definitions) NewState() State { + bufSize := 1 + axises := make([]*AxisValue, 0, len(c.AxisDefs)) + for _, v := range c.AxisDefs { + + axises = append(axises, &AxisValue{ + constraint: v, + Value: 0, + }) + } + btnSize := (c.ButtonCnt + 7) / 8 + bufSize += btnSize + if c.HatSwitchCnt > 0 { + bufSize++ + } + bufSize += len(axises) * 2 + initBuf := make([]byte, bufSize) + initBuf[0] = c.ReportID + return State{ + buf: initBuf, + Buttons: make([]byte, btnSize), + HatSwitches: make([]HatDirection, c.HatSwitchCnt), + Axises: axises, + } +} + +type State struct { + buf []byte + Buttons []byte + HatSwitches []HatDirection + Axises []*AxisValue +} + +func (s State) MarshalBinary() ([]byte, error) { + s.buf = s.buf[0:1] + s.buf = append(s.buf, s.Buttons...) + if len(s.HatSwitches) > 0 { + hat := byte(0) + for _, v := range s.HatSwitches { + hat <<= 4 + hat |= byte(v & 0xf) + } + s.buf = append(s.buf, hat) + } + for _, v := range s.Axises { + c := v.constraint + val := fit(v.Value, c.MinIn, c.MaxIn, c.MinOut, c.MaxOut) + s.buf = binary.LittleEndian.AppendUint16(s.buf, uint16(val)) + } + return s.buf, nil +} + +func (s State) Button(index int) bool { + idx := index / 8 + bit := uint8(1 << (index % 8)) + return s.Buttons[idx]&bit > 0 +} + +func (s State) SetButton(index int, push bool) { + idx := index / 8 + bit := uint8(1 << (index % 8)) + b := s.Buttons[idx] + b &= ^bit + if push { + b |= bit + } + s.Buttons[idx] = b +} + +func (s State) Hat(index int) HatDirection { + return s.HatSwitches[index] +} + +func (s State) SetHat(index int, dir HatDirection) { + s.HatSwitches[index] = dir +} + +func (s State) Axis(index int) int { + return s.Axises[index].Value +} + +func (s State) SetAxis(index int, v int) { + s.Axises[index].Value = v +} + +func DefaultDefinitions() Definitions { + return Definitions{ + ReportID: 1, + ButtonCnt: 16, + HatSwitchCnt: 1, + AxisDefs: []Constraint{ + {MinIn: -32767, MaxIn: 32767, MinOut: -32767, MaxOut: 32767}, + {MinIn: -32767, MaxIn: 32767, MinOut: -32767, MaxOut: 32767}, + {MinIn: -32767, MaxIn: 32767, MinOut: -32767, MaxOut: 32767}, + {MinIn: -32767, MaxIn: 32767, MinOut: -32767, MaxOut: 32767}, + {MinIn: -32767, MaxIn: 32767, MinOut: -32767, MaxOut: 32767}, + {MinIn: -32767, MaxIn: 32767, MinOut: -32767, MaxOut: 32767}, + }, + descriptor: []byte{ + 0x05, 0x01, + 0x09, 0x04, + 0xa1, 0x01, // COLLECTION (Application) + 0x85, 0x01, // REPORT_ID (1) + 0x05, 0x09, // USAGE_PAGE (Button) + 0x19, 0x01, // USAGE_MINIMUM (Button 1) + 0x29, 0x10, // USAGE_MAXIMUM (Button 16) + 0x15, 0x00, // LOGICAL_MINIMUM (0) + 0x25, 0x01, // LOGICAL_MAXIMUM (1) + 0x75, 0x01, // REPORT_SIZE (1) + 0x95, 0x10, // REPORT_COUNT (16) + 0x55, 0x00, // Unit Exponent (-16) + 0x65, 0x00, // Unit (0x00) + 0x81, 0x02, // INPUT (Data/Var/Abs) + 0x05, 0x01, // USAGE_PAGE (Generic Desktop Controls) + 0x09, 0x39, // USAGE(Hat Switch) + 0x15, 0x00, // LOGICAL_MINIMUM (0) + 0x25, 0x07, // LOGICAL_MAXIMUM (7) + 0x35, 0x00, // PHYSICAL_MINIMUM (0) + 0x46, 0x3b, 0x01, // PHYSICAL_MAXIMUM(315) + 0x65, 0x14, // UNIT (Eng Rot:Angular Pos) + 0x75, 0x04, // REPORT_SIZE (4) + 0x95, 0x01, // REPORT_COUNT (1) + 0x81, 0x02, // INPUT (Data/Var/Abs) + 0x09, 0x39, // USAGE(Hat Switch) + 0x15, 0x00, // LOGICAL_MINIMUM (0) + 0x25, 0x07, // LOGICAL_MAXIMUM (7) + 0x35, 0x00, // PHYSICAL_MINIMUM (0) + 0x46, 0x3b, 0x01, // PHYSICAL_MAXIMUM(315) + 0x65, 0x14, // UNIT (Eng Rot:Angular Pos) + 0x75, 0x04, // REPORT_SIZE (4) + 0x95, 0x01, // REPORT_COUNT (1) + 0x81, 0x02, // INPUT (Data/Var/Abs) + 0x09, 0x01, // USAGE (Pointer) + 0x16, 0x01, 0x80, // LOGICAL_MINIMUM (-32767) + 0x26, 0xff, 0x7f, // LOGICAL_MAXIMUM (32767) + 0x75, 0x10, // REPORT_SIZE (16bits) + 0x95, 0x06, // REPORT_COUNT (6) + 0xa1, 0x00, // COLLECTION (Physical) + 0x09, 0x30, // USAGE(X) + 0x09, 0x31, // USAGE(Y) + 0x09, 0x32, // USAGE(Z) + 0x09, 0x33, // USAGE(RX) + 0x09, 0x34, // USAGE(RY) + 0x09, 0x35, // USAGE(RZ) + 0x81, 0x02, // INPUT (Data/Var/Abs) + 0xc0, // END_COLLECTION + 0xc0, // END_COLLECTION + }, + } +} diff --git a/src/machine/usb/midi/midi.go b/src/machine/usb/midi/midi.go index 58f802b51..c0796be49 100644 --- a/src/machine/usb/midi/midi.go +++ b/src/machine/usb/midi/midi.go @@ -2,11 +2,12 @@ package midi import ( "machine" + "machine/usb" ) const ( - midiEndpointOut = 5 // from PC - midiEndpointIn = 6 // to PC + midiEndpointOut = usb.MIDI_ENDPOINT_OUT // from PC + midiEndpointIn = usb.MIDI_ENDPOINT_IN // to PC ) var Midi *midi diff --git a/src/machine/usb/usb.go b/src/machine/usb/usb.go index 592b32beb..8363fc361 100644 --- a/src/machine/usb/usb.go +++ b/src/machine/usb/usb.go @@ -9,6 +9,7 @@ const ( DescriptorConfigCDC = 1 << iota DescriptorConfigHID DescriptorConfigMIDI + DescriptorConfigJoystick ) const ( @@ -35,7 +36,6 @@ const ( EndpointOut = 0x00 EndpointIn = 0x80 - NumberOfEndpoints = 8 EndpointPacketSize = 64 // 64 for Full Speed, EPT size max is 1024 // standard requests @@ -51,7 +51,12 @@ const ( SET_INTERFACE = 11 // non standard requests - SET_IDLE = 10 + GET_REPORT = 1 + GET_IDLE = 2 + GET_PROTOCOL = 3 + SET_REPORT = 9 + SET_IDLE = 10 + SET_PROTOCOL = 11 DEVICE_CLASS_COMMUNICATIONS = 0x02 DEVICE_CLASS_HUMAN_INTERFACE = 0x03 @@ -75,9 +80,11 @@ const ( CDC_ENDPOINT_ACM = 1 CDC_ENDPOINT_OUT = 2 CDC_ENDPOINT_IN = 3 - HID_ENDPOINT_IN = 4 - MIDI_ENDPOINT_OUT = 5 - MIDI_ENDPOINT_IN = 6 + HID_ENDPOINT_IN = 4 // for Interrupt In + HID_ENDPOINT_OUT = 5 // for Interrupt Out + MIDI_ENDPOINT_IN = 6 // for Bulk In + MIDI_ENDPOINT_OUT = 7 // for Bulk Out + NumberOfEndpoints = 8 // bmRequestType REQUEST_HOSTTODEVICE = 0x00 |