When you connect an Arduino to a PC what actually happens is that the supporting circuitry creates a USB CDC ACM device which emulates a good old fashioned RS-232 serial port. If I wanted to add similar functionality to my standalone circuits then the most common way of doing so would be to use an FT232RL, but the chip alone would almost double the cost of the circuit plus it is only available as a surface mount part making it difficult to experiment with on a breadboard and I'm not sure my soldering skills are good enough to deal with surface mount parts either.
After pondering this for a bit and doing a little research I came across a potential solution in the form of V-USB. V-USB is a software only implementation of low speed USB for Atmel microcontrollers, such as the ATMega328P-PU. Unfortunately the distribution doesn't directly support the Arduino (it supports the ATMega328P-PU but not through the Arduino IDE etc.), however, I did find a previous attempt to add Arduino support although this project seems to have been abandoned as it hasn't seen any updates in over three years. It did, however, give me a good point to start from.
So far I haven't managed to emulate a serial port, but I have managed to make the Arduino behave like a USB keyboard which means that I can use it for debugging by having it pretend to press lots of keys in sequence, which is better than nothing. Before we get to looking at how to make use of V-USB we need to wire a USB plug up to the Arduino.
USB Connection Parts List | ||
---|---|---|
Part | Unit Cost | Quantity |
USB Socket, Type B | £0.50 | 1 |
3.6V, 0.5W Zener Diode | £0.071 | 2 |
68Ω Resistor | £0.008 | 2 |
2.2kΩ Resistor | £0.008 | 1 |
Now we have the hardware what we need is some working software we can upload to the Arduino. As I said above I'm using a previous attempt to get this working as my starting point. This code hasn't been updated for over three years and I'm guessing that a number of things have changed within the Arduino libraries since then as the code didn't just work. After a bit of trial and error I discovered that the problems were mostly related to timer code that had been added to get V-USB to work with the Arduino and which was now doing the exact opposite. Removing this code got me to a point where when I plugged in the cable the Arduino was now being recognized as a USB v1.01 HID Keyboard, which I know is my device because of the vendor and product strings being shown in this screenshot.
Having got the code running I then took the plunge to update the version of V-USB being used to the latest version (currently 20121206) which, after a few little tweaks, was a success. Unfortunately because of the way the library has to be configured a single copy can't be shared between multiple projects, but in the rest of this post I'll explain how to customize the library and show you a fully worked example: TheCaffeineButton.
Firstly I've had problems compiling the library through the Arduino IDE, so I'd recommend compiling any sketches that use the library via the
arduino-mk
project that I discussed in a previous post. This allows you to have libraries local to a sketch by putting them in a libs
subfolder. A basic sketch to use the libary then looks like the following:// pull in the USB Keyboard library #include <USBKeyboard.h> void setup() { // TODO: setup like stuff in here... } void loop() { // poll the USB connection USBKeyboard.update(); // TODO: whatever you want in here... }You simply include the library (line 2) and then poll the connection every time through the main loop (line 6). As I mentioned though, the library needs customizing for each project and so if you try and compile the sketch at this point you'll end up with an error:
libs/USBKeyboard/usbdrv.h:12:23: fatal error: usbconfig.h: No such file or directoryV-USB doesn't provide a copy of
usbconfig.h
as it is project specific, what they do provide is a file called usbconfig-prototype.h
which you can copy and rename as a starting point. I've already done a lot of the configuration for you by editing usbconfig-prototype.h
leaving just four lines you need to edit for yourself. Firstly you need to set the vendor name property by editing lines 244 and 245:#define USB_CFG_VENDOR_NAME 'o', 'b', 'd', 'e', 'v', '.', 'a', 't' #define USB_CFG_VENDOR_NAME_LEN 8and then the device name by editing lines 254 and 256:
#define USB_CFG_DEVICE_NAME 'T', 'e', 'm', 'p', 'l', 'a', 't', 'e' #define USB_CFG_DEVICE_NAME_LEN 8These values have to be changed and can't be set to any random value because as part of the V-USB license agreement you need to conform to the following rules (taken verbatim from the file
USB-IDs-for-free.txt
):(2) The textual manufacturer identification MUST contain either an Internet domain name (e.g. "mycompany.com") registered and owned by you, or an e-mail address under your control (e.g. myname@gmx.net"). You can embed the domain name or e-mail address in any string you like, e.g. "Objective Development http://www.obdev.at/vusb/".If you glance back at the screenshot I showed earlier of my example device being recognized then you can see that I followed these rules by setting the vendor name to englishcoffeedrinker.co.uk and the device name to
(3) You are responsible for retaining ownership of the domain or e-mail address for as long as any of your products are in use.
(4) You may choose any string for the textual product identification, as long as this string is unique within the scope of your textual manufacturer identification.
TheCaffeineButton
.So having created a valid
usbconfig.h
file you can now compile the basic sketch shown above. Of course this does nothing other than allow the Arduino to be recognized as a USB keyboard. If you want to actually do something interesting then you need a little more code. As an example, I'll finally introduce you to The Caffeine Button!Caffeine (usually in the form of coffee) is my one true addiction and so I thought I'd build a device with a single button that when pressed types out the chemical formula for caffeine: C8H10N4O2. The full sketch is fairly simple and assumes the basic circuit above with a push button between pin 12 and ground (it uses the internal pull-up resistor to keep the part list down to the single button):
// pull in the USB Keyboard library #include <USBKeyboard.h> // using Bounce makes working with buttons nice and easy // http://www.arduino.cc/playground/Code/Bounce #include <Bounce.h> // we have the button on pin 12 #define BUTTON_PIN 12 // create a Bounce instance to manage the button Bounce button = Bounce(BUTTON_PIN, 5); void setup() { // initialise the pin the button is connected to pinMode(BUTTON_PIN, INPUT); // enable the internal pull-up resistor digitalWrite(BUTTON_PIN, HIGH); } void loop() { // poll the USB connection USBKeyboard.update(); // check the status of the button button.update(); if (button.fallingEdge()) { // if the button has just been pressed then... // ...print out the formula for caffeine USBKeyboard.print("C8H10N4O2"); } }There are actually four different methods you can use to emulate pressing keys:
void write(byte keycode); void write(byte keycode, byte modifiers); void print(const char* text); void println(const char* text);The first two method send a USB key usage code (with or without a modifier, such as the shift key) and then release the key, while the last two methods translate alphanumeric characters (and space) into a sequence of keystrokes (followed by the enter key in the
println
case) to make the library a little easier to use. Unfortunately there is no guarantee that my simple sketch will always result in C8H10N402 being displayed when you press the button.When you press a key on a keyboard a keycode is sent to the computer which determines which letter has been pressed. This allows the same physical keyboard to be used for different languages simply by changing the printed labels on each key. Unfortunately this makes it impossible to translate a string into a sequence of keycodes which will always display the same on every computer. For example, if you send keycode 28 on a computer with an English keyboard mapping you'll get a 'y', but if the keyboard mapping is German you'll get a 'z'. I've defined a bunch of constants in
USBKeyboard.h
(i.e. KEY_A
) which work for English, and I've used the same mapping in the print
and println
methods. If you can set the keyboard mapping for the device to English then these methods will work properly for you, if not you might need to tweak the mappings to get what you need. You can find the full list of mappings in chapter 10 of the Universal Serial Bus HID Usage Tables document should you need more details.So here we have the final working item. As you can see I built the main USB circuit onto a prototyping shield so I can experiment with lots of different circuits without having to keep recreating the basics every time, and in this case have simply jammed the button between pins 12 and ground.
If you've read all the way to here then I'm guessing you might want to know where you can get the library from, well it is available under the GPL licence (a restriction imposed because I'm using the free USB vendor and product IDs from Objective Development) from my SVN repository.
Whilst I haven't yet been able to emulate a serial port I haven't given up and when/if I'm successful I'm sure there will be a post about it and another Arduino library for you to play with.
I did look at this briefly and it would appear that the ATmega32u4 would cost not much more than the ATMega328P-PU I'm using. The downside is that it is only available as a surface mount part with 44 tiny little pins! Certainly not ideal for breadboarding.
ReplyDeleteIt also turns out that I can't emulate a CDC ACM device as it requires a high speed USB connection and V-USB only does low speed (it used to work but a change to the linux kernel means that the device won't now work under linux at least). I have managed to send data from the Arduino via the above circuit as a plain USB Human Interface Device and can read that data out using Java. There are a few caveats though and I don't have writing from the PC working yet. Once I do I'm sure there will be another post.