Have you ever wondered how your computer knows all the characteristics of your monitor— the supported resolutions, the model, and even the serial number? Most monitors use a system called DDC to communicate this information to the computer.1 This information is transmitted using the I2C communication protocol—a protocol also popular for connecting hobbyist devices. In this post, I look inside a VGA monitor cable, use a tiny PocketBeagle (a single-board computer in the BeagleBone family) to read the I2C data from an LCD monitor, and then analyze this data.
To connect to the monitor, I cut a VGA cable in half and figured out which wire goes to which pin.3 The wire (above) is constructed in an interesting way, more complicated than I expected. The red, green, blue and horizontal sync signals are transmitted over coaxial-like cables formed by wrapping a wire a spiral of thin copper wires for shielding.2 The remaining signals travel over thinner plain wires. Several strands of string form the structural center of the VGA cable, and the ten internal wires are wrapped in a foil shield and woven outer shield.
The photo above shows the male VGA connector on each end of the cable. The function assigned to each pin is shown in the table below. The I2C clock (SCL) and data (SDA) are the important pins for this project. The wire colors are not standardized; they refer to my VGA cable and may be different for a different cable.
The 5 volt wire in the cable has a clever purpose. This wire allows the computer to power the EEPROM chip that provides the configuration data. Thus, the computer can query the display’s characteristics even if the display is turned off or even unplugged from the wall.
Reading the configuration data
To read the data over I2C, I used the PocketBeagle, a tiny Linux computer that I had handy. (You could use a different system that supports I2C, such as the Raspberry Pi, Beaglebone or Arduino.) I simply connected the I2C clock (SCL), data (SDA) and ground wires from the VGA cable to the PocketBeagle’s I2C pins as shown below.
Simple Linux commands let me access I2C. First, I probed the I2C bus to see what devices were present, using the
i2cdetect command. (Many devices can be connected to an I2C bus, each assigned a different address.) The output below shows that devices 30, 37, 4a, 4b and 50 responded on I2C bus 1. Device 50 is the relevant I2C device, assigned to the configuration information. Device 37 is DDC/CI, allowing monitor settings to be controlled by the computer, but I’ll ignore it for this post. Devices 30, 4a, and 4b are a mystery to me so leave a comment if you know what they are.
$ i2cdetect -y -r 1 0 1 2 3 4 5 6 7 8 9 a b c d e f 00: -- -- -- -- -- -- -- -- -- -- -- -- -- 10: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 20: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 30: 30 -- -- -- -- -- -- 37 -- -- -- -- -- -- -- -- 40: -- -- -- -- -- -- -- -- -- -- 4a 4b -- -- -- -- 50: 50 -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 60: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 70: -- -- -- -- -- -- -- --
Next, I used the
i2cdump command to read 128 bytes from device 50’s registers, providing the raw VGA information. The hex values are on the left and ASCII is on the right.
$ i2cdump -y -r 0-127 1 0x50 b 0 1 2 3 4 5 6 7 8 9 a b c d e f 0123456789abcdef 00: 00 ff ff ff ff ff ff 00 04 69 fa 22 01 01 01 01 ........?i?"???? 10: 12 19 01 03 1e 30 1b 78 ea 3d 25 a3 59 51 a0 25 ?????0?x?=%?YQ?% 20: 0f 50 54 bf ef 00 71 4f 81 80 81 40 95 00 a9 40 [email protected][email protected] 30: b3 00 d1 c0 01 01 02 3a 80 18 71 38 2d 40 58 2c ?.?????:[email protected], 40: 45 00 dd 0c 11 00 00 1e 00 00 00 fd 00 32 4c 1e E.???..?...?.2L? 50: 53 11 00 0a 20 20 20 20 20 20 00 00 00 fc 00 56 S?.? ...?.V 60: 45 32 32 38 0a 20 20 20 20 20 20 20 00 00 00 ff E228? .... 70: 00 46 34 4c 4d 51 53 31 32 38 35 34 36 0a 00 bb .F4LMQS128546?.?
Understanding the monitor’s EDID data
The configuration data is encoded in the EDID (Extended Display Identification Data) format, so it’s not immediately obvious what the data means. But the format is well-documented, so it’s not too hard to figure out. For instance, the first 8 bytes
00 ff ff ff ff ff ff 00 are the header. The next two bytes
04 69 encode three 5-bit characters for the manufacturer ID, in this case “ACI” – Asus Computer International. (The data format uses a lot of annoying bit manipulations like these to make the data compact.) Near the end of the output, the ASCII strings “VE228” and “F4LMQS128546” are clearly visible; these are the monitor’s model number and serial number.
I made a simple Python program to decode the data, giving the following results:
Header: Manufacturer: ACI Product code: 8954 Week: 18 Year: 2008 Edid version 1, revision 3 Analog input Levels: +0.7/-.03 Blank-to-black setup (pedestal) expected Separate sync supported Composite sync supported Sync on green supported Horizontal screen size: 48cm Vertical screen size: 27cm Display gamma: 2.200 DPMS standby supported DPMS suspend supported DPMS active-off supported Display type (analog): RGB color Preferred timing mode in descriptor block 1 Chromaticity coordinates: r: (0.637, 0.351), g: (0.319, 0.626), b: (0.145, 0.061), w: (0.313, 0.329) Established timings: 720x400 @ 70 Hz 640x480 @ 60 Hz 640x480 @ 67 Hz 640x480 @ 72 Hz 640x480 @ 75 Hz 800x600 @ 56 Hz 800x600 @ 60 Hz 800x600 @ 72 Hz 800x600 @ 75 Hz 832x624 @ 75 Hz 1024x768 @ 60 Hz 1024x768 @ 72 Hz 1024x768 @ 75 Hz 1280x1024 @ 75 Hz Standard timing information: X res: 1152, aspect 4:3, Y res (derived): 864), vertical frequency: 75 X res: 1280, aspect 5:4, Y res (derived): 1024), vertical frequency: 60 X res: 1280, aspect 4:3, Y res (derived): 960), vertical frequency: 60 X res: 1440, aspect 16:10, Y res (derived): 900), vertical frequency: 60 X res: 1600, aspect 4:3, Y res (derived): 1200), vertical frequency: 60 X res: 1680, aspect 16:10, Y res (derived): 1050), vertical frequency: 60 X res: 1920, aspect 16:9, Y res (derived): 1080), vertical frequency: 60 Descriptor 1: Detailed timing descriptor: Pixel clock: 148500kHz Horizontal active pixels: 1920 Horizontal blanking pixels: 280 Vertical active lines: 1080 Vertical blanking lines: 45 Horizontal front porch pixels: 88 Horizontal sync pulse pixels: 44 Vertical front porch lines: 4 Vertical sync pulse lines: 5 Horizontal image size: 477mm Vertical image size: 268mm Horizontal border pixels: 0 Vertical border lines: 0 Digital separate sync VSync serration Positive horizontal sync polarity Descriptor 2: Display range limits Minimum vertical field rate 50Hz Maximum vertical field rate 76Hz Minimum horizontal field rate 30Hz Maximum horizontal field rate 83Hz Maximum pixel clock rate: 170Mhz Default GTF Descriptor 3: Display name VE228 Descriptor 4: Display serial number F4LMQS128546
As you can see, the EDID format crams a lot of configuration information into 128 bytes. The output starts off with some basic data about the monitor’s characteristics and inputs. The VGA standard doesn’t nail down as many things as you’d hope. For instance the sync signals can be provided on one wire (separate), two wires (composite), or on the green wire. The output above shows my monitor supports all three sync types.
The monitor then provides a long list of supported resolutions, which is how your computer knows what the monitor supports. The “detailed timing descriptor” provides more information on signal voltage levels and timings. The timing of VGA signals contains some strange features (e.g. blanking and “front porch”) inherited from obsolete CRT (Cathode Ray Tube) displays. The values in the configuration provide the information necessary for the computer’s graphics board to synthesize a proper VGA signal that the monitor can understand.
The CIE chromaticity coordinates provided by the monitor are interesting, but need a bit of background to understand. A CIE chromaticity diagram (below), shows all the colors in the real world. (Brightness is factored out, so grays and browns don’t appear.) Individual wavelengths of light (i.e. the spectrum) curve around the outside of the diagram. The colors inside the curve are combinations of the pure spectral colors, with white in the middle.
A display, however, generates its colors by combining red, green, and blue. The result is that a display can only show the colors inside the triangle above with red, green, and blue at the corners. A display doesn’t generate the light wavelengths necessary to display colors outside the triangle. Like most monitors, this monitor can only show a surprisingly small fraction of the possible colors. (A wide-gamut display uses different phosphors to expand the triangle and get more vivid colors.) The triangle vertices and white point4 in the diagram above come from the x,y chromaticity coordinates in the configuration data.
You might wonder how you can see the whole CIE diagram on your display if only the colors inside the triangle can be displayed. The answer is the diagram “cheats”—the colors are scaled to fit into RGB values, so you’re not seeing the exact colors but just an approximate representation. If you look at the spectrum through a prism, for instance, the colors will be more intense than what you see in the CIE diagram.
The I2C protocol (Inter-Integrated Circuit) was invented in 1982 by Philips Semiconductor to connect a CPU to peripheral chips inside televisions. It’s now a popular protocol for many purposes, including connecting sensors, small LED displays, and other devices to microcontrollers. Many I2C products are available from Adafruit and Sparkfun for instance.
The I2C protocol provides a simple, medium-speed way to connect multiple devices on a bus using just two wires—one for a clock and one for data. I2C is a serial protocol, but it differs from serial protocols like RS-232 in a couple ways. First, I2C is synchronous (using a clock), unlike RS-232 which is asynchronous (no clock). Second, I2C is a bus and can connect dozens of devices, while RS-232 connects two devices.5
The oscilloscope trace below shows what an I2C communication with the monitor looks like on the wire. The top line (cyan) shows the clock. Note that the clock only runs while data is transmitted.) The yellow line is the binary data. At the bottom, the oscilloscope decoded the data (green). In this trace, register number 0x26 is being read from device 0x50. The I2C protocol is rather peculiar since a read is performed by doing a write followed by a read. That is, to read a byte, the master first does a write to the device of the desired register number: the master first sends 0x50 (the device ID) the write flag bit (indicated with “W:50”), and 0x26 (the register number, ASCII “&”). Then, the master does a read; it sends 0x50 and the read flag bit (“R:50”). The device responds with the value in the register, 0x71 (ASCII “q”).6
Devices on the I2C bus can only pull a line low; pull-up resistors keeps the lines high by default.7 As a result, the traces above drop low sharply, but climb back up slowly.8 Even though the transitions look sloppy, the I2C bus worked fine. I couldn’t find a source to tell me if VGA monitors included pull-up resistors, or if I needed to add them externally. However, I measured voltage on the lines coming from the monitor and everything worked without external resistors, so there must be pull-up resistors inside the monitor.
The VGA specification includes a data link that allows a computer to learn about a monitor and configure it appropriately. It is straightforward to read this configuration data using the I2C protocol and a board with an I2C port. While VGA is mostly obsolete now, the same data protocol is used with DVI and HDMI displays. My goal in reading the monitor’s config data was so I could use the timing data in an FPGA to generate a VGA video signal. (That project is yet to come.) Follow me on Twitter or RSS to find out about my latest blog posts.