max30100, китайский модуль: настроить, чтобы всё работало

runaway
Offline
Зарегистрирован: 25.09.2012
Здравствуйте, уважаемые участники!
 
Ни один из готовых примеров, которых мне удалось найти в сети - не работает. По крайней мере с прилагаемыми библиотеками. В том смысле, что всё "якобы работает" - выводит в монитор, на ЖКИ, но... Пульс скачет от нуля до непонятных значений, сатурацию кажет как 94 .. 95%, и она ни от чего не зависит вообще - дышу ли я кислородом из подушки, или в кулёк, чуть не до кругов перед глазами... 
 
Программировать не умею, пользователь Ардуино - чуть более, чем новоначальный. Пробовал крутить параметры в заголовках прилагаемых библиотек, согласно ДШ: выставлял режим "только пульсометр", "пульс + сатурация", выключал режим "высокое разрешение", менял кол-во сэмплов в секунду, битрейт, ширину импульса, токи сведодиодов... Железо реагирует видимым образом (меняется ток потребления, свечение красного светодиода, вижу, что меняется автоподстройка красного ... ). Но практического толку - ноль.
 
Нашёл статью merf (Raivis Strogonovs) по dsp оптического оксиметра. На Гите он выложил заголовок своей библиотеки. Без .срр, только .h; попробовал вставить этот заголовок в текущую библу, но ... вы понимаете. )) Печаль-беда, короче... А результаты у него красивые - пульс плавает в пределах одного удара в минуту, сатурация - в пределах долей процента. 
 
В связи с чем вопрос: не доводил ли кто из уважаемых участников до практического применения библиотеку указанного господина? Или может кто каким-то своим путём пошёл в приручении этого сенсора, и получил достойный результат? 
v258
v258 аватар
Offline
Зарегистрирован: 25.05.2020

http://reedpaper.com/archives/936 - говорят, у этих модулей есть детская болячка

runaway
Offline
Зарегистрирован: 25.09.2012

Это уже исправил давно, практически сразу. Если оставить И2Ц-подтяжки как есть, то Ардуино этот модуль вообще не видит.  Когда притягиваешь к 3,3 = всё якобы заводится как надо... Да не совсем.

Спасибо за помощь с Гитхабом, с библиотекой Merf'a (Raivis Strogonovs): https://github.com/xcoder123/MAX30100

Всё скачалось, прошилось, завелось, но... )) Вместо пульса - мусор, а сатурацию кажет в р-не 92%, и показания не меняются (кислородная подушка / дыхание в кулёк)... Надо настраивать, как я понимаю... Буду пока курить статью автора.

Также буду очень признателен, если у кого-то есть в наличии такой модуль, поделиться здесь своими результатами работы с данной библой/скетчем. Я два одинаковых модуля попробовал - одно и то же, +/-... Похоже, дело именно в настройках!

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

v258
v258 аватар
Offline
Зарегистрирован: 25.05.2020

Дочитайте по ссылке до конца

[quote]

Но не все так просто!

Как вы думаете, что сделали китайцы, когда узнали, что изготовили 100500 миллионов штук плат с ошибкой? Дышите глубже: вместо регулятора на напряжение 1,8V они начали устанавливать на плату регулятор с выходным напряжением 2,8V. Это подлый удар в спину радиолюбителю – не могу назвать это иначе. При таком напряжении питания шина I2C нормально работает с пятивольтовыми платами Arduino. На первый взгляд с модулем все в порядке. Он вроде бы работает и даже что-то измеряет. Однако для самой микросхемы пульсомера это напряжение далеко выходит за пределы допустимого диапазона.

Нет, микросхема не выходит из строя. Просто она измеряет что попало и как попало. В даташите четко сказано, что соблюдение диапазона питания необходимо для устойчивой работы встроенного счетчика (имеется в виду встроенный блок тактирования для АЦП и таймеров логики). В итоге мы с удивлением обнаруживаем, что сенсор профессионального уровня работает крайне нестабильно и совсем не так, как описано в интернетных статьях. Я убил целый вечер, чтобы понять, что происходит не так. Только когда ткнул щупом осциллографа в вывод SDA и увидел там логический уровень 2,8V, у меня появились подозрения.[/qoute]

 

runaway
Offline
Зарегистрирован: 25.09.2012

Всё прочитал до конца И СРАЗУ. Имею такую привычку... Всё промерил: 1,75; 3,25. Всё чётко. Только я дорогу не резал, пока просто поставил на торцы эти три резюка, и поверху соединил их свободные концы жилкой из МГТФа, кончик которой кинул на +3,3

По железу всё в ажуре.

runaway
Offline
Зарегистрирован: 25.09.2012

С резюками так поступил для того, чтобы продать, если не побеДЮ.

Пожевал - передай другому! (С)

Нужно было сохранить товарный вид.

зы: Торцы резюков поднял именно те, что нужно - ПРОВЕРИЛ. ))

runaway
Offline
Зарегистрирован: 25.09.2012

С отключенным режимом высокого разрешения вроде как чуть лучше детектирует пульс... Но всё равно, кажет шо попало...

Я не знаю, правильно ли я отключаю hi res mode... 

Было: setHighresModeEnabled(highResMode);

Сделал: setHighresModeEnabled(!highResMode);

(добавил "!")

Скомпилировалось без замечаний, и начал бежать столбик данных, если прикладываешь палец. Без "отрицания" сенсор не реагировал на прикладывание пальца - новые данные в мониторе появлялись только в момент прикладывания и снятия пальца. А сейчас столбик данных бежит, пока приложен палец. Но всё равно, показания пульса - бред. Как было, так и осталось...

nik182
Offline
Зарегистрирован: 04.05.2015

Да, он так и работает. Пульс - бред, насыщение 94 -96%. Поигрался и выкинул, как совершенно бесполезный.

runaway
Offline
Зарегистрирован: 25.09.2012

Периодически и на меня накатывают подобные настроения. Но нашёл я вот такой документ (о нём я упоминал выше):

https://morf.lv/implementing-pulse-oximeter-using-max30100

Из которого можно понять, что автору удалось добиться адекватных показаний. На скриншотах сом-монитора можно видеть, что показания пульса весьма стабильны (+/- 1 в минуту). 

Автор как бы предлагает читателю пройти вслед за ним по пунктам, и, насколько я понимаю, в итоге должно получиться что-то похожее на его скрины. Беда только в том, что этот текст подразумевает определённый УРОВЕНЬ подготовки повторяльщика, которого я, к великому сожалению, не имею. Статья не для начинающих. Я, например, не могу даже его плоттер на компе запустить - там на джава-скрипте что-то, я кликаю по ###.js в списке файлов, и получаю матюк винды. И всё. И спросить не у кого. А так, если бы я строго по шагам за ним повторял бы, глядишь, и добрался бы до финиша... Наверное.

А пункты примерно такие: 

1. Избавление от постоянной составляющей в сигнале (избавились - посмотрели сигнал без дс в плоттере)

2. Какая-то нормализация-сглаживание (сгладили - посмотрели, как было, и как стало)

3. ФНЧ Баттерворта

4. Вычисления

5. Вывод результатов в СОМ

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

runaway
Offline
Зарегистрирован: 25.09.2012

И по словам автора, нихрена из перечисленных пунктов этот мах30100 вообще не делает! Он просто импульсно светит двумя светодиодами (ИК и К) в мясо, а фотодиодом ловит отражённый свет. И отсылает в И2Ц просто сырец, который без надлежащей обработки вообще ничего не значит. А все вычисления делает Ардуина. В связи с чем у меня и закрадываются смутные подозрения, что "так оно и работает" отнюдь не потому что МАКСИМ накосячил, а ПОТОМУ ЧТО. 

