Forum Rule: Always post complete source code & details to reproduce any issue!
Results 1 to 3 of 3

Thread: Custom USB device based on teensy 3.2

  1. #1

    Custom USB device based on teensy 3.2

    I would like to make a custom USB device with a teensy 3.2. Programmed with Teensyduino and controlled by an Android app using USB host.
    I think all I need is access to the device layer of the USB stack.
    I found the Freescale USB stack manual which seems to provide what I need but seems no longer supported.
    Which USB stack does Teensy use?
    Could another USB option in the tools menu be created to open up the device layer?

  2. #2
    Senior Member PaulStoffregen's Avatar
    Join Date
    Nov 2012
    Quote Originally Posted by George Shering View Post
    Which USB stack does Teensy use?
    Teensy uses a USB stack I wrote from scratch.

    There isn't any formal documentation, but it's pretty simple to use. You will need to dig into the code, located in Arduino's hardware/teensy/avr/cores/teensy3 folder. The files beginning with "usb" are the USB stack, so you can mostly ignore all the other stuff in that folder.

    The first step is to add your descriptors in usb_desc.h and usb_desc.h. The code is set up to use one of many sets in usb_desc.h depending on which item was selected in Arduino's Tools > USB Type menu. You can edit boards.txt to add your own to the menu, or just commandeer one of the existing options. The usb_desc.h define causes 1 or more parts of usb_desc.c to be used. These files are simple but long, because they contain the pre-defined descriptors for everything Teensy supports. As will all USB device programming, the first step is just getting the descriptors right so your PC recognizes the device the way you want. This is also where you'll define which endpoints your device uses.

    If your device will use control transfers other than the standard chapter 9 stuff for enumeration, you'll need to add your control transfer handler code in the huge usb_setup() function in usb_dev.c. The best way to learn how it looking at the others already there. The basic idea is you check the setup packet info, do whatever you need to do with any data structures you made. If you want to send a reply, put your reply into "data" and "datalen" and break from the huge switch/case, which runs the code to queue your data to an IN response. If you want to do nothing, just return. If the inputs are an error, call endpoint0_stall() and then return. If your custom device doesn't need any special control transfers, as most do not, then you can skip this part.

    Next you'll need to actually add your API to do whatever your custom device does. usb_joystick.h and usb_joystick.c are good place to start. As you can see in usb_joystick_send(), you'll be dealing with usb_packet_t structures, which are just a USB packet plus some basic info like how much data it has. To transmit, you get a new usb_packet_t with usb_malloc(), and then when you've filled it with data and set the length field to tell the USB stack how much data you put into the packet buffer, just use usb_tx() to queue it for transfer.

    Receiving data works similarly. You use usb_rx() to get a usb_packet_t pointer for the next packet that's been received. You'll get NULL when nothing has been received. The "len" field tells you how much data the USB packet actually has. When you're done getting the data from the buffer, use usb_free() to return the buffer to the USB stack.

    usb_packet_t has an index field which is meant for you to use, to remember how much of the packet you've used, if you will read or write data less than 1 full packet at a time.

    For transmitting, if you don't always send in full-size packets from your application, you'll need to think of how you want partial-packet data flushed. The simplest way is to just add a flush function to your API and gives any partially filled usb_packet_t to usb_tx(). The more elegant/usable API does a timeout-based flush, so rapid writing tends to fill whole packets and the user doesn't need to remember to manually flush. If you will implement flush timeout, you'll also need to edit usb_dev.c to add a timeout callback. MIDI does this the simplest way, where you add a call from the SOF interrupt and just give any partially filled packet to usb_tx(). If you wish to wait longer, which adds latency but makes "bursty" applications more efficiently pack data into USB packets, you'll want to look at the multi-frame timeout used by usb_serial.c.

    The usb_packet_t buffers come from a fixed-size pool. You can control the total number from usb_desc.h. For bulk transfer, you should arrange for the pool to have buffers for as much data as you're likely to send in any 1 millisecond USB frame. If your device will do simultaneous input and output transfer, or be part of a composite device where other interfaces are simultaneously moving data, you need to be mindful of exhausting the memory pool. The main issue is a possible performance issues of even deadlock where an application on Teensy transmits a huge amount of data very rapidly. If you consume all the available memory with usb_malloc(), you can starve the other receiving endpoints of buffers to allow incoming data to arrive. The common approach is to call usb_tx_packet_count(), which tells you how many of the packets you've previously given to usb_tx() are still waiting in the queue to actually transmit over USB. Look at usb_serial.c for an example how to limit this to a safe level, if you need your interface to "play nice" with other USB data flow.

    That just about covers everything for ordinary devices using control, interrupt and bulk transfers. If you need isochronous, things are a bit more complicated, partly on the USB side, partly because whatever your USB code will do needs to integrate with other code that's designed for real-time streaming. This usually involves dealing with slight mismatch in data rate over USB versus whatever rate you're actually producing or consuming data on Teensy. The usb audio code can give you an idea, including buffering and monitoring the buffer level to produce asynchronous rate feedback to the PC. But the honest truth is making isochronous streaming really work in practice is a pretty advanced topic that I can't adequately cover in this forum message. Fortunately you don't need to worry about such things for the simpler control, interrupt and bulk transfers.

    Hopefully this helps?
    Last edited by PaulStoffregen; 01-08-2018 at 09:47 PM.

  3. #3
    Thanks for the detailed reply Paul.
    It will be enough to get me started though I fear I will be back with more questions.
    Bravo for writing the stack from scratch, quite some work.
    Last edited by George Shering; 01-09-2018 at 06:03 AM. Reason: Grammer

Posting Permissions

  • You may not post new threads
  • You may not post replies
  • You may not post attachments
  • You may not edit your posts