Jeden DC-Motor in einen individuellen 360°-Servo verwandeln – Schritt-für-Schritt
In diesem Tutorial lernen wir, wie man jeden DC-Motor in einen eigenständigen Custom-Servomotor mit vielen Features verwandelt. Im Gegensatz zu normalen Servos mit begrenzter Bewegung (180° oder 270°) hat dieser uneingeschränkten 360°-Bereich und lässt sich der Drehbereich beliebig einstellen.
Zudem kann der Mittelpunkt (Center Point) des Servos verschoben werden – Center und Range gleichzeitig anpassbar.
Video ansehen oder schriftliche Anleitung unten lesen.
Weitere Features: Einstellbare Empfindlichkeit (Ansprechgeschwindigkeit). Drei Eingabemodi: Analogspannung/Potentiometer, RC-Sender, Serial Port (Winkelwerte per Serial Monitor am PC/Laptop).
Gleichzeitige Steuerung möglich: Serial Monitor Werte eingeben + manuell per RC-Sender bewegen. Servo kennt seine Position jederzeit, im Serial Monitor sichtbar.
Top-Feature: Continuous Rotation Mode. Auch hier Positionserfassung und -steuerung mit unendlicher Drehzahl. Beliebige Position bei beliebig vielen Umdrehungen anfahrbar.
Alles dank 12-Bit-Encoder AS5600 (magnetischer Drehwinkelsensor) und PID-Regelung für DC-Motor.
Ich entwickelte eine Custom-Servo-Controller-Platine mit eigenem Mikrocontroller – alles drauf, um jeden DC-Motor (bis 3,5 A) in Standalone-Servo zu verwandeln. Board mittig auf Abtriebswelle (inkl. Magnet auf Welle), 12V Versorgung → fertig.
Ich führe dich durch den kompletten Bau: Servo-Prinzip, Closed-Loop, PID, Custom PCB & Getriebe Design, Code.
Servomotor Funktionsprinzip
Typischen RC-Servo zerlegen: Kleiner DC-Motor, Controllerplatine, Potentiometer, 3-Draht-Anschluss (2× Strom, 1× Signal), Getriebe für Drehzahlreduktion/Drehmomenterhöhung.
Potentiometer an Abtriebswelle = Positionssensor → gibt Istposition an Controller. Controller regelt Motor basierend auf Sollsignal (Eingang) und Istposition (Rückführung) → Closed-Loop.
Sollposition vs. Istposition → Fehler → Regler → Motor bewegt bis Fehler = 0.
Eigenen Servomotor bauen
Für größeren DC-Motor dasselbe Closed-Loop-Prinzip: Positionssensor an Abtriebswelle + Mikrocontroller für Motoransteuerung.
Einfacher Positionssensor: Potentiometer (wie im RC-Servo). Problem: Begrenzter Drehbereich (270°) → begrenzt Servobereich. Mehrgangpotentiometer bieten mehr Bereich/Auflösung, aber immer noch begrenzt.
Für unbegrenzten Drehbereich: Encoder. Elektromechanische Geräte, die Winkelposition bei unbegrenzter Drehung erfassen. Typen: inkrementell/absolut, optisch/magnetisch/kapazitiv – jeweils Vor-/Nachteile.
AS5600 Encoder – Magnetischer Drehwinkelsensor
Ich wählte magnetischen Encoder AS5600: sehr kompakt, einfach zu implementieren, hohe Präzision. Winziger Chip!
Integrierter Hall-Effekt-Sensor detektiert Änderungen der Magnetfeldrichtung. Magnet an Motorabtriebswelle, 0,5–3 mm Abstand zum Chip.
Bei Drehung erfasst Hall-Sensor Feldänderungen. 12-Bit A/D-Wandler → 4096 Positionen pro Umdrehung (360°). Auflösung: 0,0878° pro Schritt. Beeindruckend, günstig, leicht beschaffbar – ideale Wahl.
Weiter: Mikrocontroller + DC-Motortreiber. DRV8871 (bis 3,5 A) + ATmega328 (SMD-Version für kompakte PCB). Ziel: Kleinste Standalone-PCB mit allem Drum und Dran.
Custom Servomotor Schaltplan
Kompletter Schaltplan dieses Custom-Servos.
Bauteile:
- AS5600 Magnetencoder – Amazon / AliExpress
- DRV8871 DC-Motortreiber – Amazon / AliExpress
- ATmega328p-AU – Amazon / AliExpress
- 16 MHz Quarz – Amazon / AliExpress
- AMS1117 5V Regler – Amazon / AliExpress
- 3386P Trimmer-Poti – Amazon / AliExpress
- Kondensatoren 0805 Sortiment – Amazon / AliExpress
- 12V DC-Motor ~50 U/min – Amazon / AliExpress
Hinweis: Affiliate-Links.
ATmega328 mit Minimalbeschaltung (16 MHz Quarz, paar Kondensatoren, Widerstand). AMS1117 wandelt 12V → 5V für Mikrocontroller/5V-Bauteile.
AS5600 mit empfohlener Beschaltung (2 Kondensatoren, 2 Pull-ups für I²C). DRV8871: 1 Strombegrenzungswiderstand, 2 Entkopplungskondensatoren. Zwei Potis an Analogeingängen: eines für Drehbereich, eines für Empfindlichkeit. Taster für Center-Point-Einstellung. 2-Wege-DIP-Schalter für Betriebsmoduswahl. Pinheader für Servo-Eingang (Analogspannung oder Digital-PWM von RC-Empfänger) + 5V/GND. Pinheader für Programmierung (SPI + Serial).
Workflow: Eingang (Sollwinkel) an zwei Pins (Analog von Poti oder Digital-PWM von RC-Empfänger) → Mikrocontroller vergleicht mit Istwinkel (AS5600 via I²C) → Fehlerberechnung → PWM an DRV8871 → Motor fährt bis Soll erreicht. Gesamtschaltung 12V, AMS1117 liefert 5V.
PCB Design
PCB so klein wie möglich: 40×40 mm. AS5600 auf Unterseite exakt mittig – für einfache Montage/Ausrichtung auf Abtriebswelle. Restliche Bauteile auf Oberseite – keine Störung.
Bestellt bei PCBWay: Gerber hochladen, Eigenschaften wählen, günstig. 4 Lagen (mittlere GND) – etwas teurer. Nur Farbe Weiß geändert, Immersion Gold (kostenlos) akzeptiert. Gerber in PCBWay Community.
Nach paar Tagen: PCB top Qualität, alles wie designed, Immersion Gold Finish.
Bestückung: Erst kleine SMDs (LED, Kondensatoren, Widerstände). Erste Erfahrung mit so kleinen SMDs – echt schlecht gelaufen. ATmega328 löten: Pins winzig, eng beieinander – irgendwie geschafft. AS5600 auf Rückseite easy, THT-Teile (DIP-Schalter, Potis, Klemmen, Pinheader) easy. Endergebnis: ordentlich.
Passendes Getriebe für DC-Motor und Controllerboard designen.
Custom Servo 3D-Modell
Getriebe in Onshape designed. Design hängt vom DC-Motor ab. Controllerboard auf Rückseite des Getriebes, mittig zur Abtriebswelle ausgerichtet.
Verwendeter DC-Motor: 37 mm Durchmesser, eingebautes Getriebe, 50 U/min Ausgang. Gut für Servo, aber ich wollte niedrigere Drehzahl für mehr Drehmoment → 3-fache Reduktion im Getriebe. Kegelräder (Herringbone) – effizient, 3D-druckfreundlich.
Freie Gestaltungsmöglichkeit: Getriebe ganz nach Motor und gewünschter Ausgangsdrehzahl. Direktwellenantrieb möglich (1:1 Übersetzung für korrekte Positionsverfolgung) oder Riemenantrieb. Unendliche Möglichkeiten.
3D-Modell & STL Download
3D-Modell im Browser mit Onshape (kostenloser Account für Privatnutzer).
3D-Modell (STEP) sowie STL-Dateien für 3D-Druck hier:
STEP-Datei:
STL-Dateien:
Custom Servo montieren
3D-gedruckte Teile bereit. Dazu M3-Schrauben, Gewindeeinsätze, Lager.
DC-Motor auf Grundplatte: M3×8 Schrauben. Zwei Zahnräder: Kleines direkt auf Motorwelle, großes = Servoausgang. Ausgangswelle zweiteilig: Gewindeeinsätze beidseitig (eine Seite für Zahnrad, andere für Anbauteile). Kleines Zahnrad: Gewindeeinsätze für Madenschrauben zur Welle. Beide Zahnräder gleichzeitig einschieben (Herringbone – nacheinander geht nicht). Kleines Zahnrad per Madenschraube auf Motorwelle fixieren. 12V an Motor → Getriebe testen.
Getriebe fertigstellen: Seitenplatte, Kugellager für Abtriebswelle, Oberdeckel. M3-Gewindeeinsätze in Rückplatte, 20 mm M3-Schrauben für Gesamtbefestigung. Getriebe test → läuft super. Abtriebswelle dreht sich hinten → Magnet für AS5600 einsetzen.
Controllerboard mit M2-Schrauben/Muttern am Getriebe befestigen. AS5600 nun perfekt auf Magnet ausgerichtet – misst Feldänderungen korrekt.
Wichtig: Magnetisierungsrichtung des Permanentmagneten entscheidend. Axial oder diametral magnetisiert → Magnet senkrecht oder parallel zum AS5600 IC positionieren. Ich musste Magnetrichtung ändern, da falsche Magnetisierung für AS5600.
Zwei Drähte an DC-Motor löten, an Controllerklemme anschließen. Strom: Zwei Drähte an Stromklemme, andere Seite DC-Hohlstecker für 12V Netzteil. Fertig – Custom-Servo done.
Controller programmieren
Bootloader auf ATmega328p brennen – ohne versteht er den Code nicht.
Bootloader brennen
Arduino Board nötig (hier Nano). SPI-Verbindung: Passende SPI-Pins Arduino ↔ Controllerboard verbinden.
Arduino IDE: ArduinoISP Beispielsketch öffnen → auf Nano hochladen. Nano ist jetzt ISP-Programmer.
Tools → Programmer: „Arduino as ISP“ → „Bootloader brennen“. Nano-LEDs blinken stark → Erfolg.
Code hochladen
USB-to-UART-Interface-Modul für Code-Upload. Controllerboard hat dedizierte Pins dafür (wie Schaltplan).
Code öffnen, hochladen. Vorher Bibliotheken installieren: AS5600 (Library Manager), PID (Library Manager). Upload → Code auf ATmega328 via USB-UART.
Fertig! Potentiometer anschließen zum Test. Hinweis: Analogeingang geht an „S“-Pin (nicht „A“) – PCB-Layoutfehler: Pins vertauscht an ATmega328. DIP-Schalter auf Analogmodus, Strom an.
Servo folgt Potentiometer – DC-Motor erfolgreich zum Servo verwandelt.
Quellcode
Code des Custom-Servos:
/* Custom-built Servo Motor - Arduino Code by Dejan, www.HowToMechatronics.com
Libraries: AS5600 encoder: https://github.com/RobTillaart/AS5600
PID controller: https://github.com/br3ttb/Arduino-PID-Library/blob/master/PID_v1.h */
#include "AS5600.h"
#include "Wire.h"
#include
AS5600 as5600;
double Pk1 = 2; double Ik1 = 0; double Dk1 = 0.025;
double Setpoint, Input, Output;
PID myPID(&Input, &Output, &Setpoint, Pk1, Ik1, Dk1, DIRECT);
#define motor_IN1 5
#define motor_IN2 6
#define ch1 2
#define centerSet 7
#define inputSwitch 3
#define modeSwitch 4
int ch1Value;
int encoderValue, inputValue, pwmValue;
String inString = "";
int centerAngle = 2047; // 180 degrees
int angleDifference = 0;
int angleValue = 0;
int leftLimit = 30;
int rightLimit = 4067;
int rangeAdjustment = 0;
float sensitivityAdjustment = 0;
float angle = 0;
int quadrantNumber = 2;
int previousQuadrantNumber = 3;
int numberOfTurns = 0;
float totalAngle = 0;
int error = 0;
char incomingByte = 0;
int intInput = 0;
void setup() {
Serial.begin(115200);
Serial.println(__FILE__);
Serial.print("AS5600_LIB_VERSION: "); Serial.println(AS5600_LIB_VERSION);
Wire.begin();
pinMode(motor_IN1, OUTPUT); pinMode(motor_IN2, OUTPUT);
pinMode(centerSet, INPUT_PULLUP); pinMode(inputSwitch, INPUT_PULLUP); pinMode(4, INPUT_PULLUP);
myPID.SetMode(AUTOMATIC);
myPID.SetOutputLimits(-255, 255); myPID.SetSampleTime(20);
}
void loop() {
encoderValue = as5600.readAngle();
// Continuous rotation mode
if (digitalRead(modeSwitch) == 0) {
while (Serial.available() > 0) {
int inChar = Serial.read();
if (isDigit(inChar)) inString += (char)inChar;
if (inChar == '\n') { Setpoint = inString.toInt(); inString = ""; }
}
if (digitalRead(inputSwitch) == 0) {
inputValue = analogRead(A0);
if (inputValue < 400) Setpoint -= 0.3;
if (inputValue < 300) Setpoint -= 0.3;
if (inputValue < 200) Setpoint -= 0.3;
if (inputValue > 600) Setpoint += 0.3;
if (inputValue > 700) Setpoint += 0.3;
if (inputValue > 800) Setpoint += 0.3;
} else {
inputValue = pulseIn(ch1, HIGH, 30000);
if (inputValue < 1450) Setpoint--;
if (inputValue < 1350) Setpoint--;
if (inputValue < 1250) Setpoint--;
if (inputValue < 1150) Setpoint--;
if (inputValue > 1550) Setpoint++;
if (inputValue > 1650) Setpoint++;
if (inputValue > 1750) Setpoint++;
if (inputValue > 1850) Setpoint++;
}
angle = encoderValue * 0.087890625;
if (angle >= 0 && angle <= 90) quadrantNumber = 1;
else if (angle >= 90 && angle <= 180) quadrantNumber = 2;
else if (angle >= 180 && angle <= 270) quadrantNumber = 3;
else if (angle >= 270 && angle <= 360) quadrantNumber = 4;
if (quadrantNumber != previousQuadrantNumber) {
if (quadrantNumber == 1 && previousQuadrantNumber == 4) numberOfTurns++;
if (quadrantNumber == 4 && previousQuadrantNumber == 1) numberOfTurns--;
previousQuadrantNumber = quadrantNumber;
}
if (totalAngle >= 0) totalAngle = (numberOfTurns * 360) + angle;
else totalAngle = (numberOfTurns * 360) + angle;
Input = totalAngle;
}
// Limited Rotation Mode
else if (digitalRead(modeSwitch) == 1) {
rangeAdjustment = analogRead(A1);
leftLimit = 0 + 30 + rangeAdjustment;
rightLimit = 4097 - 30 - rangeAdjustment;
if (digitalRead(inputSwitch) == 0) {
inputValue = analogRead(A0);
if (inputValue < 15) inputValue = 15;
if (inputValue > 1008) inputValue = 1008;
Setpoint = map(inputValue, 15, 1008, -255, 255);
} else {
inputValue = pulseIn(ch1, HIGH, 30000);
Setpoint = map(inputValue, 1000, 2000, -255, 255);
}
if (digitalRead(centerSet) == LOW) {
centerAngle = encoderValue;
angleDifference = 2047 - encoderValue;
delay(1000);
}
if (centerAngle < 2047) {
angleValue = encoderValue + angleDifference;
if (encoderValue < 4097 && encoderValue > (4096 - angleDifference))
angleValue = encoderValue - 4097 + angleDifference;
}
if (centerAngle > 2047) {
angleValue = encoderValue + angleDifference;
if (encoderValue >= 0 && encoderValue < abs(angleDifference))
angleValue = encoderValue + 4097 + angleDifference;
} else if (centerAngle == 2047) {
angleValue = encoderValue;
}
Input = map(angleValue, leftLimit, rightLimit, -255, 255);
}
// Sensitivity adjust
Pk1 = analogRead(A2) * 0.002;
myPID.SetTunings(Pk1, Ik1, Dk1);
myPID.Compute();
if (Output > 1) {
pwmValue = Output;
if (pwmValue < 30 && pwmValue > 5) pwmValue += 30;
if (pwmValue <= 5) pwmValue = 0;
digitalWrite(motor_IN1, LOW); analogWrite(motor_IN2, pwmValue);
} else if (Output < 1) {
pwmValue = abs(Output);
if (pwmValue < 30 && pwmValue > 5) pwmValue += 30;
if (pwmValue <= 5) pwmValue = 0;
analogWrite(motor_IN1, pwmValue); digitalWrite(motor_IN2, LOW);
} else if (Output > -1 && Output < 1) {
pwmValue = 0;
digitalWrite(motor_IN1, LOW); digitalWrite(motor_IN2, LOW);
}
}
Code-Überblick
Loop startet mit Encoder-Einlesen:
encoderValue = as5600.readAngle();
Continuous Rotation Mode (modeSwitch=0): Serial Monitor Werte als Setpoint.
while (Serial.available() > 0) { ... Setpoint = inString.toInt(); }
Poti-Modus: Analogwert lesen, Setpoint korrigieren je nach Ausschlag.
if (inputValue < 400) Setpoint -= 0.3; ... if (inputValue > 800) Setpoint += 0.3;
RC-Empfänger: pulseIn() lesen, Setpoint analog korrigieren.
RAW-Encoder in Winkel (×0,087890625), Quadranten-Erkennung für Drehzähler (Vollumdrehungen tracken). totalAngle = Input für PID.
if (quadrantNumber != previousQuadrantNumber) {
if (quadrantNumber == 1 && previousQuadrantNumber == 4) numberOfTurns++;
if (quadrantNumber == 4 && previousQuadrantNumber == 1) numberOfTurns--;
previousQuadrantNumber = quadrantNumber;
}
totalAngle = (numberOfTurns * 360) + angle;
Input = totalAngle;
Limited Rotation Mode (modeSwitch=1): Erst Range-Poti (A1) lesen → left/right Limit setzen.
rangeAdjustment = analogRead(A1);
leftLimit = 30 + rangeAdjustment;
rightLimit = 4097 - 30 - rangeAdjustment;
Poti-Eingang (A0) → Setpoint mappen. RC-Eingang → pulseIn() → Setpoint mappen.
Setpoint = map(inputValue, 15, 1008, -255, 255); // Poti
Setpoint = map(inputValue, 1000, 2000, -255, 255); // RC
Center-Set: Taster gedrückt → aktuelle Encoder-Position als neuen Mittelpunkt speichern.
if (digitalRead(centerSet) == LOW) { centerAngle = encoderValue; angleDifference = 2047 - encoderValue; delay(1000); }
Encoderwerte um angleDifference offseten → angleValue. Dann auf left/right Limit mappen → Input für PID.
Input = map(angleValue, leftLimit, rightLimit, -255, 255);
Empfindlichkeit: Trimmer-Poti (A2) justiert P-Glied. PID rechnen → Output → Motor PWM ansteuern.
Pk1 = analogRead(A2) * 0.002; myPID.SetTunings(Pk1, Ik1, Dk1); myPID.Compute();
Output >1 → rechts, < -1 → links, dazwischen → stop. PWM-Minimum-Anhebung für Anfahrschwelle.
Code nicht optimal, Verbesserungspotenzial. Nachbau: Troubleshooting einkalkulieren (SMD-Löten, PCB-Fehler). Ich brauchte mehrere Anläufe (falsche Verbindungen, PCB-Neuauflage, etc.).
Dieser Artikelabschnitt ist noch im Aufbau, später wieder schauen…
Herstellungsprozess
- 65.280 Relais mit Arduino steuern: Erweiterbare IO- & Relais-Lösungen
- Jawbreaker: Geschichte, Zutaten und Handwerk eines klassischen Hartbonbons
- Automatische Zentralschmieranlagen: Funktionsweise, Typen & Nutzen
- Arduino-gesteuerte NeoPixel-Lichtschwerter mit Party-Modi (Kylo Ren & Rey)
- Gesteuerter Roboter per Gestensteuerung – Bauen Sie Ihren eigenen Motion-Sensing-Bot
- Handwerk und Geschichte der Gitarre: Von antiken Wurzeln zu modernen Meisterwerken
- Modellbahn DCC-Zentrale selbst bauen mit Arduino – Kostenlose App inklusive
- RFID-basierte Lagerort-Ortung für Raspberry Pi, Arduino & Komponenten
- Der moderne Haartrockner: Design, Sicherheit und zukünftige Innovationen
- Wesentliche Elemente des physischen Prototypings für die Produktentwicklung