Return to Projects


USB-GPIO – Simple Digital I/O for a PC

Ever need just a couple of bits of Digital I/O for your PC? Maybe want to bit-bang and SPI interface to simple test a new device? Used to be the PC’s parallel port was the answer. These days, that’s no longer an option.

Here’s a quick solution. Sixerdoodle USB General Purpose IO board (USB-GPIO). One small circuit board, and a bit of software, and magically, you’ve got simple IO for your PC again. You can even use Perl to read and write data bits! The hardware can even be configured for either 3.3v digital I/O or 5v digital I/O so that it’s compatible with whatever you need to interface to.


Here’s the circuit diagram. It’s pretty simple. The 12 pin header is an edge connector, so you can plug this directly into your breadboard. The 3×2 header allows reprogramming the firmware.

Sixerdoodle USB GPIO v2 circuit

Sixerdoodle USB GPIO v2 circuit

The three solder jumpers configure the power:

  • SJ2 open and SJ3 closed selects the operation with the 3.3v regulator (3.3v Digital I/O signals, Zener not needed).
  • SJ2 closed, SJ3 open, configures for 5v Digital I/O (Zener must be in place)
  • SJ1 (1-2)(3) = off board power; (1)(2-3) = USB powered; (1-2-3) = USB Powered, USB 5vdc available on 12 pin connector

The 12×1 PIN header can be used to connect the USB-GPIO board directly to a breadboard so you can connect the breadboard signals to your PC. The available signals from pin 1 out are: GND, PA7, PA6, PA5, PA4, PA3, PA2, PA1, PA0, VCC (either 3.3vdc or 5vdc), USB 5vdc or power in (depending on SJ1 selection), GND

LED1, by default is simply a status indicator. My V-USB software toggles this LED with each USB command received.

Here’s the USB-GPIO v2 bare board with component locations.

USB-GPIO bare board

USB-GPIO bare board

The component list is pretty short and some of the parts are optional.

15pf – 0603 C1,C2 3.3Vdc – Reg
optional, only for 5v I/O capability
MCP1700T-3302E or equivalent in SOT-23 pkg
LED – 0603 LED1
optional, indicator only
1K – 0805 R1
optional, only if using LED1
BZB84 – Zener
optional if using 3.3v reg
BZB84-B3V6,215 or equivalent in SOT-23 pkg
68R – 0805 R2, R4
optional, for impedance matching
short with wire if unused
1K5 – 0805 R5 ATTINY261 SOICW-20
XTAL 12Mhz – Q1 Mini-USB connector
10uf – thru hole C3 12X1 PIN
3×2 PIN
optional, only if you want to reprogram the ATTiny261

Here’s the fully populated USB-GPIO v2 board populated. Yes, it’s mostly surface mount components, but it’s completely feasible to hand solder this together. In fact this photo is of a board I hand soldered with a soldering iron with a big wedge tip just to prove that it can be done.

USBGPIO v2 populated board

USBGPIO v2 populated board


There’s two parts to the software for this device, the firmware for the ATTiny261 and the windows driver and DLL.

For the firmware, use Objective Developments V-USB. Three pluses to using V-USB for the firmware. First, it’s still being actively supported. Second, the majority of the code is in C, so it’s easy to enhance. Third, it resolves the problem of how to get a valid USB ID by providing a general purpose one everyone can use as long as you follow their usage guidelines (see the V-USB site for more info)

For the Windows side of things, LibUSB-Win32, a universal driver for USB devices that works on Windows XP, Windows 7 both 32 and 64 bit mode. There are multiple wrappers and bindings making LibUSB-Win32 usable from a variety of languages from C# and .Net to Python, Perl, and Ruby. Take your pick and you should be able to easily write code to access the digitial I/O on the Sixerdoodle USB-GPIO device.

So, what’s the end result? an USB general purpose 8 bit I/O interface that you can control almost as easily as that old parallel port!


You might ask why even do this? Well, here’s a use case. There are times when I need to cycle the power on my cable modem to get it working again. So now I’ve got a Perl script that automatically pings a selected web site. if that ping fails, the Perl code uses the USB-GPIO board to cycle the power on the cable modem. PA2 drives a relay which controls the power to the cable modem. Simple Perl code to turn a single IO bit on and off looks like this:

# Power off modem
print "power off modem and network n";
$value = 0x0000;
$index = 0x0000;
print "Set Port Data ", $value, " returned ",
$dev->control_msg( USB_ENDPOINT_IN | USB_TYPE_VENDOR,
SetOutDataPort, $value, $index, $bytes, 1, 250 ),"n";

# Wait a bit...
sleep 10;

# Power on modem
print "power modem and network back upn";
$value = 0x0004;
$index = 0x0000;
print "Set Port Data ", $value, " returned ",
$dev->control_msg( USB_ENDPOINT_IN | USB_TYPE_VENDOR,
SetOutDataPort, $value, $index, $bytes, 1, 250 ),"n";

