From 9818707c06da80e0dde764f7146304c3fe31f4b0 Mon Sep 17 00:00:00 2001 From: Noah Metz Date: Thu, 9 Jul 2026 13:30:02 -0600 Subject: [PATCH] Added USB Serial interface and HID Lighting stubs --- cfg/halconf.h | 2 +- main.c | 120 ++++++++++++++++- source/usbcfg.c | 332 +++++++++++++++++++++++++++++++++++++----------- source/usbcfg.h | 28 ++-- 4 files changed, 382 insertions(+), 100 deletions(-) diff --git a/cfg/halconf.h b/cfg/halconf.h index 6cbc4cd..3807352 100644 --- a/cfg/halconf.h +++ b/cfg/halconf.h @@ -149,7 +149,7 @@ * @brief Enables the SERIAL over USB subsystem. */ #if !defined(HAL_USE_SERIAL_USB) || defined(__DOXYGEN__) -#define HAL_USE_SERIAL_USB FALSE +#define HAL_USE_SERIAL_USB TRUE #endif /** diff --git a/main.c b/main.c index 09a569c..d5ccf4c 100644 --- a/main.c +++ b/main.c @@ -1,20 +1,132 @@ -#include #include +#include +#include "ch.h" #include "hal.h" #include "usbcfg.h" +/* + * HID report sizes (data only, excluding the report ID byte prepended by the + * USB host). These match the LampArray HID descriptor in usbcfg.c. + */ +#define RPT1_LEN 22 /* LampArrayAttributesReport */ +#define RPT3_LEN 28 /* LampAttributesResponseReport */ +#define RPT6_LEN 1 /* LampArrayControlReport */ + +/* + * Called from ISR context — fill buf with the response for report `id`. + * buf[0] must be set to the report ID; *len must be set to the total length. + * Return false to stall (unknown ID). + */ +bool isr_hid_get_report(uint8_t id, uint8_t *buf, size_t *len) { + switch (id) { + case 1: /* LampArrayAttributesReport */ + *len = 1 + RPT1_LEN; + memset(buf, 0, *len); + buf[0] = 1; + /* LampCount, BoundingBox*, LampArrayKind, MinUpdateInterval all zero */ + return true; + + case 3: /* LampAttributesResponseReport */ + *len = 1 + RPT3_LEN; + memset(buf, 0, *len); + buf[0] = 3; + /* All lamp attribute fields zero until real lamp state is added */ + return true; + + case 6: /* LampArrayControlReport */ + *len = 1 + RPT6_LEN; + buf[0] = 6; + buf[1] = 0; /* AutonomousMode = 0 (host-controlled) */ + return true; + + default: + return false; + } +} + +/* + * Called from ISR context — process an incoming SET_REPORT payload. + * buf[0] is the report ID; len includes that byte. + */ +void isr_hid_set_report(uint8_t id, const uint8_t *buf, size_t len) { + (void)buf; + (void)len; + switch (id) { + case 2: /* LampAttributesRequestReport — which lamp to query next */ + break; + case 4: /* LampMultiUpdateReport — set up to 8 lamps */ + break; + case 5: /* LampRangeUpdateReport — set a range of lamps */ + break; + case 6: /* LampArrayControlReport — autonomous mode flag */ + break; + default: + break; + } +} + +#define SERIAL_LINE_BUF 128 + +static THD_WORKING_AREA(waSerial, 512); +static THD_FUNCTION(SerialThread, arg) { + (void)arg; + chRegSetThreadName("serial"); + + char buf[SERIAL_LINE_BUF]; + int len; + + while (true) { + while (SDU1.config->usbp->state != USB_ACTIVE || !cdc_dtr) + chThdSleepMilliseconds(100); + + len = 0; + chnWrite((BaseChannel *)&SDU1, (uint8_t *)"> ", 2); + + while (SDU1.config->usbp->state == USB_ACTIVE && cdc_dtr) { + msg_t c = chnGetTimeout((BaseChannel *)&SDU1, TIME_MS2I(100)); + if (c < MSG_OK) + continue; + + uint8_t ch = (uint8_t)c; + + if (ch == '\r' || ch == '\n') { + chnWrite((BaseChannel *)&SDU1, (uint8_t *)"\r\n", 2); + if (len > 0) { + chnWrite((BaseChannel *)&SDU1, (uint8_t *)buf, (size_t)len); + chnWrite((BaseChannel *)&SDU1, (uint8_t *)"\r\n", 2); + len = 0; + } + chnWrite((BaseChannel *)&SDU1, (uint8_t *)"> ", 2); + } else if (ch == 0x08 || ch == 0x7F) { /* backspace / DEL */ + if (len > 0) { + len--; + chnWrite((BaseChannel *)&SDU1, (uint8_t *)"\b \b", 3); + } + } else if (ch >= 0x20 && ch < 0x7F) { /* printable ASCII */ + if (len < SERIAL_LINE_BUF - 1) { + buf[len++] = (char)ch; + chnWrite((BaseChannel *)&SDU1, &ch, 1); + } + } + } + } +} + int main(void) { halInit(); chSysInit(); + sduObjectInit(&SDU1); + sduStart(&SDU1, &serusbcfg); + usbDisconnectBus(&USBD1); chThdSleepMilliseconds(1500); usbStart(&USBD1, &usbcfg); usbConnectBus(&USBD1); - while(true) { - chThdSleepMilliseconds(1000); - } + chThdCreateStatic(waSerial, sizeof(waSerial), NORMALPRIO, SerialThread, NULL); + + chThdSleep(TIME_INFINITE); } diff --git a/source/usbcfg.c b/source/usbcfg.c index 1793e51..a32ba35 100644 --- a/source/usbcfg.c +++ b/source/usbcfg.c @@ -1,16 +1,45 @@ #include "hal.h" -#include "hal_usb.h" +#include "hal_usb_cdc.h" +#include "hal_serial_usb.h" +#include "usbcfg.h" + +SerialUSBDriver SDU1; +volatile bool cdc_dtr = false; + +#define HID_EP 1 +#define CDC_INT_EP 2 +#define CDC_DATA_EP 3 + +#define HID_EP_SIZE 0x0020U +#define CDC_INT_SIZE 0x0010U +#define CDC_DATA_SIZE 0x0040U + +#define HID_IF_DESC_SIZE \ + (USB_DESC_INTERFACE_SIZE + 9U + (USB_DESC_ENDPOINT_SIZE * 2U)) + +#define CDC_FUNC_DESC_SIZE (5U + 5U + 4U + 5U) + +#define CDC_IF_DESC_SIZE \ + (USB_DESC_INTERFACE_SIZE + CDC_FUNC_DESC_SIZE + \ + USB_DESC_ENDPOINT_SIZE + \ + USB_DESC_INTERFACE_SIZE + (USB_DESC_ENDPOINT_SIZE * 2U)) + +#define IAD_CDC_SIZE \ + (USB_DESC_INTERFACE_ASSOCIATION_SIZE + CDC_IF_DESC_SIZE) + +#define CONFIG_TOTAL_SIZE \ + (USB_DESC_CONFIGURATION_SIZE + HID_IF_DESC_SIZE + IAD_CDC_SIZE) /* * USB Device Descriptor. */ static const uint8_t device_descriptor_data[18] = { - USB_DESC_DEVICE (0x0200, /* bcdUSB (2.0). */ - 0x00, /* bDeviceClass (Unknown). */ - 0x00, /* bDeviceSubClass. */ - 0x00, /* bDeviceProtocol. */ + USB_DESC_DEVICE (0x0200, + 0xEF, /* bDeviceClass (Miscellaneous). */ + 0x02, /* bDeviceSubClass (Common). */ + 0x01, /* bDeviceProtocol (IAD). */ 0x40, /* bMaxPacketSize. */ - 0xEDDE, /* idVendor (ST). */ + 0xEDDE, /* idVendor. */ 0x2025, /* idProduct. */ 0x0200, /* bcdDevice. */ 1, /* iManufacturer. */ @@ -19,53 +48,114 @@ static const uint8_t device_descriptor_data[18] = { 1) /* bNumConfigurations. */ }; -/* - * Device Descriptor wrapper. - */ static const USBDescriptor device_descriptor = { sizeof device_descriptor_data, device_descriptor_data }; -/* Configuration Descriptor tree for an HID Lighting Array.*/ -static const uint8_t configuration_descriptor_data[41] = { - /* Configuration Descriptor.*/ - USB_DESC_CONFIGURATION(41, /* wTotalLength. */ - 0x01, /* bNumInterfaces. */ +/* + * Configuration Descriptor tree for HID LampArray + CDC ACM. + */ +static const uint8_t configuration_descriptor_data[] = { + /* Configuration Descriptor */ + USB_DESC_CONFIGURATION(CONFIG_TOTAL_SIZE, + 3, /* bNumInterfaces (HID + CDC ctrl + CDC data). */ 0x01, /* bConfigurationValue. */ 0, /* iConfiguration. */ 0xC0, /* bmAttributes (self powered). */ 50), /* bMaxPower (100mA). */ - /* Interface Descriptor.*/ + + /* HID Interface (0) */ USB_DESC_INTERFACE (0x00, /* bInterfaceNumber. */ 0x00, /* bAlternateSetting. */ 0x02, /* bNumEndpoints. */ 0x03, /* bInterfaceClass (HID). */ - 0x00, /* bInterfaceSubClass */ - 0x00, /* bInterfaceProtocol */ + 0x00, /* bInterfaceSubClass. */ + 0x00, /* bInterfaceProtocol. */ 0), /* iInterface. */ - - /* HID Descriptor (HID Section 6.2.1).*/ + /* HID Class Descriptor */ USB_DESC_BYTE (0x09), /* bLength. */ USB_DESC_BYTE (0x21), /* bDescriptorType (HID). */ - USB_DESC_BCD (0x111), /* bcdHID(1.1) */ - USB_DESC_BYTE (0x00), /* bCountryCode(None) */ - USB_DESC_BYTE (0x01), /* bNumDescriptors */ - USB_DESC_BYTE (0x22), /* bDescriptorType */ - USB_DESC_WORD (292), /* wDescriptorLength */ - - /* Endpoint Descriptors */ - USB_DESC_ENDPOINT (0x81, /* bEndpointAddress */ - 0x03, /* bmAttributes */ - 0x0020, /* wMaxPacketSize. */ - 0xFF), /* bInterval. */ - - USB_DESC_ENDPOINT (0x01, /* bEndpointAddress */ - 0x03, /* bmAttributes */ - 0x0020, /* wMaxPacketSize. */ - 0xFF), /* bInterval. */ + USB_DESC_BCD (0x111), /* bcdHID (1.1). */ + USB_DESC_BYTE (0x00), /* bCountryCode (None). */ + USB_DESC_BYTE (0x01), /* bNumDescriptors. */ + USB_DESC_BYTE (0x22), /* bDescriptorType (Report). */ + USB_DESC_WORD (292), /* wDescriptorLength. */ + + /* HID EP1 IN */ + USB_DESC_ENDPOINT (USB_ENDPOINT_IN(HID_EP), + USB_EP_MODE_TYPE_INTR, + HID_EP_SIZE, + 0xFF), + /* HID EP1 OUT */ + USB_DESC_ENDPOINT (USB_ENDPOINT_OUT(HID_EP), + USB_EP_MODE_TYPE_INTR, + HID_EP_SIZE, + 0xFF), + + /* IAD grouping the two CDC interfaces */ + USB_DESC_INTERFACE_ASSOCIATION( + 0x01, /* bFirstInterface. */ + 2, /* bInterfaceCount. */ + CDC_COMMUNICATION_INTERFACE_CLASS, + CDC_ABSTRACT_CONTROL_MODEL, + 0x01, /* bFunctionProtocol (AT commands). */ + 0), /* iFunction. */ + + /* CDC Control Interface (1) */ + USB_DESC_INTERFACE (0x01, + 0x00, + 0x01, /* bNumEndpoints (interrupt IN). */ + CDC_COMMUNICATION_INTERFACE_CLASS, + CDC_ABSTRACT_CONTROL_MODEL, + 0x01, + 0), + /* Header Functional Descriptor */ + USB_DESC_BYTE(5), USB_DESC_BYTE(CDC_CS_INTERFACE), USB_DESC_BYTE(CDC_HEADER), + USB_DESC_BCD(0x0110), + + /* Call Management Functional Descriptor */ + USB_DESC_BYTE(5), USB_DESC_BYTE(CDC_CS_INTERFACE), USB_DESC_BYTE(CDC_CALL_MANAGEMENT), + USB_DESC_BYTE(0x03), /* bmCapabilities. */ + USB_DESC_BYTE(0x02), /* bDataInterface. */ + + /* Abstract Control Management Functional Descriptor */ + USB_DESC_BYTE(4), USB_DESC_BYTE(CDC_CS_INTERFACE), + USB_DESC_BYTE(CDC_ABSTRACT_CONTROL_MANAGEMENT), + USB_DESC_BYTE(0x02), /* bmCapabilities. */ + + /* Union Functional Descriptor */ + USB_DESC_BYTE(5), USB_DESC_BYTE(CDC_CS_INTERFACE), USB_DESC_BYTE(CDC_UNION), + USB_DESC_BYTE(0x01), /* bMasterInterface. */ + USB_DESC_BYTE(0x02), /* bSlaveInterface. */ + + /* CDC Interrupt IN EP2 */ + USB_DESC_ENDPOINT (USB_ENDPOINT_IN(CDC_INT_EP), + USB_EP_MODE_TYPE_INTR, + CDC_INT_SIZE, + 0x01), + + /* CDC Data Interface (2) */ + USB_DESC_INTERFACE (0x02, + 0x00, + 0x02, /* bNumEndpoints (bulk IN + OUT). */ + CDC_DATA_INTERFACE_CLASS, + 0x00, + 0x00, + 0), + + /* CDC Bulk OUT EP3 */ + USB_DESC_ENDPOINT (USB_ENDPOINT_OUT(CDC_DATA_EP), + USB_EP_MODE_TYPE_BULK, + CDC_DATA_SIZE, + 0x00), + /* CDC Bulk IN EP3 */ + USB_DESC_ENDPOINT (USB_ENDPOINT_IN(CDC_DATA_EP), + USB_EP_MODE_TYPE_BULK, + CDC_DATA_SIZE, + 0x00), }; static const USBDescriptor configuration_descriptor = { @@ -221,48 +311,32 @@ static const USBDescriptor hid_report_descriptor = { hid_report_descriptor_data }; -/* - * U.S. English language identifier. - */ static const uint8_t usb_string0[] = { - USB_DESC_BYTE(4), /* bLength. */ - USB_DESC_BYTE(USB_DESCRIPTOR_STRING), /* bDescriptorType. */ - USB_DESC_WORD(0x0409) /* wLANGID (U.S. English). */ + USB_DESC_BYTE(4), + USB_DESC_BYTE(USB_DESCRIPTOR_STRING), + USB_DESC_WORD(0x0409) }; -/* - * Vendor string. - */ static const uint8_t usb_string1[] = { - USB_DESC_BYTE(16), /* bLength. */ - USB_DESC_BYTE(USB_DESCRIPTOR_STRING), /* bDescriptorType. */ + USB_DESC_BYTE(16), + USB_DESC_BYTE(USB_DESCRIPTOR_STRING), 'M', 0, 'e', 0, 't', 0, 'z', 0, 'N', 0, 'e', 0, 't', 0 }; -/* - * Device Description string. - */ static const uint8_t usb_string2[] = { - USB_DESC_BYTE(30), /* bLength. */ - USB_DESC_BYTE(USB_DESCRIPTOR_STRING), /* bDescriptorType. */ + USB_DESC_BYTE(30), + USB_DESC_BYTE(USB_DESCRIPTOR_STRING), 'T', 0, 'w', 0, 'i', 0, 'n', 0, 'k', 0, 'l', 0, 'e', 0, 'S', 0, 't', 0, 'i', 0, 'c', 0, 'k', 0, 'V', 0, '2', 0 }; -/* - * Serial Number string. - */ static const uint8_t usb_string3[] = { - USB_DESC_BYTE(8), /* bLength. */ - USB_DESC_BYTE(USB_DESCRIPTOR_STRING), /* bDescriptorType. */ - '0' + CH_KERNEL_MAJOR, 0, - '0' + CH_KERNEL_MINOR, 0, - '0' + CH_KERNEL_PATCH, 0 + USB_DESC_BYTE(2 + 12*2), /* bLength */ + USB_DESC_BYTE(USB_DESCRIPTOR_STRING), + 'T', 0, 'w', 0, 'i', 0, 'n', 0, 'k', 0, 'l', 0, + 'e', 0, 'S', 0, 't', 0, 'i', 0, 'c', 0, 'k', 0 }; -/* - * Strings wrappers array. - */ static const USBDescriptor usb_strings[] = { {sizeof usb_string0, usb_string0}, {sizeof usb_string1, usb_string1}, @@ -270,7 +344,8 @@ static const USBDescriptor usb_strings[] = { {sizeof usb_string3, usb_string3} }; -static const USBDescriptor *get_descriptor(USBDriver *usbp, uint8_t dtype, uint8_t dindex, uint16_t lang) { +static const USBDescriptor *get_descriptor(USBDriver *usbp, uint8_t dtype, + uint8_t dindex, uint16_t lang) { (void)usbp; (void)lang; switch (dtype) { @@ -288,24 +363,108 @@ static const USBDescriptor *get_descriptor(USBDriver *usbp, uint8_t dtype, uint8 return NULL; } +#define HID_GET_REPORT 0x01U +#define HID_SET_REPORT 0x09U +#define HID_SET_IDLE 0x0AU + +#define HID_REPORT_BUF_SIZE 64U + +static uint8_t hid_get_buf[HID_REPORT_BUF_SIZE]; +static uint8_t hid_set_buf[HID_REPORT_BUF_SIZE]; + +static struct { + uint8_t id; + uint16_t len; +} hid_set_ctx; + +static void hid_set_cb(USBDriver *usbp) { + (void)usbp; + isr_hid_set_report(hid_set_ctx.id, hid_set_buf, hid_set_ctx.len); +} + +/* EP1: HID interrupt IN/OUT */ static USBInEndpointState ep1instate; static USBOutEndpointState ep1outstate; static const USBEndpointConfig ep1config = { + USB_EP_MODE_TYPE_INTR, + NULL, NULL, NULL, + HID_EP_SIZE, HID_EP_SIZE, + &ep1instate, &ep1outstate, + 1, NULL +}; + +/* EP2: CDC interrupt IN (notifications) */ +static USBInEndpointState ep2instate; +static const USBEndpointConfig ep2config = { USB_EP_MODE_TYPE_INTR, NULL, + sduInterruptTransmitted, NULL, + CDC_INT_SIZE, 0x0000, + &ep2instate, NULL, + 1, NULL +}; + +/* EP3: CDC bulk IN/OUT (data) */ +static USBInEndpointState ep3instate; +static USBOutEndpointState ep3outstate; +static const USBEndpointConfig ep3config = { + USB_EP_MODE_TYPE_BULK, NULL, - 0x0020, - 0x0020, - &ep1instate, - &ep1outstate, - 1, - NULL + sduDataTransmitted, + sduDataReceived, + CDC_DATA_SIZE, CDC_DATA_SIZE, + &ep3instate, &ep3outstate, + 1, NULL }; static bool usb_request(USBDriver *usbp) { - (void)usbp; - return false; + uint8_t rtype = usbp->setup[0]; + uint8_t req = usbp->setup[1]; + + if ((rtype & USB_RTYPE_RECIPIENT_MASK) == USB_RTYPE_RECIPIENT_INTERFACE && + req == USB_REQ_SET_INTERFACE) { + usbSetupTransfer(usbp, NULL, 0, NULL); + return true; + } + + /* HID class requests targeted at interface 0 */ + if ((rtype & (USB_RTYPE_TYPE_MASK | USB_RTYPE_RECIPIENT_MASK)) == + (USB_RTYPE_TYPE_CLASS | USB_RTYPE_RECIPIENT_INTERFACE) && + usbp->setup[4] == 0x00U) { + + uint8_t report_id = usbp->setup[2]; + uint16_t wlength = (uint16_t)usbp->setup[6] | + ((uint16_t)usbp->setup[7] << 8); + + switch (req) { + case HID_GET_REPORT: { + size_t len = HID_REPORT_BUF_SIZE; + if (!isr_hid_get_report(report_id, hid_get_buf, &len)) + return false; + usbSetupTransfer(usbp, hid_get_buf, + len < wlength ? len : wlength, NULL); + return true; + } + case HID_SET_REPORT: + hid_set_ctx.id = report_id; + hid_set_ctx.len = wlength < HID_REPORT_BUF_SIZE + ? wlength : HID_REPORT_BUF_SIZE; + usbSetupTransfer(usbp, hid_set_buf, hid_set_ctx.len, hid_set_cb); + return true; + case HID_SET_IDLE: + usbSetupTransfer(usbp, NULL, 0, NULL); + return true; + default: + return false; + } + } + + if ((rtype & USB_RTYPE_TYPE_MASK) == USB_RTYPE_TYPE_CLASS && + req == CDC_SET_CONTROL_LINE_STATE) + cdc_dtr = (usbp->setup[2] & 0x01U) != 0U; + + return sduRequestsHook(usbp); } static void usb_event(USBDriver *usbp, usbevent_t event) { @@ -314,27 +473,39 @@ static void usb_event(USBDriver *usbp, usbevent_t event) { return; case USB_EVENT_CONFIGURED: chSysLockFromISR(); - - usbInitEndpointI(usbp, 0x01, &ep1config); - + if (usbp->state == USB_ACTIVE) { + usbInitEndpointI(usbp, HID_EP, &ep1config); + usbInitEndpointI(usbp, CDC_INT_EP, &ep2config); + usbInitEndpointI(usbp, CDC_DATA_EP, &ep3config); + sduConfigureHookI(&SDU1); + } else if (usbp->state == USB_SELECTED) { + usbDisableEndpointsI(usbp); + } chSysUnlockFromISR(); return; case USB_EVENT_RESET: - /* Falls into.*/ case USB_EVENT_UNCONFIGURED: - /* Falls into.*/ case USB_EVENT_SUSPEND: + cdc_dtr = false; + chSysLockFromISR(); + sduSuspendHookI(&SDU1); + chSysUnlockFromISR(); return; case USB_EVENT_WAKEUP: + chSysLockFromISR(); + sduWakeupHookI(&SDU1); + chSysUnlockFromISR(); return; case USB_EVENT_STALLED: return; } - return; } static void sof_handler(USBDriver *usbp) { (void)usbp; + osalSysLockFromISR(); + sduSOFHookI(&SDU1); + osalSysUnlockFromISR(); } const USBConfig usbcfg = { @@ -343,3 +514,10 @@ const USBConfig usbcfg = { usb_request, sof_handler }; + +const SerialUSBConfig serusbcfg = { + &USBD1, + CDC_DATA_EP, /* bulk_in */ + CDC_DATA_EP, /* bulk_out */ + CDC_INT_EP /* int_in */ +}; diff --git a/source/usbcfg.h b/source/usbcfg.h index aee5679..49c4c9f 100644 --- a/source/usbcfg.h +++ b/source/usbcfg.h @@ -1,24 +1,16 @@ -/* - ChibiOS - Copyright (C) 2006..2018 Giovanni Di Sirio - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ - #ifndef USBCFG_H #define USBCFG_H extern const USBConfig usbcfg; +extern const SerialUSBConfig serusbcfg; +extern SerialUSBDriver SDU1; +extern volatile bool cdc_dtr; + +/* Called from ISR context to fill a GET_REPORT response. + Returns false to stall the request (unknown report id). */ +bool isr_hid_get_report(uint8_t id, uint8_t *buf, size_t *len); -#endif /* USBCFG_H */ +/* Called from ISR context when a SET_REPORT payload has arrived. */ +void isr_hid_set_report(uint8_t id, const uint8_t *buf, size_t len); -/** @} */ +#endif /* USBCFG_H */