Skip to content

Commit 8776b77

Browse files
authored
Create Find_EDID_Values.py
1 parent 064a654 commit 8776b77

File tree

1 file changed

+238
-0
lines changed

1 file changed

+238
-0
lines changed
Lines changed: 238 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,238 @@
1+
# SPDX-FileCopyrightText: Copyright (c) 2025 Anne Barela for Adafruit Industries
2+
#
3+
# SPDX-License-Identifier: MIT
4+
#
5+
# Display the EDID monitor resolutions for a connected HDMI/DVI display
6+
#
7+
import sys
8+
import board
9+
import busio
10+
11+
def parse_established_timings(edid):
12+
"""Parse established timings from EDID bytes 35-37"""
13+
modes = []
14+
15+
# Byte 35 (established timings I)
16+
if edid[35] & 0x80:
17+
modes.append("720x400 @70Hz")
18+
if edid[35] & 0x40:
19+
modes.append("720x400 @88Hz")
20+
if edid[35] & 0x20:
21+
modes.append("640x480 @60Hz")
22+
if edid[35] & 0x10:
23+
modes.append("640x480 @67Hz")
24+
if edid[35] & 0x08:
25+
modes.append("640x480 @72Hz")
26+
if edid[35] & 0x04:
27+
modes.append("640x480 @75Hz")
28+
if edid[35] & 0x02:
29+
modes.append("800x600 @56Hz")
30+
if edid[35] & 0x01:
31+
modes.append("800x600 @60Hz")
32+
33+
# Byte 36 (established timings II)
34+
if edid[36] & 0x80:
35+
modes.append("800x600 @72Hz")
36+
if edid[36] & 0x40:
37+
modes.append("800x600 @75Hz")
38+
if edid[36] & 0x20:
39+
modes.append("832x624 @75Hz")
40+
if edid[36] & 0x10:
41+
modes.append("1024x768 @87Hz")
42+
if edid[36] & 0x08:
43+
modes.append("1024x768 @60Hz")
44+
if edid[36] & 0x04:
45+
modes.append("1024x768 @70Hz")
46+
if edid[36] & 0x02:
47+
modes.append("1024x768 @75Hz")
48+
if edid[36] & 0x01:
49+
modes.append("1280x1024 @75Hz")
50+
51+
# Byte 37 (manufacturer timings)
52+
if edid[37] & 0x80:
53+
modes.append("1152x870 @75Hz")
54+
55+
return modes
56+
57+
def parse_standard_timings(edid):
58+
"""Parse standard timings from EDID bytes 38-53"""
59+
modes = []
60+
61+
for i in range(38, 54, 2): # 8 standard timing descriptors, 2 bytes each
62+
if edid[i] == 0x01 and edid[i+1] == 0x01:
63+
continue # Unused timing
64+
65+
if edid[i] == 0x00:
66+
continue # Invalid timing
67+
68+
# Calculate horizontal resolution
69+
h_res = (edid[i] + 31) * 8
70+
71+
# Calculate aspect ratio and vertical resolution
72+
aspect_ratio = (edid[i+1] & 0xC0) >> 6
73+
refresh_rate = (edid[i+1] & 0x3F) + 60
74+
75+
if aspect_ratio == 0: # 16:10
76+
aspect = "16:10"
77+
v_res = (h_res * 10) // 16
78+
elif aspect_ratio == 1: # 4:3
79+
aspect = "4:3"
80+
v_res = (h_res * 3) // 4
81+
elif aspect_ratio == 2: # 5:4
82+
aspect = "5:4"
83+
v_res = (h_res * 4) // 5
84+
else: # aspect_ratio == 3, 16:9
85+
aspect = "16:9"
86+
v_res = (h_res * 9) // 16
87+
88+
modes.append(f"{h_res}x{v_res} @{refresh_rate}Hz, Aspect: {aspect}")
89+
90+
return modes
91+
92+
def parse_detailed_timings(edid):
93+
# pylint: disable=unused-variable
94+
"""Parse detailed timing descriptors from EDID bytes 54-125"""
95+
modes = []
96+
97+
for i in range(54, 126, 18): # 4 detailed timing descriptors, 18 bytes each
98+
# Check if this is a timing descriptor (pixel clock != 0)
99+
pixel_clock = edid[i] | (edid[i+1] << 8)
100+
if pixel_clock == 0:
101+
continue # Not a timing descriptor
102+
103+
# Parse horizontal resolution
104+
h_active_low = edid[i+2]
105+
h_active_high = (edid[i+4] & 0xF0) >> 4
106+
h_active = h_active_low | (h_active_high << 8)
107+
108+
# Parse vertical resolution
109+
v_active_low = edid[i+5]
110+
v_active_high = (edid[i+7] & 0xF0) >> 4
111+
v_active = v_active_low | (v_active_high << 8)
112+
113+
# Parse horizontal sync
114+
h_sync_offset_low = edid[i+8]
115+
h_sync_width_low = edid[i+9]
116+
h_sync_high = (edid[i+11] & 0xC0) >> 6
117+
h_sync_offset = h_sync_offset_low | ((h_sync_high & 0x3) << 8)
118+
h_sync_width = h_sync_width_low | ((h_sync_high & 0xC) << 6)
119+
120+
# Parse vertical sync
121+
v_sync_offset_low = (edid[i+10] & 0xF0) >> 4
122+
v_sync_width_low = edid[i+10] & 0x0F
123+
v_sync_high = (edid[i+11] & 0x0C) >> 2
124+
v_sync_offset = v_sync_offset_low | ((v_sync_high & 0x3) << 4)
125+
v_sync_width = v_sync_width_low | ((v_sync_high & 0xC) << 2)
126+
127+
# Calculate refresh rate (approximate)
128+
h_blank_low = edid[i+3]
129+
h_blank_high = edid[i+4] & 0x0F
130+
h_blank = h_blank_low | (h_blank_high << 8)
131+
h_total = h_active + h_blank
132+
133+
v_blank_low = edid[i+6]
134+
v_blank_high = edid[i+7] & 0x0F
135+
v_blank = v_blank_low | (v_blank_high << 8)
136+
v_total = v_active + v_blank
137+
138+
if h_total > 0 and v_total > 0:
139+
refresh_rate = (pixel_clock * 10000) // (h_total * v_total)
140+
modes.append(f"{h_active}x{v_active} @{refresh_rate}Hz")
141+
142+
return modes
143+
144+
# Main EDID reading code
145+
i2c = busio.I2C(board.SCL, board.SDA)
146+
if not i2c:
147+
print("Board doesn't have I2C")
148+
sys.exit(1)
149+
if not i2c.try_lock():
150+
print("Cannot lock I2C")
151+
sys.exit(1)
152+
153+
print("\nI2C Present")
154+
155+
devices = i2c.scan()
156+
if not (0x50 in devices):
157+
print("No device found at EDID address 0x50, is the monitor plugged in " +
158+
"& board power cycled?")
159+
sys.exit(1)
160+
161+
print("Device 0x50 found!")
162+
device = 0x50
163+
edid = bytearray(128)
164+
out = bytearray([0])
165+
i2c.writeto_then_readfrom(device, out, edid)
166+
167+
if edid[0] != 0x00 or edid[1] != 0xFF or edid[2] != 0xFF or \
168+
edid[3] != 0xFF or edid[4] != 0xFF or edid[5] != 0xFF or \
169+
edid[6] != 0xFF or edid[7] != 0x00:
170+
print("EDID signature not recognized")
171+
sys.exit(1)
172+
173+
print("Valid EDID signature!")
174+
175+
# Verify checksum
176+
checksum = sum(edid) & 0xFF
177+
if checksum != 0:
178+
print("Bad EDID checksum detected")
179+
sys.exit(1)
180+
181+
print("Good EDID checksum!")
182+
183+
# Parse all supported modes
184+
supported_modes = []
185+
186+
# Get established timings
187+
established_modes = parse_established_timings(edid)
188+
supported_modes.extend(established_modes)
189+
190+
# Get standard timings
191+
standard_modes = parse_standard_timings(edid)
192+
supported_modes.extend(standard_modes)
193+
194+
# Get detailed timings
195+
detailed_modes = parse_detailed_timings(edid)
196+
supported_modes.extend(detailed_modes)
197+
198+
# Remove duplicates while preserving order
199+
unique_modes = []
200+
for mode in supported_modes:
201+
if mode not in unique_modes:
202+
unique_modes.append(mode)
203+
204+
# Display results
205+
print(f"\nSupported Display Modes ({len(unique_modes)} found):")
206+
print("=" * 50)
207+
for mode in unique_modes:
208+
print(f" {mode}")
209+
210+
# Original default resolution logic
211+
established_timings = edid[35]
212+
if (established_timings & 0xa0) == 0:
213+
print("\nWarning: 720x400@70Hz and 640x480@60Hz not supported")
214+
default_width = 640
215+
default_height = 480
216+
else:
217+
offset = 54
218+
preferred_pixel_clock = edid[offset] | (edid[offset + 1] << 8)
219+
if preferred_pixel_clock != 0:
220+
preferred_width = ((edid[offset + 4] & 0xf0) << 4) | edid[offset + 2]
221+
preferred_height = ((edid[offset + 7] & 0xf0) << 4) | edid[offset + 5]
222+
if (established_timings & 0x80) != 0 and \
223+
preferred_width % 1920 == 0 and \
224+
preferred_height % 1080 == 0:
225+
default_width = 720
226+
default_height = 400
227+
else:
228+
default_width = 640
229+
default_height = 480
230+
else:
231+
default_width = 640
232+
default_height = 480
233+
234+
print(f"\nDefault resolution: {default_width}x{default_height}")
235+
print(f"Preferred resolution: {preferred_width}x{preferred_height}")
236+
237+
i2c.unlock()
238+
print("\nDone!")

0 commit comments

Comments
 (0)