#1 2023-06-26 18:15:16

Honey
Учасник
З Київ
Зареєстрований: 2020-09-26
Повідомлень: 433

Модуль розширення портів + Linux

Є гарна новина для користувачів такого модуля розширення портів:
pinout.jpg
Написав для нього драйвер під Linux.

Нагадаю, що вміє модуль розширення портів:

  • 5шт GPIO з режимами: input, input_pullup, low, high

  • Активація тривоги при появі заданого логічного рівня на GPIO

  • Додаткова функція АЦП на 4-ох портах

  • Активація тривоги при виході результату АЦП за діапазон

  • Додаткова функція ШІМ на 1-ому порту

  • Конвертер 1-wire в SPI-master

В Linux є підходящі підсистеми: gpio, industrialio, pwm та шина SPI, реалізував можливість їх окремого підключення до кожного пристрою лише за необхідності під час роботи. Підтримку кожної підсистеми в драйвері пристрою також можна повністю відключити на етапі конфігурування перед компіляцією:

Device Drivers  --->
<M> Dallas's 1-wire support  --->
    1-wire Slaves  --->
        <M> DS2450 compatible Port Expander 0x20 family support
        [*]   Include 1-wire to SPI-master bridge support
        [*]   Include GPIO framework support
        [*]   Include PWM framework support
        [*]   Include Industrial IO framework support

Як бачимо, без підтримки залишились "тривоги". Хоч в підсистемі w1 і є підтримка пошуку на шині пристроїв з тривогою, але вона ніяк не задіяна, лише доступна через інтерфейс netlink для простору користувача.

Крім того за будь-яких обставин залишається доступний інтерфейс керування пристроєм через його память (опис буде нижче).

Підсистеми ядра дають уніфікований (такий же як і для інших пристроїв, ховаючи за інтерфейсом особливості пристроїв) інтерфейс до пристрою умовно трьох видів: внутрішній між драйверами, через символьний пристрій (в /dev) для програм на Сі, через файлову систему sysfs (в /sys) для доступу з командного рядка.

Розглянемо, що пропонує драйвер.
Без драйвера в sysfs модуль розширення портів виглядає так:

# ls -l /sys/devices/w1_bus_master1/20-594e480f4241
lrwxrwxrwx 1 root root    0 чер 25 15:49 driver -> ../../../bus/w1/drivers/w1_slave_driver
-r--r--r-- 1 root root 4096 чер 25 15:49 id
-r--r--r-- 1 root root 4096 чер 25 15:49 name
drwxr-xr-x 2 root root    0 чер 25 15:49 power
-rw-r--r-- 1 root root 4096 чер 25 15:49 rw
lrwxrwxrwx 1 root root    0 чер 25 15:49 subsystem -> ../../../bus/w1
-rw-r--r-- 1 root root 4096 чер 25 15:45 uevent

А з драйвером так:

# ls -l /sys/devices/w1_bus_master1/20-594e480f4241
--w------- 1 root root    0 чер 25 16:17 convert
lrwxrwxrwx 1 root root    0 чер 25 16:17 driver -> ../../../bus/w1/drivers/w1_slave_driver
--w------- 1 root root    0 чер 25 16:17 gpio_enable
-r--r--r-- 1 root root 4096 чер 25 16:17 id
--w------- 1 root root    0 чер 25 16:17 iio_enable
-rw-r--r-- 1 root root   32 чер 25 16:17 memory
-r--r--r-- 1 root root 4096 чер 25 16:17 name
drwxr-xr-x 2 root root    0 чер 25 16:17 power
--w------- 1 root root    0 чер 25 16:17 pwm_enable
--w------- 1 root root    0 чер 25 16:17 spi_bind
lrwxrwxrwx 1 root root    0 чер 25 16:17 subsystem -> ../../../bus/w1
-rw-r--r-- 1 root root 4096 чер 25 16:16 uevent

Коротко розгляну кожен новий файл.

convert
В нього потрібно записати рівно два байти для запуску АЦП, перший байт - маска каналів, другий - як заповнити комірки памяті перед запуском АЦП (детальніше в даташиті на DS2450). Наприклад так:

# echo -ne '\x0f\x00' >/sys/devices/w1_bus_master1/20-594e480f4241/convert

Результати АЦП будуть збережені в пам'яті (див. наступний абзац).

memory
Відображає 32 байти пам'яті пристрою, файл можна читати і писати, причому з довільної позиції (підтримується lseek). Ось так виглядають його нутрощі:

# hexdump -C /sys/devices/w1_bus_master1/20-594e480f4241/memory
00000000  c0 ff cc 3e bd 43 68 29  20 8c 00 8c 00 8c 00 8c  |...>.Ch) .......|
00000010  00 ff 00 ff 00 ff 00 ff  00 00 00 00 40 00 ff 00  |............@...|

