10 Output devices
Assignment
- Group assignment:
- Measure the power consumption of an output device.
- Document your work on the group work page and reflect on your individual page what you learned.
- Individual assignment:
- Add an output device to a microcontroller board you've designed and program it to do something.
Overview
In this assignment, my primary goal was to integrate output devices into a microcontroller board I designed. However, during the testing phase, I encountered some technical issues with my custom PCB that are currently being debugged. To ensure a deep understanding of the output logic and to verify my code's functionality, I decided to use the Seeed Studio XIAO ESP32C3 as a prototyping platform. This allows me to master the programming of the RGB LED, Servo, and OLED display while I continue to troubleshoot my custom hardware.

Related Web Control Test
I have already completed the web-based control test with my custom board and a programmable LED strip. The board can receive control from the web interface and change the LED behavior. This confirms that the basic workflow between the custom board, LED output, and web interface works.
The detailed web control test is documented here:
Web Control / Network Connection Test

This Week 10 page focuses on the output device side. The later system integration page explains the network connection and web interface in more detail.
Custom Board + Programmable LED Strip Output Test
My custom board still had bugs this week, so I used the XIAO ESP32C3 to prototype the LED control instead. I ran the WS2813 strip with the same control logic I plan to use on my final board. The strip responded correctly, and the web interface could switch patterns remotely.
Result
LED output works. The XIAO drove the strip with stable timing, and the web-to-hardware path is confirmed. Once my custom board is fixed, I can drop the same code in. This maps directly to my final project: the cushion will use a 24V COB LED strip as the main feedback, controlled the same way.
Individual Assignment
System Architecture
I integrated three distinct output types into my XIAO ESP32C3 system: Visual (WS2813 RGB LED), Mechanical (Servo), and Information (OLED Display).
| Category | Item | Model | Port | Signal Type |
|---|---|---|---|---|
| Controller | Microcontroller | XIAO ESP32C3 | N/A | Digital |
| Visual | RGB LED Strip | Grove - WS2813 | D0 | Serial Data |
| Mechanical | Servo | Grove - Servo | D5 | PWM |
| Display | OLED Screen | 0.96" OLED | I2C | I2C Protocol |
Hardware Setup & Schematic
The XIAO ESP32C3 is mounted on the Expansion Board. Based on the physical connections observed:
- D0 (GPIO 2): Linked to the Data Input of the WS2813 LED Strip.
- D5 (GPIO 21): Linked to the Grove Port where the Servo is physically connected.
- I2C (GPIO 6/7): Linked to the OLED SDA/SCL.
Software Implementation
Project A: Yellow Breathing LED Strip (Visual)

