The 25¢ I2C Adapter: an Inexpensive Method of Interfacing
I2C Devices with Personal Computers by Leveraging the Existing
Display Data Channel (DDC)
April 1, 2008 — Phillip Burgess — http://www.PaintYourDragon.com/uc/
The author assumes no liability for injury or damage resulting from the use of this technique.
Update 6/9/08: After exploring several avenues I’ve concluded there is no practical manner of implementing this technique in Microsoft Windows. Sorry! Windows users needing I2C capabilities are best served by existing USB-based solutions.
Update 5/24/08: Linux support has been added, as well as sample code for the Nintendo Wii Nunchuk controller and the BlinkM “smart LED.”
This project demonstrates the concept of using the Display Data Channel (DDC) lines present on many graphics cards as frugal method of interfacing with I2C peripherals.
I2C (Inter-Integrated Circuit) is a two-wire serial bus typically used in computers for low-level communication between internal components, but is also seen in robotics and hobbyist electronics for interfacing a variety of sensors, displays and actuators. I2C connections are often readily available on microcontrollers and esoteric embedded systems, but there’s little call for it on mainstream personal computers…unless you happen to be among the aforementioned robotics and electronics hobbyists, in which case a USB to I2C adapter device is typically used, often at considerable expense.
DDC — supported by most graphics cards and monitors produced within the past several years — is a communication channel within a video cable that allows the computer and display to negotiate mutually compatible resolutions and permit software control of monitor functions normally accessed with physical buttons on the display.
DDC is, in fact, simply an implementation of an I2C bus with a few established rules. By tapping into this connection between the computer and monitor (or making use of the DDC lines on an spare unused video port, such as the external monitor connection on a laptop), one can interface with many I2C devices at virtually no expense, bypassing the usual need for an adapter device entirely.
Commercial USB to I2C adapters can cost $100, $250, sometimes even more, and driver support is spotty for systems outside the popular Windows fold. Homebrew alternatives exist, but assume a prior investment and knowledge in microcontroller development. The method outlined here requires only a modified video cable…I just bought one at a garage sale for 50 cents, and that’s enough to make two such adapters!
Being a quick and dirty hack, there are some limitations to this approach:

There are still plenty of fun projects that can be attempted, so with those caveats out of the way, let’s proceed! All that’s required is a modified video cable, or, if tapping an unused video port, simply the appropriate end connector (though it’s often cheaper to buy the full cable and cut it in half). If the port will still be used for a monitor connection, it’s necessary to create a “Y” or “hydra” cable that allows one’s I2C device(s) and the monitor to be attached at the same time (sharing the DDC lines). When modifying an existing cable, after removing just the outer shielding, the DDC wires are usually visible at this point, while the lines carrying video signals are wrapped into further-shielded bundles. A multimeter or continuity tester will help correlate wires to pins. DDC establishes four wires: +5VDC, ground, serial data and serial clock, the pin numbers of which will vary depending on the type of video port used. Refer to pinouts.ru, Wikipedia or other sources if you need more help than the diagrams below can provide.