Опис комірок пам'яті є тут.
Для керування пристроєм через memory і convert є бібліотека на Сі з кількома прикладами.

gpio_enable
Дозволяє запис 0 або 1 і відповідно підключає або відключає підсистему ядра gpio до відповідного модуля розширення портів.

# echo 1 >/sys/devices/w1_bus_master1/20-594e480f4241/gpio_enable
# ls -l /sys/devices/w1_bus_master1/20-594e480f4241
--w------- 1 root root    0 чер 25 18:00 convert
lrwxrwxrwx 1 root root    0 чер 25 18:10 driver -> ../../../bus/w1/drivers/w1_slave_driver
drwxr-xr-x 3 root root    0 чер 25 18:10 gpio
--w------- 1 root root    0 чер 25 18:10 gpio_enable
drwxr-xr-x 3 root root    0 чер 25 18:10 gpiochip6
-r--r--r-- 1 root root 4096 чер 25 18:10 id
--w------- 1 root root    0 чер 25 18:10 iio_enable
-rw-r--r-- 1 root root   32 чер 25 17:59 memory
-r--r--r-- 1 root root 4096 чер 25 18:10 name
drwxr-xr-x 2 root root    0 чер 25 18:10 power
--w------- 1 root root    0 чер 25 18:10 pwm_enable
--w------- 1 root root    0 чер 25 18:10 spi_bind
lrwxrwxrwx 1 root root    0 чер 25 18:10 subsystem -> ../../../bus/w1
-rw-r--r-- 1 root root 4096 чер 25 18:10 uevent
# cat /sys/devices/w1_bus_master1/20-594e480f4241/gpio/gpiochip478/base
478
# cat /sys/devices/w1_bus_master1/20-594e480f4241/gpio/gpiochip478/ngpio
5

Бачимо, що з'явився gpiochip6 з 5шт GPIO, номери яких будуть 478..482.
GPIO вже доступні через символьний пристрій:

# ls -l /dev/gpio*
crw-rw-rw- 1 root root 254, 6 чер 25 18:10 /dev/gpiochip6
lrwxrwxrwx 1 root root      9 чер 25 18:10 /dev/gpiochip_20-594e480f4241 -> gpiochip6

Можемо також експортувати один з GPIO в sysfs і поблимати на ньому світлодіодом з командного рядка:

# echo 479 >/sys/class/gpio/export
# echo high >/sys/devices/w1_bus_master1/20-594e480f4241/gpiochip6/gpio/gpio479/direction
# echo low >/sys/devices/w1_bus_master1/20-594e480f4241/gpiochip6/gpio/gpio479/direction

Робота з GPIO модуля розширення портів нічим не відрізняється від роботи з GPIO тієї ж 40-пінової гребінки в Raspberry Pi.
Ремарка:
Символьне посилання gpiochip_20-594e480f4241 створює udevd, для цого потрібно прописати правила, ось приклад таких правил. Правила також можуть автоматично записати 1 в gpio_enable при появі пристрою в системі, а інше правило зробить символьне посилання і змінить права доступу.

pwm_enable
Дозволяє запис 0 або 1 і відповідно підключає або відключає підсистему ядра pwm до відповідного модуля розширення портів.

# echo 1 >/sys/devices/w1_bus_master1/20-594e480f4241/pwm_enable
# ls -l /sys/devices/w1_bus_master1/20-594e480f4241
--w------- 1 root root    0 чер 25 18:00 convert
lrwxrwxrwx 1 root root    0 чер 25 18:58 driver -> ../../../bus/w1/drivers/w1_slave_driver
--w------- 1 root root    0 чер 25 18:58 gpio_enable
-r--r--r-- 1 root root 4096 чер 25 18:59 id
--w------- 1 root root    0 чер 25 19:22 iio_enable
-rw-r--r-- 1 root root   32 чер 25 17:59 memory
-r--r--r-- 1 root root 4096 чер 25 18:59 name
drwxr-xr-x 2 root root    0 чер 25 18:59 power
drwxr-xr-x 3 root root    0 чер 25 19:22 pwm
--w------- 1 root root    0 чер 25 19:22 pwm_enable
--w------- 1 root root    0 чер 25 18:59 spi_bind
lrwxrwxrwx 1 root root    0 чер 25 18:58 subsystem -> ../../../bus/w1
-rw-r--r-- 1 root root 4096 чер 25 18:58 uevent

Процедура експорту в sysfs трохи нагадує таку ж для gpio:

