Проект не содержит Ардуино


Этот проект изначально должен был выглядеть иначе — монументальное сооружение, состоящее из тумбы с канистрами и насосами, водружённого на неё аквариума и помидорного оазиса поверх него. В райских кущах помидорного оазиса планировался водопад, а в аквариуме — рыбные формы жизни, главное требование к которым — умение поедать незапланированных жителей аквариума и держать в чистоте стёкла; основные кандидаты — сомики и гурами. Как вы уже могли догадаться, мой девиз — «лень — двигатель прогресса» (и чего только не сделаешь, чтобы аквариум не чистить и помидоры не поливать).

Монумент этому девизу, наверно, таки был бы воздвигнут, если бы не завалился уже на этапе согласования набросков внешнего вида с женой. Она не прониклась идеей сделать эту бандуру главной декорацией гостиной, и даже водопад её в этом не убедил. Но идея автономной системы, симбиоза биологии и электроники, не желала вылетать у меня из головы, и проект скукожился до габаритов цветочного горшка — аквапоника превратилась в гидропонику, рыбьи жизни были спасены.

Основная идея гидропоники — использование водного раствора питательных веществ вместо почвы. Это позволяет на порядок ускорить рост растений. Однако, нельзя просто опустить корни в воду — им необходим кислород, без которого они начнут отмирать. В связи с этим есть варианты — постоянно продувать воду компрессором, как в аквариуме, либо периодически затапливать корни питательным раствором, и сливать его через какое-то время. У первого варианта есть недостаток — постоянное гудение компрессора. Второй вариант имеет преимущество — корни большую часть времени находятся на воздухе, активно дышат, и эффект ускорения роста должен быть ещё больше. При этом они погружены в субстрат из специальных пористых гранул, которые удерживают влагу. Выбор был очевиден, второй вариант я и взял за основу. В случае с рыбами система могла бы получится практически полностью замкнутой — выделения рыб перерабатываются специальными бактериями в биофильтре, продукт переработки подаётся растениям, слой песка фильтрует воду, чистая вода возвращается в аквариум. В идеальном случае, в автоматическую кормушку изредка досыпается корм, с кустов собираются помидоры. Но не срослось, может оно и к лучшему — кто знает, чем бы закончился заказ по почте штамма нужных бактерий.

В итоге, устройство помидорной установки обрело контуры. Два сосуда — нижний с водой, верхний с субстратом и растением. Для затопления будем использовать небольшой китайский насос с двигателем постоянного тока, для слива — автоматический сифон. Принцип работы сифона на видео:

Гидропоника с подобным сифоном:


Мозг устройства — микроконтроллер ATMEGA328P (просто, потому что россыпь была под рукой). В его задачи входит управление затоплением и сливом по расписанию, слежение за уровнем воды в баке и сигнализация о её нехватке, управление подсветкой растения (мы хотим иметь некую минимальную продолжительность светового дня; когда заканчивается естественное освещение, постепенно включается искусственное), пользовательский интерфейс для просмотра статуса, управления и конфигурации всего этого хозяйства. Очевидно, что для этого необходимо какое-то решение датчика уровня воды, датчики освещения, часы реального времени и какой-нибудь пользовательский терминал.

Перед описанием подробностей список ресурсов проекта:

Здесь можно посмотреть фотографии результата и процесса изготовления.

Небольшое видео:



Проект доступен на GitHub. Там же в релизах выложен файл с проектом электронной части в KiCAD и проектами конструктивных прибамбасов в SolidWorks (STL-файлы для печати прилагаются).

Особенности сборки прошивки
Для сборки используется другой мой проект — библиотека под кодовым названием «велосипедная фабрика». Там можно найти вещи, достойные отдельной статьи, например, душераздирающая история о собственной полностью программной реализации USB протокола для микроконтроллеров AVR (да, я в курсе, что я не первый, но для меня главное процесс, плюс не забываем о кодовом названии), но в данном проекте используется только система сборки и немного вынесенного туда типового кода прошивки. Если кто-то реально решит собрать прошивку, то эту библиотеку надо скачать, выставить переменную окружения 'ADK_ROOT' равную пути до её директории, и в директории проекта прошивки выполнить команду 'scons'.


Схема электронной части:



Далее подробности, описание подводных камней и немного кода. Описание программных моментов в самом конце. Возможно, кому-то будет интересно посмотреть новый пример реализации работы с I2C, валкодером, модулем RTC, графическим дисплеем. Весь код в проекте написан «с нуля» без использования сторонних решений (потому что могу).

Датчик уровня воды


Самый щекотливый вопрос решался первым. Был, конечно, вариант какого-нибудь поплавка, чтобы он, например, двигал рейку, на которой нанесён код Грея, и оптические датчики бы считывали. Но очень уж выглядело ненадёжно. Поиск по eBay результата не дал — там были либо поплавковые концевики (достигнут нужный уровень или нет), либо погружённые электроды и показания на основе проводимости среды, но это сразу отметалось, так как состав воды бы постоянно менялся вместе с проводимостью от добавляемых удобрений и растворения примесей из субстрата. В итоге, пришла идея использовать ультразвуковой дальномер, из тех, что обычно ставятся на разных роботов. По задумке, датчик ставится в крышке бака и сигнал отражается непосредственно от поверхности воды. Был куплен HC-SR04 (выбор по самому маленькому значению минимальной рабочей дистанции — она у него 2см), и на ведре с водой была проверена концепция. Оказалось, что это вполне себе работает (были опасения, что от поверхности воды не будет нормального отражения, или, что не хватит направленности луча и будут нежелательные отражения от стенок бака). Кстати, запасным вариантом был тоже дальномер, но инфракрасный. На поверхности воды предполагалось бросить поплавок с отражателем. Единственная проблема — минимальная рабочая дистанция у них 10см (из тех, что я нашёл), что уже многовато для заданных габаритов.




По результатам проекта — такой подход рабочий, и может использоваться на практике, проблем замечено не было. Стоит предпринять меры по изоляции платы от влаги (герметизации в корпусе). Вот только сами сенсоры остаются открытыми, возможно, это ещё аукнется.

Интерфейс у датчика простой — на вход trigger подаётся импульс, который запускает эхо-сигнал. На выходе echo формируется импульс, длина которого равна времени от начала излучения до принятия отражённого эхо-сигнала. Измерив длину импульса, зная скорость звука и тот факт, что сигнал идёт до объекта и обратно, можно посчитать дистанцию. В проекте это реализовано в классе LevelGauge. Для измерения длины импульса используется аппаратная возможность МК AVR «input capture». При этом аппаратный таймер сбрасывается по восходящему фронту импульса, а по нисходящему значение таймера аппаратно сохраняется в регистре ICR1, и генерируется прерывание. Таким образом, можно измерить длительность импульса с достаточной точностью и минимальным расходом процессорного времени.

Ещё у данной модели датчика был замечен глюк — при подаче питания линия echo оставалась постоянно активной. Обошёл подачей импульса на trigger и дождавшись, пока пройдёт первый цикл эхо-локации.

Подсветка


