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; |