Community project
Servo Angle Controller
Generated with AIThis project builds a servo angle controller that lets you precisely adjust a servo motor's position using three push buttons and real-time feedback on an OLED display. The Arduino Uno reads button inputs with debouncing, calculates the desired servo angle, and displays the current angle on a 128x64 pixel screen while driving the SG90 servo to the selected position.
The guide includes a complete wiring diagram showing how to connect the OLED display via I2C, the servo to PWM pin 9, and the three control buttons to digital pins. You'll get a full parts list, step-by-step assembly instructions, and ready-to-upload firmware with button debouncing and angle stepping logic built in.
Wiring diagram
Interactive · read-only
Pan and zoom to explore the wiring. Remix the project to edit it in your own workspace.
Parts list
Bill of materials| Component | Qty | Notes |
|---|---|---|
| SSD1306 OLED0.96 inch, 128x64 | 1 | 0.96 inch 128x64 OLED display with I2C interface |
| SG90 ServoSG90 or compatible | 1 | Micro servo motor (SG90) |
| Push ButtonPlus | 1 | Momentary push button switch |
| Push ButtonMinus | 1 | Momentary push button switch |
| Push ButtonSelect | 1 | Momentary push button switch |
Assembly
5 stepsПодготовьте общее подключение питания
Подключите Nano к USB компьютера. Для сервопривода используйте отдельный стабильный источник 5 V, способный отдать не менее 1 A. Соедините его минусовой вывод с любым GND на Nano — без общего провода управляющий сигнал не будет понятен сервоприводу.
- Tip: Красный провод большинства SG90 — это +5 V, а коричневый или чёрный — минус.
- ⚠ Не подключайте красный провод сервопривода к выводу 5V Nano от USB: при повороте мотор может потребовать большой ток, из-за чего Nano будет перезапускаться или порт USB может отключиться.
Подключите маленький экран
На OLED соедините VCC с 5V Nano (питание), GND с GND Nano (земля), SDA с A4 Nano (данные) и SCL с A5 Nano (синхронизация).
- Tip: На некоторых платах Nano выводы A4 и A5 также подписаны SDA и SCL.
- ⚠ Убедитесь, что VCC и GND не перепутаны: неверно подключённое питание может повредить экран.
Подключите сервопривод
Соедините оранжевый или жёлтый провод сервопривода с D9 Nano (сигнал), красный — с плюсом отдельного источника 5 V (питание), коричневый или чёрный — с минусом этого источника и с GND Nano (земля).
- Tip: Перед первым включением снимите рычаг с вала, если механизм может упереться в ограничитель.
- ⚠ Не допускайте заклинивания рычага: зажатый сервопривод быстро потребляет большой ток и может перегреться.
Подключите три кнопки
У каждой кнопки используйте две противоположные ножки. Для кнопки «плюс» соедините одну ножку с D2 Nano (сигнал), вторую с GND (земля). Для «минус» соедините одну ножку с D3 (сигнал), вторую с GND. Для «выбор» соедините одну ножку с D4 (сигнал), вторую с GND. Дополнительные резисторы не нужны — плата включает их внутри себя.
- Tip: У четырёхконтактной тактовой кнопки две ножки на одной стороне уже соединены между собой; берите ножки с разных сторон кнопки.
- ⚠ Если кнопка срабатывает постоянно, поверните её на макетной плате на 90 градусов или переставьте провод на ножку с противоположной стороны.
Проверьте и включите устройство
Ещё раз проверьте общий GND Nano и источника сервопривода. После включения экран на 2 секунды покажет «Squad - service», затем угол 90 градусов. Кнопками плюс и минус выберите угол; строка будет мигать, пока вы не нажмёте «выбор». Удерживайте плюс и минус одновременно одну секунду, чтобы переключать шаг 5° и 1°.
- Tip: Строка Pulse показывает длительность управляющего импульса в микросекундах, рассчитанную для выбранного угла.
- ⚠ Перед включением убедитесь, что вал сервопривода может свободно повернуться в сторону начального положения 90 градусов.
Pin assignments
Board wiring reference| Pin | Connection | Type |
|---|---|---|
| 5V | oled_1 VCC | power |
| GND | oled_1 GND | ground |
| GPIO 18 | oled_1 SDA | i2c |
| GPIO 19 | oled_1 SCL | i2c |
| EXT | servo_1 VCC → Separate regulated 5 V servo supply positive | power |
| GND | servo_1 GND | ground |
| GPIO 9 | servo_1 SIGNAL | pwm |
| GND | button_plus GND | ground |
| GPIO 2 | button_plus SIGNAL | digital |
| GND | button_minus GND | ground |
| GPIO 3 | button_minus SIGNAL | digital |
| GND | button_select GND | ground |
| GPIO 4 | button_select SIGNAL | digital |
Firmware
Arduino#include <Arduino.h>
#include <Wire.h>
#include <U8g2lib.h>
#include <Servo.h>
struct Button {
uint8_t pin;
bool stableState;
bool lastReading;
unsigned long changedMs;
};
// Forward declarations
bool pressed(Button &button);
int pulseForAngle(int angle);
void drawScreen();
void showSplash();
const uint8_t SERVO_PIN = 9;
const uint8_t PLUS_BUTTON_PIN = 2;
const uint8_t MINUS_BUTTON_PIN = 3;
const uint8_t SELECT_BUTTON_PIN = 4;
const uint8_t OLED_SDA_PIN = A4;
const uint8_t OLED_SCL_PIN = A5;
const unsigned long DEBOUNCE_MS = 35;
const unsigned long BOTH_HOLD_MS = 1000;
const int MIN_ANGLE = 0;
const int MAX_ANGLE = 180;
const int SERVO_MIN_US = 544;
const int SERVO_MAX_US = 2400;
// Full-buffer mode is required by this OLED driver; the screen is sent only when it changes.
U8G2_SSD1306_128X64_NONAME_F_HW_I2C display(U8G2_R0, U8X8_PIN_NONE);
Servo servo;
Button plusButton = {PLUS_BUTTON_PIN, HIGH, HIGH, 0};
Button minusButton = {MINUS_BUTTON_PIN, HIGH, HIGH, 0};
Button selectButton = {SELECT_BUTTON_PIN, HIGH, HIGH, 0};
int selectedAngle = 90;
int appliedAngle = 90;
int angleStep = 5;
unsigned long bothPressedSinceMs = 0;
bool stepChangedForCurrentHold = false;
bool pressed(Button &button) {
bool reading = digitalRead(button.pin);
bool pressedEvent = false;
if (reading != button.lastReading) {
button.changedMs = millis();
button.lastReading = reading;
}
if (millis() - button.changedMs > DEBOUNCE_MS && reading != button.stableState) {
button.stableState = reading;
if (button.stableState == LOW) {
pressedEvent = true;
}
}
return pressedEvent;
}
int pulseForAngle(int angle) {
return map(angle, MIN_ANGLE, MAX_ANGLE, SERVO_MIN_US, SERVO_MAX_US);
}
void drawScreen() {
const bool pending = selectedAngle != appliedAngle;
char pulseText[8];
char angleText[4];
display.clearBuffer();
display.setFontMode(1);
display.setDrawColor(1);
display.setFont(u8g2_font_unifont_t_cyrillic);
display.drawUTF8(0, 12, "УГОЛ_СЕРВО...");
display.drawHLine(0, 15, 128);
snprintf(angleText, sizeof(angleText), "%d", selectedAngle);
display.setFont(u8g2_font_logisoso20_tn);
display.drawStr(0, 37, angleText);
display.setFont(u8g2_font_unifont_t_cyrillic);
// Short labels fit beside the large angle number without clipping.
display.drawUTF8(52, 36, pending ? "ОЖИДАНИЕ" : "ВЫБРАНО");
snprintf(pulseText, sizeof(pulseText), "%d", pulseForAngle(selectedAngle));
display.drawUTF8(0, 51, "ИМПУЛЬС");
display.drawStr(61, 51, pulseText);
display.drawUTF8(96, 51, "МКС");
display.drawUTF8(0, 64, angleStep == 1 ? "ШАГ 1 ГРАДУС" : "ШАГ 5 ГРАДУСОВ");
display.sendBuffer();
}
void showSplash() {
const char *splashText = "Squad - service";
display.clearBuffer();
// This is the largest bold Latin font that keeps the complete text on one 128-pixel line.
display.setFont(u8g2_font_helvB12_tf);
const int textX = (128 - display.getStrWidth(splashText)) / 2;
display.drawStr(textX, 38, splashText);
display.sendBuffer();
delay(2000);
}
void setup() {
pinMode(PLUS_BUTTON_PIN, INPUT_PULLUP);
pinMode(MINUS_BUTTON_PIN, INPUT_PULLUP);
pinMode(SELECT_BUTTON_PIN, INPUT_PULLUP);
Wire.begin();
display.begin();
display.enableUTF8Print();
showSplash();
servo.attach(SERVO_PIN, SERVO_MIN_US, SERVO_MAX_US);
servo.write(appliedAngle);
drawScreen();
}
void loop() {
bool redraw = false;
if (pressed(plusButton)) {
selectedAngle = min(MAX_ANGLE, selectedAngle + angleStep);
redraw = true;
}
if (pressed(minusButton)) {
selectedAngle = max(MIN_ANGLE, selectedAngle - angleStep);
redraw = true;
}
if (pressed(selectButton)) {
appliedAngle = selectedAngle;
servo.write(appliedAngle);
redraw = true;
}
const bool bothHeld = digitalRead(PLUS_BUTTON_PIN) == LOW && digitalRead(MINUS_BUTTON_PIN) == LOW;
if (bothHeld) {
if (bothPressedSinceMs == 0) {
bothPressedSinceMs = millis();
}
if (!stepChangedForCurrentHold && millis() - bothPressedSinceMs >= BOTH_HOLD_MS) {
angleStep = angleStep == 5 ? 1 : 5;
stepChangedForCurrentHold = true;
redraw = true;
}
} else {
bothPressedSinceMs = 0;
stepChangedForCurrentHold = false;
}
if (redraw) {
drawScreen();
}
}“Deploy to device” opens this project in Schematik, where you can flash it to your board over USB.
Remix this project
Make it yours in one click
Open a full copy of this project in your own Schematik workspace — diagram, code, parts, and assembly steps included. Swap the sensor, add features, or redesign the whole thing with AI. The author's original stays untouched.