Created
March 25, 2018 15:58
-
-
Save shirriff/dd9e35da12879cf1c5ed9ed92ff704ec to your computer and use it in GitHub Desktop.
Parse VGA configuration data (EDID) accessed from I2C device 0x50
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# 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() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
This is sweet! Thanks!