Промышленное производство
Промышленный Интернет вещей | Промышленные материалы | Техническое обслуживание и ремонт оборудования | Промышленное программирование |
home  MfgRobots >> Промышленное производство >  >> Manufacturing Technology >> Производственный процесс

Превращение любого DC-двигателя в кастомный 360° сервопривод — пошаговое руководство

В этом уроке научимся превращать любой DC-двигатель в автономный кастомный сервопривод с богатым функционалом. В отличие от обычных серв с ограниченным ходом 180° или 270°, этот имеет неограниченный диапазон 360° (непрерывное вращение) и позволяет настраивать диапазон поворота под любые нужды.

Кроме того, можно смещать центральную точку сервы — одновременно настраивать и центр, и диапазон.

Смотрите видео или читайте текст ниже.

Ещё одна фича — регулировка чувствительности (скорости отклика). Входов три режима:

Можно комбинировать: вводить углы в 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, где всё помещается для автономной работы сервы.

Схема кастомного сервопривода

Полная схема ниже. Компоненты:

Партнёрские ссылки.

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, перезаказ, несколько итераций до рабочего варианта.

Раздел статьи в доработке, проверьте позже…

Производственный процесс

  1. Создайте надежный детектор лжи на Arduino
  2. Виды углеродного волокна и технологии его производства
  3. Фрезеровка прямых и спиралевых пазов в точных деталях: особенности и лучшие практики
  4. Ускоренное прототипирование литья: Eagle Alloy доставляет быстрые качественные детали малого серийного производ…
  5. Освоение сварки титана: проверенные процессы и техники
  6. Многоступенчатые водяные насосы: принцип работы, преимущества и применение
  7. Собираем RC‑монстр‑трак Badland Brawler на Arduino: полный обзор
  8. Полное руководство по безопасной остановке доменной печи
  9. Тостер: от древнего огня к умному кухонному прибору
  10. Точный мониторинг температуры и влажности с SHT15 на Windows 10 IoT Core