Превращение любого DC-двигателя в кастомный 360° сервопривод — пошаговое руководство
В этом уроке научимся превращать любой DC-двигатель в автономный кастомный сервопривод с богатым функционалом. В отличие от обычных серв с ограниченным ходом 180° или 270°, этот имеет неограниченный диапазон 360° (непрерывное вращение) и позволяет настраивать диапазон поворота под любые нужды.
Кроме того, можно смещать центральную точку сервы — одновременно настраивать и центр, и диапазон.
Смотрите видео или читайте текст ниже.
Ещё одна фича — регулировка чувствительности (скорости отклика). Входов три режима:
- Аналоговое напряжение / потенциометр;
- RC-передатчик;
- Последовательный порт — ввод углов через Serial Monitor на ПК.
Можно комбинировать: вводить углы в Serial Monitor и параллельно рулить RC-передатчиком — серва всегда знает свою позицию и показывает её в мониторе.
Главная фича — режим непрерывного вращения с отслеживанием позиции. Можно задать любое положение вала с бесконечным числом оборотов.
Всё это благодаря 12-битовому энкодеру AS5600 (магнитный угловой датчик) и ПИД-контролю DC-двигателя.
Сделал кастомную плату серво-контроллера со своим микроконтроллером и всей обвязкой — просто установите плату в центр выходного вала (с магнитом на валу), подключите любой DC-двигатель до 3.5 А, питание 12V — и получаете сервопривод со всеми этими возможностями.
Пройдём весь процесс: принцип сервы, замкнутый контур, ПИД, проектирование PCB и редуктора, код.
Принцип работы сервопривода
Разберём типичный RC-сервопривод. Внутри: малый DC-двигатель, плата контроллера, потенциометр, трёхпроводное подключение (2 — питание, 1 — сигнал), редуктор для снижения скорости и повышения момента.
Потенциометр на выходном валу — датчик позиции, сообщает контроллеру текущий угол. Контроллер сравнивает входной сигнал (желаемую позицию) с актуальной от потенциометра — замкнутая система управления.
Вход (желаемая позиция) сравнивается с актуальной позицией от датчика. Разница (ошибка) обрабатывается контроллером, который командует двигателем до достижения цели.
Как сделать кастомный сервопривод
Чтобы построить свою серву с более крупным DC-двигателем, реализуем тот же замкнутый контур. Нужен датчик позиции на выходном валу и микроконтроллер для управления двигателем.
Простейший датчик — потенциометр как в RC-сервах. Проблема: ограниченный ход 270°, что лимитирует диапазон сервы. Есть многооборотные потенциометры — лучше диапазон и разрешение, но всё равно ограничены.
Для неограниченного вращения нужен энкодер. Энкодеры — электро-механические устройства, отслеживающие угловую позицию с неограниченным вращением. Много типов: инкрементные/абсолютные, оптические/магнитные/ёмкостные — у каждого плюсы/минусы.
Энкодер AS5600 — магнитный угловой датчик
Выбрал магнитный энкодер AS5600 — компактный, лёгкий в внедрении, высокоточный. Посмотрите, как мал этот чип.
Встроенный Холл-сенсор фиксирует изменения направления магнитного поля. Нужно лишь приклеить магнит на выходной вал и поставить чип на расстоянии 0.5–3 мм.
При вращении вала/магнита Холл-сенсор ловит изменения поля. Встроенный 12-битный АЦП выдаёт 4096 позиций на оборот (360°). Разрешение 0.0878° — очень точно. Дешёвый и доступный — идеальный выбор.
Остальное: микроконтроллер и драйвер DC. Выбрал DRV8871 (до 3.5 А) и ATmega328 (SMD-версия — компактнее DIP). Цель — максимально маленькая PCB, где всё помещается для автономной работы сервы.
Схема кастомного сервопривода
Полная схема ниже. Компоненты:
- AS5600 магнитный энкодер — Amazon / AliExpress
- DRV8871 драйвер DC — Amazon / AliExpress
- ATmega328p-AU — Amazon / AliExpress
- Кварц 16 МГц — Amazon / AliExpress
- Стабилизатор AMS1117 5V — Amazon / AliExpress
- Подстроечный резистор 3386P — Amazon / AliExpress
- Набор конденсаторов 0805 — Amazon / AliExpress
- DC-двигатель 12V ~50 RPM — Amazon / AliExpress
Партнёрские ссылки.
ATmega328 с минимальной обвязкой (кварц, конденсаторы, резистор). Питание 5V — AMS1117 от 12V входа. AS5600 с рекомендованной схемой (2 конденсатора, 2 pull-up на I2C). DRV8871 — 1 резистор ограничения тока, 2 развязывающих конденсатора. Два подстроечных резистора на аналоговых входах — настройка диапазона поворота и чувствительности. Кнопка — установка центральной точки. DIP-переключатель 2-канальный — выбор режимов работы. Pin header для входов (аналоговый или PWM от RC-приёмника) с 5V и GND. Pin header для прошивки (SPI + UART).
Алгоритм: вход (желаемый угол) — аналоговый от потенциометра или PWM от RC — в микроконтроллер. Там сравнивается с актуальным углом от AS5600 (I2C). Микроконтроллер считает ошибку, по ПИД выдаёт PWM на DRV8871 → DC-двигатель до нужной позиции. Всё питание 12V, AMS1117 даёт 5V для логики.
Проектирование PCB
По схеме сделал PCB максимально компактной — 40×40 мм. Энкодер на нижней стороне ровно в центре — для легкого монтажа и выравнивания с выходным валом. Остальные компоненты с другой стороны — не мешают энкодеру и валу.
Заказ у PCBWay: загрузка Gerber, выбор параметров, адекватная цена. Сделал 4 слоя (средние — GND) — чуть дороже. Дефолты, только цвет белый, согласился на immersion gold при возможности. Gerber в сообществе проектов PCBWay (там же заказ).
Через несколько дней PCB пришла. Качество отличное, всё как в проекте, покрытие immersion gold.
Паяю компоненты. Начал с мелких SMD (LED, конденсаторы, резисторы). Первый раз паяю такие мелкие SMD — было очень плохо. Самое сложное — ATmega328, ножки микроскопические и близко, но получилось. AS5600 на обратной стороне — легко, выводные компоненты (DIP-переключатель, подстроечники, клеммники, pin headers) — просто. Итог — плата выглядит достойно.
Теперь делаем подходящий редуктор для DC-двигателя и этой платы.
3D-модель кастомной сервы
Редуктор проектировал в Onshape. Дизайн зависит от DC-двигателя. Можно использовать любой DC-двигатель с нашей платой.
Здесь — двигатель 37 мм с встроенным редуктором 50 RPM. 50 RPM хорошо для сервы, но хотел ниже для большего момента — сделал редуктор 3:1. Использовал chevron-шестерни (herringbone) — эффективны и легко печатаются.
Свобода полная: дизайн редуктора зависит от двигателя и желаемой выходной скорости.
Плата контроллера на задней стороне редуктора, идеально по центру выходного вала.
Если использовать вал двигателя напрямую как выход — 1:1 шестерни для корректного отслеживания позиции. Или ремёночная передача. Вариантов бесконечно.
Скачать 3D-модель и STL
Модель в браузере Onshape (нужен аккаунт, бесплатно для дома). Скачать 3D-модель (STEP) и STL для печати:
STEP файл: [ссылка]
STL файлы: [ссылка]
Сборка кастомной сервы
3D-детали готовы. Нужен набор: M3 болты, вставки, подшипники.
1. DC-двигатель на базовую пластину — болты M3×8.
2. Две шестерни: малая на вал двигателя, большая — выходная. Выходной вал — две части: с одной стороны вставки под шестерню, с другой — под насадки на выходе. Вставки и в малую шестерню (под прижимной винт на вал двигателя).
3. Chevron-шестерни нужно вставлять одновременно — по одной не зацепить. Малую прижимным винтом на вал. Подал 12V — редуктор работает.
4. Добираем боковину, подшипник выходного вала, верхнюю крышку. Вставки M3 на задней стенке, болты M3×20 — сборка твёрдая. Тест — отлично. Выходной вал вращается сзади — сюда вставляем магнит для AS5600.
5. Плата контроллера на редуктор — болты M2 с гайками. AS5600 теперь идеально по центру магнита — при вращении вала поле измеряется верно.
Важно: направление намагничивания постоянного магнита критично. В зависимости от аксиального/диаметрального намагничивания магнит ставим перпендикулярно или параллельно AS5600. Пришлось перевернуть магнит — было не то намагничивание.
6. Паяю два провода к двигателю, клеммник на плате. Питание — два провода на клеммник питания, другой конец — DC-разъём 5.5 мм для 12V БП. Готово.
Прошивка контроллера
Осталось оживить серву — прошить контроллер. Сначала нужен загрузчик (bootloader) в ATmega328p. Без него микроконтроллер не поймёт код.
Запись загрузчика
Нужен Arduino (у меня Nano). SPI: соединяем соответствующие SPI-ножки Arduino и нашей платы. В Arduino IDE открываем пример ArduinoISP, заливаем в Nano — теперь он программатор. Tools → Programmer: «Arduino as ISP» → Burn Bootloader. При записи светодиоды Nano мигают активно — успех.
Загрузка кода
Теперь прошиваем код через USB-UART модуль. На плате выведены пины для легкого подключения (по схеме). Открываем код кастомной сервы, заливаем. Предварительно ставим библиотеки AS5600 и PID из Library Manager Arduino IDE. Код запишется через USB-UART.
Готово. Подключаем потенциометр для теста. Примечание: аналоговый вход на пине «S» платы, а не «A» — при проектировке PCB перепутал эти пины на ATmega328. Выбираем аналоговый режим DIP-переключателем, включаем питание.
Работает: позиция сервы управляется потенциометром. DC-двигатель превращён в сервопривод.
Исходный код
Код кастомной сервы (библиотеки AS5600 — RobTillaart, PID — br3ttb):
/* 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, Ik1 = 0, 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°
int angleDifference = 0, angleValue = 0;
int leftLimit = 30, rightLimit = 4067;
int rangeAdjustment = 0;
float sensitivityAdjustment = 0, angle = 0;
int quadrantNumber = 2, previousQuadrantNumber = 3, numberOfTurns = 0;
float totalAngle = 0, 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();
// Непрерывный режим вращения
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 if (digitalRead(inputSwitch) == 1) { // RC
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++;
}
// RAW энкодер → угол, отслеживание квадрантов и полных оборотов
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;
}
totalAngle = (numberOfTurns * 360) + angle;
Input = totalAngle;
}
// Ограниченный режим вращения
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 if (digitalRead(inputSwitch) == 1) { // RC
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);
}
// Регулировка чувствительности (P-член)
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 {
pwmValue = 0;
digitalWrite(motor_IN1, LOW); digitalWrite(motor_IN2, LOW);
}
// Serial.print(Setpoint); Serial.print("\t"); Serial.println(totalAngle);
}
Обзор кода
Цикл: чтение энкодера — текущая позиция.
encoderValue = as5600.readAngle();
Режим непрерывного вращения: принимаем числа из Serial Monitor как Setpoint (желаемый угол).
while (Serial.available() > 0) { ... Setpoint = inString.toInt(); }
Потенциометр: корректируем Setpoint в зависимости от отклонения от центра.
if (inputValue < 400) Setpoint -= 0.3; ... if (inputValue > 800) Setpoint += 0.3;
RC-приёмник — аналогичная коррекция.
RAW энкодер → угол (×0.087890625), определение квадранта, подсчёт полных оборотов (numberOfTurns) — totalAngle — Input для ПИД.
if (quadrantNumber != previousQuadrantNumber) { ... }
totalAngle = (numberOfTurns * 360) + angle;
Input = totalAngle;
Ограниченный режим: читаем подстроечник диапазона (A1) → leftLimit/rightLimit. Аналоговый вход (A0) или RC → Setpoint в диапазоне -255..255. Кнопка centerSet — запоминаем текущий угол как новый центр, вычисляем разницу angleDifference. Корректируем угол относительно нового центра с учётом перехода через 0/4096. Input = map(скорректированный_угол, leftLimit, rightLimit, -255, 255).
Чувствительность: Pk1 = analogRead(A2) * 0.002; myPID.SetTunings(...). ПИД Compute() → Output → PWM на двигатель (пороговые поправки для малых значений).
Pk1 = analogRead(A2) * 0.002; myPID.SetTunings(Pk1, Ik1, Dk1);
myPID.Compute();
Код не идеально оптимизирован, есть куда улучшать. При повторении будьте готовы к отладке: много нюансов, особенно пайка SMD. Не заработало с первой попытки: ошибки в PCB, перезаказ, несколько итераций до рабочего варианта.
Раздел статьи в доработке, проверьте позже…
Производственный процесс
- Создайте надежный детектор лжи на Arduino
- Виды углеродного волокна и технологии его производства
- Фрезеровка прямых и спиралевых пазов в точных деталях: особенности и лучшие практики
- Ускоренное прототипирование литья: Eagle Alloy доставляет быстрые качественные детали малого серийного производ…
- Освоение сварки титана: проверенные процессы и техники
- Многоступенчатые водяные насосы: принцип работы, преимущества и применение
- Собираем RC‑монстр‑трак Badland Brawler на Arduino: полный обзор
- Полное руководство по безопасной остановке доменной печи
- Тостер: от древнего огня к умному кухонному прибору
- Точный мониторинг температуры и влажности с SHT15 на Windows 10 IoT Core