Controlling a servo motor with a push button on a custom KiCad PCB using the Seeed XIAO ESP32-C3
For this week's assignment, I extended my custom PCB design to include a servo motor as an output device, controlled by an SMD push button as the input. The system is built around the Seeed XIAO ESP32-C3, the same microcontroller I used in Week 9. Each press of the button rotates the servo by 10 degrees, allowing precise incremental position control.
This builds directly on my Week 9 work — where I established the button-to-LED input/output loop — and adds a mechanical output dimension to the design.
| Component / Tool | Details |
|---|---|
| Microcontroller | Seeed XIAO ESP32-C3 |
| Output device | Servo motor |
| Input | SMD tactile push button |
| Design software | KiCad |
| Programming IDE | Arduino IDE |
| Library | ESP32Servo |
| Button pin | D1 (INPUT_PULLUP) |
| Servo pin | D3 (PWM) |
A servo motor is a rotary actuator that enables precise angular position control. Unlike a standard DC motor that spins continuously, a servo receives a PWM (Pulse Width Modulation) signal and moves to a specific angle — typically in the range of 0° to 180°. This makes servos ideal for applications requiring controlled, repeatable mechanical movement such as robotic joints, camera gimbals, and smart fixtures.
PWM is a technique for encoding information in a pulsed signal by varying the duty cycle — the proportion of time the signal is HIGH versus LOW within each period. For servo motors, the pulse width (typically 1ms–2ms within a 20ms period) directly maps to a specific angle. The ESP32-C3 generates this signal in hardware, making servo control straightforward with the ESP32Servo library.
As part of the group assignment, we measured the power consumption of an output device using a multimeter in series with the circuit. The full methodology, measurements, and results are documented on Sarah's group assignment page.
Measuring power consumption made me much more deliberate about component selection. Servo motors draw significantly more current than LEDs — especially under load — which means power budgeting is critical for battery-powered designs. I also learned that current draw varies with mechanical load: an unloaded servo draws minimal current, while a stalled or heavily loaded servo can draw several times more.
Before designing the PCB, I selected the components needed for this output device project: a servo motor as the mechanical actuator and an SMD tactile push button as the user input.
| Component | Spec | Why it was chosen |
|---|---|---|
| TowerPro MG995 Servo | 4.8–7.2V, metal gear, 0°–180° | Metal gears for durability; digital control for precise 10° steps; directly relevant to final project actuation needs |
| SMD Tactile Button | 4-pin, surface-mount | Compact footprint, easy to route in KiCad, same button type proven in W09 design |
| Seeed XIAO ESP32-C3 | PWM on all GPIO, 3.3V logic | Reused from W09; ESP32Servo library handles PWM generation in hardware |
I designed the schematic in KiCad, expanding on the W09 board layout to add a servo motor connector. The XIAO ESP32-C3 drives the servo via its D3 PWM pin, while the SMD button on D1 remains as the user input.
After exporting Gerber files from KiCad, the PCB was fabricated and then populated with components. The SMD button was soldered first, followed by the servo connector and the XIAO ESP32-C3 module.
The firmware was written in Arduino IDE using the ESP32Servo library. The logic increments the servo angle by 10° with each button press, and resets to 0° after reaching 180°.
#include <ESP32Servo.h>
const int SERVO_PIN = D3;
const int BUTTON_PIN = D1;
Servo myServo;
int currentAngle = 0;
bool lastButtonState = HIGH;
void setup() {
Serial.begin(115200);
delay(3000);
pinMode(BUTTON_PIN, INPUT_PULLUP);
myServo.attach(SERVO_PIN);
myServo.write(currentAngle);
Serial.println("=== Servo + Button Ready ===");
Serial.print("Starting angle: ");
Serial.println(currentAngle);
Serial.println("Each button press = +10 degrees");
Serial.println("Resets to 0 after reaching 180°");
}
void loop() {
bool buttonState = digitalRead(BUTTON_PIN);
// Detect press moment only (not hold)
if (buttonState == LOW && lastButtonState == HIGH) {
delay(50); // debounce
buttonState = digitalRead(BUTTON_PIN);
if (buttonState == LOW) {
currentAngle += 10;
// Reset to 0 after reaching 180
if (currentAngle > 180) {
currentAngle = 0;
Serial.println(">>> Limit reached! Resetting to 0°");
}
myServo.write(currentAngle);
Serial.print("Button PRESSED --> Servo at ");
Serial.print(currentAngle);
Serial.println("°");
}
}
lastButtonState = buttonState;
delay(10);
}
ESP32Servo library required: The standard Arduino Servo.h is not compatible with the ESP32. Install ESP32Servo via Arduino IDE → Tools → Manage Libraries before compiling.
After uploading the firmware, I tested the board by pressing the button repeatedly to verify:
All conditions were satisfied. The servo responded smoothly and consistently, with no jitter between presses — confirming that the PWM signal and edge-detection logic are working correctly.
The video below shows the servo rotating 10° with each button press.
Initially I used the standard #include <Servo.h> library, which produced a compile error: "Servo.h: No such file or directory" on the ESP32 target. Switching to #include <ESP32Servo.h> and installing the ESP32Servo library via the Library Manager resolved this immediately.
| File | Description |
|---|---|
| W10_output_Button_Servo_KiCAD.zip | Full KiCad project (ZIP) |
w10-servo-button.ino | Arduino sketch (firmware — reproduced above) |
Design files are committed to the repository alongside this documentation. The full source code is reproduced in the Code section above.
This week extended my understanding of microcontroller programming from simple on/off outputs (the LED in W09) to position-controlled mechanical actuation. The servo motor introduces a richer interaction model: rather than a binary state, you're controlling a continuous physical parameter — angle — with precision.
The edge-detection pattern for the button (detecting HIGH→LOW transitions rather than just reading the current state) is a technique I'll use in my final project. It's a clean, software-only way to treat a physical button as an event trigger rather than a continuous input.
The power consumption measurement from the group assignment also changed how I think about output device selection. This needs to be factored into PCB design decisions — especially trace widths and power supply sizing. For my final project, this means I need to ensure the power rail can sustain the peak current without causing voltage drops that could reset the microcontroller.
AI Disclosure: Claude (Anthropic) was used as a writing tool to help proofread and structure the documentation on this page. All designs, fabrication, and technical decisions are my own.