Подсветка выполнена тремя трёхватовыми светодиодами. Согнул из алюминиевого профиля треугольную раму, приклеил светодиоды на неё эпоксидкой. Для питания заказал китайский стабилизатор тока на 700mA. На каждом диоде падает около трёх вольт, стабилизатор требует разницы между входным и выходным напряжением не менее двух вольт, а питать всю вундервафлю я собирался от 12-вольтового блока питания. Отсюда несложно посчитать, почему именно три светодиода.

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





Важная особенность стабилизатора — наличие входа для ШИМ-регуляции, который я использую для регуляции яркости. Тут очередные китайские грабли. Во-первых, это оказался просто функционал включения/выключения тока. То есть я ожидал, что выходной ток модулироваться не будет, а его значение будет зависеть от скважности ШИМ-сигнала, но ток просто повторял импульсы на управляющем входе. Но это полбеды, другая засада была в том, что на ШИМ с достаточно высокой частотой регулятор реагирует неадекватно. Пришлось снизить до 300Гц, на которых он работал более-менее нормально. ШИМ-сигнал генерируется микроконтроллером аппаратно, используя один из таймеров.



Другой важной частью узла подсветки являются датчики освещения. В этой роли были выбраны фототранзисторы. И да, их два — один над светодиодами для измерения естественного освещения, второй под светодиодами для обеспечения обратной связи. Правда, функционал автоматического продления светового дня пока не реализован, так как дело было летом, и это было не нужно (а мотивация — дело серьёзное). Предполагалось, что как только первый датчик фиксирует снижение уровня освещённости (а время, выделенное на световой день, ещё не закончилось), то свет регулируется таким образом, чтобы второй датчик выдавал уровень, соответствующий нужной освещённости. Для этого в коде надо реализовать простенький ПИД-регулятор. Но пока в интерфейсе можно только посмотреть текущие показания датчиков, и вручную накрутить нужную яркость подсветки.

Обратите внимание на подключение датчиков. Каждый из них имеет два фиксированных диапазона, которые выбираются подключением к нулю соответствующего резистора. Нога микроконтроллера, подключённая ко второму резистору, в это время переводится в состояние высокого сопротивления. Можно включить и оба резистора одновременно, тогда будет три фиксированных диапазона измерений. Сигнал с эмитерных резисторов пропускается через RC-цепочку для фильтрации модулирующих импульсов — свет от свтодиодов пульсирует вместе с ШИМ-сигналом на регуляторе тока.

Насос




Самый дешёвый китайский, шестерёночный, с двигателем постоянного тока. Засады, конечно же, имеются. Несмотря на то, что на нём написано 12В, при таком напряжении он долго не проработает. Один сгорел ещё до сборки конструкции. В схеме для него предусмотрен ШИМ, максимальная мощность настраивается в интерфейсе, на практике не ставил выше 70%. Уже на этом уровне он дико завывает при работе, но большую часть времени он работает на гораздо меньшей мощности — около 30% и достаточно тихо урчит. О его режимах работы ниже, в описании логики затопления. Конденсатор побольше (C8 на схеме) надо расположить поближе к контуру питания насоса, иначе будут большие помехи на всю схему (на практике оказалось, что к ним более всего чувствителен регулятор тока для светодиодов, начинается светомузыка).

Часы реального времени


Была шальная мысль использовать ресурсы микроконтроллера под эти цели. Кварц тактового генератора довольно имеет довольно неплохую точность, в другом проекте такой подход неплохо работал. Но беда в том, что абсолютно все аппаратные таймеры были уже заняты под другие цели. Не оставалось ничего другого, как найти внешний модуль RTC. Хвала китайцам, их есть и они копеечные.



Модуль на основе DS3231 имеет I2C интерфейс, собственный резервный источник питания — время не собьётся при обесточивании. Есть выход меандра на несколько фиксированных частот — 1кГц, 4 кГц и 8 кГц. Это очень пригодилось для звуковых сигналов — опять же не надо загружать MCU, да и таймеров свободных для этого не оставалось. Бонусом идёт EEPROM на 32Кбит, но в этом проекте оно не используется.

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

За работу с данным модулем в коде отвечает класс Rtc.

Дисплей


Давно хотелось сделать что-нибудь с графическим дисплеем. Поиск самого дешёвого с I2C интерфейсом выдал данный вариант.



Монохромный OLED-дисплей 128х64 пикселя на основе довольно популярного контроллера SSD1306. При выборе надо внимательно смотреть описание — этот же чип поддерживает другие интерфейсы, кроме I2C, и встречаются варианты без него. Либо пишут, что универсальный, поддерживает I2C в том числе, но на деле потребуется немного модифицировать плату, переставив нулёвки на другие площадки. Поэтому, если планируете использовать I2C, лучше выбирайте такой, где на плате выведен только I2C, будет меньше возни с платой, не имеющей практически никакой документации (документация только на чип). Данное исполнение работает от 5В, на плате стоит регулятор на 3.3В, требуемых для контроллера. Встречал отзывы, что в каких-то исполнениях его может не быть.

Работой дисплея в целом доволен. Заметил только одну неприятную особенность — яркость пиксельной строки зависит от того, сколько в ней зажжено пикселей. Чем больше зажжено, тем меньше яркость. Может бросаться в глаза контраст между строками, если на экране чередуются полностью заполненные области с какими-нибудь узкими элементами. Но на практике на моих картинках этого не видно и в глаза не бросается.

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

Часто практикуется подход, при котором память дисплея дублируется в RAM MCU — сначала все действия с изображением производятся в RAM, а потом все изменённые пиксели копируются в память дисплея. В данном проекте подобный подход не используется в целях экономии ресурсов. Все изменённые места перерисовываются сразу в памяти дисплея.

Как подсказали в комментариях, OLED-дисплеи со временем выгорают. Я об этом тоже подозревал (помня, что такое screen saver), и предусмотрел отключение дисплея по прошествии нескольких минут после последней активности на органах управления. Включается при повороте или нажатии валкодера.

В коде работа с дисплеем реализована в классе Display.

Валкодер:



На мой взгляд, валкодер — самый лучший вариант органа управления для подобных устройств, имеющих хоть какой-то пользовательский интерфейс. Он компактен и очень удобен. Им удобно листать и выбирать пункты меню, менять значения каких-либо параметров, переключать режимы и т.п.

Для подключения требуется три входных ноги микроконтроллера. Одна — для кнопки (на ручку можно нажимать), две — для самого валкодера. С валкодера идёт сигнал кодом Грея. При каждом шаге поворота на двух линиях меняется один бит. Последовательность определяет направление вращения.

Вроде бы всё просто, но, видимо, не всегда разработчики способны сделать качественную поддержку подобного устройства. К примеру, на моём 3Д-принтере стоит плата RAMPS и подключена плата с дисплеем и точно таким же валкодером. Прошивка Marlin с ним работает, но впечатления от использования очень нехорошие — нет ощущения надёжности — когда при повороте ручке происходит щелчок, в интерфейсе зачастую остановка происходит не на том пункте меню или значении параметра, на котором ожидалось. При быстром вращении, создаётся ощущение, что щелчки пропускаются. В какой-то момент, переключения начинают происходить не во время щелчков, а где-то между ними, очень неприятно. Да что там Marlin, у меня на встроенной мультимедиа-системе в машине иногда такие же ощущения. В связи с этим несколько советов (ну и, конечно, смотрите в код в окрестностях класса RotEnc).

