From 1cae96951f59e7549ff1a2d5643cda66784c22a1 Mon Sep 17 00:00:00 2001 From: Nathan Conrad Date: Sat, 14 Sep 2019 12:13:11 -0400 Subject: [PATCH] Add usbtmc class driver. --- examples/device/usbtmc/Makefile | 12 + examples/device/usbtmc/src/main.c | 113 +++++ examples/device/usbtmc/src/tusb_config.h | 66 +++ examples/device/usbtmc/src/usb_descriptors.c | 254 +++++++++++ examples/device/usbtmc/src/usbtmc_app.c | 133 ++++++ examples/rules.mk | 1 + src/class/usbtmc/usbtmc.h | 267 ++++++++++++ src/class/usbtmc/usbtmc_device.c | 420 +++++++++++++++++++ src/class/usbtmc/usbtmc_device.h | 130 ++++++ src/device/usbd.c | 16 + src/tusb.h | 4 + src/tusb_option.h | 4 + 12 files changed, 1420 insertions(+) create mode 100644 examples/device/usbtmc/Makefile create mode 100644 examples/device/usbtmc/src/main.c create mode 100644 examples/device/usbtmc/src/tusb_config.h create mode 100644 examples/device/usbtmc/src/usb_descriptors.c create mode 100644 examples/device/usbtmc/src/usbtmc_app.c create mode 100644 src/class/usbtmc/usbtmc.h create mode 100644 src/class/usbtmc/usbtmc_device.c create mode 100644 src/class/usbtmc/usbtmc_device.h diff --git a/examples/device/usbtmc/Makefile b/examples/device/usbtmc/Makefile new file mode 100644 index 00000000..69b633fe --- /dev/null +++ b/examples/device/usbtmc/Makefile @@ -0,0 +1,12 @@ +include ../../../tools/top.mk +include ../../make.mk + +INC += \ + src \ + $(TOP)/hw \ + +# Example source +EXAMPLE_SOURCE += $(wildcard src/*.c) +SRC_C += $(addprefix $(CURRENT_PATH)/, $(EXAMPLE_SOURCE)) + +include ../../rules.mk diff --git a/examples/device/usbtmc/src/main.c b/examples/device/usbtmc/src/main.c new file mode 100644 index 00000000..0293261a --- /dev/null +++ b/examples/device/usbtmc/src/main.c @@ -0,0 +1,113 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2019 Ha Thach (tinyusb.org) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + */ + +#include +#include +#include + +#include "bsp/board.h" +#include "tusb.h" + +//--------------------------------------------------------------------+ +// MACRO CONSTANT TYPEDEF PROTYPES +//--------------------------------------------------------------------+ + +/* Blink pattern + * - 250 ms : device not mounted + * - 1000 ms : device mounted + * - 2500 ms : device is suspended + */ +enum { + BLINK_NOT_MOUNTED = 250, + BLINK_MOUNTED = 1000, + BLINK_SUSPENDED = 2500, +}; + +static uint32_t blink_interval_ms = BLINK_NOT_MOUNTED; + +void led_blinking_task(void); + +/*------------- MAIN -------------*/ +int main(void) +{ + board_init(); + + tusb_init(); + + while (1) + { + tud_task(); // tinyusb device task + led_blinking_task(); + } + + return 0; +} + +//--------------------------------------------------------------------+ +// Device callbacks +//--------------------------------------------------------------------+ + +// Invoked when device is mounted +void tud_mount_cb(void) +{ + blink_interval_ms = BLINK_MOUNTED; +} + +// Invoked when device is unmounted +void tud_umount_cb(void) +{ + blink_interval_ms = BLINK_NOT_MOUNTED; +} + +// Invoked when usb bus is suspended +// remote_wakeup_en : if host allow us to perform remote wakeup +// Within 7ms, device must draw an average of current less than 2.5 mA from bus +void tud_suspend_cb(bool remote_wakeup_en) +{ + (void) remote_wakeup_en; + blink_interval_ms = BLINK_SUSPENDED; +} + +// Invoked when usb bus is resumed +void tud_resume_cb(void) +{ + blink_interval_ms = BLINK_MOUNTED; +} + +//--------------------------------------------------------------------+ +// BLINKING TASK +//--------------------------------------------------------------------+ +void led_blinking_task(void) +{ + static uint32_t start_ms = 0; + static bool led_state = false; + + // Blink every interval ms + if ( board_millis() - start_ms < blink_interval_ms) return; // not enough time + start_ms += blink_interval_ms; + + board_led_write(led_state); + led_state = 1 - led_state; // toggle +} diff --git a/examples/device/usbtmc/src/tusb_config.h b/examples/device/usbtmc/src/tusb_config.h new file mode 100644 index 00000000..022dc769 --- /dev/null +++ b/examples/device/usbtmc/src/tusb_config.h @@ -0,0 +1,66 @@ +/* + * tusb_config.h + * + * Created on: Sep 5, 2019 + * Author: nconrad + */ + +#ifndef TUSB_CONFIG_H_ +#define TUSB_CONFIG_H_ + +#ifdef __cplusplus + extern "C" { +#endif + +//-------------------------------------------------------------------- +// COMMON CONFIGURATION +//-------------------------------------------------------------------- + +// defined by compiler flags for flexibility +#ifndef CFG_TUSB_MCU + #error CFG_TUSB_MCU must be defined +#endif + +#if CFG_TUSB_MCU == OPT_MCU_LPC43XX || CFG_TUSB_MCU == OPT_MCU_LPC18XX +#define CFG_TUSB_RHPORT0_MODE (OPT_MODE_DEVICE | OPT_MODE_HIGH_SPEED) +#else +#define CFG_TUSB_RHPORT0_MODE OPT_MODE_DEVICE +#endif + +#define CFG_TUSB_OS OPT_OS_NONE + +// CFG_TUSB_DEBUG is defined by compiler in DEBUG build +// #define CFG_TUSB_DEBUG 0 + +/* USB DMA on some MCUs can only access a specific SRAM region with restriction on alignment. + * Tinyusb use follows macros to declare transferring memory so that they can be put + * into those specific section. + * e.g + * - CFG_TUSB_MEM SECTION : __attribute__ (( section(".usb_ram") )) + * - CFG_TUSB_MEM_ALIGN : __attribute__ ((aligned(4))) + */ +#ifndef CFG_TUSB_MEM_SECTION +#define CFG_TUSB_MEM_SECTION +#endif + +#ifndef CFG_TUSB_MEM_ALIGN +#define CFG_TUSB_MEM_ALIGN __attribute__ ((aligned(4))) +#endif + +//-------------------------------------------------------------------- +// DEVICE CONFIGURATION +//-------------------------------------------------------------------- + +#define CFG_TUD_ENDOINT0_SIZE 64 + +//------------- CLASS -------------// + +#define CFG_TUD_USBTMC 1 +#define CFG_TUD_USBTMC_ENABLE_INT_EP +//#define USBTMC_CFG_ENABLE_488 0 + +#ifdef __cplusplus + } +#endif + +#endif /* TUSB_CONFIG_H_ */ diff --git a/examples/device/usbtmc/src/usb_descriptors.c b/examples/device/usbtmc/src/usb_descriptors.c new file mode 100644 index 00000000..29663635 --- /dev/null +++ b/examples/device/usbtmc/src/usb_descriptors.c @@ -0,0 +1,254 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2019 Ha Thach (tinyusb.org) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + */ + +#include "tusb.h" +#include "class/usbtmc/usbtmc.h" +#include "class/usbtmc/usbtmc_device.h" + +/* A combination of interfaces must have a unique product id, since PC will save device driver after the first plug. + * Same VID/PID with different interface e.g MSC (first), then CDC (later) will possibly cause system error on PC. + * + * Auto ProductID layout's Bitmap: + * [MSB] HID | MSC | CDC [LSB] + */ +#define _PID_MAP(itf, n) ( (CFG_TUD_##itf) << (n) ) +#define USB_PID (0x4000 | _PID_MAP(CDC, 0) | _PID_MAP(MSC, 1) | _PID_MAP(HID, 2) | \ + _PID_MAP(MIDI, 3) | _PID_MAP(VENDOR, 4) ) + +//--------------------------------------------------------------------+ +// Device Descriptors +//--------------------------------------------------------------------+ +tusb_desc_device_t const desc_device = +{ + .bLength = sizeof(tusb_desc_device_t), + .bDescriptorType = TUSB_DESC_DEVICE, + .bcdUSB = 0x0200, + + #if CFG_TUD_CDC + // Use Interface Association Descriptor (IAD) for CDC + // As required by USB Specs IAD's subclass must be common class (2) and protocol must be IAD (1) + .bDeviceClass = TUSB_CLASS_MISC, + .bDeviceSubClass = MISC_SUBCLASS_COMMON, + .bDeviceProtocol = MISC_PROTOCOL_IAD, + #else + .bDeviceClass = 0x00, + .bDeviceSubClass = 0x00, + .bDeviceProtocol = 0x00, + #endif + + .bMaxPacketSize0 = CFG_TUD_ENDOINT0_SIZE, + + .idVendor = 0xCafe, + .idProduct = USB_PID, + .bcdDevice = 0x0100, + + .iManufacturer = 0x01, + .iProduct = 0x02, + .iSerialNumber = 0x03, + + .bNumConfigurations = 0x01 +}; + +// Invoked when received GET DEVICE DESCRIPTOR +// Application return pointer to descriptor +uint8_t const * tud_descriptor_device_cb(void) +{ + return (uint8_t const *) &desc_device; +} + +//--------------------------------------------------------------------+ +// HID Report Descriptor +//--------------------------------------------------------------------+ +#if CFG_TUD_HID + +uint8_t const desc_hid_report[] = +{ + TUD_HID_REPORT_DESC_KEYBOARD( HID_REPORT_ID(REPORT_ID_KEYBOARD), ), + TUD_HID_REPORT_DESC_MOUSE ( HID_REPORT_ID(REPORT_ID_MOUSE), ) +}; + +// Invoked when received GET HID REPORT DESCRIPTOR +// Application return pointer to descriptor +// Descriptor contents must exist long enough for transfer to complete +uint8_t const * tud_hid_descriptor_report_cb(void) +{ + return desc_hid_report; +} + +#endif + +//--------------------------------------------------------------------+ +// Configuration Descriptor +//--------------------------------------------------------------------+ + +#if defined(CFG_TUD_USBTMC) + +# define USBTMC_DESC_MAIN(_itfnum,_bNumEndpoints) \ + USBTMC_IF_DESCRIPTOR(_itfnum, _bNumEndpoints, /*_stridx = */ 7u, USBTMC_PROTOCOL_USB488), \ + USBTMC_BULK_DESCRIPTORS(/* OUT = */0x03, /* IN = */ 0x83) + +#if defined(CFG_TUD_USBTMC_ENABLE_INT_EP) + +# define USBTMC_DESC(_itfnum) \ + USBTMC_DESC_MAIN(_itfnum, /* _epCount = */ 3), \ + USBTMC_INT_DESCRIPTOR(/* INT ep # */ 0x84, /* epMaxSize = */ 64, /* bInterval = */16u ) +# define USBTMC_DESC_LEN (USBTMC_IF_DESCRIPTOR_LEN + USBTMC_BULK_DESCRIPTORS_LEN + USBTMC_INT_DESCRIPTOR_LEN) + +#else + +# define USBTMC_DESC(_itfnum) \ + USBTMC_DESC_MAIN(_itfnum, /* _epCount = */ 2u) +# define USBTMC_DESC_LEN (USBTMC_IF_DESCRIPTOR_LEN + USBTMC_BULK_DESCRIPTORS_LEN) + +#endif /* CFG_TUD_USBTMC_ENABLE_INT_EP */ + +#else +# define USBTMC_DESC_LEN (0) +#endif /* CFG_TUD_USBTMC */ + +enum +{ +#if CFG_TUD_CDC + ITF_NUM_CDC = 0, + ITF_NUM_CDC_DATA, +#endif + +#if CFG_TUD_MSC + ITF_NUM_MSC, +#endif + +#if CFG_TUD_HID + ITF_NUM_HID, +#endif +#if CFG_TUD_USBTMC + ITF_NUM_USBTMC, +#endif + ITF_NUM_TOTAL +}; + + +#define CONFIG_TOTAL_LEN (TUD_CONFIG_DESC_LEN + CFG_TUD_CDC*TUD_CDC_DESC_LEN + CFG_TUD_MSC*TUD_MSC_DESC_LEN + \ + CFG_TUD_HID*TUD_HID_DESC_LEN + (CFG_TUD_USBTMC)*USBTMC_DESC_LEN) + +#if CFG_TUSB_MCU == OPT_MCU_LPC175X_6X || CFG_TUSB_MCU == OPT_MCU_LPC177X_8X || CFG_TUSB_MCU == OPT_MCU_LPC40XX + // LPC 17xx and 40xx endpoint type (bulk/interrupt/iso) are fixed by its number + // 0 control, 1 In, 2 Bulk, 3 Iso, 4 In etc ... + // Note: since CDC EP ( 1 & 2), HID (4) are spot-on, thus we only need to force + // endpoint number for MSC to 5 + #define EPNUM_MSC 0x05 +#else + #define EPNUM_MSC 0x03 +#endif + + +uint8_t const desc_configuration[] = +{ + // Interface count, string index, total length, attribute, power in mA + TUD_CONFIG_DESCRIPTOR(ITF_NUM_TOTAL, 0, CONFIG_TOTAL_LEN, TUSB_DESC_CONFIG_ATT_REMOTE_WAKEUP, 100), + +#if CFG_TUD_CDC + // Interface number, string index, EP notification address and size, EP data address (out, in) and size. + TUD_CDC_DESCRIPTOR(ITF_NUM_CDC, 1, 0x81, 8, 0x02, 0x82, 64), +#endif + +#if CFG_TUD_USBTMC + USBTMC_DESC(ITF_NUM_USBTMC), +#endif + +#if CFG_TUD_MSC + // Interface number, string index, EP Out & EP In address, EP size + TUD_MSC_DESCRIPTOR(ITF_NUM_MSC, 5, EPNUM_MSC, 0x80 | EPNUM_MSC, (CFG_TUSB_RHPORT0_MODE & OPT_MODE_HIGH_SPEED) ? 512 : 64), +#endif + +#if CFG_TUD_HID + // Interface number, string index, protocol, report descriptor len, EP In address, size & polling interval + TUD_HID_DESCRIPTOR(ITF_NUM_HID, 6, HID_PROTOCOL_NONE, sizeof(desc_hid_report), 0x84, 16, 10) +#endif +}; + + +// Invoked when received GET CONFIGURATION DESCRIPTOR +// Application return pointer to descriptor +// Descriptor contents must exist long enough for transfer to complete +uint8_t const * tud_descriptor_configuration_cb(uint8_t index) +{ + (void) index; // for multiple configurations + return desc_configuration; +} + +//--------------------------------------------------------------------+ +// String Descriptors +//--------------------------------------------------------------------+ + +// array of pointer to string descriptors +char const* string_desc_arr [] = +{ + (const char[]) { 0x09, 0x04 }, // 0: is supported language is English (0x0409) + "TinyUSB", // 1: Manufacturer + "TinyUSB Device", // 2: Product + "123456", // 3: Serials, should use chip ID + "TinyUSB CDC", // 4: CDC Interface + "TinyUSB MSC", // 5: MSC Interface + "TinyUSB HID", // 6: HID + "TinyUSB USBTMC", // 7: USBTMC +}; + +static uint16_t _desc_str[32]; + +// Invoked when received GET STRING DESCRIPTOR request +// Application return pointer to descriptor, whose contents must exist long enough for transfer to complete +uint16_t const* tud_descriptor_string_cb(uint8_t index) +{ + uint8_t chr_count; + + if ( index == 0) + { + memcpy(&_desc_str[1], string_desc_arr[0], 2); + chr_count = 1; + }else + { + // Convert ASCII string into UTF-16 + + if ( !(index < sizeof(string_desc_arr)/sizeof(string_desc_arr[0])) ) return NULL; + + const char* str = string_desc_arr[index]; + + // Cap at max char + chr_count = strlen(str); + if ( chr_count > 31 ) { + chr_count = 31; + } + + for(uint8_t i=0; i +#include "class/usbtmc/usbtmc_device.h" + +#if (USBTMC_CFG_ENABLE_488) +usbtmc_response_capabilities_488_t const +#else +usbtmc_response_capabilities_t const +#endif +usbtmcd_app_capabilities = +{ + .USBTMC_status = USBTMC_STATUS_SUCCESS, + .bcdUSBTMC = USBTMC_VERSION, + .bmIntfcCapabilities = + { + .listenOnly = 0, + .talkOnly = 0, + .supportsIndicatorPulse = 0 + }, + .bmDevCapabilities = { + .canEndBulkInOnTermChar = 0 + }, + +#if (USBTMC_CFG_ENABLE_488) + .bcdUSB488 = USBTMC_488_VERSION, + .bmIntfcCapabilities488 = + { + .supportsTrigger = 0, + .supportsREN_GTL_LLO = 0, + .is488_2 = 1 + }, + .bmDevCapabilities488 = + { + .SCPI = 1, + .SR1 = 0, + .RL1 = 0, + .DT1 =0, + } +#endif +}; + +static const char idn[] = "TinyUSB,ModelNumber,SerialNumber,FirmwareVer"; +static uint8_t status; +static bool queryReceived = false; + + +bool usbtmcd_app_msgBulkOut_start(usbtmc_msg_request_dev_dep_out const * msgHeader) +{ + (void)msgHeader; + return true; +} + + +bool usbtmcd_app_msg_data(void *data, size_t len, bool transfer_complete) +{ + (void)transfer_complete; + if(transfer_complete && (len >=4) && !strncasecmp("*idn?",data,4)) { + queryReceived = true; + } + return true; +} + +bool usbtmcd_app_msgBulkIn_complete(uint8_t rhport) +{ + (void)rhport; + return true; +} + +static uint8_t noQueryMsg[] = "ERR: No query"; +bool usbtmcd_app_msgBulkIn_request(uint8_t rhport, usbtmc_msg_request_dev_dep_in const * request) +{ + usbtmc_msg_dev_dep_msg_in_header_t hdr = { + .header = + { + .MsgID = request->header.MsgID, + .bTag = request->header.bTag, + .bTagInverse = request->header.bTagInverse + }, + .TransferSize = sizeof(idn)-1, + .bmTransferAttributes = + { + .EOM = 1, + .UsingTermChar = 0 + } + }; + if(queryReceived) + { + usbtmcd_transmit_dev_msg_data(rhport, &hdr, idn); + } + else + { + hdr.TransferSize = sizeof(noQueryMsg)-1; + usbtmcd_transmit_dev_msg_data(rhport, &hdr, noQueryMsg); + } + queryReceived = false; + return true; +} + +// Return status byte, but put the transfer result status code in the rspResult argument. +uint8_t usbtmcd_app_get_stb(uint8_t rhport, uint8_t *rspResult) +{ + (void)rhport; + *rspResult = USBTMC_STATUS_SUCCESS; + // Increment status so that we see different results on each read... + status++; + + return status; +} + diff --git a/examples/rules.mk b/examples/rules.mk index 478a5907..c3daf9da 100644 --- a/examples/rules.mk +++ b/examples/rules.mk @@ -15,6 +15,7 @@ SRC_C += \ src/class/cdc/cdc_device.c \ src/class/hid/hid_device.c \ src/class/midi/midi_device.c \ + src/class/usbtmc/usbtmc_device.c \ src/class/vendor/vendor_device.c \ src/portable/$(VENDOR)/$(CHIP_FAMILY)/dcd_$(CHIP_FAMILY).c diff --git a/src/class/usbtmc/usbtmc.h b/src/class/usbtmc/usbtmc.h new file mode 100644 index 00000000..846c0e38 --- /dev/null +++ b/src/class/usbtmc/usbtmc.h @@ -0,0 +1,267 @@ + +/* + * The MIT License (MIT) + * + * Copyright (c) 2019 N Conrad + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * This file is part of the TinyUSB stack. + */ + +#ifndef _TUSB_USBTMC_H__ +#define _TUSB_USBTMC_H__ + +#include "common/tusb_common.h" + + +/* Implements USBTMC Revision 1.0, April 14, 2003 + + String descriptors must have a "LANGID=0x409"/US English string. + Characters must be 0x20 (' ') to 0x7E ('~') ASCII, + But MUST not contain: "/:?\* + Also must not have leading or trailing space (' ') + Device descriptor must state USB version 0x0200 or greater + + If USB488DeviceCapabilites.D2 = 1 (SR1), then there must be a INT endpoint. +*/ + +#define USBTMC_VERSION 0x0100 +#define USBTMC_488_VERSION 0x0100 + +typedef enum { + USBTMC_MSGID_DEV_DEP_MSG_OUT = 1u, + USBTMC_MSGID_DEV_DEP_MSG_IN = 2u, + USBTMC_MSGID_VENDOR_SPECIFIC_MSG_OUT = 126u, + USBTMC_MSGID_VENDOR_SPECIFIC_IN = 127u, + USBTMC_MSGID_USB488_TRIGGER = 128u, +} usbtmc_msgid_enum; + +/// \brief Message header (For BULK OUT and BULK IN); 4 bytes +typedef struct TU_ATTR_PACKED +{ + uint8_t MsgID ; ///< Message type ID (usbtmc_msgid_enum) + uint8_t bTag ; ///< Transfer ID 1<=bTag<=255 + uint8_t bTagInverse ; ///< Complement of the tag + uint8_t _reserved ; ///< Must be 0x00 +} usbtmc_msg_header_t; + +typedef struct TU_ATTR_PACKED +{ + usbtmc_msg_header_t header; + uint8_t data[8]; +} usbtmc_msg_generic_t; + +/* Uses on the bulk-out endpoint: */ +// Next 8 bytes are message-specific +typedef struct TU_ATTR_PACKED { + usbtmc_msg_header_t header ; ///< Header + uint32_t TransferSize ; ///< Transfer size; LSB first + struct { + uint8_t EOM : 1 ; ///< EOM set on last byte + } bmTransferAttributes; + uint8_t _reserved[3]; +} usbtmc_msg_request_dev_dep_out; + +// Next 8 bytes are message-specific +typedef struct TU_ATTR_PACKED { + usbtmc_msg_header_t header ; ///< Header + uint32_t TransferSize ; ///< Transfer size; LSB first + struct { + uint8_t : 0; + uint8_t TermCharEnabled : 1 ; ///< "The Bulk-IN transfer must terminate on the specified TermChar."; CAPABILITIES must list TermChar + } bmTransferAttributes; + uint8_t TermChar; + uint8_t _reserved[2]; +} usbtmc_msg_request_dev_dep_in; + +/* Bulk-in headers */ + +typedef struct TU_ATTR_PACKED +{ + usbtmc_msg_header_t header; + uint32_t TransferSize; + struct { + uint8_t EOM: 1; ///< Last byte of transfer is the end of the message + uint8_t UsingTermChar: 1; ///< Support TermChar && Request.TermCharEnabled && last char in transfer is TermChar + } bmTransferAttributes; + uint8_t _reserved[3]; +} usbtmc_msg_dev_dep_msg_in_header_t; + + +/* Unsupported vendor things.... Are these ever used?*/ + +typedef struct TU_ATTR_PACKED { + usbtmc_msg_header_t header ; ///< Header + uint32_t TransferSize ; ///< Transfer size; LSB first + uint8_t _reserved[4]; +} usbtmc_msg_request_vendor_specific_out; + + +typedef struct TU_ATTR_PACKED { + usbtmc_msg_header_t header ; ///< Header + uint32_t TransferSize ; ///< Transfer size; LSB first + uint8_t _reserved[4]; +} usbtmc_msg_request_vendor_specific_in; + +// Control request type should use tusb_control_request_t + +/* +typedef struct TU_ATTR_PACKED { + struct { + uint8_t Recipient : 5 ; ///< EOM set on last byte + uint8_t Type : 2 ; ///< EOM set on last byte + uint8_t DirectionToHost : 1 ; ///< 0 is OUT, 1 is IN + } bmRequestType; + uint8_t bRequest ; ///< If bmRequestType.Type = Class, see usmtmc_request_type_enum + uint16_t wValue ; + uint16_t wIndex ; + uint16_t wLength ; // Number of bytes in data stage +} usbtmc_class_specific_control_req; + +*/ +// bulk-in protocol errors +enum { + USBTMC_BULK_IN_ERR_INCOMPLETE_HEADER = 1u, + USBTMC_BULK_IN_ERR_UNSUPPORTED = 2u, + USBTMC_BULK_IN_ERR_BAD_PARAMETER = 3u, + USBTMC_BULK_IN_ERR_DATA_TOO_SHORT = 4u, + USBTMC_BULK_IN_ERR_DATA_TOO_LONG = 5u, +}; +// bult-in halt errors +enum { + USBTMC_BULK_IN_ERR = 1u, ///< receives a USBTMC command message that expects a response while a + /// Bulk-IN transfer is in progress +}; + +typedef enum { + USBTMC_bREQUEST_INITIATE_ABORT_BULK_OUT = 1u, + USBTMC_bREQUEST_CHECK_ABORT_BULK_OUT_STATUS = 2u, + USBTMC_bREQUEST_INITIATE_ABORT_BULK_IN = 3u, + USBTMC_bREQUEST_CHECK_ABORT_BULK_IN_STATUS = 4u, + USBTMC_bREQUEST_INITIATE_CLEAR = 5u, + USBTMC_bREQUEST_CHECK_CLEAR_STATUS = 6u, + USBTMC_bREQUEST_GET_CAPABILITIES = 7u, + + USBTMC_bREQUEST_INDICATOR_PULSE = 64u, // Optional +} usmtmc_request_type_enum; + +typedef enum { + USBTMC488_bREQUEST_READ_STATUS_BYTE = 128u, + USBTMC488_bREQUEST_REN_CONTROL = 160u, + USBTMC488_bREQUEST_GO_TO_LOCAL = 161u, + USBTMC488_bREQUEST_LOCAL_LOCKOUT = 162u, +} usbtmc_request_type_488_enum; + +typedef enum { + USBTMC_STATUS_SUCCESS = 0x01, + USBTMC_STATUS_PENDING = 0x02, + USBTMC_STATUS_FAILED = 0x80, + USBTMC_STATUS_TRANSFER_NOT_IN_PROGRESS = 0x81, + USBTMC_STATUS_SPLIT_NOT_IN_PROGRESS = 0x82, + USBTMC_STATUS_SPLIT_IN_PROGRESS = 0x83 +} usbtmc_status_enum; + +/************************************************************ + * Control Responses + */ + +typedef struct TU_ATTR_PACKED { + uint8_t USBTMC_status; ///< usbtmc_status_enum + uint8_t _reserved; + uint16_t bcdUSBTMC; ///< USBTMC_VERSION + + struct { + uint8_t listenOnly :1; + uint8_t talkOnly :1; + uint8_t supportsIndicatorPulse :1; + } bmIntfcCapabilities; + struct { + uint8_t canEndBulkInOnTermChar :1; + } bmDevCapabilities; + uint8_t _reserved2[6]; + uint8_t _reserved3[12]; +} usbtmc_response_capabilities_t; + +TU_VERIFY_STATIC(sizeof(usbtmc_response_capabilities_t) == 0x18, "struct wrong length"); + +typedef struct TU_ATTR_PACKED +{ + uint8_t USBTMC_status; ///< usbtmc_status_enum + uint8_t _reserved; + uint16_t bcdUSBTMC; ///< USBTMC_VERSION + + struct + { + uint8_t listenOnly :1; + uint8_t talkOnly :1; + uint8_t supportsIndicatorPulse :1; + } bmIntfcCapabilities; + + struct + { + uint8_t canEndBulkInOnTermChar :1; + } bmDevCapabilities; + + uint8_t _reserved2[6]; + uint16_t bcdUSB488; + + struct + { + uint8_t is488_2 :1; + uint8_t supportsREN_GTL_LLO :1; + uint8_t supportsTrigger :1; + } bmIntfcCapabilities488; + + struct + { + uint8_t SCPI :1; + uint8_t SR1 :1; + uint8_t RL1 :1; + uint8_t DT1 :1; + } bmDevCapabilities488; + uint8_t _reserved3[8]; +} usbtmc_response_capabilities_488_t; + +TU_VERIFY_STATIC(sizeof(usbtmc_response_capabilities_488_t) == 0x18, "struct wrong length"); + +typedef struct TU_ATTR_PACKED +{ + uint8_t USBTMC_status; + uint8_t bTag; + uint8_t statusByte; +} usbtmc_read_stb_rsp_488_t; + +TU_VERIFY_STATIC(sizeof(usbtmc_read_stb_rsp_488_t) == 3u, "struct wrong length"); + +typedef struct TU_ATTR_PACKET +{ + union { + struct { + uint8_t bTag : 7; + uint8_t one : 1; + } bNotify1Struct; + uint8_t bNotify1; + }; + uint8_t StatusByte; +} usbtmc_read_stb_interrupt_488_t; +TU_VERIFY_STATIC(sizeof(usbtmc_read_stb_interrupt_488_t) == 2u, "struct wrong length"); + +#endif + diff --git a/src/class/usbtmc/usbtmc_device.c b/src/class/usbtmc/usbtmc_device.c new file mode 100644 index 00000000..4bb21f35 --- /dev/null +++ b/src/class/usbtmc/usbtmc_device.c @@ -0,0 +1,420 @@ +/* + * usbtmc.c + * + * Created on: Sep 9, 2019 + * Author: nconrad + */ + +/* + * The MIT License (MIT) + * + * Copyright (c) 2019 N Conrad + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * This file is part of the TinyUSB stack. + */ + +#include "tusb_option.h" + +// We don't do any cross-task anything here (everything is in tud or interrupt context). +// You must ensure thread safety in your own app. + + +//Limitations (not planned to be implemented): +// "vendor-specific" commands are not handled + +// TODO: +// USBTMC 3.2.2 error conditions not strictly followed +// No local lock-out, REN, or GTL. +// Cannot issue clear. +// No "capabilities" supported +// Interrupt-IN endpoint +// 488 MsgID=Trigger +// Clear message available status byte at the correct time? (488 4.3.1.3) +// Split transfers +// No CLEAR_FEATURE/HALT (yet) + +#if (TUSB_OPT_DEVICE_ENABLED && CFG_TUD_USBTMC) + +#include "usbtmc.h" +#include "usbtmc_device.h" +#include "device/dcd.h" +#include "device/usbd.h" + +// FIXME: I shouldn't need to include _pvt headers. +#include "device/usbd_pvt.h" + +typedef enum +{ + STATE_IDLE, + STATE_RCV, + STATE_TX_REQUESTED, + STATE_TX_INITIATED +} usbtmcd_state_enum; + +typedef struct +{ + usbtmcd_state_enum state; + uint8_t itf_id; + uint8_t ep_bulk_in; + uint8_t ep_bulk_out; + uint8_t ep_int_in; + uint8_t ep_bulk_in_buf[64]; + uint8_t ep_bulk_out_buf[64]; + uint8_t lastTag; + + uint32_t transfer_size_remaining; + uint8_t const * devInBuffer; +} usbtmc_interface_state_t; + +static usbtmc_interface_state_t usbtmc_state = +{ + .state = STATE_IDLE, + .itf_id = 0xFF, + .ep_bulk_in = 0, + .ep_bulk_out = 0, + .ep_int_in = 0 +}; + +// We want everything to fit nicely in a single packet, so lets require EP size >32 +// I'm not sure if this is really necessary, though. +TU_VERIFY_STATIC(USBTMCD_MAX_PACKET_SIZE >= 32u,"USBTMC dev EP packet size too small"); + +// called from app +// We keep a reference to the buffer, so it MUST not change until the app is +// notified that the transfer is complete. +// length of data is specified in the hdr. +bool usbtmcd_transmit_dev_msg_data( + uint8_t rhport, + usbtmc_msg_dev_dep_msg_in_header_t const * hdr, + const void *data) +{ + TU_ASSERT(usbtmc_state.state == STATE_TX_REQUESTED); + TU_ASSERT(hdr->TransferSize > 0u); + + // Copy in the header + memcpy(usbtmc_state.ep_bulk_in_buf, hdr, sizeof(*hdr)); + uint packetLen = sizeof(*hdr); + // Single-packet transfer + if((packetLen + hdr->TransferSize) <= USBTMCD_MAX_PACKET_SIZE) + { + memcpy((uint8_t*)(usbtmc_state.ep_bulk_in_buf) + packetLen, data, hdr->TransferSize); + packetLen = (uint16_t)(packetLen+ hdr->TransferSize); + // Pad up to multiple of 4 bytes + while((packetLen % 4) != 0) + { + usbtmc_state.ep_bulk_in_buf[packetLen] = 0; + packetLen++; + } + usbtmc_state.transfer_size_remaining = 0; + usbtmc_state.devInBuffer = NULL; + } + else + { + memcpy((uint8_t*)(usbtmc_state.ep_bulk_in_buf) + packetLen, data, USBTMCD_MAX_PACKET_SIZE - packetLen); + usbtmc_state.transfer_size_remaining = hdr->TransferSize - (USBTMCD_MAX_PACKET_SIZE - packetLen); + usbtmc_state.devInBuffer += (USBTMCD_MAX_PACKET_SIZE - packetLen); + packetLen = USBTMCD_MAX_PACKET_SIZE; + } + usbtmc_state.state = STATE_TX_INITIATED; + TU_VERIFY( usbd_edpt_xfer(rhport, usbtmc_state.ep_bulk_in, usbtmc_state.ep_bulk_in_buf,(uint16_t)packetLen)); + return true; +} + +void usbtmcd_init(void) +{ + +} + +bool usbtmcd_open(uint8_t rhport, tusb_desc_interface_t const * itf_desc, uint16_t *p_length) +{ + (void)rhport; + uint8_t const * p_desc; + uint8_t found_endpoints = 0; + + // Perhaps there are other application specific class drivers, so don't assert here. + if( itf_desc->bInterfaceClass != USBTMC_APP_CLASS) + return false; + if( itf_desc->bInterfaceSubClass != USBTMC_APP_SUBCLASS) + return false; + + // Only 2 or 3 endpoints are allowed for USBTMC. + TU_ASSERT((itf_desc->bNumEndpoints == 2) || (itf_desc->bNumEndpoints ==3)); + + // Interface + (*p_length) = 0u; + p_desc = (uint8_t const *) itf_desc; + + usbtmc_state.itf_id = itf_desc->bInterfaceNumber; + + while (found_endpoints < itf_desc->bNumEndpoints) + { + if ( TUSB_DESC_ENDPOINT == p_desc[DESC_OFFSET_TYPE]) + { + tusb_desc_endpoint_t const *ep_desc = (tusb_desc_endpoint_t const *)p_desc; + switch(ep_desc->bmAttributes.xfer) { + case TUSB_XFER_BULK: + if (tu_edpt_dir(ep_desc->bEndpointAddress) == TUSB_DIR_IN) + { + usbtmc_state.ep_bulk_in = ep_desc->bEndpointAddress; + } else { + usbtmc_state.ep_bulk_out = ep_desc->bEndpointAddress; + } + + break; + case TUSB_XFER_INTERRUPT: + TU_ASSERT(tu_edpt_dir(ep_desc->bEndpointAddress) == TUSB_DIR_IN); + TU_ASSERT(usbtmc_state.ep_int_in == 0); + usbtmc_state.ep_int_in = ep_desc->bEndpointAddress; + break; + default: + TU_ASSERT(false); + } + TU_VERIFY( dcd_edpt_open(rhport, ep_desc)); + found_endpoints++; + } + (*p_length) = (uint8_t)((*p_length) + p_desc[DESC_OFFSET_LEN]); + p_desc = tu_desc_next(p_desc); + } + + // bulk endpoints are required, but interrupt IN is optional + TU_ASSERT(usbtmc_state.ep_bulk_in != 0); + TU_ASSERT(usbtmc_state.ep_bulk_out != 0); + if (itf_desc->bNumEndpoints == 2) { + TU_ASSERT(usbtmc_state.ep_int_in == 0); + } + else if (itf_desc->bNumEndpoints == 2) + { + TU_ASSERT(usbtmc_state.ep_int_in != 0); + } + TU_VERIFY( usbd_edpt_xfer(rhport, usbtmc_state.ep_bulk_out, usbtmc_state.ep_bulk_out_buf, 64)); + + return true; +} +void usbtmcd_reset(uint8_t rhport) +{ + // FIXME: Do endpoints need to be closed here? + (void)rhport; +} +static bool handle_devMsgOut(uint8_t rhport, void *data, size_t len) +{ + (void)rhport; + bool shortPacket = (len < USBTMCD_MAX_PACKET_SIZE); + if(usbtmc_state.state == STATE_IDLE) + { + // must be a header, should have been confirmed before calling here. + usbtmc_msg_request_dev_dep_out *msg = (usbtmc_msg_request_dev_dep_out*)data; + usbtmc_state.transfer_size_remaining = msg->TransferSize; + TU_VERIFY(usbtmcd_app_msgBulkOut_start(msg)); + len -= sizeof(*msg); + data = (uint8_t*)data + sizeof(*msg); + } + // Packet is to be considered complete when we get enough data or at a short packet. + bool atEnd = false; + if(len >= usbtmc_state.transfer_size_remaining || shortPacket) + atEnd = true; + if(len > usbtmc_state.transfer_size_remaining) + len = usbtmc_state.transfer_size_remaining; + usbtmcd_app_msg_data(data, len, atEnd); + if(atEnd) + usbtmc_state.state = STATE_IDLE; + else + usbtmc_state.state = STATE_RCV; + return true; +} +static bool handle_devMsgIn(uint8_t rhport, void *data, size_t len) +{ + TU_VERIFY(len == sizeof(usbtmc_msg_request_dev_dep_in)); + usbtmc_msg_request_dev_dep_in *msg = (usbtmc_msg_request_dev_dep_in*)data; + TU_VERIFY(usbtmc_state.state == STATE_IDLE); + usbtmc_state.state = STATE_TX_REQUESTED; + usbtmc_state.transfer_size_remaining = msg->TransferSize; + TU_VERIFY(usbtmcd_app_msgBulkIn_request(rhport, msg)); + return true; +} + +bool usbtmcd_xfer_cb(uint8_t rhport, uint8_t ep_addr, xfer_result_t result, uint32_t xferred_bytes) +{ + TU_VERIFY(result == XFER_RESULT_SUCCESS); + if(ep_addr == usbtmc_state.ep_bulk_out) + { + switch(usbtmc_state.state) + { + case STATE_IDLE: + TU_VERIFY(xferred_bytes >= sizeof(usbtmc_msg_generic_t)); + usbtmc_msg_generic_t *msg = (usbtmc_msg_generic_t*)(usbtmc_state.ep_bulk_out_buf); + uint8_t invInvTag = (uint8_t)~(msg->header.bTagInverse); + TU_VERIFY(msg->header.bTag == invInvTag); + TU_VERIFY(msg->header.bTag != 0x00); + usbtmc_state.lastTag = msg->header.bTag; + + switch(msg->header.MsgID) { + case USBTMC_MSGID_DEV_DEP_MSG_OUT: + TU_VERIFY(handle_devMsgOut(rhport, msg, xferred_bytes)); + TU_VERIFY(usbd_edpt_xfer(rhport, usbtmc_state.ep_bulk_out, usbtmc_state.ep_bulk_out_buf, 64)); + break; + case USBTMC_MSGID_DEV_DEP_MSG_IN: + TU_VERIFY(handle_devMsgIn(rhport, msg, xferred_bytes)); + break; + case USBTMC_MSGID_VENDOR_SPECIFIC_MSG_OUT: + case USBTMC_MSGID_VENDOR_SPECIFIC_IN: + case USBTMC_MSGID_USB488_TRIGGER: + default: + TU_VERIFY(false); + } + return true; + + case STATE_RCV: + TU_VERIFY(handle_devMsgOut(rhport, usbtmc_state.ep_bulk_out_buf, xferred_bytes)); + TU_VERIFY( usbd_edpt_xfer(rhport, usbtmc_state.ep_bulk_out, usbtmc_state.ep_bulk_out_buf, 64)); + return true; + break; + + default: + TU_VERIFY(false); + } + } + else if(ep_addr == usbtmc_state.ep_bulk_in) + { + TU_ASSERT(usbtmc_state.state == STATE_TX_INITIATED); + if(usbtmc_state.transfer_size_remaining == 0) + { + usbtmc_state.state = STATE_IDLE; + TU_VERIFY(usbtmcd_app_msgBulkIn_complete(rhport)); + TU_VERIFY( usbd_edpt_xfer(rhport, usbtmc_state.ep_bulk_out, usbtmc_state.ep_bulk_out_buf, 64)); + } + else if(usbtmc_state.transfer_size_remaining >= USBTMCD_MAX_PACKET_SIZE) + { + memcpy(usbtmc_state.ep_bulk_in_buf, usbtmc_state.devInBuffer, USBTMCD_MAX_PACKET_SIZE); + usbtmc_state.devInBuffer += USBTMCD_MAX_PACKET_SIZE; + usbtmc_state.transfer_size_remaining -= USBTMCD_MAX_PACKET_SIZE; + TU_VERIFY( usbd_edpt_xfer(rhport, usbtmc_state.ep_bulk_in, usbtmc_state.ep_bulk_in_buf,USBTMCD_MAX_PACKET_SIZE)); + } + else // short packet + { + uint packetLen = usbtmc_state.transfer_size_remaining; + memcpy(usbtmc_state.ep_bulk_in_buf, usbtmc_state.devInBuffer, usbtmc_state.transfer_size_remaining); + while((packetLen % 4) != 0) + { + usbtmc_state.ep_bulk_in_buf[packetLen] = 0; + packetLen++; + } + usbtmc_state.transfer_size_remaining = 0; + usbtmc_state.devInBuffer = NULL; + TU_VERIFY( usbd_edpt_xfer(rhport, usbtmc_state.ep_bulk_in, usbtmc_state.ep_bulk_in_buf,(uint16_t)packetLen)); + } + return true; + } + else if (ep_addr == usbtmc_state.ep_int_in) { + // Good? + return true; + } + return false; +} + +bool usbtmcd_control_request(uint8_t rhport, tusb_control_request_t const * request) { + +#if (USBTMC_CFG_ENABLE_488) + ushort bTag; +#endif + // We only handle class requests. + if(request->bmRequestType_bit.type != TUSB_REQ_TYPE_CLASS) + return false; + + switch(request->bRequest) + { + // USBTMC required requests + case USBTMC_bREQUEST_INITIATE_ABORT_BULK_OUT: + case USBTMC_bREQUEST_CHECK_ABORT_BULK_OUT_STATUS: + case USBTMC_bREQUEST_INITIATE_ABORT_BULK_IN: + case USBTMC_bREQUEST_CHECK_ABORT_BULK_IN_STATUS: + case USBTMC_bREQUEST_INITIATE_CLEAR: + case USBTMC_bREQUEST_CHECK_CLEAR_STATUS: + TU_VERIFY(false); + break; + + case USBTMC_bREQUEST_GET_CAPABILITIES: + TU_VERIFY(request->bmRequestType == 0xA1); + TU_VERIFY(request->wValue == 0x0000); + TU_VERIFY(request->wIndex == usbtmc_state.itf_id); + TU_VERIFY(request->wLength == sizeof(usbtmcd_app_capabilities)); + TU_VERIFY(tud_control_xfer(rhport, request, (void*)&usbtmcd_app_capabilities, sizeof(usbtmcd_app_capabilities))); + return true; + // USBTMC Optional Requests + case USBTMC_bREQUEST_INDICATOR_PULSE: // Optional + TU_VERIFY(false); + return false; + +#if (USBTMC_CFG_ENABLE_488) + // USB488 required requests + case USBTMC488_bREQUEST_READ_STATUS_BYTE: + + bTag = request->wValue & 0x7F; + TU_VERIFY(request->bmRequestType == 0xA1); + TU_VERIFY((request->wValue & (~0x7F)) == 0u); // Other bits are required to be zero + TU_VERIFY(bTag >= 0x02 && bTag <= 127); + TU_VERIFY(request->wIndex == usbtmc_state.itf_id); + TU_VERIFY(request->wLength == 0x0003); + usbtmc_read_stb_rsp_488_t rsp; + rsp.bTag = (uint8_t)bTag; + if(usbtmc_state.ep_int_in != 0) + { + rsp.USBTMC_status = USBTMC_STATUS_SUCCESS; + rsp.statusByte = 0x00; // Use interrupt endpoint, instead. + + usbtmc_read_stb_interrupt_488_t intMsg = + { + .bNotify1 = (uint8_t)(0x80 | bTag), + .StatusByte = usbtmcd_app_get_stb(rhport, &(rsp.USBTMC_status)) + }; + usbd_edpt_xfer(rhport, usbtmc_state.ep_int_in, (void*)&intMsg,sizeof(intMsg)); + + } + else + { + rsp.statusByte = usbtmcd_app_get_stb(rhport, &(rsp.USBTMC_status)); + } + TU_VERIFY(tud_control_xfer(rhport, request, (void*)&rsp, sizeof(rsp))); + return true; + + // USB488 optional requests + case USBTMC488_bREQUEST_REN_CONTROL: + case USBTMC488_bREQUEST_GO_TO_LOCAL: + case USBTMC488_bREQUEST_LOCAL_LOCKOUT: + TU_VERIFY(false); + return false; +#endif + + default: + TU_VERIFY(false); + } + TU_VERIFY(false); +} + +bool usbtmcd_control_complete(uint8_t rhport, tusb_control_request_t const * request) +{ + (void)rhport; + //------------- Class Specific Request -------------// + TU_VERIFY (request->bmRequestType_bit.type == TUSB_REQ_TYPE_CLASS); + + return true; +} + +#endif /* CFG_TUD_TSMC */ diff --git a/src/class/usbtmc/usbtmc_device.h b/src/class/usbtmc/usbtmc_device.h new file mode 100644 index 00000000..c03ca946 --- /dev/null +++ b/src/class/usbtmc/usbtmc_device.h @@ -0,0 +1,130 @@ +/* + * usbtmc_device.h + * + * Created on: Sep 10, 2019 + * Author: nconrad + */ +/* + * The MIT License (MIT) + * + * Copyright (c) 2019 N Conrad + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * This file is part of the TinyUSB stack. + */ + + +#ifndef CLASS_USBTMC_USBTMC_DEVICE_H_ +#define CLASS_USBTMC_USBTMC_DEVICE_H_ + +#include "usbtmc.h" + +// Enable 488 mode by default +#if !defined(USBTMC_CFG_ENABLE_488) +#define USBTMC_CFG_ENABLE_488 (1) +#endif + +// USB spec says that full-speed must be 8,16,32, or 64. +// However, this driver implementation requires it to be >=32 +#define USBTMCD_MAX_PACKET_SIZE (64u) + +/*********************************************** + * Functions to be implemeted by the class implementation + */ + +#if (USBTMC_CFG_ENABLE_488) +extern usbtmc_response_capabilities_488_t const usbtmcd_app_capabilities; +#else +extern usbtmc_response_capabilities_t const usbtmcd_app_capabilities; +#endif + +bool usbtmcd_app_msgBulkOut_start(usbtmc_msg_request_dev_dep_out const * msgHeader); + +// transfer_complete does not imply that a message is complete. +bool usbtmcd_app_msg_data(void *data, size_t len, bool transfer_complete); + +bool usbtmcd_app_msgBulkIn_request(uint8_t rhport, usbtmc_msg_request_dev_dep_in const * request); + +bool usbtmcd_app_msgBulkIn_complete(uint8_t rhport); + +#if (USBTMC_CFG_ENABLE_488) +uint8_t usbtmcd_app_get_stb(uint8_t rhport, uint8_t *rspResult); + +//TU_ATTR_WEAK bool usbtmcd_app_go_to_local(uint8_t rhport); +#endif + +/******************************************* + * Called from app + * + * We keep a reference to the buffer, so it MUST not change until the app is + * notified that the transfer is complete. + ******************************************/ + +bool usbtmcd_transmit_dev_msg_data( + uint8_t rhport, + usbtmc_msg_dev_dep_msg_in_header_t const * hdr, + const void *data); + + +/* "callbacks" from USB device core */ + +bool usbtmcd_open(uint8_t rhport, tusb_desc_interface_t const * itf_desc, uint16_t *p_length); +void usbtmcd_reset(uint8_t rhport); +bool usbtmcd_xfer_cb(uint8_t rhport, uint8_t ep_addr, xfer_result_t result, uint32_t xferred_bytes); +bool usbtmcd_control_request(uint8_t rhport, tusb_control_request_t const * request); +bool usbtmcd_control_complete(uint8_t rhport, tusb_control_request_t const * request); +void usbtmcd_init(void); + +/************************************************************ + * USBTMC Descriptor Templates + *************************************************************/ + +#define USBTMC_APP_CLASS TUSB_CLASS_APPLICATION_SPECIFIC +#define USBTMC_APP_SUBCLASS 0x03 + +#define USBTMC_PROTOCOL_STD 0x00 +#define USBTMC_PROTOCOL_USB488 0x01 + +// Interface number, number of endpoints, EP string index, USB_TMC_PROTOCOL*, bulk-out endpoint ID, +// bulk-in endpoint ID +#define USBTMC_IF_DESCRIPTOR(_itfnum, _bNumEndpoints, _stridx, _itfProtocol) \ +/* Interface */ \ + 0x09, TUSB_DESC_INTERFACE, _itfnum, 0x00, _bNumEndpoints, USBTMC_APP_CLASS, USBTMC_APP_SUBCLASS, _itfProtocol, _stridx + +#define USBTMC_IF_DESCRIPTOR_LEN 9u + +// bulk-out Size must be a multiple of 4 bytes +#define USBTMC_BULK_DESCRIPTORS(_epout, _epin) \ +/* Endpoint Out */ \ +7, TUSB_DESC_ENDPOINT, _epout, TUSB_XFER_BULK, U16_TO_U8S_LE(USBTMCD_MAX_PACKET_SIZE), 0u, \ +/* Endpoint In */ \ +7, TUSB_DESC_ENDPOINT, _epin, TUSB_XFER_BULK, U16_TO_U8S_LE(USBTMCD_MAX_PACKET_SIZE), 0u + +#define USBTMC_BULK_DESCRIPTORS_LEN (7u+7u) + +/* optional interrupt endpoint */ \ +// _int_pollingInterval : for LS/FS, expressed in frames (1ms each). 16 may be a good number? +#define USBTMC_INT_DESCRIPTOR(_ep_interrupt, _ep_interrupt_size, _int_pollingInterval ) \ +7, TUSB_DESC_ENDPOINT, _ep_interrupt, TUSB_XFER_INTERRUPT, U16_TO_U8S_LE(_ep_interrupt_size), 0x16 + +#define USBTMC_INT_DESCRIPTOR_LEN (7u) + + +#endif /* CLASS_USBTMC_USBTMC_DEVICE_H_ */ diff --git a/src/device/usbd.c b/src/device/usbd.c index c9288433..572708f7 100644 --- a/src/device/usbd.c +++ b/src/device/usbd.c @@ -145,6 +145,22 @@ static usbd_class_driver_t const usbd_class_drivers[] = .sof = NULL }, #endif + + #if CFG_TUD_USBTMC + // Presently USBTMC is the only defined class with the APP_SPECIFIC class code. + // We maybe need to add subclass codes here, or a callback to ask if a driver can + // handle a particular interface. + { + .class_code = TUSB_CLASS_APPLICATION_SPECIFIC, + .init = usbtmcd_init, + .reset = usbtmcd_reset, + .open = usbtmcd_open, + .control_request = usbtmcd_control_request, + .control_complete = usbtmcd_control_complete, + .xfer_cb = usbtmcd_xfer_cb, + .sof = NULL + }, + #endif }; enum { USBD_CLASS_DRIVER_COUNT = TU_ARRAY_SIZE(usbd_class_drivers) }; diff --git a/src/tusb.h b/src/tusb.h index fe867372..1a6ff0b1 100644 --- a/src/tusb.h +++ b/src/tusb.h @@ -83,6 +83,10 @@ #if CFG_TUD_VENDOR #include "class/vendor/vendor_device.h" #endif + + #if CFG_TUD_USBTMC + #include "class/usbtmc/usbtmc_device.h" + #endif #endif diff --git a/src/tusb_option.h b/src/tusb_option.h index 9aacb558..76ea5975 100644 --- a/src/tusb_option.h +++ b/src/tusb_option.h @@ -181,6 +181,10 @@ #define CFG_TUD_VENDOR 0 #endif +#ifndef CFG_TUD_USBTMC + #define CFG_TUD_USBTMC 0 +#endif + //-------------------------------------------------------------------- // HOST OPTIONS