nik182
Offline
Зарегистрирован: 04.05.2015

Тут согласен. Только сырые сигналы. Посмотрел библиотеку, как то всё грустно. Вот например  теория

И что выводится - других циферок о результату расчёта не будет. От 93 до 100.  

const uint8_t SpO2Calculator::spO2LUT[43] = {100,100,100,100,99,99,99,99,99,99,98,98,98,98,
                                             98,97,97,97,97,97,97,96,96,96,96,96,96,95,95,
                                             95,95,95,95,94,94,94,94,94,93,93,93,93,93}; 

 

runaway
Offline
Зарегистрирован: 25.09.2012

Установил авторский плоттер - тот, что из статьи. Получил тестер модулей на предмет их пригодности к измерениям. 

https://mega.nz/file/oQ0RRRKJ#qMwcMd5j3ZLIvx-vV68hOADskvDtykn236KUqzmIHr4

https://mega.nz/file/tYthRZKS#ffE6Zguy1aPh97nnET5cbvrsrgBvZlvGVc8q3bNJLEc

https://mega.nz/file/JF1VzTJL#SKq1Bnxb42iKblnZ1By7zeUNQVnVnEPl1qbd3Hsoj0g

 

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

Первый китаец демонстрирует шум по красному каналу амплитудой до 2000 попугаев. Хаотический.

Второй китаец шумит гораздо меньше, примерно одинаково по обоим каналам, но всё равно, картина далека от тех скринов, что в авторской статье. Там у него всё гладко.

Европейца убил по недосмотру, неправильными настройками ик-станции, дал на неё ~ 350 .. 400 С. Корпус поплыл, линзы остались без видимых изменений. И2Ц остался полностью живой, чип видится контроллером, коммуницируется, но в установившемся режиме сигналы по обоим каналам равны нулю.

Печаль-беда-непруха...

Владельцам модулей - вот ссылка на дистрибутив авторского плоттера, для унификации тестирования:

https://github.com/xcoder123/FlexiPlot/releases

Проверьте свои модули, поделИтесь скринами! 

runaway
Offline
Зарегистрирован: 25.09.2012

Для тестирования модулей залейте скетч из примеров в библиотечном архиве:

https://github.com/xcoder123/MAX30100

И выведите цифирь на авторский плоттер. Тогда мы получим унифицированное тестирование, т.е. можно будет сравнивать качество модулей удалённых владельцев. Интересны сигналы ИК и К каналов. На неделе ожидаю прибытия ещё одного европейца, с Диджикея. В этот раз буду осторожнее. Посажу его на модуль, и поделюсь результатами. Ну и от вас жду данных! Надо разъяснить этот вопрос. У меня сильное подозрение, что всё, что тургуется на Али - это не что иное, как отбраковка МАКСИМа. Потому что уже не единожды слышал подобные мнения от знающих людей, которые на этом обжигались... И цены на чипы совершенно разные - здесь китайский модуль продают по три бака, а оригинальный ЧИП (без модуля, просто обрезок ленты с чипом внутри) - за двенадцать. И вот за двенадцать я как раз и расплавил. Угораздило... Но ничё...

runaway
Offline
Зарегистрирован: 25.09.2012

Да, по условиям испытаний... На модуль кладём кусок гладкой стенки от бесцветной ПЭТ-бутылки, и сверху прижимаем неподвижным пальцем. Плотно, но не до усеру. Потому что если голым пальцем в модуль тыкать, то при касании кожей контактных площадок, у меня, например, идут левые помехи, к оптике отношения не имеющие. Пропускаем кусок с переходными процессами, после установки пальца, и когда устаканится автомасштабирование - тыцаем ПринтСкрин. И можно будет сравнить!

runaway
Offline
Зарегистрирован: 25.09.2012

Поменял местами чипы двух вышепомянутых китайских модулей. Друг с другом. Чтобы исключить из круга подозреваемых обвязку (стабилизаторы, конденсаторы)... Снял сигналы. Если кратко - ничего не поменялось: где были "волоса" с амплитудой в 2000 попугаев, на той плате они и остались, уже с новым чипом. Т.е. чипы ни при чём, получается - проблема в обвязке, или в разводке, или и в том и в другом сразу...

Теперь попробую поставить на самодельную плату, где сейчас стоит мёртвый оплавленный чип, один из чипов с китайских модулей... А вдруг!

зы: Чуда не произошло - картина не изменилась. Всё то же, как по ссылке "китаец_2", чуть выше. Смена платы и обвязки видимым образом на сигналы не повлияла. Разбираемся дальше...

nik182
Offline
Зарегистрирован: 04.05.2015

Получилось только это.  . При попытке переключиться в другие окошки программа падает и не поднимается.

runaway
Offline
Зарегистрирован: 25.09.2012

О, отлично... А других окошек там вроде как особо и нет... Все настройки по умолчанию, выставляем только номер сом-порта, скорость обмена, тыцаем "подключиться" и открываем окошко "add line chart". И оно едет себе справа налево. Палец неподвижно прижат к модулю через прозрачный диэлектрик, например, кусок пластика, вырезанный из стенки бесцветной ПЭТ-бутылки.

Прилагаю картинку наглядного сравнения. Амплитуда вашего сигнала выше моего примерно в десять раз. Соотношение "сигнал/шум" у вас в несколько раз ниже. И всё равно модуль у вас нерабочий, говорите... Ну, тогда, что уж о моих говорить...

https://mega.nz/file/MFlj2JJI#-42egnPhY6e7jtLj-w7zvojaxCWDla_bFUzgOPp9neo

Не знаю... Осталось вроде как попробовать с нормальным чипом... Вы какой скетч залили, откуда картинка?

 

runaway
Offline
Зарегистрирован: 25.09.2012
nik182
Offline
Зарегистрирован: 04.05.2015

#11 и #12 Картинка - printscreen.

runaway
Offline
Зарегистрирован: 25.09.2012

Да, у меня тот же скетч, из библиотечного архива. Т.е. сигнал идёт уже со всеми обработками, насколько я понимаю. По тексту скетча мне трудно судить... Он уже после всех фильтров данные в порт отсылает?

nik182
Offline
Зарегистрирован: 04.05.2015

Да и нет. Есть сырые, есть обработанные. Что рисует не разобрался, но судя то графикам, только после вычитания постоянной составляющей.  

runaway
Offline
Зарегистрирован: 25.09.2012

Разобрался, как открывать все три графика, что скетч выводит в порт. Просто три раза нажать на кнопку "Add line chart", и растянуть мышкой три открывшихся окошка по монитору, слева направо: Р0, Р1 и Р2. Видео по ссылке ниже. Р0 и Р2 не похожи на авторские скрины. Причём Р0 ВООБЩЕ не похож. Что дальше делать, пока не пойму. Через пару дней должен подъехать европеец, продолжу с ним.

https://mega.nz/file/NE0XBZQR#eL17Xyb0jzcHF21O5d5eTKLk4pGOLygzQTBQalEZEjk

runaway
Offline
Зарегистрирован: 25.09.2012

Может разные покровные материалы попробовать? Стекло тонкое? В ДШ вообще нарисовано стекло с окошком...

runaway
Offline
Зарегистрирован: 25.09.2012

У меня график "Р0" демонстрирует уровень сигналов в каналах R и IR около 200 единиц. В смысле - по шкале на оси Y. У вас, как я писал ранее, этот уровень раз в десять выше, чем у меня, т.е. около 2000 единиц. В то время как у автора уровень сигналов на графике "Р0" находится в районе МИЛЛИОНА единиц!! 

Вот в этом пункте, я считаю, и сосредоточена вся разница между нормальным сенсором и фуфлом, как у нас с вами...

nik182
Offline
Зарегистрирован: 04.05.2015

Количество единиц это не показатель. АЦП 16 бит. Больше 65535 быть не может. К тому же вычитаем постоянную составляющую, которая в несколько раз больше флуктуаций. Остается у меня 1200 в IR и 300 в RED. 

  . Как то реагирует. При выполнения упражнений по повышению сатурации прыгают показатели пульса и сатурации. Пульса кое как, сатурации адекватно.

runaway
Offline
Зарегистрирован: 25.09.2012

Если смотреть на авторский скрин Р0, то можно заметить, что постоянка, если и убрана, то не до конца. От уровня сигнала, как мне кажется, решающим образом зависит отношение "сигнал/шум"... Миллион попугаев можно легко разбить на 16 бит, а вот 200 на те же 65000+ отчётов... Ннну, не знаю... Как по мне - нет уровня сигнала, нет и битности, нечего квантовать?.. Но это такое... Посмотрим, что европеец покажет. Пн, надеюсь, расставит всё по своим местам...

nik182
Offline
Зарегистрирован: 04.05.2015

Нельзя разбить не разбиваемое. Мы получаем сырые данные. Это данные АЦП.  Их сразу рисует программа на графике. Если убрать палец с датчика,а потом обратно приложить, то несколько секунд данные идут без вычитания постоянной оставляющей. У меня прыгает от -32000 до +32000. Это как бы намекает, что АЦП 16 бит. Прикладывал к разным местам тела. На сонной артерии размах +-50 единиц. Самый большой размах на подушечке большого пальца правой руки при непосредственном вдавливании в датчик - картинка #15. Любое удаление - даже через пластик от бутылки резко сокращает размах колебаний.  

runaway
Offline
Зарегистрирован: 25.09.2012

Я так понимаю, что Р0 - это сырец? Авторский скрин показывает, надо думать, именно установившиеся данные, не переходный процесс? Исхожу именно из этого. У него установившийся процесс - и у меня установившийся. Я прикладываю палец (через бутылку, через полиэт. пакет, наконец голый - на размах сырца эти нюансы у меня совершенно не влияют. И он прикладывает ПАЛЕЦ, надо думать - поскольку иное в статье не указано? Считаем, что и он прикладывает палец. Т.е. исходные условия у нас идентичны (библиотека, скетч, часть тела, средство тестирования). Получается, что разные у нас только экземпляры сенсора. Вот, и я так вижу, что у него сырой размах - миллион единиц, а у меня - двести единиц. И вот он наваливается на свой сырой миллион со всей шестнадцатибитной ненавистью. И получает уверенную, устойчивую детекцию пульса и сатурации. А я - не получаю. Именно потому, что нельзя разбить неразбиваемое, как вы совершенно справедливо заметили... Из чего и заключаю, что проблема именно в моём чипе. Вроде как прослеживается логика в моих построениях?

nik182
Offline
Зарегистрирован: 04.05.2015

Нет. Логика хромает. Больше 65535 сырой сигнал быть не может. Если у автора больше, то это сигнал после какой то обработки и сравнивать с нашими бесполезно. У меня размах 600 единиц максимальный, на мизинце правой руки - 300. Через стекло - 150. Сатурация меняется при насыщении  крови кислородом. Я много работаю с разными датчиками. Этот датчик, как источник данных меня вполне устраивает. Напрягает меня то, что данные надо обрабатывать, а теории единой нет. Все работы, которые прочитал имеют кривые, после которых идёт фраза  - для каждого человека индивидуальны и коэффициенты надо подбирать. Даже в описании библиотеки есть слова, что показывает мало, поэтому коэффициент увеличиваю. Я не специалист в области реакции датчика на насыщение кислородом, поэтому для меня он бесполезен. Получить точное значение я не смогу. Единственно можно следить за изменением и в случае, если поползёт вниз вызывать скорую. 

P.S. Написал и что то меня тыркнуло. Где миллионы? На первом рисунке статьи сырой сигнал 600 единиц - точно так же как у меня. Только перевёрнут.  

runaway
Offline
Зарегистрирован: 25.09.2012

График Р0, значения на вертикальной оси. Верхняя кривуля на уровне 950 000 (именно так - с пробелом). Может это просто 950? Скрин, где на рабочем столе открыты сразу три окна плоттера, + окно монитора поверх (с числовыми значениями).

runaway
Offline
Зарегистрирован: 25.09.2012

График Р1 (детекция пульса) у меня получается похожим на авторский спорадически, большей частью сразу после загрузки (подачи питания). Поймал картинку - и не дыши, так держи. Тогда может долго пульс рисовать, а вместе с ним и сатурацию. Но если палец снять, и попытаться вновь поместить его так, чтобы вернулась правильная картинка - ещё ни разу не получилось. Подбирай положение, не подбирай - бесполезно...

Насчёт графиков. Непонятно, почему сначала автор пишет, что первым делом избавляемся от постоянки (и да - он иллюстрирует это соотв. скрином в начале статьи, про который вы говорите), а на Р0 явно видна постоянная составляющая, причем, судя по отметкам оси У - немалая... Надо внимательнее почитать текст, там что-то было про "сведение" постоянных составляющих.

runaway
Offline
Зарегистрирован: 25.09.2012

Насчёт того, что "надо подбирать коэффициенты индивидуально"... Почитайте текст внимательнее: он пишет это к тому, что ему просто нечем КАЛИБРОВАТЬ свой датчик. Нет образцового прибора, по которому он мог бы выставить свой. В этом смысле - да, коэффициент нужно подбирать индивидуально! Но ведь даже в обычном мультиметре подстроечные элементы присутствуют, разве не так? Автор принял текущее значение сатурации волевым решением, "назначив" себе 94%, считая своё текущее самочуствие эталонным. Т.е. - с потолка. Ну, не совсем - согласно стандартной модели (средняя температура по больнице).
Но я думаю, что абсолютное значение сатурации - фигня по сравнению с её устойчивой динамикой, ОСОБЕННО на понижение. Вот именно такую динамику, на мой взгляд, и призваны обнаруживать подобные устройства. И именно поэтому они имеют как минимум право на жизнь, в т.ч. и в вашей аптечке. Тут я с вами не согласен.

runaway
Offline
Зарегистрирован: 25.09.2012

Ещё раз... Поскольку уровень моих познаний не оставляет мне шансов для инженерного анализа проблемы, мне приходится руководствоваться формальной логикой. Которая уверенно спотыкается о явное несоответствие двух экземпляров графика "Р0" - авторского и моего. И пока я не добьюсь хотя бы примерной их схожести, рассуждать о тонкостях прикладной DSP не вижу смысла - потому что прочие входящие идентичны, КРОМЕ чипа. Хотя бы в этом вы со мной согласны? Или даже моя формальная логика - и та хромает? ))

nik182
Offline
Зарегистрирован: 04.05.2015

Скачайте pdf. Там можно масштабировать картинки и точно видно какие цифры на оси.
Я именно то и сказал- только относительные измерения, при устойчивом понижении бить тревогу. Может быть я плохо выразился и Вы не поняли?
У меня после приложения датчика к любой части тела картинки примерно за 5 секунд стабилизируется. Похожими на авторские они становятся только при прикладывании пальцев рук.
Р0 график raw сигнала, после применения вычитания постоянной составляющей. Так как постоянная составляющая вычитается динамически, с постоянной времени около 5 секунд, то при прикладывании пальца в течении нескольких секунд виден весь сигнал, затем масштабирунтся только на переменную чаcть должен становиться похожем на авторский при прикладывании пальца. Отличие может возникать при разной светимости светодиодов. В программе есть настройка светимости. Поиграйте с ней .

runaway
Offline
Зарегистрирован: 25.09.2012

Уже игрался... Перебирал все токи красного по табличке в ДШ, при фиксированном токе ИК = 27,1 мА. Но это было ещё до того, как настроил отображение графиков... Разницы в поведении не заметил никакой.
Попробую ещё потыкаться, теперь - наблюдая за картинками!
зы: Если вы говорите, что толщина покровного стекла/пластика влияет на уровень сигналов, то я сделаю прямоугольное окошечко, по размеру чипа, в тонком диэлектрическом материале, типа тонкой лакоткани или какой-нибудь плёнки. И вы тоже сделайте такое же, чтобы отсечь недоразумения с электрическими наводками, которые могут возникать при касании голым пальцем токоведущих частей модуля. И прямо это окошечко плотненько напялить на чип, чтоб он оттуда выступал по высоте. Так, я думаю, будет лучше всего, для начала. Ну и светом от ламп всяких при измерениях туда лучше не светить всё-таки, я думаю. В чипе есть фильтр 50/60 Гц, но сейчас актуально светодиодное освещение, а там пульсации килогерцовые...

nik182
Offline
Зарегистрирован: 04.05.2015

Я беру модуль голыми пальцами и никаких наводок или изменений в сигналах не происходит. корпус датчика пластмасса, а с другой стороны дорожки i2c им вообще по барабану касание пальцев. Меня хватило только на кусок пластика от ПЭТ бутылки. Подносите потихоньку палец к датчику сверху до касания - как минимум 5 секунд миллиметр - и у Вас должно быть видно как увеличивается переменная составляющая при приближении. Вот на расстоянии 4мм  , при мягком касании датчик - лучший результат - размах переменной почти 1000 , при вдавливании в датчик  размах 700 -причина уменьшение тока крови из-за пережимания сосудов, без пальца - это просто шумы  размах 50. На лучшем результате шумы это 5 % почти не видны. У меня на лучшем результате постоянная составляющая 30000. Если поиграть и заставить её подняться до 32 то начинаются проблемы с переменной составляющей из за ограничения.  

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

runaway
Offline
Зарегистрирован: 25.09.2012

Не замечал такого. По-разному подносил. Создалось устойчивое впечатление, что сигнал живёт своей жизнью, поэтому и озаботился наводками, голыми пальцами и т.п. - не от нечего делать. Завтра попробую ещё, как вы описываете...
зы: Приехал датчик с Диджикея. Завтра посмотрим, что ОН покажет. Вроде на мази всё...

runaway
Offline
Зарегистрирован: 25.09.2012

Правильно ли я понял, что библиотека от xCoder123 побудила вас пересмотреть ваше отношение к обсуждаемому сенсору?

nik182
Offline
Зарегистрирован: 04.05.2015

Нет. Сенсор нормальный. Мне не нравиться, что он не дает конечных данных, а только упрощает интерфейс получения промежуточных данных. Теории обработки нет. Доверять результатам расчётов не получается. Пульс можно рассчитать, насыщение нет. 

runaway
Offline
Зарегистрирован: 25.09.2012

Добрался до компа, открыл ваши скрины. Вы обрезаете их заголовки (Р0, Р1, Р2). А из текстового комментария мне пока довольно затруднительно определить, что к чему относится... Давайте завершим наладку унификации тестов: на скринах заголовки графиков не обрезаем; в тексте сообщений графики называем либо однообразно, по согласованию сторон, либо, если хочется назвать как-то по-своему, своё название в скобочках обязательно дублируем авторским (Р0, Р1, Р2). Тогда не будет путаницы и ненужных вопросов.

runaway
Offline
Зарегистрирован: 25.09.2012

> Мне не нравится, что он не дает конечных данных, а только упрощает интерфейс получения промежуточных данных.

Подозреваю, что именно такое разбиение системы выбрано производителем неспроста. И неспроста пользователю оставлена туча настроек - ДАЖЕ в таком варианте разбиения. Здесь компромисс, я думаю... Оптическая оксиметрия - на сегодня единственный неинвазивный метод измерения (зачёркнуто) оценки SpO2. Не считая цветовой шкалы носогубного треугольника, который не обсуждаем. Иначе - только через иглу... Метод, на мой взгляд, уверенно занял предназначенную ему нишу (как индикатор ДИНАМИКИ). Всё это, конечно, было просчитано производителем, перед тем, как вкладываться в этот чип.

nik182
Offline
Зарегистрирован: 04.05.2015

runaway пишет:
Вы обрезаете их заголовки (Р0, Р1, Р2). А из текстового комментария мне пока довольно затруднительно определить, что к чему относится...

На Ваших графиках ( кстати почему Вы их размещаете на сторонних ресурсах? ) тоже нет никакой информации о названии кривых. RED IR не отражают точного содержания. Вид графиков и величины на оси У однозначно дают понимание что изображено. Всего 4 вида - Р0 -фильтрованный от постоянной составляющей, выдается всегда, значение от -32000 до +32000, Р1 - бит сердца с характерной синей линией порога начала отсчёта бита сердечных сокращений, выдаётся всегда, Р2 - Рассчитанный пульс и насыщение, выдается только если пульс рассчитан, Р3 - добавлен мной  сырой сигнал АЦП - выдается всегда, значение от 0 до 65535. 

Код 

#include <Arduino.h>
#include <math.h>
#include <Wire.h>

#include "MAX30100.h"

MAX30100* pulseOxymeter;

void setup() {
  Wire.begin();
  Serial.begin(115200);
  Serial.println("Pulse oxymeter test!");

  //pulseOxymeter = new MAX30100( DEFAULT_OPERATING_MODE, DEFAULT_SAMPLING_RATE, DEFAULT_LED_PULSE_WIDTH, DEFAULT_IR_LED_CURRENT, true, true );
  pulseOxymeter = new MAX30100();
  pinMode(2, OUTPUT);

  //pulseOxymeter->printRegisters();
}

void loop() {
  //return;
  //You have to call update with frequency at least 37Hz. But the closer you call it to 100Hz the better, the filter will work.
  pulseoxymeter_t result = pulseOxymeter->update();
  

  if( result.pulseDetected == true )
  {
    Serial.println("BEAT");
    
    Serial.print( "BPM: " );
    Serial.print( result.heartBPM );
    Serial.print( " | " );
  
    Serial.print( "SaO2: " );
    Serial.print( result.SaO2 );
    Serial.println( "%" );

    Serial.print("{P2|BPM|255,40,0|");
    Serial.print(result.heartBPM);
    Serial.print("|SaO2|0,0,255|");
    Serial.print(result.SaO2);
    Serial.println("}");
  }

  
    
  //These are special packets for FlexiPlot plotting tool
  Serial.print("{P0|IR|0,0,255|");
  Serial.print(result.dcFilteredIR);
  Serial.print("|RED|255,0,0|");
  Serial.print(result.dcFilteredRed);
  Serial.println("}");
  
  Serial.print("{P1|RED|255,0,255|");
  Serial.print(result.irCardiogram);
  Serial.print("|BEAT|0,0,255|");
  Serial.print(result.lastBeatThreshold);
  Serial.println("}");

  Serial.print("{P3|IR|255,0,255|");
  Serial.print(result.rawIR);
  Serial.print("|RED|0,0,255|");
  Serial.print(result.rawRed);
  Serial.println("}");

  

  delay(10);

  //Basic way of determening execution of the loop via oscoliscope
  digitalWrite( 2, !digitalRead(2) );
}

  MAX30100.h 

