LUFA CDC Streams
It’s almost time for the next LUFA release. I’m really digging the 2-month release cycle I’ve gotten myself into; not too short that it annoys everyone, and long enough to give me time to work on a decent feature set. This next release will contain a bunch more demos, mainly focusing on combining the class drivers into a single application – but there’s a few new things in there too. Most of the demos have also been updated in some way, to give an even better set of examples for people to base their own code from.
The last two months have been hectic for me, with three simultaneous programming assignments plus the impending exams, the first of which starts this Friday. In a little over a week this will all be over and I can go back to emulating a sponge until the start of the next semester in March. On that note — I’ll be needing some work, somone hire me! Pretty Please?
Now, on to today’s topic – CDC streams. It’s always been an end-goal of LUFA to give full USB functionality to projects with a minimal of effort. First there was the API, cleaner than Atmels, then the extra features. A few months ago I introduced the new Class Driver framework which, although I’m not happy with in all areas, has made it almost trivial to integrate multiple different USB functions in the one device.
The biggest number of questions I get is about two of the classes however – HID and CDC. The Human Interface Device specification (HID) is a topic I’ll be writing more on in the future, as I want to focus today on the Communications Device Class, aka. CDC.
The CDC class is actually a huge class with many subclasses, and is designed mainly for telephony, modems, networking, and other communication related functions. No surprise there. In actual fact the networking portion of the standard is a bit wonky, with three different standards (including Microsoft’s proprietary RNDIS “standard”) of which almost no one seems to use. The most used portion of the standard is actually the “Abstract Control Model” portion – although most will know it by a more common name, virtual serial ports.
CDC-ACM is designed to replace the serial port communications between the host and ye olde diallup modems, which means it includes all the trappings including virtual baud rate (used only at the device end if desired), virtual control lines and, of course, a two way data channel. Exactly bunk number of people use it for modems however, since everyone just uses it to replace traditional serial ports in legacy – and in many cases new – applications.
Now, those familiar with the avr-libc and LUFA APIs will know about standard FILE streams, which the library functions printf, puts, etc. can operate on. When properly coded, you can associate a FILE structure a system of your chosing (such as the USART or SPI port) and read/write streams of data quite easily. However, until recently, it hasn’t been possible to do this with CDC serial interfaces without a bit of hoop-jumping.
Before I continue, let’s look at the following snippet:
static FILE USBSerialStream; int main(void) { // ... CDC_Device_CreateStream(&VirtualSerial_CDC_Interface, &USBSerialStream); // ... fprintf(&USBSerialStream, "Test String"); }
Which should give a fairly good idea on how simple it now is with the next LUFA release. Once the new CDC_Device_CreateStream() function is called on a CDC Device Class Driver interface and a given FILE variable, it sets up a transparent stream which can then be used with any of the standard library functions, for both reading and writing. Pretty darn cool, if I do say so myself. Of course, you could always pass in the “stdout” avr-libc internal stream to the function if desired, which would redirect all the standard output stream functions (printf, gets) to the virtual serial port. This would conflict with the SerialStream LUFA driver for the AVR’s physical USART however, so it’s probably best avoided.
The bonus here is that you can make as many such streams for as many interfaces as you like, without increasing the binary size by more than a few bytes. You could make the DualCDC demo make two seperate streams for each of the interfaces, and read/write to them seperately.
The magic is all centered around the “udata” (User Data) field of the FILE structure, which I ran into recently. This is a void* member of the FILE struct, which can be set/retrieved via the fdev_set_udata() and fdev_get_udata() functions to associate user data with a particular stream. That gave me the epiphany I needed — if I stored the pointer to the CDC Class Driver instance as the udata member in the stream, I could make generic stream character routines in the class driver and use the udata member to determine which endpoint the data is operating on.
That’s just one new feature of the upcomming release. Stay tuned!