Во-первых, достаточно очевидный пункт для всех, кто подключает какие-либо кнопки к микроконтроллеру — нужно бороться с дребезгом. Данный валкодер механический и его сигнальные линии — это, по сути, те же кнопки, и на них тоже есть дребезг. Сначала фильтруем дребезг, потом уже обрабатываем последовательности состояний сигнальных линий. Могут быть валкодеры с оптическими датчиками, там уже зависит от схем обработки сигнала с них. Если напрямую выведены ноги какого-нибудь фототранзистора, то может дребезжать и там при медленном вращении, а вот если есть какая-нибудь схема обработки, вводящая гистерезис, то программное подавление не требуется. Но такие устройства стоят дороже и в любительских устройствах редко используются, самые распространённые — это механические, по несколько баксов за кучку.

Во-вторых, несколько менее очевидный пункт, наверняка один из тех, на которых погорел Marlin — у ручки при вращении есть устойчивые положения — щелчки(клики). У данной модели на каждый щелчёк происходит четыре шага кодовой последовательности. Так вот, реагировать надо на щелчки, а не на шаги последовательности. Причём самое важное — синхронизироваться с устойчивыми положениями. Многие просто вводят константу STEPS_PER_CLICK, и, к примеру реагируют на каждый четвёртый шаг. Но проблема в том, что сигнал не идеален, последовательности могут быть не совсем правильными. При определённом написании код может «сбиться со счёта», в результате каждый четвёртый шаг будет получаться где-нибудь посередине щелчка, что будет некомфортно для пользователя. При этом устойчивому положению ручки у конкретной модели соответствует фиксированное значение кода, к нему и надо привязываться.

В-третьих, опять же достаточно очевидный пункт для более-менее опытных разработчиков микроконтроллерных систем — используйте аппаратные прерывания на изменение состояния входных линий. Как минимум, будет меньше риск «потерять» шаги последовательности. Ну а вообще, как известно, прерывания — наше всё. MCU должен по возможности спать, просыпаясь только по прерываниям — либо от внешней периферии, либо от таймера для выполнения отложенной задачи. Таковы принципы хорошего дизайна системной архитектуры.

Конструкция в целом


Выполнена из подручных материалов и различных деталей, напечатанных на 3д-принтере из ABS.

Принцип работы сифона проиллюстрирован в видео выше. У меня он представляет собой внешнюю трубку из ПВХ, и внутреннюю трубку с воронкой на конце. Для классического сифона требуется ещё колено, но у меня его конструктивно уже было сделать сложно. Когда всё-таки обнаружились проблемы со сливом, на стенку нижнего бака прилепил ещё небольшую ванночку, в которую погружается конец внутренней трубки, и создающую сопротивление сливу, чтобы мог сработать сифон.

Оказалось, что ABS — очень гидрофобный материал. Через него вода буквально не переливается, пришлось даже переделывать воронку сифона. Стоит учитывать это свойство, создать какие-то миниатюрные гидротехнические системы с ним не получится (к примеру, я хотел на воронке сифона сделать направляющие поверхности, для закручивания воды, чтобы улучшить срабатывание сифона. Но при таких размерах и гидрофобности ABS это не имеет смысла).

Ещё пытался сначала всё это склеивать клеевым пистолетом с горячим клеем. Не работает — сначала казалось, что всё намертво держится, но через несколько дней само отваливалось. Лучший вариант — ЦА. Детали даже под водой держаться намертво.

Самый большой просчёт в конструкции — прозрачные ёмкости. Совсем забыл о том факте, что вода на свету цветёт. Пришлось обматывать непрозрачным материалом. Ну и можно периодически добавлять марганцовку для дезинфекции, растениям это вроде не вредит.

Алгоритм затопления следующий — сначала насос включается на небольшой мощности и тихо заполняет всю верхнюю ёмкость. Процесс отслеживается датчиком уровня. Когда вода начинает переливаться через воронку сифона, снижение уровня в нижнем баке останавливается, что и фиксируется датчиком. Небольшого потока, создаваемого на малой мощности, недостаточно для срабатывания сифона. Насос останавливается, объём, закаченный в верхний бак, запоминается. Корни выдерживаются в растворе несколько минут, после чего насос включается опять. Сначала на небольшой мощности, пока вода опять не дойдёт до воронки (за время простоя она опускается ниже, из-за эффекта сифона), а когда фиксируется достижение уровня воронки, насос включается на повышенную мощность, обеспечивая поток, достаточный для срабатывания сифона. Поток через сифон гарантированно выше потока насоса, в результате уровень в нижнем баке начинает повышаться, это фиксируется датчиком и насос останавливается.

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


А где же C++11?



Возможно, кто-то усомнится, что от C++11 может быть польза при программировании микроконтроллеров (из числа тех, кто вообще в курсе, что микроконтроллеры можно программировать на C++). Попробую привести конкретные примеры пользы от C++11 в этой сфере (помимо очевидных приятных мелочей типа constexpr, override, default и пр.).

Размещение строковых ресурсов

Многие знают, что RAM в микроконтроллерах — очень ограниченный ресурс. Это может стать проблемой, если ваше приложение, к примеру, имеет пользовательский интерфейс, и ваша программа использует достаточно большое количество строк. Если в коде написать что-нибудь типа
PromptUser("Are you sure you want to format SD-card?");

то строка, переданная в аргументах, будет размещена в секции инициализированных данных (здесь и далее речь о поведении компилятора GCC для платформы AVR) — то есть в области RAM, которая при старте (до вызова функции main) инициализируется из программной флеш-памяти. Функции PromptUser() будет передан указатель на нужное место в RAM. Если использовать подобный подход по всей программе, то RAM довольно быстро закончится (в использованном в данном проекте ATMEGA328P его всего 2 килобайта, а это ещё для BSS, кучи и стека). Чтобы обойти это ограничение, функции типа PromptUser() учат работать не с указателями на RAM, а с указателями на область в программной флеш-памяти. Читать оттуда можно только с помощью специальных инструкций, которые, к примеру, в avr-libc завёрнуты в функции семейства eeprom_read_[byte|word|dword|...].
При этом строку надо предварительно поместить в переменную, снабжённую атрибутом PROGMEM, который говорит компилятору, что её следует размещать в программной памяти.
char prompt[] PROGMEM = "Are you sure you want to format SD-card?";
PromptUser(prompt);

Здесь возникает неудобство, если вы хотите все строки объявить централизованно. Тогда вам придётся сначала в заголовочном файле объявить их декларацию:
extern char prompt[] PROGMEM;

А в отдельном .cpp файле дать определение:
char prompt[] PROGMEM = "Are you sure you want to format SD-card?";

Дублирование кода, что не есть хорошо, и очень неудобно, когда таких строк много. Да, это можно обойти, сделав хитрый макро, и включив заголовочный файл в отдельный .cpp файл, в котором макро раскроется в определение, тогда как в остальных контекстах он будет раскрываться в декларацию. Но с C++11 есть вариант почище, если использовать инициализацию членов класса при декларации. В заголовочном файле декларируем класс со строками:
#define DEF_STR(__name, __text)     const char __name[sizeof(__text)] = __text;

class Strings {
public:
    DEF_STR(Prompt, "Are you sure you want to format SD-card?")
    DEF_STR(OtherString, "...")
    …
} __attribute__((packed));

