Станки с ЧПУ - это компьютеризированные станки с числовым программным управлением, которые могут выполнять определенный набор операций в соответствии с заложенной в них программой. Подобные станки могут управляться с помощью компьютеров (наиболее сложные станки) или микроконтроллеров. Станки с ЧПУ обычно имеют в своем составе как шаговые, так и серводвигатели. К станкам ЧПУ относятся и плоттеры, которые могут рисовать какие-либо объекты по заданной программе.
В этом проекте мы рассмотрим создание самодельного (DIY) плоттера с ЧПУ на основе платы Arduino Uno. Из всех плоттеров, которые можно изготовить самому, этот является одним из самых простых. Наш самодельный плоттер сможет рисовать большинство основных форм, текстов и даже мультфильмов. Он работает примерно по такому же принципу, как и человеческая рука, но намного быстрее и точнее чем может рисовать человек. Подробно процесс функционирования этого плоттера вы можете посмотреть на видео, приведенном в конце статьи.
Также на нашем сайте вы можете посмотреть проект более "продвинутого" плоттера с ЧПУ на Arduino с автоматической сменой инструмента - более сложный проект чем представленный в данной статье, но зато с исключительно подробным описанием.
Работа плоттера с ЧПУ
Для работы плоттера с ЧПУ при построения графиков с ЧПУ требуется 3 оси (ось x, ось y и ось z). Оси x и y работают в унисон для создания 2D-изображения на обычной бумаге. Эти оси (x и y) расположены под углом 90 градусов друг к другу таким образом, что любая точка на плоской поверхности определяется заданным значением x и y. Ось z используется для подъема и опускания пера на плоскую бумагу.
В зависимости от того, какое изображение необходимо нарисовать, компьютер будет генерировать соответствующие координаты и отправлять их на микроконтроллер через USB-порт. Микроконтроллер интерпретирует эти координаты, а затем управляет положением двигателей для создания изображения. В качестве микроконтроллера в данном проекте мы использовали плату Arduino.
Необходимые компоненты
Аппаратные компоненты
- Плата Arduino Uno (купить на AliExpress).
- Шилд (плата расширения) драйвера двигателей L293D (купить на AliExpress).
- Старый HP/Epson принтер. Можно использовать старый компьютерный DVD привод.
- Мини сервомотор (купить на AliExpress).
- Алюминиевый лист (710mm x 710mm).
- Органическое стекло.
- Болты и гайки.
- Ручка.
Реклама: ООО "АЛИБАБА.КОМ (РУ)" ИНН: 7703380158
Примечание: механическая часть этого проекта может во многом отличаться от того, что вы видите на фотографиях в этой статье. Но какую бы “механику” вы не использовали, убедитесь что в ней есть сервомотор. Мы, к примеру, не смогли найти старый DVD привод, поэтому использовали части от старого принтера для конструирования нашего плоттера.
Инструменты
Отвертка
Дрель
Режущий инструмент (ножовка)
Клей
Стендовое устройство
Программное обеспечение
Arduino IDE version 1.6.6 или новее
Processing IDE version 3.1.1 или новее (последнюю версию можно скачать здесь)
Inkscape version 0.48.5 или новее. (скачать здесь)
Grbl controller (опционально)
Основание для ЧПУ плоттера
Основание нашего плоттера – это база, к которой прикрепляются все элементы конструкции чтобы устройство получилось жестким и в то же время портативным. Для основания нашего плоттера мы использовали алюминиевый лист поскольку он легкий, прочный, его легко сгинать и резать, а еще он не ржавеет (вдруг ваши внуки через много-много лет будут рисовать на этом плоттере).
Дизайн и размеры основания показаны на следующем рисунке (все размеры указаны в мм):
После проведения необходимых операций сгинания и обрезания у нас получилась следующая конструкция:
Сборка X, Y и Z осей
Для сборки x и y осей мы использовали две опоры (люльки) от принтера. Каждая из этих частей содержит шаговый двигатель, а механизм ременной передачи используется для перемещения картриджа в прямом и обратном направлении.
Для z-оси мы использовали мини сервомотор, который мы прикрепили к y-оси с помощью клея. Этот сервомотор будет использоваться для подъема и опускания ручки (карандаша). Также необходимо сконструировать хороший поддерживающий механизм, который бы позволял свободно поднимать и опускать ручку.
Платформа рисования для плоттера
В связи с огромными размерами нашей машины она может рисовать на листах бумаги формата A5. Поэтому мы вырежем платформу размера A5 (148mmx210mm) из оргстекла и приклеим ее к движущейся части x-оси нашего плоттера.
Схема плоттера
Вставьте шилд (плату расширения) драйвера двигателей L293D в плату Arduino. Эта плата расширения может одновременно управлять двумя шаговыми и двумя серводвигателями. Присоедините к ней два шаговых двигателя как показано на рисунке. Соединения “земли” необходимо оставить не соединенными поскольку у нас двигатели биполярного типа.
Также подключите мини сервомотор к разъему servo1. Подайте питание напряжением 7.5V - 9V на порт питания шилда драйвера мотора. Устройство готово к тестированию.
Написание программы управления плоттером для Arduino и тестирование проекта
Перед началом написания программы необходимо удостовериться в том, подключены ли шаговые двигатели и работают ли они корректно.
Поскольку в нашем проекте мы используем шилд драйвера двигателей L293D нам необходимо скачать библиотеку AFmotor Library. Затем добавьте ее в каталог библиотек Arduino IDE. Переименуйте его в AFMotor. Если у вас открыта Arduino IDE, то закройте ее и снова откройте (то есть перезапустите), кликните на пункт меню file -> examples -> Adafruit Motor Shield Library -> stepper. Убедитесь в том, что вы выбрали правильный COM порт и плату Arduino и затем загрузите код этого примера в вашу плату Arduino. После этого на шаговом двигателе 1 вы должны наблюдать некоторые движения.
Для того чтобы протестировать работу шагового двигателя 2 измените порт двигателя с 2 на 1 в следующем фрагменте кода и снова загрузите код примера в плату Arduino.
1 2 3 4 |
#include <AFMotor.h> // Connect a stepper motor with 48 steps per revolution (7.5 degree) // to motor port #2 (M3 and M4) AF_Stepper motor(48, 2); |
Код программы для Arduino
Теперь, когда вы убедились в том, что шаговые двигатели функционируют, скопируйте код Arduino для нашего плоттера (приведен в конце статьи, также его можно скачать по этой ссылке) и загрузите его в плату Arduino.
G-код (G-Code) для плоттера с ЧПУ
G-код представляет собой язык, на которым мы говорим машине с числовым программным управлением (ЧПУ) что она должна делать. В основном он содержит координаты X, Y и Z.
Пример этого кода:
1 2 3 4 5 6 7 8 9 10 |
G17 G20 G90 G94 G54 G0 Z0.25X-0.5 Y0. Z0.1 G01 Z0. F5. G02 X0. Y0.5 I0.5 J0. F2.5 X0.5 Y0. I0. J-0.5 X0. Y-0.5 I-0.5 J0. X-0.5 Y0. I0. J0.5 G01 Z0.1 F5. G00 X0. Y0. Z0.25 |
Написание G-кода вручную даже для простых геометрических фигур достаточно утомительно, поэтому мы воспользуемся специальным программным обеспечением которое позволит нам генерировать G-код. В нашем проекте мы для этой цели использовали программное обеспечение "Inkscape", которое можно скачать по этой ссылке. Также вы можете скачивать уже готовые G-коды в интернете.
Программная среда Processing IDE для загрузки G-кода в Arduino
Программная среда с открытым исходным кодом Processing IDE поможет нам в загрузке G-кодов в плату Arduino. Для этого вам сначала необходимо скачать GCTRL.PDE file. После скачивания откройте этот файл в Processing IDE.
После этого нажмите “run” (запуск программы). На экране появится окно со всеми необходимыми инструкциями. Нажмите “p” на клавиатуре. Система попросит вас выбрать порт. Выберите порт, к которому подключена ваша плата Arduino. В нашем случае это порт 6.
Теперь нажмите “g” на клавиатуре и выберите папку на компьютере, где у вас хранится ваш G-код. Выберите необходимый файл с G-кодом и нажмите enter. Если все соединения в схеме у вас сделаны правильно, то вы заметите как устройство (плоттер) начнет рисовать на бумаге.
Если вы хотите остановить процесс рисования, то просто нажмите “x” и плоттер остановит свою работу.
Как сгенерировать свой собственный G-код
В этом разделе статьи мы рассмотрим как с помощью программного обеспечения Inkscape сгенерировать G-код для надписи HELLO WORLD.
Примечание: Inkscape не умеет сохранять G-коды. Поэтому дополнительно установите вот этот MakerBot Unicorn plugin который позволяет экспортировать изображения в G-коды. Но новые версии Inkscape, возможно, уже умеют сохранять G-коды. Оригинал этой статьи был написан в 2017 году, возможно, с тех пор уже что то изменилось.
Если установка прошла успешно, откройте File menu в Inkscape кликните на "Document Properties" (свойства документа). Сначала измените размеры с px на миллиметры (mm). Также уменьшите ширину и высоту до 90 мм. Теперь закройте это окно. После этого в зоне рисования появится квадрат – именно в нем мы и будем писать наш текст.
Теперь слева в панели инструментов кликните на “create and edit text object tab”. Напишите текст "HELLO WORLD" и установите его необходимую позицию с помощью инструмента, показанного на следующем рисунке.
Кликните text и выберите необходимый вам шрифт. Кликните apply (применить) и закройте.
Теперь кликните на "path" и выберите "object to path". Теперь ваш текст готов к сохранению в виде G-кода. Кликните на file -> save и напишите имя файла "hello world".
Измените тип файла на "MakerBot Unicon G-Code" как показано на следующем рисунке (эта возможность будет вам доступна если вы успешно установили плагин MakerBot Unicorn). Теперь нажмите на "save" и кликните на "ok" в открывшемся окне.
Сохраненный G-код вы можете использовать для рисования на плоттере с помощью выше описанных операций.
Контроллер GRBL
После того как вы сгенерировали G-код с помощью Inkscape может возникнуть необходимость в проверке того, укладываются ли он в заданные ограничения (по возможности рисования).
Ограничения по рисованию определяются в следующих строчках кода нашей программы для Arduino:
В следующем окне GRBL контроллера можно проверить не выходит ли изображение на сгенерированном нами G-коде за пределы рисования, указанные в программе для Arduino. Если какая то часть изображения будет выходить за эти ограничения, то она не будет нарисована.
В нашем примере значения x и y изменяются в диапазоне от 0 до 40 мм. Но поскольку мы сконструировали плоттер с большей зоной рисования, то мы изменили максимальную границу с 40 до 60 мм.
Поэтому после того как вы нарисовали G-код в Inkscape желательно перед загрузкой его в плату Arduino проверять его с помощью программы GRBL не выходит ли он за пределы области рисования. Если выходит, то просто измените его размеры в Inkscape.
Исходный код программы
Таким образом, в этой статье мы рассмотрели один из самых дешевых способов создания собственного плоттера с ЧПУ. Далее представлен полный код программы для Arduino для этого проекта. Я специально оставил некоторые комментарии в нем без перевода (так часто более понятно некоторым разработчикам), но если необходимо я могу их для вас перевести если у вас возникнут затруднения.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 |
/* Send GCODE to this Sketch using gctrl.pde https://github.com/damellis/gctrl Convert SVG to GCODE with MakerBot Unicorn plugin for Inkscape available here https://github.com/martymcguire/inkscape-unicorn Arduino code for this Mini CNC Plotter based on: https://github.com/adidax/mini_cnc_plotter_firmware */ #include <Servo.h> #include <AFMotor.h> #define LINE_BUFFER_LENGTH 512 char STEP = MICROSTEP ; // позиции сервомотора для Up и Down (нижнего и верхнего положения ручки) const int penZUp = 115; const int penZDown = 83; // Servo on PWM pin 10 const int penServoPin =10 ; // Should be right for DVD steppers, but is not too important here const int stepsPerRevolution = 48; // создаем объект типа servo для управления сервомотором Servo penServo; // Initialize steppers for X- and Y-axis using this Arduino pins for the L293D H-bridge AF_Stepper myStepperY(stepsPerRevolution,1); AF_Stepper myStepperX(stepsPerRevolution,2); /* Structures, global variables */ struct point { float x; float y; float z; }; // текущая позиция плоттера struct point actuatorPos; // Drawing settings, should be OK float StepInc = 1; int StepDelay = 0; int LineDelay =0; int penDelay = 50; // Motor steps to go 1 millimeter. // Используйте тестовый sketch чтобы передвинуть двигатель на 100 шагов. Измерьте длину получившейся линии. // Измерьте длину шага в мм. Введите здесь float StepsPerMillimeterX = 100.0; float StepsPerMillimeterY = 100.0; // Drawing robot limits, in mm // OK to start with. Could go up to 50 mm if calibrated well. float Xmin = 0; float Xmax = 40; float Ymin = 0; float Ymax = 40; float Zmin = 0; float Zmax = 1; float Xpos = Xmin; float Ypos = Ymin; float Zpos = Zmax; // уствновите в true чтобы задействовать функции отладки программы boolean verbose = false; // Needs to interpret // G1 for moving // G4 P300 (wait 150ms) // M300 S30 (pen down) // M300 S50 (pen up) // Discard anything with a ( // Discard any other command! /********************** * void setup() - Initialisations ***********************/ void setup() { // Setup Serial.begin( 9600 ); penServo.attach(penServoPin); penServo.write(penZUp); delay(100); // уменьшите скорость если необходимо myStepperX.setSpeed(600); myStepperY.setSpeed(600); // Set & move to initial default position // TBD // Notifications!!! Serial.println("Mini CNC Plotter alive and kicking!"); Serial.print("X range is from "); Serial.print(Xmin); Serial.print(" to "); Serial.print(Xmax); Serial.println(" mm."); Serial.print("Y range is from "); Serial.print(Ymin); Serial.print(" to "); Serial.print(Ymax); Serial.println(" mm."); } /********************** * void loop() - Main loop ***********************/ void loop() { delay(100); char line[ LINE_BUFFER_LENGTH ]; char c; int lineIndex; bool lineIsComment, lineSemiColon; lineIndex = 0; lineSemiColon = false; lineIsComment = false; while (1) { // Serial reception - Mostly from Grbl, added semicolon support while ( Serial.available()>0 ) { c = Serial.read(); if (( c == '\n') || (c == '\r') ) { // достигнут конец строки if ( lineIndex > 0 ) { // Line is complete. Then execute! line[ lineIndex ] = '\0'; // Заканчиваем строку if (verbose) { Serial.print( "Received : "); Serial.println( line ); } processIncomingLine( line, lineIndex ); lineIndex = 0; } else { // Empty or comment line. Skip block. } lineIsComment = false; lineSemiColon = false; Serial.println("ok"); } else { if ( (lineIsComment) || (lineSemiColon) ) { // Throw away all comment characters if ( c == ')' ) lineIsComment = false; // End of comment. Resume line. } else { if ( c <= ' ' ) { // Throw away whitepace and control characters } else if ( c == '/' ) { // Block delete not supported. Ignore character. } else if ( c == '(' ) { // Enable comments flag and ignore all characters until ')' or EOL. lineIsComment = true; } else if ( c == ';' ) { lineSemiColon = true; } else if ( lineIndex >= LINE_BUFFER_LENGTH-1 ) { Serial.println( "ERROR - lineBuffer overflow" ); lineIsComment = false; lineSemiColon = false; } else if ( c >= 'a' && c <= 'z' ) { // Upcase lowercase line[ lineIndex++ ] = c-'a'+'A'; } else { line[ lineIndex++ ] = c; } } } } } } void processIncomingLine( char* line, int charNB ) { int currentIndex = 0; char buffer[ 64 ]; // Hope that 64 is enough for 1 parameter struct point newPos; newPos.x = 0.0; newPos.y = 0.0; // Needs to interpret // G1 for moving // G4 P300 (wait 150ms) // G1 X60 Y30 // G1 X30 Y50 // M300 S30 (pen down) // M300 S50 (pen up) // Discard anything with a ( // Discard any other command! while( currentIndex < charNB ) { switch ( line[ currentIndex++ ] ) { // Select command, if any case 'U': penUp(); break; case 'D': penDown(); break; case 'G': buffer[0] = line[ currentIndex++ ]; // /!\ Dirty - Only works with 2 digit commands // buffer[1] = line[ currentIndex++ ]; // buffer[2] = '\0'; buffer[1] = '\0'; switch ( atoi( buffer ) ){ // Select G command case 0: // G00 & G01 - Movement or fast movement. Same here case 1: // /!\ Dirty - Suppose that X is before Y char* indexX = strchr( line+currentIndex, 'X' ); // Get X/Y position in the string (if any) char* indexY = strchr( line+currentIndex, 'Y' ); if ( indexY <= 0 ) { newPos.x = atof( indexX + 1); newPos.y = actuatorPos.y; } else if ( indexX <= 0 ) { newPos.y = atof( indexY + 1); newPos.x = actuatorPos.x; } else { newPos.y = atof( indexY + 1); indexY = '\0'; newPos.x = atof( indexX + 1); } drawLine(newPos.x, newPos.y ); // Serial.println("ok"); actuatorPos.x = newPos.x; actuatorPos.y = newPos.y; break; } break; case 'M': buffer[0] = line[ currentIndex++ ]; // /!\ Dirty - Only works with 3 digit commands buffer[1] = line[ currentIndex++ ]; buffer[2] = line[ currentIndex++ ]; buffer[3] = '\0'; switch ( atoi( buffer ) ){ case 300: { char* indexS = strchr( line+currentIndex, 'S' ); float Spos = atof( indexS + 1); // Serial.println("ok"); if (Spos == 30) { penDown(); } if (Spos == 50) { penUp(); } break; } case 114: // M114 - Repport position Serial.print( "Absolute position : X = " ); Serial.print( actuatorPos.x ); Serial.print( " - Y = " ); Serial.println( actuatorPos.y ); break; default: Serial.print( "Command not recognized : M"); // команда не распознана Serial.println( buffer ); } } } } /********************************* * Draw a line from (x0;y0) to (x1;y1). * int (x1;y1) : Starting coordinates * int (x2;y2) : Ending coordinates **********************************/ void drawLine(float x1, float y1) { if (verbose) { Serial.print("fx1, fy1: "); Serial.print(x1); Serial.print(","); Serial.print(y1); Serial.println(""); } // Bring instructions within limits if (x1 >= Xmax) { x1 = Xmax; } if (x1 <= Xmin) { x1 = Xmin; } if (y1 >= Ymax) { y1 = Ymax; } if (y1 <= Ymin) { y1 = Ymin; } if (verbose) { Serial.print("Xpos, Ypos: "); Serial.print(Xpos); Serial.print(","); Serial.print(Ypos); Serial.println(""); } if (verbose) { Serial.print("x1, y1: "); Serial.print(x1); Serial.print(","); Serial.print(y1); Serial.println(""); } // преобразуем координаты в количество шагов x1 = (int)(x1*StepsPerMillimeterX); y1 = (int)(y1*StepsPerMillimeterY); float x0 = Xpos; float y0 = Ypos; // находим изменения координат long dx = abs(x1-x0); long dy = abs(y1-y0); int sx = x0<x1 ? StepInc : -StepInc; int sy = y0<y1 ? StepInc : -StepInc; long i; long over = 0; if (dx > dy) { for (i=0; i<dx; ++i) { myStepperX.onestep(sx,STEP); over+=dy; if (over>=dx) { over-=dx; myStepperY.onestep(sy,STEP); } delay(StepDelay); } } else { for (i=0; i<dy; ++i) { myStepperY.onestep(sy,STEP); over+=dx; if (over>=dy) { over-=dy; myStepperX.onestep(sx,STEP); } delay(StepDelay); } } if (verbose) { Serial.print("dx, dy:"); Serial.print(dx); Serial.print(","); Serial.print(dy); Serial.println(""); } if (verbose) { Serial.print("Going to ("); Serial.print(x0); Serial.print(","); Serial.print(y0); Serial.println(")"); } // Delay before any next lines are submitted delay(LineDelay); // обновляем позиции Xpos = x1; Ypos = y1; } // поднимаем ручку void penUp() { penServo.write(penZUp); delay(penDelay); Zpos=Zmax; digitalWrite(15, LOW); digitalWrite(16, HIGH); if (verbose) { Serial.println("Pen up!"); // ручка поднята } } // опускаем ручку void penDown() { penServo.write(penZDown); delay(penDelay); Zpos=Zmin; digitalWrite(15, HIGH); digitalWrite(16, LOW); if (verbose) { Serial.println("Pen down."); // ручка опущена } } |
Код прошивки УЖАСЕН!
Половина целочисленных переменных объявлена как вещественные. Про парсинг я вообще молчу...
Почему по команде M300 делаются действия с ручкой - вообще не понятно.
M300: Play beep sound
Вы про то что координаты объявлены с помощью вещественных переменных? Так координаты могут в определенных ситуациях принимать вещественные значения.
А про команду M300 там же в комментариях к коду указано что это будут команды действия с ручкой