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.

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.