В процессе автоматизации домашнего хозяйства было обнаружено, что имеющийся в наличии счетчик расхода газа ВК-G4 обладает интересной особенностью: его в младший разряд встроен магнит, который может замыкать геркон, устанавливаемый снаружи самого устройства (т.е. для его подключения не требуется разрешения от газовой компании). Это даже указано в паспорте на сам счетчик. Правда там рекомендуется использовать «НЧ генератор импульсов IN-Z 61», но на самом деле это просто геркон с креплением на счетчик за невменяемую цену. Поэтому вместо IN-Z 61 было решено использовать самый дешевый датчик Холла с цифровым выходом (т.е. со встроенным триггером Шмитта).

Из имеющегося в наличии был взят датчик Холла типа SS441A. В соответствии с datasheet на SS44xA в третьей цифре кодируется его магнитная чуствительность, которая обуславливает физическое расположение датчика на газовом счетчике.
В качестве управляющей системы у меня используется одноплатный компьютер Banana PI, работающий под управлением ОС Linux (vanilla kernel 4.2+). Физическое подключение SS44xA очень простое:
вывод (-) подключаем к общему проводу;
вывод (+) подключаем к +5V (а не к +3.3V);
вывод (D) подключаем к порту GPIO и подтягиваем через резистор 4.7 кОм на +3.3V.
Но каково же было мое удивление, когда я не смог обнаружить kernel in-tree drivers, способных просто подсчитывать кол-во импульсов на заданном порту GPIO! Я понимаю, что Linux — это не ОС реального времени, но просто считать низкочастотные импульсы… Неужели только у меня возникла такая задача?
Внимательно посмотрев последние исходники ядра, было обнаружено два промежуточных решения:
  1. Использовать штатный драйвер UIO. Если такое устройство открыть как файл в прикладной программе и записать в него соответствующее значение, то последующая операция чтения из него будет приостановлена до появления прерывания, вызванного изменением уровня сигнала на соответствующем GPIO;
  2. Использовать штатный драйвер gpio_keys. При помощи него можно объявить GPIO в качестве «кнопки» (button) или «переключателя» (switch), и отлавливать в прикладной программе события, связанные с изменением их состояния.

Использование любого из этих решений потребует наличия прикладной программы-демона, которая должна быть активна для выполнения подсчета импульсов. Это не лучшее решение, так как в случае ее завершения по какой-либо причине мы можем пропустить некоторое количество импульсов, что для целей учета достаточно критично. Поэтому, для минимизации рисков, было принято решение написать собственный драйвер устройства, который бы работал непосредственно на уровне ядра.
Итак, встречайте: драйвер для подсчета импульсов на произвольной линии GPIO, конфигурируемый при помощи технологии Device Tree.

Preconditions
  • Используемое ядро Linux версии не ниже 4.x
  • Заголовочные файлы ядра, использованные при его сборке (обычно расположены в /usr/include/linux на целевой системе)
  • Средства для компиляции модулей на целевой системе либо средства для кросс-компиляции
  • Исходный или двоичный файл Device Tree для используемой аппаратной платформы
  • Компилятор Device Tree в двоичный формат (программа dtc)

Для своей работы я использую сборку от Armbian, причем на их же сайте можно взять и исходники ядра, на основе которых и была подготовлена сборка. Но, в принципе, нет никаких ограничений на целевую сборку быть не должно.
Сборку внешнего модуля я здесь не описываю (а надо? в принципе ресурсов с таким описанием достаточно много), поэтому считаем, что у вас уже есть готовые модули counters.ko gpio-pulse.ko, собранные под ваше ядро. Дальнейший процесс я описываю на примере Banana PI, но по аналогии его можно перенести и на любую другую платформу.

Открываем табличку с описанием разъемов на плате. Нас интересует разъем CON3 (GPIO Headers). Выбираем любой понравившийся нам контакт, и определяем его функционал (например мне понравился контакт 12 на разъеме CON3, на который выведен порт сокета PH2). Сверяемся с Allwinner A20 datasheet (таблица GPIO multiplexing function) — выбранный порт должен поддерживать генерацию прерываний (в моем случае это EINT2 в столбце Multi 6). Дальше нам нужно определить номер pin с точки зрения GPIO, которому соответствует выбранный порт (PH2). Мне проще было определить это непосредственно на рабочем устройстве:

# grep '(PH2)' /sys/kernel/debug/pinctrl/1c20800.pinctrl/pinmux-pins
pin 226 (PH2): (MUX UNCLAIMED) (GPIO UNCLAIMED)

заодно и убедился, что этот порт в данный момент ничем не используется (MUX и GPIO UNCLAIMED).

