В процессе автоматизации домашнего хозяйства было обнаружено, что имеющийся в наличии счетчик расхода газа ВК-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 — это не ОС реального времени, но просто считать низкочастотные импульсы… Неужели только у меня возникла такая задача?
Внимательно посмотрев последние исходники ядра, было обнаружено два промежуточных решения:
- Использовать штатный драйвер UIO. Если такое устройство открыть как файл в прикладной программе и записать в него соответствующее значение, то последующая операция чтения из него будет приостановлена до появления прерывания, вызванного изменением уровня сигнала на соответствующем GPIO;
- Использовать штатный драйвер 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
(это мы имитировали сигнал программными средствами).
Теперь подключаем датчик Холла и убеждаемся в его работоспособности путем поднесения к нему какого-нибудь магнитика (например, от магнитной наклейки на холодильник).
Автор: Vedga