Goal: Control the entire LED strip with real-time brightness feedback on the OLED.
#include <Adafruit_NeoPixel.h>
#include <U8g2lib.h>
#include <Wire.h>
// OLED Setup (I2C) - Explicitly defining pins for ESP32C3
U8G2_SSD1306_128X64_NONAME_F_HW_I2C u8g2(U8G2_R0, /* reset=*/ U8X8_PIN_NONE, /* clock=*/ 7, /* data=*/ 6);
#define LED_PIN D0
#define NUMPIXELS 30
Adafruit_NeoPixel strip(NUMPIXELS, LED_PIN, NEO_GRB + NEO_KHZ800);
void setup() {
Wire.begin(6, 7); // SDA, SCL
u8g2.begin();
strip.begin();
strip.show();
}
void updateLEDDisplay(int brightness, const char* phase) {
u8g2.clearBuffer();
u8g2.setFont(u8g2_font_ncenB08_tr);
u8g2.drawStr(0, 15, "DEVICE: LED STRIP");
u8g2.drawLine(0, 20, 128, 20);
u8g2.drawStr(0, 40, "Phase:");
u8g2.drawStr(50, 40, phase);
u8g2.drawStr(0, 55, "Brightness:");
int percent = map(brightness, 0, 150, 0, 100);
u8g2.setCursor(85, 55);
u8g2.print(percent);
u8g2.print("%");
u8g2.sendBuffer();
}
void loop() {
for(int brightness=0; brightness<150; brightness++) {
for(int n=0; n<NUMPIXELS; n++) {
strip.setPixelColor(n, strip.Color(brightness, brightness, 0));
}
strip.show();
if(brightness % 10 == 0) updateLEDDisplay(brightness, "Inhaling...");
delay(10);
}
for(int brightness=150; brightness>=0; brightness--) {
for(int n=0; n<NUMPIXELS; n++) {
strip.setPixelColor(n, strip.Color(brightness, brightness, 0));
}
strip.show();
if(brightness % 10 == 0) updateLEDDisplay(brightness, "Exhaling...");
delay(15);
}
}
Code Explanation & Debug:
- Visual Animation Logic: The "breathing" effect is created using a nested loop that progressively increments and decrements the RGB PWM values.
- Color Composition: Yellow is synthesized by mixing Red and Green at identical intensity levels ($R=G, B=0$).
- OLED Sync: To prevent the I2C display update from causing "stutters" in the LED animation, the screen is refreshed only every 10 brightness steps using the modulo operator (
% 10). - Pixel Density Configuration: Initially, only the first LED lit up. This was corrected by setting
NUMPIXELSto match the actual count (30) of the strip, ensuring the data signal is shifted through the entire daisy-chain. - Bootloader Mode: To successfully upload code to the XIAO ESP32C3, it is often necessary to quickly double-click the Reset button to enter "Bootloader Mode" when the serial port is not recognized or the upload fails.
- Hardware Interfacing: Using the
Adafruit_NeoPixellibrary allows for precise timing-sensitive serial data transmission to the WS2813 chips.
Project B: Massage Rhythmic Servo (Mechanical)

Goal: Focus on pure mechanical output to avoid I2C interference issues.
#include <ESP32Servo.h>
// Removed OLED code to focus on Servo stability
Servo myServo;
const int servoPin = D5; // GPIO 21
void setup() {
Serial.begin(115200);
// 1. Critical Servo PWM Allocation
ESP32PWM::allocateTimer(0);
ESP32PWM::allocateTimer(1);
myServo.setPeriodHertz(50); // Standard 50Hz
// 2. Attach Servo with pulse width range
if (!myServo.attach(servoPin, 500, 2400)) {
Serial.println("Servo attach failed!");
} else {
Serial.println("Servo attached successfully.");
}
}
void loop() {
// Rhythmic striking movement
Serial.println("Action: STRIKE (45°)");
myServo.write(45);
delay(800);
Serial.println("Action: RETRACT (135°)");
myServo.write(135);
delay(800);
}
Debug & Troubleshooting Record :
- Initial Failure: Uploading the code resulted in no movement. Serial Monitor reported "Error: Servo Attachment Failed!".
- Root Cause 1 (Pin Mapping): The servo was physically connected to the top-right Grove port on the Expansion Board. Initial code used
D1, but investigation revealed this port maps toD5(GPIO 21). - Root Cause 2 (Timer Conflict): ESP32C3 requires explicit PWM timer allocation. Without
ESP32PWM::allocateTimer(), the servo library failed to hook into the hardware PWM generator. - Root Cause 3 (I2C Interference): Simultaneous use of OLED and Servo caused persistent "snow" artifacts and occasional system hangs. To ensure reliable mechanical performance, OLED functionality was removed from the "Massager" project. This simplified the code and eliminated the bus noise caused by the servo's back-EMF.
- Final Result: By correcting the pin to
D5and stripping down the code to its mechanical core, the rhythmic movement is now stable and responsive.
24V COB LED Strip Power Test for Final Project
For the final project, I plan to use a 24V COB LED strip as the main light output. Compared with the programmable LED strip used in the web control test, the COB LED strip is more suitable for the final cushion because it gives a softer and more continuous light, without visible individual LED dots.
This part is a preparation test for the final light system. Before connecting the 24V COB LED strip to my custom board, I tested it separately with a bench power supply to check the real voltage, current, power consumption, brightness, and heat behavior.
At this stage, this is a power test only. The 24V COB LED strip has not yet been connected to my custom board, because it still needs a MOSFET or switching module.
Final light output plan
→ 24V COB LED strip
→ bench power supply test first
→ record V / A / W
→ check brightness and heat
→ decide normal working range
→ connect to custom board later through MOSFET / switching module