/*
Copyright (c) 2017 Raivis Strogonovs
 
Permission is hereby granted, free of charge, to any person obtaining a copy of 
this software and associated documentation files (the "Software"), to deal in the 
Software without restriction, including without limitation the rights to use, copy, modify, 
merge, publish, distribute, sublicense, and/or sell copies of the Software, and to 
permit persons to whom the Software is furnished to do so, subject to the following conditions:
 
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
 
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, 
INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR 
PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE 
FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 
ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

*/

#ifndef MAX30100_H
#define MAX30100_H

#include <Arduino.h>
#include <Wire.h>
#include <math.h>
/*----------------------------------------------*/
/* Config defines, you can tailor to your needs */
/*----------------------------------------------*/

/* MAX30100 parameters */
#define DEFAULT_OPERATING_MODE            MAX30100_MODE_SPO2_HR    
/*!!!IMPORTANT
 * You can't just throw these two values at random. Check Check table 8 in datasheet on page 19.
 * 100hz + 1600us is max for that resolution
 */
#define DEFAULT_SAMPLING_RATE             MAX30100_SAMPLING_RATE_100HZ
#define DEFAULT_LED_PULSE_WIDTH           MAX30100_PULSE_WIDTH_1600US_ADC_16

#define DEFAULT_IR_LED_CURRENT            MAX30100_LED_CURRENT_50MA
#define STARTING_RED_LED_CURRENT          MAX30100_LED_CURRENT_27_1MA


/* Adjust RED LED current balancing*/
#define MAGIC_ACCEPTABLE_INTENSITY_DIFF         65000
#define RED_LED_CURRENT_ADJUSTMENT_MS           500

/* SaO2 parameters */
#define RESET_SPO2_EVERY_N_PULSES     4

/* Filter parameters */
#define ALPHA 0.95  //dc filter alpha value
#define MEAN_FILTER_SIZE        15  

/* Pulse detection parameters */
#define PULSE_MIN_THRESHOLD         100 //300 is good for finger, but for wrist you need like 20, and there is shitloads of noise
#define PULSE_MAX_THRESHOLD         2000
#define PULSE_GO_DOWN_THRESHOLD     1

#define PULSE_BPM_SAMPLE_SIZE       10 //Moving average size





/* Enums, data structures and typdefs. DO NOT EDIT */
struct pulseoxymeter_t {
  bool pulseDetected;
  float heartBPM;

  float irCardiogram;

  float irDcValue;
  float redDcValue;

  float SaO2;

  uint32_t lastBeatThreshold;

  float dcFilteredIR;
  float dcFilteredRed;
  uint16_t rawIR;
  uint16_t rawRed;
};

typedef enum PulseStateMachine {
    PULSE_IDLE,
    PULSE_TRACE_UP,
    PULSE_TRACE_DOWN
} PulseStateMachine;

struct fifo_t {
  uint16_t rawIR;
  uint16_t rawRed;
};

struct dcFilter_t {
  float w;
  float result;
};

struct butterworthFilter_t
{
  float v[2];
  float result;
};

struct meanDiffFilter_t
{
  float values[MEAN_FILTER_SIZE];
  byte index;
  float sum;
  byte count;
};

/* MAX30100 register and bit defines, DO NOT EDIT */
#define MAX30100_DEVICE                   0x57

//Part ID Registers
#define MAX30100_REV_ID                   0xFE
#define MAX30100_PART_ID                  0xFF

//status registers
#define MAX30100_INT_STATUS               0x00
#define MAX30100_INT_ENABLE               0x01

//Fifo registers
#define MAX30100_FIFO_WRITE               0x02
#define MAX30100_FIFO_OVERFLOW_COUNTER    0x03
#define MAX30100_FIFO_READ                0x04
#define MAX30100_FIFO_DATA                0x05

//Config registers
#define MAX30100_MODE_CONF                0x06
#define MAX30100_SPO2_CONF                0x07
#define MAX30100_LED_CONF                 0x09

//Temperature registers
#define MAX30100_TEMP_INT                 0x16
#define MAX30100_TEMP_FRACTION            0x17


//Bit defines MODE Regsiter
#define MAX30100_MODE_SHDN                (1<<7)
#define MAX30100_MODE_RESET               (1<<6)
#define MAX30100_MODE_TEMP_EN             (1<<3)

typedef enum Mode {
    MAX30100_MODE_HR_ONLY                 = 0x02,
    MAX30100_MODE_SPO2_HR                 = 0x03
} Mode;

//Bit defines SpO2 register
#define MAX30100_SPO2_HI_RES_EN           (1 << 6)
typedef enum SamplingRate {
    MAX30100_SAMPLING_RATE_50HZ           = 0x00,
    MAX30100_SAMPLING_RATE_100HZ          = 0x01,
    MAX30100_SAMPLING_RATE_167HZ          = 0x02,
    MAX30100_SAMPLING_RATE_200HZ          = 0x03,
    MAX30100_SAMPLING_RATE_400HZ          = 0x04,
    MAX30100_SAMPLING_RATE_600HZ          = 0x05,
    MAX30100_SAMPLING_RATE_800HZ          = 0x06,
    MAX30100_SAMPLING_RATE_1000HZ         = 0x07
} SamplingRate;

typedef enum LEDPulseWidth {
    MAX30100_PULSE_WIDTH_200US_ADC_13     = 0x00,
    MAX30100_PULSE_WIDTH_400US_ADC_14     = 0x01,
    MAX30100_PULSE_WIDTH_800US_ADC_15     = 0x02,
    MAX30100_PULSE_WIDTH_1600US_ADC_16    = 0x03,
} LEDPulseWidth;

typedef enum LEDCurrent {
    MAX30100_LED_CURRENT_0MA              = 0x00,
    MAX30100_LED_CURRENT_4_4MA            = 0x01,
    MAX30100_LED_CURRENT_7_6MA            = 0x02,
    MAX30100_LED_CURRENT_11MA             = 0x03,
    MAX30100_LED_CURRENT_14_2MA           = 0x04,
    MAX30100_LED_CURRENT_17_4MA           = 0x05,
    MAX30100_LED_CURRENT_20_8MA           = 0x06,
    MAX30100_LED_CURRENT_24MA             = 0x07,
    MAX30100_LED_CURRENT_27_1MA           = 0x08,
    MAX30100_LED_CURRENT_30_6MA           = 0x09,
    MAX30100_LED_CURRENT_33_8MA           = 0x0A,
    MAX30100_LED_CURRENT_37MA             = 0x0B,
    MAX30100_LED_CURRENT_40_2MA           = 0x0C,
    MAX30100_LED_CURRENT_43_6MA           = 0x0D,
    MAX30100_LED_CURRENT_46_8MA           = 0x0E,
    MAX30100_LED_CURRENT_50MA             = 0x0F
} LEDCurrent;

