Another report for the DIY folk, before I forget This time about PWM/DC regulation of display brightness. Some displays, such as the Waveshare 70H-1024600 QLED are very bright by default and have a PWM/DC regulation of the backlight.
Their wiki suggests regulating the brightness by PWM signal using Python GPIO library. However, this causes terrible flickering either all the time or as soon as there is any load on the zynthian (playing a single note in Pianoteq is enough). I tried smoothing up the PWM signal into a more stable DC control using a RC filter, but the flickering just turned into equally annoying quick brightness changes. It seems the Python library uses just software PWM emulation, which is unstable under load (and probably adds to the system load as well?).
Fortunately, the RaspberryPi has hardware PWM available on 4 od its pins, too. Pins 18 and 19 are used for the I2S sound (unless you use an external USB sound card), so only the pins 12 and 13 remain available. To make them work, one needs to add the proper overlay configuration into /boot/config.txt
:
dtoverlay=pwm-2chan,pin=12,func=4,pin2=13,func2=4
After reboot, the pins 12 and 13 are available as “0” and “1” via the kernel pwm interface. From shell you can activate the pin 13 by sending:
echo 1 > /sys/class/pwm/pwmchip0/export
Next, you have to set the period (frequency), e.g. 1000ns = 1ms ~ 1000Hz, and then you can start the PWM:
echo 1000 > /sys/class/pwm/pwmchip0/pwm1/period
echo 1 > /sys/class/pwm/pwmchip0/pwm1/enable
Now, you can adjust the duty_cycle
from 0 to 100% of the period
value by sending the desired value - in this case also 0 to 1000:
# set brightness to 50% (of the period value)
echo 500 > /sys/class/pwm/pwmchip0/pwm1/duty_cycle
In this way, the brightness remains stable all the time at any load. You can still add the RC filter, but then you will never reach the full level of DC to turn the display completely off, even when you set the duty cycle to 100%. It will just reach the minimal possible brightness.
As for the range of settings otherwise: as expected, the change of brightness is not linear. On my display, the value of 20% is a minimal change and 50% just a slightly dimmer. More significant dimming is achieved by raising the duty cycle to 70, 80 or 90%. The maximum (i.e. minimal brightness) is reached somewhere between 95-99.5%, then the displays just turns off completely (depends on the period: e.g. when setting it to 1000000 you can reach slightly higher values without turning the display off completely).
I noticed there is already an implementation of a PWM system service to regulate a fan by @wyleu. But it also uses just the Python SW PWM. It is probably not a problem for a fan if the speed is not stable (unless you have a noisy one). I didn’t test how does it affect the load on the system, but it might be just negligible.
Open questions I would like to find an answer to next:
- Will it also work this way on a RPi5? On the way, I noticed people mentioning the Pi5 is different and these older overlays do not work there. But the situation may change quickly now, when the Pi5 is increasingly being deployed by everyone. So, maybe just switch to another overlay designed for the Pi5?
- Can this also be exposed via the
/sys/class/backlight/rpi_backlight/brightness
kernel interface in order to be directly used by the current UI settings for V5 as presented by @jofemodo in the thread V5 LED brightness control? Would it involve writing another overlay?