Два с половиной года назад я купил набор Lego Mindstorms EV3, чтобы сделать свои первые шаги в робототехнике. Вкатив на контроллер сборку ev3dev и наигравшись с управлением двигателями и сенсорами по SSH, я на два года охладел к покупке. Причина состояла в том, что мне не хватало фантазии по части того, что бы такое можно было собрать: после нескольких собранных моделей из Lego Technics (как коробочных, так и самодельных) я уже привык к чудесам наподобие дистанционно управляемых игрушек, а простые роботы наподобие представленных на Краковской выставке моделей из Lego у меня как у человека, работавшего в своё время над системой телемеханики, уже не вызывали достаточного вдохновения. Повторять чужой опыт тоже не особо хотелось.
В конце концов, меня осенило: моделью, достаточно сложной, интересной, практичной и при этом не слишком заезженной могли стать часы с кукушкой. Вдохновлённый этой идеей, я взялся за дело.
По моей задумке, модель должна была стать не просто игрушкой, а полнофункциональным устройством. Это удалось в полной мере: часы висят у меня на стене и в режиме 24/7 отображают текущее время. Я не ставил перед собой задачу воспроизвести механизм оригинала во всех подробностях — так, стрелки у меня независимы, звук воспроизводится через динамик контроллера, а гири и маятник служат исключительно для антуража. Переложив, таким образом, всю основную работу на программную часть, я собрал механизм в максимально простом варианте. Я не документировал весь процесс сборки, так что собрать модель ещё раз в том же виде я не смогу; тем не менее, основные принципы сопряжения софта и механики таковы, что программа должна с минимальными доработками работать и на других моделях, собранных по сходему принципу.
Часы в различных видах представлены в видеоролике, который я разместил на Youtube:
Основная информация о модели представлена в ролике. Ниже я в чуть больших подробностях расскажу о механике, о софте, а также о проблемах, которые возникли при сборке и эксплуатации сей конструкции.
Механика
Сборка механической части заняла 6 дней. Разложив коробочки с Lego на столе, я за время новогодних каникул собрал механизм, от конечного варианта отличающийся только дверцами, за которыми живёт кукушка (об этом ниже). Особых дизайнерских навыков я не имею, поэтому часы представляют собой прямоугольную коробку с двумя стрелками и размеченным точками циферблатом на лицевой стороне. В верхней части часов расположен закрытый двумя дверцами отсек, в котором поселилась кукушка, а в нижней — схематичный маятник и две гири. Детали были взяты из нескольких наборов Lego: одного Lego Mindstorms EV3 (31313), двух грейдеров Volvo (42030), одного грузовика Mercedes с пневматической стрелой (42043) и одного автокрана (42009). Несмотря на обилие наборов, на модель такого размера деталей еле хватило — крышка часов собрана из планок разного цвета, что, впрочем, не бросается в глаза, поскольку при настенной установке она находится выше уровня глаз.
В качестве минутной стрелки используется стандартная планка Lego Technics на 15 юнитов. Она размещена на оси, жёстко связывающей её с двигателем. Таким образом, точность выставления минутной стрелки оказалась достаточной, чтобы по ней можно было определять время с точностью до минут. Часовая стрелка короче и закреплена на поворотном механизме. С двигателем она связана через несколько шестерней, поэтому здесь имеется довольно заметный люфт — впрочем, не настолько большой, чтобы вызывать проблемы с определением времени.
Стрелки приводятся в действие двумя большими моторами (LM). Кроме того, в паре с каждой из внешних стрелок работает ещё одна, внутренняя — для минутной стрелки она закреплена на той же оси, что и первая, а для часовой поворотный механизм дублирован с внутренней стороны часов. Цель внутренних стрелок — нажимать на кнопку (Touch sensor) при прохождении внешних стрелок через верхнее положение. Таким образом, контроллер имеет возможность определить, в каком положении находятся стрелки сразу после включения, и задать их правильное положение на основе системного времени, избавив пользователя от необходимости подводить стрелки вручную.
Гири носят сугубо декоративную функцию. Изначально я думал обойтись одной гирей, которая будет медленно опускаться (как в обычных часах), а затем быстро подтягиваться в верхнее положение (аналог ручного завода маятниковых часов). От этого варианта я впоследствии отказался по нескольким причинам. Во-первых, механизм переключения передаточных чисел между двигателем и гирей привносил в модель лишнюю сложность (а привод маятника от того же двигателя лишь увеличивал её). Во-вторых, быстрая подводка, как ни крути, будет шумной операцией, а мне хотелось, чтобы часы работали так тихо, как это возможно. В-третьих, в обычных часах с кукушкой обычно имеется две гири (одна обеспечивает ход часов, вторая — работу кукушки), а размещение в нижней части часов сразу двух перекидных колёс для цепи привело бы к ещё большему росту размеров модели.
Исходя из этого, я пошёл по более простому пути: я действительно установил на часы две гири, но разместил их на общей цепи, перекинутой через единственное приводное колесо. В таком варианте гири всегда движутся в противополодных направлениях, опускаясь по очереди. Такое поведение имеет мало общего с реальными маятниковыми часами, однако выглядит вполне интересно.
Цепь сделана из обычных планок по три юнита с использованием вперемешку осей и пистонов: проскальзывающий пистон — довольно редкая деталь, и я использовал оси, чтобы удвоить длину получившейся цепи. Сделать колесо, которое двигает такую цепь без проскальзываний и срывов, оказалось не так-то просто, но, в конце концов, удалось и это.
Что касается переключения направления движения гирь, то моей первой идеей было сделать его чисто механическим: при подъёме до нижней стенки корпуса часов широкая ось, установленная прямо над гирей, нажимает на пластину, сквозь которую продета цепь, и вызывает переключение направления вращения колеса. Переключающий элемент я собирался использовать такой же, какой, например, у крана 42009: приводное колесо, сдвигаясь, входит в сцепление то с правым, то с левым соседом. Эта идея, однако, не полетела: нажимная пластина выводила скользящую деталь в центральное положение, когда она не сцеплена ни с правым, ни с левым соседом, крутящий момент переставал поступать на колесо, и переключатель замирал в центральном положении.
Чтобы выправить эту ситуацию, я решил добавить в эту схему подвижный треугольник, две стороны которого имели бы фиксированную длину, а третья была бы представлена подпружиненной раздвижной деталью, использующейся в подвеске того же автокрана. Смысл этой конструкции был в том, что треугольник всегда находится на стороне той нажимной панели, которая соответствует поднимающейся гире: когда гиря достигает верха, панель давит на треугольник, пружина сжимается, треугольник проходит через положение равновесия и затем «перекидывается» на другую сторону, высвобождая запасённую пружиной энергию. Эта энергия по моему замыслу и должна была сдвигать переключатель скоростей через положение равновесия, сразу переводя его в противоположное положение.
Стабилизировать эту систему мне, однако, не удалось. По размеру часов можно понять, что миниатюрные механизмы — не мой конёк, а с ростом размеров узла возникающие в нём перекосы приводят к трению, съедающую большую часть энергии переключения. Намучившись с этим механизмом, я, наконец, решил переложить всю грязную работу на микроконтроллер, установив над нажимными панелями по кнопке (Touch sensor) вместо сложной механики.
Немного простой арифметики. У EV3 есть четыре входа и четыре выхода. Два входа и два выхода уже были заняты кнопками и двигателями стрелок, соответственно. Добавление к этой схеме двигателя, вращающего колесо цепи, и двух кнопок означало, что, во-первых, у меня не осталось свободных портов для подключения датчика положения кукушки (я думал использовать для этой цели оптический измеритель расстояний), а во-вторых — что на оставшиеся узлы (кукушку и маятник) приходится всего один двигатель. Я рассматривал вариант, когда обе нажимные панели над гирями были бы связаны с общей кнопкой, но отказался от него, поскольку, если в момент включения часов одна из гирь находилась бы в верхнем положении, зажав кнопку, у часов было бы недостаточно информации, чтобы выбрать направление движения цепи.
Поскольку маятник было проще связать с механизмом движения цепи, чем с кукушкой (и по характеру движения, и по расположению), я разместил в нижней части корпуса ещё один большой мотор, который, во-первых, водит маятник туда-сюда (1 оборот двигателя переводится в одно полное колебание маятника), а во-вторых — через понижающую передачу вращает колесо, приводящее в действие цепь. Такая система обладает тем минусом, что при обращении направления движения цепи и, соответственно, вращения двигателя маятник также меняет направления движения, даже если он находится в промежуточном положении, что может выглядеть некрасиво. Впрочем, переключение направления движения гирь — достаточно редкая операция, так что я пренебрёг данной проблемой. Другой недостаток моей сборки — это довольно заменый люфт в приводе маятника, из-за чего при медленном его движении маятник на заметную глазу долю секунды останавливается в нижней точке. Я поленился исправлять эту проблему.
Кстати о скорости движения маятника. Поэкспериментировав с ней, я выбрал значение в 30 градусов двигателя в секунду. Это означает, что маятник совершает полное колебание за 12 секунд. Это много, однако при увеличении скорости двигатель начинал подвывать, а мне вовсе не улыбается перспектива непрерывно слушать его песню. Перебирать механизм и вводить повышающую передачу мне, опять-таки, было лень, так что я остановился на таком «лунном» формате. Передаточные числа между двигателем и колесом привода цепи таковы, что гиря проходит путь от нижнего до верхнего положения за 40-50 минут.
Оставшийся свободным после всех предыдущих манипуляций порт EV3 занял средний мотор (Medium motor), который приводит в действие кукушку. Сама кукушка собрана из деталей того же Lego technics. В высоту птица достигает 11 юнитов и размещена на выдвижной планке (на таких в упомянутом автокране размещались выносные упоры). Миниатюрный механизм вводит передачу между крыльями птицы и её хвостом, через который я перекинул закреплённый внутри часов тросик. Длина троса подобрана таким образом, чтобы кукушка полностью выезжала с опущенными крыльями и лишь в самом конце движения слегка разводила их в стороны. Получилось атмосферно — как раз на уровне механизации простых моделей часов. Отсутствие датчика положения компенсируется тем фактом, что привод кукушки включен через проскальзывающую шестерню — она позволяет не повредить двигатель, даже если тот пытается двигать кукушку за граничные точки. Дверцы жёстко связаны с планкой, на которой закреплена птица, и открываются одновременно с выездом самой кукушки.
Изначально в качестве дверей я использовал панели 11 на 5 на 1 юнит, однако это оказалось плохой идеей: их края не были скруглены, и дверцы, цепляясь за соседние детали, нередко блокировались, оставляя кукушку внутри. На видео показан уже второй вариант сборки дверей — обычные планки со скруглёнными краями не вызывают такой проблемы. Кроме того, «планочный» вариант дверей открывается и закрывается куда тише.
Часы крепятся на прикрученную к стене планку. Вся конструкция весит более килограмма.
На этом описание механической части, в принципе, заканчивается. Осталось упомянуть только, что модель на 100% собрана из Lego Mindstorms и Lego Technics, и единственная часть, которая не относится к этим наборам — это провод питания, который я припаял к своему EV3, чтобы не приходилось всё время менять в часах батарейки. Этот провод идёт к блоку питания, включенному в розетку.
Программа
Сборка ev3dev предоставляет байндинги под многие языки программирования. Чтобы не иметь сложностей со сборкой, я решил ограничиться скриптом на Pythonе. Этот скрипт доступен в репозитории GitLab. Ниже я коротко пробегусь по его функционалу.
Код, который двигает стрелки, относительно прост — он пересчитывает значения минут и часов в значения градусов поворота двигателей, используя данные о количестве градусов двигателя на один оборот стрелки и о первоначальном смещении стрелки от вертикального положения. Всё самое интересное начинается, когда программа пытается получить эти данные.
Как я уже говорил, от ручной подводки стрелок было решено отказаться. Вместо этого, при старте часы выполняют калибровку, определяя параметры геометрии. Калибровка выполняется для обеих стрелок независимо друг от друга.
Алгоритм калибровки следующей. Сначала двигатель начинает вращаться с относительно большой скоростью (я использую эмпирическое значение в 180 градусов в секунду) до тех пор, пока от кнопки, фиксирующей верхнее положение стрелки, не придёт подтверждающий сигнал. После этого двигатель сбрасывает скорость до 30 градусов в секунду и продолжает вращать стрелку по часовой стрелке (направление вращения двигателя зашито в программе) до тех пор, пока от точки последнего размыкания кнопки его не будет отделять 70 градусов. После этого стрелка проходит через верхнее положение в обратном направлении, фиксируя первое замыкание и последнее размыкание контактов (кнопка может дребезжать в краевых положениях). Отойдя от последнего размыкания на те же 70 градусов, стрелка снова меняет направление движения на прямое, и процедура регистрации первого замыкания и последнего размыкания повторяется.
Считается, что верхнее положение стрелки соответствует среднему арифметическому между четыремя полученными таким образом значениями. Практика показывает, что это приближение является достаточно хорошим — отклонение целевого положения минутной стрелки от фактического редко составляет более 1 шага и практически никогда не превышает двух шагов. (На ролике видны более существенные отклонения, связанные с тем, что для целей съёмки я переводил системное время, и стрелки двигались быстрее, чем при нормальной работе, увеличивая ошибку коррекции на каждом обороте, см. ниже.) Для часовой стрелки люфт съедает точность, но алгоритм, разумеется, используется тот же самый.
Вычислив верхнее положение стрелки и разницу между первым включением датчика и фактически верхним положением стрелки (это значение потребуется для коррекции, см. ниже), алгоритм калибровки снова увеличивает скорость вращения двигателя до 180 градусов в секунду и повторяет процедуру поиска верхнего положения. Разница между двумя последовательными верхними положениями определяет длину полного оборота стрелки в единицах поворота двигателя. Процедура поиска верхнего положения повторяется пять раз, что позволяет усреднить полученные значения и несколько повысить точность.
После того, как калибровка завершена, стрелки переходят в режим отображения текущего времени. При этом на каждом круге стрелки алгоритм фиксирует положение, при котором произошло первое замыкание выключателя. Разница между фактическим и ожидаемым углами поворота двигателя в этот момент составляет величину коррекции; таким образом, даже если длина полного круга была рассчитана неверно на этапе калибровки (отладочный вывод показывает, что отклонение редко превышает 1 градус), коррекция на каждом круге не позволяет стрелке с течением времени сместиться из-за накопления ошибки.
Обратите внимание, что единственная информация об отношении двигателя и стрелки, которая зашита в программу в явном виде, — это направление вращения двигателя (прямое либо обратное), соответствующее прямому ходу стрелки. Благодаря датчикам положения, необходимость в данных о передаточном числе и начальном положении стрелки пропадает. Таким образом, если Вы соберёте часы, у которых, помимо часовой и минутной, будет также представлена секундная стрелка, алгоритм позволит задействовать её добавлением всего нескольких строк, не тратя времени на пересчёт зубцов.
Чего в программе движения стрелок нет — так это защиты от переполнения или потери точности. Я не исследовал вопрос о том, что произойдёт раньше: переполнится разрядность угла поворота двигателя или стрелки начнут двигаться рывками из-за ухода точности в старшие разряды числа с плавающей точкой. Тем не менее, практика показывает, что на временах порядка нескольких недель ничего подобного не случается. Если я когда-нибудь встречу эту проблему, я, скорее всего, просто добавлю команду сброса двигателя на каждом круге, с соответствующей коррекцией углов в программе.
Если класс Hand в моей программе отвечает за движение стрелок, то класс LedIndication то зажигает, то гасит светодиоды на передней панели EV3. Я знаю, что того же эффекта можно добиться одной командой, но изначально эта фича была предназначена для отслеживания вылетов программы, а потом так и осталась. Больше ничего интересного в этом классе нет.
Класс Pendulum отвечает за движение гирь и маятника. Алгоритм просто запускает двигатель в одном направлении и вращает его с постоянной скоростью до тех пор, пока кнопка, соответствующая полному поднятию данной гири, не окажется зажата; после этого направление движения мотора инвертируется.
В этом классе, кстати, представлена защита от вылета программы, которая заключается в том, что двигатель включается всего на 2 секунды с периодом обновления команды в 1 секунду. Если бы я включил двигатель в режиме постоянного вращения, то при вылете программы гиря, упёршись в кнопку, продолжала бы двигаться вверх, что могло бы привести к перегрузке мотора, проскакиванию шестерёнок или даже повреждению механизма. Защита позволяет не допустить этой проблемы.
Наконец, класс Cuckoo запускает кукушку каждый раз, когда значение в 59 минут сменяется значением в 0 минут. Сначала кукушка выдвигается до упора, затем кукует один раз, воспроизводя звук через динамик EV3, а затем вновь прячется в корпус часов. Движение повторяется в соответствии с тем, который сейчас час — от 1 до 12 раз. Звук был взят из бесплатной библиотеки FreeSound и отредактирован так, чтобы лучше звучать на EV3. (Не могу сказать, чтобы я был доволен результатом, поскольку басов при ударе молотка по пружине практически не слышно.)
Задержка между командой на вынос кукушки и командой на воспроизведение звука подобрана экспериментально так, чтобы звук раздавался примерно в тот момент, когда кукушка достигает крайнего положения. С этим, однако, у ev3dev есть небольшая проблема: из-за загрузки системы звук периодически запаздывает, особенно если к часам в этот момент имеется SSH-подключение. Задержка может составлять до нескольких секунд. Я не уверен, проблема ли это моего контроллера, самой сборки или принятых из сети обновлений (после установки ev3dev я по привычке ввёл команду apt get upgrade, хотя, возможно, делать этого и не следовало), но в среднем кукушка звучит нормально, так что отлаживать этот вопрос я не собираюсь.
С задержкой звука, кстати, был связан баг в одной из ранних версий программы, когда я ещё не добавил ожидание окончания воспроизведения: если кукушка успевала спрятаться и вновь показаться за время задержки, следующий звук «проглатывался», и количество «кукуков» не совпадало с количеством часов. Позже я модифицировал программу так, чтобы кукушка ожидала окончания звука в крайнем положении и лишь потом уходила на следующий цикл.
Получасовые интервалы озвучиваются одиночным сигналом, полученным из того же исходного звука. Кукушка при этом не появляется.
Скорость вращения двигателя, приводящего в действие кукушку, и угол его поворота зашиты в программе. На старте программы кукушка затягивается в корпус — с учётом установленной на оси двигателя проскальзывающей шестерёнки такая операция безопасна, но имеет эффект в случае, если в момент запуска часов кукушка находилась вне корпуса.
Чтобы не мешать спать, кукушка кукует лишь днём. Её рабочий день начинается в десять утра, а последним значением времени, которое она озвучивает, является полночь. Я также отключил кукушку в то время, когда у меня проходят еженедельные рабочие звонки по Skype, чтобы не мешала.
Программа установлена в виде сервиса и стартует при запуске Debian. Таким образом, часы достаточно включить, а всё дальнейшее они делают сами. Время берётся из интернета — в моей EV3 стоит WiFi-донгл. После того, как часы запустились, сеть я, как правило, отключаю, чтобы не создавать лишней нагрузки на процессор.
Программа доступна по лицензии BSD. Если у Вас будет желание собрать похожую модель и использовать мой софт, я буду только рад.
Автор: Furax