Browse Source

USBSerial fix add USBSerialEmu

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 character
main
Kurt Eckhardt 3 years ago
parent
commit
2c5cbccebc
7 changed files with 343 additions and 22 deletions
  1. +236
    -0
      SerEMU.cpp
  2. +80
    -6
      USBHost_t36.h
  3. +12
    -0
      hid.cpp
  4. +1
    -0
      keywords.txt
  5. +2
    -2
      library.properties
  6. +6
    -2
      rawhid.cpp
  7. +6
    -12
      serial.cpp

+ 236
- 0
SerEMU.cpp View File

/* 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;
// }
}

+ 80
- 6
USBHost_t36.h View File

// USBHost. // USBHost.
class USBDriver; class USBDriver;
class USBDriverTimer; class USBDriverTimer;
class USBHIDInput;


/************************************************/ /************************************************/
/* Added Defines */ /* Added Defines */
public: public:
USBDriverTimer() { } USBDriverTimer() { }
USBDriverTimer(USBDriver *d) : driver(d) { } USBDriverTimer(USBDriver *d) : driver(d) { }
USBDriverTimer(USBHIDInput *hd) : driver(nullptr), hidinput(hd) { }

void init(USBDriver *d) { driver = d; }; void init(USBDriver *d) { driver = d; };
void start(uint32_t microseconds); void start(uint32_t microseconds);
void stop(); void stop();
uint32_t started_micros; // testing only uint32_t started_micros; // testing only
private: private:
USBDriver *driver; USBDriver *driver;
USBHIDInput *hidinput;
uint32_t usec; uint32_t usec;
USBDriverTimer *next; USBDriverTimer *next;
USBDriverTimer *prev; USBDriverTimer *prev;
const uint8_t *serialNumber() const uint8_t *serialNumber()
{ return ((mydevice == nullptr) || (mydevice->strbuf == nullptr)) ? nullptr : &mydevice->strbuf->buffer[mydevice->strbuf->iStrings[strbuf_t::STR_ID_SERIAL]]; } { return ((mydevice == nullptr) || (mydevice->strbuf == nullptr)) ? nullptr : &mydevice->strbuf->buffer[mydevice->strbuf->iStrings[strbuf_t::STR_ID_SERIAL]]; }



private: private:
virtual hidclaim_t claim_collection(USBHIDParser *driver, Device_t *dev, uint32_t topusage); 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;} virtual bool hid_process_in_data(const Transfer_t *transfer) {return false;}
virtual void hid_input_data(uint32_t usage, int32_t value); virtual void hid_input_data(uint32_t usage, int32_t value);
virtual void hid_input_end(); virtual void hid_input_end();
virtual void disconnect_collection(Device_t *dev); virtual void disconnect_collection(Device_t *dev);
virtual void hid_timer_event(USBDriverTimer *whichTimer) { }
void add_to_list(); void add_to_list();
USBHIDInput *next = NULL; USBHIDInput *next = NULL;
friend class USBHIDParser; friend class USBHIDParser;


class USBHIDParser : public USBDriver { class USBHIDParser : public USBDriver {
public: public:
USBHIDParser(USBHost &host) { init(); }
USBHIDParser(USBHost &host) : hidTimer(this) { init(); }
static void driver_ready_for_hid_collection(USBHIDInput *driver); static void driver_ready_for_hid_collection(USBHIDInput *driver);
bool sendPacket(const uint8_t *buffer, int cb=-1); bool sendPacket(const uint8_t *buffer, int cb=-1);
void setTXBuffers(uint8_t *buffer1, uint8_t *buffer2, uint8_t cb); void setTXBuffers(uint8_t *buffer1, uint8_t *buffer2, uint8_t cb);


bool sendControlPacket(uint32_t bmRequestType, uint32_t bRequest, bool sendControlPacket(uint32_t bmRequestType, uint32_t bRequest,
uint32_t wValue, uint32_t wIndex, uint32_t wLength, void *buf); 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: protected:
enum { TOPUSAGE_LIST_LEN = 4 }; enum { TOPUSAGE_LIST_LEN = 4 };
enum { USAGE_LIST_LEN = 24 }; enum { USAGE_LIST_LEN = 24 };
virtual void disconnect(); virtual void disconnect();
static void in_callback(const Transfer_t *transfer); static void in_callback(const Transfer_t *transfer);
static void out_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 in_data(const Transfer_t *transfer);
void out_data(const Transfer_t *transfer); void out_data(const Transfer_t *transfer);
bool check_if_using_report_id(); bool check_if_using_report_id();
void parse(uint16_t type_and_report_id, const uint8_t *data, uint32_t len); void parse(uint16_t type_and_report_id, const uint8_t *data, uint32_t len);
void init(); 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;} uint8_t activeSendMask(void) {return txstate;}


uint8_t *tx1 = nullptr; uint8_t *tx1 = nullptr;
uint8_t *tx2 = nullptr; uint8_t *tx2 = nullptr;
bool hid_driver_claimed_control_ = false; bool hid_driver_claimed_control_ = false;
USBDriverTimer hidTimer;
uint8_t bInterfaceNumber = 0;
}; };


