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() |
This comment has been minimized.
This comment has been minimized.
This is sweet! Thanks! |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
This comment has been minimized.
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 :)