0% found this document useful (0 votes)
129 views30 pages

AVR ATtiny USB Tutorial

This document provides instructions for using a USB cable to power an ATtiny2313 microcontroller and implement basic USB communication. It begins by explaining how to cut open a USB cable and solder the wires to a pin header for use with a breadboard. It then demonstrates powering an LED using the 5V power from the USB. Next, it introduces using a 3.3V voltage regulator to limit the voltage and prepare for USB data communication. The second part of the tutorial expands on this by adding an ATtiny2313 microcontroller connected to the USB data lines via resistors and programmed to blink an LED for testing. It concludes by discussing software setup and fuse bit configuration for using the V-USB library with the microcontroller

Uploaded by

NIKOLAOS NANNOS
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
129 views30 pages

AVR ATtiny USB Tutorial

This document provides instructions for using a USB cable to power an ATtiny2313 microcontroller and implement basic USB communication. It begins by explaining how to cut open a USB cable and solder the wires to a pin header for use with a breadboard. It then demonstrates powering an LED using the 5V power from the USB. Next, it introduces using a 3.3V voltage regulator to limit the voltage and prepare for USB data communication. The second part of the tutorial expands on this by adding an ATtiny2313 microcontroller connected to the USB data lines via resistors and programmed to blink an LED for testing. It concludes by discussing software setup and fuse bit configuration for using the V-USB library with the microcontroller

Uploaded by

NIKOLAOS NANNOS
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 30

AVR ATtiny USB Tutorial Part 1

I wanted to build an USB device using AVR microcontrollers since I found out that it
was possible. However, both the USBtiny project and the more extensive V-USB
library lacked an easy-to-approach tutorial. So I decided to make one.
This first part covers the basics for making USB-powered devices, and serves as
introduction for second part, which goes through simple example for using V-USB
library to implement USB communication to and from ATtiny2313. Additional parts
might be published later if I have the time and there’s interest.

But let’s get started. Here is what you need for this first part:

 USB cable and pin header


 Small breadboard and a few jump wires
 LED and 330 ohm resistor
 Low voltage drop 3.3V regulator, such as LD1086V33 or LE33CZ

The cable

The first thing we need to do is cut the USB cable so the end that goes into computer
remains, strip the other end and solder the four wires into a pin header so it’s easy to
plug the cable into a breadboard. USB contains four wires which you should solder in
the following order (note: not all cables conform to this so check with a multimeter!):

Pin Color Function


1 Red VCC (+5V)
2 White D-
3 Green D+
4 Black Ground (0V)

Σελίδα 1 από 30 
 
Here you see the end result. When stripping the wire, be careful not to damage the wires
and make sure the wires will not touch each other so your cable won’t short circuit your
computer or USB hub!

If you want to know more about USB connectors and electrical characteristics, I warmly
recommend USB in a NutShell by Beyond Logic, and of course the USB 2.0
specification. For now, it’s enough to understand that USB bus can provide small
amounts of current (couple hundred milliamperes at most) at roughly 5V.

Simple breadboard test

Now let’s try if we succeeded in our soldering project. I recommend you first connect
your cable to USB hub and use a multimeter to measure if you actually have 5V between
VCC (red) and GND (black). I myself got 5.18V. Then plug the pin header into
breadboard and use jumper wires to transfer VCC and GND to power rails, and connect
a LED in series with the resistor to see if you get it lit!

Σελίδα 2 από 30 
 
Congratulations! If all you want is power from USB, you can now proceed to build any
5V circuit as long as the current draw remains rather small. In case the LED stays dark,
check that you did not make a mistake with the wires, soldering or insert the LED the
wrong way. :)

Preparing for USB communication – getting to 3.3V

While USB power is 5V, the data lines require 3.3. Some computers tolerate 5V logic
but not all. To play by the book, you have three options:

1. Limit the voltage provided by USB to 3.3V


2. Power the circuit externally from 3.3V
3. Use resistors, diodes or zener diodes to convert 5V logic to 3.3V
We will choose the first option here. The second can be achieved using your favorite
method, such as 9V battery and a regulator, cell phone charger with suitable voltage
setting, or 3 AAs and one or two protection diodes that drop the voltage. For third one,
you can find many articles from the web by googling “zener diode usb” (Update: You
can also take a look at “Part 6” of my tutorial which covers this). V-USB wiki has a
good overview of the options:
Σελίδα 3 από 30 
 