# echo 0 >/sys/devices/w1_bus_master1/20-594e480f4241/pwm/pwmchip0/export
# ls -l /sys/devices/w1_bus_master1/20-594e480f4241/pwm/pwmchip0/pwm0
-r--r--r-- 1 root root 4096 чер 25 19:29 capture
-rw-r--r-- 1 root root 4096 чер 25 19:29 duty_cycle
-rw-r--r-- 1 root root 4096 чер 25 19:29 enable
-rw-r--r-- 1 root root 4096 чер 25 19:29 period
-rw-r--r-- 1 root root 4096 чер 25 19:29 polarity
drwxr-xr-x 2 root root    0 чер 25 19:29 power
-rw-r--r-- 1 root root 4096 чер 25 19:29 uevent
# cat /sys/devices/w1_bus_master1/20-594e480f4241/pwm/pwmchip0/pwm0/period
256
# echo 1 >/sys/devices/w1_bus_master1/20-594e480f4241/pwm/pwmchip0/pwm0/enable
# echo 123 >/sys/devices/w1_bus_master1/20-594e480f4241/pwm/pwmchip0/pwm0/duty_cycle

Період 256 змінити не можна, duty_cycle можна встановити від 0 до 256 включно, якщо в enable записати 1, то буде виводитись ШІМ сигнал, якщо 0 - то пін переводиться в режим input.
В Linux відсутній символьний пристрій для pwm в /dev, тому, якщо необхідно керувати з програми, то єдиний спосіб - через memory.

iio_enable
Дозволяє запис 0 або 1 і відповідно підключає або відключає підсистему ядра industrialio до відповідного модуля розширення портів.

# echo 1 >/sys/devices/w1_bus_master1/20-594e480f4241/iio_enable
# ls -l /sys/devices/w1_bus_master1/20-594e480f4241
--w------- 1 root root    0 чер 25 18:00 convert
lrwxrwxrwx 1 root root    0 чер 25 18:58 driver -> ../../../bus/w1/drivers/w1_slave_driver
--w------- 1 root root    0 чер 25 18:58 gpio_enable
-r--r--r-- 1 root root 4096 чер 25 18:59 id
drwxr-xr-x 3 root root    0 чер 25 18:59 iio:device0
--w------- 1 root root    0 чер 25 18:59 iio_enable
-rw-r--r-- 1 root root   32 чер 25 17:59 memory
-r--r--r-- 1 root root 4096 чер 25 18:59 name
drwxr-xr-x 2 root root    0 чер 25 18:59 power
--w------- 1 root root    0 чер 25 18:59 pwm_enable
--w------- 1 root root    0 чер 25 18:59 spi_bind
lrwxrwxrwx 1 root root    0 чер 25 18:58 subsystem -> ../../../bus/w1
-rw-r--r-- 1 root root 4096 чер 25 18:58 uevent
# ls -l /sys/devices/w1_bus_master1/20-594e480f4241/iio\:device0
-r--r--r-- 1 root root 4096 чер 25 19:00 dev
-r--r--r-- 1 root root 4096 чер 25 19:00 in_voltage0_pin
-rw-r--r-- 1 root root 4096 чер 25 19:00 in_voltage0_raw
-rw-r--r-- 1 root root 4096 чер 25 19:00 in_voltage0_reference
-rw-r--r-- 1 root root 4096 чер 25 19:00 in_voltage0_resolution
-r--r--r-- 1 root root 4096 чер 25 19:00 in_voltage1_pin
-rw-r--r-- 1 root root 4096 чер 25 19:00 in_voltage1_raw
-rw-r--r-- 1 root root 4096 чер 25 19:00 in_voltage1_reference
-rw-r--r-- 1 root root 4096 чер 25 19:00 in_voltage1_resolution
-r--r--r-- 1 root root 4096 чер 25 19:00 in_voltage2_pin
-rw-r--r-- 1 root root 4096 чер 25 19:00 in_voltage2_raw
-rw-r--r-- 1 root root 4096 чер 25 19:00 in_voltage2_reference
-rw-r--r-- 1 root root 4096 чер 25 19:00 in_voltage2_resolution
-r--r--r-- 1 root root 4096 чер 25 19:00 in_voltage3_pin
-rw-r--r-- 1 root root 4096 чер 25 19:00 in_voltage3_raw
-rw-r--r-- 1 root root 4096 чер 25 19:00 in_voltage3_reference
-rw-r--r-- 1 root root 4096 чер 25 19:00 in_voltage3_resolution
-r--r--r-- 1 root root 4096 чер 25 19:00 in_voltage_reference_available
-r--r--r-- 1 root root 4096 чер 25 19:00 in_voltage_resolution_available
-rw-r--r-- 1 root root 4096 чер 25 19:00 in_voltage_scale
-r--r--r-- 1 root root 4096 чер 25 19:00 name
drwxr-xr-x 2 root root    0 чер 25 19:00 power
lrwxrwxrwx 1 root root    0 чер 25 19:00 subsystem -> ../../../../bus/iio
-rw-r--r-- 1 root root 4096 чер 25 18:59 uevent
# cat /sys/devices/w1_bus_master1/20-594e480f4241/iio\:device0/in_voltage_reference_available
vcc 1v1
# cat /sys/devices/w1_bus_master1/20-594e480f4241/iio\:device0/in_voltage_resolution_available
[1 1 16]
# cat /sys/devices/w1_bus_master1/20-594e480f4241/iio\:device0/in_voltage*_raw
65472
16164
2217
24883

