I was workign on some other USBHost extensions and found I needed debug information that was being generated by a Teensy plugged into the USB host port that was output using Serial Emulation to be available. Likewise I wanted the ability to feed data back to that other Teensy. So I decided to create USBHost_t36 implemention to handle the USB types that include Serial Emulation USBSerialEmu class. The USBSerialEmu code was based on the USBSerial code as well as RAWHID. I found that there was a bug in the Receiving of data that was sent by the remote SEREMU output, I would miss the first character Fixed. USBSerial fixed same bug in reading first charactermain
| @@ -0,0 +1,236 @@ | |||
| /* USB EHCI Host for Teensy 3.6 | |||
| * Copyright 2017 Paul Stoffregen (paul@pjrc.com) | |||
| * | |||
| * 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 <Arduino.h> | |||
| #include "USBHost_t36.h" // Read this header first for key info | |||
| //#define SEREMU_PRINT_DEBUG | |||
| void USBSerialEmu::init() | |||
| { | |||
| USBHost::contribute_Transfers(mytransfers, sizeof(mytransfers)/sizeof(Transfer_t)); | |||
| USBHIDParser::driver_ready_for_hid_collection(this); | |||
| } | |||
| hidclaim_t USBSerialEmu::claim_collection(USBHIDParser *driver, Device_t *dev, uint32_t topusage) | |||
| { | |||
| // only claim SerEMU devices currently: 16c0:0486 | |||
| #ifdef SEREMU_PRINT_DEBUG | |||
| USBHDBGSerial.printf("SerEMU Claim: %x:%x usage: %x\n", dev->idVendor, dev->idProduct, topusage); | |||
| #endif | |||
| if (dev->idVendor != 0x16c0) return CLAIM_NO; // NOT PJRC | |||
| if (mydevice != NULL && dev != mydevice) return CLAIM_NO; | |||
| if (usage_) return CLAIM_NO; // Only claim one | |||
| // make sure it is the SEREMU usage | |||
| if (topusage != 0xffc90004) return CLAIM_NO; // Not the SEREMU | |||
| mydevice = dev; | |||
| collections_claimed++; | |||
| usage_ = topusage; | |||
| driver_ = driver; // remember the driver. | |||
| rx_head_ = 0;// receive head | |||
| rx_tail_ = 0;// receive tail | |||
| tx_head_ = 0; | |||
| rx_pipe_size_ = driver->inSize(); | |||
| tx_pipe_size_ = driver->outSize(); | |||
| tx_out_data_pending_ = 0; | |||
| //if (setup.word1 == 0x03000921 && setup.word2 == ((4<<16)|SEREMU_INTERFACE)) { | |||
| tx_buffer_[0] = 0; | |||
| tx_buffer_[1] = 0; | |||
| tx_buffer_[2] = 0; | |||
| tx_buffer_[3] = 0; | |||
| driver_->sendControlPacket( 0x21, 0x9, 0x300, driver_->interfaceNumber(), 4, tx_buffer_); | |||
| return CLAIM_INTERFACE; // We wa | |||
| } | |||
| void USBSerialEmu::disconnect_collection(Device_t *dev) | |||
| { | |||
| if (--collections_claimed == 0) { | |||
| mydevice = NULL; | |||
| usage_ = 0; | |||
| } | |||
| } | |||
| bool USBSerialEmu::hid_process_in_data(const Transfer_t *transfer) | |||
| { | |||
| uint16_t len = transfer->length; | |||
| const uint8_t *buffer = (const uint8_t *)transfer->buffer; | |||
| #ifdef SEREMU_PRINT_DEBUG | |||
| USBHDBGSerial.printf("USBSerialEmu::hid_process_in_data: %x %d: %x %x %x\n", usage_, len, buffer[0], buffer[1], buffer[2]); | |||
| #endif | |||
| const uint8_t *buffer_end = buffer + len -1; | |||
| while ((buffer_end > buffer) && (*buffer_end == 0))buffer_end--; | |||
| // lets trim off the trailing null characters. | |||
| // Now lets move the bytes onto our queue. | |||
| uint16_t tail = rx_tail_; | |||
| while (buffer <= buffer_end) { | |||
| uint16_t new_head = rx_head_ + 1; | |||
| if (new_head == RX_BUFFER_SIZE) new_head = 0; | |||
| if (new_head == tail) break; // we don't have room so bail out. | |||
| rx_buffer_[rx_head_] = *buffer++; | |||
| rx_head_ = new_head; // point off to the new next head. | |||
| } | |||
| return true; | |||
| } | |||
| bool USBSerialEmu::hid_process_out_data(const Transfer_t *transfer) | |||
| { | |||
| #ifdef SEREMU_PRINT_DEBUG | |||
| USBHDBGSerial.printf("USBSerialEmu::hid_process_out_data: %x\n", usage_); | |||
| #endif | |||
| if (tx_out_data_pending_) { | |||
| tx_out_data_pending_--; | |||
| } | |||
| return true; | |||
| } | |||
| bool USBSerialEmu::sendPacket() | |||
| { | |||
| USBHDBGSerial.printf("SEMU: SendPacket\n"); | |||
| if (!driver_) return false; | |||
| if (!driver_->sendPacket(tx_buffer_)) return false; | |||
| tx_out_data_pending_++; | |||
| tx_head_ = 0; | |||
| return true; | |||
| } | |||
| int USBSerialEmu::available(void) | |||
| { | |||
| if (!driver_) return 0; | |||
| uint32_t head = rx_head_; | |||
| uint32_t tail = rx_tail_; | |||
| if (head >= tail) return head - tail; | |||
| return RX_BUFFER_SIZE + head - tail; | |||
| } | |||
| int USBSerialEmu::peek(void) | |||
| { | |||
| if (!driver_) return -1; | |||
| if (rx_head_ == rx_tail_) return -1; | |||
| return rx_buffer_[rx_tail_]; | |||
| } | |||
| int USBSerialEmu::read(void) | |||
| { | |||
| if (!driver_) return -1; | |||
| if (rx_head_ == rx_tail_) return -1; | |||
| int c = rx_buffer_[rx_tail_]; | |||
| if (++rx_tail_ >= RX_BUFFER_SIZE) rx_tail_ = 0; | |||
| return c; | |||
| } | |||
| int USBSerialEmu::availableForWrite() | |||
| { | |||
| if (!driver_) return 0; | |||
| return tx_pipe_size_ - tx_head_ + (2-tx_out_data_pending_)*tx_pipe_size_; | |||
| } | |||
| size_t USBSerialEmu::write(uint8_t c) | |||
| { | |||
| // Single buffer, as our HID device has double buffers. | |||
| if (c >= ' ') USBHDBGSerial.printf("SEMU: %c\n", c); | |||
| else USBHDBGSerial.printf("SEMU: 0x%x\n", c); | |||
| if (!driver_) return 0; | |||
| if (tx_head_ == tx_pipe_size_) { | |||
| while (!sendPacket()) yield(); // wait until the device above queues this packet | |||
| } | |||
| tx_buffer_[tx_head_++] = c; | |||
| // if this character filled it. then try to queue it again | |||
| if (tx_head_ == tx_pipe_size_) sendPacket(); | |||
| driver_->stopTimer(); | |||
| driver_->startTimer(write_timeout_); | |||
| return 1; | |||
| } | |||
| void USBSerialEmu::flush(void) | |||
| { | |||
| if (!driver_) return; | |||
| USBHDBGSerial.printf("SEMU: flush\n"); | |||
| driver_->stopTimer(); // Stop longer timer. | |||
| driver_->startTimer(100); // Start a mimimal timeout | |||
| if (tx_head_) sendPacket(); | |||
| // And wait for HID to say they were all sent. | |||
| elapsedMillis em = 0; | |||
| while (tx_out_data_pending_ && (em < 10000)) yield(); // wait up to 10 seconds? | |||
| } | |||
| void USBSerialEmu::hid_timer_event(USBDriverTimer *whichTimer) | |||
| { | |||
| USBHDBGSerial.printf("SEMU: Timer\n"); | |||
| if (!driver_) return; | |||
| driver_->stopTimer(); | |||
| if (tx_head_) { | |||
| memset(tx_buffer_ + tx_head_, 0, tx_pipe_size_ - tx_head_); // clear the rest of bytes in buffer. | |||
| sendPacket(); | |||
| } | |||
| } | |||
| void USBSerialEmu::hid_input_begin(uint32_t topusage, uint32_t type, int lgmin, int lgmax) | |||
| { | |||
| // These should not be called as we are claiming the whole interface and not | |||
| // allowing the parse to happen | |||
| #ifdef SEREMU_PRINT_DEBUG | |||
| USBHDBGSerial.printf("SerEMU::hid_input_begin %x %x %x %x\n", topusage, type, lgmin, lgmax); | |||
| #endif | |||
| //hid_input_begin_ = true; | |||
| } | |||
| void USBSerialEmu::hid_input_data(uint32_t usage, int32_t value) | |||
| { | |||
| // These should not be called as we are claiming the whole interface and not | |||
| // allowing the parse to happen | |||
| #ifdef SEREMU_PRINT_DEBUG | |||
| USBHDBGSerial.printf("SerEMU: usage=%X, value=%d", usage, value); | |||
| if ((value >= ' ') && (value <='~')) USBHDBGSerial.printf("(%c)", value); | |||
| USBHDBGSerial.println(); | |||
| #endif | |||
| } | |||
| void USBSerialEmu::hid_input_end() | |||
| { | |||
| // These should not be called as we are claiming the whole interface and not | |||
| // allowing the parse to happen | |||
| #ifdef SEREMU_PRINT_DEBUG | |||
| USBHDBGSerial.println("SerEMU::hid_input_end"); | |||
| #endif | |||
| // if (hid_input_begin_) { | |||
| // hid_input_begin_ = false; | |||
| // } | |||
| } | |||
| @@ -99,6 +99,7 @@ typedef enum { CLAIM_NO=0, CLAIM_REPORT, CLAIM_INTERFACE} hidclaim_t; | |||
| // USBHost. | |||
| class USBDriver; | |||
| class USBDriverTimer; | |||
| class USBHIDInput; | |||
| /************************************************/ | |||
| /* Added Defines */ | |||
| @@ -488,6 +489,8 @@ class USBDriverTimer { | |||
| public: | |||
| USBDriverTimer() { } | |||
| USBDriverTimer(USBDriver *d) : driver(d) { } | |||
| USBDriverTimer(USBHIDInput *hd) : driver(nullptr), hidinput(hd) { } | |||
| void init(USBDriver *d) { driver = d; }; | |||
| void start(uint32_t microseconds); | |||
| void stop(); | |||
| @@ -496,6 +499,7 @@ public: | |||
| uint32_t started_micros; // testing only | |||
| private: | |||
| USBDriver *driver; | |||
| USBHIDInput *hidinput; | |||
| uint32_t usec; | |||
| USBDriverTimer *next; | |||
| USBDriverTimer *prev; | |||
| @@ -518,6 +522,7 @@ public: | |||
| const uint8_t *serialNumber() | |||
| { return ((mydevice == nullptr) || (mydevice->strbuf == nullptr)) ? nullptr : &mydevice->strbuf->buffer[mydevice->strbuf->iStrings[strbuf_t::STR_ID_SERIAL]]; } | |||
| private: | |||
| virtual hidclaim_t claim_collection(USBHIDParser *driver, Device_t *dev, uint32_t topusage); | |||
| virtual bool hid_process_in_data(const Transfer_t *transfer) {return false;} | |||
| @@ -526,6 +531,7 @@ private: | |||
| virtual void hid_input_data(uint32_t usage, int32_t value); | |||
| virtual void hid_input_end(); | |||
| virtual void disconnect_collection(Device_t *dev); | |||
| virtual void hid_timer_event(USBDriverTimer *whichTimer) { } | |||
| void add_to_list(); | |||
| USBHIDInput *next = NULL; | |||
| friend class USBHIDParser; | |||
| @@ -658,13 +664,21 @@ private: | |||
| class USBHIDParser : public USBDriver { | |||
| public: | |||
| USBHIDParser(USBHost &host) { init(); } | |||
| USBHIDParser(USBHost &host) : hidTimer(this) { init(); } | |||
| static void driver_ready_for_hid_collection(USBHIDInput *driver); | |||
| bool sendPacket(const uint8_t *buffer, int cb=-1); | |||
| void setTXBuffers(uint8_t *buffer1, uint8_t *buffer2, uint8_t cb); | |||
| bool sendControlPacket(uint32_t bmRequestType, uint32_t bRequest, | |||
| uint32_t wValue, uint32_t wIndex, uint32_t wLength, void *buf); | |||
| // Atempt for RAWhid and SEREMU to take over processing of data | |||
| // | |||
| uint16_t inSize(void) {return in_size;} | |||
| uint16_t outSize(void) {return out_size;} | |||
| void startTimer(uint32_t microseconds) {hidTimer.start(microseconds);} | |||
| void stopTimer() {hidTimer.stop();} | |||
| uint8_t interfaceNumber() { return bInterfaceNumber;} | |||
| protected: | |||
| enum { TOPUSAGE_LIST_LEN = 4 }; | |||
| enum { USAGE_LIST_LEN = 24 }; | |||
| @@ -673,6 +687,7 @@ protected: | |||
| virtual void disconnect(); | |||
| static void in_callback(const Transfer_t *transfer); | |||
| static void out_callback(const Transfer_t *transfer); | |||
| virtual void timer_event(USBDriverTimer *whichTimer); | |||
| void in_data(const Transfer_t *transfer); | |||
| void out_data(const Transfer_t *transfer); | |||
| bool check_if_using_report_id(); | |||
| @@ -681,10 +696,6 @@ protected: | |||
| void parse(uint16_t type_and_report_id, const uint8_t *data, uint32_t len); | |||
| void init(); | |||
| // Atempt for RAWhid to take over processing of data | |||
| // | |||
| uint16_t inSize(void) {return in_size;} | |||
| uint16_t outSize(void) {return out_size;} | |||
| uint8_t activeSendMask(void) {return txstate;} | |||
| @@ -708,6 +719,8 @@ private: | |||
| uint8_t *tx1 = nullptr; | |||
| uint8_t *tx2 = nullptr; | |||
| bool hid_driver_claimed_control_ = false; | |||
| USBDriverTimer hidTimer; | |||
| uint8_t bInterfaceNumber = 0; | |||
| }; | |||
| //-------------------------------------------------------------------------- | |||
| @@ -1758,6 +1771,68 @@ private: | |||
| //-------------------------------------------------------------------------- | |||
| class USBSerialEmu : public USBHIDInput, public Stream { | |||
| public: | |||
| USBSerialEmu(USBHost &host) { init(); } | |||
| uint32_t usage(void) {return usage_;} | |||
| // Stream stuff. | |||
| uint32_t writeTimeout() {return write_timeout_;} | |||
| void writeTimeOut(uint32_t write_timeout) {write_timeout_ = write_timeout;} // Will not impact current ones. | |||
| virtual int available(void); | |||
| virtual int peek(void); | |||
| virtual int read(void); | |||
| virtual int availableForWrite(); | |||
| virtual size_t write(uint8_t c); | |||
| virtual void flush(void); | |||
| using Print::write; | |||
| protected: | |||
| virtual hidclaim_t claim_collection(USBHIDParser *driver, Device_t *dev, uint32_t topusage); | |||
| virtual bool hid_process_in_data(const Transfer_t *transfer); | |||
| virtual bool hid_process_out_data(const Transfer_t *transfer); | |||
| virtual void hid_input_begin(uint32_t topusage, uint32_t type, int lgmin, int lgmax); | |||
| virtual void hid_input_data(uint32_t usage, int32_t value); | |||
| virtual void hid_input_end(); | |||
| virtual void disconnect_collection(Device_t *dev); | |||
| virtual void hid_timer_event(USBDriverTimer *whichTimer); | |||
| bool sendPacket(); | |||
| private: | |||
| void init(); | |||
| USBHIDParser *driver_; | |||
| enum { MAX_PACKET_SIZE = 64 }; | |||
| bool (*receiveCB)(uint32_t usage, const uint8_t *data, uint32_t len) = nullptr; | |||
| uint8_t collections_claimed = 0; | |||
| uint32_t usage_ = 0; | |||
| // We have max of 512 byte packets coming in. How about enough room for 3+3 | |||
| enum { RX_BUFFER_SIZE=1024, TX_BUFFER_SIZE = 512 }; | |||
| enum { DEFAULT_WRITE_TIMEOUT = 3500}; | |||
| uint8_t rx_buffer_[RX_BUFFER_SIZE]; | |||
| uint8_t tx_buffer_[TX_BUFFER_SIZE]; | |||
| volatile uint8_t tx_out_data_pending_ = 0; | |||
| volatile uint16_t rx_head_;// receive head | |||
| volatile uint16_t rx_tail_;// receive tail | |||
| volatile uint16_t tx_head_; | |||
| uint16_t rx_pipe_size_;// size of receive circular buffer | |||
| uint16_t tx_pipe_size_;// size of transmit circular buffer | |||
| uint32_t write_timeout_ = DEFAULT_WRITE_TIMEOUT; | |||
| // See if we can contribute transfers | |||
| Transfer_t mytransfers[2] __attribute__ ((aligned(32))); | |||
| }; | |||
| //-------------------------------------------------------------------------- | |||
| class BluetoothController: public USBDriver { | |||
| public: | |||
| static const uint8_t MAX_CONNECTIONS = 4; | |||
| @@ -2037,5 +2112,4 @@ private: | |||
| bool deviceAvailable = false; | |||
| }; | |||
| #endif | |||
| @@ -55,6 +55,7 @@ bool USBHIDParser::claim(Device_t *dev, int type, const uint8_t *descriptors, ui | |||
| uint32_t numendpoint = descriptors[4]; | |||
| if (numendpoint < 1 || numendpoint > 2) return false; | |||
| if (descriptors[5] != 3) return false; // bInterfaceClass, 3 = HID | |||
| println(" bInterfaceNumber = ", descriptors[2]); | |||
| println(" bInterfaceClass = ", descriptors[5]); | |||
| println(" bInterfaceSubClass = ", descriptors[6]); | |||
| println(" bInterfaceProtocol = ", descriptors[7]); | |||
| @@ -148,6 +149,7 @@ bool USBHIDParser::claim(Device_t *dev, int type, const uint8_t *descriptors, ui | |||
| topusage_drivers[i] = NULL; | |||
| } | |||
| // request the HID report descriptor | |||
| bInterfaceNumber = descriptors[2]; // save away the interface number; | |||
| mk_setup(setup, 0x81, 6, 0x2200, descriptors[2], descsize); // get report desc | |||
| queue_Control_Transfer(dev, &setup, descriptor, this); | |||
| return true; | |||
| @@ -213,10 +215,12 @@ void USBHIDParser::in_data(const Transfer_t *transfer) | |||
| } | |||
| USBHDBGSerial.printf("\n"); */ | |||
| /* | |||
| print("HID: "); | |||
| print(use_report_id); | |||
| print(" - "); | |||
| print_hexbytes(transfer->buffer, transfer->length); | |||
| */ | |||
| const uint8_t *buf = (const uint8_t *)transfer->buffer; | |||
| uint32_t len = transfer->length; | |||
| @@ -249,6 +253,14 @@ void USBHIDParser::out_data(const Transfer_t *transfer) | |||
| } | |||
| } | |||
| void USBHIDParser::timer_event(USBDriverTimer *whichTimer) | |||
| { | |||
| if (topusage_drivers[0]) { | |||
| topusage_drivers[0]->hid_timer_event(whichTimer); | |||
| } | |||
| } | |||
| bool USBHIDParser::sendPacket(const uint8_t *buffer, int cb) { | |||
| if (!out_size || !out_pipe) return false; | |||
| if (!tx1) { | |||
| @@ -9,6 +9,7 @@ MIDIDevice KEYWORD1 | |||
| MIDIDevice_BigBuffer KEYWORD1 | |||
| USBSerial KEYWORD1 | |||
| USBSerial_BigBuffer KEYWORD1 | |||
| USBSerialEmu KEYWORD1 | |||
| USBSerialBase KEYWORD1 | |||
| AntPlus KEYWORD1 | |||
| JoystickController KEYWORD1 | |||
| @@ -2,9 +2,9 @@ name=USBHost_t36 | |||
| version=0.1 | |||
| author=Paul Stoffregen | |||
| maintainer=Paul Stoffregen | |||
| sentence=Connect USB devices to the USB Host of Teensy 3.6. | |||
| sentence=Connect USB devices to the USB Host of Teensy 3.6 and Teensy 4.x | |||
| paragraph= | |||
| category=Communication | |||
| url=https://github.com/PaulStoffregen/USBHost_t36 | |||
| architectures=* | |||
| includes=USBHost_t36 | |||
| @@ -37,10 +37,14 @@ hidclaim_t RawHIDController::claim_collection(USBHIDParser *driver, Device_t *de | |||
| USBHDBGSerial.printf("Rawhid Claim: %x:%x usage: %x\n", dev->idVendor, dev->idProduct, topusage); | |||
| #endif | |||
| if ((dev->idVendor != 0x16c0 || (dev->idProduct) != 0x486)) return CLAIM_NO; | |||
| if (dev->idVendor != 0x16c0) return CLAIM_NO; // NOT PJRC | |||
| if (mydevice != NULL && dev != mydevice) return CLAIM_NO; | |||
| if (usage_) return CLAIM_NO; // Only claim one | |||
| if (fixed_usage_ && (fixed_usage_ != topusage)) return CLAIM_NO; // See if we want specific one and if so is it this one | |||
| if (fixed_usage_ ) { | |||
| if (fixed_usage_ != topusage) return CLAIM_NO; // See if we want specific one and if so is it this one | |||
| } else if (dev->idProduct != 0x486) return CLAIM_NO; // otherwise mainly used for RAWHID Serial type. | |||
| mydevice = dev; | |||
| collections_claimed++; | |||
| usage_ = topusage; | |||
| @@ -1239,25 +1239,19 @@ int USBSerialBase::available(void) | |||
| int USBSerialBase::peek(void) | |||
| { | |||
| if (!device) return -1; | |||
| uint32_t head = rxhead; | |||
| uint32_t tail = rxtail; | |||
| if (head == tail) return -1; | |||
| if (++tail >= rxsize) tail = 0; | |||
| return rxbuf[tail]; | |||
| if (rxhead == rxtail) return -1; | |||
| return rxbuf[rxtail]; | |||
| } | |||
| int USBSerialBase::read(void) | |||
| { | |||
| if (!device) return -1; | |||
| uint32_t head = rxhead; | |||
| uint32_t tail = rxtail; | |||
| if (head == tail) return -1; | |||
| if (++tail >= rxsize) tail = 0; | |||
| int c = rxbuf[tail]; | |||
| rxtail = tail; | |||
| if (rxhead == rxtail) return -1; | |||
| int c = rxbuf[rxtail]; | |||
| if (++rxtail >= rxsize) rxtail = 0; | |||
| if ((rxstate & 0x03) != 0x03) { | |||
| NVIC_DISABLE_IRQ(IRQ_USBHS); | |||
| rx_queue_packets(head, tail); | |||
| rx_queue_packets(rxhead, rxtail); | |||
| NVIC_ENABLE_IRQ(IRQ_USBHS); | |||
| } | |||
| return c; | |||