Обновить

Комментарии 39

Хороший проект. Успехов!

🤍

А вы не рассматривали более классические реализации? События, которые будут доступны к обработке в Userspace через input-подсистему? АЦП, читаемый через industrial-input-output? В первом приближении сильно меньше велосипедов и проще сопровождать?

Нет, то, о чем вы говорите, я даже не рассматривал. Сначала я подумал, что речь про отправку команд по SPI через ioctl, но, кажется, это что-то совсем другое, я обязательно про это прочитаю. На первый взгляд, input-подсистема включает примерно те же вещи, что я пытался внедрять в свой модуль, например, на ранних этапах я пробовал скачивать буфер данных из ядра через "файл" в /proc.

Примерно так. input позволит прикинуться кнопкой на клавиатуре со специфическим кодом (типа F13 или Macro), по нажатию на которую можно будет запустить цикл чтения. А iio специально создана для работы с промышленными датчиками (ускорение, химия, давление, АЦП и им подобными). Правда если с input все просто - штатный драйвер gpio-keys, то с iio возможно придется написать драйвер для вашего АЦП. Не уверен что он есть в ядре. Но это не сложно - примеров с реализацией море.

Главный вопрос хватит ли времени реакции. Ибо у вас всё в ядре, а тут будет весьма часть в userspace. За то переносимость и независимость от конкретной микросхемы. Ну и штатный интерфейс - соответственно основной код в userspace.

Я бы сделал плавное ресемплирование с фиксированной частоты АЦП на нужную нам частоту отсчётов с помощью табличного интерполятора. Имея в распоряжении Raspberry Pi таблица может легко вмещать не 600 значений, как у АЦП в вашем варианте, а, скажем, 600 тысяч наборов коэффициентов. Т.е. плавность подстройки задержки и синхронизации частот может быть в 1000 раз лучше.
Принцим работы такой:

  • вычисляем требуемый сдвиг по времени для текущего отсчёта

  • выбираем соответствующий этому сдвигу набор коэффициентов из нашей таблицы

  • применяем КИХ фильтр с примерно 30-ю коэффициентами

  • получаем исключительно плавно перестроенную частоту отсчетов

Интересно, спасибо! Насколько я понял, не нужно настраивать регистр DRATE в АЦП: частота АЦП при включении 30 кГц и мы ее не трогаем. Перестроиться на любую частоту можно буквально одной командой (у меня так сделать нельзя, всё жестко прописано). Технически, можно даже определять точное время каждого импульса, GNSS-приемники дают такой функционал (раздел 14. Timemark). Я, получается, в своей работе пошел от обратного, я сначала настраиваю GNSS-приемник на импульсный сигнал определенной частоты, а уже он управляет преобразованием. Я так могу легко сопоставлять между собой записи с разных регистраторов, даже удаленных друг от друга, и они реально работают синхронно (не знаю, смогу ли опубликовать пост, как проверяю синхронность).

Да, частота отсчётов АЦП остаётся фиксированной, мы никак не меняем его режим работы. Имея точное время отсчёта АЦП от GNSS, можно прямо вычислять и брать нужный набор коэффициентов из таблицы.
Есть несколько нюансов как рассчитать такую большую таблицу. Основной из них - необходимо использовать интерполяцию исходного КИХ фильтра 30-го порядка в 600 тысяч раз с помошью ДПФ вместо вычисления "в лоб" КИХ фильтра порядка 30 * 600000 = 18 миллионов коэффициентов.

Для частоты 50 Гц нужно сделать 600 таких усреднений.

а нельзя сделать 100 усреднений, а дальше усреднять уже в драйвере?

600 усреднений делается внутри микросхемы, нужно просто дискретизацию увеличить, на частоте 500 SPS их будет уже 60. Я примерно о таком и пишу, что да, можно после строба SYNC получить несколько значений на более высокой частоте, и усреднить их уже программно. Но мне не нравится этот путь, поскольку АЦП и так должен выплюнуть готовое значение, которое не нуждается в постобработке. А я хотел бы в перспективе подключать к одному АЦП несколько разных датчиков, код тогда будет совсем перегруженным. Лучше, конечно, устранить первопричину помехи, а не пытаться под нее приспособиться.

Но мне не нравится этот путь, поскольку АЦП и так должен выплюнуть готовое значение, которое не нуждается в постобработке.

Насколько я знаю это не очень правильный путь - руководствоваться тем что нравится/не нравится. Надо руководствоваться тем что работает/не работает и как работает и почему не работает.

Честно говоря из статьи мне не очень понятно как у вас система работает поэтому очень сложно что-то предложить. Как то сумбурно все начинаем с микросхемы АЦП, потом вдруг перепрыгиваем на драйвер SPI. Как связаны времена обращений по SPI с временами запуска АЦП если и написано - я не смог понять. Еще не понятно что вы имеете ввиду под тактированием АЦП - настройку моментов запуска преобразований или внутреннюю работу АЦП. То есть не то чтобы совсем не понятно - есть подозрение что там что-то напутано, потому что изложение местами выглядит как обрывки логики, а не полная логика.

Еще вроде вообще ничего не написано о моменте завершения АЦП и готовности данных очередного семпла, а это обычно ключевой параметр времени, по которому и строится вся логика синхронизации драйвера и устройства.

Как связаны времена обращений по SPI с временами запуска АЦП

Начать стоит с того, что при включении АЦП она работает на базовой частоте 30,000 SPS, нужно записать в регистры новую частоту, аналоговые входы для считывания, PGA.

Картинка номер 2, SYNC - это вход микросхемы, DRDY - выход. Пока DRDY в 1 - идет очередное преобразование, его результат извлекается после смены уровня на DRDY. Параметры SPI соединения нужно настроить заранее, отправляется команда RDATA (это всего 1 байт 0х01), после этого нужно подождать, пока АЦП по SPI вернёт 3 байта. На этом базисе основан код, который я предлагаю в статье.

SYNC - это вход микросхемы, DRDY - выход. Пока DRDY в 1 - идет очередное преобразование, его результат извлекается после смены уровня на DRDY.

вот так вот намного понятнее! Следующий шаг - надо понять как у вас формируются импульсы на SYNC. Там целых 2 параметра: частота импульсов и период отдельного импульса, как вы их задаете, как вы ими управляете? Далее надо понять сколько времени занимает чтение сэмпла по SPI и определиться с логикой контроля чтобы это время не превышало времени экспозиции очередного значения АЦП, соответственно надо понять как у вас определено время экспозиции и как это время накладывается на очередное преобразование...

Я подозреваю что у микросхемы АЦП есть и автоматический режим само запуска очередного АЦП после завершения предыдущего АЦП вы не пробовали такой режим включать-использовать если он конечно есть?

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

частота импульсов и период отдельного импульса, как вы их задаете, как вы ими управляете?

50 Гц частота SYNC и период импульса 1 мкс, это есть в статье. Вы точно читали текст?

чтобы это время не превышало времени экспозиции очередного значения АЦП

Эх, новое преобразование не идет, пока не извлечешь данные, об этом я тоже писал.

вы не пробовали такой режим включать-использовать

Попробовать можно.

То что у вас в конце написано тоже довольно сумбурно выглядит как попытка самостоятельно вывести теорему Котельникова-Найквиста

А чего вы не процитировали, как вы это обычно делаете, что я чего-то нового вывожу? Мне тоже любопытно посмотреть, где это у меня в тексте.

50 Гц частота SYNC и период импульса 1 мкс

числа я заметил в статье, вопрос откуда они у вас взялись и что с ними можно делать. А это:

Далее надо понять сколько времени занимает чтение сэмпла по SPI и определиться с логикой контроля чтобы это время не превышало времени экспозиции очередного значения АЦП, соответственно надо понять как у вас определено время экспозиции и как это время накладывается на очередное преобразование...

вам видимо не о чем не говорит, ну и ладно.

вам видимо не о чем не говорит

Конечно не говорит, вы же так сумбурно пишите, уахахахаха)))

Обратите внимание, что сигнал SYNC должен быть согласован с CLKIN. От этого вполне могут быть помехи или неправильная работа АЦП, что и дает вам выбросы.

Хм, у меня в документации только указано, что синхронизация случится на первом же такте CLKIN, после того, как SYNC перейдет из 0 в 1, т.е. как будто требование не такое жесткое. Но это интересный момент, можно его поисследовать.

с одной стороны, "это просто офигительно" - по уровню.

с другой стороны - если речь идет об солнечных батереях и 150-500м то малина с линуксами здесь лютый оверинжииниринг. Для этого есть esp32 и подобные микрухи, и радиоканал по ble lr и lora

с третей стороны - Минпромторг данному НИИ дозволил закупать импортные микросхемы? Импортозамещение наше все, даешь RepkaPi ;)

тут кстати еще одни товарищи недавно публиковали про свою распределенную сеть сенсоров https://habr.com/ru/articles/957828/

Минпромторг данному НИИ дозволил закупать импортные микросхемы?

Пока что да) Но скоро лавочку прикроют, действительно.

НИИ не в ведении минпромторга. Тем более что работы скорее всего по внебюджетному финансированию.

НИИ не в ведении минпромторга

Ага шазз. ПП есть, для госучереждений.

импортные микросхемы? Импортозамещение

В RepkaPi только плата своя, Allwinner ещё не импортозаместили. Но это-то ладно, можно на ВЕ48 систему сбора данных сваять. А вот 24-х битный АЦП сильно сомневаюсь что можно импортозаместить, а если и так, то цена будет кусачая.

А это Минпромторг решает есть у нас аналог или нет

Сигма дельта АЦП синхронизировать идея так себе, потому что это сброс цифрового фильтра и в результате измеренное напряжение - это некое среднее значение между t0 + N/Fs и t0 + (N+1)/Fs, где N в зависимости от фильтра около 3, 4, 5.

Хотя есть, например, ads1263 где это аккуратно внутрь всё спрятано и есть режим одиночного преобразования по дерганию за отдельную ногу START, и 1cycle settling фильтры.

Но имхо правильнее запустить АЦП в непрерывном режиме, а таймером в МК захватывать когда именно дергался DRDY и когда прилетал PPS, а потом сделать передискретизацию в нужные моменты времени

https://ru.dsplib.org/content/resampling_lagrange/resampling_lagrange.html

Благодарю за инфу! Мне тоже больше нравится такое решение, когда АЦП живет своей жизнью, а время каждого преобразования вычисляется внешними средствами, Я уже писал в комментариях, что GNSS может измерять время внешнего сигнала, таким сигналом как раз может быть флаг готовности DRDY. Конечно, чтобы потом сводить данные с разных регистраторов, придется лишний раз делать интерполяцию, но это уже не так страшно.

Какой лютый оверхед если юзать линух

Разрядность АЦП не менее 24 бит, чтобы за счет широкого динамического диапазона наблюдать едва заметные сигнальчики.

"не менее" - круто, 3 V делить на 16 777 216 - разрешение не менее 1/5 микровольта. Нет смысла хвататься за малинки и линуксы, если у вас нет готового аналогового модуля, который это может и вы можете его пусть не поверить, а хотя бы проверить на аналоговом стенде. Аналоговая и цифровая земля должны быть правильно разделены, должен быть экран для модуля, общий экран для модуля и датчика, потом земли, экраны, питание и spi интерфейс должны быть правильно соеденены. Spi должен заходить так, чтобы не вносить шум и т.д и т. п. Вся остальная цифра должна быть снаружи. Нельзя вот так просто взять и собрать устройство с разрешением 1/5 микровольта.

В работе, кстати, готовая плата используется, ссылка. Да, кстати, решение было довольно спонтанное и не самое обдуманное, гнаться за разрядностью не стоило. Шумы самого датчика (не считая помехи, описанной в статье) с лихвой перекрывают эти микровольты. Я лишь показываю, что АЦП может цифровать без дополнительной помехи, но почему-то этого не делает в режиме синхронизации от внешнего источника.

 ...гнаться за разрядностью не стоило. Шумы самого датчика (не считая помехи, описанной в статье) с лихвой перекрывают эти микровольты. Я лишь показываю, что АЦП может цифровать без дополнительной помехи, ...

Нет смысла что-либо показывать, пока у вас нет аналогового модуля. Неплохо бы определиться какой уровень помех на АЦП может обеспечить ваша квалификация, затем посчитать разрядность АЦП. Скорее всего уложитесь в 12 разрядов - можно просто взять микроконтроллер с 12-битным АЦП и сделать как написано в соответствующем Application Notes.

Нельзя вот так просто взять и собрать устройство с разрешением 1/5 микровольта.

боромир.jpg

С одной стороны как бы да, просто два разных куска меди не из одной катушки вполне могут дать до 1мкВ/С термоЭДС, и просто подключив как попало в эту китайскую плату свой датчик можно легко получить "погодную станцию" вместо измерения нужного мкВ напряжения.

