Хабрахабр

Делаем контроллер для умного дома

Делаем контроллер для умного дома и не только.

В этой я опишу разработку контроллера, который отвечает за опрос датчиков и модулей ввода-вывода. В предыдущей статье я описывал разработку системы в целом. Во-первых, это интересно, во вторых, как ни странно, нет OpenSource решения для подобного контроллера, покрывающего как программную так и аппаратную часть. «Зачем изобретать велосипед?» — спросите вы. Статья ориентирована на людей немного разбирающихся как в электронике так и в embedded linux development.

Но в реалии всё чуть-чуть ещё сложнее, вот во что это вылилось для меня, но вы в принципе правы:
1. Сделать контроллер, скажите вы, это же так сложно — нужно делать плату, писать софт, печатать корпус. аппаратная часть контроллера

— выбор cpu платы для контроллера
— выбор IO контроллера
— выбор блока питания
— структурная схема контроллера
— разработка кросс платы для контроллера
— разработка плат для RS-485 модулей
— производство плат

софт для контроллера 2.

— выбор системы сборки для ядра linux и rootfs
— структура разделов SD карты
— выбор бутлоадера и загрузка нужного rootfs
— изменения в device tree
— выбор системы сбора дебажных трейсов
— написание билдсистемы
— написание коммуникационного ядра
— написание mqtt гейтвея (дискретные/аналоговые точки контроллера -> топики mqtt)
— написание парсера гуглтаблицы и построение конфигурационного json файла для гейтвея
— написание point monitor для доступа к точкам контроллера
— монтирование readonly файловой системы

корпус контроллера 3.

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

Пару слов о аппаратной части.

Остальные же пользуются плодами трудов других людей, так быстрее и проще. Наверно только самые отчаянные сейчас берут отдельно процессор, память, флеш, контроллер питания, еще пару сот компонентов и начинают лепить это всё вместе. Мне нужно было много последовательных портов и желательно что бы плата поддерживала -40°C до +85°C, поэтому выбор пал на BeagleBone Black(BBB). Нужно всего лишь открыть браузер и написать «одноплатный компьютер» и весь остаток дня провести в выборе нужного. 54, что удобно для макетирования и разработки кросс платы. Также на ВВВ вся периферия выведена на два разъёма PBD по 46 пинов с шагом 2. Также именно кросс плату нужно крепить к корпусу и на ней стоят разъемы для подключения питания и кабеля RS485. Кросс плата нужна что бы объединить все компоненты на одной плате, у меня это — cpu плата, блок питания, IO контроллер, и платы каналов RS485.

Я его заложил на плату, и успешно им пока не пользуюсь. Так, с cpu платой разобрались, следующее что надо решить — нужно ли на кросс плату ставить Input/Output(IO) контроллер или можно и без него. Единственное что он делает это откладывает старт BBB на 1с после подачи питания и обслуживает кнопку reset.

На ЖКИ не обращайте внимание, он есть на плате, но поддержку я не реализовал. Блок питания для контроллера я взял уже готовый MeanWell NSD10-12S5, разрабатывать его для единичного девайса — бессмысленная затея, просто подобрал его по потреблению и всё.

Пару слов о платах каналов RS485.

Так что туда можно поставить любой тип канала который нужно, RS485, CAN, Zigbee модуль… На кросс плату выведены 4 последовательных интерфейса BBB.

Почему не использовать управление приёмо-передачей с ВВВ, да потому что TI официально перестали поддерживать строб для RS485 в драйвере serial устройства. Мне нужны были каналы RS485, так что я сделал только их, они с автоматическим управлением приемом/передачей и с гальванической развязкой. Сделав канал самостробирующий, можно его будет ставить на любую плату, например на RaspberyPi, где никогда и не было такой поддержки, если была, то поправьте меня. Можно найти патч к драйверу, можно дописать самому, но зачем? Строб для драйвера rs485 формируется на attiny10, дешево и сердито.

Возвращаемся к софту.

Выбор системы сборки для ядра linux и rootfs.

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

  1. почитать что написано здесь habr.com/ru/post/448638
  2. make clean
  3. make beaglebone_defconfig
  4. make

Вот и всё. Теперь всё необходимое для запуска платы, лежит в папке /buildroot/output/images.

