Skip to content

Instantly share code, notes, and snippets.

@shirriff
Created March 25, 2018 15:58
Show Gist options
  • Save shirriff/dd9e35da12879cf1c5ed9ed92ff704ec to your computer and use it in GitHub Desktop.
Save shirriff/dd9e35da12879cf1c5ed9ed92ff704ec to your computer and use it in GitHub Desktop.
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
Copy link

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
Copy link

This is sweet! Thanks!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment