А задумывались ли Вы, что можно сделать на ЭВМ «Берёста-4» с объемом памяти ПЗУ 256 байт? Многие из Вас скажут, что можно всего лишь поморгать светодиодом, а я скажу Вам, что можно сделать ЧПУ станок. Лично я давно хотел себе ЧПУ «рисовалку» вот её и задумал сделать.

Начнем с самого простого: подергаем шаговым двигателем. В роли шагового двигателя - шаговый двигатель 17HS08-1004S. Управление «шаговиком» доверим драйверу на микросхеме tb6600. Здесь все просто: выбрал направление кручения и подал импульс. Шаговый двигатель сделал один микрошаг. Главное - настроить драйвер по току да по количеству микрошагов в одном шаге. Настраивается это все выключателями на самом драйвере.

Ну, с этой задачей «одноплатник» справился. В принципе, я и не сомневался, потому что задача из разряда легких. А вот дальше начинается ужас- он же кошмар.) Так как памяти у «Берёсты» очень мало (256 байт), а поставленная задача не из разряда «Hello world», то нужно как-то выкручиваться. На «Берёсте» нет аппаратного UART. При реализации программного, возможно, не хватит памяти. Что же делать?

диаграмма UART
диаграмма UART

Открываю перед собой диаграмму UART и, гипнотизируя её взглядом, начинаю сворачивать извилину мозга в ленту Мебиуса: «Плюс. Все время плюс. Началась передача — это минус. Дальше пошли данные, бит четности и все сначала.  Данные… Вся сложность в данных. А если я их упрощу? Но как? Компьютер выдает стандарт, и станок должен этот стандарт принять. Ставить посредника в виде микроконтроллера не хочу и не буду. В комплексе с этой проблемой идет еще одна. «Берёсте» не хватит памяти, чтобы отработать G-код. Значит, нужно придумать упрощённый стандарт: команда выбора направления вперед/назад, движение по Х шаг (согласно выбранного направления). По Y - то же самое. По Z - поднять/опустить перо.  Итого 6 команд. Если первая команда будет число 1 переданный по UART, вторая команда будет число 3, третья - 7, четвертая - 15, пятая – 31. А так как это все в двоичной системе, то данные будут всегда плюс, который зажат с двух сторон минусами, а различаться будут только длительностью этого плюса. Такой своеобразный «UARTовый ШИМ». А это может сработать!»

 И тут из телевизора раздается голос Пина (Смешарики): «Компрессия!!!»

Я мысленно согласился и начал писать код «ШИМового UARTA».

Диаграмма «ШИМового UARTа»)
Диаграмма «ШИМового UARTа»)

И так, у меня есть программа, которая передает команды. Станок их принимает и даже распознает. Шаговые двигатели в движении. Вроде бы все хорошо, но ставить шаговый двигатель с громоздким драйвером на ось Z это как стрелять из пушки по воробью. От оси Z требуется поднять перо или опустить. Более рационально установить на эту ось сервопривод.

В ходе непринуждённого диалога сервопривод сообщил мне, что он любит ШИМ, но у меня нет аппаратного ШИМ. Придется писать программный. Но у меня нет таймера и прерываний. Можно, конечно, и без этой роскоши (таймер/прерывание), но возникает другая проблема: большую часть времени «Берёста» занята обработкой UART. От того очень сложно рассчитать интервалы. Как же мне быть? Решение возникло, само собой. Оказывается, моему сервоприводу SG90-9G (за другие модели ничего не скажу) не обязательно в определенное время выдавать сигнал угла поворота. То есть достаточно выдать импульс продолжительностью 500-24000 мкс. , что соответствует углу поворота 0-180 градусов, а потом безгранично долго можно ничего не подавать, до возникновения необходимости в этом.

код для "Берёсты":
//начинаем опрашивать UART
start:
mov buf,0
mov r2,buf
mov buf,8
mov adr,buf
mov buf,1
mov ram,buf

mov buf,10
mov r1,buf

mov a,p2
mov roma1, rx
mov roma2, rx
jbz0		//если - то переход на прием
// если +
mov roma1, start
mov roma2, start
jmp

rx:
mov a,p2
mov roma1, rx
mov roma2, rx
jbz0		//если - то ждем
// если +

start2:
mov a,p2
mov roma1, rx2
mov roma2, rx2
jbz0		//если - то переход на закончить
// если +

mov a,r1
add r1
mov roma1, up
mov roma2, up
jc	//если переполнение

mov roma1, start2
mov roma2, start2
jmp

up:
mov a,r2
add r2
mov r1,buf
mov roma1, start2
mov roma2, start2
jmp

rx2:
mov a,p2
mov roma1, rx2
mov roma2, rx2
jbz0		//если - то ждем

// если +
mov buf,1
mov ram,buf
mov a,r2

mov ROMa1, Xclk
mov ROMa2, Xclk
je	//если равны то переход

mov buf,3
mov ram,buf
mov a,r2

mov ROMa1, Yclk
mov ROMa2, Yclk
je	//если равны то переход

mov buf,2
mov ram,buf
mov a,r2

mov ROMa1, DIRup
mov ROMa2, DIRup
je	//если равны то переход

mov buf,4
mov ram,buf
mov a,r2

mov ROMa1, DIRdown
mov ROMa2, DIRdown
je	//если равны то переход

mov buf,5
mov ram,buf
mov a,r2

mov ROMa1, Zdown
mov ROMa2, Zdown
je	//если равны то переход

mov buf,6
mov ram,buf
mov a,r2

mov ROMa1, Zup
mov ROMa2, Zup
je	//если равны то переход

//иначе ждем следующий байт
mov roma1, loop	//указываем координаты для переходов на метку start
mov roma2, loop	//указываем координаты для переходов на метку start
jmp		//повторим

Zup:
mov buf,8		//1 в буфер
mov p1, buf		//отправить в порт 1

mov buf,0
mov r1,buf

delay270:

mov buf,8
mov adr,buf
mov buf,1
mov ram,buf
mov buf,1 
mov a,buf

delay27:
add a
mov roma1, vse27
mov roma2, vse27
jc	//если регистр а переполнился то закончить задержку
mov roma1, delay27
mov roma2, delay27
jmp 

vse27:
mov a,r1
add r1
mov roma1, vse270
mov roma2, vse270
jc	//если регистр а переполнился то закончить задержку
mov roma1, delay270
mov roma2, delay270
jmp 

vse270:
mov buf,0		//0 в буфер
mov p1, buf		//отправить в порт 0

mov roma1, loop	//указываем координаты для переходов на метку start
mov roma2, loop	//указываем координаты для переходов на метку start
jmp		//повторим

Zdown:
mov buf,8		//1 в буфер
mov p1, buf		//отправить в порт 1

mov buf,1
mov r1,buf

delay2701:
mov buf,8
mov adr,buf
mov buf,1
mov ram,buf
mov buf,1 
mov a,buf

delay271:
add a
mov roma1, vse271
mov roma2, vse271
jc	//если регистр а переполнился то закончить задержку
mov roma1, delay271
mov roma2, delay271
jmp 

vse271:
mov a,r1
add r1
mov roma1, vse2701
mov roma2, vse2701
jc	//если регистр а переполнился то закончить задержку
mov roma1, delay2701
mov roma2, delay2701
jmp 

vse2701:
mov buf,0		//0 в буфер
mov p1, buf		//отправить в порт 0
mov roma1, loop	//указываем координаты для переходов на метку start
mov roma2, loop	//указываем координаты для переходов на метку start
jmp		//повторим

DIRup:
mov buf,2 //выбор направления вращения DIR
mov p1,buf
mov buf,10 //сохраняем выбор направления вращения DIR
mov adr,buf
mov buf,1
mov ram,buf

mov roma1, loop	//указываем координаты для переходов на метку start
mov roma2, loop	//указываем координаты для переходов на метку start
jmp		//повторим

DIRdown:
mov buf,10 //сохраняем выбор направления вращения DIR
mov adr,buf
mov buf,0 //выбор направления вращения DIR
mov p1,buf
mov ram,buf

mov roma1, loop	//указываем координаты для переходов на метку start
mov roma2, loop	//указываем координаты для переходов на метку start
jmp		//повторим

Yclk:
mov buf,10
mov adr,buf
mov a,ram
mov roma1, lab2
mov roma2, lab2
jbz0		//если 0 то переход

mov buf,6
mov p1,buf

mov roma1, delay
mov roma2, delay
jmp

lab2:
mov a,p2
mov roma1, stepY	//указываем координаты для переходов на метку 
mov roma2, stepY	//указываем координаты для переходов на метку 
jbz2		//если - то переход (концевик не сработал, Y>0)
// теперь Y=0
mov roma1, delay
mov roma2, delay
jmp
stepY:

mov buf,4
mov p1,buf
mov roma1, delay
mov roma2, delay
jmp


Xclk:
mov buf,10
mov adr,buf
mov a,ram
mov roma1, lab1
mov roma2, lab1
jbz0		//если 0 то переход
mov buf,3
mov p1,buf

mov roma1, delay
mov roma2, delay
jmp

lab1:
mov a,p2
mov roma1, stepx	//указываем координаты для переходов на метку 
mov roma2, stepx	//указываем координаты для переходов на метку 
jbz1		//если - то переход (концевик не сработал, Х>0)
// если X=0
mov roma1, delay
mov roma2, delay
jmp
stepx:
mov buf,1
mov p1,buf

//вместо delay	------------
delay:
			//-
mov buf,8			//-
mov adr,buf			//-
mov buf,0			//-
mov r1,buf			//-
delay_lab:
			//-
mov a,r1			//-
add r1			//-
			//-
mov roma1, delay_out			//-
mov roma2, delay_out			//-
jc			//если переполнение
mov roma1, delay_lab			//-
mov roma2, delay_lab			//-
jmp			//-		
//------------------------
delay_out:
mov buf,10
mov adr,buf
mov a,ram
mov roma1, label3
mov roma2, label3
jbz0		//если 0 то переход

mov buf,2
mov p1,buf
mov ROMa1, loop
mov ROMa2, loop
jmp

label3:
mov buf,0
mov p1,buf

loop:
mov roma1, start	//указываем координаты для переходов на метку start
mov roma2, start	//указываем координаты для переходов на метку start
jmp		//повторим

Готовые решения управления «рисовалкой» мне не подойдут, так как они отправляют G-коды. Значит, надо писать свою.  Она должна уметь: устанавливать соединение с ЧПУ и преобразовывать G-код в нужную мне систему команд. Если с установкой соединения все просто, то с преобразованием G-кода чуть-чуть интересней.

Описание команд ЧПУ «рисовалки»:

мнемоника

описание команды

U

Выбор направления вперед для осей Х, Y

D

Выбор направления назад для осей Х, Y

X

Двинуть Х на 1 микрошаг

Y

Двинуть Y на 1 микрошаг

+

поднять перо

-

опустить перо

Пример G-кода (предположим, что исполнительное устройство находится в координатах X=0, Y=0):

G0 X2 Y2

В данном примере ЧПУ-станок построит вектор с координатами (X=0, Y=0)-(X=2, Y=2) и начнет двигать исполнительное устройство по этому вектору до координаты X=2, Y=2

Моя же «рисовалка» вектор строить не может, поэтому за нее это сделает программа на компьютере. И тут я в очередной раз освежил в памяти алгоритм Брезенхема. Вот он как раз и превратит координаты начала и конца линии в координаты каждой точки этой линии.