Выглядит всё очень просто и не интересно, так что можно сделать немного посложнее:

  1. интегрировать buildroot в свою билдсистему, скачивать его скриптом, не забыв использовать стабильный тэг, а не брать последний develop
  2. написать свой defconfig и перед сборкой buildroot подкидывать его скриптом в папку /buildroot/configs, не забываем что все дефконфиги должны заканчиваться на *_defconfig, иначе buildroot его не видит
  3. копировать свой post-build.sh в board/beaglebone/post-build.sh
  4. сделать prepare скрипт, который будет делать п1, п2 и п3 за вас

Как результат buildroot будет генерировать zImage и rootfs.tar

Выбор структуры разделов SD карты:

На этом, думаю, не стоит много акцентировать внимание.
Я сделал 4 раздела BOOT / ROOT_1 / ROOT_2 / DATA.
В BOOT разделе хранится всё необходимое для начальной загрузки: MLO, barebox.bin, barebox.env, am335x-boneblack.dtb, zImage, boot.txt.

ниже). ROOT_1 и ROOT_2 содержат rootfs, выбор которого вписан в файле boot.txt(см. DATA содержит проектные конфиги, при изменении которых нет необходимости пересобирать код. Все эти разделы монтируются как readonly во избежания крешев файловой системы при выключении питания.

Эта компонента будет перезаписывать один из разделов ROOT_1/ROOT_2, который сейчас не используется, а потом просто менять файл boot.txt, если не нужно менять ядро. Такая структура разделов в будущем позволит легко написать software update компоненту.

Выбор бутлоадера.

Сначала я использовал, как все, U-Boot который генерирует BuildRoot. У меня было много экспериментов с бутлоадерами для BBB. Потом, я подумал что не плохо бы было стартануть систему быстро, секунды за 2-3, и подпилив X-Loader так что бы он грузил ядро, у меня это получилось, но опять же остался вопрос с конфигурированием, да и время старта для меня не критично(система на systemd грузится сама по себе медленно, даже если удалить всё не нужное). Но мне он не понравился, может быть, конечно, это дело привычки, но мне показалось что это перебор, уж очень он тяжелый и сложно конфигурируемый.

В конце концов я остановился на barebox, его простота мне очень понравилась, плюс на сайте есть вся документация (www.barebox.org).

Например, что-бы грузить rootfs c первого или второго раздела нужно всего лишь:

на boot разделе сделать файл boot.txt который экспортит переменную типа «export BOOT_NUM=Х» 1.

сделать два скрипта /env/boot/sdb1 /env/boot/sdb2 в которых описать параметры загрузки, например: 2.

echo "botting with mmcblk0p2 as rootfs..." global.bootm.image=/boot/zImage global.bootm.oftree=/boot/am335x-boneblack.dtb global.linux.bootargs.console="console=ttyO0,115200" global.linux.bootargs.debug="earlyprintk ignore_loglevel" global.linux.bootargs.base="root=/dev/mmcblk0p2 ro rootfstype=ext4 rootwait"

3. сделать скрипт /env/boot/sd в котором в зависимости от BOOT_NUM стартовать sdb1 или sdb2 скрипт

установить переменную boot.default 4.

nv boot.default=sd saveenv

5. далее меняя BOOT_NUM в boot.txt мы будем загружать rootfs с первого или второго раздела, что в будущем можно использовать для software update.

Изменения в device tree.

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

Правильно бы было сделать свой патч для файла am335x-bone-common.dtsi из пакета билдрута и применять его каждый раз перед его сборкой, но лень победила, и я просто вытащил все нужные файлы, поменял всё что нужно и сбилдил руками.

это делается один раз, то можно и так: Т.к.

Создаем папку с файлами необходимые для сборки: 1.

am335x-bone-common.dtsi am335x-boneblack-common.dtsi am335x-boneblack.dts am33xx-clocks.dtsi am33xx.dtsi am33xx.h gpio.h omap.h tps65217.dtsi

2. В файле am335x-bone-common.dtsi нужно правильно настроить пины и заэнейблить драйверы портов:

uart1_pins: pinmux_uart1_pins ; uart2_pins: pinmux_uart2_pins { pinctrl-single,pins = < AM33XX_IOPAD(0x950, PIN_INPUT_PULLUP | MUX_MODE1) AM33XX_IOPAD(0x954, PIN_OUTPUT_PULLDOWN | MUX_MODE1) >; }; uart4_pins: pinmux_uart4_pins { pinctrl-single,pins = < AM33XX_IOPAD(0x870, PIN_INPUT_PULLUP | MUX_MODE6) AM33XX_IOPAD(0x874, PIN_OUTPUT_PULLDOWN | MUX_MODE6) >; }; uart5_pins: pinmux_uart5_pins { pinctrl-single,pins = < AM33XX_IOPAD(0x8C4, PIN_INPUT_PULLUP | MUX_MODE4) AM33XX_IOPAD(0x8C0, PIN_OUTPUT_PULLDOWN | MUX_MODE4) >; }; &uart1 { pinctrl-names = "default"; pinctrl-0 = <&uart1_pins>; status = "okay"; }; &uart2 { pinctrl-names = "default"; pinctrl-0 = <&uart2_pins>; status = "okay"; }; &uart4 { pinctrl-names = "default"; pinctrl-0 = <&uart4_pins>; status = "okay"; }; &uart5 { pinctrl-names = "default"; pinctrl-0 = <&uart5_pins>; status = "okay"; };

3. Далее немного магии, и готовый файл am335x-boneblack.dtb лежит в этом же каталоге:

a. sudo apt-get install device-tree-compiler

b. запускаем препроцессор:

cpp -Wp,-MD,am335x-boneblack.dtb.d.pre.tmp -nostdinc -Iinclude -Isrc -Itestcase-data -undef -D__DTS__ -x assembler-with-cpp -o am335x-boneblack.dtb.dts.tmp am335x-boneblack.dts

c. запускаем сам компилятор:

dtc -O dtb -o am335x-boneblack.dtb -b 0 -i src -d am335x-boneblack.dtb.d.dtc.tmp am335x-boneblack.dtb.dts.tmp

4. am335x-boneblack.dtb нужно положить на boot раздел рядом с ядром и в скрипте запуска для barebox добавить следующую строчку — "global.bootm.oftree=/boot/am335x-boneblack.dtb"

Выбор системы сбора дебажных трейсов.

Очень удобно если эти трейсы не выводятся просто в консоль, а собираются чем-то специально для этого созданным, да так что бы можно было сортировать их по процессам, применять фильтры и т.д. Как известно систем без багов не бывает, как и анализа многопоточной системы без трейсов. Это DLT, если вы никогда не слышали об этом, то это не беда, все пробелы в знаниях можно легко покрыть прочитав at.projects.genivi.org/wiki/display/PROJ/Diagnostic+Log+and+Trace.
Данная система состоит из dlt-daemon и dlt-viewer. И я как раз знаю одну неплохую систему, которую легко собрать как под host так и под target. Плюс к этому всему, к своему бинарнику, с которого мы хотим собирать трейсы, нужно прилинковать dlt либу. Как и понятно из названия dlt-daemon запускается на таргете, а dlt-viewer на хосте.

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

Написание билдсистемы.

Но повторить такой трюк через месяц будет сложнее, а через два — так вообще невозможно. Зачем писать билдсистему, ведь можно всё скачать с репозиториев, сбилдить руками, собрать на основе этого rootfs и вуалля, контроллер работает. Поэтому потратив немало времени сначала, вы экономите его потом, плюс вы получаете возможность удобно билдить под host и target. Опять придётся вспоминать что, куда положить, что сбилдить и как запустить. А уж потом можно запускать билд вашего проекта. Билдсистема состоит из набора скриптов, которые сначала подготавливают host для билда, скачивают сторонние компоненты, такие как buildroot, mosquitto, DLT daemon, с их репозиториев, билдят их, раскладывают по своим местам. Если билд под host, сделать не сложно, то с билдом под таргет всегда нужно повозиться, и лучше что бы это делал скрипт.

Это даёт отличную возможность вам положить туда всё то, что вам нужно. Buildroot можно сконфигурировать так что бы он вызывал post-build скрипт после того как он сформирует rootfs, которая будет лежать в buildroot/output/target. И тогда, образ файловой системы уже будет содержать всё необходимое для запуска вашей системы.

Рецепт примерно такой:

  1. надо скопировать свои бинарники куда-то в buildroot/output/target, например в /opt/bin
  2. если есть конфиги, то с ними сделать то же самое, только в /opt/etc
  3. скопировать сторонние бинарники, для меня это mosquitto, DLT daemon, их либы и конфиги
  4. Что бы при загрузке контроллера система стартанула сама — нужно скопировать ваши systemd сервисы, лучше их объединить в свой target и заэнэйблить его сделав симлинку в multi-user.
  5. скопировать модифицированный fstab(зачем, расскажу позже)

После этого вам достаточно распаковать buildroot/output/images/rootfs.tar на нужный раздел SD карты и включить питание.

build git repo: https://github.com/azhigaylo/build

Написание коммуникационного ядра.

Концепция этого стара как сам modbus.

У контроллера, в свою очередь, есть массивы дискретных(статус и byte значение) и аналоговых точек(статус и float значение), в которых он и хранит состояние всех параметров. У каждого устройства ввода-вывода в modbus сети есть регистры(16 bit), доступные на чтение, чтение/запись, в которых хранятся данные и через которые идет управление этими устройствами.

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

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

Также я решил разделить логические устройства на две группы:

  1. Стандартные (модули Овен дискретного ввода/вывода), для которых заранее известны номера modbus регистров с данными, и достаточно только определить точки контроллера куда сохранить эти данные.
  2. Пользовательские устройства, для них надо самостоятельно описывать маппинг modbus регистров на точки контроллера.

Из всего выше сказанного логично иметь какой-то конфигуратор для контроллера, будь то просто json конфиг или самописная тула генерирующая бинарный конфиг, подходит что угодно. У меня второй вариант, потому что были идеи писать коммуникационное ядро так, что бы его можно было легко запускать не только на линукс борде но и на Ардуине с FreeRtos, меняя PAL уровень в софте.

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

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

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

core git repo: https://github.com/azhigaylo/homebrain_core

Написание mqtt гейтвея.

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

параметров у меня довольно много, то постоянно возникали путаницы в файле конфигурации гейтвея, где был прописан маппинг точек контроллера на mqtt топики гейтвея. Т.к. Помогла гугл таблица, и написание csv парсера этой таблицы в конфигурационный json файл для гейтвея.

gateway git repo
parser git repo

Написание point monitor.

У меня довольно-таки туго с UI так что кое-как смог накидать приложение на QML, оно со скрипом заработало, можно считать точку, можно её записать, а мне большего и не надо. Иногда очень полезно посмотреть что же сейчас происходит с точками контроллера, для этого я написал небольшое приложение который подключается непосредственно к коммуникационному ядру и считывает состояние дискретных и аналоговых точек.

pointmonitor git repo: https://github.com/azhigaylo/pointmonitor

Монтирование readonly файловой системы.

Это рано или поздно приводит к крешу любой, даже самой устойчивой файловой системы. Обычно этому мало кто уделяет внимание, и даже в продакшен проектах можно встретить устройства в которых партишен с rootfs доступен на запись. контроллер может быть выключен в любой время, то это лишь вопрос времени/случая когда это произойдет. Т.к. В fstab, во-первых, надо примонтировать файловую систему как readonly а во вторых всё что может меняться замапить в tmpfs. Что бы минимизировать эту вероятность надо немного повозиться с fstab и перед сборкой образа rootfs, положить его туда, как было описано выше.

Мой fstab таков, у вас он может отличатся:

/dev/root / auto ro 0 1
tmpfs /tmp tmpfs nodev,nosuid,size=50M 0 0
tmpfs /srv tmpfs nodev,size=50M 0 0
tmpfs /var/log tmpfs defaults,noatime,size=50M 0 0
tmpfs /var/tmp tmpfs defaults,noatime,size=50M 0 0
tmpfs /var/run tmpfs defaults,noatime,size=50M 0 0
tmpfs /var/lib tmpfs defaults,noatime,size=10M 0 0

Корпус контроллера.

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

Рисуем в FreeCAD, генерим gcode в Cura и получаем корпус, не забыв сделать посадочные места под плату, вырезы под разъёмы и охлаждение и закладные для клипс на din рейку.

Берем напильник(не шучу) и соединяем всё вместе, подключаем питание, кабели RS485 и всё начинает работать. Ну вот и всё, теперь у нас есть плата, софт на SD карте и корпус. А вы говорили сложно, сложно…

Теги
Показать больше

Похожие статьи

Добавить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *

Кнопка «Наверх»
Закрыть