http://vusb.wikidot.com/hardware
In this tutorial, I’m using LD1086V33. From the datasheet, we can see that pin 1 of
regulator is ground, pin 2 output and pin 3 input. Furthermore, 10 uF capacitors are
specified between ground and output, as well as between ground and input.

Here I have connected the regulator’s ground and and input pins to power rail supplying
5V and the output pin into LED and resistor.

Σελίδα 4 από 30 
 
While this circuit will likely work, any small glitches in power draw or supply require
the regulator to compensate, and this can lead to a continuously oscillating voltage. So
we add the 10 uF capacitors between 5V and ground (added here to power rail) and 3.3V
and ground (added in front of the regulator). Be sure to connect electrolytic capacitors
the right way (minus side is marked). And voila!

Now we are ready to power our AVR circuit or whatever project using a nice 3.3V
voltage. Use a multimeter to check that voltage between ground and regulator input is
still indeed about 5V, and the regulator output and ground is 3.3 (I got a nice 3.30V
myself).

Σελίδα 5 από 30 
 
 

AVR ATtiny USB Tutorial Part 2


This is the second part of my USB tutorial for ATtiny2313 and V-USB library. In
the first part we learned how to get 3.3V from USB to power our circuits. In this part, we
will expand our setup with following parts:
 Larger breadboard and additional jumper wires
 ATtiny2313
 12 MHz crystal oscillator
 Two 27 pF ceramic capacitors to stabilize the crystal
 Two 68 Ω resistors between USB data lines and the microcontroller pins
 1 MΩ pullup resistor for D+ and 1.5 kΩ pullup for D-
 6-pin header for programming the ATtiny and 4.7 kΩ pullup for reset pin
Update: Some people have noted that the setup I’m using here runs ATtiny2313 at 12
MHz with only 3.3V VCC, which is outside the specified range (frequencies over 10
Mhz require 4.5V or more). I’ve never had any problems, and many others have
succeeded with this setup, but if you encounter persistent problems, I suggest you to
power the ATtiny2313 straight from 5V of the USB line and use zener diodes on D+ and
D- lines to drop their voltage, as is done in my later tutorial with the ATtiny85
microcontroller.

Breadboard setup

This time I will not walk you through every connection. Instead, I’ll just outline the
steps needed and show the pictures of end result. Here is the schematic we’re building:

Σελίδα 6 από 30 
 
Basically it’s the regulator circuit we just built, pullups and line resistors for D+ and D-
in the USB bus going to PD2 and PD3, and the ATtiny2313 with RESET pullup and
MISO, MOSI, SCK and RESET connected to the 6-pin programming header, and a 12
MHz crystal. An easy order to make these connections is to:
Σελίδα 7 από 30 
 
1. Reconstruct the power circuit from part 1 in one corner of the breadboard. Instead of
connecting 5V from USB to the positive power rail, connect it straight to the input pin of
the regulator – we won’t be needing 5V anywhere else
2. Use jumper wires to connect regulator output and shared ground from USB to
breadboard power rail
3. If you have multiple power rails, you can use jumper wires to provide current to all of
them
4. Double-check the regulator pin connections and that capacitors are connected the right
way
5. Plug in the USB and use a multimeter to check you have 3.3V in power rails
6. Insert the ATtiny and do standard wiring for power, reset pullup and 6-pin programming
header
7. If your programmer has a mode where it provides power to the chip – disable it
8. Use programmer to check that the chip responds OK (i.e. gets power and acts in a stable
manner)
9. Connect the LED to PB0 and do a “test run” – flash a program that blinks the LED to
see that it works
10. Connect pullup resistors to D+ and D- (1M and 1k5)
11. Connect D+ and D- to PD2 and PD3, respectively – use 68 ohm resistors in series
here!
12. Connect the 12 MHz crystal to PA1 and PA0, and ceramic capacitors between
crystal pins and ground
Here’s how the end result should look like:

Σελίδα 8 από 30 
 
Here is another closeup of the power part and USB jumpers. Note how 5V from USB
goes to regulator only and nowhere else.

Σελίδα 9 από 30 
 
By the way, if you need a refresher on how to connect the RESET pullup and 6-pin
header and use the programmer, check out this excellent tutorial – wiring is the same but
of course pin locations in ATtiny2313 are different than in ATtiny45. Connecting the
crystal is also very easy, just connect the two legs of the crystal to PA0 and PA1, and
add ceramic capacitors between each leg and ground (they need to be close to the crystal
to make it stable).

Software side – preparing for V-USB

Now for the good stuff! If you did not already test that your ATtiny2313 setup is
working, I suggest you to do that now. A good way to do it is to use the following
simple test program:

#include <avr/io.h>

Σελίδα 10 από 30 
 
#define F_CPU 1000000UL // 1 MHz
#include <util/delay.h>

int main() {
DDRB |= 1; // LED on PB0

while(1) {
PORTB |= 1; // Turn LED on
_delay_ms(500);
PORTB &= ~1; // Turn LED off
_delay_ms(500);
}

return 1;
}
This test program has the great additional feature that once we update the fuse bits for 12
MHz clock, it’s easy to verify that blink gets 12x faster. Now for ATtiny2313, we want
to disable the internal clock/8 division and use an over 8 MHz clock with 14 clock
cycles + 4.1 ms startup delay (CLKSEL=1111, SUT=10). This means that low fuse
should be 0xEF, which is done using avrdude command “-U lfuse:w:0xef:m” (without
quotes). In case you are using any other part or just want to check it yourself, use the
excellent AVR Fuse Calculator.
Once you have updated the lfuse, reset or reprogram the chip to verify that the LED is
now blinking 12 times per second. Update the blink example’s F_CPU to 12000000L
and reflash to verify that the blinking returns to 1 Hz speed. Congratulations, everything
is now ready for the V-USB -part of the tutorial! I will stop here for tonight, and post the
third part of this tutorial shortly, covering the basic V-USB setup for our circuit.

Σελίδα 11 από 30 
 
AVR ATtiny USB Tutorial Part 3
This is the third part of my USB tutorial for ATtiny2313 and V-USB library. In
the second part we got the breadboard setup more or less covered, and now is the time
for actual code! This will most likely be the longest of the three parts, so let’s get started.

Adding V-USB as a part of your project

First, we will download the latest version V-USB library from OBdev. Head to the
Downloads-section and get the latest .zip – I got vusb-20120109.zip.
Unzip the archive and copy the usbdrv subfolder to your project folder (the whole folder,
not just contents). Go to the subfolder and make a copy of usbconfig-
prototype.h with the nameusbconfig.h. Locate the #define lines for IO port and
port bits and clock rate, and update them as necessary to reflect our configuration where
D+ is in PD2 and D- in PD3 and clock rate is 12 MHz:
#define USB_CFG_IOPORTNAME D
#define USB_CFG_DMINUS_BIT 3
#define USB_CFG_DPLUS_BIT 2
#define USB_CFG_CLOCK_KHZ 12000

It’s also a good idea to ensure that V-USB tells the computer that it is powered via USB
(i.e. not self powered) and takes maximum of 50 mA of power (defaults in my version is
USB power, 100 mA max.):
#define USB_CFG_IS_SELF_POWERED 0
#define USB_CFG_MAX_BUS_POWER 50
We will be using OBdev’s licenced vendor and device IDs, so they don’t need to be
changed (note that this also means my tutorial will fall under GPL, including the
schematic in part 2). But we do want to customize the vendor name and device name
(note the backward slash in vendor name string used to split the #define to two lines so it
fits into this blog post):

#define USB_CFG_VENDOR_ID 0xc0, 0x16 /* = 0x16c0 */


#define USB_CFG_DEVICE_ID 0xdc, 0x05 /* = 0x05dc */
Σελίδα 12 από 30 
 
#define USB_CFG_DEVICE_VERSION 0x00, 0x01

#define USB_CFG_VENDOR_NAME 'c', 'o', 'd', 'e', 'a', 'n', 'd', 'l', \
'i', 'f', 'e', '.', 'c', 'o', 'm'
#define USB_CFG_VENDOR_NAME_LEN 15

#define USB_CFG_DEVICE_NAME 'U', 'S', 'B', 'e', 'x', 'a', 'm', 'p', 'l', 'e'
#define USB_CFG_DEVICE_NAME_LEN 10
The usbconfig.h header file is well documented so you may want to read the rest of
the options to get an idea what the library can can do. Now the only thing missing is the
actual C file to use the library. Here is the barebones version of main.c we’ll start
with:
#include <avr/io.h>
#include <avr/interrupt.h>
#include <avr/wdt.h>

#include "usbdrv.h"

#define F_CPU 12000000L


#include <util/delay.h>

USB_PUBLIC uchar usbFunctionSetup(uchar data[8]) {


return 0; // do nothing for now
}