Мой код (предположим, что исполнительное устройство находится в координатах X=0, Y=0):

UXYXY

После выполнения этой последовательности исполнительное устройство будет находиться по координатам X=2, Y=2

Ну и пару слов о компьютерной программе. Подключаемся к ЧПУ на скорости 9600 бод. Открываем файл G-кода. Программа преобразует его в упрощённый формат, используя алгоритм Брезенхема. Жмем «отправить» и ЧПУ начинает рисовать. Также есть возможность управлять ЧПУ по всем осям в ручную.

программа управления ЧПУ
программа управления ЧПУ

Максимальный размер рисуемой картинки - 75х85 мм. В роли пера используется карандаш с диаметром графита 0.7 мм. В роли посредника между компьютером и ЧПУ выступает любой преобразователь USB-UART. Я пробовал как основанный на микросхеме CP2102, так и на микросхеме CH240.

художник собственной персоной)
художник собственной персоной)
/это шедевр!)
/это шедевр!)

Подведем итог: программа драйвера для «Берёсты» получилась объёмом 228 байт, что прекрасно вписывается в максимальный объем 256 байт. На её плечи ложится контроль двух концевых выключателей по оси Х и Y, два шаговых двигателя, один сервопривод, прием и обработку 6 команд по UART. И остается еще аж целых 28 байт на развитие.)

Вот такая «Берёста» у нас получилась: поморгает, порисует, а может и еще какие скрытые таланты имеет. Ну, это уже совсем другая история. ;)

исходные файлы проекта: Яндекс Диск

немного видео:

Комментарии (26)


  1. drWhy
    14.12.2024 16:34

    "это шедевр!)" - Определённо!

    Да и задумка неплоха.


    1. PNP80 Автор
      14.12.2024 16:34

      тут не поспоришь)))


  1. Dynasaur
    14.12.2024 16:34

    а если убрать комментарии из кода, объём станет ещё меньше?


    1. PNP80 Автор
      14.12.2024 16:34

      нет, это так не работает)


    1. drWhy
      14.12.2024 16:34

      В прошлом веке декомпилировал прошивку плоттера формата А4 - там треть прошивки занимала таблица синусов - чтобы не вычислять положение движков слабым микроконтроллером.


      1. SIISII
        14.12.2024 16:34

        Ну, это обычное решение для тригонометрических функций. Если очень уж высокая точность не нужна, то, как правило, такой подход -- оптимальный.


  1. viordash
    14.12.2024 16:34

    Классно!
    :) напомнило сказку "Каша из топора", тот случай когда периферия умней мозгов


    1. YegorP
      14.12.2024 16:34

      В том и дело. Роль "одноплатника" в этой схеме сводится к какому-то промежуточному драйверу. А вся ЧПУшность ушла в программу-упрощалку на ПК. То есть самостоятельного ЧПУ на 155-й серии тут не получилось.


      1. SIISII
        14.12.2024 16:34

        Поправочка: на конкретном одноплатном компьютере. Полноценные станки вполне себе были, но электроника занимала куда больше места (и жрала, как правило, перфоленту).


        1. Moog_Prodigy
          14.12.2024 16:34

          В принципе ничто не мешает сделать standalone ЧПУ, с вводом команд с пульта, так раньше и делали. Там относительно простая конструкция получится, наподобие старинных программаторов с тумблерами. Но как по мне, аутентичность-аутентичностью, но меру знать надо :)


      1. ds138
        14.12.2024 16:34

        Любой станок с ЧПУ по сути это исполнительное устройство, G-код нынче довольно часто формируется в постпроцессоре из CAD модели. Под ваше определение точно также определить любую абсолютно ЧПУ стойку - типа промежуточный драйвер между программой упрощалкой на ПК и сервоприводами станка. ) Там, правда, может быть и ещё один промежуточный драйвер - "человек".


  1. Slonosvin
    14.12.2024 16:34

    Вы поменяли кварц на РК257ДГ или это что-то другое? Все 257е что я видел были на 2МГц


    1. PNP80 Автор
      14.12.2024 16:34

      в конечном варианте используется РК180ДГ на 2 Мгц


      1. Sly_tom_cat
        14.12.2024 16:34

        А зачем было менять кварц?


        1. PNP80 Автор
          14.12.2024 16:34

          Изначально был кварц 1 МГц. Замена на 2 МГц позволила увеличить скорость соединения UART c 4800 до 9600 бод и как следствие скорость рисования увеличилась в два раза.


  1. Victor_Panic
    14.12.2024 16:34

    Наша страна не потеряна, пока есть такие энтузиасты


    1. ABRogov
      14.12.2024 16:34

      Нельзя потерять то, чего нет.


    1. Tirarex
      14.12.2024 16:34

      А я не понимаю восторга. Вся сложная логика работает на хост машине, а логика чпу - прием шагов и передача сигналов на драйверы. По сложности это даже не близко к логике того же марлина работающего на атмегах.

      Напомнило мне статью где DOOM запустили на тесте для беременности, а на практике просто в корпус теста засунули oled Дисплей, который был подключен к обычному пк, от теста для беременности был корпус и красивый заголовок новостей.


      1. PNP80 Автор
        14.12.2024 16:34

        Программа, находящаяся на ПК по сути это конвертер файла из одного формата (имя файла.NC) в другой формат (. Это как JPG конвертировать в BMP и потом открыть в графическом редакторе, который может открывать только BMPшный формат. Но перестает ли он быть графическим редактором или всё-таки нет?  Заводской ЧПУ не создает сам себе G-code, также, как и мой ЧПУ не создает себе UDXY+- code.


        1. osmanpasha
          14.12.2024 16:34

          Ну просто формат, который понимает ваша плата, гораздо ближе к step+dir для драйвера мотора, чем к более высокоуровневему формату файлов для ЧПУ. По сути, плата скорее переводит step+dir из одного формата в другой, а все остальное выполняется на ПК (и если бы у вас был LPT порт в ПК, то сингалы можно было бы выдавать из питона в драйвера и серву без дополнительного железа, а-ля LinuxCNC). От ЧПУ ожидается, что там будут системы координат, скорости подачи, ускорение/замедление, планировка движения с заглядыванием на пару команд вперед (это, например, то, что умеет grbl на атмеге328).

          Это все конечно, не отменяет того, что проект очень интересный с точки зрения выполнения максимально возможного в очень ограниченных ресурсах на экзотическом железе.


        1. nafikovr
          14.12.2024 16:34

          ну, как минимум, можно было команды типа g0/g1 реализовать на стороне "одноплатника"


  1. slog2
    14.12.2024 16:34

    Помню советские станки с ЧПУ. А рядом пара шкафов с платами набитыми 133-й серией. Полноценное ЧПУ было, программу с перфоленты читало.


    1. HardWrMan
      14.12.2024 16:34

      Когда я учился, нам показывали станки на основе Электроники НЦ-31:

      И помню один фрезер, как нам говорил мастер, на основе Электроника-85 (возможно кустарно обновлённый), но оформленный вот так:

      На экранчике 23см был листинг G-кода.


      1. MaFrance351
        14.12.2024 16:34

        Самые красивые были шкафы с индикаторами МС6205.


  1. anna_meow
    14.12.2024 16:34

    Учитывая, что даже современные микроконтроллеры бывают с проблемами в плане ресурсов, твой подход с упрощённым G-кодом — это прямо гениально! Кстати, сейчас вот тоже вижу, как на новых РКПУ начинают использовать программные решения для ШИМ, вроде твоего сервопривода — видимо, это тренд на оптимизацию


  1. sim2q
    14.12.2024 16:34

    Наконец то практическая конструкция а не бесполезных хотя и тоже отчасти прикольные типа "запуск linux на эмуляторе avr".