Overview
The TV Backlight illuminates the wall behind the TV display to reduce eye strain. The backlight extends the background of the screen image by watching the color near an edge of the display. To reduce distraction, the color and brightness are integrated over time to avoid sudden changes.
The project code reads the AS7341 spectrometer sensor's eight visible light channels to determine the backlight target color. Using a Euclidean "color distance" comparison, the backlight color is slowly changed to match the target color, within a specified tolerance.
The spectrometer sensor settings are adjusted for relatively low display light levels. The sensor's internal amplifier gain is set to maximum and the integration step and time values are adjusted to maintain a moderately fast conversion rate. Also, rather than just analyzing red, green, and blue components, all eight visible light channels are used to increase the accuracy and resolution of color measurements.
In this configuration, just one of the sensor's channels can reach a count value of near 13k, producing a composite 8-channel resolution that approaches 8x1032 color combinations, much larger than the 17x106 (24-bit) color resolution of the NeoPixel strip. A color count to RGB converter helper reads the three primary color sensor channels, scales the count, and produces an RGB888 (24-bit) color value that's compatible with NeoPixels.
To assist in finding a position near the TV screen for the sensor, the Feather M4's on-board NeoPixel mimics the readings in real-time, albeit at a slightly lower brightness than the illumination strip NeoPixels.
Documents
Test video: https://youtu.be/yFqbalF0FGw
Next Steps
- Build a camouflaged enclosure and vertical mounting wands for the NeoPixel strips.
- Investigate animating the NeoPixel strip color change.
- For home security, enable the NotFlix (Fake TV) code when the TV display is dark for 5 minutes or more.
# SPDX-FileCopyrightText: 2023 Cedar Grove Maker Studios # SPDX-License-Identifier: MIT # TV Backlight # Feather M4 + AS7341 Color Sensor + NeoPixel strip import board import math from simpleio import map_range from adafruit_as7341 import AS7341, Gain import neopixel strip = neopixel.NeoPixel(board.D12, 30, brightness=1.0) pixel = neopixel.NeoPixel(board.NEOPIXEL, 1, brightness=0.5) # Instantiate and set up the spectrometer sensor i2c = board.I2C() # uses board.SCL and board.SDA sensor = AS7341(i2c) # The integration time step size in 2.78µs increments (JP 128, 999 max) sensor.astep = 256 # The integration time step count (JP 50, 255 max) sensor.atime = 50 # The internal gain setting (JP 256, 512 max) sensor.gain = Gain.GAIN_512X # The breakout board's LED brightness (JP 5, ? max) sensor.led_current = 0 # Disable the LED sensor.led = False # Calculate the sensor channel count value maximum (13,107) MAX_COUNT = (sensor.astep + 1) * (sensor.atime + 1) # The color matching tolerance index; 0 to 104,8856 (8 * max_sensor_count) TOLERANCE = 40 def convert_count_to_rgb(count_tuple): """Converts the 8-channel sensor measurement count tuple into RGB888 for use by a NeoPixel.""" r8 = int(map_range(count_tuple[7], 0, MAX_COUNT, 0, 255)) g8 = int(map_range(count_tuple[4], 0, MAX_COUNT, 0, 255)) b8 = int(map_range(count_tuple[2], 0, MAX_COUNT, 0, 255)) return r8, g8, b8 # The initial starting color (black) previous_color = [0, 0, 0, 0, 0, 0, 0, 0] while True: # Get counts from the eight visible channels target_color = sensor.all_channels[0:8] # Light up the on-board NeoPixel with the target color pixel[0] = convert_count_to_rgb(target_color) # Create list of channel deltas using list comprehension channel_deltas = [ (target_color[idx] - count) for idx, count in enumerate(previous_color) ] # Resolve squared deltas to a Euclidean difference color_distance = math.sqrt(sum([d**2 for d in channel_deltas])) # Check fof a color match color_match = bool(color_distance <= TOLERANCE) # Determine the step sizes needed to move from the previous color to the # target color if not color_match: # Create a list of step sizes for each channel if colors aren't matched steps = [8 * (d / color_distance) for d in channel_deltas] else: # Create a list of zero-sized steps if colors are matched steps = [0 for d in channel_deltas] # create a list of zero-sized steps # Calculate a new previous color by adding a step to the previous previous_color previous_color = [color + steps[idx] for idx, color in enumerate(previous_color)] # Light up the neopixel strip with the new previous color strip.fill(convert_count_to_rgb(previous_color))