int main() {
uchar i;

wdt_enable(WDTO_1S); // enable 1s watchdog timer

usbInit();

usbDeviceDisconnect(); // enforce re-enumeration


for(i = 0; i<250; i++) { // wait 500 ms
wdt_reset(); // keep the watchdog happy
_delay_ms(2);
}
usbDeviceConnect();

sei(); // Enable interrupts after re-enumeration

Σελίδα 13 από 30 
 
while(1) {
wdt_reset(); // keep the watchdog happy
usbPoll();
}

return 0;
}
The code should be pretty straightforward to understand:

 Include the usbdrv.h to access V-USB functions


 Implement usbFunctionSetup() to handle USB requests (we’ll do that soon)
 In the main function, set up a 1 second watchdog timer that resets the microcontroller if
1000 milliseconds pass without a call to wdt_reset()
 Call usbInit() to initialize the V-USB library
 Enforce USB device re-enumeration using usbDeviceDisconnect(), a 500 ms
delay (while calling the watchdog reset every 2 ms) andusbDeviceConnect()
 Enable interrupts
 Loop forever while calling the watchdog reset and usbPoll()
The reason we are using the watchdog timer is, that our code might for some reason
freeze (for example, corrupt data is read and a bug or eternal loop occurs) and the USB
device would then stop responding. In this case, the wdt_reset() gets no longer
called and after 1 second, the watchdog timer automatically resets our ATtiny, and the
program execution starts again (like you’d just resetted the circuit yourself). While this
is not absolutely necessary, it is a good practice and saves the user from unplugging and
replugging the device if something strange happens.
Another thing you may wonder is why the disconnect/delay/connect -procedure is
needed at all. This is because the host PC can remember the identifier assigned to a USB
device even if our device resets and forgets that identifier. By enforcing re-enumeration
we make sure that both the host PC and our device have the same ID used to
communicate over the USB bus.

Σελίδα 14 από 30 
 
Now let’s see if we can get it all to compile. The easiest way is to use my ready-
made Makefile which also contains the necessary statements for our PC side command-
line client. Put the makefile to your project folder and run make main.hex too see if
it works. This way, you also see the actual commands the Makefile is used to run – there
is nothing special with V-USB, the main things you need to remember is to have -
Iusbdrv in place for the compiler to find the .h files from usbdrv folder, and then just
having the three additional object files (usbdrv.o, oddebug.o, usbdrvasm.o) put into the
same .elf file along with main.o.

Responding to USB control messages

Before we head to PC side code, let’s make our device firmware respond to two basic
commands sent via USB: Turning a LED on and off. USB communication is initiated
from host side and so-called control messages are sent to make USB device return it’s
name or any other desired function. The specification also has room for vendor-specific
control messages. Let’s define two such messages:
#define USB_LED_OFF 0
#define USB_LED_ON 1
Now V-USB library automatically calls our usbFunctionSetup()method when it
receives a vendor-specific control message. The parameter is a 8-byte buffer actually
containing a structure calledusbRequest_t that is defined in usbdrv.h. I suggest
you to check that definition out. At this point we are interested in
the bRequestproperty that will either be 0 (USB_LED_OFF) or 1 (USB_LED_ON) –
we will shortly see how our command-line client on PC side will generate these control
requests, so there is nothing magical happening here – we just send a request from PC
side and interpret it on the other side. Here’s the modified function to turn the LED on or
off:
// this gets called when custom control message is received
USB_PUBLIC uchar usbFunctionSetup(uchar data[8]) {
usbRequest_t *rq = (void *)data; // cast data to correct type

switch(rq->bRequest) { // custom command is in the bRequest field


case USB_LED_ON:
Σελίδα 15 από 30 
 
PORTB |= 1; // turn LED on
return 0;
case USB_LED_OFF:
PORTB &= ~1; // turn LED off
return 0;
}

return 0; // should not get here


}
Of course for this to work we need to set PB0 as output in the beginning of main() (I
suggest just after the “uchar i;” line):
DDRB = 1; // PB0 as output
Now we’re ready to deploy our code to the chip. Just call make flash and the
dependencies should take care of recompiling the main code file automatically.

Windows driver hell

Now if you are doing this project on a Linux box, congratulations, you likely can just get
LibUSB and compile the command-line client to talk to your new device. On Windows,
however, you will need a custom device driver. Especially nasty was the fact that older
example projects on V-USB site don’t work with 64-bit Windows 7 at all. For this
reason, I’ll outline the basic steps for creating and installing your own driver.

