illumium.org

Главная › Блоги › Блог kayo

Сверлим печатные платы на 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 и попутно исправить все выше перечисленные недостатки.

Держатель платы итерация вторая

Во-первых, детали я решил сделать цельные, наделав в них дырок, чтобы было больше ребер жесткости для прочности. Во-вторых шпильки я решил сделать вращающимися, чтобы плату можно было подтягивать барашками, расположенными снаружи, а не внутри конструкции. В-третьих, я вынес оси наружу, чтобы расширить область, куда можно крепить плату. Вот что у меня получилось:

Держатель платы. Итерация 2. Верх.

Держатель платы. Итерация 2. Низ.

Конструкция стала очень простой, но выявился один недостаток: при высвобождении платы средний подвижный элемент не выталкивается, потому что осям некуда упираться, приходится толкать ручки.

Программная часть

KiCAD умеет генерировать файлы сверловки в формате Excellon, который представляет из себя команды выбора инструмента и набор координат отверстий. А принтерный софт требует полноценного GCode для работы. Проще было написать конвертер Excellon -> RepRap GCode, чем учить прошивку понимать и выполнять Экселон.

Программа на голом Haskell, получилась достаточно простой:

  • Парсер/Форматтер GCode
  • Сам преобразователь кода
  • Система опций и настроек

Я только начинал изучать и применять этот замечательный язык, когда писал код, всё делалось с целью получить опыт и поэтому другие библиотеки не применялись, кроме тех, что идут с компилятором. Хаскельщики скажут, что мне следовало бы применить монаду State для поддержки состояния виртуального станка при конвертации кода. А поскольку работа идёт в монаде IO то удобнее было бы применить монадный трансформер StateT. Но я по сути эмулировал это, использовав чистые функции, принимающие и возвращающие состояние.

Алгоритм работы весьма прост:

  • Перед началом работы сбрасываем координаты в 0, поскольку считаем, что оператор установил инструмент в угол платы
  • Команду выбора инструмента превращаем в команду паузы и ожидания действия со стороны оператора, чтобы тот мог неспешно установить нужный инструмент
  • Каждый набор координат превращаем в последовательность такого вида:
  1. Перемещаемся в позицию сверления
  2. Медленно опускаем инструмент на заданную глубину для сверления
  3. Быстро поднимаемся обратно

А вот так это выглядит на хорошем годном небыдлокодерском языке программирования:

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

  • Разработка устройств
  • excellon
  • gerber
  • Haskell
  • KiCAD
  • OpenSCAD
  • pcb
  • prusa mendel
  • RepRap
  • Бортовой журнал Иллюмиума

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

Содержимое этого поля является приватным и не будет отображаться публично.
  • Доступные HTML теги: <a> <em> <strong> <cite> <code> <ul> <ol> <li> <dl> <dt> <dd>
  • Syntax highlight code surrounded by the {syntaxhighlighter SPEC}...{/syntaxhighlighter} tags, where SPEC is a Syntaxhighlighter options string or "class="OPTIONS" title="the title".

Подробнее о форматировании

CAPTCHA
Этот вопрос задается для того, чтобы выяснить, являетесь ли Вы человеком или представляете из себя автоматическую спам-рассылку.
 __  __   __     _____      _          _         
\ \/ / / /_ | ____| / \ | | __ _
\ / | '_ \ | _| / _ \ _ | | / _` |
/ \ | (_) | | |___ / ___ \ | |_| | | (_| |
/_/\_\ \___/ |_____| /_/ \_\ \___/ \__,_|
Введите код, изображенный в стиле ASCII-арт.
RSS-материал

Навигация

  • Подшивки
  • Фотоальбомы

«Иллюмиум» на якоре.

Работает на Drupal, система с открытым исходным кодом.

(L) 2010, Illumium.Org. All rights reversed ^_~