Файли *_reference і *_resolution доступні для запису. З'являється також символьний пристрій в /dev.

spi_bind
Дозволяє запис у форматі "cs modalias" або "cs", перший підключає протокол-драйвер до відповідного cs (chip select), другий - звільняє cs. cs може приймати значення 0 або 1. modalias може бути протокол-драйвером шини SPI з наявних в Linux (bmp280, bme680, max31856, ...). Після запису в spi_bind протокол-драйвер через внутрішній інтерфейс ядра починає обмін даними по SPI з своїм пристроєм (якщо пристрій і драйвер сумісні, вони зрозуміють одне одного), також протокол-драйвер може підключати інші підсистеми ядра, наприклад сенсори/АЦП/ЦАП підключають industrialio.
Є один спеціальний протокол-драйвер spidev, який створює символьний пристрій в /dev, через який з'являється доступ до SPI з простору користувача:

# echo 0 spidev >/sys/devices/w1_bus_master1/20-594e480f4241/spi_bind
# ls -l /dev/spi*
crw-rw-rw- 1 root root 153, 0 чер 26 17:09 /dev/spidev0.0
lrwxrwxrwx 1 root root      9 чер 26 17:09 /dev/spidev_20-594e480f4241.0 -> spidev0.0
# echo 1 >/sys/devices/w1_bus_master1/20-594e480f4241/gpio_enable
# avrdude -c linuxspi -E noreset -P /dev/spidev_20-594e480f4241.0:/dev/gpiochip_20-594e480f4241:0 -p m328p -U sig:r:-:h
avrdude: AVR device initialized and ready to accept instructions
Reading | ################################################## | 100% 0.05s
avrdude: Device signature = 0x1e950f (probably m328p)
avrdude: reading signature memory:
Reading | ################################################## | 100% 0.05s
avrdude: writing output file "<stdout>"
0x1e,0x95,0xf
avrdude done.  Thank you.

Остання редакція Honey (2023-06-26 18:31:15)

Неактивний

#2 2023-07-31 23:07:47

Honey
Учасник
З Київ
Зареєстрований: 2020-09-26
Повідомлень: 433

Re: Модуль розширення портів + Linux

Придбав екранчик ssd1306 128x64 версію з SPI інтерфейсом щоб підключити до модуля розширення портів напряму, без Ардуіно, але виявилося, що в версії з SPI є нюанси, тому розкажу про підключення детальніше.

У екранчика є звичайні входи SPI: CS, SCK, MOSI - тут все добре, і є також входи RES і D/C - от з ними розберемося.
Одразу приведу робочу схему підключення і поясню призначення частин.
ssd1306.png
Нижні три деталі виконують "підтяжку з затримкою" для RES, щоб входи мікросхеми, які відповідають за вибір інтерфейсу обміну (I2C, SPI та ще кілька), встигли встановитись. При підтяжці одним лише резистором робота буде дуже нестабільною, в чому особисто переконався. Конденсатор робить наростання напруги на RES при включенні живлення повільним, а діод потрібен, щоб при вимкненні живлення швидко розрядити конденсатор. Номінали вказані приблизні, можна взяти резистор 4.7k або 20k, конденсатор - 1uF або 10uF. Доречі, ця схема є в версії плати з I2C інтерфейсом.

Якщо в протоколі I2C для цього екранчика першим байтом вказувався тип даних, які записуються - дані (0x40) чи команди (0x00), то в SPI цей байт не передаємо, а за вибір типу відповідає вхід D/C (data - high / command - low). Спочатку я використовував схему без верхнього діода, P0 (на схемі CS0) через підсистему gpio керував станом D/C, а весь обмін був через spi.1 (CS1 керує сигналом CS). Схема робоча, але виявилась незручною, тоді я придумав красивіше рішення: spi.1 зробити для надсилання даних, а spi.0 - для надсилання команд. Для цього CS0 і CS1 повинні мати змогу незалежно один від одного понижувати рівень одного й того ж CS, це і реалізується за допомогою верхнього діода, таблиця станів виглядає так:

       CS0 CS1 => CS  D/C
idle   1   1      1   1
spi.0  0   1      0   0
spi.1  1   0      0   1

Розділення на різні інтерфейси дозволяє не так плутатися, в якому режимі D/C зараз знаходимось, крім того можна для інтерфейсу даних окремо встановити, наприклад, порядок передачі бітів у байті (LSB замість MSB).

Для створення двох spidev виконуєм команди:

# echo 0 spidev >/sys/devices/w1_bus_master1/20-594e480f4241/spi_bind
# echo 1 spidev >/sys/devices/w1_bus_master1/20-594e480f4241/spi_bind

З'являться два пристрої:

