Метеостанция не на Arduino, или Работа с таймерами и прерываниями GPIO в OpenWRT
Привет, Хабр, давно не виделись! Сначала — несколько важных новостей о проекте микрокомпьютеров Black Swift, а потом перейдём к основной теме: как на микрокомпьютер с OpenWRT сделать полноценным встраиваемым устройством.
Полноценный встраиваемый компьютер, на мой взгляд, невозможен без двух вещей:
Поддержка прерываний по разным поводам, включая сигналы на входах GPIO Поддержка аппаратных таймеров с хорошим разрешением Без этого значительная часть реальных задач автоматизации либо не решается вообще, либо решается так, что лучше бы не решалась вообще. Исключение составляют только самые банальные — реле подёргать, светодиодиком помигать, пару кнопок отследить. Всё, что требует точных таймингов и/или более-менее быстрой реакции на внешние события — попросту нереализуемо без выноса соответствующего функционала во внешний контроллер, который вышеуказанные вещи умеет, что бессмысленно усложняет конструкцию устройства.
Тем удивительнее, что при достаточном количестве микрокомпьютеров на SoC Atheros AR9331 и более дешёвых Ralink RT5350, позиционируемых именно как встраиваемые решения, с поддержкой в OpenWRT именно этих двух функций всё крайне печально. Здесь, конечно, возникает вопрос, кому эти микрокомпьютеры при таком уровне поддержки нужны —, но оставим его висеть в воздухе.
Впрочем, если посмотреть на неё же по TCP/IP, то может сойти и для здорового человека.
Но сначала — новости:
Первая партия Black Swift — в России и уже рассылается по рублёвым предзаказам (то есть сделанным не на Kickstarter). Если вы при предзаказе указывали доставку — ждите, если самовывоз — звоните в офис и подъезжайте. Если вы не оформляли предзаказ, но хотите купить Black Swift — звоните в офис и подъезжайте, у нас довольно много плат сверх объёма предзаказов. Оформить доставку можно, но тогда вы по понятным причинам встанете в хвост очереди предзаказов. Сертификация FCC скоро завершится, судя по всему — успешно. Удовлетворение бэкеров с Kickstarter, как и обещалось, ожидается в июне. Разослать отдельно русскоязычным или каким-либо ещё отдельным бэкерам платы мы не можем по техническим особенностям Kickstarter. Потихоньку пополняется документация, из-за нехватки времени — в первую очередь англоязычная. В частности, образы виртуальных машин с OpenWRT SDK и Eclipse — вот тут (через пару дней обновлю их на свежую версию SDK с нашими патчами). Полезны для всех, кто хочет что-то писать под OpenWRT, но не очень понимает, как. Ну, а теперь — к теме.
ТеорияВ Atheros AR9331 есть две вещи, весьма важные для встраиваемого компьютера — это аппаратные таймеры (четыре штуки, тикают на частоте системной шины, по достижении нуля генерируют прерывание) и прерывания на ножках GPIO (умеют срабатывать по обоим фронтам сигнала).К сожалению, на программном уровне в OpenWRT нет поддержки ни того, ни другого — то есть, если вы захотите сделать отсчёт каких-то интервалов или эмулировать какие-то протоколы, то udelay () вам в руки и надежду на то, что в многозадачной среде никто не влезет в середину эмуляции вашего протокола — на шею. Для поддержки GPIO IRQ есть широко известный в узкой среде патч, который решает только часть проблемы: вам мало получить прерывание, надо его ещё обработать и вывести в юзерспейс, где живёт уже ваша собственная программа. Ну и, конечно, чтобы патч наложить, вам потребуется самостоятельно собрать всю прошивку — занятие небыстрое и небезгеморройное.
Для поддержки таймеров нет ничего.
Точнее, до недавнего момента не было.
Модуль ядра, ловящий прерывания от GPIO и передающий их сигналом в юзерспейс — тут: github.com/blackswift/gpio-irq-handler
Работает просто: вам надо в псевдофайл /sys/kernel/debug/gpio-irq записать строку »+ GPIO PID», где GPIO — номер нужной ножки, PID — ваш процесс, в который надо слать сигнал. Потом поднять обработчик сигнала номер 42 и стойко ждать. Снять прерывание — »- GPIO PID» в тот же файл.
Модуль ядра, реализующий поддержку аппаратных таймеров — здесь: github.com/blackswift/timer-irq-handler
Работает не сложнее: сначала в /sys/kernel/debug/timer-irq пишем »+ TIMER TICK», где TIMER — номер таймера (от 0 до 3, системой не занят ни один, так что остаётся следить между своими приложениями, чтобы они не пытались за один таймер хвататься вдвоём), TICK — его разрешение в микросекундах (каждый тик будет генерировать прерывание, так что меньше 20 мкс лучше не ставьте). Потом туда же — »+TIMER TIMEOUT PID», где TIMEOUT — период, через который вы хотите получать сигнал, а PID — ваш процесс. Номер сигнала — 43. Если хотите, чтобы таймер сработал только один раз — добавьте в конце слово «once». Если не хотите — он будет работать, пока вы в вышеуказанный файл не скажете »- TIMER».
Модули ядра со всей очевидностью будут работать только на AR9331, так как у других чипов — другая адресация регистров. Более того, для работы GPIO IRQ вам потребуется прошивка с вышеупомянутым патчем, и на данный момент «из коробки» вы получаете такую прошивку только на Black Swift.
Практика Полученным знанием надо пользоваться, то есть — собрать что-то полезное с таймерами и прерываниями. У меня это будет метеостанция (см. КДПВ). Почему? Во-первых, понятная всем вещь, во-вторых, распространённый датчик DHT22 — та ещё заноза в заднице. У него похожий на 1-Wire протокол, только если в 1-Wire синхронизация есть, то здесь хост один раз притягивает ножку данных к «земле», а дальше датчик сам оттарабанивает на выходе 41 импульс в ответ.»0» и »1» отличаются длительностями импульсов — 28 мкс против 70 мкс.
Довольно неприятная для декодирования вещь: вам надо высчитать длину приходящих коротких импульсов. Без прерываний решается, но стабильность работы вам, прямо скажем, не понравится. Если хотите, я дам вам программную реализацию протокола, она у меня есть. Попробуете.
С прерываниями дело упрощается радикально: без особого труда можно написать модуль ядра, который ловит прерывание на нужной ножке, отсчитывает от него 35 мкс банальным udelay () и измеряет уровень на этой же ножке. Если там »1» — значит, сигнал длиннее 35 мкс, то есть, собственно,»1». Собственно, всё. Если вам написать свой модуль ядра чуть сложнее, чем нам, то вот исходники: github.com/blackswift/gpio-dht-handler
Поймав данные с датчика, модуль выдаёт их либо в вывод ядра (смотреть командой logread), либо сигналом 44 в приложение по указанному PID. При ошибке CRC в консоль приедет «Error», в приложение — 0.
Теперь осталось написать вокруг этого собственно приложение — мы же не в лог ядра будем ходить посмотреть, надевать ли шапку.
Здесь всё оказывается столь же банально: запускаем аппаратный таймер, которые будет тикать раз в минуту (максимально возможный период тикания — UINT_MAX микросекунд, то есть чуть больше 71 минуты). От него приходит сигнал, обработчик которого посылает запрос модулю протокола DHT22 — просто пишет в псевдофайл. Ещё через десяток миллисекунд прилетает ответный сигнал от DHT22, обработчик которого, собственно, его обрабатывает — в нашем случае пишет в файл.
void irq_handler (int n, siginfo_t *info, void *unused) { //every minute counter++; dht_request_data (); }
void dht_request_data () { int fd=open (»/sys/kernel/debug/irq-dht», O_WRONLY); sprintf (buf,»%d %u», DHT_GPIO, getpid ()); // gpio-dht-handler kernel module write (fd, buf, strlen (buf) + 1); close (fd); }
void dht_handler (int n, siginfo_t *info, void *unused) { if (info→si_int == 0) // DHT protocol CRC error { if (++failcounter > 10) { printf («Error retrieving data from DHT sensor\n»); failcounter = 0; return; } usleep (3000000); // wait 3 seconds between attempts dht_request_data (); return; }
float humidity = (float)((info→si_int) >> 16)/10.0; // 2xMSB float temperature = (float)((info→si_int) & 0xFFFF)/10.0; //2xLSB
// … }
int main (int argc, char* argv[]) { int timer = TIMER_TIMER; int tick = 1000×1000; // 1 sec unsigned int timeout = 1000×1000*60; // 60 sec
if (! init_handler (timer, tick, timeout)) { printf («Error initializing timer %d\n», timer); return -1; }
struct sigaction sig; sig.sa_sigaction = dht_handler; sig.sa_flags = SA_SIGINFO | SA_NODEFER; sigaction (SIG_DHT_IRQ, &sig, NULL);
// … }
Только что мы с минимальными трудозатратами, в прямом смысле слова за полчаса на коленке, получили метеостанцию, ведущую лог температуры и влажности в текстовом файле. С той же легкостью текстовый файл можно сменить, например, на базу данных sqlite3.
Текстовый файл — это лучше команды logread, но пока не совсем то, что хочется иметь дома более-менее здоровому человеку. Надо бы выводить данные красиво, с графиками. Рисование графиков в C с выводом в PNG-файл — задача достаточно нетривиальная, наверное, вы не только не хотите решать её голыми руками, но и искать умеющие это библиотеки. К счастью, так как на микрокомпьютере у нас бегает вполне полноценный Linux, то ничего этого делать не надо: ставим пакет gnuplot и далее просто скармливаем ему наши данные, сопроводив их несложным скриптом, указывающим, где какая ось и какой масштабчик поставить.
set terminal png medium size 600,300 set format x » set key below set xrange [60:0] set yrange [0:100] set grid ytics lc rgb »#bbbbbb» lw 1 lt 0 set grid xtics lc rgb »#bbbbbb» lw 1 lt 0 set output »/www/light/img/last_60m.png» plot »-» using 0:1 title «Temperature» with lines,»-» using 0:1 title «Humidity» with lines
Опять же, как мы будем эту температуру узнавать? Да элементарно: ставим веб-сервер (например, opkg install lighttpd — поставит веб-сервер, соответственно, lighttpd, примечательный тем, что он ещё и PHP умеет, если надо) и делаем HTML-страничку, подсасывающую наши картинки, а заодно текущие данные в цифрах. Если вам не надо выдерживать хаброэффект, то можно всё сделать на PHP, если нужно — текущие данные можно менять в статическом HTML-файле известным всяком линуксоиду (и способным привести в ужас остальных) редактором sed, дёргая его из той же нашей софтины по мере прихода новых данных.
SR_TIME=«class=\«time\»>.* $3 .* $1\°C $2%
cat /www/light/index.html | sed -e s,»$SR_TIME»,»$RP_TIME», | sed -e s,»$SR_TEMP»,»$RP_TEMP»,
Что ещё осталось? Ну, наверное, иногда полезно посмотреть глазами, что там вообще происходит-то. Берём веб-камеру, втыкаем через переходник USB-OTG в Black Swift и с помощью утилитки fswebcam выводим с неё раз в минуту картинку. Заходим браузером по ссылке и смотрим.
Ссылка? Вот ссылка: http://files.black-swift.com:9000/
Порт пробрасывается напрямую на Black Swift, поэтому всё, что вы видите по ссылке, крутится исключительно на нём.
Также, говорят, последнее время модно выводить данные о том, сколько у тебя на балконе градусов, на сервис narodmon.ru. Честно говоря, никогда им не пользовался, но выяснилось, что это не очень сложно — копируем из документации пример на PHP, записываем его в файл /root/narodmon.php на Black Swift и далее раз в 15 минут дёргаем его из нашей софтины командой /usr/bin/php-cgi /root/narodmon.php, благо PHP у нас тоже есть и готов к использованию. Ну и параметры не забываем передать, конечно.
Здесь читатель может удивиться — зачем нужно такое разнообразие языков и утилит? Не лучше ли писать всё месяц на ассемблере неделю на чистом C, а потом восхищаться красотой? Нет, не лучше. Прелесть полноценного линукса на встраиваемом компьютере — в частности в том, что результат вы можете получить за считанные часы, пользуясь уже известными утилитами и языками, а не устраивать полноценный НИОКР на неделю без сна и отдыха.
Базовое знание C, знакомство с популярными юниксовыми утилитами — и готово. Хотите PHP — вот, пожалуйста. Хотите в базу данных — получите, распишитесь. Хотите на ассмеблере — тоже можно, но очень дорого.
Nothing works and nobody knows why Theory is when you know everything but nothing works. Practice is everything works but no one knows why. In our laboratory, theory and practice are combined: nothing works and no one knows why.
Впрочем, чтобы не перехвалить самих себя, отмечу — пока что не всё работает так, как хотелось бы.
Если вы прогуляетесь по нашему гитхабу, то без труда найдёте там альтернативный модуль для работы с DHT22, который внутри сделан совсем красиво — он в прямом смысле слова измеряет длительность приходящих импульсов. В микросекундах. И довольно точно — я не проверял его на калиброванном генераторе, но приходящее с DHT22 отличается от указанного в даташите на него не более чем на 3–4 мкс.
За одним исключением.
В глубинах OpenWRT есть довольно злой обработчик прерываний USB, который на время своей работы маскирует все остальные прерывания —, а работает он долго, запросто может уйти в себя микросекунд на 30–40. В результате часть прерываний от GPIO в нашем случае мы можем просто пропустить, в лучшем случае — получить с большой задержкой. Увы, в дебрях ассемблера и копирайтов 1999 года мы пока окончательно не разобрались, поэтому модуль, работающий по двум прерываниям на импульс, приходится отложить в сторону — ловить первое прерывание и далее отмерять от него 35 мкс в таких условиях оказывается значительно надёжнее.
Вот тут он скушал два прерывания и подвинул третье:
Кроме того, при активной работе USB (например, по нему идёт поток с веб-камеры) такие задержки случаются настолько часто, что и модуль, которому одного прерывания достаточно, оказывается под угрозой. Впрочем, в чуть менее критичных вещах, где задержки в пару десятков микросекунд несущественны, вы этого не заметите —, но всё равно неприятно.
Если у вас есть что сказать по этому поводу, мы с интересом послушаем.
В следующих сериях А вы знаете, что в OpenWRT нет работающего способа полностью отключить вывод консоли в UART?
Да, это ещё одна весёлая ловушка: какие бы параметры и в какие файлы вы бы ни писали, при загрузке платы в UART высыпается гора мусора; доступными пользователю параметрами определяется только размер этой горы — и он всегда ненулевой.
На Black Swift для полного отключения всех консолей от UART достаточно дать команду «fw_setenv silent 1» и перезагрузиться. Но это — другая история.
P.S. Завтра на Skolkovo Startup Village приедет не только Д.А. Медведев, но и я — в частности, вот на это мероприятие. Если хотите пообщаться по темам краудфандинга и/или (за рамками мероприятия) Black Swift — подходите.