Most makers start with Arduino libraries that hide the complexity of hardware. But to truly master embedded systems, you have to go deeper.
This project is about peeling back the layers. My goal was to build a real-time environmental monitor using the RT-Spark (STM32F407), but without relying on pre-made "black box" libraries. I wanted to understand exactly how the microcontroller talks to the pixels on the screen and the sensors on the board.
The task was clear: Interface an AHT21 Temperature/Humidity Sensor and a high-speed ST7789 Parallel LCD using bare-metal driver concepts on the STM32.
Before writing a single line of code, I had to become a detective. The RT-Spark documentation provided the schematics, but I had to map them to the STM32's peripherals manually.
The Display Challenge: The schematic revealed the LCD uses an 8080 Parallel Interface connected to the STM32's FSMC (Flexible Static Memory Controller). Crucially, it uses an 8-bit data bus (D0-D7), not the standard 16-bit bus often found in examples. This meant standard drivers wouldn't work out of the box.
The Sensor: The AHT21 was connected to pins PE0 and PE1. Since I wanted total control over the timing, I decided to implement a Software I2C (Bit-Banging) driver rather than using the hardware peripheral.
With the pin map in hand, I moved to STM32CubeMX to configure the silicon. This is where the project comes to life.
FSMC Setup: I enabled Bank 1, SRAM 3. The critical step was setting the Memory Data Width to 8 Bits. If I had left it at the default 16 bits, the color data would have been garbled.GPIO Setup: I configured PE0 and PE1 as Output Open-Drain. This is essential for I2C simulation because it allows the line to be pulled low by the master but "released" to be pulled high by the resistor, mimicking the I2C physical layer.
This was the core of the engineering effort. I couldn't just include <lcd.h>. I had to build the logic.
The LCD Driver (The 8-bit Fix):The standard ST7789 driver expects a 16-bit interface to send a pixel color (RGB565) in one cycle. Since my hardware is 8-bit, I wrote a custom wrapper function.
- The Logic: It takes a 16-bit color (e.g.,
0xF800for Red), shifts the bits to isolate the High Byte, sends it, and then masks the bits to send the Low Byte. - The Result: The STM32 successfully reconstructs 16-bit colors over an 8-bit bus.
The AHT21 Driver (The I2C Conversation): I implemented the full I2C protocol from scratch: Start, Stop, WaitAck, and SendByte.
- The Protocol: To trigger a measurement, I send the command
0xAC(Trigger Measure). - The Timing: I inserted a precise 80ms blocking delay to respect the sensor's physical measurement time before reading the 6-byte data packet.
The final step was fusing these drivers in main.c.
I created a custom GUI engine—lightweight and fast. Instead of using heavy font libraries, I implemented a simple bitmap font renderer. The main loop is a rhythmic cycle:
- Trigger the sensor.
- Wait for data.
- Convert the raw bits to Float values.
- Render the new values to the screen.
The result is a system that feels "alive." The high-speed parallel bus updates the screen instantly, and the custom driver ensures the sensor data is read correctly every single second.
OUTPUT
video link: https://drive.google.com/file/d/1F1-q0pxhbm6LW9tQk1RHIwzECM2CN-Oi/view?usp=sharing











Comments