Skip to content

Conversation

@sago35
Copy link
Member

@sago35 sago35 commented Jun 2, 2022

This PR adds USBHID (keyboard / mouse) support.
The following microcontrollers are targeted

  • atsamd51
  • atsamd21
  • nrf52840 (WIP: nrf52840's USBCDC / USBHID is broken) (It works fine now)

Unlike #1829, it extends the existing USB implementation.
Also, the keyboard implementation is made by copying the @ardnew implementation.
The USBHID Support being added in this PR is to buy time until #1829 is completed.

The remaining tasks are as follows

  • Add smoketest
  • Make it work with atsamd51
  • Make it work with atsamd21
  • Make it work with nrf52840

@sago35
Copy link
Member Author

sago35 commented Jun 3, 2022

The commit message is probably correct as follows

samd21,samd51,nrf52840: add support for USBHID (keyboard / mouse)

@sago35 sago35 marked this pull request as ready for review June 3, 2022 06:43
@sago35
Copy link
Member Author

sago35 commented Jun 3, 2022

Now ready for review.

A summary of the changes in each file is as follows

  • M Makefile
    • Add smoketest
  • A src/examples/hid-keyboard/main.go
    • Add example
  • A src/examples/hid-mouse/main.go
    • Add example
  • M src/machine/machine_atsamd21.go
    • Add callback invocation when using HID
    • Add branch to support HID
    • Add SendUSBHIDPacket() for interrupt transfer
  • M src/machine/machine_atsamd51.go
    • Same as samd21
  • M src/machine/machine_nrf52840_usb.go
    • Same as samd21
  • M src/machine/usb.go
    • The processing for the descriptor was removed and it is now just a string of bytes
      • VID and PID are now initialized with values such as machine.VID
    • USB device settings can be switched by rewriting usbDescriptor
      • If EnableHID() is called, the descriptor is changed
    • Support for some enumeration instructions required for USBHID support
  • A src/machine/usb_descriptor.go
    • descriptor is defined
      • Initialized with the same contents as arduino
  • A src/machine/usb/hid/buffer.go
    • ringbuffer for interrupt transfer
    • It is based on machine/buffer.go
    • Size could be optimized a bit more
  • A src/machine/usb/hid/hid.go
  • A src/machine/usb/hid/keyboard.go
    • Extracted source code from ardnew
    • Source code for Keyboard operations.
      • Currently, mediakey etc. are not supported.
  • A src/machine/usb/hid/keycode.go
    • Extracted source code from ardnew
  • A src/machine/usb/hid/mouse.go
    • Source code for mouse operations.

@deadprogram
Copy link
Member

@sago35 this PR is really quite useful to me, because it helps me to actually study the HID API a little bit. I will provide feedback later on about how I think we should modify it, but consider how the API would need to handle the following 3 use cases:

  • HID gamepad/joystick
  • MIDI
  • MSD

I think the current implementation makes some assumptions about keyboard/mouse that might not be sufficient.

What do you think?

@sago35
Copy link
Member Author

sago35 commented Jun 7, 2022

@deadprogram Thanks for the comment.
gamepad/joystick, MIDI and MSD should be considered separately from this PR.
My thoughts as of now are as follows.

src/machine/usb.go

I have simplified USB Enumeration in this PR.
With this change, Descriptors for devices such as CDC + MIDI can be easily created.

To support MIDI and MSD, it should be necessary to add callbacks to BulkIn and BulkOut.

src/machine/usb/hid/

There may be something missing in the keyboard or mouse functionality, but I don't understand what it is.

Gamepad and Joystick support should be implemented here.
Of course, src/machine/usb_descriptor.go must also be changed.

Based on my optimistic view, I believe that all implementations can be done with only the following Interface.
Ah, but it does not handle the data received from InterruptOut, so that will need to be added in the future.

func callback() {
if done := Keyboard.callback(); done {
return
}
if done := Mouse.callback(); done {
return
}
}
func sendUSBPacket(b []byte) {
machine.SendUSBHIDPacket(hidEndpoint, b)
}

src/machine/usb/midi

I have not been able to confirm much yet, but after the enumeration is done, BulkIn and BulkOut need to be implemented.
I don't know how much functionality midi has, but if it is just for serial communication, it is not too difficult.

src/machine/usb/msd

I am not sure what I need for msd.
Perhaps BulkIn and BulkOut should be implemented.
And we will need to handle File and Dir according to the MSD way.
I think we should then implement the io/fs interface.

@deadprogram
Copy link
Member

@sago35 I was imagining something more like this: https://gist.github.com/deadprogram/4e55c3d0e663295839dd66f6517d8aa4

@sago35
Copy link
Member Author

sago35 commented Jun 10, 2022

@deadprogram

I would like to complete USB enumeration as soon as possible, so I think it is better not to have the hid.Enable(kb) part.
(This is because if enumeration is not completed quickly, USBCDC will be unavailable for a longer period of time)
It is better to have the enumeration fixed when the import is done.

keyboard.NewKeyboard() might be good.
But perhaps keyboard.New() or hid.NewKeyboard() would be better.

package main

import (
	"machine"
	"machine/usb/hid"
	"time"
)

func main() {
	button := machine.BUTTON
	button.Configure(machine.PinConfig{Mode: machine.PinInputPullup})

	kb := hid.NewKeyboard()
	mouse := hid.NewMouse()

	for {
		if !button.Get() {
			kb.Write([]byte("tinygo"))
			mouse.Move(0, 0)
			time.Sleep(200 * time.Millisecond)
		}
	}
}

@deadprogram
Copy link
Member

I also thought we should put the different devices into their own package folders, e.g.

"machine/usb/hid/keyboard"
"machine/usb/hid/mouse"

@deadprogram
Copy link
Member

Lastly, would be good to use an array of handlers. For example (not syntax checked or anything):

type hidDevicer interface {
    callback()
}

var devices [5]hidDevicer
var size int

func SetCallbackHandler(d hidDevicer) {
    callbacks[size] = d
    size++
}

func PerformCallbacks() {
    for(i := 0; i < size; i++ {
        if done := devices[i].callback(); done {
		return
	}
    }
}

Then

@sago35
Copy link
Member Author

sago35 commented Jun 10, 2022

@deadprogram
Package structure has been changed.
Certainly a good suggestion for the future.

@deadprogram
Copy link
Member

Tested on my Circuit Playground Express, and I had to change the example to this for it to work:

package main

import (
	"machine"
	"machine/usb/hid/keyboard"
	"time"
)

func main() {
	button := machine.BUTTON
	button.Configure(machine.PinConfig{Mode: machine.PinInputPulldown})

	kb := keyboard.New()

	for {
		if button.Get() {
			kb.Write([]byte("tinygo"))
			time.Sleep(200 * time.Millisecond)
		}
	}
}

Otherwise it was amazing!

@deadprogram
Copy link
Member

I think if we correct these small items, and squash commits, we can merge this @sago35

@sago35
Copy link
Member Author

sago35 commented Jun 11, 2022

I made a squash commit.
Thank you for your review.

@deadprogram
Copy link
Member

Thank you for all the work on this PR @sago35 very exciting!

Now merging.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants