[Системное программирование, C, Разработка под Linux] Нам нужно поговорить про Linux IIO
Автор
Сообщение
news_bot ®
Стаж: 6 лет 9 месяцев
Сообщений: 27286
IIO (промышленный ввод / вывод) — это подсистема ядра Linux для аналого-цифровых преобразователей (АЦП), цифро-аналоговых преобразователей (ЦАП) и различных типов датчиков. Может использоваться на высокоскоростных промышленных устройствах. Она, также, включает встроенный API для других драйверов.
Подсистема Industrial I/O Linux предлагает унифицированную среду для связи (чтения и записи) с драйверами, охватывающими различные типы встроенных датчиков и несколько исполнительных механизмов. Он также предлагает стандартный интерфейс для приложений пользовательского пространства, управляющих датчиками через sysfs и devfs.
Вот несколько примеров поддерживаемых типов датчиков в IIO:
- АЦП / ЦАП
- акселерометры
- магнетометры
- гироскопы
- давление
- влажность
- температура
- дальнометры
IIO может использоваться во многих различных случаях:
- Низкоскоростная регистрация для медленно меняющегося входного сигнала (пример: запись температуры в файл)
- Высоко-скоростной сбор данных с использованием АЦП, DFSDM или внешних устройств (например, аудио, измеритель мощности)
- Считывание положения вращающегося элемента, используя интерфейс квадратурного энкодера TIM или LPTIM
- Управление аналоговым источником через ЦАП
- Внешние устройства подключенные через SPI или I2C
В целом про IIO информации немного, но но она есть, а поэтому в данной обзорной статья мы сначала ...
Сосредоточимся на моментах почему IIO это хорошо
Все наверняка встречали/пользовались конструкциями типа:
# https://www.kernel.org/doc/Documentation/i2c/dev-interface
open("/dev/i2c-1", O_RDWR);
# https://www.kernel.org/doc/Documentation/spi/spidev.rst
open("/dev/spidev2.0", O_RDWR);
У данного способа много недостатков, я перечислю те которые считаю основными:
- нет прерываний
- способ доступа для данных индивидуален для каждого устройства
Ну как говориться зачем всё это — если есть драйвера ?
Здесь мы опять сталкиваемся с "индивидульностью" каждого устройства (как допустим способ калибровки или размерность).
Собственно IIO даёт нам во-первых универсальность, во-вторых возможность poll по поступлению новых данных.
Сам IIO разделен на два уровня абстракции — устройства и каналы измерений.
Выделим два основных способа доступа поддержанных в официальном ядре.
Простое использование IIO
Мы можем читать данные через sysfs (допустим для акселерометра):
# cat /sys/bus/iio/devices/iio\:device0/in_accel_x_raw
-493
Это мы прочитали "сырые" измерения, их еще надо привести к общему виду.
Либо через read():
# Включим захват измерений для каждого канала
(cd /sys/bus/iio/devices/iio:device0/scan_elements/ && for file in *_en; do echo 1 > $file; done)
Тогда мы можем свести взаимодействие к виду :
int fd = open("/dev/iio:device0");
read(fd, buffer, scan_size);
# где scan_size это сумма размера всех заказанных измерений, то есть для всех 1 в /sys/bus/iio/devices/iio:device0/scan_elements/*_en
Размер прочитанного блока всегда кратен scan_size, мы получаем "сырые" измерения, которые надо привести к общему виду, об этом позже.
Внутреннее устройство
Каналы
Любой драйвер IIO предоставляет информацию о возможных измерениях в виде стандартного описания каналов struct iio_chan_spec:
(IIO types)[https://elixir.bootlin.com/linux/v5.9-rc1/source/include/uapi/linux/iio/types.h#L14]
Пример для датчика BME280
/* https://elixir.bootlin.com/linux/v5.9-rc1/source/drivers/iio/pressure/bmp280-core.c#L132*/
static const struct iio_chan_spec bmp280_channels[] = {
{
.type = IIO_PRESSURE,
.info_mask_separate = BIT(IIO_CHAN_INFO_PROCESSED) |
BIT(IIO_CHAN_INFO_OVERSAMPLING_RATIO),
},
{
.type = IIO_TEMP,
.info_mask_separate = BIT(IIO_CHAN_INFO_PROCESSED) |
BIT(IIO_CHAN_INFO_OVERSAMPLING_RATIO),
},
{
.type = IIO_HUMIDITYRELATIVE,
.info_mask_separate = BIT(IIO_CHAN_INFO_PROCESSED) |
BIT(IIO_CHAN_INFO_OVERSAMPLING_RATIO),
},
};
Как мы можем видеть, данный датчик предоставляет измерения температуры, влажности и давления — три отдельных канала с разными типами.
То есть мы можем читать температуру с любого датчика температуры для которого есть драйвер в ядре Linux одним и тем же способом, а так же выбрать любое сочетание данных каналов и читать только их.
Кольцевой буфер
Собственно это не так интригующее как звучит, основан на kfifo делает всё что положено кольцевому буфферу.
Новые данные вытесняют старые, что гарантирует доступ к последнему измерению в любое время, а так же то, что в худшем случае будут потеряны только старые измерения.
Метка времени
Присутствует для любого типа устройства. Нам важно знать, что метка времени выставляется, как правило, в верхней половине обработчика прерывания, что конечно же хуже чем собственная метка времени датчика, но лучшее на что мы можем рассчитывать без неё.
Представлена в наносекундах, является CLOCK_REALTIME.
(IIO Triggered Buffers)[https://www.kernel.org/doc/html/latest/driver-api/iio/triggered-buffers.html]
Триггеры
Представляет из себя "внешнее" событие, которое инициирует захват данных с последующей передачей наверх в user space.
Один и тот же триггер может быть назначен нескольким устройствам, что позволяет получить близкие по времени измерения с нескольких независимых устройств.
Назначить триггер устройству:
# cat /sys/bus/iio/devices/iio\:device0/trigger/current_trigger
icm20608-dev0
# echo > /sys/bus/iio/devices/iio\:device0/trigger/current_trigger
# cat /sys/bus/iio/devices/iio\:device0/trigger/current_trigger
# echo "icm20608-dev0" > /sys/bus/iio/devices/iio\:device0/trigger/current_trigger
(Official Trigger Documentation)[https://www.kernel.org/doc/html/latest/driver-api/iio/triggers.html]
(IIO sysfs trigger)[https://wiki.analog.com/software/linux/docs/iio/iio-trig-sysfs]
(Industrial IIO configfs support
)[https://www.kernel.org/doc/Documentation/iio/iio_configfs.txt]
(Triggered buffer support trigger buffer support for IIO subsystem of Linux device driver
)[https://programmer.group/5cbf67db154ab.html]
Device owned triggers
Данный класс триггеров относиться к собственным триггерам устройства, они определяются в device tree:
icm20608: imu@0 {
...
interrupt-parent = <&gpio5>;
interrupts = <11 IRQ_TYPE_EDGE_RISING>;
...
};
Это даст нам соответствующий триггер с именем:
cat /sys/bus/iio/devices/trigger0/name
icm20608-dev0
Собственно конкретный данный триггер это просто выход прерывания заведенный на соответствующую ножку gpio, но это не всегда так, таким триггером может являться любой источник прерывания связанный с устройством.
Interrupt triggers (also known as gpio trigger)
iio-trig-interrupt
Фактически тоже самое что и предыдущий тип, но он не привязан ни к какому конкретному устройству. Это может быть просто кнопка подсоединенная к gpio, так и любой источник прерываний.
Данный драйвер не поддержан в ядре в полном виде, ввиду сомнений текущего maintainer'a IIO Jonathan Cameron, хотя он так же является его автором.
Единственный способ задания в официальном ядре через платформенный код — необходимый для этого платформенный код вы можете подсмотреть тут Triggered buffer support trigger buffer support for IIO subsystem of Linux device driver
.
Но кому очень хочется может воспользоваться серией патчей:
[v3,1/6] dt-bindings: iio: introduce trigger providers, consumers
Тогда задание через device tree будет выглядеть приблизительно так:
trig0: interrupt-trigger0 {
#io-trigger-cells = <0>;
compatible = "interrupt-trigger";
interrupts = <11 0>;
interrupt-parent = <&gpioa>;
};
sysfs trigger
iio-trig-sysfs
Тут всё очень просто пишем в sysfs — срабатывает триггер, устройство захватывает текущие измерения и уведомляет потребителя.
Создание триггера:
# echo 10 > /sys/bus/iio/devices/iio_sysfs_trigger/add_trigger
Число используется для генерации имени триггера в виде "sysfstrig%d", его же мы используем при задании триггера устройству.
High resolution timer trigger
Представляет из себя таймер с минимальным возможным разрешением в 1 наносекунду.
# mkdir /sys/kernel/config/iio/triggers/hrtimer/my_trigger_name
# cat /sys/bus/iio/devices/trigger4/name
my_trigger_name
# cat /sys/bus/iio/devices/trigger4/sampling_frequency
100
Одним из дополнительных случаев использования может быть опрос устройств без собственных прерываний — допустим "забыли" завести прерывание на SoC.
loop trigger
iio-trig-loop
Экспериментальный триггер предположительно инициированный (PATCH v1 5/5 iio:pressure:ms5611: continuous sampling support
)[https://www.spinics.net/lists/linux-iio/msg23004.html].
Смысл заключается в опросе устройства с максимально возможной скоростью. Дополнительно можно посмотреть оригинальный комментарий к коммиту:
(iio:trigger: Experimental kthread tight loop trigger)[https://github.com/torvalds/linux/commit/bc2e1126eccb47517b9d1c685020c38600f99a3d#diff-0d329fecbdfa98eba57fd93cd6350578].
Опять же нет поддержки DT, так что либо добавлять через патч, либо через платформенный код.
Device tree
Здесь я хочу обратить особое внимание на возможность задать label для узла, которую лучше всего использовать если у вас много однотипных устройств,
всегда текущие значения заданные в узле можно подсмотреть в директории of_node для каждого iio:device — /sys/bus/iio/devices/iio\:device0/of_node/.
Какой общей рекомендации не существует — всё индивидуально и описано в https://elixir.bootlin.com/linux/v5.9-rc1/source/Documentation/devicetree/bindings/iio
Типы каналов измерений
Многие датчики, который раньше существовали как отдельные сущности были перенесены на инфраструктуру IIO, так что похоже тут (enum iio_chan_type)[https://elixir.bootlin.com/linux/v5.9-rc1/source/include/uapi/linux/iio/types.h#L14] можно найти почти любой тип измерений. Расшифровку можно посмотреть тут (iio_event_monitor)[https://elixir.bootlin.com/linux/v5.9-rc1/source/tools/iio/iio_event_monitor.c#L28]
Формат данных
IIO умеет сообщать в каком формате нам передаются данные (iio-buffer-sysfs-interface)[https://01.org/linuxgraphics/gfx-docs/drm/driver-api/iio/buffers.html#iio-buffer-sysfs-interface]
[be|le]:[s|u]bits/storagebitsXrepeat[>>shift]
Живой пример для icm20608:
# cat /sys/bus/iio/devices/iio\:device0/scan_elements/*_type
be:s16/16>>0
be:s16/16>>0
be:s16/16>>0
be:s16/16>>0
be:s16/16>>0
be:s16/16>>0
le:s64/64>>0
Тут более ли менее все понятно:
- первым идёт порядок байт le или be соответственно мы должны позаботиться о том что порядок совпадает с нашей архитектурой или c выбранным нами порядком байт
- затем идет тип — знаковое или без знаковое, s или u соответственно
- затем идет длина значения в битах и через / длина поля в котором содержится значение опять же в битах, кратное количеству битов в байте
- последним идет сдвиг
То есть если бы у нас было два значения по четыре бита упакованных в одно и тоже поле мы видели бы следующее:
be:u4/8>>0
be:u4/8>>4
Предпоследнее не показанное в живом примере поле repeat — если оно больше 1 передается сразу массив измерений.
Scaling and offset
Как я уже говорил ранее прочитанные данные в сыром виде необходимо привести к общему виду:
/sys/bus/iio/devices/iio:deviceX/in_*_raw
/sys/bus/iio/devices/iio:deviceX/in_*_offset
/sys/bus/iio/devices/iio:deviceX/in_*_scale
В общем случае преобразование будет иметь вид (raw + offset)*scale, для какого то из типов датчиков offset'a может и не быть.
(How to do a simple ADC conversion using the sysfs interface)[https://wiki.st.com/stm32mpu/wiki/How_to_use_the_IIO_user_space_interface#How_to_do_a_simple_ADC_conversion_using_the_sysfs_interface]
iio_simple_dummy
Для изучения и тестирования может пригодится iio_simple_dummy — модуль ядра эмулирующий абстрактное устройство IIO устройство для следующих каналов:
- IIO_VOLTAGE
- IIO_ACCEL
- IIO_ACTIVITY
(The iio_simple_dummy Anatomy)[https://flusp.ime.usp.br/iio/iio-dummy-anatomy]
(iio_simple_dummy)[https://elixir.bootlin.com/linux/latest/source/drivers/iio/dummy/iio_simple_dummy.c]
libiio
Если вышеприведенное показалось вам сложным — на помощь к вам идет (libiio)[https://github.com/analogdevicesinc/libiio] от Analog Devices.
Помимо того, что она берет на себя рутинные вещи наподобие разбора формата канала или включения/выключения каналов.
У неё есть интересная особенность в виде возможности работы в виде сервера/клиента, в таком случае устройство с датчиками служит в качестве сервера данных, а клиент может располагаться на Linux, Windows или Mac машине, и соединяться через USB, Ethernet или Serial.
Соединение с удаленным узлом iiod:
On remote :
host # iiod
On local :
local $ iio_info -n [host_address]
local $ iio_attr -u ip:[host_address] -d
local $ iio_readdev -u ip:[host_address] -b 256 -s 0 icm20608
Отдельно хочется отметить поддержку (Matlab)[https://wiki.analog.com/resources/tools-software/linux-software/libiio/clients/matlab_simulink], а так же интересный проект (осциллографа)[https://wiki.analog.com/resources/tools-software/linux-software/iio_oscilloscope].
Пример программы для чтения акселерометра
Приведу пример программы для чтения, как с использованием libiio так и без.
https://github.com/maquefel/icm20608-iio
Работа без использования libiio
Я не буду касаться банальной работы с sysfs так, что в общих чертах для чтения необходимо сделать следующее:
- Поиск устройства, здесь мы ориентируемся на /sys/bus/iio/iio:deviceN/name, соответственно /sys/bus/iio/iio:deviceN будет совпадать с /dev/iio:deviceN
- Инициализация каналов в /sys/bus/iio/iio:deviceN/scan_elements/, нам будут передаваться измерения только с тех каналов, которые мы заказали в *_en
- Инициализация буфера /sys/bus/iio/iio:deviceN/enable
В примере есть минимум необходимый для работы.
Выравнивание
Eго придется делать самим если мы хотим обойтись без libiio.
https://elixir.bootlin.com/linux/v5.9-rc1/source/drivers/iio/industrialio-buffer.c#L574
Простой код для вычисления смещения для каждого канала:
# bytes - всего длина всего пакета в байтах
# length - длина канала в байтах
# offset - смещения относительно начала пакета для канала в байтах
if (bytes % length == 0)
offset = bytes;
else
offset = bytes - bytes % length + length;
bytes = offset + length;
Что в случае без libiio, что в противоположном случае измерение необходимо привести к окончательному виду:
- привести порядок байт в соответствие с используемым
- сдвинуть на необходимое значение
- обрезать лишнее
- если знаковое, то проделать расширение знака (Sign extension)
- если есть offset, то прибавить до применения шкалы
- если есть scale, то применить шкалу
input = is_be ? betoh(input) : letoh(input);
input >>= shift;
input &= BIT_MASK(bits);
value = is_signed ? (float)sext(input, bits) : (float)input;
if(with_offset) value += offset;
if(with_scale) value *= scale;
Примечание: Расширение знака (Sign extension) в примере представлен самый простой непортируемый вариант. Дополнительно по теме можно глянуть тут (SignExtend)[https://graphics.stanford.edu/~seander/bithacks.html#FixedSignExtend]
Работа с использованием libiio
Пример работы можно глянуть тут (libiio-loop.c
)[https://github.com/maquefel/icm20608-iio/blob/master/src/libiio-loop.c]
Приведу псевдокод с комментариями:
# Создать контекст из uri
# uri = "ip:127.0.0.1"
# uri = "local:"
# uri = "usb:"
ctx = iio_create_context_from_uri(uri);
# Найти устройство
# допустим device = icm20608
dev = iio_context_find_device(ctx, device);
# Количество доступных каналов
nb_channels = iio_device_get_channels_count(dev);
# Включить каждый канал
for(int i = 0; i < nb_channels; i++)
iio_channel_enable(iio_device_get_channel(dev, i));
# buffer_size = SAMPLES_PER_READ, количество последовательных измерений (по всем каналам)
buffer = iio_device_create_buffer(dev, buffer_size, false);
# Задать блокирующий режим работы
iio_buffer_set_blocking_mode(buffer, true);
while(true) {
# Заполнить буфер
iio_buffer_refill(buffer);
# Способов несколько - можно читать и без использования libiio
# Приведу в качестве примера "каноничный" способ, который заключается в том что предоставленная нами функция
# вызывается для каждого канала
# ssize_t print_sample(const struct iio_channel *chn, void *buffer, size_t bytes, __notused void *d)
# const struct iio_channel *chn - текущий канал который мы обрабатываем
# void *buffer - указатель на буфер содержащий измерения для данного канала
# size_t bytes - длина измерения в байтах
# __notused void *d - пользовательские данные которые мы передаем вместе с вызовом iio_buffer_foreach_sample
iio_buffer_foreach_sample(buffer, print_sample, NULL);
}
# освободить буфер
iio_buffer_destroy(buffer);
# освободить контекст
iio_context_destroy(ctx);
Пара слов об альтернативном механизме для чтения данных
В качестве альтернативы доступа к данным был предложен прототип, который позволял перемещать данные из буфера устройства сразу в пользовательский буфер, так называемый механизм Zero-Copy.
Всё это относиться к методам обработки высокоскоростного потока данных.
Сравнение методов (тезисы из презентации):
Решение первое — Блоки
- Группировать несколько измерений в блок
- Генерировать одно прерывание на один блок
- Уменьшить расходы на управление
- Размер блока должен быть конфигурируемым
- Позволить пользовательского приложению выбирать между задержкой и накладными расходами
Решение второе — DMA + mmap()
- Использовать DMA чтобы перемещать данные от устройства к выделенному блоку памяти
- Использовать mmap() чтобы иметь доступ к памяти из пользовательского пространства
- Избежать копирования данных
- "Бесплатное" демультиплексирование в пользовательском пространстве
(High-speed Data Acquisition
using the
Linux Industrial IO framework)[https://elinux.org/images/8/8d/Clausen--high-speed_data_acquisition_with_the_linux_iio_framework.pdf]
По мне так это отличное решения для SDR.
Из переписки с автором я понял, что данная функциональность будет включена в официальное ядро, хотя и не в текущем виде и неизвестно когда.
Автор любезно предоставил данные изменения для ядра (4.19)[https://github.com/larsclausen/linux/tree/iio-high-speed-4.19] и (5.4)[https://github.com/larsclausen/linux/tree/iio-high-speed-5.4].
С дискуссией по данной теме можно ознакомиться тут: https://patchwork.kernel.org/project/linux-iio/list/?series=284793
Рекомендуемые материалы
https://bootlin.com/pub/conferences/2012/fosdem/iio-a-new-subsystem/iio-a-new-subsystem.pdf
https://archive.fosdem.org/2012/schedule/event/693/127_iio-a-new-subsystem.pdf
https://events19.linuxfoundation.org/wp-content/uploads/2017/12/Bandan-Das_Drone_SITL_bringup_with_the_IIO_framework.pdf
https://programmer.group/5cbf67db154ab.html
https://elinux.org/images/b/ba/ELC_2017_-_Industrial_IO_and_You-_Nonsense_Hacks%21.pdf
https://elinux.org/images/8/8d/Clausen--high-speed_data_acquisition_with_the_linux_iio_framework.pdf
Для дополнительного изучения
https://linux.ime.usp.br/~marcelosc/2019/09/Simple-IIO-driver
P.S.
Приношу извинения за ссылки, я не смог заставить их выглядеть на markdown как положено, и мне непонятно почему.
===========
Источник:
habr.com
===========
Похожие новости:
- [Информационная безопасность, IT-компании] Leak-Search: как и зачем QIWI создала сервис, который ищет утечки исходных кодов компаний
- [Разработка веб-сайтов, Системное администрирование, Серверное администрирование] Дарим ISPmanager при создании нового сервера
- [Системное администрирование, Сетевые технологии, Облачные сервисы, Сетевое оборудование] Построение сетевой инфраструктуры на базе Nebula. Часть 1 — задачи и решения
- [Управление проектами, Управление продуктом, Управление персоналом] Change Management 3: Колесо изменений и борьба с партизанами
- [Git] Git compare: быстрый способ сравнить две ветки
- [История IT, Старое железо, Читальный зал] Найдено давно утерянное руководство к самому старому компьютеру в мире
- [Интернет-маркетинг, Контекстная реклама, Медийная реклама, Повышение конверсии] Сравнение моделей оплаты в интернет-рекламе: какая подойдет именно вам?
- [MySQL, DevOps] Создание резервной копии MySQL при помощи утилиты XtraBackup
- [Информационная безопасность, IT-инфраструктура, Облачные сервисы] Как мы защищаем виртуальные рабочие столы клиентов от вирусов, шпионов и атак
- [Python, Node.JS, Машинное обучение] Machine learning in browser: ways to cook up a model
Теги для поиска: #_sistemnoe_programmirovanie (Системное программирование), #_c, #_razrabotka_pod_linux (Разработка под Linux), #_iio, #_linux, #_linux_kernel, #_sensors, #_sistemnoe_programmirovanie (
Системное программирование
), #_c, #_razrabotka_pod_linux (
Разработка под Linux
)
Вы не можете начинать темы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы
Текущее время: 22-Ноя 21:54
Часовой пояс: UTC + 5
Автор | Сообщение |
---|---|
news_bot ®
Стаж: 6 лет 9 месяцев |
|
IIO (промышленный ввод / вывод) — это подсистема ядра Linux для аналого-цифровых преобразователей (АЦП), цифро-аналоговых преобразователей (ЦАП) и различных типов датчиков. Может использоваться на высокоскоростных промышленных устройствах. Она, также, включает встроенный API для других драйверов. Подсистема Industrial I/O Linux предлагает унифицированную среду для связи (чтения и записи) с драйверами, охватывающими различные типы встроенных датчиков и несколько исполнительных механизмов. Он также предлагает стандартный интерфейс для приложений пользовательского пространства, управляющих датчиками через sysfs и devfs. Вот несколько примеров поддерживаемых типов датчиков в IIO:
IIO может использоваться во многих различных случаях:
В целом про IIO информации немного, но но она есть, а поэтому в данной обзорной статья мы сначала ... Сосредоточимся на моментах почему IIO это хорошо Все наверняка встречали/пользовались конструкциями типа: # https://www.kernel.org/doc/Documentation/i2c/dev-interface
open("/dev/i2c-1", O_RDWR); # https://www.kernel.org/doc/Documentation/spi/spidev.rst open("/dev/spidev2.0", O_RDWR); У данного способа много недостатков, я перечислю те которые считаю основными:
Ну как говориться зачем всё это — если есть драйвера ? Здесь мы опять сталкиваемся с "индивидульностью" каждого устройства (как допустим способ калибровки или размерность). Собственно IIO даёт нам во-первых универсальность, во-вторых возможность poll по поступлению новых данных. Сам IIO разделен на два уровня абстракции — устройства и каналы измерений. Выделим два основных способа доступа поддержанных в официальном ядре. Простое использование IIO Мы можем читать данные через sysfs (допустим для акселерометра): # cat /sys/bus/iio/devices/iio\:device0/in_accel_x_raw
-493 Это мы прочитали "сырые" измерения, их еще надо привести к общему виду. Либо через read(): # Включим захват измерений для каждого канала
(cd /sys/bus/iio/devices/iio:device0/scan_elements/ && for file in *_en; do echo 1 > $file; done) Тогда мы можем свести взаимодействие к виду : int fd = open("/dev/iio:device0");
read(fd, buffer, scan_size); # где scan_size это сумма размера всех заказанных измерений, то есть для всех 1 в /sys/bus/iio/devices/iio:device0/scan_elements/*_en Размер прочитанного блока всегда кратен scan_size, мы получаем "сырые" измерения, которые надо привести к общему виду, об этом позже. Внутреннее устройство Каналы Любой драйвер IIO предоставляет информацию о возможных измерениях в виде стандартного описания каналов struct iio_chan_spec: (IIO types)[https://elixir.bootlin.com/linux/v5.9-rc1/source/include/uapi/linux/iio/types.h#L14] Пример для датчика BME280 /* https://elixir.bootlin.com/linux/v5.9-rc1/source/drivers/iio/pressure/bmp280-core.c#L132*/
static const struct iio_chan_spec bmp280_channels[] = { { .type = IIO_PRESSURE, .info_mask_separate = BIT(IIO_CHAN_INFO_PROCESSED) | BIT(IIO_CHAN_INFO_OVERSAMPLING_RATIO), }, { .type = IIO_TEMP, .info_mask_separate = BIT(IIO_CHAN_INFO_PROCESSED) | BIT(IIO_CHAN_INFO_OVERSAMPLING_RATIO), }, { .type = IIO_HUMIDITYRELATIVE, .info_mask_separate = BIT(IIO_CHAN_INFO_PROCESSED) | BIT(IIO_CHAN_INFO_OVERSAMPLING_RATIO), }, }; Как мы можем видеть, данный датчик предоставляет измерения температуры, влажности и давления — три отдельных канала с разными типами. То есть мы можем читать температуру с любого датчика температуры для которого есть драйвер в ядре Linux одним и тем же способом, а так же выбрать любое сочетание данных каналов и читать только их. Кольцевой буфер Собственно это не так интригующее как звучит, основан на kfifo делает всё что положено кольцевому буфферу. Новые данные вытесняют старые, что гарантирует доступ к последнему измерению в любое время, а так же то, что в худшем случае будут потеряны только старые измерения. Метка времени Присутствует для любого типа устройства. Нам важно знать, что метка времени выставляется, как правило, в верхней половине обработчика прерывания, что конечно же хуже чем собственная метка времени датчика, но лучшее на что мы можем рассчитывать без неё. Представлена в наносекундах, является CLOCK_REALTIME. (IIO Triggered Buffers)[https://www.kernel.org/doc/html/latest/driver-api/iio/triggered-buffers.html] Триггеры Представляет из себя "внешнее" событие, которое инициирует захват данных с последующей передачей наверх в user space. Один и тот же триггер может быть назначен нескольким устройствам, что позволяет получить близкие по времени измерения с нескольких независимых устройств. Назначить триггер устройству: # cat /sys/bus/iio/devices/iio\:device0/trigger/current_trigger
icm20608-dev0 # echo > /sys/bus/iio/devices/iio\:device0/trigger/current_trigger # cat /sys/bus/iio/devices/iio\:device0/trigger/current_trigger # echo "icm20608-dev0" > /sys/bus/iio/devices/iio\:device0/trigger/current_trigger (Official Trigger Documentation)[https://www.kernel.org/doc/html/latest/driver-api/iio/triggers.html] (IIO sysfs trigger)[https://wiki.analog.com/software/linux/docs/iio/iio-trig-sysfs] (Industrial IIO configfs support )[https://www.kernel.org/doc/Documentation/iio/iio_configfs.txt] (Triggered buffer support trigger buffer support for IIO subsystem of Linux device driver )[https://programmer.group/5cbf67db154ab.html] Device owned triggers Данный класс триггеров относиться к собственным триггерам устройства, они определяются в device tree: icm20608: imu@0 {
... interrupt-parent = <&gpio5>; interrupts = <11 IRQ_TYPE_EDGE_RISING>; ... }; Это даст нам соответствующий триггер с именем: cat /sys/bus/iio/devices/trigger0/name
icm20608-dev0 Собственно конкретный данный триггер это просто выход прерывания заведенный на соответствующую ножку gpio, но это не всегда так, таким триггером может являться любой источник прерывания связанный с устройством. Interrupt triggers (also known as gpio trigger) iio-trig-interrupt Фактически тоже самое что и предыдущий тип, но он не привязан ни к какому конкретному устройству. Это может быть просто кнопка подсоединенная к gpio, так и любой источник прерываний. Данный драйвер не поддержан в ядре в полном виде, ввиду сомнений текущего maintainer'a IIO Jonathan Cameron, хотя он так же является его автором. Единственный способ задания в официальном ядре через платформенный код — необходимый для этого платформенный код вы можете подсмотреть тут Triggered buffer support trigger buffer support for IIO subsystem of Linux device driver . Но кому очень хочется может воспользоваться серией патчей: [v3,1/6] dt-bindings: iio: introduce trigger providers, consumers Тогда задание через device tree будет выглядеть приблизительно так: trig0: interrupt-trigger0 {
#io-trigger-cells = <0>; compatible = "interrupt-trigger"; interrupts = <11 0>; interrupt-parent = <&gpioa>; }; sysfs trigger iio-trig-sysfs Тут всё очень просто пишем в sysfs — срабатывает триггер, устройство захватывает текущие измерения и уведомляет потребителя. Создание триггера: # echo 10 > /sys/bus/iio/devices/iio_sysfs_trigger/add_trigger
Число используется для генерации имени триггера в виде "sysfstrig%d", его же мы используем при задании триггера устройству. High resolution timer trigger Представляет из себя таймер с минимальным возможным разрешением в 1 наносекунду. # mkdir /sys/kernel/config/iio/triggers/hrtimer/my_trigger_name
# cat /sys/bus/iio/devices/trigger4/name my_trigger_name # cat /sys/bus/iio/devices/trigger4/sampling_frequency 100 Одним из дополнительных случаев использования может быть опрос устройств без собственных прерываний — допустим "забыли" завести прерывание на SoC. loop trigger iio-trig-loop Экспериментальный триггер предположительно инициированный (PATCH v1 5/5 iio:pressure:ms5611: continuous sampling support )[https://www.spinics.net/lists/linux-iio/msg23004.html]. Смысл заключается в опросе устройства с максимально возможной скоростью. Дополнительно можно посмотреть оригинальный комментарий к коммиту: (iio:trigger: Experimental kthread tight loop trigger)[https://github.com/torvalds/linux/commit/bc2e1126eccb47517b9d1c685020c38600f99a3d#diff-0d329fecbdfa98eba57fd93cd6350578]. Опять же нет поддержки DT, так что либо добавлять через патч, либо через платформенный код. Device tree Здесь я хочу обратить особое внимание на возможность задать label для узла, которую лучше всего использовать если у вас много однотипных устройств, всегда текущие значения заданные в узле можно подсмотреть в директории of_node для каждого iio:device — /sys/bus/iio/devices/iio\:device0/of_node/. Какой общей рекомендации не существует — всё индивидуально и описано в https://elixir.bootlin.com/linux/v5.9-rc1/source/Documentation/devicetree/bindings/iio Типы каналов измерений Многие датчики, который раньше существовали как отдельные сущности были перенесены на инфраструктуру IIO, так что похоже тут (enum iio_chan_type)[https://elixir.bootlin.com/linux/v5.9-rc1/source/include/uapi/linux/iio/types.h#L14] можно найти почти любой тип измерений. Расшифровку можно посмотреть тут (iio_event_monitor)[https://elixir.bootlin.com/linux/v5.9-rc1/source/tools/iio/iio_event_monitor.c#L28] Формат данных IIO умеет сообщать в каком формате нам передаются данные (iio-buffer-sysfs-interface)[https://01.org/linuxgraphics/gfx-docs/drm/driver-api/iio/buffers.html#iio-buffer-sysfs-interface] [be|le]:[s|u]bits/storagebitsXrepeat[>>shift]
Живой пример для icm20608: # cat /sys/bus/iio/devices/iio\:device0/scan_elements/*_type
be:s16/16>>0 be:s16/16>>0 be:s16/16>>0 be:s16/16>>0 be:s16/16>>0 be:s16/16>>0 le:s64/64>>0 Тут более ли менее все понятно:
То есть если бы у нас было два значения по четыре бита упакованных в одно и тоже поле мы видели бы следующее: be:u4/8>>0
be:u4/8>>4 Предпоследнее не показанное в живом примере поле repeat — если оно больше 1 передается сразу массив измерений. Scaling and offset Как я уже говорил ранее прочитанные данные в сыром виде необходимо привести к общему виду: /sys/bus/iio/devices/iio:deviceX/in_*_raw
/sys/bus/iio/devices/iio:deviceX/in_*_offset /sys/bus/iio/devices/iio:deviceX/in_*_scale В общем случае преобразование будет иметь вид (raw + offset)*scale, для какого то из типов датчиков offset'a может и не быть. (How to do a simple ADC conversion using the sysfs interface)[https://wiki.st.com/stm32mpu/wiki/How_to_use_the_IIO_user_space_interface#How_to_do_a_simple_ADC_conversion_using_the_sysfs_interface] iio_simple_dummy Для изучения и тестирования может пригодится iio_simple_dummy — модуль ядра эмулирующий абстрактное устройство IIO устройство для следующих каналов:
(The iio_simple_dummy Anatomy)[https://flusp.ime.usp.br/iio/iio-dummy-anatomy] (iio_simple_dummy)[https://elixir.bootlin.com/linux/latest/source/drivers/iio/dummy/iio_simple_dummy.c] libiio Если вышеприведенное показалось вам сложным — на помощь к вам идет (libiio)[https://github.com/analogdevicesinc/libiio] от Analog Devices. Помимо того, что она берет на себя рутинные вещи наподобие разбора формата канала или включения/выключения каналов. У неё есть интересная особенность в виде возможности работы в виде сервера/клиента, в таком случае устройство с датчиками служит в качестве сервера данных, а клиент может располагаться на Linux, Windows или Mac машине, и соединяться через USB, Ethernet или Serial. Соединение с удаленным узлом iiod: On remote : host # iiod
On local : local $ iio_info -n [host_address]
local $ iio_attr -u ip:[host_address] -d local $ iio_readdev -u ip:[host_address] -b 256 -s 0 icm20608 Отдельно хочется отметить поддержку (Matlab)[https://wiki.analog.com/resources/tools-software/linux-software/libiio/clients/matlab_simulink], а так же интересный проект (осциллографа)[https://wiki.analog.com/resources/tools-software/linux-software/iio_oscilloscope]. Пример программы для чтения акселерометра Приведу пример программы для чтения, как с использованием libiio так и без. https://github.com/maquefel/icm20608-iio Работа без использования libiio Я не буду касаться банальной работы с sysfs так, что в общих чертах для чтения необходимо сделать следующее:
В примере есть минимум необходимый для работы. Выравнивание Eго придется делать самим если мы хотим обойтись без libiio. https://elixir.bootlin.com/linux/v5.9-rc1/source/drivers/iio/industrialio-buffer.c#L574 Простой код для вычисления смещения для каждого канала: # bytes - всего длина всего пакета в байтах
# length - длина канала в байтах # offset - смещения относительно начала пакета для канала в байтах if (bytes % length == 0) offset = bytes; else offset = bytes - bytes % length + length; bytes = offset + length; Что в случае без libiio, что в противоположном случае измерение необходимо привести к окончательному виду:
input = is_be ? betoh(input) : letoh(input);
input >>= shift; input &= BIT_MASK(bits); value = is_signed ? (float)sext(input, bits) : (float)input; if(with_offset) value += offset; if(with_scale) value *= scale; Примечание: Расширение знака (Sign extension) в примере представлен самый простой непортируемый вариант. Дополнительно по теме можно глянуть тут (SignExtend)[https://graphics.stanford.edu/~seander/bithacks.html#FixedSignExtend] Работа с использованием libiio Пример работы можно глянуть тут (libiio-loop.c )[https://github.com/maquefel/icm20608-iio/blob/master/src/libiio-loop.c] Приведу псевдокод с комментариями: # Создать контекст из uri
# uri = "ip:127.0.0.1" # uri = "local:" # uri = "usb:" ctx = iio_create_context_from_uri(uri); # Найти устройство # допустим device = icm20608 dev = iio_context_find_device(ctx, device); # Количество доступных каналов nb_channels = iio_device_get_channels_count(dev); # Включить каждый канал for(int i = 0; i < nb_channels; i++) iio_channel_enable(iio_device_get_channel(dev, i)); # buffer_size = SAMPLES_PER_READ, количество последовательных измерений (по всем каналам) buffer = iio_device_create_buffer(dev, buffer_size, false); # Задать блокирующий режим работы iio_buffer_set_blocking_mode(buffer, true); while(true) { # Заполнить буфер iio_buffer_refill(buffer); # Способов несколько - можно читать и без использования libiio # Приведу в качестве примера "каноничный" способ, который заключается в том что предоставленная нами функция # вызывается для каждого канала # ssize_t print_sample(const struct iio_channel *chn, void *buffer, size_t bytes, __notused void *d) # const struct iio_channel *chn - текущий канал который мы обрабатываем # void *buffer - указатель на буфер содержащий измерения для данного канала # size_t bytes - длина измерения в байтах # __notused void *d - пользовательские данные которые мы передаем вместе с вызовом iio_buffer_foreach_sample iio_buffer_foreach_sample(buffer, print_sample, NULL); } # освободить буфер iio_buffer_destroy(buffer); # освободить контекст iio_context_destroy(ctx); Пара слов об альтернативном механизме для чтения данных В качестве альтернативы доступа к данным был предложен прототип, который позволял перемещать данные из буфера устройства сразу в пользовательский буфер, так называемый механизм Zero-Copy. Всё это относиться к методам обработки высокоскоростного потока данных. Сравнение методов (тезисы из презентации): Решение первое — Блоки
Решение второе — DMA + mmap()
(High-speed Data Acquisition using the Linux Industrial IO framework)[https://elinux.org/images/8/8d/Clausen--high-speed_data_acquisition_with_the_linux_iio_framework.pdf] По мне так это отличное решения для SDR. Из переписки с автором я понял, что данная функциональность будет включена в официальное ядро, хотя и не в текущем виде и неизвестно когда. Автор любезно предоставил данные изменения для ядра (4.19)[https://github.com/larsclausen/linux/tree/iio-high-speed-4.19] и (5.4)[https://github.com/larsclausen/linux/tree/iio-high-speed-5.4]. С дискуссией по данной теме можно ознакомиться тут: https://patchwork.kernel.org/project/linux-iio/list/?series=284793 Рекомендуемые материалы https://bootlin.com/pub/conferences/2012/fosdem/iio-a-new-subsystem/iio-a-new-subsystem.pdf https://archive.fosdem.org/2012/schedule/event/693/127_iio-a-new-subsystem.pdf https://events19.linuxfoundation.org/wp-content/uploads/2017/12/Bandan-Das_Drone_SITL_bringup_with_the_IIO_framework.pdf https://programmer.group/5cbf67db154ab.html https://elinux.org/images/b/ba/ELC_2017_-_Industrial_IO_and_You-_Nonsense_Hacks%21.pdf https://elinux.org/images/8/8d/Clausen--high-speed_data_acquisition_with_the_linux_iio_framework.pdf Для дополнительного изучения https://linux.ime.usp.br/~marcelosc/2019/09/Simple-IIO-driver P.S. Приношу извинения за ссылки, я не смог заставить их выглядеть на markdown как положено, и мне непонятно почему. =========== Источник: habr.com =========== Похожие новости:
Системное программирование ), #_c, #_razrabotka_pod_linux ( Разработка под Linux ) |
|
Вы не можете начинать темы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы
Текущее время: 22-Ноя 21:54
Часовой пояс: UTC + 5