Bad apple played on 8051 microcontroller study kit, powered by a computer server.
- MCU: STC89C52
- Kit: 8051 Pro-KIT (Schematic in
img
folder)
The project is divided into 2 components: the firmware on the controller and the software on the computer.
The server will process video footage (including camera capture) and send data to the controller to process.
This KIT uses 74HC595 shift register to control a 8x8 LED matrix. Input pins are clock, latch and serial data. The register outputs 8 bits periodically. This gift demonstrates how this shift register works:
(source in image)
At each clock's pulse, corresponding bit value in data pin will be pushed into an 8-bit queue. Once the latch pin is set, the register uses new value from the queue.
In order to display an image on the matrix, we need to scan and set LED status on each row continously. This is my approach:
- Set data bit to 1
- Latch, now the register outputs 0b10000000, only first row can be lit
- Set data bit to 0
- Update values in the first row, effectively render it
- Latch, register outputs 0b01000000
- Continue updating values in second row
- Repeat from 5. until all rows are rendered
The project also features PWM, please refer to source code or Result section.
Media data is processed using the code in src/svr
(will be refered to as "server"). The server compresses each frame into a 8x8 image with 4-level greyscale. Then, the data is sent to the microcontroller.
Here, I used a primal hand-shaking protocol:
- Server on, starts processing and waits for signals from MCU
- MCU sends signal
uint8_t 0xFF
to server, requests a whole frame - Server receives signal, waits for row signal
- MCU sends signal
uint8_t 0xFE
to server, request a row in that frame - Server receives signal, sends 8
uint8_t
-s to MCU - MCU receives data for a row, requests next row until all rows are cached into a buffer
- MCU requests the next frame
Why don't receive the whole frame at once, you may ask. Because there is a problem about interrupts.
I used 2 types of interrupt: timer and external.
Timer to steadily call the display function. Remember, to make the frame visible we have to draw it multiple times a second.
External to handle UART communication.
Interrupt priority is the problem here. Communication with server requires precise and steady timer to ensure consistent baudrate. Hence, display must have lower priority. However, if we don't call display function regularly enough, the frame will happen to be dimmed.
I did my best to balance these two sides. My method is to separate a frame into multiple rows, and call display directly after each row gets its data (normally, display is called by timer interrupt).
SDCC
(Small Device C Compiler) version4.2
or higherCMake
version 3.23 or higher- C++ compiler capable of
c++20
standard
- OpenCV
- SFML
- CSerialPort
- glad - an OpenGL loader (files included in source code)
It's recommended you get familiar with CMake build process.
It's possible to generate a binary of the firmware. However please note it's only compatible with the specific hardware as shown in img
folder.
Build the firmware:
On Windows most likely CMake will default the generator to Visual Studio, which will disregard the sdcc usage. If that's the case you must use something like MinGW Makefiles or Ninja.
# Currently in bad-apple folder
cmake --preset=base -DCMAKE_C_COMPILER=sdcc -DCMAKE_SYSTEM_NAME=Generic # -G"MinGW Makefiles" or -G"Ninja"
cmake --build build
The binary is build/bad-apple.hex
.
Depending on whether or not your CMake packages can be located via environment variables, you may have to add SDCC's include folder manually. One way to achieve that is to use CMake configure presets.
To build the server, first you must build and install 3 dependencies above in your local machine.
# Move to server folder
cd src/svr
cmake --preset=base
cmake --build build
You may have to provide environment variables to CMake, such as SFML_DIR
or so.
Once both firmware and server are built, flash the firmware into the microcontroller using external tools. stcgal did the job for me.
Important: In order for the firmware to behave correctly, you must enable 6T double-speed mode of the microcontroller via the flasher tool.
You can try calling the executable and see what parameters must be passed. That part is horrible though. Some things you would like to note:
- The configurations of
timer1
in source code were calculated so the perfect baud rate is19200 bps
. - Brightness thresholds must be 4 numbers between 0 and 1 in increasing order.
- The last parameter can be either
-c
(open camera) or-f<file_name>
(open video).
After a long time fiddling around I found some good examples:
# Currently in bad-apple/src/svr
sudo build/bad-apple-server /dev/ttyUSB0 19200 0.2 0.4 0.7 0.9 -frsc/bad-apple.mp4
sudo build/bad-apple-server /dev/ttyUSB0 19200 0.2 0.4 0.7 0.9 -frsc/fire.mp4
sudo build/bad-apple-server /dev/ttyUSB0 19200 0.2 0.4 0.6 0.85 -c
- If the video source is too small, the texture on preview window won't be created correctly.
- Software's source code is a mix of ketchup and spaghetti, should not be further inspected in any case.