I often get the question how to control the display of your Raspberry Pi 4 through software.
When you tightly integrate your bare computer display into a nicely built picture frame you sometimes can’t access the monitor controls easily. Or you may want to have some brightness automation running which dims your screen in the evening.
Since the 5.10 kernel exposed the HDMI I2C channel on the Raspberry Pi 4 in 2021, that is possible.
One thing to say at the top, because it changes what this article is for. My current frame software, picframe3, dims in software itself and has an evening dim schedule and a screen-off schedule built in, which works on any monitor whether it speaks DDC/CI or not. So ddcutil is no longer the answer to “how do I dim my frame” — it is the answer to “how do I reach the settings buried in my monitor’s own menu”, which is a different and still useful question: colour mode, contrast, input source, and the backlight itself rather than a darkened image.
Introduction to ddcutil
“ddcutil is a Linux program for managing monitor settings, such as brightness, color levels, and input source. Generally speaking, any setting that can be changed by pressing buttons on the monitor can be modified by ddcutil.
ddcutil primarily uses DDC/CI (Display Data Channel Command Interface) to communicate with monitors implementing MCCS (Monitor Control Command Set) over I2C. Normally, the video driver for the monitor exposes the I2C channel as devices named /dev/i2c-n.”
I haven’t found that claim to be 100% true, but I was happy to discover that “almost” all hardware controls can now be soft-controlled. Given the number of monitor models in the market, lacking a universal standard, this is no wonder.
There are a few restrictions like that ddcutil does not support laptop displays, which are controlled using a special API, not I2C. So if you just butchered your old laptop display, ddcutil won’t work. But for most of us with a standard computer monitor, it should be fine.
In the following, I will describe how it worked using a BenQ PD3200U monitor, a 32-inch 4K display that I used for a digital picture frame project. That monitor is long out of production now, so treat the feature lists below as an example of what a capable monitor reports, not as a shopping recommendation — yours will answer with a different set.
Installing ddcutil on a Raspberry Pi 4
If you haven’t updated your system for a while, first run
sudo apt update && sudo apt upgrade -y
Then install ddcutil by typing
sudo apt install ddcutil
The last step is to check your Pi configuration file. When I first wrote this, raspi-config still had a graphics driver menu at “6 Advanced Options > A2 GL Driver”, Fake KMS was what most people had selected, and ddcutil needs full KMS.
Both that menu and Fake KMS have since been removed. On Bookworm and Trixie, full KMS (vc4-kms-v3d) is the only driver there is and it is already switched on, so on a fresh card there is normally nothing to change here at all. The file has also moved.
Enter
sudo nano /boot/firmware/config.txt
and scroll down to the bottom. What you want to see is:
[all]
dtoverlay=vc4-kms-v3d
If an old card still says vc4-fkms-v3d, remove the “f”. Two settings that used to sit beside it, max_framebuffers=2 and gpu_mem=256, are leftovers from the old graphics stack — under full KMS there is no memory split to set and neither does anything.
Reboot with sudo reboot.
On a Raspberry Pi 5 the HDMI and I2C arrangement is not the same as on the Pi 4, and the bus number ddcutil detect reports will be different. I have only run this on a Pi 4, so I cannot tell you what yours will be — run ddcutil detect and read the bus number out of its output rather than copying mine. This part needs a proper re-test on a Pi 5, and I will update it when I have done one.
Detecting your monitor
Once you have done all of the above, let’s test if your monitor is supported by ddcutil.
Type
ddcutil detect
If you get something like this, all is well.
Display 1
I2C bus: /dev/i2c-11
EDID synopsis:
Mfg id: BNQ
Model: BenQ PD3200U
Serial number: FAL05444019
Manufacture year: 2020
EDID version: 1.3
VCP version: 2.2
If you now enter
ddcutil capabilities
You will be able to see all the features that are being supported in detail.
In my case, with the BenQ PD3200U, this is what I get:
ddcutil capabilities
MCCS version: 2.2
Commands:
Command: 01 (VCP Request)
Command: 02 (VCP Response)
Command: 03 (VCP Set)
Command: 07 (Timing Request)
Command: 0c (Save Settings)
Command: e3 (Capabilities Reply)
Command: f3 (Capabilities Request)
VCP Features:
Feature: 02 (New control value)
Feature: 04 (Restore factory defaults)
Feature: 08 (Restore color defaults)
Feature: 10 (Brightness)
Feature: 12 (Contrast)
Feature: 14 (Select color preset)
Values:
04: 5000 K
05: 6500 K
08: 9300 K
0b: User 1
Feature: 16 (Video gain: Red)
Feature: 18 (Video gain: Green)
Feature: 1A (Video gain: Blue)
Feature: 60 (Input Source)
Values:
0f: DisplayPort-1
10: DisplayPort-2
11: HDMI-1
12: HDMI-2
Feature: 62 (Audio speaker volume)
Feature: 72 (Gamma)
Values: 50 64 78 8c a0 (interpretation unavailable)
Feature: 7D (unrecognized feature)
Values: 00 01 02 (interpretation unavailable)
Feature: 7E (Trapezoid)
Values: 0f 10 11 12 (interpretation unavailable)
Feature: 7F (unrecognized feature)
Feature: 80 (Keystone)
Values: 00 01 02 (interpretation unavailable)
Feature: 86 (Display Scaling)
Values:
01: No scaling
02: Max image, no aspect ration distortion
05: Max vertical image with aspect ratio distortion
0c: Unrecognized value
10: Unrecognized value
11: Unrecognized value
13: Unrecognized value
14: Unrecognized value
15: Unrecognized value
16: Unrecognized value
17: Unrecognized value
Feature: 87 (Sharpness)
Feature: 8D (Audio mute/Screen blank)
Values: 01 02 (interpretation unavailable)
Feature: 8A (Color Saturation)
Feature: 90 (Hue)
Feature: AA (Screen Orientation)
Values:
01: 0 degrees
02: 90 degrees
Feature: B6 (Display technology type)
Feature: C6 (Application enable key)
Feature: C8 (Display controller type)
Feature: C9 (Display firmware level)
Feature: CA (OSD/Button Control)
Feature: CC (OSD Language)
Values:
01: Chinese (traditional, Hantai)
02: English
03: French
04: German
05: Italian
06: Japanese
07: Korean
09: Russian
0a: Spanish
0b: Swedish
0d: Chinese (simplified / Kantai)
0e: Portuguese (Brazil)
0f: Arabic
12: Czech
14: Dutch
1a: Hungarian
1e: Polish
1f: Romanian
Feature: DA (Scan mode)
Values:
00: Normal operation
02: Overscan
Feature: DC (Display Mode)
Values:
00: Standard/Default mode
0a: Demonstration
0c: Unrecognized value
10: Unrecognized value
12: Unrecognized value
13: Unrecognized value
14: Unrecognized value
20: Unrecognized value
Feature: DF (VCP Version)
Feature: E2 (manufacturer specific feature)
Values: 00 01 (interpretation unavailable)
Feature: E3 (manufacturer specific feature)
Values: 00 01 (interpretation unavailable)
Feature: E7 (manufacturer specific feature)
Values: 00 01 (interpretation unavailable)
Feature: E8 (manufacturer specific feature)
Values: 00 01 (interpretation unavailable)
Feature: E9 (manufacturer specific feature)
Values: 00 01 02 03 (interpretation unavailable)
Feature: EA (manufacturer specific feature)
Values: 00 01 02 03 04 05 (interpretation unavailable)
Feature: EE (manufacturer specific feature)
Values: 00 01 02 (interpretation unavailable)
Feature: EF (manufacturer specific feature)
Values: 00 01 (interpretation unavailable)
Feature: F0 (manufacturer specific feature)
Values: 00 01 02 (interpretation unavailable)
Feature: F1 (manufacturer specific feature)
Values: 00 01 (interpretation unavailable)
Feature: F2 (manufacturer specific feature)
Values: 14 28 3c 50 64 (interpretation unavailable)
Feature: F4 (manufacturer specific feature)
Values: 00 01 ff (interpretation unavailable)
Feature: F5 (manufacturer specific feature)
Values: 00 01 (interpretation unavailable)
Feature: F6 (manufacturer specific feature)
Values: 00 01 (interpretation unavailable)
Feature: F7 (manufacturer specific feature)
Values: 00 01 (interpretation unavailable)
Feature: F8 (manufacturer specific feature)
Values: 00 0a 14 1e (interpretation unavailable)
Feature: F9 (manufacturer specific feature)
How to read and set screen parameters
Reading the current value of a particular parameter is very simple. The basic command is:
getvcp feature-code-or-group
So to get you current brightness level, you would look up the feature number in your capabilities list, e.g.
Feature: 10 (Brightness)
and enter
ddcutil getvcp 10
This will result in
VCP code 0x10 (Brightness ): current value = 75, max value = 100
To reduce brightness to 50% type:
ddcutil setvcp 10 50
Another example, reduce the contrast setting to 50%:
ddcutil setvcp 12 50
My suggestion is to read all the features that you may want to read and control via ddcutil with ddcutil getvcp and see if you can change them.
Recommended settings for the digital picture frame
Back when I wrote this, quite a few people were using the BenQ PD3200U as a screen for their 4K 32-inch frame, judging by the user gallery and the emails I received. That roundup is from 2021 and the monitors in it are no longer sold.
Reader Daniel sent me his favorite settings for this digital picture frame which I have adopted for my frame as well which I find useful to share:
sRGB color mode
Saturation 45%
Contrast 50%
Brightness automatically controlled between 10% and 40%.
Conclusion
Soft controlling your digital picture frame screen is a very useful feature especially when you want to manually or automatically control the brightness.
On a Raspberry Pi 4 the installation is a breeze. On a Pi 5 the installation is the same; finding the right I2C bus is the part I have not tested.
Was this article helpful?
Thank you for your support and motivation.