If your project has the luxury of using the desktop OS on a Raspberry Pi 4 (or 400), do it! Interfacing weird displays is so much simpler. If you must use an earlier Pi, or the command-line “Lite” OS, or just have a cantankerous display that even the Pi 4 won’t negotiate with, this is how it’s done…
Things Needed
You’ll need the Raspberry Pi system booting and on a network and/or temporarily connected to a known-compatible monitor. We’ll assume some familiarity with Pi setup and file editing here…it’s covered in other tutorials.
- With a network connection — either Ethernet or WiFi — you wouldn’t need the temporary monitor, instead you’d log in remotely and can copy-and-paste commands from this page. This is usually best. ssh (allowing remote login) can be enabled by creating an empty /boot/ssh file on the SD card.
- With a compatible monitor and keyboard connected, you can follow the steps outlined here and might not need a network connection. There’s no software to install, just some system configuration. You may be connecting either the compatible monitor or weird display between reboots.
- Another option is to insert the micro SD card in a different computer (e.g. Windows, Mac or Linux) and edit the video configuration there, then move the card to the Pi and try booting. The card’s /boot partition will mount on most computers and files can be edited.
There will be some command-line invocations and some editing of files with your text editor of preference. And you will likely be rebooting several times.
Before going down a technological rat hole, try…
sudo raspi-config
Under Display Options is a Resolution setting, and you might find a compatible option there. Or you might just get kicked back to the main menu, in which case we’ll do this the manual way…
Basic Configurating
All of the setup is done in the file /boot/config.txt and (if editing on Pi) must be done as root, e.g.:
sudo nano /boot/config.txt
The default config.txt file has a number of HDMI-related settings peppered throughout, most of them commented out (preceded with a # character). I like to collect these all in a group, usually at the end of the file, so they’re easier to find and work with while ironing out any problems.
These four lines are the minimum starting requirement. They should all be enabled (remove any leading # character) and the values should match what’s shown here:
hdmi_force_hotplug=1 hdmi_group=2 hdmi_mode=87 hdmi_force_mode=1
hdmi_force_hotplug=1
tells the Pi to output a signal even if an HDMI display is not detected. Since these displays are weird, this just makes extra sure something is coming out of the Pi, whether it likes the display or not.
hdmi_group=2
with hdmi_mode=87
enables custom video configuration, which we’ll set up shortly…it tells the system that we might not be using a common resolution like 1080p. And think of hdmi_force_mode=1
as saying “no, I really mean it.”
Then there’s some optional additions…
To disable the black border around the screen, use:
disable_overscan=1
A few displays require the border, in which case set this to 0
. Usually it’s television screens being repurposed as computer monitors, but a few odd items like the Vufine wearable display seem to prefer it.
If using a DVI or HDMI monitor and it almost works but flashes on and off, try:
config_hdmi_boost=7
It’s ignored by the Raspberry Pi 4 and 400, but other boards can use this to generate a stronger video signal. The default is normally 2
or 5
on some Pi models. 7
is usually an ample boost. It can go as high as 11
but that’s not recommended unless you’re seeing issues with very long cables. Work up incrementally (rebooting with each change) to find the lowest reliable value.
If Pi and monitor aren’t negotiating capabilities correctly…yet it’s a consumer monitor that you’d expect to be plug-and-play…try the boot_delay setting, which pauses the system for a number of seconds before loading the kernel. This can give some monitors the ready time they need if everything’s powered on at once:
boot_delay=5
The value (5 in this example) is the number of seconds to wait before booting. If that makes the two devices behave in a plug-and-play manner, all this other stuff might be unnecessary!
Finally, this setting routes audio to the HDMI port if the display supports it (some monitors have built-in speakers)…so it’s not strictly video related, but may as well go in the same group:
hdmi_drive=2
None of this actually configures the display resolution yet, we’re just setting the stage.
Getting the Pi and display to actually sync up involves some trial and error. That’s why this is best done through a remote ssh session, or editing config.txt on the SD card in another system.
Also, none of these changes take effect until the system is rebooted. Writing to the config.txt file has no immediate effect; you must reboot each time.
CVT stands for the “Coordinated Video Timing” VESA standard. This is the easier way of defining a custom video mode…it seems to work more often than not, but might not be sufficient for all displays. It’s worth a try.
You will need: the display’s native pixel resolution and refresh rate. If unsure about refresh rate, 60 Hz is usually a reasonable guess.
Add a line like the following to /boot/config.txt, replacing the [tags]
with suitable numbers (explained below):
hdmi_cvt=[width] [height] [framerate] [aspect] [margins] [interlace] [rb]
For example:
hdmi_cvt=800 400 60 6 0 0 0
or sometimes just:
hdmi_cvt=800 400 60
The first three values are required:
[width]
and [height]
are the display’s exact native pixel dimensions.
[framerate]
is the display’s refresh rate, in Hertz…often 60
, but occasionally higher or lower values.
The remaining four values are optional…if unspecified, defaults will be used:
[aspect]
is a value from 1
to 6
indicating the nearest width-to-height aspect ratio:
1
= 4:32
= 14:93
= 16:94
= 5:45
= 16:106
= 15:9
There doesn’t seem to be much harm in getting this wrong. Most of the “raw” displays I’ve experimented with are either 1:1 or 2:1, and using values of 4
and 3
respectively — or just leaving it off — has worked just fine. Default if unspecified is 3
.
[margins]
, with a value of 0
(disabled, the default) or 1
(enabled), seems to be the inverse of the config.txt’s disable_overscan
setting. The latter seems to take precedence and overrides any setting here, I think.
[interlace]
states whether the display is progressive scan (0
, the default) or interlaced (1
). Usually everything is progressive scan nowadays.
[rb]
is for “reduced blanking,” a way of saving video signal bandwidth on LCDs. 1
enables reduced blanking, while 0
(the default) disables it.
If hdmi_cvt
doesn’t get the Pi and display talking, there’s an even more fiddly level of control possible, where every aspect of the video signal is specified. Fortunately there is a command-line tool to help us puzzle this out. With the display (and adapter/driver board, if present) connected to the Pi and powered on, run the following commands:
sudo apt-get install edid-decode tvservice -d edid.dat; edid-decode edid.dat
If using an earlier release of Raspberry Pi OS (prior to “Bullseye” in late 2021), instead try:
/opt/vc/bin/tvservice -d edid.dat; /opt/vc/bin/edidparser edid.dat
This will output a ton of data, so hopefully you’re using a terminal with scrollback capability…or you can redirect the output to a file, or pipe it through the more command.
The above command probes the display for every compatible resolution setting…sometimes several times for each. You’ll get something like the following that just scrolls on for pages…
HDMI:EDID found monitor S/N descriptor tag 0xff HDMI:EDID found monitor range descriptor tag 0xfd HDMI:EDID monitor range offsets: V min=0, V max=0, H min=0, H max=0 HDMI:EDID monitor range: vertical is 23-75 Hz, horizontal is 15-240 kHz, max pixel clock is 340 MHz HDMI:EDID monitor range does not support GTF HDMI:EDID failed to find a matching detail format for 800x400p hfp:400 hs:20 hbp:400 vfp:20 vs:4 vbp:12 pixel clock:42 MHz HDMI:EDID calculated refresh rate is 60 Hz HDMI:EDID guessing the format to be 800x400p @60 Hz HDMI:EDID found unknown detail timing format: 800x400p hfp:400 hs:20 hbp:400 vfp:20 vs:4 vbp:12 pixel clock:42 MHz HDMI:EDID established timing I/II bytes are 00 00 00 HDMI:EDID standard timings block x 8: 0x0000 0000 0000 0000 0000 0000 0000 0000 HDMI:EDID parsing v3 CEA extension 0 HDMI:EDID monitor support - underscan IT formats:no, basic audio:yes, yuv444:yes, yuv422:yes, #native DTD:4
You’ll want to scroll through this looking for any mentions of a resolution that best matches the display hardware. In the example above, it’s an 800x400 pixel framebuffer that feeds two 400x400 round displays…a truly Weird Display.
If you’re using one of the weird “raw” displays like this, you’ll also see something like the following toward the end of the list, referencing a 640x480 pixel resolution:
HDMI:EDID adding mandatory support for DMT (4) 640x480p @ 60Hz HDMI:EDID adding mandatory support for CEA (1) 640x480p @ 60Hz HDMI:EDID best score mode initialised to CEA (1) 640x480p @ 60 Hz with pixel clock 20 MHz (score 25) HDMI:EDID best score mode is now CEA (1) 640x480p @ 60 Hz with pixel clock 25 MHz (score 61864) HDMI:EDID DMT mode (4) 640x480p @ 60 Hz with pixel clock 25 MHz has a score of 18432 HDMI0:EDID preferred mode is updated to CEA (1) 640x480p @ 60 Hz with pixel clock 25200000 Hz
This is fake…apparently displays are required to report a basic 640x480 pixel mode…but the weird display drivers seldom support it or provide up/down-sampling. So, unless you know that actually is the display’s true native resolution, pretend you didn’t see it, focus on the prior data.
For the 800x400 pixel weird display, these are the lines we’re after:
HDMI:EDID guessing the format to be 800x400p @60 Hz HDMI:EDID found unknown detail timing format: 800x400p hfp:400 hs:20 hbp:400 vfp:20 vs:4 vbp:12 pixel clock:42 MHz
Copy and paste those lines into a note for later reference, you’ll need all those numbers.
Now go back to editing /boot/config.txt
Before getting carried away with hdmi_timings, try feeding just those first three values from the first line into hdmi_cvt…unless you already tried this combination previously.
hdmi_cvt=800 400 60
Reboot and see what happens. If it sticks, great! If not, you’ll go back into config.txt to specify the full hdmi_timings, which expects more arguments than a Monty Python sketch:
hdmi_timings=[h_active_pixels] [h_sync_polarity] [h_front_porch] [h_sync_pulse] [h_back_porch] [v_active_lines] [v_sync_polarity] [v_front_porch] [v_sync_pulse] [v_back_porch] [v_sync_offset_a] [v_sync_offset_b] [pixel_rep] [frame_rate] [interlaced] [pixel_freq] [aspect_ratio]
So we can take values shown by the tvservice command and plug them into the corresponding spots in the hdmi_timings line. The hfp:
, hs:
and hbp:
values shown by tvservice stand for horizontal front porch, horizontal sync pulse and horizontal back porch. vfp:
, vs:
, and vbp:
are the same for vertical. And you can see there are corresponding spots in the hdmi_timings syntax above, but in a different order. A few values are simply defaults.
Rather than try to explain every item…because there’s like a billion…let’s just illustrate how to reorder the output of the tvservice command into a usable hdmi_timings line:
The first five values relate to horizontal resolution and timing, second five are for the vertical, and others are for refresh rate and bandwidth (add six zeros), and the rest are sufficiently obscure that you can probably use the default values above.
This example’s a little tricky because the horizontal porch values and the vertical resolution are coincidentally the same. That won’t always be true…others will be different. Don’t mix them up.
Be sure to include every item and in the correct order. If you miss one, or mix them up, the Pi might not boot. You’ll have to take the SD card to another computer and fix the file there.
It’s rare, but I’ve encountered at least one display that worked fine with hdmi_cvt but not hdmi_timings, so don’t feel pressured to be “extra 1337” with the latter if the former will do. Also one situation where the Pi 4 auto-detect worked better than any amount of cvt/timings fiddling…apparently making better use of this information, so use that if the situation allows.
Multiple Displays (Pi 4 and 400)
The Raspberry Pi 4 and 400 have two HDMI outputs. It’s possible to specify a unique configuration for each by appending a :0
or :1
to the hdmI_cvt and/or hdmi_timings commands:
# 88mm round displays on HDMI 0 hdmi_cvt:0=1600 800 60 6 0 0 0 # Bar display on HDMI 1 hdmi_timings:1=480 0 30 30 30 1920 0 6 6 6 0 0 0 60 0 66000000 4
On the Raspberry Pi 4, HDMI outputs 0 and 1 are labeled on the board, but for posterity: HDMI 0 is closer to the USB-C port, HDMI 1 closer to the audio jack.
On the Pi 400, HDMI 0 is closer to the SD card slot, HDMI 1 is closer to USB-C.
Changing the Screen Orientation
Sometimes a screen will boot up sideways or upside-down from what you’d expect. Or you might just want it aimed differently…for example, some retro arcade games were designed for a vertical “portrait” display, or you might have a bar display that you’d expect would be wide and short, but its default layout is actually tall and thin. Add a display_hdmi_rotate line (or display_rotate in older versions of the operating system) to /boot/config.txt to set this up:
display_hdmi_rotate=3
With multiple displays, you can rotate them independently by adding :0
and :1
as previously shown with hdmi_cvt and hdmi_timings.
The value passed to display_hdmi_rotate works like so:
display_htmi_rotate |
result |
0 |
No rotation (use default for screen) |
1 |
Rotate 90 degrees clockwise |
2 |
Rotate 180 degrees |
3 |
Rotate 90 degrees counterclockwise |
0x10000 |
Horizontal flip |
0x20000 |
Vertical flip |
Flip seems dubious, but consider…many 1980s arcade games had you watching a reflection of a vertically-flipped screen, either to make the cabinet shallower, or to add background artwork using a Pepper’s Ghost effect. This could be useful for a heads-up display or other Weird Ideas.
With exceptionally wide or tall displays you may need a line or two to override the Pi’s notion that it has a 1920x1080 framebuffer…otherwise it won’t use the whole display, only a section in the middle.
For example, with the aforementioned “wide” bar display (which is actually a tall display), here’s how we change its behavior from 480x1920 to the 1920x480 that we’d prefer, and use the whole screen:
display_hdmi_rotate=3 max_framebuffer_width=480 max_framebuffer_height=1920
On Pi 4 and 400, some rotation settings are only possible if 3D acceleration is disabled.
Look for this line in /boot/config.txt:
dtoverlay=vc4-fkms-v3d
and add a # to comment it out, like so:
#dtoverlay=vc4-fkms-v3d
Reboot and the screen should be pointed however you’d like, albeit without acceleration. This is another reason why the desktop (rather than lite) OS is preferred if the situation allows…the Screen Configuration tool has a lot of flexibility for things like this.
Anything Else?
If we’ve overlooked something, here is the official Raspberry Pi documentation on config.txt video settings, which goes into even more detail on some of these fiddly settings.
Text editor powered by tinymce.