Here’s a v1 version of the board showing how the 12×1 PIN header works on a breadboard:

USBGPIO on a proto board

USB-GPIO on a proto board


For firmware, this circuit is compatable with the V-USB firmware. Basically start with their download, modify the usbconfig.h file to match the specific hardware requirements and you’re good to go. You do have to add code for what you want the firmware to actually do. In my case everything ties into the stub usbFunctionSetup since this routine traps the Vendor Control messages (same way as the Atmel code firmware did).

So you’ll find my V-USB.c with it’s simple main() routine and usbFunctionSetup routine in the below zip file.

You can download my specific V-USB implementation here. Features implemented include 8 bits of digital I/O, simple ADC converter, i2c and SPI interfaces. The i2c routines come from i2c master library by Peter Fleury

Feel free to modify my code to add your own user functions!


Now about that LibUSB-Win32 stuff….

LibUSB-Win32 is a generic USB driver and a corresponding DLL. You can actually use LibUSB-Win32 to talk to any USB device, but just using it here for talking to the modified AVR309 firmware. Your program talks to the DLL, the DLL talks to the LibUSB-Win32 driver and the driver talks directly to your USB device. The modified AVR309 firmware is really simple USB, it utilizes USB Control Messages for all data transfers, so that greatly simplifies the interface and any programming you need to do.

Once you’ve unpacked the LibUSB-Win32 software, The first step you have to do is tell windows to use the LibUSB=Win32 driver for your USB device. Do this by running the inf-wizard.exe delivered with LibUSB-Win32. Run the wizard with your device plugged in, select your device and the wizard will generate the appropriate inf file to connect it’s driver to the selected USB device. From that point on, you can use the LibUSB-Win32 DLL to communicate with your USB device.

As mentioned earlier, the firmware is designed to send small chunks of data back and forth through the a USB control message and implements it’s own specific functions through a vendor control command with the “Value”, “Index”, and “Bytes” arguments being passed back and forth. “Value” and “Index” are data send to the USB device and “Bytes” contains the resulting data from.

The functions accessible through the Vendor Control message are:

  • DoSetDataPortDirection = 3, Value = byte sent to DDRA
  • DoGetDataPortDirection = 4, Bytes[0] = returned DDRA
  • DoSetOutDataPort = 5, Value = byte sent to PortA
  • DoGetOutDataPort = 6, Bytes[0] = returned PortA
  • DoGetInDataPort = 7, Bytes[0] = returned PinA
  • DoUserFunction0 = 8, Bytes[0] = version number
  • DoGetADC = 10, Bytes[0..1] = returned ADCL/ADCH
  • DoSPISetup = 11, sets PA0-PA3 as DI/DO/USCK/Select
  • DoSPIM2 = 12, Value[0..1], Index[0..1] = 4 bytes to send out SPI mode 2
  • i2c_init = 20, set up for i2c transfers
  • i2c_start = 21, Value[0] = i2c address+mode= 1 byte issue an i2c start command
  • i2c_stop = 22, i2c stop command
  • i2c_write = 23, Value[0] = 1 byte i2c write
  • i2c_readAck = 24, bytes[0] = 1 byte i2c read w/ Ack
  • i2c_readNak =25, bytes[0] = 1 byte i2c read w/ Nak

Again, simple Perl script showing an example of using the Vendor Command “setOutDataPort” to output a 04x to the ATTiny261’s DDRA port.

$value = 0x0004;
$dev->control_msg( USB_ENDPOINT_IN | USB_TYPE_VENDOR,
SetOutDataPort, $value, $index, $bytes, 1, 250 );

There are some additional steps necessary to use the LibUSB-Win32 API to get the API to locate your USB device and start the connection before you can send control messages. Check the LibUSB-Win32 site for more info and look at my for a complete example.

Note that the USB device ID supplied by V-USB is a generic, shared device ID. There will be other devices out there using this ID so you must enumerate the list of all connected USB devices and pick only the one with the correct Product and Manufacturer name. You cannot rely on the USB device ID alone!

Note – if you try installing Perl Device-USB follow the documentation on installing with StrawberryPerl. it’s pretty close. use cpan to do the install. Don’t forget to set environment variables outlined in that documentation. cpan’s got to compile some stuff against the libUSB-win32 libraries. You may also need to rename lusb0_usb.h to usb.h to get cpan Device::USB to correctly pull in the libusb-win32 include file.

and finally…. just to finish up, here’s the C code to do an equivalent “toggle PA0/PA1” to show that you can also do this with straight C if you want. You don’t have to use Perl. (note – I need to update this C routine with the V-USB ID and device ID enumeration requirements as well, but again it’s still a good template to follow)

You can download all the software here