Week 10 - Output Devices

Published on: March 26, 2025
In this week's assignment, the focus was on output devices — components that allow a microcontroller to communicate with the outside world by producing a visual, audible, or mechanical response. This can include anything from LEDs and motors to speakers and displays.
The goal was to connect and control at least one output device using a board we previously designed or modified. In my case, I continued working with my custom ESP32S3 Plus-based development board and chose to explore an I2C LCD display as an output device.
As always you will find our group assignment on our group documentation page. This week, we measured the power consumption of an LED strip depending on the color output.
I used my development board I produced for input week.
What is I²C and how does it work?
I²C (Inter-Integrated Circuit) is a super handy communication protocol that lets microcontrollers talk to sensors, displays, and other modules using just two wires: SDA (data) and SCL (clock). It's perfect for connecting multiple devices without a mess of cables.
- How it works: The master device sends a clock signal and controls the data flow, while slaves respond.
- Master/slave concept: One master can manage several slaves — each waits for its turn to communicate.
- Addressing: Every I²C device has a unique address so the master knows who to talk to.
- Connecting multiple I²C devices: You can hook up multiple modules on the same two lines, just avoid address conflicts.
Displaying Sensor Values on the I2C LCD
As part of the output devices week and to prepare for my final project, I wanted to display the distance readings from two ultrasonic sensors on an I2C LCD display.
Wiring the I2C LCD Display
I used a common 16x2 LCD display with an I2C interface backpack. It only requires four connections:
- GND → GND on DevBoard
- VCC → 5V or 3.3V on DevBoard
- SDA → SDA pin (GPIO5)
- SCL → SCL pin (GPIO6)
Initial Setup
#include <LiquidCrystal_I2C.h>
LiquidCrystal_I2C lcd(0x27, 16, 2);
void setup() {
lcd.init(); // initialize the LCD
lcd.clear(); // clear the LCD display
lcd.backlight(); // make sure backlight is on
lcd.setCursor(0, 0);
lcd.print("Starting...");
delay(1000);
}
void loop(){
}
At first, the display remained dark but I noticed the backlight flashed briefly every second. After checking all connections, I found that the GND was not properly connected. Using a different GND pin fixed the issue.
Finding the I2C Address
Before I could communicate with the display, I used an I2C scanner sketch to find its address. It returned 0x27, which is a common default for many I2C LCD modules.
Adjusting the Contrast
Even with correct wiring and code, the display remained dark until I manually adjusted the potentiometer on the back of the display. After tweaking the contrast, the characters became visible.
Display Sensor Values
I wanted to show the values from both sensors on the 2-line display. The first version used lcd.clear() in every loop and used a delay of 500ms, which caused flickering and a slowly updating display:
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("L: ");
lcd.print(distanceCmL);
lcd.print("cm");
lcd.setCursor(0, 1);
lcd.print("R: ");
lcd.print(distanceCmR);
lcd.print("cm");
delay(500);
I noticed that without using clear(), the digits would shift when displaying different values, which sometimes resulted in "cmm" being printed. To fix this issue, I decided to place the unit ("cm") at a fixed position on the display.
To improve readability and reduce flickering, I removed lcd.clear() and fixed the "cm" to a constant cursor position, updating only the numeric part:
char buffer[7];
dtostrf(distanceCmL, 6, 2, buffer);
lcd.setCursor(3, 0);
lcd.print(buffer);
lcd.setCursor(10, 0);
lcd.print("cm");
delay(200);
To ensure the measurement values are consistently aligned and avoid visual glitches like "cmm" due to shifting digits, I used a fixed-length char array as a temporary buffer. The function dtostrf(distanceCmL, 6, 2, buffer) converts the floating-point distance value into a right-aligned string with a total width of 6 characters and 2 decimal places, and stores it in the buffer. This prevents leftover characters when the number of digits changes.
The command lcd.setCursor(3, 0) positions the cursor at column 3 of row 0 (top row). This fixed placement ensures consistent alignment of the numeric value. Similarly, lcd.setCursor(10, 0) places the cursor at column 10 to append the unit "cm" after the numeric value. By splitting the output into numeric value and unit and using fixed positions, the layout remains stable even when the number of digits varies.

You can find my code for displaying the ultrasonic sensors in the download section. The wiring and functionality of the ultrasonic sensors were already explained in detail in previous weeks.

Final Setup
The final display shows the left sensor on line 1 and the right sensor on line 2. The refresh rate is almost fast enough for real-time feedback, and no more flickering or ghost characters appear.
This was a successful first prototype for measuring and displaying the values of the ultrasonic sensors for my robotic project.
Stepper Motor & Motor Driver
After successfully implementing the display output, my next step was to start designing the motor control board. Unfortunately, the package containing my components was tampered with, and the ESP32 modules I had ordered were stolen. This unexpected issue has delayed my progress slightly, but I'm already working on getting replacements as soon as possible.