Materials and Tools Used
24V COB LED strip
→ temporary power wires / clips
→ bench power supply
→ V / A / W display reading
→ photo documentation
| Item | Purpose |
|---|---|
| 24V COB LED strip | Main light output planned for the final cushion |
| Bench power supply | Provides controlled voltage and current for testing |
| Power wires | Connect the LED strip to the power supply |
| Wire connectors / clips | Help make a temporary test connection |
| Camera / phone | Record the test setup and result |
LED Strip Parameters
| Item | Parameter | Notes |
|---|---|---|
| LED strip type | 24V COB LED strip | Continuous soft light |
| Tested length | 1.35 m | Cut section for the cushion prototype |
| Rated voltage | 24 V | Tested with a bench power supply |
| Supplier nominal power | about 12 W/m | Reference value from product information |
| Estimated nominal power for 1.35 m | about 16.2 W | 12 W/m × 1.35 m |
| Measured lower-current setting | 22.7 V / 0.30 A / about 6.8 W | Bright enough for soft feedback |
| Measured higher-current setting | 24.0 V / 0.57 A / 13.68 W | Brighter, but more heat |
| Selected working current | around 0.30 A | Chosen for normal use |
| Selected working power | about 6.8 W | Enough brightness with lower heat |
Test Workflow
Prepare COB LED strip
→ check polarity
→ connect to bench power supply
→ set voltage around 24 V
→ start from low current
→ increase current slowly
→ record V / A / W values
→ compare brightness and heat
→ choose safer working range for the final cushion
The test was used to understand the actual working range of the COB LED strip before system integration. I mainly compared two states: a lower-current state that was already bright enough, and a higher-current state that showed the upper brightness range but produced more heat.
Power Test Result
Power supply display
→ voltage
→ current
→ power
→ brightness observation
→ heat consideration
| Test | Voltage | Current | Power | Observation |
|---|---|---|---|---|
| Lower current test | 22.7 V | 0.30 A | about 6.8 W | Already bright enough for soft visual feedback |
| Higher current test | 24.0 V | 0.57 A | 13.68 W | Brighter, but heat needs more attention |
Test Result
The COB LED strip worked normally with the bench power supply. At around 0.30 A / about 6.8 W, the brightness was already enough for the visual feedback I need in the final cushion.