С другой стороны PSRR у сигма-дельта АЦП обычно какой-то запредельный, и чтобы изгадить измеряемый даже субмкВ сигнал неправильным питанием, землями и цифровым интерфейсом надо сильно постараться, и при измерении совсем уж НЧ сигналов все ВЧ помехи очень хорошо им дополнительно отфильтруются.

Вот собственные шумы различных вольтметров измеренные с закороткой на входе:

https://raw.githubusercontent.com/pavel212/noise/main/integrators_v.png

ad7195 который при усреднении в пару минут (0.01SPS) вполне дотягивается до 1нВ шумов, запитан был при этом тупо от +5 USB от ПК к которому был подключен, схема платы AD7195EBZ на сайте AD есть.

а ADS1263 на этом же графике, с теми же 6нВ/rtHz шумами на максимальном усилении встроенного PGA, это точно такие же китайские модули от waveshare как у автора, и питание на них сделано действительно как попало, то есть вообще никак. На графике они правда были запитаны аккуратно, но ради интереса я их напрямую в USB тоже втыкал, AVDD == +5VUSB, без каких-либо дополнительных фильтров, и шумы только процентов на 40-50% подрастали от даташитных значений, а с дополнительным LDO, +5USB->4.9V AVDD становились обратно как по даташиту.

Последние лет пять занимаюсь внедрением регистрирующего оборудования на базе микрокомпьютера Raspberry Pi

За 5 лет на выходе прототип из готовых модулей?
Самое главное не огласили детали техзадание, что физически измеряется, какая точность нужна как по напряжению, так и по времени. И это выглядит как сами придумали проблемы и героически их решаете.

Судя по тому, что 50 выборок в секунду достаточно, аналоговая полоса сигнала до 10гц. Зачем здесь сверхточность синхронизации по GPS - непонятно.
Ну допустим, сигнал медленно меняющийся, 50sps на выходе. Вполне нормально запустить преобразователь в свободном режиме на 1000sps или как у вас 500. И потом раз в секунду делить полученный массив на 50 частей. И даже никакая сложная передискетизация, как выше предлагали, не нужна. Не страшно, если на один выходной семпл придется 20 отсчетов, полученных с АЦП или 19.

Помехи на столе: вы используете готовую "игрушечную" плату, в которой разрабочики не задумавались об аналоговой части особенно. Опорник с собсвенным шумом в 120uV, Питание напрямую от RPI... Ну и самое главное, частота 50гц "на столе" будет проникать со всех сторон. Про разводку и землю уже сказали выше.

То что у вас не получилось 20ms на измерение после синхронизации - выглядит вполне нормально для "первого" измерения (что указано как t18 = 2.18ms) + собственно длина строба Sync.

Ну и если уж так хочется чтоб все было жестко сихнронизировано - убрать кварц и использовать GPS-PPS->PLL (x7.68M). По идее u-blox модули и сами имют PLL и выдавать до 10Мгц на выходе, но там надо смотреть что с джиттером. Но подозреваю, для вашей задачи можно пренебречь. Но тут все равно начинаются проблемы если необходимо использовать больше 1 входа.

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

частота 50гц "на столе" будет проникать со всех сторон

Хорошо, а почему помеха проявляет себя даже в том случае, когда нет никаких 50 Гц? Когда питание от аккумулятора через DC-DC преобразователь. Я об этом не пишу напрямую, но я всё-таки добавил про "не зависит от источника питания".

По идее u-blox модули и сами имют PLL и выдавать до 10Мгц на выходе

Про эту фишку знаю. Там вся сложность в том, что, хорошо, u-blox теперь тактирует АЦП вместо кварца. А как отследить момент, когда он начал тактировать? У ублоксов момент переключения частот слегка отвратительно реализован. И ведь потом надо когда как-то считать эти тики 7.68М, чтобы не потерять привязку к моменту переключения частот (выраженному в чч:мм:сс), в полевых условиях, когда регистрация должна запускаться автоматически, это непросто. Так что, я считаю, что пошел по пути наименьшего сопротивления. И, повторюсь, синхронность отрабатывается на все деньги, и мне уже не важно, что ради этого пришлось пойти на сложности с GNSS и пр. Подробности я могу в личку написать.

Ну и самое главное, RPI и и линукс тут явно избыточны

Согласен. Единственное, имхо (повторюсь: опыта немного, могу ошибаться), работа с физическими носителями в ESP32/Ардуине - тихий ужас. А у меня часто бывает, что пропадает Wi-Fi, но малинка и в таком случае продолжает писать в файл на флэшку. Я спорить не буду, это "ошибка молодости", но у меня в обсерватории пять комплектов регистраторов на Raspberry работает, не вижу смысла начинать с чистого листа.

Ну допустим, сигнал медленно меняющийся, 50sps на выходе. Вполне нормально запустить преобразователь в свободном режиме на 1000sps или как у вас 500. И потом раз в секунду делить полученный массив на 50 частей.

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


Только не делить полученный массив на 50 частей, раз уж, как предлагаете, преобразователь в свободном режиме, то считать "running average" ("rolling mean"). .

Первый вопрос: а какая точность по времени у выхода GNSS приёмника? В смысле какая максимальная гарантированная разница между таймингами прихода импульсов от этих конкретных моделей приёмников разнесенных на несколько сотен метров?

Просто если можно запустить АЦП в свободным режиме на высокой частоте, а по сигналу лишь выбирать какие группы импульсов усреднять, то это было бы проще и надёжнее. Но, конечно, если приемник может выдавать сигналы очень точно, то не прокатит.

Ну и в целом, я бы такой проект начинал с анализа предела точности, чтобы знать, что можно добиться на этом оборудовании, а что нельзя (либо чтобы выбирать, собственно, приемники, если их можно выбирать разные).

Хотя и в таком случае правильно было бы использовать свободный режим. Просто вычислить локально время между импульсами от приёмника, вычислить время от подачи сигнала запуска до первого измерения и, получив очередной импульс, выждать разницу первого и второго и самостоятельно выдать сигнал старта, чтобы получить результат аккурат к следующему импульсу. Можно периодически повторять калибровку, если устройство работает неделями и месяцами.

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

Наконец, конечно, там лучше подходит не малинка, а ESP32 и аналоги. Та же поддержка WiFi, но риалтайм из коробки без заморочек с модулями ядра. Плюс компактнее и меньше жрёт. По сложности программирования должно быть легко за счёт Arduino SDK, который осваивается прямо по ходу разработки, если уже есть знакомство с embedded.

Первый вопрос: а какая точность по времени у выхода GNSS приёмника? В смысле какая максимальная гарантированная разница между таймингами прихода импульсов от этих конкретных моделей приёмников разнесенных на несколько сотен метров?

Точность прихода импульсов у отдельно взятого u-blox NEO-7M 60 наносекунд. Т.е., если я правильно понимаю разработчика, в 99% очередной фронт PPS сдвинут относительно истинного положения (как в атомных часах) максимум на 60 нс. Точность системы в целом не знаю, как посчитать, очень грубо 120 нс для двух приемников, 180 нс - для трех и т.д. Скорее всего, речь идет о величине, которая для 5-6 датчиков не превышает 1 мкс.

Наконец, конечно, там лучше подходит не малинка, а ESP32 и аналоги. Та же поддержка WiFi, но риалтайм из коробки без заморочек с модулями ядра.

А я уже выстроил свою экосистему на малинках) Надеюсь лишь, что какой-нибудь новичок, прочитав статью, зайдет в комментарии и сделает для себя выводы, какое оборудование ему больше подходит. Мне нравится в малинках, что не нужно городить монструозный код-прошивку, и разные задачи можно поручить разным прогам. Да, я писал уже в комментариях, что Wi-Fi у меня не всегда может работать, а запись не прерывается, я просто потом отправляю файлы на сервер через cron и rsync, как появляется сеть.

А какие требования к синхронности? Просто данный ацп дельта-сигма и каждый раз делать синк это сбивает конвеер, возможно поэтому проблемы с малыми сигналами. Возможно первые три измерения вообще не валидны - это часто для сигма-дельта. Может имеет смысл работать на более высокой частоте постоянно, а по синхросигналу с gps выдергивать нужное измерение.

Вообще реалтайм (точная синхронизация) и линукс плохо сочетаются. Я бы поставил мелкий мк на который возложил задачи синхронизации, а с него уже на распберри с метками времени. Хотя в таком случае распбери вообще ввглядит лишним и избыточным на каждом датчике.

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

А какие требования к синхронности?

Поскольку частота маленькая, у меня требование довольно грубое: смещение сигнала по времени на 20 мс (на 1 сэмпл) в одну или в другую сторону неприемлемо. Собственно, долгое время я пользовался кодом на Python, в котором реализовано то, о чем вы говорите - свободные измерения, не привязанные к внешней синхронизации. Существенных помех тогда не было, но и выдерживать нужное число сэмплов в час не получилось (пишу часовые бинарные файлы), т.е. итоговая частота получалась не 50 SPS, а типа 49.95. И при сопоставлении данных с разных регистраторов получалась дичь.

Python-код (старая прога)
#!/usr/bin/python
# coding: utf-8

# Программа чтения данных с микросхемы ADS1256
# и их последующей записи на HDD
# Исходная редакция 27.09.19
# Редакция 26.09.20

# В помощь: https://ph0en1x.net/106-rpi-gpio-installation-working-with-inputs-outputs-interrupts.html
# А также: http://academicfox.com/kak-yspolzovat-preryivanyya-s-python-na-raspberry-pi-y-rpi-gpio/

import os
import RPi.GPIO as GPIO
import time
import spidev
import struct
import datetime as dt

# Основные методы модуля RPi.GPIO
# GPIO.setmode(GPIO.BCM либо GPIO.BOARD)            выбор типа нумерации
# pin_mode = GPIO.getmode()                         получить текущий режим нумерации пинов
# GPIO.setup(pin_number, pin_mode)                  pin_mode - GPIO.IN (1) либо GPIO.OUT (0)
# GPIO.setup(7, GPIO.OUT, initial=GPIO.HIGH)        установка нач. состояния пина на вывод
# GPIO.setup(7, GPIO.IN, pull_up_down=GPIO.PUD_UP)  подтягивание к питанию, к примеру
# GPIO.input(pin_number)                            чтение пина, сконф. на вход
# GPIO.output(pin_number, pin_state)                установка уровня пина на выход
# GPIO.cleanup(pin_number)                          сброс состояний пинов

# Основные методы time
# time.sleep()
# time.time()               время в сек, прошедшее с 01.01.1970

# Основные методы spidev
# spi0 = spidev.SpiDev()        создать новый spi объект
# spi0.open(port, device)
# spi0.max_speed_hz = ...
# spi0.cshigh = True/False      уст. уровня на CS
# spi0.mode = [0b00...0b11]     полярность и фаза

# Основные константы
# Усиление
PGA_GAIN1   = 0 # Input voltage range: +- 5 V
PGA_GAIN2   = 1 # Input voltage range: +- 2.5 V
PGA_GAIN4   = 2 # Input voltage range: +- 1.25 V
PGA_GAIN8   = 3 # Input voltage range: +- 0.625 V
PGA_GAIN16  = 4 # Input voltage range: +- 0.3125 V
PGA_GAIN32  = 5 # Input voltage range: +- 0.15625 V
PGA_GAIN64  = 6 # Input voltage range: +- 0.078125 V
# data rate
DRATE_30000 = 0xF0 
DRATE_15000 = 0xE0
DRATE_7500  = 0xD0
DRATE_3750  = 0xC0
DRATE_2000  = 0xB0
DRATE_1000  = 0xA1
DRATE_500   = 0x92
DRATE_100   = 0x82
DRATE_60    = 0x72
DRATE_50    = 0x63
DRATE_30    = 0x53
DRATE_25    = 0x43
DRATE_15    = 0x33
DRATE_10    = 0x20
DRATE_5     = 0x13
DRATE_2d5   = 0x03
# Регистры
REG_STATUS = 0   # Register adress: 00h, Reset value: x1H
REG_MUX    = 1  # Register adress: 01h, Reset value: 01H
REG_ADCON  = 2  # Register adress: 02h, Reset value: 20H
REG_DRATE  = 3  # Register adress: 03h, Reset value: F0H
REG_IO     = 4  # Register adress: 04h, Reset value: E0H
REG_OFC0   = 5  # Register adress: 05h, Reset value: xxH
REG_OFC1   = 6  # Register adress: 06h, Reset value: xxH
REG_OFC2   = 7  # Register adress: 07h, Reset value: xxH
REG_FSC0   = 8  # Register adress: 08h, Reset value: xxH
REG_FSC1   = 9  # Register adress: 09h, Reset value: xxH
REG_FSC2   = 10 # Register adress: 0Ah, Reset value: xxH
# Команды
CMD_WAKEUP   = 0x00 # Completes SYNC and Exits Standby Mode
CMD_RDATA    = 0x01 # Read Data
CMD_RDATAC   = 0x03 # Read Data Continuously
CMD_SDATAC   = 0x0F # Stop Read Data Continuously
CMD_RREG     = 0x10 # Read from REG - 1st command byte: 0001rrrr 
                    #                   2nd command byte: 0000nnnn
CMD_WREG     = 0x50 # Write to REG  - 1st command byte: 0001rrrr
                    #                   2nd command byte: 0000nnnn
                    # r = starting reg address, n = number of reg addresses
CMD_SELFCAL  = 0xF0 # Offset and Gain Self-Calibration
CMD_SELFOCAL = 0xF1 # Offset Self-Calibration
CMD_SELFGCAL = 0xF2 # Gain Self-Calibration
CMD_SYSOCAL  = 0xF3 # System Offset Calibration
CMD_SYSGCAL  = 0xF4 # System Gain Calibration
CMD_SYNC     = 0xFC # Synchronize the A/D Conversion
CMD_STANDBY  = 0xFD # Begin Standby Mode
CMD_RESET    = 0xFE # Reset to Power-Up Values
# Аналоговые входы
AIN0   = 0 # Binary value: 0000 0000
AIN1   = 1 # Binary value: 0000 0001
AIN2   = 2 # Binary value: 0000 0010
AIN3   = 3 # Binary value: 0000 0011
AIN4   = 4 # Binary value: 0000 0100
AIN5   = 5 # Binary value: 0000 0101
AIN6   = 6 # Binary value: 0000 0110
AIN7   = 7 # Binary value: 0000 0111

# Основные задействованные пины
# DRDY - 11(bcm), 17(py)
# RST  - 12(bcm), 18(py)
# CS   - 15(bcm), 22(py)
# DIN  - 19(bcm), 10(py)
# DOUT - 21(bcm), 9(py)
# SCLK - 23(bcm), 11(py)

# Функция задержки в мкс
def delayus(us):
    time.sleep(us*10**-6)
# end

# Функция прерывания по событию
# падение с 1 на 0 на DRDY
def interrupting():
    GPIO.wait_for_edge(17, GPIO.FALLING)
# end

# Запись в регистр
def writereg(regID, value):
    interrupting()
    GPIO.output(22, GPIO.LOW)
    spi0.writebytes([CMD_WREG | regID])
    spi0.writebytes([0x00])
    spi0.writebytes([value])
    GPIO.output(22, GPIO.HIGH)
#end

# Основная функция
# Массив параметров регистров
regBuf = []

# Инициализация и настройка SPI, CS, DRDY
GPIO.setwarnings(False)
GPIO.setmode(GPIO.BCM)
spi0 = spidev.SpiDev()
spi0.open(0, 0)
spi0.max_speed_hz = 976000
spi0.mode = 0b01
GPIO.setup(22, GPIO.OUT)
spi0.cshigh = True
GPIO.setup(17, GPIO.IN, pull_up_down=GPIO.PUD_UP)

# Настройка управляющего скриптом пина 13(bcm) -> 27(py)
# GPIO.setup(27, GPIO.IN, initial=GPIO.LOW)

# Сброс регистров к дефолту
spi0.writebytes([CMD_RESET])
delayus(10)

# Настройка регистров
# автокалибровка вкл, остальное по дефолту
regBuf.append(0 << 3 | 1 << 2 | 0 << 1)
# analog input positive (AINP) = AIN1, AINN = AINCOM (земля, фактич.)
regBuf.append(AIN1 << 4 | AIN0)#1 << 3)
# внеш. тактирование выкл, сенсоры выкл, усиления нет
regBuf.append(0 << 5 | 0 << 3 | PGA_GAIN2)
# частота оцифровки 50 SPS
regBuf.append(DRATE_50)
# GPIO пины отключены
regBuf.append(0x00)

# Внесение данных в регистры
writereg(REG_STATUS, regBuf[0])
writereg(REG_MUX, regBuf[1])
writereg(REG_ADCON, regBuf[2])
writereg(REG_DRATE, regBuf[3])
writereg(REG_IO, regBuf[4])
delayus(50)

# Основная директория:
main_dir = '/home/pi/Documents/ADC'
os.chdir(main_dir)

# Время начала отсчета
base = dt.datetime(1970,1,1)

