Сверлим печатные платы на RepRap Prusa Mendel
kayo — Пнд, 25/08/2014 - 22:31
Собрал я как-то себе 3D-принтер Prusa Mendel из проекта RepRap. Надо сказать, полезная в хозяйстве вещь, напрограммил нужные детальки в OpenSCAD, прогнал в слайсере, включил, напечатал. Платформа Mendel разработана весьма добротно за свою цену и может делать намного больше, нужно только поставить соответствующую оснастку. В прототипировании всякой электроники самый неприятный момент связан с производством печатных плат. Есть, конечно, спецы сборки сложных прототипов на макетках, но я не один из них. Макетирование слишком чревато: то проводки перепутаю, то и вовсе закорочу цепи, а замена MCU на отладочной плате — то ещё удовольствие, особенно, когда это уже третья замена, и контактные площадки с шагом 0.3 мм начинают отваливаться. С другой стороны в изготовлении печатных плат кустарным способом много неприятных моментов, и один из них — сверление. Ровно насверлить ~100 переходных отверстий сверлом 0.3 мм — убиться можно. Скажу по секрету, я это проделывал, куда деваться, когда тебе нужен прототип вот прямо здесь и сейчас, но я не горжусь этим, а скорее наоборот. Как-то подумалось: что если поставить на место экструдера BLDC двигатель с цанговым патроном для фрез, а на место подогревающего стола — держатель для плат.
Железная часть
Пошел в тот самый магазин и прикупил у наших китайских друзей подходящий двигатель с самым ходовым цанговым патроном ER11. А сам пока посылка едет сел за проектирование оснастки в OpenSCAD.
Адаптер для фрезерующей головы
Адаптер для двигателя нарисовался практически сразу, однако, как позже выяснилось, я допустил небольшую ошибку, детали мешал держатель ремня. Но уже успел напечатать, править пришлось IRL бормашинкой. Вот вот как выглядит адаптер:
Держатель для платы
Голову пришлось поломать над держателем для печатных плат. Конструкция должна позволять надежно закреплять платы разных размеров, да так, чтобы ничего не болталось при перемещении стола и сверлении. Решил сделать вот такую рамочную конструкцию:
В отверстия пропускаются шпильки M8, которые жестко фиксируются гайками на крайних элементах конструкции, которые в свою очередь крепятся к столу винтами M3. Плата устанавливается в рамку, образуемую уголками. Средние четыре элемента подвижные и подтягиваются гайками с ушками, которые в простонародье называют барашками. Я поленился здесь накидывать шпильки с гайками и болтами, ибо конструкция итак простая, поэтому на картинке только печатные детали.
Эта версия мне не понравилась уже на этапе печати, затем опасения только подтвердились: конструкция получилась не очень прочная и не очень удобная для постоянного использования. Барашки приходилось крутить с внутренней стороны, выгнав предварительно стол на сколько это возможно. Поэтому я решил уменьшить размеры конструкции под шпильки M6 и попутно исправить все выше перечисленные недостатки.
Держатель платы итерация вторая
Во-первых, детали я решил сделать цельные, наделав в них дырок, чтобы было больше ребер жесткости для прочности. Во-вторых шпильки я решил сделать вращающимися, чтобы плату можно было подтягивать барашками, расположенными снаружи, а не внутри конструкции. В-третьих, я вынес оси наружу, чтобы расширить область, куда можно крепить плату. Вот что у меня получилось:
Конструкция стала очень простой, но выявился один недостаток: при высвобождении платы средний подвижный элемент не выталкивается, потому что осям некуда упираться, приходится толкать ручки.
Программная часть
KiCAD умеет генерировать файлы сверловки в формате Excellon, который представляет из себя команды выбора инструмента и набор координат отверстий. А принтерный софт требует полноценного GCode для работы. Проще было написать конвертер Excellon -> RepRap GCode, чем учить прошивку понимать и выполнять Экселон.
Программа на голом Haskell, получилась достаточно простой:
- Парсер/Форматтер GCode
- Сам преобразователь кода
- Система опций и настроек
Я только начинал изучать и применять этот замечательный язык, когда писал код, всё делалось с целью получить опыт и поэтому другие библиотеки не применялись, кроме тех, что идут с компилятором. Хаскельщики скажут, что мне следовало бы применить монаду State для поддержки состояния виртуального станка при конвертации кода. А поскольку работа идёт в монаде IO то удобнее было бы применить монадный трансформер StateT. Но я по сути эмулировал это, использовав чистые функции, принимающие и возвращающие состояние.
Алгоритм работы весьма прост:
- Перед началом работы сбрасываем координаты в 0, поскольку считаем, что оператор установил инструмент в угол платы
- Команду выбора инструмента превращаем в команду паузы и ожидания действия со стороны оператора, чтобы тот мог неспешно установить нужный инструмент
- Каждый набор координат превращаем в последовательность такого вида:
- Перемещаемся в позицию сверления
- Медленно опускаем инструмент на заданную глубину для сверления
- Быстро поднимаемся обратно
А вот так это выглядит на хорошем годном небыдлокодерском языке программирования:
module Transform ( Params(Params) , needReset , addComment , drillSpeed , drillDepth , moveSpeed , coordsType , Coords(Absolute, Relative) , unitsType , Units(Millimeters, Inches) , defaults , process , valueFrom , valueTo ) where import System.IO(Handle, hGetLine, hPutStrLn, hIsEOF) import GCode data Coords = Absolute | Relative deriving (Show, Eq) data Units = Millimeters | Inches deriving (Eq) instance Show Units where show Millimeters = "mm" show Inches = "in" data Params = Params -- параметры преобразования и состояние виртуального станка { needReset :: Bool -- надо ли сбрасывать координаты перед началом , addComment :: Bool -- добавлять ли комментарии к командам , moveSpeed :: Value -- скорость перемещения в ед/мин , drillSpeed :: Value -- скорость сверления в ед/мин , drillDepth :: Value -- глубина сверления , coordsType :: Coords -- тип координат , unitsType :: Units -- единицы измерения } -- defaults :: Params defaults = -- значения параметров по-умолчанию Params { needReset = True , addComment = False , moveSpeed = Value 3000 0 -- units/min , drillSpeed = Value 6 0 -- units/min , drillDepth = Value 2 0 -- units , coordsType = Absolute , unitsType = Millimeters } -- главная функция преобразования process :: Params -> Handle -> Handle -> IO () process params input output = initialize' >> process' params >> finalize' where initialize' :: IO () initialize' = outputCommands $ initialize params finalize' :: IO () finalize' = outputCommands $ finalize params process' :: Params -> IO () process' params = do eof <- hIsEOF input if eof then return () else do line <- hGetLine input let command = parse line params' = prepare params command commands' = transform params' command outputCommands $ commands' process' params' outputCommands :: [Command] -> IO () outputCommands [] = return () outputCommands (cmd:rest) = do hPutStrLn output $ format cmd outputCommands rest initialize :: Params -> [Command] initialize p = resetCoords p finalize :: Params -> [Command] finalize _ = [] -- предпросмотр команды с изменением состояния -- (здесь нам помогает такая особенность языка, как сопоставление с образцом) prepare :: Params -> Command -> Params prepare p (Command (Entries (Entry 'G' (Value 90 0) : _)) _) = p { coordsType = Absolute } prepare p (Command (Entries (Entry 'G' (Value 91 0) : _)) _) = p { coordsType = Relative } prepare p (Command (Entries (Entry 'G' (Value 20 0) : _)) _) = p { unitsType = Inches } prepare p (Command (Entries (Entry 'G' (Value 21 0) : _)) _) = p { unitsType = Millimeters } prepare p (Command (Directive "METRIC" _) _) = p { unitsType = Millimeters } prepare p (Command (Directive "INCH" _) _) = p { unitsType = Inches } prepare p _ = p -- трансформация входной команды в список выходных -- (ещё более активно используем сопоставление с образцом для распознавания команд) transform :: Params -> Command -> [Command] transform p (Command (Entries (Entry 'T' t : Entry 'C' c : _)) _) = setupTool p t c transform p (Command (Entries (Entry 'T' t : _)) _) = changeTool p t transform p (Command (Entries (Entry 'X' x : Entry 'Y' y : _)) _) = drillHole p x y transform p (Command (Entries (Entry 'Y' y : Entry 'X' x : _)) _) = drillHole p x y transform p (Command (Entries (Entry 'G' (Value 90 0) : _)) _) = [ Command (Entries [ Entry 'G' $ Value 90 0 ]) $ comment p "Set absolute positioning" ] transform p (Command (Entries (Entry 'G' (Value 91 0) : _)) _) = [ Command (Entries [ Entry 'G' $ Value 90 0 ]) $ comment p "Set relative positioning" ] transform p (Command (Directive "METRIC" _) _) = [ Command (Entries [ Entry 'G' $ Value 21 0 ]) $ comment p "Set units to millimeters" ] transform p (Command (Directive "INCH" _) _) = [ Command (Entries [ Entry 'G' $ Value 20 0 ]) $ comment p "Set units to inches" ] transform _ _ = [] {- вспомогательные функции -} --comment :: Params -> String -> String comment p s | addComment p = s | otherwise = "" -- комментарий оператору об используемом инструменте (Tool #номер: диаметр) setupTool p t c = [ Command Empty ("Tool #" ++ formatValue t ++ ": " ++ formatValue c ++ " " ++ show (unitsType p)) ] -- приостановка для смены инструмента changeTool p (Value t _) | t /= 0 = [ Command (Entries [ Entry 'M' $ Value 300 0 , Entry 'S' $ Value 4400 0 , Entry 'P' $ Value 1600 0 ]) $ comment p "Beep to change tool" , Command (Entries [ Entry 'M' $ Value 226 0 ]) $ comment p "Pauses processing" ] | otherwise = [] -- каррированные синонимы часто используемых функций value0 = Value 0 0 entryG = Entry 'G' entryG1 = entryG $ Value 1 0 -- Controlled movement entryG92 = entryG $ Value 92 0 -- Set coords entryX = Entry 'X' entryY = Entry 'Y' entryZ = Entry 'Z' entryF = Entry 'F' -- Speed entryFMove = entryF . moveSpeed -- Move speed entryFDrill = entryF . drillSpeed -- Drill speed -- сброс координат resetCoords :: Params -> [Command] resetCoords p | needReset p = [ Command (Entries [ entryG92 , entryX $ value0 , entryY $ value0 , entryZ $ drillDepth p ]) $ comment p "Reset coords to initial point" ] | otherwise = [] -- последовательность команд для сверления отверстия -- (учитываем тип координат и скорости перемещения инструмента) drillHole p x y = [ Command (Entries [ entryG1 , entryX x , entryY y , entryFMove p ]) $ comment p "Moving to hole position" , Command (Entries [ entryG1 , entryZ (if coordsType p == Absolute then value0 else negative $ drillDepth p) , entryFDrill p ]) $ comment p "Drilling hole down" , Command (Entries [ entryG1 , entryZ $ drillDepth p , entryFMove p ]) $ comment p "Returning up" ] where negative (Value d p) = Value (-d) p
Как я и говорил, всё просто. Модуль разбора и форматирования GCode не стану приводить, скажу только, что он поддерживает как обычный GCode, так и Excellon расширения. Value представляет собой десятичное число с плавающей точкой (Decimal), которое состоит из собственно значения и положения точки. В общем, применяйте Haskell во имя добра.
Вызываем конвертер так:
$ gerber2reprap -h Usage: gerber2reprap [options] [excellon.drl] -h, -? --help This usage hint. -V --version Version information. -r --dont-reset Do not reset coords before processing. -c --add-comments Adds comments after each command. -d VALUE --drill-depth=VALUE Board depth in target units. (default: 2.0) -m VALUE --move-speed=VALUE Moving speed in units/second. (default: 50.0) -s VALUE --drill-speed=VALUE Drilling speed in units/second. (default: 0.3) -o FILE --output=FILE Output GCode file. $ gerber2reprap -o output.gcode input.drl
Ну и, разумеется, исходный файл можно подавать в стандартный ввод, а результат получать на стандартный вывод. Единицы измерения приходят из исходного файла сверловки, то есть никаких преобразований к числам не применяется.
Заключение
Результат, на удивление, получился лучше, чем я ожидал. Заранее прикупил набор фрез с наконечников из карбида вольфрама. При сверлении с небольшой скоростью отверстия с обоих сторон получаются гладкие, никаких ошметков фольги нигде не торчит, ничего заглаживать не надо. Однако, стружки выходит много, после часа сверления овер 100 отверстий плата вся покрыта стеклотекстолитовой пылью. Местами она свисает даже снизу через отверстия, иногда приходится продувать.
Обычно я сверлю до наложения маски дорожек и травления, так, что слои можно совместить по уже просверленным отверстиям. Как выяснилось, при медленном заходе в материал фреза может немного смещаться в сторону. Хоть это смещение не превышает 1/4 диаметра инструмента, но в будущем лучше добавить в конвертер опции начальной и конечной скоростей перемещения фрезы при сверлении. Так при более быстром перемещении не будет происходит увода фрезы в сторону на входе, а на выходе не возникнет рваных краёв при более медленном перемещении инструмента.
В настоящий момент проект живёт тут: BitBucket.Org
Видео процесса можно посмотреть здесь: Vimeo.Com

Отправить комментарий