Now immediately after you have flashed the ATtiny you should hear the standard
Windows fanfare di-dum sound that tells a new USB device has been plugged in.
Windows will look for a driver and most likely fail. This is where libusb-win32 comes to
the rescue:
1. Go to the libusb-win32 home page (google if it the above link doesn’t work)
2. Locate the heading “Device driver installation” and follow the instructions to download
the latest version. I used libusb-win32-bin-1.2.6.0.zip. Unzip to a subfolder called
“libusb” in the project folder.
3. Run the INF-wizard using the instructions, put the installer somewhere you like (I
recommend a new driver-subfolder in your project folder)

Σελίδα 16 από 30 
 
4. Easiest way to install the driver is to do it as the final step of INF-wizard, there’s a
button for it
5. Replug your device and pray for the best
I could have a separate tutorial covering my trial-and-error methods of getting the USB
device driver to install and work. I hope you have better luck. In case of problems, here
are some helpful tips:

 You can use Windows Device Manager to uninstall wrong drivers for your device, or if
you get and “Unknown device”, update drivers for the device
 Select “Browse my computer for driver software” and point Windows manually for
correct location. Do not mind any “drivers not certified” -alerts, to go through them
 Sometimes Windows manages to cache the device in a certain port and no matter how
many times you unplug and replug it, it gets recognized wrong. Changing USB port or
switching even to a different hub (e.g. from computer case front to back) helped me in
some cases
 Restarting the machine or googling for tools to reset USB cache may help, too :)
This is what the INF-wizard (located in the bin-subfolder of libusb) looks like if you are
lucky (USBexample device visible):

Σελίδα 17 από 30 
 
This is what you can also enter manually if you are not so lucky:

After clicking Next, you get to save your generated driver to a folder. It’s a good idea to
use “Install now…” to avoid installing the drivers manually. My installation hanged 50
% of the time (needed to wait 5 minutes for the installer to exit), but the driver still
seemed to install OK.
Σελίδα 18 από 30 
 
Command-line client

Congratulations! We are almost there! Now we only need to prepare the host-side
software. For compiling it, I recommend the GNU C compiler gcc from MinGW
project and the MSYS tools that you should be able to install along with it, but probably
Visual C and others work just fine. MinGW installer is really hard to locate (big thumbs
down for the wiki authors on usability), but currently trial and error should eventually
get you here.
We already installed libusb-win32 in the previous section, and the needed library and
header files are all there. Just copy (or rename) the oddly named lusb0_usb.h to usb.h in
the libusb/include folder and we’re good to go.

First thing we’ll need to do is to have a little helper function to decipher USB descriptor
strings used for vendor name and device name. For this and also later for communicating
with our device, we will use usb_control_msg() function provided by libusb. You
basically pass the device handle, direction of message (USB_ENDPOINT_IN for device
to host and USB_ENDPOINT_OUT for host to device) and other information used to
define the recipient and direction, and the control message request, index and value
codes, as well as a buffer for input/output, buffer size and maximum timeout for the call.
For those who want to understand the control messages better, I warmly recommend the
surprisingly user-friendly USB 2.0 specification. The part on control messages starts on
page 248 and the first table concerning this should be Table 9-2. You can find the
download link for the spec from part 1 of this tutorial. The specification is rather simple
and defined constant values closely reflect the #defines in libusb so you should
understand everything rather well just by comparing the spec and my example code.
If you wonder what the rest of our helper function does after
calling usb_control_msg, it’s basically checking the return value and response
length, and converting it from UTF16-LE to Latin1 (same as ASCII if there’s no special
characters). The USB descriptor return format is strictly defined to remove any
possibilities for interpretation. This is all explained in chapter 9.5 of the USB

Σελίδα 19 από 30 
 
specification. So here’s the helper function (it’s basically a slightly revised version of the
one used in V-USB PowerSwitch example):
/* Used to get descriptor strings for device identification */
static int usbGetDescriptorString(usb_dev_handle *dev, int index, int langid,
char *buf, int buflen) {
char buffer[256];
int rval, i;

// make standard request GET_DESCRIPTOR, type string and given index


// (e.g. dev->iProduct)
rval = usb_control_msg(dev,
USB_TYPE_STANDARD | USB_RECIP_DEVICE | USB_ENDPOINT_IN,
USB_REQ_GET_DESCRIPTOR, (USB_DT_STRING << 8) + index, langid,
buffer, sizeof(buffer), 1000);

if(rval < 0) // error


return rval;

// rval should be bytes read, but buffer[0] contains the actual response size
if((unsigned char)buffer[0] < rval)
rval = (unsigned char)buffer[0]; // string is shorter than bytes read

if(buffer[1] != USB_DT_STRING) // second byte is the data type


return 0; // invalid return type

// we're dealing with UTF-16LE here so actual chars is half of rval,


// and index 0 doesn't count
rval /= 2;

/* lossy conversion to ISO Latin1 */


for(i = 1; i < rval && i < buflen; i++) {
if(buffer[2 * i + 1] == 0)
buf[i-1] = buffer[2 * i];
else
buf[i-1] = '?'; /* outside of ISO Latin1 range */
}
buf[i-1] = 0;

return i-1;
}
Now we can use this helper function when we iterate over USB devices to recognize our
own device with vendor name “codeandlife.com” and device name “USBexample”.
Σελίδα 20 από 30 
 
