Helligkeit des Raspberry Pi 7 Zoll Displays mit integriertem Fotowiderstand regeln

In diesem Artikel zeige ich euch, wie man das Original 7 Zoll Display des Raspberry Pi mit einem Fotowiderstand automatisch steuern kann. Der Fotowiderstand ist durch einen kleinen Umbau direkt in das Display integriert und ermöglicht es, die Helligkeit des Displays an die Umgebungshelligkeit anzupassen.

Analoge Signale mit dem Raspberry Pi messen

Die einfachste Möglichkeit, die Helligkeit zu messen, ist mit einem Fotowiderstand. Mit einem Spannungsteiler könnte man so an einem analogen Eingang den Wert des Fotowiderstands bestimmen und damit auf die Helligkeit schließen. Leider ist es aber so, dass der Raspberry Pi keine analogen Eingänge hat. Es gibt jedoch eine andere Möglichkeit, einen analogen Wert mit einem digitalen Eingang zu messen. Diese Methode ist zwar alles andere als genau, aber für diese Anwendung reicht es. 

Der Trick besteht darin, den Vorgang "Messen von Spannung" in ein "Messen von Zeit" zu transformieren, was mit dem Raspberry Pi problemlos möglich ist. Dazu ist neben dem Fotowiderstand nur noch ein Kondensator nötig. Die Schaltung dazu sieht folgendermaßen aus:

Schaltplan Fotowiderstand

Die Messung erfolgt nun in zwei Schritten:

  1. Entladen des Kondensators: Dazu werden beide Pins auf 0 gesetzt. Dadurch fließt keine weitere Ladung in den Kondensator und die vorhandene Ladung fließt über das Read Pin ab. 
  2. Laden des Kondensators: Das Read Pin wird auf Input geschaltet und das Charge Pin wird auf 1 gesetzt. Jetzt wird der Kondensator geladen. Die Geschwindigkeit der Ladung hängt vom aktuellen Wert des Fotowiderstands ab. Die Spannung am Read Pin steigt nun an und wird irgendwann den Schwellwert übersteigen, bei dem der Wert vom Raspberry Pi als logische 1 (ca. 1,37 Volt) erkannt wird. Diese Zeitspanne kann mit dem Raspberry Pi gemessen werden. 

Mit dieser Methode ist also möglich, indirekt den aktuellen Wert des Fotowiderstands und damit die Helligkeit zu messen. Am Oszilloskop kann man diesen Vorgang sehr schön beobachten:

Das erste Bild zeigt den Ladevorgang bei normaler Helligkeit. Die gelbe Linie ist das Charge Pin und die blaue Linie das Read Pin. Die Ladezeit bis der Schwellwert erreicht ist, beträgt hier etwas 0,5 Millisekunden

RC charging curve fast

Das folgende Bild zeigt den Ladevorgang bei weniger Licht. Die Ladezeit beträgt hier etwa 2,8 Millisekunden.

RC charging curve slow

Das letzte Bild zeigt den Ladevorgang bei sehr wenig Licht. In diesem Fall wird der Schwellwert gar nicht erreicht. Das liegt daran, dass hier der Innenwiderstand des Raspberry Pi eine Rolle spielt, der gemeinsam mit dem Fotowiderstand einen Spannungsteiler bildet. Das muss bei der Implementierung berücksichtigt werden, da man hier einen Timeout brauchen wird. In meinem Fall habe ich diesen auf 15 Millisekunden festgelegt.

RC charging curve too low

Implementierung der Helligkeitssteuerung

Die Steuerung der Helligkeit für das Raspberry Pi Display ist in C programmiert und verwendet die gpiod Bibliothek, um die Ports anzusteuern. Zunächst müssen zwei Pakete installiert werden:

sudo apt install gpiod
sudo apt install libgpiod-dev

Das erste Paket bietet einige Werkzeuge für die Kommunikation mit den Linux GPIO Ports. Die zweite Bibliothek enthält die nötigen Bibliotheken und Header Files für die C-Programmierung.

