Exposure unit
for expose PCB by UV light
BananaPi sigle board computer
Banana Pi is an open source hardware project lead by GuangDong BiPai technology co., LTD.
Radiation monitor with BananaPi Zero.
Screenshot of Grafana data interpretation of radiation meter
Load dependent speed controller of the mini drill
The basic equipment of every electrical engineering or model maker's workshop
DPS with soldered components
view of the mounted printed circuit board exported from CAD
Reflow oven
for soldering SMD prited circuits board
Reflow oven
also suitable for drying solid materials
RC433 for HomeAsistant
remote controler for garage door from HomeAssistant
LK-20 power source
Two independent power source for your laboratory
Internet radio and buzzer
Volumio media player with automatic brightness control
LK-29 smart meteo station
meteo station with radiation measurement

Backlight control for LCD displays

 

     More and more devices and equipment are being equipped with LCD displays for displaying values or entering input parameters. One of the basic disadvantages is the lack of automatic brightness adjustment. Multiple screens in close proximity are disruptive if they do not have approximately the same brightness level. The designed solution for the Raspberry Pi 3b+ and 7" DSI display is described in the following post.

disp veml 1

 

 The solution was originally created for an internet radio with VolumioOS, but it is suitable for any device running Python. The algorithm is simple. An ambient light sensor measures its intensity, which controls the LCD display backlight. The following describes the solution as implemented for LCD backlight self-regulation for Volumio, or other similar embedded systemsLCD backlight self-regulation for Volumio, or other similar embedded systems.

 

Required Features:

 Automatic brightness adjustment based on ambient light
 Logarithmic brightness curve for natural perception
 Smooth transitions to prevent flickering
 Configurable parameters via external files
 Automatic start at boot via systemd service
 Real-time monitoring with detailed logging

 

Hardware Components:

ComponentModel/TypeDescription
Main Unit Raspberry Pi 3B+ Control Unit
Display 7" LCD DPI (OFI009) Touch display connected via DPI interface
Encoder KY-040 Rotary encoder for volume control
Light Sensor VEML7700 (BH-014PA) 16-bit I2C ambient light sensor

 

note: For the correct function of brightness control, the use of the KY-040 encoder is not necessary, I mention it for a better understanding of the GPIO input selection and due to the complexity of the implementation.

 

Wiring Diagram:

 

Pin 1 (3.3V) ──────── Pin 5 (+3.3V)
Pin 3 (GPIO 2) ──────── Pin 2 (SDA)
Pin 5 (GPIO 3) ──────── Pin 1 (SCL)
Pin 6 (GND) ──────── Pin 4 (GND)


GPIO Pin Reference:

 

screenshot gpio readall


Rotary Encoder (KY-040):

CLK: GPIO pin (BCM numbering from gpio readall)
DT: GPIO pin (BCM numbering)
SW: GPIO pin (button)
POWER+: 3.3V
GND: Ground
Note: Configure the encoder pins in the Volumio Rotary Encoder plugin using BCM pin numbers. For the correct function of brightness control, the use of the KY-040 encoder is not necessary.

Display Connection:

Power: +5V and GND from connector X1
DPI signals: Connected according to DPI configuration in /boot/config.txt

I mounted the VEML7700 light sensor on a plastic holder printed on a 3D printer on the back of the display. I removed the color coating on the front glass mask of the display in the place where the VEML7700 sensor is located behind the glass, as is evident from the picture.
disp veml

 

Software Requirements:

Operating System: Linux system like Raspi, Armbian, VolumioOS...
Python: 3.x
 

Installation:

example installation for VolumioOS, for other OS the procedure is similar and hardly needs a comment for a user with basic knowledge. For a running Linux system, you can use the prepared installation (or uninstallation) script install.sh (uninstall.sh), which you can find in the download section.

Step 1: SD Card Preparation

Use Balena Etcher to flash the Volumio image, or another e.g.:

Volumio-3.832-2025-07-26-pi.zip
Step 2: APT Repository Configuration

After the first boot, edit the APT sources:

sudo nano /etc/apt/sources.list

Replace the content:

deb http://archive.raspbian.org/raspbian/ buster main contrib non-free rpi
Enable SSH: systemctl enable sshd.service
Step 3: Installation of System Dependencies
sudo apt update
sudo apt upgrade
sudo apt install python3-pip i2c-tools
Step 4: Installation of Python Libraries
sudo pip3 install adafruit-circuitpython-veml7700
sudo pip3 install RPi.GPIO
sudo pip3 install smbus
Step 5: I2C Sensor Verification
sudo i2cdetect -y 1

Expected output (sensor at address 0x10):

veml7700 addr

 

Step 6: Creating the Python Script

Create the file /home/volumio/backlight_control.py:

nano /home/volumio/backlight_control.py

Insert the script content (see backlight_control.py in this LCD backlight repository).

#!/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()

Set permissions:

chmod +x /home/volumio/backlight_control.py
 
Step 7: Creating the Systemd Service
sudo nano /etc/systemd/system/backlight.service

Content:

[Unit]
Description=Backlight Control Service
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

 