Теперь можно создавать Device Tree configuration. Примеры на некоторые устройства имеются в исходных текстах ядра Linux в папке arch/arm/boot/dts, для Banana PI файл называется sun7i-a20-bananapi.dts
В нем мы производим следующие изменения:
/ {
        model = "Banana Pi BPI-M1";
        compatible = "sinovoip,bpi-m1", "allwinner,sun7i-a20";

...

        counters {
                compatible = "gpio-pulse-counter";
                gas-meter@0 {
                    label = "Gas meter";
                    pinctrl-names = "default";
                    pinctrl-0 = <&ext_counter_bananapi>;
                    /* CON3, pin 12: PH2 - pin 226 (Multi6 function: EINT2) */
                    /* bank: 226 / 32 = 7, pin into the bank 226 % 32 = 2 */
                    gpios = <&pio 7 2 GPIO_ACTIVE_LOW>;
                    interrupt-parent = <&pio>;
                    interrupt-names = "counter-edge-falling";
                    interrupts = <2 IRQ_TYPE_EDGE_FALLING>; /* PH2 / EINT2 */
                };
        };

&pio {
        ...

        /* External counter */
        ext_counter_bananapi: counter_pins@0 {
                allwinner,pins = "PH2";
                allwinner,function = "gpio_in";
                allwinner,drive = <SUN4I_PINCTRL_10_MA>;
                allwinner,pull = <SUN4I_PINCTRL_NO_PULL>;
        };
};


Параметр gpios в node расчитывается следующим образом:
  • Сначала идет ссылка на метку pio;
  • Далее идет номер банка, в котором содержится искомый порт GPIO. Для Allwinner A20 в каждом банке содержатся 32 порта, поэтому номер банка определяется как целая часть от деления GPIO pin на 32;
  • Далее идет номер pin внутри банка. Т.к. в каждом банке по 32 pin, то это значение вычисляется как остаток от деления GPIO pin на 32;
  • Последним параметром идет указание, какой уровень сигнала считать активным

Параметр interrupts в node расчитывается следующим образом:
  • Первым параметром указывается номер прерывания у контроллера GPIO (для EINT2 это будет 2)
  • Вторым параметром указываем IRQ_TYPE_EDGE_FALLING, разрешающей генерацию прерывания при переходе сигнала с высокого уровня в низкий (т.к. датчик у нас с открытым коллектором и подтянут к +VCC)

Выполняем компиляцию измененного файла Device Tree:
dtc -I dts -O dtb sun7i-a20-bananapi.dts > sun7i-a20-bananapi.dtb

Полученным sun7i-a20-bananapi.dtb перезаписываем файл в /boot/dtb/sun7i-a20-bananapi.dtb
Модули ядра counters.ko gpio-pulse.ko записываем в любое место внутри /lib/modules/$(uname -r)/kernel/drivers и загружаем целевую систему. На загруженной целевой системе даем команду
depmod -a

и снова выполняем перезагрузку. После этого смотрим вывод команды dmesg:
# dmesg
...
[    4.745570] counters: Class driver loaded.
[    4.749235] gpio_pulse: Device #0 gas-meter: IRQ: 53 GPIO: 226
...

Отлично, модули загружены и работоспособны. Проверяем функционал сначала программным путем:
# cat /sys/class/counters/counter0/values/count
0
# echo 1 > /sys/class/counters/counter0/values/pulse
# cat /sys/class/counters/counter0/values/count
1
# echo 1 > /sys/class/counters/counter0/values/pulse
# echo 1 > /sys/class/counters/counter0/values/pulse
# cat /sys/class/counters/counter0/values/count
3

(это мы имитировали сигнал программными средствами).

Теперь подключаем датчик Холла и убеждаемся в его работоспособности путем поднесения к нему какого-нибудь магнитика (например, от магнитной наклейки на холодильник).

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


  1. vchslv13
    12.11.2015 17:53

    Простите, но разве эта статья не для Хабра больше подходит?


    1. Vedga
      12.11.2015 18:05

      Так вроде больше о hardware статья. Ну да, не обзорная, скорее howto получился. По идее подключения сенсора, по созданию device tree конфигурации по физическим исходным разъемам. Если статья все-таки не по теме, то перенесу ее на хабр.


      1. vchslv13
        12.11.2015 18:35

        Ох, проблема в том, что я тоже не особо опытный член сообщества и не могу сказать Вам точно, для ГТ эта статья или нет. Просто я всегда предполагал, что это место для новостей и обзоров, а не технических статей (Таймс для гиков, всё же :) ). Спросите лучше у кого-то из более опытных пользователей в личке.


  1. Frantony
    13.11.2015 00:50

    Попробуйте двинуть этот драйвер в mainline, см. www.kernel.org/doc/Documentation/SubmittingPatches


    1. Vedga
      13.11.2015 13:04

      Можно попробовать. Только исходники придется привести в соответствие с linux code guedelines.


  1. ZigFisher
    13.11.2015 02:42
    +1

    Достаточно интересное решение, спасибо за информацию.

    Будучи то-же роутерным маньяком, решил для себя вопрос несколько другим путём — установил RTC PCF8583 с интерфейсом I2C. Счётчик накапливает импульсы, даже когда отключают электричество, т.к. подключен к нему небольшой аккумулятор. Съём данных по шине I2C через свободные GPIO или usb-tiny-i2c переходник не вызывает каких-либо затруднений.

    Так-же эту задачу можно реализовать и на популярном нынче ESP8266, стоит копейки, работает достаточно стабильно.

    Однако в планах есть пойти еще дальше — собрать всё в самодельном, чуть более удлинённом корпусе а-ля IN-Z 61. Поставить туда датчик холла, nRF24LE1 и небольшой аккум или хорошие батарейки. Работать будет автономно пол-года. Проверено.
    Ну а принимать данные от nRF на том-же роутере или ESP, подключив к ним такой-же nRF трансивер.


    1. Vedga
      13.11.2015 13:03

      О, спасибо за идею, тоже подумаю, из чего можно сделать автономный счетчик. Единственное, что смущает — SS441A в режиме покоя от 5V кушает 5mA. Многовато будет для батарейки…