The libusb-win32 API documentation gives us a good starting point in the examples
section. I won’t be hand-holding you too much on this function, the basic logic is just to
loop through every USB bus and every USB device in them, open a device and ask it’s
vendor and device name, return it if it matches or close and proceed to next one if it does
not:
static usb_dev_handle * usbOpenDevice(int vendor, char *vendorName,
int product, char *productName) {
struct usb_bus *bus;
struct usb_device *dev;
char devVendor[256], devProduct[256];

usb_dev_handle * handle = NULL;

usb_init();
usb_find_busses();
usb_find_devices();

for(bus=usb_get_busses(); bus; bus=bus->next) {


for(dev=bus->devices; dev; dev=dev->next) {
if(dev->descriptor.idVendor != vendor ||
dev->descriptor.idProduct != product)
continue;

/* we need to open the device in order to query strings */


if(!(handle = usb_open(dev))) {
fprintf(stderr, "Warning: cannot open USB device: %sn",
usb_strerror());
continue;
}

/* get vendor name */


if(usbGetDescriptorString(handle, dev->descriptor.iManufacturer,
0x0409, devVendor, sizeof(devVendor)) < 0) {
fprintf(stderr,
"Warning: cannot query manufacturer for device: %sn",
usb_strerror());
usb_close(handle);
continue;
}

/* get product name */


Σελίδα 21 από 30 
 
if(usbGetDescriptorString(handle, dev->descriptor.iProduct,
0x0409, devProduct, sizeof(devVendor)) < 0) {
fprintf(stderr,
"Warning: cannot query product for device: %sn",
usb_strerror());
usb_close(handle);
continue;
}

if(strcmp(devVendor, vendorName) == 0 &&


strcmp(devProduct, productName) == 0)
return handle;
else
usb_close(handle);
}
}

return NULL;
}
Oh and the 0x0409 is the language code for English, this is also found from the USB
specification. Note how the vendor and device names need to be fetched using our helper
function – the standard device descriptor in dev only tells the parameter values we need
to use for usbGetDescriptorString() to get them (fields iManufacturer and
iProduct). This is of course understandable as device descriptor is constant length but the
vendor and device names have varying length.
Now that we got past all the sillyness of helper functions to scan all USB devices and
return their specially formatted descriptor messages in order to get one simple device
handle, the rest of the code is very straightforward. Only thing making the USB
communication different from standard structure of accessing a file are the calls
to usb_control_msg(), otherwise the general structure of open / do stuff / close
applies:
int main(int argc, char **argv) {
usb_dev_handle *handle = NULL;
int nBytes = 0;
char buffer[256];

if(argc < 2) {
printf("Usage:\n");
Σελίδα 22 από 30 
 
printf("usbtext.exe on\n");
printf("usbtext.exe off\n");
exit(1);
}

handle = usbOpenDevice(0x16C0, "codeandlife.com", 0x05DC, "USBexample");

if(handle == NULL) {
fprintf(stderr, "Could not find USB device!n");
exit(1);
}

if(strcmp(argv[1], "on") == 0) {
nBytes = usb_control_msg(handle,
USB_TYPE_VENDOR | USB_RECIP_DEVICE | USB_ENDPOINT_IN,
USB_LED_ON, 0, 0, (char *)buffer, sizeof(buffer), 5000);
} else if(strcmp(argv[1], "off") == 0) {
nBytes = usb_control_msg(handle,
USB_TYPE_VENDOR | USB_RECIP_DEVICE | USB_ENDPOINT_IN,
USB_LED_OFF, 0, 0, (char *)buffer, sizeof(buffer), 5000);
}

if(nBytes < 0)
fprintf(stderr, "USB error: %sn", usb_strerror());

usb_close(handle);

return 0;
}
Note that the second parameter for usb_control_msg now uses
USB_TYPE_VENDOR to indicate we are sending a vendor-specific control message.
You can also see that the request, value and index parameters (here
USB_LED_ON/OFF, 0, 0) can be freely used to communicate with our code on device
side.
You can grab the complete code for usbtest.c from project zip filewith two #defines
that match the ones in main.c and compile withmake usbtest.exe (actually just
“make” suffices, as usbtest.exe is one of the default targets). Now you should be able to
turn the LED on and off using commands “usbtest on” and “usbtest off”. Note again that

