0

Implementing USB

Posted by Chris on Friday, April 17, 2009
Right, let's get this straight. Implementing USB is tricky. It's full of crazy descriptors, end-points, reports and a whole host of weird stuff. Being a fan of Oshonsoft's PIC simulator already, it made sense (to me anyway) to investigate the PIC18 simulator.

I've already got some PIC18F2455 chips on order from RS Components which include built in USB support. And best of all, they cost just £4.60 each - less than the combined cost of a PIC16F628A (or a PIC16F877A) plus a USB-to-serial convertor chip, and there's no messy surface-mount soldering to worry about!

A quick read through the USB basics and a lucky Google hit that shows how to write your own applications with USB support and I think I've got all I need to get started (once the chips arrive I can put the theory into practice). And if you stick to HID-type devices, you can even create your own funky USB descriptors (so when you plug it into your computer it says something like "Found New Hardware - Crazy Dave's Crazy Thing" instead of some boring old "Found USB - serial virtual com port". You can even set your own VID (vendor ID) and PID (product ID) combinations, and write code specifically for your device (and not just send data to anything that happens to be plugged into a virtual COM port, hoping that it's your device on there!).
Here's what I've picked up so far...

PICBasic listing for microcontroller

Define CLOCK_FREQUENCY = 20
Define CONFIG1L = 0x24
Define CONFIG1H = 0x0c
Define CONFIG2L = 0x3e
Define CONFIG2H = 0x00
Define CONFIG3L = 0x00
Define CONFIG3H = 0x83
Define CONFIG4L = 0x80
Define CONFIG4H = 0x00
Define CONFIG5L = 0x0f
Define CONFIG5H = 0xc0
Define CONFIG6L = 0x0f
Define CONFIG6H = 0xe0
Define CONFIG7L = 0x0f
Define CONFIG7H = 0x40

UsbSetVendorId 0x1234
UsbSetProductId 0x1234
UsbSetVersionNumber 0x1122
UsbSetManufacturerString "OshonSoft.com"
UsbSetProductString "Generic USB HID Device"
UsbSetSerialNumberString "1111111111"
UsbOnIoInGosub usbonioin
UsbOnIoOutGosub usbonioout
UsbOnFtInGosub usbonftin
UsbOnFtOutGosub usbonftout

AllDigital
ADCON1 = 0x0e
TRISB = 0
PORTB = 0xff
UsbStart
PORTB = 0

Dim an0 As Byte

loop:
Adcin 0, an0
If an0 < 50 Then
PORTB = 0
UsbStop
While an0 < 100
Adcin 0, an0
Wend
PORTB = 0xff
UsbStart
PORTB = 0
Endif
UsbService
Goto loop
End

usbonftout:
Toggle PORTB.7
Return

usbonftin:
UsbFtBuffer(0) = UsbFtBuffer(0) - 1
UsbFtBuffer(1) = UsbFtBuffer(1) - 1
UsbFtBuffer(2) = UsbFtBuffer(2) - 1
UsbFtBuffer(3) = UsbFtBuffer(3) - 1
UsbFtBuffer(4) = UsbFtBuffer(4) - 1
UsbFtBuffer(5) = UsbFtBuffer(5) - 1
UsbFtBuffer(6) = UsbFtBuffer(6) - 1
UsbFtBuffer(7) = UsbFtBuffer(7) - 1
Return

usbonioout:
Toggle PORTB.6
Return

usbonioin:
UsbIoBuffer(0) = UsbIoBuffer(0) + 1
UsbIoBuffer(1) = UsbIoBuffer(1) + 1
UsbIoBuffer(2) = UsbIoBuffer(2) + 1
UsbIoBuffer(3) = UsbIoBuffer(3) + 1
UsbIoBuffer(4) = UsbIoBuffer(4) + 1
UsbIoBuffer(5) = UsbIoBuffer(5) + 1
UsbIoBuffer(6) = UsbIoBuffer(6) + 1
UsbIoBuffer(7) = UsbIoBuffer(7) + 1
Return