The DDC clock and data lines correspond directly to I2C clock and data. Pull-up resistors are not required — that’s already implemented in the graphics card — so it’s just the cable, your I2C device(s) and a bit of wire.
For a 15-pin VGA cable, the pins of interest are:
| Pin | Description |
|---|---|
| 5 | Ground |
| 9 | +5VDC |
| 12 | Data |
| 15 | Clock |
Note that on some VGA cables (perhaps even a majority), pin 9 is not present (occasionally others as well). Just examime the male end connector to see if it’s even worth dissecting — the missing pins are quite apparent. You’ll want to use a cable with the full complement of pins, or work from just a bare D-sub 15 connector (if scrounging at the local swap meet for cheap cables is not your style, these connectors can be found at Radio Shack for $1.99).
For a DVI cable:
| Pin | Description |
|---|---|
| 6 | Clock |
| 7 | Data |
| 14 | +5VDC |
| 15 | Ground |
For an HDMI Type A cable:
| Pin | Description |
|---|---|
| 15 | Clock |
| 16 | Data |
| 17 | Ground |
| 18 | +5VDC |
Connect the corresponding I2C lines to one’s device as required, and the other end of the cable to the desired video port. Keep in mind that if using a “hydra” cable with a monitor connected, one’s power budget should be trimmed by a few extra milliamps to ensure enough power for the DDC device within the display. There should be enough current to drive a microcontroller and an LED or possibly two…but if the I2C device(s) to be connected are going to use any substantial amount of current, it may be wise to power the device(s) externally and use an I2C buffer circuit where it connects to the cable (likewise and mandatory if the device operates at a different voltage). Also, if using with a display connected, two I2C addresses are reserved for the monitor and should not be used: 0x37 and 0x50.
While the low-level details of the I2C protocol are taken care of by the i2c.o library and the OS-specific libraries on which it in turn depends, the message sequence required by any given device must be implemented within one’s own code and will vary from one device to the next. Manufacturers’ datasheets will document the specific I2C messages needed, and the amount of code required is not onerous. Several short example programs are included: reading a Nintendo Wii Nunchuk controller accessory (currently Linux only), reading a Microchip TCN75A temperature sensor, writing to a Microchip 256 Kbit serial EEPROM, flashing a BlinkM “smart LED,” and another that interfaces with a Mindsensors I2C-SC8 8 channel servo controller.
While the source code for Linux is simpler, the setup procedure unfortunately is not. You’ll need root access. Frequently. This is how I install it under Ubuntu 7.04 and later:
sudo apt-get install lm-sensors
sudo modprobe i2c-devThe above is for an ATI Radeon. You might need a different module specific to your system. Use the command “sudo modprobe -l” (that’s a lowercase L, not a one) for a full list of kernel modules, and try to locate one that matches your graphics chip vendor.)
sudo modprobe radeonfb
Alternately, to load the I2C modules automatically at boot-time, edit the file /etc/modules and add these lines:
i2c-dev
radeonfb
Again, assuming Radeon graphics here; put your actual module name there.
Once the kernel modules are loaded, the files /dev/i2c-* will then correspond to each I2C bus on the system. Not all of these relate to video out though; some may correspond to internal I2C buses in the computer (e.g. system health, RAM controller, etc.). The i2cdetect program (installed as part of lm-sensors) will list and briefly describe each I2C interface present. Look for the one that matches the desired video port. On the ThinkPad I used for development, /dev/i2c-2 corresponds to the VGA port. If the output of i2cdetect does not mention any video ports (VGA, DVI, or HDMI), then it’s probable that the driver does not support I2C and this hack will not work, or a different video card module may simply need to be loaded.
The C API is super-basic, with just three functions:
I2Copen()
Opens the I2C (DDC) bus for subsequent communication. As some systems may support multiple “heads” and thus have multiple DDC interfaces, the code in i2c-osx.c or i2c-linux.c may to be adapted to your specific situation. On Macs the code is currently rigged to use the last connection found; on a single-head system (e.g. Mac mini), this would be the single video out port, while on a potentially two-headed system (e.g. MacBook, late-model iMac) this would be the video out port, regardless of whether there’s a monitor attached. Multi-headed systems (e.g. Mac Pro) may require some tweaks to the code to access a specific graphics card and port. For Linux, the code is rigged to open /dev/i2c-2, which corresponds to the VGA connector on my ThinkPad system used during development, but you’ll probably want to change this for your particular situation. Review the notes above regarding Linux installation.
I2Cmsg(short address, unsigned char *sendBuf, int sendBytes, unsigned char *replyBuf, int replyBytes)
Issues an I2C request and/or reads response over the previously-opened I2C bus. The address of the device is often factory-defined, and the size and content of the request data are entirely device-dependent (see the example code for several practical cases). You’ll need to work with the datasheet provided for your specific device.

An important point should be made here regarding the first parameter: there is some disagreement as to just what constitutes a valid I2C address. The I2C protocol defines data packets in 8-bit chunks, with the first byte of an I2C message containing a 7-bit device identifier and one bit indicating whether this is a read or write operation. Most presume the 7-bit value to define the address, but in some implementations they’ll include the read/write bit in referring the address, or — because the R/W flag is the least signficant bit — document the device as having two sequential addresses (one each for reading and writing). Linux and OSX disagree here; this API follows the 7-bit convention and adjusts the value passed as needed for the host operating system. If a device does not seem to be responding at the address given in the datasheet, try dividing the number by two, which shifts off the R/W bit to produce a 7-bit address.
I2Cclose()
Closes the previously-opened I2C bus.
Return values, defined in i2c.h, are currently very limited. All of the functions will return a value of zero (or I2C_ERR_NONE) on successful completion. The I2Copen() function may return I2C_ERR_OPEN if no available DDC bus can be found (or, on Linux, if not running as root). On the Mac, I2Cmsg() may return various error values defined in /System/Library/Frameworks/IOKit.framework/Headers/IOReturn.h (e.g. 0x2c0 or kIOReturnNoDevice).
Neither version of the library is especially comprehensive; there are quite a few limitations and assumptions made at present (for example, only one I2C bus can be open at a time). You might want to look inside and use this as a starting point for your own code. The core meat-and-potatoes of opening and communicating over the bus can be distilled to just a couple dozen essential lines of code, the rest being error handling, packaging and comments.