When the current was increased to around 0.57 A, the strip became brighter, but the heat also became more important to consider. Since the final project only needs a soft light effect around the base of the cushion, I do not need to run the strip at the higher-current level.
Decision for Final Project
Enough brightness at 0.30 A
→ lower heat
→ safer for cushion structure
→ selected as normal working range
From the power test, I decided to use around 0.30 A / about 6.8 W as the normal working range for the final project.
This decision is based on three reasons:
- The brightness is already enough for soft visual feedback.
- Lower current can reduce heat.
- The cushion does not need strong lighting; it only needs a visible and gentle light response.
The higher-current test helped me understand the upper brightness range, but I do not plan to use that level continuously.
Current Limitation and Next Step
At this stage, the 24V COB LED strip has been tested with a bench power supply, but it has not yet been connected to my custom board.
The reason is that the 24V COB LED strip needs a proper switching module or MOSFET circuit. The microcontroller pin cannot drive the 24V strip directly. The microcontroller should only send a control signal, while the external 24V power supply provides power to the LED strip.
The final workflow will be:
Web interface
→ custom board
→ switching module / MOSFET
→ 24V COB LED strip
After the module arrives, I will connect the COB LED strip to my custom board and update this page with the full wiring and control result.
Update: 24V COB LED Strip Controlled with a MOSFET Module
After the first bench power test of the 24V COB LED strip, I received the MOSFET switch module.
I continued the output device test and tried to control the COB LED strip using the XIAO ESP32C3.
In the earlier test, the COB LED strip was only powered directly by the DC power supply.
In this update, I added the MOSFET module between the DC power supply and the COB LED strip.
Summary
XIAO ESP32C3 sends a PWM signal
➡ MOSFET module receives the signal
➡ MOSFET module controls the brightness of the 24V COB LED strip
The XIAO ESP32C3 does not power the COB LED strip directly.
The 24V COB LED strip is still powered by the DC power supply.
The XIAO only sends a low-voltage PWM control signal to the MOSFET module.
System Setup
For this test, I separated the power supply into two parts.
Computer USB -> XIAO ESP32C3
DC Power Supply -> MOSFET Module -> 24V COB LED Strip
The USB cable powers the XIAO ESP32C3 and is also used for programming.
The DC power supply only powers the 24V COB LED strip.
The MOSFET module works as an electronic switch.
It allows the low-voltage PWM signal from the XIAO ESP32C3 to switch and dim the 24V COB LED strip.
MOSFET Module Check
Before wiring, I carefully checked the labels on the MOSFET module to confirm the pin layout.
Power Side
VIN+
VIN-
VOUT+
VOUT-
VIN+ / VIN-= 24V power inputVOUT+ / VOUT-= controlled LED output
Control Side
GND
TRIG / PWM
GND= common groundTRIG / PWM= PWM signal input
This specific MOSFET module does not require a 3V3 connection from the XIAO ESP32C3.
For the control side, I only connected:
XIAO GND -> MOSFET GND
XIAO D2 / GPIO4 -> MOSFET TRIG / PWM

Checking the labels on the back of the MOSFET module
Wiring
The wiring configuration for this test was:
| From | To | Function |
|---|---|---|
| DC Power Supply +24V | MOSFET VIN+ | 24V power input |
| DC Power Supply - | MOSFET VIN- | 24V power ground |
| MOSFET VOUT+ | COB LED Strip + | Controlled LED positive output |
| MOSFET VOUT- | COB LED Strip - | Controlled LED negative output |
| XIAO ESP32C3 GND | MOSFET GND | Common ground |
| XIAO ESP32C3 D2 / GPIO4 | MOSFET TRIG / PWM | PWM control signal |
| Computer USB | XIAO ESP32C3 | Board power and programming |
Safety Notes
24V must not be connected to the XIAO ESP32C3 3V3, 5V, or any GPIO pin.
Only GND is shared between the 24V power system and the microcontroller control system.

