Čoraz viac prístrojov a zariadení je vybavemých LCD displejmi, pre zobrazovanie hodnôt, či zadávanie vstupných parametrov. Jednou zo základných nevýhod je absencia automatického nastavenia jasu. Viacero obrazoviek v blízkosti pôsobí rušivo, ak nemajú približne rovnakú úroveň jasu. Navrhnuté riešenie pre RAspberry Pi 3b+ a 7" DSI displej je popísaný v nasledujúcom príspevku.
Riešenie vniklo pôvodne pre internetové rádio s VolumioOS , ale je vhodné pre akékoľvek zariadenie, kde beží Python. Algoritmus je jednoduchý. Snímač okolitého osvetlenia, sníma jeho intenzitu, na základe čoho sa riadi podsvietenie LCD dipleja. Ďalej bude popísané riešenie tak ako bolo realizované pre samoregulácia podsvietenia LCD pre Volumio, alebo iné podobné embedded systémysamoregulácia podsvietenia LCD pre Volumio, alebo iné podobné embedded systémy.
Požadované funkcie:
Automatické nastavenie jasu podľa okolitého osvetlenia
Logaritmická krivka jasu pre prirodzené vnímanie
Plynulé prechody zabraňujúce blikaniu
Konfigurovateľné parametre prostredníctvom externých súborov
Automatický štart pri bootovaní prostredníctvom systemd služby
Monitorovanie v reálnom čase s detailným zaznamenávaním
Hardvérové komponenty:
| Komponent | Model/Typ | Popis |
|---|---|---|
| Hlavná jednotka | Raspberry Pi 3B+ | Riadiaca jednotka |
| Displej | 7" LCD DPI (OFI009) | Dotykový displej pripojený cez DPI rozhranie |
| Enkodér | KY-040 | Otočný enkodér na ovládanie hlasitosti |
| Svetelný senzor | VEML7700 (BH-014PA) | 16-bit I2C senzor okolitého osvetlenia |
poznámka: Pre správnu funkciu riadenia jasu nie je použitie enkodéra KY-040 potrebné, uvádzam ho pre lepšie pochopenie výberu gpio vstupov a z dôvodu komplexnosti implementácie.
Schéma zapojenia:
Pin 3 (GPIO 2) ──────── Pin 2 (SDA)
Pin 5 (GPIO 3) ──────── Pin 1 (SCL)
Pin 6 (GND) ──────── Pin 4 (GND)
Otočný enkodér (KY-040):
CLK: GPIO pin (BCM číslovanie z gpio readall)
DT: GPIO pin (BCM číslovanie)
SW: GPIO pin (tlačidlo)
POWER+: 3.3V
GND: Zem
Poznámka: Nakonfigurujte piny enkodéra vo Volumio plugin-e Rotary Encoder pomocou BCM čísel pinov. Pre správnu funkciu riadenia jasu nie je použitie enkodéra KY-040 potrebné.
Pripojenie displeja:
Napájanie: +5V a GND z konektora X1
DPI signály: Pripojené podľa konfigurácie DPI v /boot/config.txt
Snímač osvetlenia VEML7700 som namontoval na plastový držiak vytlačený na 3D tlačiarni zo zadnej strany displeja. V prednej sklennej maske displeja som odstránil farebný nástrek v mieste, kde je za sklom snímač VEML7700, tak ako je to zrejmé z obrázku.
Softvérové požiadavky:
Operačný systém: Linuxový systém ako Raspi, Armbian, VolumioOS...
Python: 3.x
popis programu a jeho možnosti môžete nájsť aj na GITHUB-e.
Inštalácia:
príklad inštalácie s VolumioOS, pre iné OS je postup podobný a sotva potrebuje komentár pre užívateľa so základnými znalosťami. Pre bežiaci Linux systém môžete použiť pripravený inštalačný (prípadne odištalačný ) skript install.sh (uninstall.sh), ktorý najdete v sekcii na stiahnutie.
Krok 1: Príprava SD karty
Použite Balena Etcher na nahratie Volumio image, alebo iného napr.:
Volumio-3.832-2025-07-26-pi.zip
Krok 2: Konfigurácia APT repozitára
Po prvom spustení upravte APT zdroje:
sudo nano /etc/apt/sources.list
Nahraďte obsah:
deb http://archive.raspbian.org/raspbian/ buster main contrib non-free rpi
Povoľte SSH: http://volumio.local/dev
Krok 3: Inštalácia systémových závislostí
sudo apt update sudo apt upgrade sudo apt install python3-pip i2c-tools
Krok 4: Inštalácia Python knižníc
sudo pip3 install adafruit-circuitpython-veml7700 sudo pip3 install RPi.GPIO sudo pip3 install smbus
Krok 6: Vytvorenie Python skriptu
Vytvorte súbor /home/volumio/backlight_control.py:
nano /home/volumio/backlight_control.py
Vložte obsah skriptu (pozri backlight_control.py v tomto repozitár backlight LCD).
#!/usr/bin/env python3
"""
LCD Backlight Control based on Ambient Light Sensor (VEML7700)
Automatically adjusts display brightness based on surrounding light conditions
"""import smbus
import time
import os
import glob
from typing import Optional# ==================== DEFAULT CONFIGURATION ====================
INT_TIME = 1 # Interval for light measurement in seconds
MIN_BACKLIGHT = 12 # Minimum backlight value (0-255)
MAX_BACKLIGHT = 255 # Maximum backlight value
SMOOTHING_FACTOR = 0.3 # Smoothing factor for brightness changes (0.0-1.0)
LUX_MULTIPLIER = 0.75 # For gain=1/8, IT=100ms# I2C Configuration
I2C_BUS = 1
VEML7700_ADDR = 0x10# VEML7700 Registers
REG_ALS_CONF = 0x00
REG_ALS_WH = 0x01
REG_ALS_WL = 0x02
REG_POW_SAV = 0x03
REG_ALS = 0x04
REG_WHITE = 0x05
REG_INTERRUPT = 0x06# Sensor configuration for max range (0-120Klx), lowest precision
CONF_VALUES = [0x00, 0x00] # Max gain, 100ms integration time
INTERRUPT_HIGH = [0x00, 0x00]
INTERRUPT_LOW = [0x00, 0x00]
POWER_SAVE_MODE = [0x00, 0x00]# Configuration directory
CONFIG_DIR = "/etc/lcd_backlight/"
class BacklightController:
def __init__(self):
try:
print("=== Initializing Backlight Controller ===")# Initialize basic attributes
self.current_brightness = MIN_BACKLIGHT
self.file_handle = None
self.config_mtime = 0 # Track config file modification time
self.enabled = True # Service enabled/disabled flag
self.config_exists = os.path.exists(CONFIG_DIR)# Find backlight sysfs path
print("Searching for backlight device...")
backlight_paths = glob.glob("/sys/class/backlight/*/brightness")
if not backlight_paths:
raise FileNotFoundError("No backlight device found in /sys/class/backlight/")
self.backlight_path = backlight_paths[0]
print(f"Found backlight: {self.backlight_path}")# Initialize I2C bus
print("Initializing I2C bus...")
self.bus = smbus.SMBus(I2C_BUS)# Load configuration
print("Loading configuration...")
self._load_configuration()print(f"Backlight control initialized - {time.strftime('%Y-%m-%d %H:%M:%S')}")
print(f"Config source: {'FILES' if self.config_exists else 'DEFAULT VALUES'}")
print(f"Config: ENABLED={self.enabled}, MIN={self.min_backlight}, MAX={self.max_backlight}, INT_TIME={self.int_time}")# Initialize sensor
self._init_sensor()# Set initial brightness only if enabled
if self.enabled:
print("Setting initial brightness...")
self._update_brightness(force=True)
else:
print("Service is disabled, skipping initial brightness setup")print("=== Backlight Controller Initialized Successfully ===")except Exception as e:
print(f"Error during initialization: {e}")
raisedef _get_config_mtime(self) -> float:
"""Get the latest modification time of all config files"""
try:
if not os.path.exists(CONFIG_DIR):
return 0files = [
'lcd_enabled',
'lcd_min_backlight',
'lcd_max_backlight',
'lcd_int_time',
'lcd_lux_multiplier',
'lcd_smoothing_factor'
]mtimes = []
for filename in files:
filepath = os.path.join(CONFIG_DIR, filename)
if os.path.exists(filepath):
mtimes.append(os.path.getmtime(filepath))return max(mtimes) if mtimes else 0except Exception as e:
print(f"Error getting config mtime: {e}")
return 0def _check_config_changed(self) -> bool:
"""Check if configuration files have been modified or appeared/disappeared"""
try:
# Check if config directory existence changed
config_exists_now = os.path.exists(CONFIG_DIR)if config_exists_now != self.config_exists:
# Config directory appeared or disappeared
self.config_exists = config_exists_now
if config_exists_now:
print(f"\n[{time.strftime('%H:%M:%S')}] Configuration directory appeared, loading from files...")
else:
print(f"\n[{time.strftime('%H:%M:%S')}] Configuration directory removed, using default values...")
return True# If config exists, check for file modifications
if config_exists_now:
current_mtime = self._get_config_mtime()
if current_mtime > self.config_mtime:
print(f"\n[{time.strftime('%H:%M:%S')}] Configuration files changed, reloading...")
self.config_mtime = current_mtime
return Truereturn Falseexcept Exception as e:
print(f"Error checking config changes: {e}")
return Falsedef _read_config_value(self, filename: str, default_value, value_type=str):
"""Read a single config value from file or return default"""
try:
if not os.path.exists(CONFIG_DIR):
return default_valuefilepath = os.path.join(CONFIG_DIR, filename)
if not os.path.exists(filepath):
return default_valuewith open(filepath, "r") as f:
value = f.read().strip()if value_type == bool:
return bool(int(value))
elif value_type == int:
return int(value)
elif value_type == float:
return float(value)
else:
return valueexcept Exception as e:
print(f"Error reading {filename}, using default: {e}")
return default_valuedef _load_configuration(self):
"""Load configuration from files if they exist, otherwise use defaults"""if os.path.exists(CONFIG_DIR):
print(f"Loading configuration from: {CONFIG_DIR}")
self.config_mtime = self._get_config_mtime()# Read all config values from files
self.enabled = self._read_config_value('lcd_enabled', True, bool)
self.min_backlight = self._read_config_value('lcd_min_backlight', MIN_BACKLIGHT, int)
self.max_backlight = self._read_config_value('lcd_max_backlight', MAX_BACKLIGHT, int)
self.int_time = self._read_config_value('lcd_int_time', INT_TIME, int)
self.lux_multiplier = self._read_config_value('lcd_lux_multiplier', LUX_MULTIPLIER, float)
self.smoothing_factor = self._read_config_value('lcd_smoothing_factor', SMOOTHING_FACTOR, float)print(f"Loaded from files: enabled={self.enabled}, min={self.min_backlight}, max={self.max_backlight}")
print(f" int_time={self.int_time}, lux_mult={self.lux_multiplier}, smooth={self.smoothing_factor}")
else:
# Use default values from constants
print(f"Config directory not found, using default values")
self.config_mtime = 0
self.enabled = True
self.min_backlight = MIN_BACKLIGHT
self.max_backlight = MAX_BACKLIGHT
self.int_time = INT_TIME
self.lux_multiplier = LUX_MULTIPLIER
self.smoothing_factor = SMOOTHING_FACTORprint(f"Defaults: enabled={self.enabled}, min={self.min_backlight}, max={self.max_backlight}")
print(f" int_time={self.int_time}, lux_mult={self.lux_multiplier}, smooth={self.smoothing_factor}")def _init_sensor(self):
"""Initialize VEML7700 sensor with configuration"""
try:
self.bus.write_i2c_block_data(VEML7700_ADDR, REG_ALS_CONF, CONF_VALUES)
self.bus.write_i2c_block_data(VEML7700_ADDR, REG_ALS_WH, INTERRUPT_HIGH)
self.bus.write_i2c_block_data(VEML7700_ADDR, REG_ALS_WL, INTERRUPT_LOW)
self.bus.write_i2c_block_data(VEML7700_ADDR, REG_POW_SAV, POWER_SAVE_MODE)
time.sleep(0.1) # Wait for sensor to stabilize
print("VEML7700 sensor initialized successfully")
except Exception as e:
print(f"Error initializing sensor: {e}")
raisedef _read_lux(self) -> Optional[float]:
"""Read ambient light value from sensor in lux"""
try:
raw_value = self.bus.read_word_data(VEML7700_ADDR, REG_ALS)
lux = raw_value * self.lux_multiplier
return lux
except Exception as e:
print(f"Error reading sensor data: {e}")
return Nonedef _lux_to_brightness(self, lux: float) -> int:
"""
Convert lux value to brightness level (min_backlight to max_backlight)
Uses logarithmic curve for more natural perception
"""
if lux <= 0:
return self.min_backlight# Logarithmic mapping: 0-10000 lux -> min_backlight-max_backlight
import math
max_lux = 10000 # Maximum expected lux# Logarithmic scale feels more natural to human perception
brightness = self.min_backlight + (self.max_backlight - self.min_backlight) * (
math.log10(lux + 1) / math.log10(max_lux + 1)
)return int(max(self.min_backlight, min(self.max_backlight, brightness)))def _write_brightness(self, value: int) -> bool:
"""Write brightness value to sysfs with optimized file handling"""
try:
if self.file_handle is None:
self.file_handle = os.open(self.backlight_path, os.O_WRONLY)os.lseek(self.file_handle, 0, os.SEEK_SET)
os.write(self.file_handle, str(value).encode())
return Trueexcept OSError as e:
print(f"Error writing brightness to {self.backlight_path}: {e}")
self._close_file_handle()
return Falsedef _close_file_handle(self):
"""Safely close file handle"""
if self.file_handle is not None:
try:
os.close(self.file_handle)
except:
pass
self.file_handle = Nonedef _update_brightness(self, force: bool = False):
"""Read sensor and update backlight brightness"""
# Skip if disabled
if not self.enabled:
returnlux = self._read_lux()if lux is None:
return # Skip update on sensor errortarget_brightness = self._lux_to_brightness(lux)# Smooth brightness changes to avoid flickering
if not force:
self.current_brightness = int(
self.current_brightness * (1 - self.smoothing_factor) +
target_brightness * self.smoothing_factor
)
else:
self.current_brightness = target_brightnesssuccess = self._write_brightness(self.current_brightness)# Uncomment for debug
# if success:
# print(f"[{time.strftime('%H:%M:%S')}] Lux: {lux:6.1f} | Brightness: {self.current_brightness:3d}/{self.max_backlight}")def run(self):
"""Main control loop"""
try:
print("\n=== Starting main control loop ===")
print("Monitoring for configuration changes...")while True:
# Check for configuration changes
if self._check_config_changed():
self._load_configuration()
print(f"Active config: ENABLED={self.enabled}, MIN={self.min_backlight}, MAX={self.max_backlight}, INT_TIME={self.int_time}")# Update brightness if enabled
if self.enabled:
self._update_brightness()
time.sleep(self.int_time)
else:
# Check for config changes more frequently when disabled
time.sleep(1)except KeyboardInterrupt:
print(f"\n\nBacklight control stopped - {time.strftime('%Y-%m-%d %H:%M:%S')}")
finally:
self.cleanup()def cleanup(self):
"""Cleanup resources"""
print("Cleaning up resources...")
self._close_file_handle()
try:
self.bus.close()
except:
pass
if __name__ == "__main__":
try:
controller = BacklightController()
controller.run()
except Exception as e:
print(f"Fatal error: {e}")
import traceback
traceback.print_exc()
Nastavte oprávnenia:
chmod +x /home/volumio/backlight_control.py
Krok 7: Vytvorenie Systemd služby
sudo nano /etc/systemd/system/backlight.service
Obsah:
[Unit] Description=Služba kontroly podsvietenia After=multi-user.target [Service] Type=simple ExecStart=/usr/bin/python3 /home/volumio/backlight_control.py WorkingDirectory=/home/volumio User=volumio Group=volumio Restart=always RestartSec=5 [Install] WantedBy=multi-user.target
Krok 8: Povolenie a spustenie služby
sudo systemctl daemon-reload sudo systemctl enable backlight.service sudo systemctl start backlight.service
Krok 9: Overenie stavu služby
sudo systemctl status backlight.service
Sledovanie logov v reálnom čase:
sudo journalctl -u backlight.service -f
Konfigurácia:
Umiestnenie konfiguračných súborov
Vytvorte konfiguračný adresár:
sudo mkdir -p /etc/lcd_backlight
Dostupné konfiguračné parametre:
Minimálny jas (0-255)
echo "12" | sudo tee /etc/lcd_backlight/lcd_min_backlight
Maximálny jas (0-255)
echo "255" | sudo tee /etc/lcd_backlight/lcd_max_backlight
Interval merania (sekundy)
echo "1" | sudo tee /etc/lcd_backlight/lcd_int_time
Lux multiplikátor (kalibrácia)
echo "0.75" | sudo tee /etc/lcd_backlight/lcd_lux_multiplier
Faktor vyhladenia (0.0-1.0)
Nižšie hodnoty = plynulejšie prechody
echo "0.3" | sudo tee /etc/lcd_backlight/lcd_smoothing_factor
Aplikovanie zmien konfigurácie
sudo systemctl restart backlight.service
Predvolené hodnoty:
| Parameter | Predvolená hodnota | Popis |
|---|---|---|
| MIN_BACKLIGHT | 12 | Minimálny jas (tma) |
| MAX_BACKLIGHT | 255 | Maximálny jas (svetlo) |
| INT_TIME | 1 | Interval merania (s) |
| LUX_MULTIPLIER | 0.75 | Kalibračný koeficient luxov |
| SMOOTHING_FACTOR | 0.3 | Vyhladenie prechodov |
Používanie:
Automatický režim
Služba sa automaticky spustí pri štarte systému a beží nepretržite.
Manuálne ovládanie služby
# Stav služby sudo systemctl status backlight.service # Zastavenie služby sudo systemctl stop backlight.service # Spustenie služby sudo systemctl start backlight.service # Reštart služby sudo systemctl restart backlight.service # Zakázanie automatického štartu sudo systemctl disable backlight.service # Povolenie automatického štartu sudo systemctl enable backlight.service
Zobrazenie logov
# Posledných 50 záznamov sudo journalctl -u backlight.service -n 50 # Sledovanie v reálnom čase sudo journalctl -u backlight.service -f # Iba dnešné logy sudo journalctl -u backlight.service --since today
Príklad výstupu logov
[12:34:56] Lux: 245.3 | Brightness: 145/255 [12:34:57] Lux: 248.1 | Brightness: 147/255 [12:34:58] Lux: 251.7 | Brightness: 149/255
Riešenie problémov:
Senzor nie je detekovaný
i2cdetect -y 1
Povoľte I2C rozhranie:
sudo nano /boot/config.txt # Pridajte alebo odkomentujte: dtparam=i2c_arm=on
Reštartujte po zmene.
Služba sa nespustí
sudo journalctl -u backlight.service -n 50 --no-pager
Overte Python závislosti:
python3 -c "import adafruit_veml7700; print('VEML7700: OK')"
python3 -c "import RPi.GPIO; print('GPIO: OK')"
python3 -c "import smbus; print('SMBus: OK')"
Skontrolujte syntax skriptu:
python3 -m py_compile /home/volumio/backlight_control.py
Jas displeja sa nemení
ls -la /sys/class/backlight/*/brightness
Test manuálnej kontroly jasu:
echo 128 | sudo tee /sys/class/backlight/*/brightness echo 255 | sudo tee /sys/class/backlight/*/brightness
Skontrolujte oprávnenia súborov:
ls -la /home/volumio/backlight_control.py # Malo by byť: -rwxr-xr-x (spustiteľný)
Zmena jasu je príliš citlivá
echo "0.5" | sudo tee /etc/lcd_backlight/lcd_smoothing_factor sudo systemctl restart backlight.service
Displej príliš tmavý/svetlý
# Upravte minimálny jas echo "5" | sudo tee /etc/lcd_backlight/lcd_min_backlight # Upravte maximálny jas echo "200" | sudo tee /etc/lcd_backlight/lcd_max_backlight # Aplikujte zmeny sudo systemctl restart backlight.service
Nesprávne hodnoty senzora
# Pre vyššiu citlivosť echo "1.0" | sudo tee /etc/lcd_backlight/lcd_lux_multiplier # Pre nižšiu citlivosť echo "0.5" | sudo tee /etc/lcd_backlight/lcd_lux_multiplier sudo systemctl restart backlight.service
Monitorovanie:
Monitorovanie v reálnom čase
# Sledovanie zmien jasu sudo journalctl -u backlight.service -f
Štatistiky výkonu
# Čas behu a stav služby sudo systemctl status backlight.service # Nedávne logy s časovými pečiatkami sudo journalctl -u backlight.service -n 100 --no-pager
Pokročilá konfigurácia:
Vlastná krivka jasu
Upravte /home/volumio/backlight_control.py a modifikujte metódu _lux_to_brightness() pre implementáciu vlastných kriviek jasu.
Viaceré senzory
Skript je možné rozšíriť pre podporu viacerých VEML7700 senzorov pre rôzne zóny.
Štruktúra súborov:
/home/volumio/ └── backlight_control.py # Hlavný Python skript /etc/systemd/system/ └── backlight.service # Systemd služba /etc/lcd_backlight/ ├── lcd_min_backlight # Minimálna hodnota jasu ├── lcd_max_backlight # Maximálna hodnota jasu ├── lcd_int_time # Interval merania ├── lcd_lux_multiplier # Kalibrácia luxov └── lcd_smoothing_factor # Faktor vyhladenia
/usr/local/bin/ └── backlight_control.py # Hlavný Python skript