extern const Strings strings PROGMEM;

В .cpp файле:
const Strings strings PROGMEM;

Теперь все строки объявлены в одном месте, размещаются в программной памяти, и обращаться к ним можно так:
PromptUser(strings.prompt);

В данном проекте основанный на том же принципе подход используется и для определения битмапов — различных картинок, выводимых на графический дисплей.
/** Bitmap descriptor. */
struct Bitmap {
    /** Pointer to data array if in data memory. Offset of data array relatively
     * to Bitmaps class instance start address if in program memory.
     */
    const u8 *data;
    /** Number of pages in the bitmap. */
    u8 numPages,
    /** Number of columns in the bitmap. */
       numColumns;
} __PACKED;

template<u8... data>
constexpr static u8
Bitmap_NumDataBytes()
{
    return sizeof...(data);
}

/** Define bitmap.
 * @param __name Name for accessing.
 * @param __numPages Number of pages in the bitmap. Number of columns defined as
 *      total number of data bytes divided by number of pages.
 * @param __VA_ARGS__ Data bytes.
 */
#define DEF_BITMAP(__name, __numPages, ...)     const u8 __CONCAT(__name, __data__)         [Bitmap_NumDataBytes<__VA_ARGS__>()] = { __VA_ARGS__ };     const Bitmap __name {         reinterpret_cast<const u8 *>(OFFSETOF(Bitmaps, __CONCAT(__name, __data__))),         __numPages,         sizeof(__CONCAT(__name, __data__)) / __numPages};

/** Global bitmaps repository. Stored in program memory. */
class Bitmaps {
public:

    /** Thermometer icon. */
    DEF_BITMAP(Thermometer, 1,
        0b01101010,
        0b10011110,
        0b10000001,
        0b10011110,
        0b01101010
    )

    /** Sun icon. */
    DEF_BITMAP(Sun, 1,
        0b00100100,
        0b00011000,
        0b10100101,
        0b01000010,
        0b01000010,
        0b10100101,
        0b00011000,
        0b00100100
    )
    ...
};
extern const Bitmaps bitmaps PROGMEM;

Отличие в том, что помимо самих данных изображения требуется разместить и атрибуты (размеры изображения). Каждый байт определяет столбец из восьми пикселей. Столбцы могут заполнять одну или более строк, их число указывается вторым параметром после имени. Получается, что высота битмапов должна быть кратна восьми при произвольной ширине, что вполне допустимо для данного проекта.

Двоичные литералы

Возможно, вы уже обратили внимание, что битмапы в предыдущем примере используют двоичные литералы для определения. Это действительно очень удобно — редактировать простенькие битмапы можно прямо в коде, особенно, если редактор позволяет подсветить единички. К примеру, определения символов шрифта в файле font.h:



Variadic templates

Куда ж без них то. Ну, к примеру, команды для контроллера дисплея могут иметь длину от одного до нескольких байт. Отправляются таким кодом:

SendCommand(Command::DISPLAY_ON);
SendCommand(Command::SET_COM_PINS, COM_PINS | COM_PINS_ALTERNATIVE);
SendCommand(Command::SET_COLUMN_ADDRESS, curVp.minCol, curVp.maxCol);

Удобно, не правда ли?
    /** Queue command sending.
     * @param bytes Up to MAX_CMD_SIZE bytes of command data.
     */
    template <typename... TByte>
    void
    SendCommand(TByte... bytes)
    {
        cmdSize = sizeof...(bytes);
        controlSent = false;
        cmdInProgress = true;
        SetCmdByte(sizeof...(bytes) - 1, bytes...);
        i2cBus.RequestTransfer(DISPLAY_ADDRESS, true,
                               CommandTransferHandler);
    }

    template <typename... TByte>
    inline void
    SetCmdByte(int idx, u8 byte, TByte... bytes)
    {
        cmdBuf[idx] = byte;
        SetCmdByte(idx - 1, bytes...);
    }

    inline void
    SetCmdByte(int, u8 byte)
    {
        cmdBuf[0] = byte;
    }


В файле variant.h описан класс, отдалённо напоминающий boost::variant, используя variadic templates. Он используется для организации страниц пользовательского интерфейса. Дело опять же в экономии памяти — там, где динамическое управление памятью — непозволительная роскошь, приходиться изворачиваться (хотя 2КБ — это ещё много, можно было и не изворачиваться, но в той же линейке ATMEGA её размер доходит до 512 байт, и каждый байт на счету). В моём интерфейсе на экране в любой момент времени показывается одна страница. Соответственно для всех страниц можно использовать один и тот же кусок памяти, то, что в C называется union. Для классов в C++ это обычно называется variant. В отличие от union нам надо не забывать вызывать деструктор предыдущего содержимого, перед тем как вызвать конструктор нового.

    Variant<MainPage,
            Menu,
            LinearValueSelector,
            TimeSelector> curPage;
    ...
    /** Get type code for the specified page class. */
    template <class TPage>
    static constexpr u8
    GetPageTypeCode()
    {
        return decltype(curPage)::GetTypeCode<TPage>();
    }
...
curPage.Engage(nextPageTypeCode, page);


Для компиляции используется GCC и GNU binutils для платформы AVR (в Ubuntu есть готовый пакет gcc-avr). Выше были приведены подробности процесса сборки. Параметры компилятору выглядят примерно так (специфичные для проекта дефайны и инклуды опущены):
avr-g++ -o build/native-debug/src/firmware/cpu/lighting.cpp.o -c -fno-exceptions -fno-rtti -std=c++1y -Wall -Werror -Wextra -ggdb3 -Os -mcall-prologues -mmcu=atmega328p -fshort-wchar -fshort-enums src/firmware/cpu/lighting.cpp
Линковка:
avr-g++ -o build/native-debug/src/firmware/cpu/cpu -mmcu=atmega328p build/native-debug/src/firmware/cpu/adc.cpp.o build/native-debug/src/firmware/cpu/application.cpp.o …
Конвертация кодовой секции в hex формат:
avr-objcopy -j .text -j .data -O ihex build/native-debug/src/firmware/cpu/cpu build/native-debug/src/firmware/cpu/cpu_rom.hex
Создание образа EEPROM:
avr-objcopy -j .eeprom --change-section-lma .eeprom=0 -O ihex build/native-debug/src/firmware/cpu/cpu build/native-debug/src/firmware/cpu/cpu_eeprom.hex
Прошивка микроконтроллера:
avrdude -p atmega328p -c avrisp2 -P /dev/avrisp -U flash:w:build/native-debug/src/firmware/cpu/cpu_rom.hex:i

P.S. Уже созрели первые помидоры, и на вкус они оказались не очень. Видимо, в питании им что-то не понравилось. Наверное, придётся менять культуру.
Какой язык вы используете для программирования микроконтроллеров?

Проголосовало 358 человек. Воздержалось 158 человек.