class MAX30100 
{
  public:
    MAX30100( Mode mode = DEFAULT_OPERATING_MODE, 
              SamplingRate samplingRate = DEFAULT_SAMPLING_RATE, 
              LEDPulseWidth pulseWidth = DEFAULT_LED_PULSE_WIDTH, 
              LEDCurrent IrLedCurrent = DEFAULT_IR_LED_CURRENT,
              bool highResMode = true,
              bool debug = false 
             );

    pulseoxymeter_t update();

    void setMode(Mode mode);
    void setHighresModeEnabled(bool enabled);
    void setSamplingRate(SamplingRate rate);
    void setLEDPulseWidth(LEDPulseWidth pw);
    void setLEDCurrents( byte redLedCurrent, byte IRLedCurrent );
    float readTemperature();
    fifo_t readFIFO();
    void printRegisters();

    dcFilter_t dcRemoval(float x, float prev_w, float alpha);
    void lowPassButterworthFilter( float x, butterworthFilter_t * filterResult );
    float meanDiff(float M, meanDiffFilter_t* filterValues);

  private:
    bool detectPulse(float sensor_value);
    void balanceIntesities( float redLedDC, float IRLedDC );
    
    void writeRegister(byte address, byte val);
    uint8_t readRegister(uint8_t address);
    void readFrom(byte address, int num, byte _buff[]);

  private:
    bool debug;
    
    uint8_t redLEDCurrent;
    float lastREDLedCurrentCheck;
    
    uint8_t currentPulseDetectorState;
    float currentBPM;
    float valuesBPM[PULSE_BPM_SAMPLE_SIZE];
    float valuesBPMSum;
    uint8_t valuesBPMCount;
    uint8_t bpmIndex;
    uint32_t lastBeatThreshold;
    
    fifo_t prevFifo;
    
    dcFilter_t dcFilterIR;
    dcFilter_t dcFilterRed;
    butterworthFilter_t lpbFilterIR;
    meanDiffFilter_t meanDiffIR;

    float irACValueSqSum;
    float redACValueSqSum;
    uint16_t samplesRecorded;
    uint16_t pulsesDetected;
    float currentSaO2Value;

    LEDCurrent IrLedCurrent;
};

#endif

MAX30100.cpp 

/*
Copyright (c) 2017 Raivis Strogonovs
 
Permission is hereby granted, free of charge, to any person obtaining a copy of 
this software and associated documentation files (the "Software"), to deal in the 
Software without restriction, including without limitation the rights to use, copy, modify, 
merge, publish, distribute, sublicense, and/or sell copies of the Software, and to 
permit persons to whom the Software is furnished to do so, subject to the following conditions:
 
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
 
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, 
INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR 
PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE 
FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 
ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

*/

#include "MAX30100.h"

MAX30100::MAX30100(
        Mode mode, 
        SamplingRate samplingRate, 
        LEDPulseWidth pulseWidth, 
        LEDCurrent IrLedCurrent,
        bool highResMode,
        bool debug)
{
  this->debug = debug;
  currentPulseDetectorState = PULSE_IDLE;

  setMode(  mode );

  //Check table 8 in datasheet on page 19. You can't just throw in sample rate and pulse width randomly. 100hz + 1600us is max for that resolution
  setSamplingRate( samplingRate );
  setLEDPulseWidth( pulseWidth );

  redLEDCurrent = (uint8_t)STARTING_RED_LED_CURRENT;
  lastREDLedCurrentCheck = 0;

  this->IrLedCurrent = IrLedCurrent;
  setLEDCurrents(redLEDCurrent, IrLedCurrent );
  setHighresModeEnabled(highResMode);


  dcFilterIR.w = 0;
  dcFilterIR.result = 0;

  dcFilterRed.w = 0;
  dcFilterRed.result = 0;


  lpbFilterIR.v[0] = 0;
  lpbFilterIR.v[1] = 0;
  lpbFilterIR.result = 0;

  meanDiffIR.index = 0;
  meanDiffIR.sum = 0;
  meanDiffIR.count = 0;


  valuesBPM[0] = 0;
  valuesBPMSum = 0;
  valuesBPMCount = 0;
  bpmIndex = 0;
  

  irACValueSqSum = 0;
  redACValueSqSum = 0;
  samplesRecorded = 0;
  pulsesDetected = 0;
  currentSaO2Value = 0;

  lastBeatThreshold = 0;

}

pulseoxymeter_t MAX30100::update()
{
  pulseoxymeter_t result = {
    /*bool pulseDetected*/ false,
    /*float heartBPM*/ 0.0,
    /*float irCardiogram*/ 0.0,
    /*float irDcValue*/ 0.0,
    /*float redDcValue*/ 0.0,
    /*float SaO2*/ currentSaO2Value,
    /*uint32_t lastBeatThreshold*/ 0,
    /*float dcFilteredIR*/ 0.0,
    /*float dcFilteredRed*/ 0.0,
    /*uint16_t lastBeatThreshold*/ 0,
    /*uint16_t lastBeatThreshold*/0
  };

  
  fifo_t rawData = readFIFO();  

  result.rawIR=rawData.rawIR;
  result.rawRed=rawData.rawRed;
  
  dcFilterIR = dcRemoval( (float)rawData.rawIR, dcFilterIR.w, ALPHA );
  dcFilterRed = dcRemoval( (float)rawData.rawRed, dcFilterRed.w, ALPHA );

  float meanDiffResIR = meanDiff( dcFilterIR.result, &meanDiffIR);
  lowPassButterworthFilter( meanDiffResIR/*-dcFilterIR.result*/, &lpbFilterIR );

  irACValueSqSum += dcFilterIR.result * dcFilterIR.result;
  redACValueSqSum += dcFilterRed.result * dcFilterRed.result;
  samplesRecorded++;

  if( detectPulse( lpbFilterIR.result ) && samplesRecorded > 0 )
  {
    result.pulseDetected=true;
    pulsesDetected++;

    float ratioRMS = log( sqrt(redACValueSqSum/samplesRecorded) ) / log( sqrt(irACValueSqSum/samplesRecorded) );

    if( debug == true )
    {
      Serial.print("RMS Ratio: ");
      Serial.println(ratioRMS);
    }

    //This is my adjusted standard model, so it shows 0.89 as 94% saturation. It is probably far from correct, requires proper empircal calibration
    currentSaO2Value = 110.0 - 18.0 * ratioRMS;
    result.SaO2 = currentSaO2Value;
    
    if( pulsesDetected % RESET_SPO2_EVERY_N_PULSES == 0)
    {
      irACValueSqSum = 0;
      redACValueSqSum = 0;
      samplesRecorded = 0;
    }
  }

  balanceIntesities( dcFilterRed.w, dcFilterIR.w );


  result.heartBPM = currentBPM;
  result.irCardiogram = lpbFilterIR.result;
  result.irDcValue = dcFilterIR.w;
  result.redDcValue = dcFilterRed.w;
  result.lastBeatThreshold = lastBeatThreshold;
  result.dcFilteredIR = dcFilterIR.result;
  result.dcFilteredRed = dcFilterRed.result;  
  
  
  return result;
}

