Skip to content

Instantly share code, notes, and snippets.

@thehilde
Forked from shirriff/edid.py
Last active December 7, 2021 04:52
Show Gist options
  • Save thehilde/40e47e998e2fce4e50370cd822a415c6 to your computer and use it in GitHub Desktop.
Save thehilde/40e47e998e2fce4e50370cd822a415c6 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
# Python 3 Forkt by Martin Hildebrandt
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[17]+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')
else:
print('Good 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) == 0x18:
print(' Digital separate sync')
if bytes[17] & 4:
print(' Positive vertical sync polarity')
else:
print(' Negative vertical sync polarity')
if bytes[17] & 2:
print(' Positive vertical sync polarity')
else:
print(' Negative vertical sync polarity')
else:
print(' Digital composite on HSync')
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