Mit diesen Werkzeugen kann man sich beispielsweise die vorhandenen GPIO Devices anzeigen lassen. Bei angeschlossenem Display sieht die Liste bei mir folgendermaßen aus:

pi@raspberrypi:~ $ gpiodetect
gpiochip0 [pinctrl-bcm2835] (54 lines)
gpiochip1 [raspberrypi-exp-gpio] (8 lines)
gpiochip2 [7inch-touchscreen-p] (2 lines)

Für die Devices können auch alle Leitungen angezeigt werden:

pi@raspberrypi:~ $ gpioinfo
gpiochip0 - 54 lines:
        line   0:     "ID_SDA"       unused   input  active-high
        line   1:     "ID_SCL"       unused   input  active-high
        line   2:       "SDA1"       unused   input  active-high
        line   3:       "SCL1"       unused   input  active-high
        line   4:  "GPIO_GCLK"       unused   input  active-high
        line   5:      "GPIO5"       unused   input  active-high
        line   6:      "GPIO6"       unused   input  active-high
        line   7:  "SPI_CE1_N"       unused   input  active-high
        line   8:  "SPI_CE0_N"       unused   input  active-high
        line   9:   "SPI_MISO"       unused   input  active-high
        line  10:   "SPI_MOSI"       unused   input  active-high
        line  11:   "SPI_SCLK"       unused   input  active-high
        line  12:     "GPIO12"       unused   input  active-high
        line  13:     "GPIO13"       unused   input  active-high
        line  14:       "TXD1"       unused   input  active-high
        line  15:       "RXD1"       unused   input  active-high
        line  16:     "GPIO16"       unused   input  active-high
        line  17:     "GPIO17"       unused   input  active-high
        line  18:     "GPIO18"  "brightness"  output  active-high [used]
        line  19:     "GPIO19"       unused   input  active-high
        line  20:     "GPIO20"       unused   input  active-high
        line  21:     "GPIO21"       unused   input  active-high
        line  22:     "GPIO22"       unused   input  active-high
        line  23:     "GPIO23"       unused   input  active-high
        line  24:     "GPIO24"  "brightness"  output  active-high [used]
        line  25:     "GPIO25"       unused   input  active-high
        line  26:     "GPIO26"       unused   input  active-high
        line  27:     "GPIO27"       unused   input  active-high
        line  28: "HDMI_HPD_N"        "hpd"   input   active-low [used]
        line  29: "STATUS_LED_G" "led0" output active-high [used]
        line  30:       "CTS0"       unused   input  active-high
        line  31:       "RTS0"       unused   input  active-high
        line  32:       "TXD0"       unused   input  active-high
        line  33:       "RXD0"       unused   input  active-high
        line  34:    "SD1_CLK"       unused   input  active-high
        line  35:    "SD1_CMD"       unused   input  active-high
        line  36:  "SD1_DATA0"       unused   input  active-high
        line  37:  "SD1_DATA1"       unused   input  active-high
        line  38:  "SD1_DATA2"       unused   input  active-high
        line  39:  "SD1_DATA3"       unused   input  active-high
        line  40:   "PWM0_OUT"       unused   input  active-high
        line  41:   "PWM1_OUT"       unused   input  active-high
        line  42:    "ETH_CLK"       unused   input  active-high
        line  43:   "WIFI_CLK"       unused   input  active-high
        line  44:       "SDA0"       unused   input  active-high
        line  45:       "SCL0"       unused   input  active-high
        line  46:   "SMPS_SCL"       unused   input  active-high
        line  47:   "SMPS_SDA"       unused  output  active-high
        line  48:   "SD_CLK_R"       unused   input  active-high
        line  49:   "SD_CMD_R"       unused   input  active-high
        line  50: "SD_DATA0_R"       unused   input  active-high
        line  51: "SD_DATA1_R"       unused   input  active-high
        line  52: "SD_DATA2_R"       unused   input  active-high
        line  53: "SD_DATA3_R"       unused   input  active-high