System wiring diagram for the MOSFET control test
Power Safety Setup
Before turning on the DC power supply, I checked the wiring order carefully.
1. DC Power Supply +24V -> MOSFET VIN+
2. DC Power Supply - -> MOSFET VIN-
3. MOSFET VOUT+ -> COB LED Strip +
4. MOSFET VOUT- -> COB LED Strip -
5. XIAO ESP32C3 GND -> MOSFET GND
6. XIAO ESP32C3 D2 / GPIO4 -> MOSFET TRIG / PWM
For the first test, I used a conservative power setting.
Voltage: 24V
Current limit: around 0.30A
I used this lower current limit because the COB LED strip was already bright enough at this level during the previous bench power test.
I also unfolded the LED strip during testing to reduce heat buildup.
Testing Process
1. Basic PWM Control
Before testing the breathing light effect, I first checked whether the MOSFET module responded correctly to the PWM signal.
Lower PWM value -> LED dimmer
Higher PWM value -> LED brighter
After confirming the PWM control worked correctly, I continued to the breathing light test.
2. Breathing Light Test
After the basic PWM control worked, I uploaded a breathing light program to the XIAO ESP32C3.
For the first breathing light test, I did not use the full brightness range.
I limited the maximum PWM value to 160 out of 255.
This was bright enough to observe the light effect clearly and was safer for the first test.
Testing the MOSFET-controlled 24V COB LED strip with the XIAO ESP32C3 and DC power supply
Code
#include <Arduino.h>
// Seeed Studio XIAO ESP32C3
// D2 = GPIO4
#ifndef D2
#define D2 4
#endif
const int MOS_PIN = D2;
// PWM settings
const int PWM_FREQ = 1000;
const int PWM_RES = 8;
// Limit the maximum brightness for the first test
const int MIN_DUTY = 0;
const int MAX_DUTY = 160;
// Breathing timing
const int BREATH_PERIOD_MS = 4000;
const int STEP_DELAY_MS = 20;
// Change to true if the MOSFET module works in reverse logic
const bool INVERT_PWM = false;
#if defined(ESP_ARDUINO_VERSION_MAJOR) && ESP_ARDUINO_VERSION_MAJOR >= 3
void setupPWM() {
ledcAttach(MOS_PIN, PWM_FREQ, PWM_RES);
}
void writePWM(int duty) {
duty = constrain(duty, 0, 255);
if (INVERT_PWM) {
duty = 255 - duty;
}
ledcWrite(MOS_PIN, duty);
}
#else
const int PWM_CH = 0;
void setupPWM() {
ledcSetup(PWM_CH, PWM_FREQ, PWM_RES);
ledcAttachPin(MOS_PIN, PWM_CH);
}
void writePWM(int duty) {
duty = constrain(duty, 0, 255);
if (INVERT_PWM) {
duty = 255 - duty;
}
ledcWrite(PWM_CH, duty);
}
#endif
void setup() {
Serial.begin(115200);
setupPWM();
writePWM(0);
delay(1000);
Serial.println("24V COB breathing test start");
Serial.println("XIAO ESP32C3 D2/GPIO4 -> MOSFET TRIG/PWM");
}
void loop() {
for (int t = 0; t < BREATH_PERIOD_MS; t += STEP_DELAY_MS) {
float phase = (float)t / BREATH_PERIOD_MS * TWO_PI;
// Smooth breathing curve
float breath = 0.5 - 0.5 * cos(phase);
int duty = MIN_DUTY + breath * (MAX_DUTY - MIN_DUTY);
writePWM(duty);
delay(STEP_DELAY_MS);
}
}
Code Notes
In this code, D2 / GPIO4 is used as the PWM output pin.
XIAO ESP32C3 D2 / GPIO4 -> MOSFET TRIG / PWM
The PWM duty value controls the brightness of the COB LED strip.
0 = off or very dim
160 = maximum brightness used in this test
255 = full PWM value, not used in this first test
The breathing effect is created with a cosine curve.
dark -> brighter -> brightest -> darker -> repeat
This makes the light transition smoother than a simple step-by-step fade.
Result
The breathing light test worked successfully.
The XIAO ESP32C3 was powered by USB.
The 24V COB LED strip was powered separately by the DC power supply.
The MOSFET module received the PWM signal from the XIAO ESP32C3 and controlled the brightness of the COB LED strip correctly.
The COB LED strip showed a smooth breathing light effect.
Successful 24V COB breathing light effectThis test confirmed that the 24V COB LED strip can be controlled reliably for my final project, as long as the power side and the microcontroller control side are separated correctly.
What I Learned
This test helped me understand how to separate the control side and the power side when working with a 24V output device.
For small LEDs, the microcontroller can sometimes drive the LED directly.
However, for a 24V COB LED strip, the microcontroller should only send the control signal.
The correct control logic is:
XIAO ESP32C3 -> sends PWM signal
MOSFET module -> switches and dims the 24V output
DC power supply -> powers the COB LED strip
The most important part of this test was checking the labels on the MOSFET module before wiring.
Different MOSFET modules may have different pin layouts, so I should not rely only on wire colors.
For this module, the final control connection was:
XIAO GND -> MOSFET GND
XIAO D2 / GPIO4 -> MOSFET TRIG / PWM
The 24V power stayed only in this path:
DC Power Supply -> MOSFET Module -> COB LED Strip
This test is useful for my final project because the COB LED strip will become the main light output of the cushion.