Плоттер из набора я однажды собирал по картинке: рама, ремни GT2, шкивы, домик на колесах — и стоп на этапе, где цена железа перевалила за всё, что я собирался на нём нарисовать. Тогда родилась макетка из подручного: две 28BYJ-48 с ULN2003, два отрезка нити, фломастер на поперечине и лист картона. Задача — не чертёжный станок, а координатная машина, на которой видно, как два мотора превращаются в точку с адресом (x, y).
Первый «круг» на ней вышел квадратом. Скетч ездил по очереди: сначала ось X проезжала свою половину приращения, потом ось Y — и перо честно чертило две стороны квадрата, потом две другие. Круг не складывается из «по очереди»: обе оси обязаны ехать одновременно, и доля хода для каждой в каждый момент своя. Вот здесь и появляется тригонометрия, которую все помнят со школы и никто не ждёт в скетче.
Разберём машину по слоям: координаты из массива вместо G-кода, приращения через синус и косинус, механика на нитках и честные ограничения — люфт, растяжение нити и потолок скорости. Проект маленький, но в нём встречается всё, что мы проходили в разделе: счёт шагов, режимы обмоток и цена блокирующего step().
Координаты вместо G-кода
Настоящие плоттеры едят G-код — текстовый язык станков, где каждая строка значит «иди в точку». Для макетки он лишний: перечень точек можно посчитать прямо в скетче. Круг радиусом R — это N точек, где координаты каждой равны x = R × cos(a) и y = R × sin(a), а угол a идёт от нуля до полного оборота с равным шагом. Двенадцать точек дают огранку, двадцать четыре — почти гладкую линию; всё это складывается в два массива до старта, и программа просто читает их подряд.
Перо между точками едет по приращениям: разность соседних x и соседних y. Дальше — пересчёт клеток в шаги, та же формула, что в уроке про передаточное число, только делитель теперь не градусы, а клетки: сколько шагов вала наматывает одну клетку. У моей машины клетка — это 8 шагов: вал 28BYJ-48 диаметром 3 мм наматывает за оборот около 9,4 мм нити, 2048 шагов на оборот — выходит примерно 0,005 мм нити на шаг, и восемь шагов дают полмиллиметра клетки. Число выбирают под свою намотку — здесь работает только ваша линейка.
Синус и косинус в скетче считаются в float: 12-24 точки на весь круг — копеечная нагрузка для платы. Массивы координат — тот же приём, что массивы-паттерны из основ: таблица, из которой программа читает заранее подготовленные значения. Хотите звезду вместо круга — пересчитайте таблицу, скетч не заметит подмены.
#include <Stepper.h> const int KLETKA = 8; // шагов вала на одну клетку сетки Stepper motorX(2048, 4, 6, 5, 7); // ось X через свой ULN2003Stepper motorY(2048, 8, 10, 9, 11); // ось Y через свой ULN2003 // круг радиусом 6 клеток: 12 опорных точек с шагом 30 градусовint xs[12] = {6, 5, 3, 0, -3, -5, -6, -5, -3, 0, 3, 5};int ys[12] = {0, 3, 5, 6, 5, 3, 0, -3, -5, -6, -5, -3}; void reis(int dx, int dy) { // ход между точками, порциями int ostX = dx * KLETKA; int ostY = dy * KLETKA; while (ostX != 0 || ostY != 0) { if (ostX >= 24) { motorX.step(24); ostX -= 24; } if (ostX <= -24) { motorX.step(-24); ostX += 24; } if (ostY >= 24) { motorY.step(24); ostY -= 24; } if (ostY <= -24) { motorY.step(-24); ostY += 24; } if (ostX > -24 && ostX < 24 && ostY > -24 && ostY < 24) { if (ostX != 0) { motorX.step(ostX); ostX = 0; } if (ostY != 0) { motorY.step(ostY); ostY = 0; } } }} void setup() { motorX.setSpeed(12); motorY.setSpeed(12); Serial.begin(9600); Serial.println("Krug: 12 tochek, porcii po 24 shaga");} void loop() { int px = xs[0]; // старт в первой точке int py = ys[0]; for (int i = 1; i <= 12; i++) { int j = i % 12; // замыкаем круг Serial.print("tochka x="); Serial.print(xs[j]); Serial.print(" y="); Serial.println(ys[j]); reis(xs[j] - px, ys[j] - py); px = xs[j]; py = ys[j]; } Serial.println("Krug zameknut"); delay(4000);}Сердце скетча — reis(): она берёт приращение в клетках, переводит в шаги и едет, дробя ход на порции по 24 шага с чередованием осей. Без дробления получится то самое, на чём я споткнулся в первый вечер: step() блокирует loop(), и пока ось X добирает свои 48 шагов, ось Y стоит — дуга рассыпается в лесенку из горизонталей и вертикалей. Порции мелкие, оси чередуются — и на глаз движение двух моторов сливается в одно диагональное.
Обратите внимание на массивы xs и ys: значения уже округлены до клеток, и программа нигде не вызывает sin() и cos() — тригонометрия сделана один раз за столом. Это честный рабочий приём: посчитать траекторию заранее и хранить числа. Если захотите считать на плате — умножайте радиус на cos в float и не забудьте про перевод радиан, но для макетки таблица прозрачнее: любая точка видна глазами и правится пальцем.
| Узел машины | Решение на макетке | Цена решения |
|---|---|---|
| Ось X и ось Y | 2 × 28BYJ-48 со своими ULN2003 | Дёшево и точно, но потолок около 15 RPM |
| Передача хода | Нить в виток на валу 3 мм + груз для натяга | Бесплатно, но тянется и проскальзывает без натяга |
| Позиция | Счёт шагов от старта | Без энкодера: срыв или проскальзывание копятся до репера |
| Траектория | Массив точек из синуса и косинуса | Без G-кода; правится руками, считается на плате при желании |
Механика: нить, ролик и честные ограничения
Передача хода на макетке — нить. Несколько витков на шейку вала, конец — к поперечине, на противоположной стороне — лёгкий груз для постоянного натяга: нить обязана выбирать слабину всегда в одну сторону, иначе люфт превратится из долей миллиметра в сантиметры. Вал 28BYJ-48 гладкий, нить на нём держится витками и трением; я капал каплю термоклея на последние витки, чтобы нить не ползла. Скорость машины считается честно: 9,4 мм окружности вала при 12 RPM — это около 113 мм/мин, чуть меньше двух миллиметров в секунду. Медленно, зато предсказуемо: чем быстрее, тем вероятнее срыв шагов, который мы вымеряли в уроке про скорость.
Теперь про предел мечтаний. Теоретическая точность шага — 0,176° на валу, на нити диаметра намотки это сотые доли миллиметра. Реальность грубее на два порядка: люфт пластиковых шестерён — доли градуса на валу, растяжение и микропроскальзывание нити, прогиб поперечины. Итоговая точность макетки — десятые доли миллиметра, и виновата не арифметика, а механика. Если хочется гладкости хода, а не точности, — переведите моторы в полушаговый режим: рывки дробятся вдвое, лесенка между точками мельчит.
Честное резюме: эта машина — учебная, и её сила не в чертежах, а в прозрачности. Каждую ось вы можете покрутить пальцем и увидеть, сколько шагов съедает люфт; каждую цифру в скетче — пересчитать линейкой. Когда захочется больше — путь известен: NEMA 17 с ремнём GT2 и драйвером с микрошагом, репер-концевик для обнуления счёта, энкодер на валу для проверки. Но начинать я советую с ниток: на них видно всё, что потом спрячет приличная механика.
Рисуем круг в симуляторе
Симулятор рисует ту же машину Serial-ом: сетка 15 на 15 клеток, круг радиусом 6 из 24 точек, звёздочка — след пера. Таблица координат печатается перед рисунком — сверьте пару точек с бумажным расчётом: 30° при радиусе 6 — это x около 5, y около 3. Циферблат шаговика в симуляторе один: движок держит единственный мотор, поэтому ось X в нём представлена настоящим циферблатом, а ось Y живёт в распечатке сетки — на реальной макетке их два, и скетч выше гонит оба.
Посмотрите на связку: проезд оси X на 96 шагов — это двенадцать клеток по восемь шагов, и циферблат это показывает. Между проездами Serial перерисовывает сетку: след пера стоит на месте, потому что позиция хранится счётом, а не датчиком. Поменяйте радиус в массиве на 4 и убедитесь, что скетч не заметил подмены — таблица координат и рисунок пересчитались сами.
- Круг — массив координат: x = R × cos(a), y = R × sin(a); G-код не нужен, таблица считается заранее или на плате
- Приращение в клетках переводится в шаги: клетка = 8 шагов при нити на валу 3 мм — число меряется линейкой под свою намотку
- step() блокирует loop(): ход делится на порции 24-50 шагов с чередованием осей, иначе дуга рассыпается в лесенку
- Скорость машины: 9,4 мм окружности вала × 12 RPM ≈ 113 мм/мин — медленно, зато без срыва шагов
- Точность макетки съедена люфтом редуктора и нитью, а не арифметикой: счёт шагов честен до срыва и проскальзывания
- 1. Первый вариант плоттера ездил по очереди: сначала весь ход по X, потом весь ход по Y. Что начертило перо?
- 2. Сколько чисел лежит в массиве координат круга из 24 точек?
- 3. Вал 28BYJ-48 наматывает нить на шейку 3 мм, мотор крутится 12 RPM. Какова скорость головки?
- 4. Что съедает точность макетного плоттера сильнее всего?
- 5. Зачем ход между точками делят на порции 24-50 шагов и чередуют оси?
Раздел замкнулся: от первой четверти оборота в уроке про подключение 28BYJ-48 до машины с двумя осями. Между ними были фазы, редуктор, режимы обмоток и выбор против серво — и всё это сложилось в проект, который рисует круг из двадцати четырёх чисел. Возьмите два мотора, нить и лист картона: круг, нарисованный вашей координатной машиной, приносит больше понимания, чем десяток чужих видео, а следующий шаг — ремень, NEMA и микрошаг — уже подождёт.
Частые вопросы по теме урока
- Первый вариант плоттера ездил по очереди: сначала весь ход по X, потом весь ход по Y. Что начертило перо?
- Ходы по одной оси в каждый момент дают только горизонтальные и вертикальные отрезки. Дуга требует одновременного движения — порций с чередованием осей. Квадрат со сторонами по осям
- Сколько чисел лежит в массиве координат круга из 24 точек?
- Каждая точка несёт две координаты: x из косинуса и y из синуса. Двадцать четыре точки — это два массива по 24 числа или один на 48. 48 — x и y каждой точки
- Вал 28BYJ-48 наматывает нить на шейку 3 мм, мотор крутится 12 RPM. Какова скорость головки?
- Окружность вала 3 мм — это около 9,4 мм нити за оборот; двенадцать оборотов в минуту дают примерно 113 мм/мин. Около 113 мм/мин — меньше 2 мм/с
- Что съедает точность макетного плоттера сильнее всего?
- Шаг вала даёт сотые доли миллиметра, а люфт пластиковых шестерён и микрорастяжение нити — десятые. Механика грубее арифметики на два порядка. Люфт редуктора и упругость нити
- Зачем ход между точками делят на порции 24-50 шагов и чередуют оси?
- Библиотека крутит один мотор за раз: пока выполняется step(), второй стоит. Мелкие чередующиеся порции создают иллюзию одновременного хода по двум осям. step() блокирует loop(): пока едет X, стоит Y