Σελίδα 23 από 30 
 
you need MinGW installed and “gcc” working in order to do this. The full compile
command that “make” runs is:
gcc -I ./libusb/include -L ./libusb/lib/gcc -O -Wall usbtest.c -o usbtest.exe -lusb
Wow! That is all. I think this is enough for anyone to grasp in a single reading, so I will
be splitting additional V-USB tricks into a fourth part of this tutorial that I will write
later. Spending 5 hours in one setting to write this one is enough. :)

Σελίδα 24 από 30 
 
AVR ATtiny USB Tutorial Part 4
All right. Now that we got the basic USB code working in part 3, it’s time to wrap things
up in this tutorial series. This fourth section will explain how to send data from your
device to PC and also the other way around. I may later do a fifth part on how to make a
USB HID device like a keyboard or mouse, so if you haven’t already, I’d recommend
subscribing to the RSS feed to get updates.

Sending data from device to PC

If you look carefully at our command-line client code, you probably noticed that the
control messages sent to toggle the led are of type USB_ENDPOINT_IN and we have a
256-byte buffer in place to receive any data the device sends. So far we have not
received any data and the return value stored in nBytes has been zero. Let’s change
that.

If you read through the documentation in usbdrv/usbdrv.h, more specifically the


comments for usbFunctionSetup() we used to toggle our LED on device side, you
can see that there are two ways to return data from usbFunctionSetup():
1. Set the global pointer usbMsgPtr to a (static RAM) memory block and return the
length of data from the function
2. Define usbFunctionRead() function to do it yourself
We will be using the first method. First we define an additional
message USB_DATA_OUT and a static buffer to store the data we want to send from
device to PC:
#define USB_DATA_OUT 2

static uchar replyBuf[16] = "Hello, USB!";


Then we add a new case to usbFunctionSetup() to return the contents of that
buffer:

Σελίδα 25 από 30 
 
case USB_DATA_OUT: // send data to PC
usbMsgPtr = replyBuf;
return sizeof(replyBuf);
Whoa, that was easy! V-USB does all the rest for us.

Now the only thing needed to do is to update our command-line client to include our
new “out” command. Copy the #define from above to the beginning
of usbtest.c (right after the existing two #defines is a good place) and add a third if
statement to the main method:
} else if(strcmp(argv[1], "out") == 0) {
nBytes = usb_control_msg(handle,
USB_TYPE_VENDOR | USB_RECIP_DEVICE | USB_ENDPOINT_IN,
USB_DATA_OUT, 0, 0, (char *)buffer, sizeof(buffer), 5000);
printf("Got %d bytes: %sn", nBytes, buffer);
}
Just run “make” and “make flash” to compile and update the firmware. Now try “usbtest
out” – how easy was that? With 128 bytes of RAM on ATtiny and V-USB using a lot of
that, you will not be able to transmit the maximum of 254 bytes this method offers
(implement usbFunctionRead() to transfer longer buffers piece by piece), but even
16 characters is quite a lot! And of course you can chain multiple control requests if you
want to receive more data.

Sending data from PC to device

If you only need to send a few bytes of additional data, you can use
the wValue and wIndex parameters for that. Let’s make a new USB_DATA_WRITE
that changes the “USB!” part of our return message to any four characters. First a new
#define (add to bothmain.c and usbtest.c):
#define USB_DATA_WRITE 3
Then a new case to firmware side:

case USB_DATA_WRITE: // modify reply buffer


replyBuf[7] = rq->wValue.bytes[0];
replyBuf[8] = rq->wValue.bytes[1];
replyBuf[9] = rq->wIndex.bytes[0];
Σελίδα 26 από 30 
 