bool MAX30100::detectPulse(float sensor_value)
{
  static float prev_sensor_value = 0;
  static uint8_t values_went_down = 0;
  static uint32_t currentBeat = 0;
  static uint32_t lastBeat = 0;

  if(sensor_value > PULSE_MAX_THRESHOLD)
  {
    currentPulseDetectorState = PULSE_IDLE;
    prev_sensor_value = 0;
    lastBeat = 0;
    currentBeat = 0;
    values_went_down = 0;
    lastBeatThreshold = 0;
    return false;
  }

  switch(currentPulseDetectorState)
  {
    case PULSE_IDLE:
      if(sensor_value >= PULSE_MIN_THRESHOLD) {
        currentPulseDetectorState = PULSE_TRACE_UP;
        values_went_down = 0;
      }
      break;

    case PULSE_TRACE_UP:
      if(sensor_value > prev_sensor_value)
      {
        currentBeat = millis();
        lastBeatThreshold = sensor_value;
      }
      else
      {

        if(debug == true) 
        {
          Serial.print("Peak reached: ");
          Serial.print(sensor_value);
          Serial.print(" ");
          Serial.println(prev_sensor_value);
        }

        uint32_t beatDuration = currentBeat - lastBeat;
        lastBeat = currentBeat;

        float rawBPM = 0;
        if(beatDuration > 0)
          rawBPM = 60000.0 / (float)beatDuration;
        if(debug == true) 
          Serial.println(rawBPM);

        //This method sometimes glitches, it's better to go through whole moving average everytime
        //IT's a neat idea to optimize the amount of work for moving avg. but while placing, removing finger it can screw up
        //valuesBPMSum -= valuesBPM[bpmIndex];
        //valuesBPM[bpmIndex] = rawBPM;
        //valuesBPMSum += valuesBPM[bpmIndex];

        valuesBPM[bpmIndex] = rawBPM;
        valuesBPMSum = 0;
        for(int i=0; i<PULSE_BPM_SAMPLE_SIZE; i++)
        {
          valuesBPMSum += valuesBPM[i];
        }

        if(debug == true) 
        {
          Serial.print("CurrentMoving Avg: ");
          for(int i=0; i<PULSE_BPM_SAMPLE_SIZE; i++)
          {
            Serial.print(valuesBPM[i]);
            Serial.print(" ");
          }
  
          Serial.println(" ");
        }

        bpmIndex++;
        bpmIndex = bpmIndex % PULSE_BPM_SAMPLE_SIZE;

        if(valuesBPMCount < PULSE_BPM_SAMPLE_SIZE)
          valuesBPMCount++;

        currentBPM = valuesBPMSum / valuesBPMCount;
        if(debug == true) 
        {
          Serial.print("AVg. BPM: ");
          Serial.println(currentBPM);
        }


        currentPulseDetectorState = PULSE_TRACE_DOWN;

        return true;
      }
      break;

    case PULSE_TRACE_DOWN:
      if(sensor_value < prev_sensor_value)
      {
        values_went_down++;
      }


      if(sensor_value < PULSE_MIN_THRESHOLD)
      {
        currentPulseDetectorState = PULSE_IDLE;
      }
      break;
  }

  prev_sensor_value = sensor_value;
  return false;
}

void MAX30100::balanceIntesities( float redLedDC, float IRLedDC )
{
  
  if( millis() - lastREDLedCurrentCheck >= RED_LED_CURRENT_ADJUSTMENT_MS) 
  {
    //Serial.println( redLedDC - IRLedDC );
    if( IRLedDC - redLedDC > MAGIC_ACCEPTABLE_INTENSITY_DIFF && redLEDCurrent < MAX30100_LED_CURRENT_50MA) 
    {
      redLEDCurrent++;
      setLEDCurrents( redLEDCurrent, IrLedCurrent );
      if(debug == true) 
        Serial.println("RED LED Current +");
    } 
    else if(redLedDC - IRLedDC > MAGIC_ACCEPTABLE_INTENSITY_DIFF && redLEDCurrent > 0) 
    {
      redLEDCurrent--;
      setLEDCurrents( redLEDCurrent, IrLedCurrent );
      if(debug == true) 
        Serial.println("RED LED Current -");
    }

    lastREDLedCurrentCheck = millis();
  }
}


// Writes val to address register on device
void MAX30100::writeRegister(byte address, byte val)
{
  Wire.beginTransmission(MAX30100_DEVICE); // start transmission to device
  Wire.write(address); // send register address
  Wire.write(val); // send value to write
  Wire.endTransmission(); // end transmission
}

uint8_t MAX30100::readRegister(uint8_t address)
{
  Wire.beginTransmission(MAX30100_DEVICE);
  Wire.write(address);
  Wire.endTransmission(false);
  Wire.requestFrom(MAX30100_DEVICE, 1);

  return Wire.read();
}

// Reads num bytes starting from address register on device in to _buff array
void MAX30100::readFrom(byte address, int num, byte _buff[])
{
  Wire.beginTransmission(MAX30100_DEVICE); // start transmission to device
  Wire.write(address); // sends address to read from
  Wire.endTransmission(false); // end transmission

  Wire.requestFrom(MAX30100_DEVICE, num); // request 6 bytes from device Registers: DATAX0, DATAX1, DATAY0, DATAY1, DATAZ0, DATAZ1

  int i = 0;
  while(Wire.available()) // device may send less than requested (abnormal)
  {
    _buff[i++] = Wire.read(); // receive a byte
  }

  Wire.endTransmission(); // end transmission
}

void MAX30100::setMode(Mode mode)
{
  byte currentModeReg = readRegister( MAX30100_MODE_CONF );
  writeRegister( MAX30100_MODE_CONF, (currentModeReg & 0xF8) | mode );
}

void MAX30100::setHighresModeEnabled(bool enabled)
{
    uint8_t previous = readRegister(MAX30100_SPO2_CONF);
    if (enabled) {
        writeRegister(MAX30100_SPO2_CONF, previous | MAX30100_SPO2_HI_RES_EN);
    } else {
        writeRegister(MAX30100_SPO2_CONF, previous & ~MAX30100_SPO2_HI_RES_EN);
    }
}

void MAX30100::setSamplingRate(SamplingRate rate)
{
  byte currentSpO2Reg = readRegister( MAX30100_SPO2_CONF );
  writeRegister( MAX30100_SPO2_CONF, ( currentSpO2Reg & 0xE3 ) | (rate<<2) );
}

void MAX30100::setLEDPulseWidth(LEDPulseWidth pw)
{
  byte currentSpO2Reg = readRegister( MAX30100_SPO2_CONF );
  writeRegister( MAX30100_SPO2_CONF, ( currentSpO2Reg & 0xFC ) | pw );
}

void MAX30100::setLEDCurrents( byte redLedCurrent, byte IRLedCurrent )
{
  writeRegister( MAX30100_LED_CONF, (redLedCurrent << 4) | IRLedCurrent );
}

float MAX30100::readTemperature()
{
  byte currentModeReg = readRegister( MAX30100_MODE_CONF );
  writeRegister( MAX30100_MODE_CONF, currentModeReg | MAX30100_MODE_TEMP_EN );

  delay(100); //This can be changed to a while loop, there is an interrupt flag for when temperature has been read.

  int8_t temp = (int8_t)readRegister( MAX30100_TEMP_INT );
  float tempFraction = (float)readRegister( MAX30100_TEMP_FRACTION ) * 0.0625;

  return (float)temp + tempFraction;
}

fifo_t MAX30100::readFIFO()
{
  fifo_t result;

  byte buffer[4];
  readFrom( MAX30100_FIFO_DATA, 4, buffer );
  result.rawIR = (buffer[0] << 8) | buffer[1];
  result.rawRed = (buffer[2] << 8) | buffer[3];

  return result;
}

dcFilter_t MAX30100::dcRemoval(float x, float prev_w, float alpha)
{
  dcFilter_t filtered;
  filtered.w = x + alpha * prev_w;
  filtered.result = filtered.w - prev_w;

  return filtered;
}