try:
    # Бесконечное создание новых файлов
    while True:
        # Возврат в главную директорию
        os.chdir(main_dir)
        # Дата и время начала записи файла
        now = dt.datetime.now()
        # Текущий год (4 цифры)
        now_year = now.strftime('%Y')
        if os.path.exists(now_year) != True:
            os.mkdir(now_year)
            os.chdir(now_year)
        else:
            os.chdir(now_year)
        # Текущий месяц (дополненный нулем)
        now_month = now.strftime('%m')
        if os.path.exists(now_month) != True:
            os.mkdir(now_month)
            os.chdir(now_month)
        else:
            os.chdir(now_month)
        # Текущий день (дополненный нулем)
        now_day = now.strftime('%d')
        if os.path.exists(now_day) != True:
            os.mkdir(now_day)
            os.chdir(now_day)
        else:
            os.chdir(now_day)
        # Создание нового файла в директории типа "день"
        # Если nomatter.bin уже существует
        if os.path.exists('nomatter.bin') == True:
            if os.path.getsize('nomatter.bin') != 0:
                f = open('nomatter.bin','rb')
                #f.seek(4)
                data1 = f.read(8)
                f.close()
                data2 = struct.unpack('d',data1)
                data2 = base + dt.timedelta(seconds=data2[0])
                new_name = data2.strftime("%Y-%m-%d-%H-%M-%S")+'.bin'
                os.rename('nomatter.bin', new_name)
            else: continue
        # Если не существует
        try:
            f = open('nomatter.bin','ab')
        except IOError:
            print('IOError!')
            break
        # Формат данных: беззнаковый int
        # format = 'I'
        # Формат данных: float32
        # format = 'f'
        # Формат данных: double64
        # format = 'd'
        
        # Оцифровка в непрерывном режиме
        start = time.time()
        data = struct.pack('d', time.time())
        f.write(data)
        end = start
        count = 0
        # На CS устанавливаем лог. '0'
        GPIO.output(22, GPIO.LOW)
        interrupting()
        spi0.writebytes([CMD_RDATAC])
        # Ожидание не менее 6.5 мкс
        delayus(10)
        # Запись файла в течение 1ч
        while (end-start)<=3600:
            interrupting()
            dataBuf = spi0.readbytes(3)
            # Получение временного отсчета
            #timeValue = time.time()
            # Конструирование 24-битного значения
            adcValue = dataBuf[0] << 16
            adcValue |= dataBuf[1] << 8
            adcValue |= dataBuf[2]
            if adcValue & 0x800000: adcValue |= 0xFF000000
            else: adcValue |= 0x00000000
            
            f.seek(8+count*4)
            data = struct.pack('I', adcValue)
            f.write(data)
            #data = struct.pack('d', timeValue)
            #f.write(data)
            
            end = time.time()
            count = count+1
        # end while
        interrupting()
        spi0.writebytes([CMD_SDATAC])
        delayus(10)
        f.close()
        # Переименование файла
        new_name = now.strftime("%Y-%m-%d-%H-%M-%S") +'.bin'
        os.rename('nomatter.bin', new_name)
    # end while
except KeyboardInterrupt:
    interrupting()
    spi0.writebytes([CMD_SDATAC])
    delayus(10)
    f.close()
    # Закрытие SPI и сброс пинов к дефолту
    GPIO.output(22, GPIO.HIGH)
    spi0.close()
    GPIO.cleanup(17)
    GPIO.cleanup(22)
    # GPIO.cleanup(27)
# return 0

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

Ход мыслей автора не могу себе представить. Ну поставили задачу - десяток, другой датчиков, регистрировать с них поток данных, распознать наступление события, выдать по этому событию инфу, координаты и т.п.

Первое, что приходит на ум - поговорить с спецом по датчикам - он скажет - есть вот головка, к ней копеешный 16-Bit Delta-Sigma ADC MCP3461 - и у нас на SPI выходе непрерывный поток up to 153.6 ksps преобразованных данных, типа знаю как сделать, чтобы помехи не проходили на вход ADC.

OK, потоки SPI данных есть. Надо их принять и зарегестрировать, т.е. сохранить в формате, к примеру, miniSEED на сервере. Спрашиваешь второго чела - он говорит, не вопрос, берем готовую платку разработчика STM32, принимаем на каждую несколько каналов SPI, упаковываем в поток из miniSEED файлов и закидываем файлы хоть на этот ITX формата сервер вот тут на столе, или, если надо на хоть на Amazon S3 Cloud.

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

Где здесь GNSS, который на территории необъятной уже не особо и ловится, где здесь линуксы, малинки, - непонятно.

Зарегистрируйтесь на Хабре, чтобы оставить комментарий

Публикации