replyBuf[10] = rq->wIndex.bytes[1];
return 0;
Note that the wValue and wIndex are actually unions that allow you to either access
the whole 16-bit word value with wValue.word or alternatively directly access the
lower and upper bytes withwValue.bytes[0] and wValue.bytes[1],
respectively (I’m not sure if the order of upper and lower bytes in the word depends on
endiandness on PC side, but that’s how it works for me).
On command-line side, we add another “else if” that sends fixed letters for simplicity:

} else if(strcmp(argv[1], "write") == 0) {


nBytes = usb_control_msg(handle,
USB_TYPE_VENDOR | USB_RECIP_DEVICE | USB_ENDPOINT_IN,
USB_DATA_WRITE, 'T' + ('E' << 8), 'S' + ('T' << 8),
(char *)buffer, sizeof(buffer), 5000);
}
Just compile with “make”, update firmware with “make flash” and try out the new
“usbtest write” command. Note that the command itself does not print anything, you
need to run “usbtest out” before and after to see how the buffer is changed. Also note
that until you reset or unplug the device, the new message “Hello, TEST” will stay in the
device.

Sending more data to the device


Using wValue and wIndex is fine for many applications, but if we’d like to update the
whole 16-byte buffer, we’d need at least four control messages. Let’s now create our
first USB_ENDPOINT_OUTmessage. On PC side the code is pretty simple:
#define USB_DATA_IN 4

...

} else if(strcmp(argv[1], "in") == 0 && argc > 2) {


nBytes = usb_control_msg(handle,
USB_TYPE_VENDOR | USB_RECIP_DEVICE | USB_ENDPOINT_OUT,
USB_DATA_IN, 0, 0, argv[2], strlen(argv[2])+1, 5000);
}

Σελίδα 27 από 30 
 
Note that the buffer size is one more than just the argument 2 length to accommodate the
string-terminating NULL character. On device side we add a new variable to keep track
how many bytes we are to read, and use a special return value
fromusbFunctionSetup() (this is all well documented in usbdrv.h):
#define USB_DATA_IN 4

static uchar dataReceived = 0, dataLength = 0; // for USB_DATA_IN

...

case USB_DATA_IN: // receive data from PC


dataLength = (uchar)rq->wLength.word;
dataReceived = 0;

if(dataLength > sizeof(replyBuf)) // limit to buffer size


dataLength = sizeof(replyBuf);

return USB_NO_MSG; // usbFunctionWrite will be called now


Now that the request is of type control-out (USB_ENDPOINT_OUT on PC side code)
and we have returned USB_NO_MSG (with byte-size return value it’s actually 255, also
the reason why data from device can be only 254 bytes tops), V-USB will call a function
named usbFunctionWrite() to receive the data sent:
USB_PUBLIC uchar usbFunctionWrite(uchar *data, uchar len) {
uchar i;

for(i = 0; dataReceived < dataLength && i < len; i++, dataReceived++)


replyBuf[dataReceived] = data[i];

return (dataReceived == dataLength); // 1 if we received it all, 0 if not


}
Last thing to do is to modify usbconfig.h to tell V-USB we have provided such a
function:
#define USB_CFG_IMPLEMENT_FN_WRITE 1
Now you should be able to remake and flash the project and use the new “usbtest in
abcdefgh” command to replace the values in device’s 16 byte buffer. Note that if you
supply a string longer than 15 characters, the terminating NULL character will not fit

Σελίδα 28 από 30 
 
into buffer and “usbtest out” may print out garbage after the 16 characters. This is just a
demo application so we ignore that for now. :)

Final remarks

Congratulations! You have successfully built and coded a USB device that is able to
receive commands and send/receive arbitary data. Once you have understood how V-
USB callsusbFunctionWrite(), it should also be pretty straightforward to
make usbFunctionRead() in similar manner to send longer data to PC in multiple
parts.
I have compiled the Makefile and source code files as well as relevant portions of V-
USB and libusb-win32 for compiling the project, as well as the final schematic into one
.zip file:

usb_tutorial_20120204.zip
Update: If you don’t for some reason like the command-lineusbtest.exe, or would
like to develop a graphical client, Günter Meinel has created an excellent GUI version of
the test program using Borland C++ Builder 3.0. You can get it with source code from
here:
http://home.arcor.de/guentermeinel/download.html
I hope you have enjoyed the tutorial, and hope to see you again on this site, I’m planning
to write at least a bit about 7 segment LED multiplexing, driving LCDs directly with
AVR, and PicoScope USB oscilloscope in the future, and may add new subjects as I get
further myself.

This is the end of “the main arc” of my V-USB tutorial, but I recommend you to read my
later posts in the V-USB tutorial series, including:

 Making HID devices


 Running the MCU on 5 volts and using zener diodes for D+ and D-
 Using ATtiny45/85 with internal 16.5 MHz clock

Σελίδα 29 από 30 
 
 Outputting larger buffers of data
 

Σελίδα 30 από 30 
 

You might also like