gpiochip1 - 8 lines:
        line   0:      "BT_ON"       unused  output  active-high
        line   1:      "WL_ON"       unused  output  active-high
        line   2:  "PWR_LED_R"       "led1"  output   active-low [used]
        line   3:    "LAN_RUN"       unused   input  active-high
        line   4:         "NC"       unused   input  active-high
        line   5:  "CAM_GPIO0" "cam1_regulator" output active-high [used]
        line   6:  "CAM_GPIO1"       unused  output  active-high
        line   7:         "NC"       unused   input  active-high
gpiochip2 - 2 lines:
        line   0:      unnamed "0.reg_bridge" output active-high [used]
        line   1:      unnamed      "reset"  output   active-low [used]

In der Liste sieht man, dass ich die Ports 18 und 24 für die Steuerung verwende.

Um die Helligkeit des Displays steuern zu können, muss auch der entsprechende Treiber aktiviert sein. Das sieht man daran, ob die Datei /sys/class/backlight/10-0045/brightness vorhanden ist. Wenn nicht, editiert man die Datei /boot/config.txt mit

sudo nano /boot/config.txt

und fügt am Ende folgende Zeile an:

dtoverlay=rpi-backlight

Nach einem Neustart ist der Treiber dann aktiv.

Nun zur eigentlichen Steuerung. Der Code besteht aus drei Teilen:

Entladen des Kondensators

Dazu werden einfach die beiden GPIO Ports auf 0 gesetzt und eine gewisse Zeit gewartet. 

ret = gpiod_line_request_output(gpio_charge_line, CONSUMER, 0);
if (ret != 0)
	goto release_lines;
ret = gpiod_line_request_output(gpio_read_line, CONSUMER, 0);
if (ret != 0)
	goto release_lines;

usleep(10000);

gpiod_line_release(gpio_charge_line);
gpiod_line_release(gpio_read_line);

Laden des Kondensators und Zeitmessung

Zunächst wird das Read Pin so konfiguriert, dass bei einer steigenden Flanke ein Event ausgelöst wird. Anschließend wird das Charge Pin auf ein 1 gesetzt. Die Funktion gpiod_line_event_wait wartet dann, bis entweder das Event ausgelöst wird oder ein Timeout auftritt. Mittels clock_gettime wird die Zeit bis zu dem Event gemessen.

ret = gpiod_line_request_rising_edge_events(gpio_read_line, CONSUMER);
if (ret != 0)
	goto release_lines;

ret = gpiod_line_request_output(gpio_charge_line, CONSUMER, 1);
if (ret != 0)
	goto release_lines;
		
clock_gettime(CLOCK_MONOTONIC, &start);
ret = gpiod_line_event_wait(gpio_read_line, &ts); // Wait until the signal changes from 0 to 1
clock_gettime(CLOCK_MONOTONIC, &end);

if (ret > 0 && end.tv_nsec > start.tv_nsec) {
	y = end.tv_nsec - start.tv_nsec;
} else {
	y = READ_TIMEOUT;
}

gpiod_line_release(gpio_charge_line);
gpiod_line_release(gpio_read_line);

Einstellen der Helligkeit

Da die Messwerte eine recht hohe Streuung haben, werden die Werte zunächst geglättet. Dazu verwende ich ein Verfahren, das man exponentielle Glättung nennt.

// Smooth values 
y_stern = 0.1 * y + (1 - 0.1) * y_stern__prev;
y_stern__prev = y_stern;

// Map timing values to brightness values
brightness = (int)map_to_brightness( y_stern / 100000.0 ); // Map to 0 - 150
		
// Set brightness of display
set_brightness(brightness);

Anschließend werden die Zeitwerte in Helligkeitswerte umgerechnet. Da der Zusammenhang zwischen den beiden Größen nicht linear ist, habe ich einen pragmatischen Weg gewählt. Dazu habe ich für einige Werte manuell eine Helligkeit gewählt, die mir passend erscheint. Diese Werte habe ich in ein Diagramm eingetragen und daraus für die einzelnen Bereiche eine Geradengleichung erstellt. Diese Näherung hat sich bewährt. 