//-------------------------------------------------------------------------- //--------------------------------------------------------------------------


//-------------------------------------------------------------------------- //--------------------------------------------------------------------------


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 { class BluetoothController: public USBDriver {
public: public:
static const uint8_t MAX_CONNECTIONS = 4; static const uint8_t MAX_CONNECTIONS = 4;
bool deviceAvailable = false; bool deviceAvailable = false;
}; };



#endif #endif

+ 12
- 0
hid.cpp View File

uint32_t numendpoint = descriptors[4]; uint32_t numendpoint = descriptors[4];
if (numendpoint < 1 || numendpoint > 2) return false; if (numendpoint < 1 || numendpoint > 2) return false;
if (descriptors[5] != 3) return false; // bInterfaceClass, 3 = HID if (descriptors[5] != 3) return false; // bInterfaceClass, 3 = HID
println(" bInterfaceNumber = ", descriptors[2]);
println(" bInterfaceClass = ", descriptors[5]); println(" bInterfaceClass = ", descriptors[5]);
println(" bInterfaceSubClass = ", descriptors[6]); println(" bInterfaceSubClass = ", descriptors[6]);
println(" bInterfaceProtocol = ", descriptors[7]); println(" bInterfaceProtocol = ", descriptors[7]);
topusage_drivers[i] = NULL; topusage_drivers[i] = NULL;
} }
// request the HID report descriptor // 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 mk_setup(setup, 0x81, 6, 0x2200, descriptors[2], descsize); // get report desc
queue_Control_Transfer(dev, &setup, descriptor, this); queue_Control_Transfer(dev, &setup, descriptor, this);
return true; return true;
} }
USBHDBGSerial.printf("\n"); */ USBHDBGSerial.printf("\n"); */


/*
print("HID: "); print("HID: ");
print(use_report_id); print(use_report_id);
print(" - "); print(" - ");
print_hexbytes(transfer->buffer, transfer->length); print_hexbytes(transfer->buffer, transfer->length);
*/
const uint8_t *buf = (const uint8_t *)transfer->buffer; const uint8_t *buf = (const uint8_t *)transfer->buffer;
uint32_t len = transfer->length; uint32_t len = transfer->length;


} }
} }


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) { bool USBHIDParser::sendPacket(const uint8_t *buffer, int cb) {
if (!out_size || !out_pipe) return false; if (!out_size || !out_pipe) return false;
if (!tx1) { if (!tx1) {

+ 1
- 0
keywords.txt View File

MIDIDevice_BigBuffer KEYWORD1 MIDIDevice_BigBuffer KEYWORD1
USBSerial KEYWORD1 USBSerial KEYWORD1
USBSerial_BigBuffer KEYWORD1 USBSerial_BigBuffer KEYWORD1
USBSerialEmu KEYWORD1
USBSerialBase KEYWORD1 USBSerialBase KEYWORD1
AntPlus KEYWORD1 AntPlus KEYWORD1
JoystickController KEYWORD1 JoystickController KEYWORD1

+ 2
- 2
library.properties View File

version=0.1 version=0.1
author=Paul Stoffregen author=Paul Stoffregen
maintainer=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= paragraph=
category=Communication category=Communication
url=https://github.com/PaulStoffregen/USBHost_t36 url=https://github.com/PaulStoffregen/USBHost_t36
architectures=* architectures=*
includes=USBHost_t36

+ 6
- 2
rawhid.cpp View File

USBHDBGSerial.printf("Rawhid Claim: %x:%x usage: %x\n", dev->idVendor, dev->idProduct, topusage); USBHDBGSerial.printf("Rawhid Claim: %x:%x usage: %x\n", dev->idVendor, dev->idProduct, topusage);
#endif #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 (mydevice != NULL && dev != mydevice) return CLAIM_NO;
if (usage_) return CLAIM_NO; // Only claim one 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; mydevice = dev;
collections_claimed++; collections_claimed++;
usage_ = topusage; usage_ = topusage;

+ 6
- 12
serial.cpp View File

int USBSerialBase::peek(void) int USBSerialBase::peek(void)
{ {
if (!device) return -1; 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) int USBSerialBase::read(void)
{ {
if (!device) return -1; 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) { if ((rxstate & 0x03) != 0x03) {
NVIC_DISABLE_IRQ(IRQ_USBHS); NVIC_DISABLE_IRQ(IRQ_USBHS);
rx_queue_packets(head, tail);
rx_queue_packets(rxhead, rxtail);
NVIC_ENABLE_IRQ(IRQ_USBHS); NVIC_ENABLE_IRQ(IRQ_USBHS);
} }
return c; return c;

Loading…
Cancel
Save