void MAX30100::lowPassButterworthFilter( float x, butterworthFilter_t * filterResult )
{  
  filterResult->v[0] = filterResult->v[1];

  //Fs = 100Hz and Fc = 10Hz
  filterResult->v[1] = (2.452372752527856026e-1 * x) + (0.50952544949442879485 * filterResult->v[0]);

  //Fs = 100Hz and Fc = 4Hz
  //filterResult->v[1] = (1.367287359973195227e-1 * x) + (0.72654252800536101020 * filterResult->v[0]); //Very precise butterworth filter 

  filterResult->result = filterResult->v[0] + filterResult->v[1];
}

float MAX30100::meanDiff(float M, meanDiffFilter_t* filterValues)
{
  float avg = 0;

  filterValues->sum -= filterValues->values[filterValues->index];
  filterValues->values[filterValues->index] = M;
  filterValues->sum += filterValues->values[filterValues->index];

  filterValues->index++;
  filterValues->index = filterValues->index % MEAN_FILTER_SIZE;

  if(filterValues->count < MEAN_FILTER_SIZE)
    filterValues->count++;

  avg = filterValues->sum / filterValues->count;
  return avg - M;
}

void MAX30100::printRegisters()
{
  Serial.println(readRegister(MAX30100_INT_STATUS),  HEX);
  Serial.println(readRegister(MAX30100_INT_ENABLE),  HEX);
  Serial.println(readRegister(MAX30100_FIFO_WRITE), HEX);
  Serial.println(readRegister(MAX30100_FIFO_OVERFLOW_COUNTER), HEX);
  Serial.println(readRegister(MAX30100_FIFO_READ), HEX);
  Serial.println(readRegister(MAX30100_FIFO_DATA),   HEX);
  Serial.println(readRegister(MAX30100_MODE_CONF), HEX);
  Serial.println(readRegister(MAX30100_SPO2_CONF), HEX);
  Serial.println(readRegister(MAX30100_LED_CONF),  HEX);
  Serial.println(readRegister(MAX30100_TEMP_INT),   HEX);
  Serial.println(readRegister(MAX30100_TEMP_FRACTION),   HEX);
  Serial.println(readRegister(MAX30100_REV_ID),      HEX);
  Serial.println(readRegister(MAX30100_PART_ID),     HEX);
}

 

runaway
Offline
Зарегистрирован: 25.09.2012

Случилось чудо... А чудес в этих делах я не люблю. Постараюсь покороче.

В ДШ прочитал, что светодиоды ЖЕЛАТЕЛЬНО включать после всего остального, что есть в чипе. И решил пойти навстречу этим скромным пожеланиям - когда разводил свою платку, применил стабилизатор 3,3В с выводом включения. Обычно этот вывод (CE) просто соединяют со входом, но в данном случае я врЕзал туда RC цепочку с постоянной времени около 300 мс. Получилась аппаратная задержка включения светодиодов. Мне трудно судить, реализовано ли это пожелание производителя в библиотеке xCoder123, но аппаратное решение работает чётко - проверял. Однако же случилось так, что развести-то плату - развёл, а тех стабилизаторов под рукой не оказалось. И поставил то, что было - пришлось развернуть этот SOT23 на 45 градусов (на фото по ссылкам выше это видно), чтобы его ноги нашли нужные пады. Вот... И вся #бля, описанная выше, происходила именно в этом, кривом варианте.

А сейчас приехали и стабилизаторы с выводом включения, и сам чип с Диджикея. Очередной круг решил начать с контрольного запуска того чипа, что был посажен на моём модуле - просто заменив стабилизатор на штатный. Снял, припаял, налепил на модуль помянутую выше диэлектрическую наклейку с окошечком по размеру чипа, подал, приложил палец... И всё работает. Сразу смотрю на Р1 (детекция пульса) - именно этот график и показывал явные проблемы (спорадические пропадания пульса). А сейчас - ЧОТКО... Приложил - проехали переходные процессы - опочки - пошли "BEAT" в числовом мониторе, строго-стабильно через строчку... Такого ещё ни разу не было! Было через пятое на десятое. А сейчас - чётко. Приложил - есть пульс (после устаканивания в течение примерно пяти-семи секунд), снял - нет пульса. Полчаса уже развлекаюсь.

Что ещё изменилось в железе, кроме стабилизатора? Спиртобензином прошоркал весь модуль, зубной щёткой, до полного удаления видимых следов флюса, потом ещё раз залил спиртобензином, и промокнул оптику чистой салфеткой. Всё, включая оптику чипа. Всё остальное - строго то же самое, что и давеча... В сухом остатке - промывка спиртобензином и новый стабилизатор.

Продолжаю эксперименты. На очереди - кислородный баллон и кулёк. Ту би континюед...

runaway
Offline
Зарегистрирован: 25.09.2012

О, спасибо за сырец... Графики в своё облако выкладываю потому, что сюда не получается. Не цепляются. Попробую ещё раз... Пока в облаке, то, что получилось с утра:

https://mega.nz/file/kM9jjKwb#AFq5stUzJnC4UpWNt4TbJzLNEWiGtV9lwcaAR356xuw

runaway
Offline
Зарегистрирован: 25.09.2012

Решил начать с пульса. На физические нагрузки (приседания, отжимания) показания пульса реагируют адекватно. Однако уровень сигналов на графике Р1 (где синяя линия наезжает на розовые пики) заметно снизился, с ~ 350 ед. до ~ 200 ед. (всё это время питание не снималось, прибор работает уже примерно час). Причём я ожидал возрастание амплитуды после физической нагрузки (отжимания от пола), но этого не произошло - сигнал Р1 остался в р-не 200 ед. На средне-сильное нажатие пальцем Р1 реагирует ПОЛНЫМ пропаданием розового сигнала - график тянет слегка зашумленную розовую линию на уровне примерно -50 ед. У вас, насколько я помню, этот момент выглядит несколько иначе.

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

runaway
Offline
Зарегистрирован: 25.09.2012

Уровень сигнала Р1 у меня получается гораздо выше (воспроизводимо достигает р-на 600 ед.), если светить не "снизу вверх" (когда светится ноготь, например), а "справа налево" (если рука правая) - как бы "в профиль" пальца. Не совсем под 90 градусов, а где-то 60.

runaway
Offline
Зарегистрирован: 25.09.2012

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

А вообще - тайно рассчитываю на то, что чувствительность оригинального сенсора будет объективно ПОВЫШЕ. И что он будет гораздо менее зависим от указанных условий...

runaway
Offline
Зарегистрирован: 25.09.2012

Всё равно, наблюдаю неравномерность чувствительности датчика (уровня сигнала Р1) во времени. Сейчас любой палец прикладываю, в любом положении, в гораздо большем диапазоне прижимов - уровень гуляет в р-не 600 ед. Непонятно... Ни с того ни с сего. Не перегружал, питания не снимал, освещение не менялось, температура окружающей та же - вообще ничего не изменилось! Опять ожили мысли об отбраковке...

runaway
Offline
Зарегистрирован: 25.09.2012

А сейчас опять 120 .. 170 ед. Хоть как прижимай... Чувствительность живёт своей жизнью...

runaway
Offline
Зарегистрирован: 25.09.2012

Ещё немного поиграюсь, и посажу не эту же плату второго китайца - что он скажет. Или, вернее, сначала подключу МОДУЛЬ со вторым китайцем, посмотрю-поиграюсь, а потом пересажу его на мою плату. С европейцем пока повременю, нужно собрать побольше впечатлений от китайцев.

nik182
Offline
Зарегистрирован: 04.05.2015

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