Step 8: Enabling and Starting the Service
sudo systemctl daemon-reload
sudo systemctl enable backlight.service
sudo systemctl start backlight.service

 

Step 9: Verifying Service Status
sudo systemctl status backlight.service

Monitoring logs in real time:

sudo journalctl -u backlight.service -f

 

Configuration:

Location of Configuration Files

Create a configuration directory:

sudo mkdir -p /etc/lcd_backlight
 

Available Configuration Parameters:

Minimum Brightness (0-255)
echo "12" | sudo tee /etc/lcd_backlight/lcd_min_backlight
Maximum Brightness (0-255)
echo "255" | sudo tee /etc/lcd_backlight/lcd_max_backlight
Measurement Interval (seconds)
echo "1" | sudo tee /etc/lcd_backlight/lcd_int_time
Lux Multiplier (calibration)
echo "0.75" | sudo tee /etc/lcd_backlight/lcd_lux_multiplier
Smoothing Factor (0.0-1.0)

Lower values = smoother transitions

echo "0.3" | sudo tee /etc/lcd_backlight/lcd_smoothing_factor
Applying Configuration Changes
sudo systemctl restart backlight.service

 

Default Values:

ParameterDefault ValueDescription
MIN_BACKLIGHT 12 Minimum brightness (dark)
MAX_BACKLIGHT 255 Maximum brightness (light)
INT_TIME 1 Measurement interval (s)
LUX_MULTIPLIER 0.75 Lux calibration coefficient
SMOOTHING_FACTOR 0.3 Transition smoothing

 

Usage:

Automatic Mode

The service starts automatically at system boot and runs continuously.

Manual Service Control
# Service status
sudo systemctl status backlight.service

# Stop service
sudo systemctl stop backlight.service

# Start service
sudo systemctl start backlight.service

# Restart service
sudo systemctl restart backlight.service

# Disable automatic start
sudo systemctl disable backlight.service

# Enable automatic start
sudo systemctl enable backlight.service
Viewing Logs
# Last 50 entries
sudo journalctl -u backlight.service -n 50

# Real-time monitoring
sudo journalctl -u backlight.service -f

# Today's logs only
sudo journalctl -u backlight.service --since today
Example Log Output
[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

 

Troubleshooting:

Sensor Not Detected
i2cdetect -y 1

Enable I2C interface:

sudo nano /boot/config.txt
# Add or uncomment:
dtparam=i2c_arm=on

Restart after change.

Service Fails to Start
sudo journalctl -u backlight.service -n 50 --no-pager

Verify Python dependencies:

python3 -c "import adafruit_veml7700; print('VEML7700: OK')"
python3 -c "import RPi.GPIO; print('GPIO: OK')"
python3 -c "import smbus; print('SMBus: OK')"

Check script syntax:

python3 -m py_compile /home/volumio/backlight_control.py
Display Brightness Does Not Change
ls -la /sys/class/backlight/*/brightness

Test manual brightness control:

echo 128 | sudo tee /sys/class/backlight/*/brightness
echo 255 | sudo tee /sys/class/backlight/*/brightness

Check file permissions:

ls -la /home/volumio/backlight_control.py
# Should be: -rwxr-xr-x (executable)
Brightness Change Too Sensitive
echo "0.5" | sudo tee /etc/lcd_backlight/lcd_smoothing_factor
sudo systemctl restart backlight.service
Display Too Dark/Bright
# Adjust minimum brightness
echo "5" | sudo tee /etc/lcd_backlight/lcd_min_backlight

# Adjust maximum brightness
echo "200" | sudo tee /etc/lcd_backlight/lcd_max_backlight

# Apply changes
sudo systemctl restart backlight.service
Incorrect Sensor Values
# For higher sensitivity
echo "1.0" | sudo tee /etc/lcd_backlight/lcd_lux_multiplier

# For lower sensitivity
echo "0.5" | sudo tee /etc/lcd_backlight/lcd_lux_multiplier

sudo systemctl restart backlight.service

 

Monitoring:

Real-time Monitoring
# Monitor brightness changes
sudo journalctl -u backlight.service -f
Performance Statistics
# Uptime and service status
sudo systemctl status backlight.service

# Recent logs with timestamps
sudo journalctl -u backlight.service -n 100 --no-pager

 

Advanced Configuration:

Custom Brightness Curve

Edit /home/volumio/backlight_control.py and modify the _lux_to_brightness() method to implement custom brightness curves.

Multiple Sensors

The script can be extended to support multiple VEML7700 sensors for different zones.

 

File Structure:

/home/volumio/
└── backlight_control.py          # Main Python script

/etc/systemd/system/
└── backlight.service              # Systemd service

/etc/lcd_backlight/
├── lcd_min_backlight              # Minimum brightness value
├── lcd_max_backlight              # Maximum brightness value
├── lcd_int_time                   # Measurement interval
├── lcd_lux_multiplier             # Lux calibration
└── lcd_smoothing_factor           # Smoothing factor
/usr/local/bin/ └── backlight_control.py # Hlavný Python skript

Related Articles

Copyright © Free Joomla! 4 templates / Design by Galusso Themes