Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
Parse VGA configuration data (EDID) accessed from I2C device 0x50
# Program to parse VGA data (EDID) accessed from I2C device 0x50
#
# This is a quick demo not a supported program, so don't expect
# correctness from it.
#
# Edid format from:
# https://en.wikipedia.org/wiki/Extended_Display_Identification_Data#EDID_1.4_data_format
#
# Ken Shirriff http://righto.com
import re
import subprocess
def edid(bytes):
assert(bytes[0] == 0)
assert(bytes[1] == 0xff)
assert(bytes[2] == 0xff)
assert(bytes[3] == 0xff)
assert(bytes[4] == 0xff)
assert(bytes[5] == 0xff)
assert(bytes[6] == 0xff)
assert(bytes[7] == 0)
print 'Header:'
word89 = (bytes[8] << 8) | bytes[9]
c0 = chr(64 + ((word89 >> 10) & 0x1f))
c1 = chr(64 + ((word89 >> 5) & 0x1f))
c2 = chr(64 + ((word89 >> 0) & 0x1f))
print ' Manufacturer: %s%s%s' % (c0, c1, c2)
print ' Product code: %d' % (bytes[10] | (bytes[11] << 8))
print ' Serial number: %d' % (bytes[12] | (bytes[13] << 8) | (bytes[14] << 16) | (bytes[15] << 24))
print ' Week: %d' % bytes[16]
print ' Year: %d' % (bytes[16]+1990)
print ' Edid version %d, revision %d' % (bytes[18], bytes[19])
if bytes[20] & 128:
print ' Digital input'
print ' Depth: %s' % ['undef', '6', '8', '10', '12', '14', '16', 'res'][(bytes[20]>>4)&7]
print ' Interface: %s' % ['undef', 'HDMIa', 'HDMIb', '?', 'MDDI', 'DisplayPort', '?', '?'][bytes[20]&7]
else:
print ' Analog input'
print ' Levels: %s' % ['+0.7/-.03', '+.714/-.386', '+1.0/-.04', '+.7/0]'][(bytes[20]>>5)&3]
if bytes[20] & 16:
print ' Blank-to-black setup (pedestal) expected'
if bytes[20] & 8:
print ' Separate sync supported'
if bytes[20] & 4:
print ' Composite sync supported'
if bytes[20] & 2:
print ' Sync on green supported'
if bytes[20] & 1:
print ' VSync pulse must be serrated when composite or sync-on-green is used.'
hsize = bytes[21]
vsize = bytes[22]
if hsize:
print ' Horizontal screen size: %dcm' % hsize
else:
print ' Portrait aspect ratio: %.3f' % (vsize+99)/100.
if vsize:
print ' Vertical screen size: %dcm' % vsize
else:
print ' Landscapae aspect ratio: %.3f' % (hsize+99)/100.
print ' Display gamma: %.3f' % (bytes[23]/100. + 1)
if bytes[24] & 128: print ' DPMS standby supported'
if bytes[24] & 64: print ' DPMS suspend supported'
if bytes[24] & 32: print ' DPMS active-off supported'
if bytes[20] & 128:
print ' Display type (digital): %s' % ['RGB 4:4:4', 'RGB 4:4:4 + YCrCb 4:4:4', 'RGB 4:4:4 + YCrCb 4:2:2', 'RGB 4:4:4 + YCrCb 4:4:4 + YCrCb 4:2:2'][(bytes[24]>>3)&3]
else:
print ' Display type (analog): %s' % ['Monochrome or grayscale', 'RGB color', 'non-RGB color', 'undef'][(bytes[24]>>3)&3]
if bytes[24] & 4: print ' Standard sRGB color space (data not printed)'
if bytes[24] & 2: print ' Preferred timing mode in descriptor block 1'
if bytes[24] & 1: print ' Continuous timings with GTF or CVT'
rx = ((bytes[27] << 2) | ((bytes[25]>>6) & 3)) / 1024.
ry = ((bytes[28] << 2) | ((bytes[25]>>4) & 3)) / 1024.
gx = ((bytes[29] << 2) | ((bytes[25]>>2) & 3)) / 1024.
gy = ((bytes[30] << 2) | ((bytes[25]>>0) & 3)) / 1024.
bx = ((bytes[31] << 2) | ((bytes[26]>>6) & 3)) / 1024.
by = ((bytes[32] << 2) | ((bytes[26]>>4) & 3)) / 1024.
wx = ((bytes[33] << 2) | ((bytes[26]>>2) & 3)) / 1024.
wy = ((bytes[34] << 2) | ((bytes[26]>>0) & 3)) / 1024.
print 'Chromaticity coordinates: r: (%.3f, %.3f), g: (%.3f, %.3f), b: (%.3f, %.3f), w: (%.3f, %.3f)' % (rx, ry, gx, gy, bx, by, wx, wy)
timings = [
'720x400 @ 70 Hz (VGA)',
'720x400 @ 88 Hz (XGA)',
'640x480 @ 60 Hz (VGA)',
'640x480 @ 67 Hz (Apple Macintosh II)',
'640x480 @ 72 Hz',
'640x480 @ 75 Hz',
'800x600 @ 56 Hz',
'800x600 @ 60 Hz',
'800x600 @ 72 Hz',
'800x600 @ 75 Hz',
'832x624 @ 75 Hz (Apple Macintosh II)',
'1024x768 @ 87 Hz, interlaced (1024x768i)',
'1024x768 @ 60 Hz',
'1024x768 @ 72 Hz',
'1024x768 @ 75 Hz',
'1280x1024 @ 75 Hz',
'1152x870 @ 75 Hz (Apple Macintosh II)',
'manufacturer-specific 6',
'manufacturer-specific 5',
'manufacturer-specific 4',
'manufacturer-specific 3',
'manufacturer-specific 2',
'manufacturer-specific 1',
'manufacturer-specific 0',
]
timingWord = (bytes[35] << 16) | (bytes[36] << 8) | bytes[37]
print 'Established timings:'
for i in range(0, 24):
if timingWord & (1 << (23-i)):
print ' %s' % timings[i]
print 'Standard timing information:'
for i in range(38, 54, 2):
if bytes[i] != 1 or bytes[i+1] != 1:
xres = (bytes[i]+31)*8
aspect = [(16, 10), (4, 3), (5, 4), (16, 9)][bytes[i+1] >> 6]
yres = xres * aspect[1] / aspect[0]
vfreq = (bytes[i+1] & 63) + 60
print ' X res: %d, aspect %d:%d, Y res (derived): %d), vertical frequency: %d' % (
xres, aspect[0], aspect[1], yres, vfreq)
for n in range(1, 5):
i = n * 18 + 36
if bytes[i] != 0 or bytes[i+1] != 0:
detailedTimingDescriptor(n, bytes[i:i+18])
else:
otherMonitorDescriptors(n, bytes[i:i+18])
if bytes[126] > 0:
print '%d extensions (not displayed)' % bytes[126]
if sum(bytes) % 256 != 0:
print 'Bad checksum'
def detailedTimingDescriptor(n, bytes):
print 'Descriptor %d: Detailed timing descriptor:' % n
print ' Pixel clock: %dkHz' % ((bytes[0] | (bytes[1]<<8)) * 10)
print ' Horizontal active pixels: %d' % (bytes[2] | ((bytes[4]&0xF0) << 4))
print ' Horizontal blanking pixels: %d' % (bytes[3] | ((bytes[4]&0x0F) << 8))
print ' Vertical active lines: %d' % (bytes[5] | ((bytes[7]&0xF0) << 4))
print ' Vertical blanking lines: %d' % (bytes[6] | ((bytes[7]&0x0F) << 8))
print ' Horizontal front porch pixels: %d' % (bytes[8] | ((bytes[11]&0xC0) << 2))
print ' Horizontal sync pulse pixels: %d' % (bytes[9] | ((bytes[11]&0x30) << 4))
print ' Vertical front porch lines: %d' % ((bytes[10]>>4) | ((bytes[11]&0x0C) << 2))
print ' Vertical sync pulse lines: %d' % ((bytes[10]&0xF) | ((bytes[11]&0x03) << 4))
print ' Horizontal image size: %dmm' % (bytes[12] | ((bytes[14]&0xF0) << 4))
print ' Vertical image size: %dmm' % (bytes[13] | ((bytes[14]&0x0F) << 8))
print ' Horizontal border pixels: %d' % bytes[15]
print ' Vertical border lines: %d' % bytes[16]
if bytes[17] & 0x80: print ' Interlaced'
if bytes[17] & 0x60: print ' Stereo'
if (bytes[17] & 0x10) == 0:
print ' Analog'
if bytes[17] & 4:
print ' Bipolar analog composite'
else:
print ' Analog composite'
if bytes[17] & 2: print ' VSync serration'
if bytes[17] & 1:
print ' Sync on all 3 RGB lines'
else:
print ' Sync on green only'
elif (bytes[17] & 0x18) == 0x10:
print ' Digital composite on HSync'
if bytes[17] & 4:
print ' Positive vertical sync polarity'
else:
print ' Negative vertical sync polarity'
else:
print ' Digital separate sync'
if bytes[17] & 4: print ' VSync serration'
if bytes[17] & 2:
print ' Positive horizontal sync polarity'
else:
print ' Negative horizontal sync polarity'
def otherMonitorDescriptors(n, bytes):
print 'Descriptor %d:' % n,
if bytes[3] == 0xFF: print 'Display serial number', text(bytes[5:18])
elif bytes[3] == 0xFE: print 'Unspecified text', text(bytes[5:18])
elif bytes[3] == 0xFD: displayRangeLimits(bytes[:18])
elif bytes[3] == 0xFC: print 'Display name', text(bytes[5:18])
elif bytes[3] == 0xFB: print 'Additional white point'
elif bytes[3] == 0xFA: print 'Additional standard timing'
elif bytes[3] == 0xF9: print 'Display color management'
elif bytes[3] == 0xF8: print 'CVT timing codes'
elif bytes[3] == 0xF7: print 'Additional standard timing'
elif bytes[3] == 0x10: print 'Extended timing'
else: print 'Unknown descriptor %x %s' % (bytes[3], text(bytes[5:18]))
def text(bytes):
return re.sub('\n *', '', ''.join([chr(x) for x in bytes]))
def displayRangeLimits(bytes):
print 'Display range limits'
assert(bytes[0] == 0)
assert(bytes[1] == 0)
assert(bytes[2] == 0)
assert(bytes[3] == 0xfd)
print ' Minimum vertical field rate %dHz' % (bytes[5] + (256 if (bytes[4]&1) else 0))
print ' Maximum vertical field rate %dHz' % (bytes[6] + (256 if (bytes[4]&2) else 0))
print ' Minimum horizontal field rate %dHz' % (bytes[7] + (256 if (bytes[4]&4) else 0))
print ' Maximum horizontal field rate %dHz' % (bytes[8] + (256 if (bytes[4]&8) else 0))
print ' Maximum pixel clock rate: %dMhz' % (bytes[9] * 10)
if bytes[10] == 0:
print ' Default GTF'
elif bytes[10] == 1:
print ' No timing information'
elif bytes[10] == 2:
print ' Secondary GTF (uninterpreted)'
elif bytes[10] == 4:
print ' CVT (uninterpreted)'
else:
print ' Unexpected timing information byte %x' % bytes[10]
def main():
# Get 128 bytes from the i2c output
# Just parse i2cdump output instead of using an API
i2c = subprocess.check_output("i2cdump -y 1 0x50 b", shell=True)
bytes = []
for line in i2c.split('\n')[1:9]:
bytes.extend(int(x, 16) for x in line.split(' ')[1:17])
edid(bytes)
if __name__ == '__main__':
main()
@przemekklosowski

This comment has been minimized.

Copy link

commented Oct 2, 2018

There's small error in the decoder; I fixed it in
https://gist.github.com/przemekklosowski/93a82b9b171014b4d009a10ff35ce4d8
The output is used in your blog post on decoding VGA monitor EDID info, so for completeness maybe change it there too..
I am a fan of your posts---your debugging skills are epic, so I am quite honored to be able do debug the code of a debugging master, however tiny my fix is :)

@roycepope

This comment has been minimized.

Copy link

commented Mar 15, 2019

This is sweet! Thanks!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.