Skip to content

Commit

Permalink
Add USB HID example
Browse files Browse the repository at this point in the history
  • Loading branch information
TiltMeSenpai committed Aug 9, 2020
1 parent a850cc1 commit 0cf3410
Show file tree
Hide file tree
Showing 2 changed files with 162 additions and 0 deletions.
39 changes: 39 additions & 0 deletions examples/usb/hid.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
#!/usr/bin/env python3
#
# This file is part of LUNA.
#
# Copyright (c) 2020 Great Scott Gadgets <[email protected]>
# SPDX-License-Identifier: BSD-3-Clause

from nmigen import Elaboratable, Module, Signal

from luna import top_level_cli
from luna.gateware.usb.devices.hid import HIDDevice

class USBHIDExample(Elaboratable):
def elaborate(self, platform):
m = Module()

# Generate our domain clocks/resets.
m.submodules.car = platform.clock_domain_generator()

# Create the 32-bit counter we'll be using as our status signal.
counter = Signal(32)
m.d.usb += counter.eq(counter + 1)

# Create our USB device interface...
ulpi = platform.request(platform.default_usb_connection)
m.submodules.hid = hid = \
HIDDevice(bus=ulpi, idVendor=0x1337, idProduct=0x1337)

# Connect counter as a pollable report
hid.add_input(counter)

m.d.comb += [
hid.connect.eq(1)
]

return m

if __name__ == "__main__":
top_level_cli(USBHIDExample)
123 changes: 123 additions & 0 deletions luna/gateware/usb/devices/hid.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
from nmigen import Elaboratable, Module, Signal, Cat

from ...stream import StreamInterface
from ..usb2.device import USBDevice
from ..usb2.request import USBRequestHandler, StallOnlyRequestHandler
from ..usb2.endpoints.status import USBSignalInEndpoint

from usb_protocol.types import USBRequestType,USBTransferType
from usb_protocol.emitters import DeviceDescriptorCollection
from usb_protocol.emitters.descriptors.hid import HIDDescriptor, HIDPrefixes




class HIDDevice(Elaboratable):

_STATUS_ENDPOINT_NUMBER = 1

def __init__(self, *, bus, idVendor, idProduct,
manufacturer_string="LUNA",
product_string="HID Register Reader",
serial_number=None, max_packet_size=64,
usage_page=0x01, usage=0x0):

self._bus = bus
self._idVendor = idVendor
self._idProduct = idProduct
self._manufacturer_string = manufacturer_string
self._product_string = product_string
self._serial_number = serial_number
self._max_packet_size = max_packet_size
self._usage_page = usage_page
self._usage = usage

#
# I/O port
#
self.connect = Signal()
self.inputs = []

def _int_to_le_bytes(self, i):
for n in (1, 2, 4):
try:
return int.to_bytes(i, n, byteorder="little")
except OverflowError:
pass
raise OverflowError("Value cannot be represented in 4 bytes")

def add_input(self, signal, input_range=None, usage=0x0):
if(input_range == None):
shape = signal.shape()
if(shape.signed):
input_range = range(-(1 << (shape.width - 1)) + 1, 1 << (shape.width - 1))
else:
input_range = range(0, (1 << shape.width) - 1)
self.inputs.append((signal, input_range, usage))


def create_descriptors(self):
descriptors = DeviceDescriptorCollection()
with descriptors.DeviceDescriptor() as d:
d.idVendor = self._idVendor
d.idProduct = self._idProduct

d.iManufacturer = self._manufacturer_string
d.iProduct = self._product_string
d.iSerialNumber = self._serial_number

d.bNumConfigurations = 1
with descriptors.ConfigurationDescriptor() as c:
with c.InterfaceDescriptor() as i:
i.bInterfaceNumber = 0

i.bInterfaceClass = 0x03 # HID
i.bInterfaceSubclass = 0x00 # No SubClass
i.bInterfaceProtocol = 0x00 # No Protocol

i.iInterface = 0x00

hid_header = HIDDescriptor(descriptors)
hid_header.add_report(HIDPrefixes.USAGE_PAGE, self._usage_page)
hid_header.add_report(HIDPrefixes.USAGE, self._usage)
hid_header.add_report(HIDPrefixes.COLLECTION, 0x01)
for (signal, input_range, usage) in self.inputs:
hid_header.add_report(HIDPrefixes.LOGICAL_MIN, *self._int_to_le_bytes(input_range.start))
hid_header.add_report(HIDPrefixes.LOGICAL_MAX, *self._int_to_le_bytes(input_range.stop))
hid_header.add_report(HIDPrefixes.REPORT_SIZE, *self._int_to_le_bytes(signal.shape().width))
hid_header.add_report(HIDPrefixes.REPORT_COUNT, 0x01),
hid_header.add_report(HIDPrefixes.USAGE, usage)
hid_header.add_input()
hid_header.add_report(HIDPrefixes.END_COLLECTION)

i.add_subordinate_descriptor(hid_header)

with i.EndpointDescriptor() as e:
e.bEndpointAddress = 0x80 | self._STATUS_ENDPOINT_NUMBER
e.bmAttributes = USBTransferType.INTERRUPT
e.wMaxPacketSize = self._max_packet_size
e.bInterval = 1

return descriptors

def elaborate(self, platform):
m = Module()

# Create our core USB device, and add a standard control endpoint.
m.submodules.usb = usb = USBDevice(bus=self._bus)
_control_ep = usb.add_standard_control_endpoint(self.create_descriptors())

# Cram all the registers into a single report
statuses = Cat([signal for (signal, _input_range, _usage) in self.inputs])

# Create an endpoint to emit our report every time we get polled
status_ep = USBSignalInEndpoint(width=statuses.shape().width, endpoint_number=1, endianness="little")
usb.add_endpoint(status_ep)

# Connect our USB device
m.d.comb += [
status_ep.signal.eq(statuses),
usb.connect .eq(self.connect)
]

return m

0 comments on commit 0cf3410

Please sign in to comment.