VB6 listing for desktop application (project, form, declarations):
http://www.nerdclub.co.uk/projects/USBPIC/usbhid.rar

The code above (from the Oshon site) doesn't make an awful lot of sense, until you see what's happening from the PC end - the VB code can be opened up in Notepad if you don't have Visual Basic/Studio on your computer.
But - as far as I understand without actually putting the code onto a chip and trying it for real - the Oshonsoft code takes away all the tricky (nasty) stuff like enumerating and responding to requests and gives you a couple of "events" to respond to. Although PICBasic isn't really event-driven, you can create a few GOSUB routines (remember them?) which are called when a USB event causes an interrupt to occur.

USB devices communicate with a PC (or other device) by putting a message into a queue. The host device periodically polls each device, asking if there are any messages from it to be processed. Similarly, when the PC sends a message to a USB device, the message is dropped into a buffer on the device.
This is a massive over-simplification but gives an idea about what is going on!

There are two buffers that can be used to send data back and forth - a feature buffer and an input-output buffer. Of the two, the io buffer is the one that is used to actually transfer data in and out of the device (stands to reason really when you think about the name!). The technique for transferring data, whether to using "feature reports" or "input reports" and "output reports" is the same:

With Oshonsoft's PIC18 Basic language, it seems that you must send data in 8-byte "chunks". On the PC side, you should populate an eight-byte array OutputReportData with data and then call the WriteReport routine. When received by the microcontroller, the USBIOBuffer is filled with these 8 bytes. Back on the PIC end of things, the USB interrupt causes the gosub routine usbonioout to be called. In the example above, it toggles a pin output/LED to show that data has been received.

The same buffer is read by the PC when the ReadReport routine is called.
Note that reading the device data is initiated by the PC, not the device. There is no way to "send" data to the PC from the device: the best you can hope for is to populate the USBIOBuffer array with your 8 bytes of data and wait for the PC to ask for them.
When the PC asks to read this buffer, the USB interrupt causes the gosub routine usbonioin to be called. In this example, each byte value held in the buffer is increased by one - so that, in this example, when the result of reading the device data is displayed on the screen, you can see the data has come from the PIC. Once the usbonioin routine ends, the contents of the buffer are sent to the PC and back at the PC, the array ReadBuffer is populated with the buffer contents.

In your PIC code, you should execute the USBService command as often as is reasonable, at it is while the USB commands are being "serviced" that the i/o interrupts are raised, that transfer the data in and out of your device. Apparently it can take multiple calls to USBService to completely transfer all of the data into/from the buffer.

That's basically it: the rest of your PIC code can get on with processing other inputs, setting output pins and so on. One of the first things to try out with the PIC18F2455 is a simple USB-to-serial alternative. Because it includes a USART interface, it should be quite simple to receive data from the USB port and re-send it down the serial data line. The obvious thing to look out for it timing issues: USB is much, much quicker than sending via serial, so some sort of basic handshaking will be needed. For example, as soon as each byte of data has been transmitted over the serial comms, you could set the appropriate byte to value zero. At the PC end, you could monitor the io buffer and when all bytes read zero, send another 8 bytes of data. This would mean that until all 8 bytes have been transmitted, the PC would do nothing (as the if-statement "are all buffer bytes zero?" would fail). As soon as all bytes have been sent (not quite as soon as, but when the PC next checks the buffer contents and finds them all zero) the PC could then send the next 8 bytes along.
This means no messing about with timing issues and synchronising USB and serial clocks. The only thing to look out for is if the data you are sending genuinely contains a sequence of 8 zero bytes - perhaps some rudimentary encoding could be used to ensure that this never happened?

Of course, all this is just theory, but it's sometimes handy to talk an idea through (and it gives me something to write about while I'm once again waiting for Mr Postman to pop an envelope full of electronic goodies through my letterbox!)

0 Comments

Post a Comment

whos.amung.us

Copyright © 2009 .Nerd Club All rights reserved. Theme by Laptop Geek. | Bloggerized by FalconHive Supported by Blogger Templates.