brightness mapping

Zuletzt wird noch der errechnete Wert in die Datei /sys/class/backlight/10-0045/brightness geschrieben und damit die Helligkeit eingestellt.

Der Quellcode für die Steuerung ist auf GitHub verfügbar.

Helligkeitssteuerung als Service einrichten

Die Steuerung der Helligkeit soll nach dem Booten automatisch gestartet werden. Dazu wird zunächst die mit dem make-Kommando erstelle Datei in das Verzeichnis /usr/local/bin kopiert:

sudo cp brightness /usr/local/bin

Dann wird eine Unit Datei mit diesem Befehl angelegt:

sudo nano /lib/systemd/system/brightness.service

und dieser Inhalt eingefügt:

[Unit]
 Description=Brightness
 After=multi-user.target

 [Service]
 Type=idle
 ExecStart=/usr/local/bin/brightness

 [Install]
 WantedBy=multi-user.target

Die Berechtigungen für diese Datei müssen noch angepasst werden:

sudo chmod 644 /lib/systemd/system/brightness.service

Abschließend wird der Service aktiviert:

sudo systemctl daemon-reload
sudo systemctl enable brightness.service

Damit ist der Service eingerichtet. Nach einem Neustart sollte er jetzt automatisch starten. 

Fotowiderstand in das Display einbauen

Der Fotowiderstand muss natürlich nicht unbedingt in das Display eingebaut werden. Da mein Gehäuse für die Wetterstation aber schon fertig ist und ich nicht extra ein neues Gehäuse drucken wollte, habe ich mich für diese Variante entschieden. Es muss einem aber bewusst sein, dass man dadurch sämtliche Garantieansprüche verliert!

Damit die Helligkeit des Displays optimal geregelt wird, sollte der Fotowiderstand auch möglichst nah am Display sein. Das Raspberry Pi 7 Zoll Display hat einen relativ breiten Rahmen, der dafür gut geeignet ist. Dazu muss man auf der schmalen Seite des Displays die schwarze Lackierung auf der Rückseite des Frontglases vorsichtig mit einem Stanleymesser abkratzen, so dass ein kleiner Bereich in der Größe des Fotowiderstandes durchsichtig wird. Dabei muss man sehr behutsam vorgehen, da sich diese Lackierung sehr leicht abkratzen lässt.

ldr_raspbeery_pi_display_DSCF8105.jpg

Im nächsten Schritt wird der Fotowiderstand auf das Loch geklebt. Ich habe dazu einen 2-Komponentenkleber verwendet. 

ldr_raspbeery_pi_display_DSCF8111.jpg

Damit von hinten kein Licht durch die aufgekratzte Öffnung scheint, habe ich den Bereich noch mit einem schwarzen Klebeband abgeklebt. Die restliche "Elektronik", also der Kondensator, wird auf eine Buchsenleiste aufgelötet. Auch die Anschlussdrähte für den Fotowiderstand werden hier angelötet. Ich habe hier eine 2x20 polige Buchsenleiste verwendet, von der ich einfach einen Teil abgezwickt habe, da die rechten Pins schon vom Display belegt sind.

ldr_raspbeery_pi_display_DSCF8115.jpg

Hier noch eine weitere Aufnahme der Buchsenleiste.

ldr_raspbeery_pi_display_DSCF8116.jpg

Am Ende sollte das Display dann von vorne so aussehen:

ldr_raspbeery_pi_display_DSCF8113.jpg

Damit ist der Umbau auch schon fertig.

ldr_raspbeery_pi_display_DSC_7163.jpg

Das Raspberry Pi Display passt sich jetzt automatisch an die Helligkeit der Umgebung an. Das ist nicht nur energieeffizient, sondern schont auch die Augen. Viel Spaß beim Nachbauen!

Konversation wird geladen