Только зарегистрированные пользователи могут участвовать в опросе. Войдите, пожалуйста.

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


  1. Rumlin
    11.10.2015 20:21

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

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

    Система как-то реагирует на аварии — протечки?


    1. vagran
      11.10.2015 20:56

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

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

      Система как-то реагирует на аварии — протечки?

      Лень победила, и я недоделал этот момент, хотя изначально считал его очень важным. Сейчас только останавливает работу и кричит, если перед очередным циклом оказывается слишком мало воды. Остальные ситуации не обрабатываются (пока). Как я уже писал, мотивация — серьёзная штука. После первой аварии код чудесным образом допишется.


      1. Rumlin
        11.10.2015 22:31

        Не думаю, что существуют датчики умеющие отличить доли Ома, изменившиеся при погружении в жидкость на разную глубину.

        Классически это так:
        image
        ВУ — верхний уровень.
        НУ — нижний уровень.


        1. vagran
          11.10.2015 23:36

          Ну вот пример того, что я видел на eBay. Насколько я понимаю, аналоговый выход пропорционален уровню погружения.

          А доли Ома мерить не проблема, если абсолютная величина порядка долей Ома (то есть 0.1-0.5, а не 100.1-100.5). Например, включив измеряемое сопротивление в плечо моста с фиксированными сопротивлениями того же порядка, и в диагональ какой-нибудь операционный усилитель.


          1. Rumlin
            12.10.2015 09:29

            Да, судя описанию, изменение сопротивления между проводниками усиливается транзистором.

            Может быть, если поделить измеряемые значения на несколько диапазонов, для компенсации проводимости, то можно получить аналог «железного» указателя уровня.
            image


          1. Alexeyslav
            12.10.2015 10:39
            +2

            Сопротивление измерять бессмысленно — электроды могут вносить поправку из-за электрохимического потенциала… да и растворятся будут понемногу, если не позолоченные или из инертного материала.
            Лучший способ измерения уровня — емкостной. Сенсоров только много надо будет. Но их преимущество — нет непосредственного контакта с водой.


            1. vagran
              12.10.2015 10:48

              Я вот тоже не до конца понимаю, на каком принципе работает этот датчик. Возможно, там как раз-таки измеряется не сопротивление, а ЭДС, возникающая между электродами, погруженными в электролит.

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


              1. Alexeyslav
                12.10.2015 11:02

                Это фигня. Так много не намеряешь. Лучше подавать фиксированную частоту на емкость сенсора через резистор. Он образует элементарный RC фильтр низких частот, когда емкость сенсора растёт меняется частота среза фильтра и резко падает амплитуда. Но и этот способ не идеален…
                Можно просто заряжать емкость большим сопротивлением 100К-1М и измерять время необходимое на заряд емкости сенсора с нуля. Так работают практически ВСЕ реализации емкостных сенсоров и тач-панелей. Итого — нужен только один резистор на сенсор… иногда даже длительность заряда не нужно измерять — достаточно прочитать значение на порту сразу после переключения пина из состояния «выход, 0» в режим «вход» — этого достаточно чтобы зарегистрировать емкость сенсора в 10пФ, если больше — отложить операцию чтения парочкой NOP-ов.


                1. vagran
                  12.10.2015 11:11

                  Можно просто заряжать емкость большим сопротивлением 100К-1М и измерять время необходимое на заряд емкости сенсора с нуля.

                  Ну собственно, концептуально это ничем не отличается от включения того же сопротивления в задающую RC-цепь генератора. Время, нобходимое на заряд — это и есть часть периода генератора. Просто, вместо формирования импульса самому, они будут сами формироваться в генераторе.


            1. Rumlin
              12.10.2015 11:13

              В описании сенсора — полоски электродов посеребренные. Но лучше угольные электроды.


              1. Alexeyslav
                12.10.2015 11:19

                Серебро растворяется в воде, эти электроды видимо не предназначены для постоянного контакта с водой — так, поиграться только.


              1. Meklon
                12.10.2015 14:51

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

                image


                1. Rumlin
                  12.10.2015 15:50

                  Если быть точным, то дешевле всего разобрать использованные батарейки — угольный стержень бесплатно.


                  1. Alexeyslav
                    12.10.2015 16:05

                    Оно то бесплатно, но и столь же бесполезно. Сколько работы надо будет проделать, чтобы из угольного стерженька сделать электрод для измерителя уровня. А главное как их срастить с медным проводником, чтобы в последствии контакт с жидкостью имел только графит иначе всё это зря.


                    1. Rumlin
                      12.10.2015 16:07

                      У графитного стрежня припрессован металлический колпачок "+" — к нему припаять, сам колпачок с проводом и местом пайки залить, например, эпоксидкой.


                1. Alexeyslav
                  12.10.2015 16:00

                  Ты смотри, не поленились даже фиксирующие винты позолотить…


            1. Meklon
              12.10.2015 14:53
              +1

              Самая жепь это электролиз раствора. А идет он очень мощно даже при коротких импульсах. Очень. Там pH улетает и что только ни начинает плавать в растворе.


          1. k_sashka
            12.10.2015 16:59

            А почему бы не использовать пластиковую трубку с герконами и магнитом на поплавке? Просто, дешево и сердито.


            1. Alexeyslav
              12.10.2015 17:11

              Соли… всё обложат и в один прекрасный момент поплавок просто застрянет.


              1. k_sashka
                12.10.2015 19:02

                А электроды соли не не обложат?


                1. Alexeyslav
                  12.10.2015 19:21

                  Они не помешают работе электродов.


            1. vagran
              12.10.2015 17:14

              Ну не так уж просто — герконов надо много, потому что шкала трбуется с достаточно большим разрешением, чтобы как можно более точно обнаруживать, когда уровень воды начал понижаться или повышаться (да и вообще, хочется точно знать уровень). Каким-то образом всю эту кучу надо подключить к микроконтроллеру, желательно не тридцатью проводами. (Как вариант, можно сделать цепочку резисторов, и чтобы геркон или пара электродов, как предлагали выше, замыкали часть цепочки, и в итоге её общее сопротивление было пропорционально уровню жидкости. И дальше уже на АЦП мерить его.). Но всё-таки просто — это пара изолированных электродов, как в случае с ёмкостным датчиком (если, конечно, эта идея рабочая и такой датчик может выдавать надёжные показания).


              1. k_sashka
                12.10.2015 19:09

                чтобы отслеживать изменение уровня, а возможно и сам уровень, как вариант можно поставить датчик давления под сам бак, чтобы измерять изменение веса, либо положить его на дно бака :)


                1. Alexeyslav
                  12.10.2015 19:23

                  Разница уровней в 20см по жидкости… для измерения надо будет сильно бороться с помехами и влиянием температуры на датчик, да и дорогое вобщем-то выйдет решение и недостаточно точное.


                1. vagran
                  12.10.2015 19:54

                  У меня была такая идея, когда хотел делать с аквариумом. Но не под аквариум, а под такую мензурку рядом с ним, которая сообщается с ним через трубку по принципу сифона. Мне тогда казалось это простым и надёжным решением.


  1. beemaster
    11.10.2015 21:21

    .


  1. lopatoid
    11.10.2015 22:14

    Там жёлтые светодиоды? Для растений они слабо подходят.
    Смотрите тут, раздел светодиоды:
    https://ru.wikipedia.org/wiki/Искусственное_освещение_растений


    1. vagran
      11.10.2015 23:40
      +2

      Да, я об этом тоже узнал, когда уже заказал «тёплые» белые (ну как же, они ведь почти как солнце :) ). Только потом включилась логика — если листья зелёные, значит зелёный цвет они отражают, а поглощают красный и синий. Но на самом деле это вопрос эффективности. Эти жёлтые (точнее тёплые белые, фотографии не очень хорошо получились) тоже подходят, просто больше энергии тратится впустую, отражаясь от листьев.


      1. Alexeyslav
        12.10.2015 10:34

        Проблема искусственного освещения растений несколько глубже. Была статья примерно год назад по поводу системы искусственного выращивания, там прозвучали утверждения что от соотношения цветовых составляющих изменяются разные аспекты роста растения — насколько быстро оно наберёт массу, насколько интенсивный будет вкус плодов и т.д.
        Хоть красно-синее излучение и приводит к интенсивному росту растения, но плоды становятся безвкусными и водянистыми. Это хорошо для не плодоносящих: цветы, марихуана, конопля…


        1. vagran
          12.10.2015 10:42

          Насколько я понимаю, эта проблема стоит oстро, если выращиваемые растения получают только искусственное освещение, например, такие фермы. У меня же основное освещение — естесственное от окна, подсветка только для продливания светового дня, задумывалась для зимы. Не знаю, насколько вообще эта концепция рабочая, и даст ли какой-то толк, но мне кажется, что в такой ситуации спектральный состав освещения гораздо менее критичен (главное, конечно, чтоб не оказалось, что оно совсем бесполезно).


      1. Mad__Max
        13.10.2015 01:39

        «Теплые» белые светодиоды тоже относительно неплохо — у них максимум приходится на красную часть спектра, поменьше не сине-фиолетовую и меньше всего как раз на зеленую.

        Тут (слева внизу график) спектр плохеньких китайских светодиодов «теплого» класса:
        lamptest.ru/images/graph/new-energy-led-corn-bulb-12w.png
        Тут получше качеством тоже теплые (3000К)
        lamptest.ru/images/graph/camelion-led10-a60-830-e27.png

        Как ни странно чисто с точки зрения эффективности конвертации энергии — солнечный свет самый плохой вариант (т.к. в нем максимум приходится как раз на не усваиваемый растениями зеленый участок спектра). Он рулит только за счет своей огромной интенсивности — даже в облачный день интенсивность естественного солнечного света как минимум на порядок выше яркого искусственного.


  1. iliasam
    11.10.2015 22:38

    Для измерения уровня жидкости еще вот такой датчик есть, хотя он довольно дорогой — www.adafruit.com/products/463


    1. evtomax
      11.10.2015 22:48

      del


    1. vagran
      11.10.2015 23:43

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


  1. evtomax
    11.10.2015 22:49

    OLED дисплей — это красиво, но недолговечно. За год яркость постоянно светящихся пикселей заметно снизится.


    1. vagran
      11.10.2015 23:45

      У меня тоже были такие подозрения, поэтому большую часть времени дисплей отключен. Он включается, если тронуть валкодер, и горит, пока с момента последних действий не пройдёт какой-то таймаут (минут 5 вроде поставлено). Добавлю в текст этот момент.


  1. gleb_kudr
    12.10.2015 01:51

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

    Еще вариант — емкостный, но он более капризный.


    1. Gorthauer87
      12.10.2015 18:50

      А если просто сделать поплавок и соединить его вторым герметичным концом с реостатом? Он будет вверх вниз двигаться и менять сопротивление. Такую штуку можно легко собрать и достаточно точно откалибровать.


      1. vagran
        12.10.2015 19:58

        Это были первые мысли. Но есть вопросы к надёжности такого решения — любая механика увеличивает сложность и уменьшает надёжность (при этом оно может быть ещё и достаточно громоздким). Ещё из тех же соображений хочется бесконтактный вариант — реостат будет изнашиваться.


      1. gleb_kudr
        13.10.2015 00:56

        В таком случае лучше на датчиках холла. Но поплавок в любом случае и дороже и менее надежен (зарастает солями жесткости, например).


  1. Aleksandr_Sv
    12.10.2015 08:01

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


    1. Rumlin
      12.10.2015 09:36

      Книг по этому поводу издано много, например, Вахмистров Д. Растения без почвы, 1960 года. Меня в этом вопросе интересует другой вопрос — как не получить овощи с передозировкой нитратов и т.п…

      Кроме того, содержание нитратов в овощах может резко увеличиться при неправильном применении азотистых удобрений (не только минеральных, но и органических). Например, при внесении их незадолго до уборки


      1. vagran
        12.10.2015 10:33
        +1

        Я на самом деле к этому вопросу безответственно подошёл. Пока росток был маленький подлил в воду чуть-чуть обычного жидкого удобрения для помидоров. Потом, когда вода начала цвести, решил, что ничего больше добавлять не буду, только марганцовкой изредка травил живность. В итоге весь этот куст вырос практически на чистой воде и марганцовке. Ну собственно, и на вкус он получился достаточно противный. В общем агроном из меня не очень. Ну и всё-таки это первый экземляр продукта — больше фана было наблюдать, как работает электроника и изредка исправлять огрехи в прошивке, когда что-то шло не так. Когда буду сажать следующего клиента, уделю вопросу правильного питания больше внимания.


        1. Rumlin
          12.10.2015 11:16

          Кстати по поводу живности в воде geektimes.ru/post/255486

          После двух месяцев роста клубники возникли проблемы. Я заметил, что её листья становились скорее белыми, чем зелёными. Перелопатив массу информации, я выяснил, что возможная причина заключается в нехватке питательных элементов в воде, необходимых для формирования правильного роста. Какую-либо химию добавлять не хотел. Поэтому принял простое решение – увеличить количество рыб, так как больше рыб выработают больше продуктов жизнедеятельности.

          По истечении месяца положительного результата не наблюдалось, листья по-прежнему оставались бледно-зелёные. Но зато начала обильно плодоносить клубника.


          1. vagran
            12.10.2015 11:23

            Да, нечто подобное я и хотел изначально сделать. В этой статье проскочила хорошая мысль — делать не дома, а на работе :).


        1. Meklon
          12.10.2015 14:58

          Марганец вызовет отравление. Вначале на молодых листьях.

          Избыток марганца, в отличие от его недостатка проявляется чаще на кислых почвах. В результате избытка марганца в клетках растений уменьшается содержание хлорофилла, поэтому при этом симптомы будут такие же, как и при недостатке магния, т.е. начинается мезжилковый хлороз, в первую очередь со старых листьев, появляются бурые некротичные пятна. Листья сморщиваются и облетают.


      1. Meklon
        12.10.2015 14:54

        Нитратами перекормить почти нереально. Растение подохнет от передоза. Вообще выход за рамки это всегда стресс и замедление роста.


  1. joger
    12.10.2015 12:13

    а есть ли веб ресурсы(рус/англ), специализирующиеся на аква-/гидропонике «домашнего» масштаба?


    1. Rumlin
      12.10.2015 12:38

      На форумах «дачников» такие темы есть, только это сплошное имхо на любые темы, иногда с переходом на личности. Например здесь много фото урожаев можно найти.


    1. etz
      12.10.2015 14:57

      По гидропонике много, только они определённой направленности, вот например – olkpeace.ru


    1. Meklon
      12.10.2015 15:00

      http://forum.ponics.ru/. Один из лучших. Крайне рекомендую ресурсы по выращиванию альтернативных «трав». Принципы те же, но там люди упоротые в плане выхода конечного продукта на стоимость электричества/удобрений и т.п. Народ цеые заросли в холодильнике и прочем выращивает.


  1. Alexeyslav
    12.10.2015 17:15

    Кстати, чтобы не мучится со шрифтами сделал программу для их редактирования и экспорта в код. от 5x7 до 16x16.


    1. vagran
      12.10.2015 17:18

      Я брал за основу сгенерированный шрифт. Но всё-таки потом пришлось вручную подправлять много погрешностей, осматривая с лупой каждый символ.


      1. Alexeyslav
        12.10.2015 19:48

        вот собственно для этого и сделал. Можно даже весь шрифт окинуть взглядом 1:1 и увеличенное изображение символа, и основное поле с «квадратами размером с кулак».


  1. lorc
    12.10.2015 18:15

    А собственно помидорки то как? Растут? Вкусные?


    1. Meklon
      12.10.2015 19:14

      У меня вкусные росли. Томат Жёлтый малыш. Но у меня смесь кокоса и агроперлита. Капельная система.


      1. Meklon
        12.10.2015 20:07




        1. sergku1213
          13.10.2015 08:55

          А чем подсвечивали? Какие удобрения давали?


          1. Meklon
            13.10.2015 10:11

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


          1. Meklon
            13.10.2015 10:17
            +1

            Самый ступор был, когда у меня грибы в кокосе выросли.
            image
            А ещё перцы отлично растут в кокосе.

            image


            1. Alexeyslav
              13.10.2015 11:19

              Класс… с грибами кстати можно было и таймлапс снять… они относительно быстро растут.


              1. Meklon
                13.10.2015 11:29

                Они там надолго поселились) постоянно лезут. Мицелий же жив


    1. vagran
      12.10.2015 19:33

      Ну я там написал в конце, что не очень получились. Как пишут в комментариях — подбор раствора целая наука, скорей всего из-за этого. Я же на первом поселенце вообще эту тему не изучал и на авось этот вопрос оставил, больше уделяя внимание на наблюдение за работой электроники и периодическое исправление недочётов в прошивке. Следующему поколению уделю больше внимания в вопросах питания.


  1. Mirn
    12.10.2015 18:27

    Спасибо за статью, очень интересно.
    Очень не хватает схемы установки гидропоники. На словах непонятно как работает всё в целом, и понятно только по отдельности.

    И ещё Вы делаете в опросе такой упор на ассемблер. Почему?


    1. vagran
      12.10.2015 19:51

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

      Видео


      1. Mirn
        12.10.2015 20:04

        спасибо, вроде яснее стало, это видео куда удачнее первого.

        а насчёт ассемблера:
        Я пишу под МК на базе кортексов М0… М4. Там все такие особенности уже реализованы в библиотеке CMSIS от арм консорциума и прочих подобных.
        За 5 лет ни разу не приходилось использовать асм ни в одном проекте что для кортексов писал все эти года. Потому что либо всё сделано уже до меня в либах и очень хорошо сделано. Обычно производительности не хватает не 5-10%, а не хватает в 5-10 раз, и тут надо либо менять МК либо ставить ПЛИС либо пересматривать хотелки и ТЗ в сторону упрощения или вообще менять идею целиком. А компилятору я доверяю больше чем моим умениям вылизывать инструкции: местами может и выйдет быстро если на асме написать, но в целом современные компиляторы существенно лучше.
        Поэтому такое сочетание С++11 и упора на асм вызвало удивление: обычно любят что то одно.


        1. vagran
          12.10.2015 20:18

          Добавлю видео в текст.

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


  1. Hertz
    12.10.2015 19:14

    Если уж C++11, то зачем макросы, можно

    static constexpr auto &&
    
    .
    И удостовериться, что во всех translation unit'ах используется один и тот же экземпляр.


    1. vagran
      12.10.2015 20:00

      Не совсем уловил мысль. Можете привести более полный пример?


      1. Hertz
        12.10.2015 20:11

        Пардон, static вам, видимо, не подходит из-за необходимости добавить аттрибут PROGMEM.
        Я предлагал завести тип со статическими полями, объявленными примерно так:

        struct Strings {
            static constexpr auto&& Prompt /*PROGMEM*/ = "Are you sure you want to format SD-card?";
        };
        

        но видимо
        static
        и
        PROGMEM
        не сочетаемы, так как по сути оба являются storage спецификаторами.


        1. vagran
          12.10.2015 20:51

          PROGMEM — это атрибут GCC, раскрывается в "__attribute__((__progmem__))". Со static он не конфликтует. Вот это компилируется.

          struct Strings {
              static constexpr auto&& Prompt PROGMEM = "Are you sure you want to format SD-card?";
          };
          

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


  1. vagran
    12.10.2015 19:59

    del


  1. jee
    14.10.2015 13:52

    Вопрос по ЛУТ.
    Со стороны меди у вас плохо перенесена краска, со стороны шелкографии — наоборот. Какой бумагой пользуетесь?
    Я со стороны меди использую фотомумагу для струйников Lomond, результат отличный.
    Со стороны шелкографии обычно кальку, потому что её не надо отмывать — результат так себе, размазано немного.
    Грею просто утюгом через газету, без конфорки.


    1. vagran
      14.10.2015 14:10

      В этом проекте попробовал какую-то китайскую с eBay специально для ЛУТа. Думаю, что не очень хороший результат связан с неправильной температурой для моего тонера или бумаги, неравномерным проглаживанием или ещё какими-нибудь тонкостями процесса. Я платы довольно редко делаю, ещё не отработал эту технологию. В принципе уже привык ретушировать маркером после перевода.


      1. jee
        14.10.2015 14:21

        Спасибо. Как она отмывается? Прилипает или легко отходит?


        1. vagran
          14.10.2015 14:34

          Отходит сама, но, как видите, частично с краской в моём случае.


  1. jee
    14.10.2015 14:25

    А насчёт RTC — внутрениий таймер по моему опыту (небольшому) использовать получается очень криво. Из-за обилия других прерываний он очень сильно уходит. Поэтому думаю что внешние RTC очень даж верное решение.


    1. vagran
      14.10.2015 14:44

      Ну по моему опыту, когда таймеров хватает, вполне сносно работает (если не нужно большой точности, но до показателя пару секунд за несколько месяцев, ему, конечно, далеко). Другие прерывания влиять не должны — прерывание от таймера (я использовал 16-битный таймер с верхним значением, удобным для ровного деления) довольно редкие, пропустить их сложно (они ведь в очередь аппартно ставятся), если код написан правильно. Как-то так для 20МГц:

      /** TOP value for clock counter. */
      #define CLOCK_MAX_VALUE     62499
      
      /** Called on each second. */
      static inline void
      OnClockSecond()
      {
          g_clockSec++;
          if (g_clockSec < 60) {
              return;
          }
          g_clockSec = 0;
          g_clockMin++;
          if (g_clockMin >= 60) {
              g_clockMin = 0;
              g_clockHour++;
              if (g_clockHour >= 24) {
                  g_clockHour = 0;
                  g_clockDow++;
                  if (g_clockDow >= 7) {
                      g_clockDow = 0;
                  }
              }
          }
          OnClockMinute();
      }
      
      /** Clock ticks occur with TICK_FREQ frequency. */
      static inline void
      OnClockTick()
      {
          static u8 divisor = TICK_FREQ;
      
          g_clockTicks++;
          divisor--;
          if (divisor == 0) {
              divisor = TICK_FREQ;
              OnClockSecond();
          }
          ProcessTaskQueue();
      }
      
      ISR(TIM1_COMPA_vect)
      {
          OnClockTick();
      }
      
      static inline void
      ClockInit()
      {
          /* CLK / 8
           * WGM = 0100 (CTC)
           */
          TCCR1B = _BV(CS11) | _BV(WGM12);
          OCR1A = CLOCK_MAX_VALUE;
          TIMSK1 = _BV(OCIE1A);
      }
      

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


      1. vagran
        14.10.2015 14:48

        /** Clock ticks frequency. */
        #define TICK_FREQ       40
        

        20000000/8/62500


      1. jee
        14.10.2015 14:52

        Если прерываний много и МК находится в другом прерывании (а там обычно ставят запрет прерываний) то прерывания от таймера не будет. Я вот это имел ввиду. То есть будут пропуски отсчёта времени.
        Если же прерывание одно, то и тут будут сдвиги — из-за многотактовых команд. Т.е. сначала довыполняется текущая команда, потом уже прерывание, а не сразу.


        1. jee
          14.10.2015 14:55

          Не сразу заметил — про очередь прерываний. Они точно ставятся в очередь?


          1. jee
            14.10.2015 14:58

            Я не прав, вот какая задержка будет:

            При входе в прерывание флаг Global Interrupt Enable автоматически сбрасывается, запрещая дальнейшие прерывания. Если он будет явно установлен в программе обработки прерывания, то новый запрос с наивысшим приоритетом прервет текущий обработчик. Приоритет прерывания уже запущенного обработчика при этом не имеет значения: если одновременно выставлены запросы INT0 и INT1 и оба они разрешены, начнет обрабатываться INT0; если далее в обработчике INT0 будет установлен флаг Global Interrupt Enable, то текущий обработчик будет вытеснен обработчиком INT1, а по его завершении будет продолжен (если кто-то еще снова не вытеснит).

            Точно так же уже запущенный обработчик INT1 будет прерван более приоритетным INT0 лишь в том случае, если сам установит Global Interrupt Enable. Приоритет важен лишь в момент выбора одного прерывания из нескольких одновременных запросов.

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


            1. vagran
              14.10.2015 15:45

              Очередь работает так — каждое прерывание выставляет свой специфичный флаг в регистрах МК (INT0F, например). Далее соответствующий хандлер аппаратно вызывается, как только будет одновременно выставлен этот флаг и GIE (в порядке приоритетов). Получается, что если в это время работал другой хандлер, то как только он выйдет, GIE будет выставлен и следующий хандлер вызовется (с задержкой в одну инструкцию).

              Время реакции на прерывание в данном случае не критично — нам ведь главное ничего не пропустить, чтоб со временем не сбивалось время. Задержка в обработке (latency), это уже другая история, там, где это действительно важно. А пропустить можно, только если МК постоянно сидит в обработке более приоритетных прерываний — а это уже перегрузка и надо что-то менять в дизайне, так ваше приложение нормально работать не сможет.

              Ещё стоит помнить, что обработчики прерываний обычно делают по возможности очень короткими, использую подход т.н. software interrupt — непосредственно обработчик только выставляет флаг, по которому уже делаются какие-то действия в главном цикле, вне контекста прерывания.


              1. jee
                14.10.2015 16:04

                Всё верно. Там ещё есть тонкость с асинхронным таймером, у меня был он на отдельном кварце.


                1. vagran
                  14.10.2015 16:16

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


                  1. jee
                    14.10.2015 16:54

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


                    1. Alexeyslav
                      14.10.2015 21:46

                      На точность отсчёта времени это никак не влияет. Таймер как тепловоз — идёт себе независимо ни от чего, досчитает до переполнения и выставит признак прерывания — когда его контроллер обработает его не волнует абсолютно и отсчет при этом НЕ останавливается, следующий «флажок» будет выставлен в строго назначенное время, незвисимо от того насколько быстро и своевременно отработал обработчик прерывания, да хоть прямо перед следующим прерыванием…
                      Очень важны только две вещи: 1) не трогать ни коим образом работающий таймер, 2) обработать прерывание ДО прихода следующего.


                      1. jee
                        15.10.2015 00:01

                        Не согласен. Вы можете пропустить событие прерывания если у вас их несколько (от разных источников). Флажок-то будет выставлен, только он может быть несколько раз выставлен.


                        1. vagran
                          15.10.2015 07:38

                          Ну так и я, и предыдущий комментатор писали:

                          2) обработать прерывание ДО прихода следующего.

                          В моём примере кода периодичность прерывания 40Гц, времени на обработку более, чем достаточно, если МК не перегружен, а про это я тоже говорил, перегруженный МК — неправильный дизайн — либо код, либо не хватает вычислительной мощности для выполняемой задачи и надо использовать более производительный МК.


                          1. jee
                            15.10.2015 12:14

                            Следующего того же прерывания или вообще любого? Я понял что такого же.
                            ОК.


                            1. vagran
                              15.10.2015 12:17

                              Да, до прихода следующего такого же, у каждого прерывания есть свой специфичный аппаратный флаг. Другие прерывания не затрут ничего, связанного с данным.


                              1. jee
                                15.10.2015 12:22

                                До при хода любого прерывания. Иначе отсчёт времени будет нарушен.


                                1. Alexeyslav
                                  15.10.2015 13:31

                                  И как он будет нарушен?
                                  Единственная ситуация когда прерывание не будет обработано до возникновения следующего это постоянно срабатывающее прерывание более высокого приоритета до того как оно будет обработано. Например, прерывание по уровню на порту, и если этот уровень держать постоянно, то флаг прерывания от порта будет висеть постоянно, контроллер не выйдет с обработки этого прерывания пока не уберёшь уровень по которому оно срабатывает. Но допущения таких ситуаций — это проблемы с дизайном…


                                  1. jee
                                    15.10.2015 14:17

                                    Обработка часового прерывания будет не в момент отсчёта времени, а позже. Статистически скажем за час будет одинаковое число обработок, только сам момент срабатывания будет гулять.
                                    Это если предположить что нет обработчика какого-то любого прерывания, отрабатывающего медленнее, чем частота часового таймера. В этом случае будут пропуски.


                                    1. vagran
                                      15.10.2015 14:39

                                      Статистически скажем за час будет одинаковое число обработок, только сам момент срабатывания будет гулять.

                                      Ну про это речь выше была. Для учёта времени маловажна задержка обработки. Важно как раз общее количество за единицу времени — именно тогда время со временем никуда не уйдёт (если точность кварца позволяет).

                                      Это если предположить что нет обработчика какого-то любого прерывания, отрабатывающего медленнее, чем частота часового таймера.

                                      Это и есть ошибка дизайна. Опять же, как сказано выше, обработчики должны быть как можно более короткими, длинные операции надо выносить за пределы контекста прерываний, например, устанвливая в обработчике только флаг, и в главном цикле проверяя его и выполняя работу (подразумевая, что обработка может длиться несколько прерываний и быть к этому готовым).


                                      1. jee
                                        15.10.2015 14:57

                                        Ясно. Спасибо за ответы!