diff --git a/examples/stm32/f0/other/usb_hid_sensor/Makefile b/examples/stm32/f0/other/usb_hid_sensor/Makefile new file mode 100644 index 00000000..a094fe91 --- /dev/null +++ b/examples/stm32/f0/other/usb_hid_sensor/Makefile @@ -0,0 +1,23 @@ +## +## This file is part of the libopencm3 project. +## +## Copyright (C) 2009 Uwe Hermann +## +## This library is free software: you can redistribute it and/or modify +## it under the terms of the GNU Lesser General Public License as published by +## the Free Software Foundation, either version 3 of the License, or +## (at your option) any later version. +## +## This library is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +## GNU Lesser General Public License for more details. +## +## You should have received a copy of the GNU Lesser General Public License +## along with this library. If not, see . +## + +BINARY = usbhid + +include ../../Makefile.include + diff --git a/examples/stm32/f0/other/usb_hid_sensor/README.md b/examples/stm32/f0/other/usb_hid_sensor/README.md new file mode 100644 index 00000000..f4466fb6 --- /dev/null +++ b/examples/stm32/f0/other/usb_hid_sensor/README.md @@ -0,0 +1,13 @@ +# README + +This example implements a USB HID Sensor device. It creates a custom device +which can be found in sysfs: + +``` +# cd /sys/bus/platform/drivers/hid_sensor_custom/HID-SENSOR-2000e1.1.auto +# echo 1 > enable_sensor +# cat input-0-200544/input-0-200544-value +``` + +See also: https://www.kernel.org/doc/html/latest/hid/hid-sensor.html + diff --git a/examples/stm32/f0/other/usb_hid_sensor/usbhid.c b/examples/stm32/f0/other/usb_hid_sensor/usbhid.c new file mode 100644 index 00000000..69de8153 --- /dev/null +++ b/examples/stm32/f0/other/usb_hid_sensor/usbhid.c @@ -0,0 +1,365 @@ +/* + * This file is part of the libopencm3 project. + * + * Copyright (C) 2010 Gareth McMullin + * Copyright (C) 2020 Stefan Agner + * + * This library is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this library. If not, see . + */ + +#include +#include +#include +#include +#include +#include +#include + +/* Define this to include the DFU APP interface. */ +#define INCLUDE_DFU_INTERFACE + +#ifdef INCLUDE_DFU_INTERFACE +#include +#include +#endif + +#include "usbhidsensor.h" + +static usbd_device *usbd_dev; + +const struct usb_device_descriptor dev_descr = { + .bLength = USB_DT_DEVICE_SIZE, + .bDescriptorType = USB_DT_DEVICE, + .bcdUSB = 0x0200, + .bDeviceClass = 0, + .bDeviceSubClass = 0, + .bDeviceProtocol = 0, + .bMaxPacketSize0 = 64, + .idVendor = 0x0483, + .idProduct = 0x5710, + .bcdDevice = 0x0200, + .iManufacturer = 1, + .iProduct = 2, + .iSerialNumber = 3, + .bNumConfigurations = 1, +}; + +static struct hid_sensor_feature { + uint32_t interval; +} __attribute__((packed)) hid_report_feature = { + .interval = 32, +}; + +static struct hid_sensor_input { + uint8_t bReportId; + uint32_t value1; + uint32_t value2; +} __attribute__((packed)) hid_report_input; + +static const uint8_t hid_report_descriptor[] = { + 0x05, 0x20, + 0x09, 0xE1, + + 0xA1, 0x00, + + /* Feature report (transmit) */ + 0x05, 0x20, + 0x0A, 0x0E, 0x03, + 0x15, 0, + 0x27, 0xFF, 0xFF, 0xFF, 0xFF, + 0x75, 32, + 0x95, 1, + 0x55, 0, + 0xB1, 0x02, + + /* Input reports (transmit) */ + 0x05, 0x20, + 0x0A, 0x44, 0x05, + 0x17, 0x00, 0x00, 0x00, 0x00, + 0x27, 0xFF, 0xFF, 0xFF, 0xFF, + 0x75, 32, + 0x95, 1, + 0x66, 0x21, 0xD1, + 0x55, 0x00, + 0x81, 0x02, + + 0x0A, 0x45, 0x05, + 0x17, 0x00, 0x00, 0x00, 0x00, + 0x27, 0xFF, 0xFF, 0xFF, 0xFF, + 0x75, 32, + 0x95, 1, + 0x66, 0x21, 0xD1, + 0x55, 0x00, + 0x81, 0x02, + + /* + * This additional byte is required, otherwise Linux reports: + * usb 1-2.1.1.2: input irq status -75 received + * Off by 1 somewhere? + */ + 0x0A, 0x46, 0x05, + 0x15, 0x00, + 0x25, 0xFF, + 0x75, 8, + 0x95, 1, + 0x55, 0x00, + 0x81, 0x02, + + 0xC0 +}; + +static const struct { + struct usb_hid_descriptor hid_descriptor; + struct { + uint8_t bReportDescriptorType; + uint16_t wDescriptorLength; + } __attribute__((packed)) hid_report; +} __attribute__((packed)) hid_function = { + .hid_descriptor = { + .bLength = sizeof(hid_function), + .bDescriptorType = USB_DT_HID, + .bcdHID = 0x0100, + .bCountryCode = 0, + .bNumDescriptors = 1, + }, + .hid_report = { + .bReportDescriptorType = USB_DT_REPORT, + .wDescriptorLength = sizeof(hid_report_descriptor), + } +}; + +const struct usb_endpoint_descriptor hid_endpoint = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + .bEndpointAddress = 0x81, + .bmAttributes = USB_ENDPOINT_ATTR_INTERRUPT, + .wMaxPacketSize = 12, + .bInterval = 0x20, +}; + +const struct usb_interface_descriptor hid_iface = { + .bLength = USB_DT_INTERFACE_SIZE, + .bDescriptorType = USB_DT_INTERFACE, + .bInterfaceNumber = 0, + .bAlternateSetting = 0, + .bNumEndpoints = 1, + .bInterfaceClass = USB_CLASS_HID, + .bInterfaceSubClass = 1, /* boot */ + .bInterfaceProtocol = 2, /* mouse */ + .iInterface = 0, + + .endpoint = &hid_endpoint, + + .extra = &hid_function, + .extralen = sizeof(hid_function), +}; + +#ifdef INCLUDE_DFU_INTERFACE +const struct usb_dfu_descriptor dfu_function = { + .bLength = sizeof(struct usb_dfu_descriptor), + .bDescriptorType = DFU_FUNCTIONAL, + .bmAttributes = USB_DFU_CAN_DOWNLOAD | USB_DFU_WILL_DETACH, + .wDetachTimeout = 255, + .wTransferSize = 1024, + .bcdDFUVersion = 0x011A, +}; + +const struct usb_interface_descriptor dfu_iface = { + .bLength = USB_DT_INTERFACE_SIZE, + .bDescriptorType = USB_DT_INTERFACE, + .bInterfaceNumber = 1, + .bAlternateSetting = 0, + .bNumEndpoints = 0, + .bInterfaceClass = 0xFE, + .bInterfaceSubClass = 1, + .bInterfaceProtocol = 1, + .iInterface = 0, + + .extra = &dfu_function, + .extralen = sizeof(dfu_function), +}; +#endif + +const struct usb_interface ifaces[] = {{ + .num_altsetting = 1, + .altsetting = &hid_iface, +#ifdef INCLUDE_DFU_INTERFACE +}, { + .num_altsetting = 1, + .altsetting = &dfu_iface, +#endif +}}; + +const struct usb_config_descriptor config = { + .bLength = USB_DT_CONFIGURATION_SIZE, + .bDescriptorType = USB_DT_CONFIGURATION, + .wTotalLength = 0, +#ifdef INCLUDE_DFU_INTERFACE + .bNumInterfaces = 2, +#else + .bNumInterfaces = 1, +#endif + .bConfigurationValue = 1, + .iConfiguration = 0, + .bmAttributes = 0xC0, + .bMaxPower = 0x32, + + .interface = ifaces, +}; + +static const char *usb_strings[] = { + "Black Sphere Technologies", + "HID Demo", + "DEMO", +}; + +/* Buffer to be used for control requests. */ +uint8_t usbd_control_buffer[128]; + +static enum usbd_request_return_codes hid_control_request(usbd_device *dev, struct usb_setup_data *req, uint8_t **buf, uint16_t *len, + void (**complete)(usbd_device *, struct usb_setup_data *)) +{ + (void)complete; + (void)dev; + + if((req->bmRequestType != 0x81) || + (req->bRequest != USB_REQ_GET_DESCRIPTOR) || + (req->wValue != 0x2200)) + return USBD_REQ_NOTSUPP; + + /* Handle the HID report descriptor. */ + *buf = (uint8_t *)hid_report_descriptor; + *len = sizeof(hid_report_descriptor); + + return USBD_REQ_HANDLED; +} +static enum usbd_request_return_codes hid_report_request(usbd_device *dev, struct usb_setup_data *req, uint8_t **buf, uint16_t *len, + void (**complete)(usbd_device *, struct usb_setup_data *)) +{ + (void)complete; + (void)dev; + + if((req->bmRequestType != 0xa1) || + (req->bRequest != USB_HID_REQ_TYPE_GET_REPORT) || + (req->wValue != 0x0300)) { + return USBD_REQ_NOTSUPP; + } + + /* Handle the HID feature report */ + *buf = (uint8_t *)&hid_report_feature; + *len = sizeof(hid_report_feature); + + return USBD_REQ_HANDLED; +} + +#ifdef INCLUDE_DFU_INTERFACE +static void dfu_detach_complete(usbd_device *dev, struct usb_setup_data *req) +{ + (void)req; + (void)dev; + + gpio_mode_setup(GPIOA, GPIO_MODE_OUTPUT, + GPIO_PUPD_NONE, GPIO10); + gpio_set(GPIOA, GPIO10); + scb_reset_system(); +} + +static enum usbd_request_return_codes dfu_control_request(usbd_device *dev, struct usb_setup_data *req, uint8_t **buf, uint16_t *len, + void (**complete)(usbd_device *, struct usb_setup_data *)) +{ + (void)buf; + (void)len; + (void)dev; + + if ((req->bmRequestType != 0x21) || (req->bRequest != DFU_DETACH)) + return USBD_REQ_NOTSUPP; /* Only accept class request. */ + + *complete = dfu_detach_complete; + + return USBD_REQ_HANDLED; +} +#endif + +static void hid_set_config(usbd_device *dev, uint16_t wValue) +{ + (void)wValue; + (void)dev; + + usbd_ep_setup(dev, 0x81, USB_ENDPOINT_ATTR_INTERRUPT, 12, NULL); + + usbd_register_control_callback( + dev, + USB_REQ_TYPE_STANDARD | USB_REQ_TYPE_INTERFACE, + USB_REQ_TYPE_TYPE | USB_REQ_TYPE_RECIPIENT, + hid_control_request); + + usbd_register_control_callback( + dev, + USB_REQ_TYPE_CLASS | USB_REQ_TYPE_INTERFACE, + USB_REQ_TYPE_TYPE | USB_REQ_TYPE_RECIPIENT, + hid_report_request); + +#ifdef INCLUDE_DFU_INTERFACE + usbd_register_control_callback( + dev, + USB_REQ_TYPE_CLASS | USB_REQ_TYPE_INTERFACE, + USB_REQ_TYPE_TYPE | USB_REQ_TYPE_RECIPIENT, + dfu_control_request); +#endif + + systick_set_clocksource(STK_CSR_CLKSOURCE_EXT); + /* SysTick interrupt every N clock pulses: set reload to N-1 */ + systick_set_reload(99999); + systick_interrupt_enable(); + systick_counter_enable(); +} + +int main(void) +{ + rcc_clock_setup_in_hsi_out_48mhz(); + + rcc_periph_clock_enable(RCC_GPIOA); + /* + * This is a somewhat common cheap hack to trigger device re-enumeration + * on startup. Assuming a fixed external pullup on D+, (For USB-FS) + * setting the pin to output, and driving it explicitly low effectively + * "removes" the pullup. The subsequent USB init will "take over" the + * pin, and it will appear as a proper pullup to the host. + * The magic delay is somewhat arbitrary, no guarantees on USBIF + * compliance here, but "it works" in most places. + */ + gpio_mode_setup(GPIOA, GPIO_MODE_OUTPUT, + GPIO_PUPD_NONE, GPIO12); + gpio_clear(GPIOA, GPIO12); + for (unsigned i = 0; i < 800000; i++) { + __asm__("nop"); + } + + usbd_dev = usbd_init(&st_usbfs_v2_usb_driver, &dev_descr, &config, usb_strings, 3, usbd_control_buffer, sizeof(usbd_control_buffer)); + usbd_register_set_config_callback(usbd_dev, hid_set_config); + + while (1) + usbd_poll(usbd_dev); +} + +void sys_tick_handler(void) +{ + static unsigned int value = 123; + + value++; + hid_report_input.value1 = value; + hid_report_input.value2 = ~value; + + usbd_ep_write_packet(usbd_dev, 0x81, &hid_report_input, sizeof(hid_report_input)); +} diff --git a/examples/stm32/f0/other/usb_hid_sensor/usbhid.ld b/examples/stm32/f0/other/usb_hid_sensor/usbhid.ld new file mode 100644 index 00000000..164d0e84 --- /dev/null +++ b/examples/stm32/f0/other/usb_hid_sensor/usbhid.ld @@ -0,0 +1,31 @@ +/* + * This file is part of the libopencm3 project. + * + * Copyright (C) 2015 Karl Palsson + * + * This library is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this library. If not, see . + */ + +/* Linker script for STM32F04xz6, 32k flash, 6k RAM. */ + +/* Define memory regions. */ +MEMORY +{ + rom (rx) : ORIGIN = 0x08000000, LENGTH = 32K + ram (rwx) : ORIGIN = 0x20000000, LENGTH = 6K +} + +/* Include the common ld script. */ +INCLUDE cortex-m-generic.ld +