# ls -ld /dev/spi*
crw-rw-rw- 1 root root 153, 0 Jul 31 14:21 /dev/spidev0.0
crw-rw-rw- 1 root root 153, 1 Jul 31 14:21 /dev/spidev0.1
lrwxrwxrwx 1 root root      9 Jul 31 14:21 /dev/spidev_20-594e480f4241.0 -> spidev0.0
lrwxrwxrwx 1 root root      9 Jul 31 14:21 /dev/spidev_20-594e480f4241.1 -> spidev0.1

Як ми знаємо, SPI - повнодуплексний інтерфейс, тому в Linux для роботи з файловим дескриптором spidev використовується не read/write а ioctl для одночасного запису і читання. Як виявляється, і про це сказано в документації, можна використовувати і read і/або write, але обмін буде напівдуплексним. А оскільки у екранчика і так передача лише в один бік, тому сміливо можна просто перенаправляти вивід в файл spidev (підсистема ядра зробить все, що потрібно - понизить відповідний CS, передасть дані і поверне високий рівень CS).

Ось мінімальна послідовність команд, щоб просто увімкнути екранчик (при цьому на екранчику відобразиться випадковий стан його пам'яті):

# echo -ne "\x8d\x14\xaf" >/dev/spidev_20-594e480f4241.0

Можна встановити, наприклад, вертикальний режим адресації і записати 1024 нульові байти в пам'ять:

# echo -ne "\xc8\x20\x01" >/dev/spidev_20-594e480f4241.0
# head -c 1024 /dev/zero >/dev/spidev_20-594e480f4241.1

Екранчик очиститься, це відбуватиметься зверху вниз, якщо екранчик повернутий як на малюнку на схемі (у портретному режимі, контактами зліва). Якщо бути ще точнішим, то кожен рядок пікселів заповнювався зліва направо, цей напрям був заданий додатковою командою xc8. Я так зробив навмисно, щоб порядок байтів пам'яті екранчика був таким же, як порядок байтів у растрових зображень.

Є такий підходящий формат для монохромних зображень без компресії .pbm (Portable Bitmap), який можна записувати в пам'ять екранчика як є.
В цьому форматі нульові біти відповідають білому кольору, а одиничні - чорному, тому потрібно ввімкнути інверсію (екранчик стане білим, бо в пам'яті нулі):

# echo -ne "\xa7" >/dev/spidev_20-594e480f4241.0

Є ще один нюанс: самий перший, верхній лівий піксель в форматі .pbm відповідає старшому (7-ому) біту першого байту, а в пам'яті екранчика цей самий піксель відповідає молодшому (0-му) біту першого байту, тому потрібно якимось чином змінити порядок бітів у всіх байтах зображення. Це можна зробити переконвертувавши файл, а можна зробити хитріше: SPI-приймач (екранчик) очікує байти в режимі MSB (від старшого до молодшого біта), а передавач (spidev) можна налаштувати в режим LSB (від молодшого до старшого біта). Змінимо режим spidev, який відповідає за передачу даних, за допомогою невеликої програми:

#include <fcntl.h>
#include <sys/ioctl.h>
#include <linux/spi/spidev.h>
int main(int argc, char **argv) {
  char s = 1;
  int fd = open(argv[1], O_RDWR);
  ioctl(fd, SPI_IOC_WR_LSB_FIRST, &s);
}

Компілюєм і запускаєм:

# gcc spi_lsb.c -o spi_lsb
# ./spi_lsb /dev/spidev_20-594e480f4241.1

Тепер екранчик готовий до прийому зображень.

Підготував ось таке зображення mona.pbm розміром 64x128:
mona.png
Відправляєм його на екранчик, але не весь файл, а частину після заголовків, це рівно 1024 останні байти файлу:

# tail -c 1024 mona.pbm >/dev/spidev_20-594e480f4241.1

Ось, що відобразиться на екранчику:
ssd1306.jpg

Тепер коротко мінімальний список команд:

# echo 0 spidev >/sys/devices/w1_bus_master1/20-594e480f4241/spi_bind
# echo 1 spidev >/sys/devices/w1_bus_master1/20-594e480f4241/spi_bind
# echo -ne "\x8d\x14\xa7\xc8\xaf\x20\x01" >/dev/spidev_20-594e480f4241.0
# ./spi_lsb /dev/spidev_20-594e480f4241.1
# tail -c 1024 mona.pbm >/dev/spidev_20-594e480f4241.1

Перші чотири команди можна доручити udevd виконувати автоматично щоразу при появі на шині 1-wire модуля розширення портів з підключеним екранчиком:

ACTION=="add", SUBSYSTEM=="w1", KERNEL=="20-594e480f4241",\
 ATTR{spi_bind}+="0 spidev", ATTR{spi_bind}+="1 spidev"
ACTION=="add", SUBSYSTEM=="spidev", KERNELS=="20-594e480f4241",\
 DEVPATH=="*/spi0.0/*", SYMLINK+="ssd1306_cmd", MODE="0666",\
 RUN+="/bin/sh -c '/bin/cat /usr/local/share/ssd1306_init.bin >/dev/%k'"
ACTION=="add", SUBSYSTEM=="spidev", KERNELS=="20-594e480f4241",\
 DEVPATH=="*/spi0.1/*", SYMLINK+="ssd1306_data", MODE="0666",\
 RUN+="/usr/local/bin/spi_lsb /dev/%k"

Необхідні файли створюєм один раз так:

# gcc spi_lsb.c -o /usr/local/bin/spi_lsb
# echo -ne "\x8d\x14\xa7\xc8\xaf\x20\x01" >/usr/local/share/ssd1306_init.bin

Правила створять також символьні посилання: /dev/ssd1306_cmd та /dev/ssd1306_data для відправки команд і даних відповідно.

Команди для конвертації будь-яких зображень в формат .pbm розміром 64x128:

# convert -resize 64x128^ -gravity center -crop 64x128+0+0 +repage -monochrome -dither FloydSteinberg mona_big.jpg mona1.pbm
# convert -resize 64x128^ -gravity center -crop 64x128+0+0 +repage -remap pattern:gray50 -dither FloydSteinberg mona_big.jpg mona.pbm

два варіанти на вибір.
Якщо зображення в ландшафтному форматі, то спочатку треба його повернути:

# convert -rotate 90 -resize 64x128^ -gravity center -crop 64x128+0+0 +repage -monochrome -dither FloydSteinberg landscape.jpg img.pbm

Остання редакція Honey (2023-07-31 23:31:44)

Неактивний

#3 2023-10-28 20:57:08

Honey
Учасник
З Київ
Зареєстрований: 2020-09-26
Повідомлень: 433

Re: Модуль розширення портів + Linux

Виготовляєм однодротовий метеодатчик на основі BME680

Датчик температури, вологості, тиску і якосі повітря BME680 має інтерфейси для підключення I2C та SPI, але, нажаль, ці інтерфейси не дозволяють підключати датчик на значній відстані, оскільки вони призначені для внутрішньосхемної взаємодії. Як же бути?

Скористаємось модулем розширення портів з інтерфейсом 1-wire, який, серед іншого, має функцію конвертера в SPI-master.
pinout.jpg
Інтерфейс 1-wire може працювати на відстанях до 100м, а при якісних кабелях навіть до 300м. Крім того, до однієї шини можна підключати майже необмежену кількість slave-пристроїв, кожен з яких адресується за унікальною адресою. Таким чином дальність підключення по SPI можна значно збільшити, а кількість сигнальних дротів скоротити з чотирьох (CS,SCK,MISO,MOSI) до лише одного, але ціною за це буде значне зниження швидкості обміну, яка теоретично не перевищить 8200 біт/с при повнодуплексній передачі по SPI.

Плата модуля розширення портів має невеличку макетну частину, на якій можна спаяти якусь нескладну схему. Модудь з BME680 підключається за дуже простою схемою:
BME680_Meteo_schematic.png
Діод Шотткі в схемі використаний в якості перетворювача логічних рівнів з 5В в 3.3В, оскільки для сигналу CS розробники плати цього не зробили.
Спершу паяємо "доріжки" і діод:
BME680_Meteo_front1.jpg
BME680_Meteo_back.jpg
Потім припаюємо плату з BME680:
BME680_Meteo_front2.jpg
Намагаємось розмістити дві плати якомога ближче, щоб пристрій помістився в корпус, але залишаємо проміжок між мікросхемою модуля розширення портів і платою з BME680 десь 0.5мм. Гвинтовий клемник в корпус не поміститься, замість нього можна використати, наприклад, кутовий роз'єм JST-XH 2.54мм 3-pin, а щоб розмістити його посередині навпроти отвору в корпусі, перерізаємо доріжку на платі між продубльованими отворами DQ і з одного з них за допомогою перемички робим вивід VCC.
Перед збіркою в корпус ретельно промиваєм плату спиртом від залишків флюсу, оскільки в вологому середовищі флюс поглинає вологу і може проводити струм. Плата модуля розширення портів майже ідеально поміщається в китайський корпус для датчиків.
BME680_Meteo_ready.jpg
Готовий датчик підключається трьома дротами GND,VCC,DQ. При використанні витої пари для підключення по 1-wire я завжди раджу помаранчевий призначати для VCC, зелений - для DQ, а біло-помаранчевий разом з біло-зеленим - для GND.

При підключенні до Arduino підключаєм DQ до будь-якого цифрового виходу і не забуваєм між DQ і VCC включити резистор підтяжки від 1кОм до 4.7кОм. Змінюєм в скетчі лише номер піна DQ і адресу свого модуля розширення портів. Функція bme680(addr, CS, &t, &h, &p); дозволяє читати параметри з довільного модуля розширення портів на шині 1-wire за його адресою addr, фунція повертає ненульове значення у випадку помилки, наприклад, якщо addr відсутній на шині, або до вказаного модуля розширення портів підключений не BME680.

При підключенні до Linux є відмінності лише пов'язані з тим, який майстер шини 1-wire використовується, а робота з модулем розширення портів і датчиком BME680 універсальна. Приведу приклад для Raspberry Pi і програмного майстра шини 1-wire w1-gpio, який перетворює один з виводів гребінки Raspberry Pi в вивід DQ. Оскільки цифрові виводи Raspberry Pi лише 3.3 вольтові, а в 1-wire використовується 5В, то потрібно використати стандартну схему конвертера логічних рівнів. Щоб включити w1-gpio, наприклад, на виводі 7 гребінки (це GPIO4), додаєм в /boot/config.txt наступну стрічку і перезавантажуєм.

dtoverlay=w1-gpio,gpiopin=4

В dmesg знайдений на шині 1-wire модуль розширення портів буде відображено так:

w1_master_driver w1_bus_master1: Attaching one wire slave 20.594e480f4245 crc 95

Залишається лише повідомити ядру Linux, що до SPI інтерфейса модуля розширення портів підключений датчик BME680 і в якості CS використовується P0:

# echo 0 bme680 >/sys/bus/w1/devices/w1_bus_master1/20-594e480f4245/spi_bind

В dmesg будуть повідомлення лише у разі помилки, якщо модуль ядра bme680_spi не зміг домовитись з пристроєм на шині SPI модуля, а якщо все гаразд, то з'являться символьний пристрій в /dev і каталог в /sys

# ls -l /dev/iio*
crw------- 1 root root 236, 0 жов 12 12:22 /dev/iio:device0
# ls -l /sys/bus/w1/devices/w1_bus_master1/20-594e480f4245/spi_master/spi3/spi3.0/iio\:device0
-r--r--r-- 1 root root 4096 жов 12 12:22 dev
-rw-r--r-- 1 root root 4096 жов 12 12:22 in_humidityrelative_input
-rw-r--r-- 1 root root 4096 жов 12 12:22 in_humidityrelative_oversampling_ratio
-rw-r--r-- 1 root root 4096 жов 12 12:22 in_pressure_input
-rw-r--r-- 1 root root 4096 жов 12 12:22 in_pressure_oversampling_ratio
-rw-r--r-- 1 root root 4096 жов 12 12:22 in_resistance_input
-rw-r--r-- 1 root root 4096 жов 12 12:22 in_temp_input
-rw-r--r-- 1 root root 4096 жов 12 12:22 in_temp_oversampling_ratio
-r--r--r-- 1 root root 4096 жов 12 12:22 name
-r--r--r-- 1 root root 4096 жов 12 12:22 oversampling_ratio_available
drwxr-xr-x 2 root root    0 жов 12 12:22 power
lrwxrwxrwx 1 root root    0 жов 12 12:22 subsystem -> ../../../../../../../bus/iio
-rw-r--r-- 1 root root 4096 жов 12 12:22 uevent

Значення температури, вологості і тиску можна отримати прямо з командного рядка:

# cat /sys/bus/w1/devices/w1_bus_master1/20-594e480f4245/spi_master/spi3/spi3.0/iio\:device0/in_temp_input
25320
# cat /sys/bus/w1/devices/w1_bus_master1/20-594e480f4245/spi_master/spi3/spi3.0/iio\:device0/in_humidityrelative_input
44.210000000
# cat /sys/bus/w1/devices/w1_bus_master1/20-594e480f4245/spi_master/spi3/spi3.0/iio\:device0/in_pressure_input
997.250000000

Але є одна незручність - номер пристрою spi3 залежить від того, скільки в системі до цього вже було зареєстровано пристроїв spi, тому повний шлях до файлів не є константою, яку можна було б використовувати в скриптах. На /sys/bus/w1/devices/w1_bus_master1/20-594e480f4245/spi_master/spi3/spi3.0/iio:device0
є символьне посилання з підсистеми iio

# ls -l /sys/bus/iio/devices/iio\:device0
lrwxrwxrwx 1 root root 0 жов 19 18:36 /sys/bus/iio/devices/iio:device0 -> ../../../devices/w1_bus_master1/20-594e480f4245/spi_master/spi3/spi3.0/iio:device0

Хоч номер пристрою iio:device0 також залежить від кількості пристроїв iio, вже зареєстрованих в системі і також не є константою, але за допомогою udev можна дуже просто зробити на цей каталог своє символьне посилання.

ACTION=="add", SUBSYSTEM=="w1", KERNEL=="20-594e480f4245",\
 ATTR{spi_bind}+="0 bme680"
ACTION=="add", SUBSYSTEM=="iio", KERNELS=="20-594e480f4245",\
 RUN+="/bin/ln -s /sys/bus/iio/devices/%k /var/run/bme680_kimnata",\
 ENV{REMOVE_CMD}="/bin/rm /var/run/bme680_kimnata"

Перше правило підключить до SPI інтерфейса заданого модуля розширення портів датчик BME680, а друге при успішній реєстрації в системі пристрою iio, пов'язаного саме з цим з BME680, виконає задану команду і створить змінну оточення для даного екземпляру iio пристрою. Змінна %k на момент виконання команди буде мати значення iio:deviceN з актуальним номером пристрою замість N. Щоб змінна оточення ENV{REMOVE_CMD} виконала завдання, передбачене її назвою, необхідно додати правило (якщо такого ще немає).

ACTION=="remove", ENV{REMOVE_CMD}!="", RUN+="$env{REMOVE_CMD}"

Зверніть увагу, в udev є подібна вбудована функція для створення символьних посилань SYMLINK+="dev_alias", але вона призначена виключно для символьних пристроїв в межах каталога /dev.
Тепер при появі на шині модуля розширення портів з ідентифікатором 20-594e480f4245 буде з'являтися символьне посилання на каталог з потрібними файлами:

# ls -l /var/run/bme680_kimnata
lrwxrwxrwx 1 root root 32 жов 19 18:36 /var/run/bme680_kimnata -> /sys/bus/iio/devices/iio:device0
# cat /var/run/bme680_kimnata/in_temp_input
23440
# cat /var/run/bme680_kimnata/in_humidityrelative_input
42.491000000
# cat /var/run/bme680_kimnata/in_pressure_input
990.310000000

Імена файлів вже ні від чого не залежать і їх можна використовувати в скриптах, наприклад, ось таким скриптом на perl, який запускається з cron щохвилини, можна відправляти дані з датчика в rrdtool.

#!/usr/bin/perl
use RRDCached;
sub rv {if(open F,$_[0]){$_=0+<F>;close F}else{$_="U"}$_;}
$t=rv("/var/run/bme680_kimnata/in_temp_input");
$h=rv("/var/run/bme680_kimnata/in_humidityrelative_input");
$p=rv("/var/run/bme680_kimnata/in_pressure_input");
$r=rv("/var/run/bme680_kimnata/in_resistance_input");
$rrd = RRDCached->new("unix:/var/run/rrdcached.sock");
$rrd->update("bme680_room.rrd", "N:$t:$h:$p:$r");
$rrd->done();

На графіку відображені результати роботи за першу добу, в лівій половині графіка датчик був підключений через програмний майстер шини (w1-gpio), а в правій - через апаратний (DS2480B).
graph_bme680_1.png
Отже з w1-gpio щось негаразд, якщо у Вас виникла схожа картина, не поспішайте звинувачувати датчик.
Якщо у Вас новий датчик, то Ви також можете спостерігати неперервний ріст значення опору газового сенсора - це норма, на сайті виробника пояснили, що опір стабілізується приблизно через тиждень неперервної роботи.
graph_bme680_2.png

Корисні формули
Датчик видає тиск в гектоПаскалях або мілібарах (це одне й те ж), переведення в міліметри ртутного стовпчика:
p_mmHg = p_mbar * 760 / 1013.25
Абсолютна вологість повітря в грамах водяної пари на кубічний метр обчислюється за наближеною формулою:
a = exp(17.62*t/(243.12+t))/(461.52*(t+273.15))*6112*h, де t - температура повітря в градусах Цельсія, h - відносна вологість в відсотках.
Температура точки роси в градусах Цельсія обчислюється за наближеною формулою:
d=243.12*f/(17.62-f), де f=17.62*t/(243.12+t)+ln(h/100)
Забруднення повітря в еквівалентних грамах на кубічний метр (забруднення, які викликають таку ж чисельну реакцію датчика, як і відповідна кількість водяної пари):
w=16.53*ln(50000/r)-a
Коефіцієнт пропорційності 16.53 був знайдений експериментально за методикою, описаною в статті, 50000 Ом - приблизний опір датчика, що відповідає абсолютно чистому повітрю без забруднювачів і водяної пари, r - виміряний опір датчика в Омах, a - абсолютна вологість в грамах на кубічний метр. Величина w є сумою реакцій на всі
забруднювачі, пропорційність реакції на окремі хімічні речовини може відрізнятися в залежності від термопрофілю і частоти опитувань, ця інформація компанією Bosch не розкривається.
Якість повітря по нелінійній шкалі від 0 (нескінченно забруднене повітря) до 100 (абсолютно чисте повітря) можна отримати по формулі:
q=exp(-w/20)*100
Тут 20 - референсна забрудненість повітря в еквівалентних грамах на кубічний метр, яка відповідає значенню якості повітря exp(-1)*100~=36.8

Неактивний

Швидке повідомлення

Введіть повідомлення і натисніть Надіслати

Підвал форуму