Механический дисплей из лего и Arduino, о котором я писал в начале января оказался, по-моему, довольно неплох для конструкции собранной за вечер. Однако в комментариях справедливо указали на возможности его улучшения. Заодно, читая отзывы, я узнал что такое «POV», спасибо всем откликнувшимся!
Выявленные недостатки
Малый размер «экрана», недостаточный для написания слова «Хабрахабр»
Монохромность
Неравномерность шага пикселей, расчитанного простым делением времени цикла на разрешение
Разочарование ожидавших увидеть видео хаброюзеров
Необходимость вручную побитово кодировать изображение каждой буквы
Общая нестабильность конструкции — «дисплей» при работе сильно раскачивается
Холостой обратный ход планки со светодиодами. Они подсвечиваются только во время движения справа налево
За прошедшие четыре недели конструкцию удалось доработать, и вот что получилось в результате:
Работа над ошибками
Стабильность и размер
Улучшить механическую стабильность оказалось проще всего — у ребёнка были позаимствованы кубики Duplo (lego двойного размера совместимое со стандартным), и из них собрана прочная и тяжёлая платформа. После этого удлинил кривошип и нарастил качающуюся планку со светодиодами, тем самым увеличив горизонтальное разрешение. Сперва думал поставить более мощный мотор, но хватило замены батареек на стабилизированный блок питания и небольшого повышения напряжения. При 10,3 вольт моя конструкция делает ровно 6 полных колебаний в секунду.
На переднем плане видны две оптические пары — теперь можно ловить и начало обратного хода.
Цвет
Сделать дисплей цветным казалось лёгкой задачей — достаточно заменить одноцветные светодиоды на RGB. Сказано — сделано. Общие аноды через ограничительные резисторы соединил с шиной данных, а катоды спаял вместе поцветно и подсоединил к выходам Arduino, ответственным за выбор цвета. Когда горят все восемь светодиодов, по каждому проводу выбора цвета стекает до 39 миллиампер, при том что Arduino может максимум 40. Для гарантии добавил три MOSFET транзистора IRF 540 — по одному на каждый цвет. Получилось примерно такая схема (показаны лишь три RGB светодиода):
Качающаяся планка соединяется с контроллером 8+3 тонкими проводами. Немало, но если их аккуратно проложить, то движению они не мешают.
Грабли номер один
Подаём логическую единицу на нужный анод (D6-D13), единицей-же открываем ответственный за цвет транзистор(D0-D2) и светодиод зажигается. Если на управляющие цветом выходы подать 001, то загорается красный, 010 — зелёный, 011… снова красный, хотя ожидался жёлтый.
Чтение даташита показало, что красный светодиод зажигается при напряжении в 2,0 Вольта, тогда как зелёному и синему нужны 3,2. В результате после открытия красного диода напряжение практически не растёт, только увеличивается ток, и зелёный с синим не открываются. Решения проблемы для приведёной схемы подключения с ограничивающими ток резисторами в цепи анода я так и не нашёл. Если подключить резисторы к катодам, то на каждом диоде будет падать своё, нужное ему напряжение, но резисторов понадобится 24 штуки, что для моей подвижной конструкции слишком тяжело. Гугль нашёл несколько микросхем драйверов матриц светодиодов, но у меня в хозяйстве их не было. В результате обошёл проблему программно, быстро зажигая цвета по очереди. При задержке в 50 микросекунд на цвет, за время показа одного пикселя элементарные цвета успевают смениться много раз и сливаются в один.
Грабли номер два
В неподвижном положении устройство теперь показывает ожидаемые цвета, пора опробовать его в движении. Запускаю мотор, и обнаруживаю следующую проблему:
В RGB cветодиоде красный и синий элементы находятся на противоположных сторонах корпуса, и в результате вместо сиреневого цвета получаем две хорошо различимые отдельные полоски — красную и синюю. На стыке красной и зелёной областей тоже хорошо видна разница в расположении светоизлучающих точек. Эту проблему удалось полностью решить заменой прозрачных светодиодов на матовые.
Механическая развёртка
Удлинение планки и увеличение её размаха повысило теоретическое количество пикселей, однако нисколько не решило проблемы неравномерности движения. В крайнем правом положении около двух десятков миллисекунд светодиоды практически неподвижны, в то время как слева они мгновенно меняют направление движения на противоположное. Мотор неидеален и скорость вращения зависит от нагрузки. Гибкий пластик и люфты в соедиениях окончательно запутывают картину. Учебник по теоретической механике я в последний раз открывал лет пятнадцать назад, так что даже не пытался рассчитать динамику движения моего дисплея, и сразу решил измерять как он ведёт себя в реальности. Это оказалось не так уж трудно: впереди поставил маску с равномерно расположенными отверстиями, направил на неё свет яркой лампы, а на конце планки закрепил фоторезистор.
Осталось написать несложную программу, которая сперва делает несколько холостых циклов и усредняет для точности их период, а потом собирает через АЦП данные об освещённости фотодиода. Каждое измерение сопровождается вызовом функции micros, выдающей количество прошедших микросекунд. Массив с данными по последовательному порту перекачал в компьютер и проанализировал в Экселе.
Вот как выглядит напряжение на фоторезисторе — отлично видно когда на него попадал свет сквозь отверстия в маске:
А вот нелинейность во всей своей красе, хорошо что не стал пытаться это смоделировать и рассчитать:
Эксель умеет аппроксимировать произвольные наборы данных полиномами до пятого порядка, так что через несколько минут в моих руках была заветная формула описывающая движение:
Здесь y это задержка в микросекундах от начала цикла, x — номер виртуального пикселя меняющийся от 1 до 20 (у меня в калибровочной маске было 20 отверстий). Давая x дробные значения, можно расчитать задержки для произвольного разрешения. Я остановился на 54 пикселях.
Пиксели
Для проверки формулы написал простенькую программку, выводящую попеременно 0x55 и 0xAA для подсвечивания пикселей в шахматном порядке.
Экселевская математика не подвела, и к равномерности шага растра претензий нет, но вот сами пиксели получились вместо прямоугольных овальными. Не удивительно, ведь формула рассчитывает задержки для центра идеального пикселя, а реальный светодиод имеет 5 мм в диаметре, и засвечивает прилегающие области. Прямоугольные пиксели проще рисовать прямоугольными светодиодами, чего я и добился, наклеив по бокам непрозрачный скотч:
Контрольная «шахматная доска» подтвердила, что так действительно намного лучше:
Программирование
Алгоритм простой и сложностей не предвиделось, однако в определённый момент написания кода программа стала давать странные, непредсказуемые, результаты. Оказалось, что кончилась оперативная память, которой в Arduino UNO всего два килобайта. В микроконтроллере нет следящей за распределением памяти операционной системы, так что никаких ошибок он не замечает и о переполнении не сигнализирует. Я предполагал, что объявленные как const массивы хранятся в Flash-памяти, однако выяснилось, что это не так. Точнее говоря, они сперва лежат действительно в постоянной памяти, но при запуске копируются в RAM, хотя их и нельзя изменять. Чтобы этого избежать, переменные нужно объявлять через PROGMEM, про это нашлась статья на сайте ардуино. В общем «читайте маны, они рулез».
Руссификация
Рисовать буквы по-точкам неудобно, так что положил в тот же PROGMEM таблицу знакогенератора и написал функцию отрисовки букв. С английскими символами никаких проблем, а вот вместо русских выводилась белиберда. В качестве среды разработки использовался VisualStudio 2010 с плагином Visaul Micro Arduino. Несмотря на то, что файлы исходников сохранялись в кодировке Win1251 (один байт на символ), строка видаchar* text = "ХАБРАХАБР";
объявляла массив длиной 27+1 байт, в котором каждая тройка имела одинаковое содержание, к исходному тексту никакого отношения не имеющее. Тот же код из-под среды Arduino UNO пишет по два байта на русский символ, скорей всего в UTF8. Проблему удалось обойти, закодировав строку посимвольно:char russianText[] = {'Х', 'А', 'Б', 'Р', 'А', 'Х', 'А', 'Б', 'Р', ''};
Теперь, как и ожидалось, получаем 9 байт текста плюс ещё один на завершающий ноль. Запускаем программу и видим… совсем другие символы. Исследование показало, что большая русская буква «А» компилируется в код 144, что никак не соответсвует ожидаемому для Win1251 коду 192. Не поленился и
почитал в Интернете про возможные кодировки русских букв, в том числе и весьма экзотические. Ни в одной из них А не транслируется в 144. Единственное приходящее в голову объяснение, это представление русских букв в юникоде, после чего из буквы А (U+0410) вырезается младший байт, к которому почему-то добавляется 128. Почему так — непонятно, но после добавления к коду букв нужной константы, они встали на свои места.
Вот что получилось в итоге:
Видео
А как же без него?
При длительности цикла в 170 миллисекунд получается почти точно шесть кадров в секунду. У камеры же — 30 кадров в секунду, и из-за кратности частот на видео заметно сильное мерцание. Если смотреть глазами, то картинка выглядит намного стабильней.
Что дальше?
Вывод изображения при обратном ходе развёртки пока не запрограммировал. Это позволило бы удвоить частоту кадров, но усложнило бы программирование анимации и прокрутки текста. При рисовании только во время прямого хода у микроконтроллера есть целых 80 миллисекунд, чтобы неторопливо рассчитать следующий кадр.