Last week at PyCon, I had a lot of fun with the MicroPython on BBC micro:bit team. I wrote a few simple games to learn the MicroPython API, including a multiplayer game that uses the new radio module.
This weekend, I built a hardware integration project to demonstrate some other capabilities.
(Very brief background: MicroPython is a lean re-implementation of the Python programming language for microcontrollers. The BBC micro:bit is a microcontroller which was given to over a million middle school students in the UK. The micro:bit uses a cheap, low-power ARM hardware platform originally designed for IoT-like devices. It supports a few programming languages and development environments, including a port of MicroPython which is under active development. Here is the story of how that happened.)
Controlling LCD Monitor Brightness
This project automatically changes the brightness of a computer monitor using the ambient light level. Instead having to press an awkward sequence of buttons on the back of the display, the microcontroller now handles it automatically, day and night.
The program also responds to user feedback: by pressing "up" and "down" buttons on the microcontroller, the program adjusts its internal mapping to suit your preference. Eventually the program learns how bright you like your monitor over the full range of possible light conditions.
Click below for a video, skip to 1:05 for the action.
Read on to learn how this came together.
Wiring and Signaling
Before writing our code, we need a way to send commands to the monitor. Here are my design goals:
- Minimize likelihood of breaking stuff
- Build this in a way that is easy to 'undo' if I want to re-purpose the hardware
My first idea was to literally press the brightness control buttons using externally mounted solenoids. I abandoned this because solenoids require a separate high-voltage (12-24V) input, make noise, and could prematurely wear out the buttons.
Instead, I reverse-engineered the electrical signals for the button panel, and built a wiring harness to send my own signals. This required exploratory surgery but resulted in a clean and simple integration. After de-bezeling the monitor, I found the buttons on a separate PCB, connected to the main system board with a ribbon-ish cable.
The button panel has 7 buttons and an LED that glows two colors, but it connects with only 6 conductors. How does this work? I probed with a multimeter and discovered that groups of three buttons share a common conductor, using different resistance values to disambiguate the signals. The six wires in the cable are used for the following:
- Buttons sw001 (2KΩ), sw002 (1KΩ), sw003 (0Ω)
- Buttons sw004 (1KΩ), sw005 (2KΩ), sw006 (0Ω)
- Button sw007 (power)
- White LED (power on)
- Orange LED (standby mode)
By poking some wires/resistors into the ribbon cable connector, I could simulate a press of any button. Now I only needed to connect my own system in parallel with the button panel.
The local electronics shop had these 6-conductor cables with breadboard-pluggable connectors -- perfect for a wiring harness. I cut the button panel cable, separated and stripped the wires, and soldered together my harness. After re-assembling the monitor, the only visible difference are two connectors dangling from the back. You can plug them together and everything still works, satisfying design goal #2.
Sending Commands with GPIO
The micro:bit and monitor both use 3.3 volts for signaling, but I couldn't use GPIO signals from the microcontroller directly; I had to ground the monitor's own voltage (sometimes with 1 or 2 KΩ resistance) instead. A mechanical relay would work for this application, but relays are bulky/noisy, and there is a better solid-state solution.
An NPN transistor can be used to switch a load from a different device, as long as the signaling input and driven load share a common ground. Since the load is only a few milli-amps, the PN2222A worked fine.
So, I tied the grounds for the micro:bit and monitor. Each transistor has its base connected to a GPIO pin on the micro:bit. Its collector is tied to a button input (some with a resistor in series), and its emitter connects to ground.
I fired up the MicroPython REPL, sent a few commands, and the monitor responded to my signals. This is what a "hello world" button press looks like:
>>> pin8.write_digital(1) >>> pin8.write_digital(0)
I placed the photosensor on top of the monitor pointing upward. Connecting it to GPIO is simple, here is a good writeup. Reading the sensor value is also simple:
>>> pin0.read_analog() # Curtains closed 155 >>> pin0.read_analog() # Curtains open 315
This completes the hardware setup.
As it is just Python, MicroPython and the microbit module are a pleasure to use. You can see my ~170-line program on GitHub, hopefully it speaks for itself. A few interesting problems I had to solve in code:
- Working around the monitor's debounce logic, which discards button signals that are too short or too frequent
- Thresholding on ambient light changes so we aren't constantly making tiny adjustments
- Flattening/smoothing the brightness mapping when user provides divergent or conflicting feedback
- Resetting the monitor to a known brightness value on startup (the micro:bit has no way to ask the monitor for its brightness)
- Detecting when the monitor is off, and not doing anything until we are sure it is back on
In this vein, it's important not to send any commands that the monitor isn't ready to receive, else the program's internal knowledge of the brightness value will diverge from reality, or you will cause undesired operation (like entering a different menu). Conversely, if you space out your commands too much then you'll annoy the user with a lingering on-screen menu. A lot of my debugging time was spent optimizing this.
The code was written for an ASUS VN248H monitor. You're welcome to adapt this project for your own monitor, you just need to figure out its button signaling and control logic.
Building with a microcontroller is fun, especially using Python. The tweak-test cycle time is very short with the micro:bit; the REPL will run your Python code interactively, and flashing a complete program only takes a few seconds.
The next step for this project is packaging the circuitry on a board in a housing, and mounting it somewhere unobtrusive. I'm also eager to play more with the micro:bit's radio module; I have some ideas for home automation and security projects.
(micro:bit image credit wikipedia)