Официальный сайт компании Arduino по адресу arduino.cc
Усреднение значений с потенциометра
- Войдите или зарегистрируйтесь, чтобы получить возможность отправлять комментарии
Пт, 14/03/2014 - 07:17
Мне нужно считать показания потенциометра и вывести в serial monitor.
Подключаем потенциометр к ардуино:
Не значительно меняем скетч из примеров
const int analogInPin = A0; // потенциометр int sensorValue = 0; // считанное значение void setup() { Serial.begin(9600); } void loop() { sensorValue = analogRead(analogInPin); Serial.print("sensor = " ); Serial.print(sensorValue); delay(50); }
И получаем значения от 0 до 1023. С этим все понятно и все так работает.
Для практического применения этой конструкции мешает "дребезг" потенциметра (из-за низкого качества последнего в состоянии покоя значения могут немного изменяться - "скакать").
Теперь вопрос. Как усреднить считывемые значения? Например, взять значения sensorValue за 10 циклов и посчитать среднее из них.
Головой понимаю, что нужно применить счетчик циклов, но вот не доходит, что вставить вместо вопросов :(
Может есть стандартное решение?
for (int i = 0; i < 10; i++) ???????? }
Думаю, для даннго случая и усреднения будет достаточно, но, если нужно, то могу поделится классом реализующий скользящий медианный фильтр. Он лучше работает когда есть редкие "большие выбросы в сторону". Фильтрация через среднее, хоть и сгладит "выброс", но все-таки почувствует его, а медиана, если "выброс" был кратковременным - вообще не заметит. Для потенциометра это не столь актуально, а вот для какого-нибудь ультразвукового дальномера.... (для чего собственно и делал).
>Может есть стандартное решение?
Конечно есть. Вы его и описали. Только почему-то не захотели в коде реализовать
Вот это и будет "лобовое" усреднее. А то что дал Tomasina дал это "скользящие среднее". С практической точки зрение его решение - лучше:
- время затрачиваемое на получение "одного усредненнго замера" не зависит от количества замеров "для усредния"
- не нужно следить за тем, что avg_sum переполнится (в "лобовом варианте", если вы захотите усреднять по очень большому количеству, скажем не 10, а 1000 замеров делать - у вас вылезет переполнение и бред в результатах).
Минус его решения только один: чуть чуть сложнее для понимания "как оно работает".
Минус его решения только один: чуть чуть сложнее для понимания "как оно работает".
Для честной реализации - надо еще и брать не 5 * old.value, а действительно "пять предыдущих результатов".
Минус его решения только один: чуть чуть сложнее для понимания "как оно работает".
Для честной реализации - надо еще и брать не 5 * old.value, а действительно "пять предыдущих результатов".
Неа.. тут все "честно". С точки зрения математики это идентично. Вопрос только. Что мы храним, скажем [1,1,2,3,3] (и вычисляем его сумму) что мы храним [1,2,2,2,3], что мы храним old.value=2 - результат один и тот же
sum([1,1,2,3,3])=10
sum([1,2,2,2,3])=10
2*5=10
Разница может "вылезти", только за счет округления в int. Но, при небольших количествах "старых значений" - она будет не существенной, а при больших достаточно хранить старое сначение в чем-то типа double.
P.S. Я же говорил что "минус в том что чуть сложнее понимать" ;)
Подскажите, а какой смысл в формулу подставлять два одинаковых значения?
этой строкой мы же их уравниваем?
>Подскажите, а какой смысл в формулу подставлять два одинаковых значения?
Ну, смыслов найти можно много... но скорее всего просто впопыхах человек чуток запутался в именах переменных.
Вот мой вариант "впопыхах", чуток подправленный, не проверял правда...
Опять мимо. первое значение будет в averageFactor (в нашем случае в 5 раз) меньше первого измеренного значения, а потом приближаться к нему по экспоненте. Если измеренное значение не будет меняться.
Имхо, надо первое измерение сделать перед loop{}, как-то.
Имхо, надо первое измерение сделать перед loop{}, как-то.
обычно в сетапе
>Имхо, надо первое измерение сделать перед loop{}, как-то.
Это уже зависит от конкретной задачи. Тут нет "правильно" или "не правильно". Начальный этап - это сугубо решение "автора". Волевое. Что считать "начальным состоянием". Если считать что "при выключенном контроллере показание сенсора ноль", то "разогрев датчика" - вполне правомерен и логичен. Если считать "при включении датчик СРАЗУ показывает правильное значение", то да... в setup() нужно впендюрить дополнительный sensorValue=analogRead(analogInPin);
Вообщем, что считать "началом соотворения мира, после подачи питания" - вопрос религии, а не логики :)
Всем ОГРОМНОЕ Спасибо! Даже не думал, что такой простой вопрос поднимет дискуссию. А до решения мне самому не хватило одного шага. Еще раз благодарю всех.
Да, мой косяк, возникший потому что этот пример собран из кусков большого проекта. Немного не в том порядке скопипастено. Правильно так: строку 17 из моего примера вставить после строки 13, т.е.
и только затем
Это было выкинуто из примера, чтобы не запутывать понимание сути. Данная ситуация у меня реализована следующим образом: первые N показаний, пока они меньше коэффициента сглаживания, выводятся "как есть" и одновременно буферизуются, а как только число измерений превысит коэффициент, выводятся уже сглаженные показания. Таким образом, с момента подачи питания показания сразу отображаются реальными, просто первые N измерений более скачкообразны ("дрейфуют"). В некоторых случаях важнее сразу видеть показания, максимально близкие к реальным, нежели плавный отчет от нуля.
P.S. можно даже еще немного смягчить этот дрейф, реализовав сразу и "скользящее среднее" и "скользящий коэффициент сглаживания" - первое измерение выводится как есть, второе усредняется с первым, третье усредняется с предыдущими и так далее до тех пор, пока счетчик не достигнет заданного числа сглаживаний, далее сглаживать по поседним N измерениям, но сейчас, думаю, это избыточно. С позиции отображения показаний это равноценно тому, что предложил leshak, но с точки зрения построения архитектуры программы более правильно - не приходится раскидывать куски кода, отвечающие за одно и то же, в разные идеологические блоки, следовательно проще в отладке и поддержании кода.
leshak, а можно все же полюбопытствовать у вас, что за алгоритм "скользящий медианный фильтр" ? , примерчик есть?:)
leshak, а можно все же полюбопытствовать у вас, что за алгоритм "скользящий медианный фильтр" ? , примерчик есть?:)
Не, примерчиков нет пока.... оно писалось "для себя побыстрому" (поэтому слегка сыровато). Смогу дать где-то ближе к вечеру (на другом компе исходники того проекта). Поэтому, пока, только саму "идею" алгоритма описать могу.
Первая часть: нахождение медианы числового ряда. Предположим у нас есть набор замеров: [100,50, 90,95, 999]
Что-бы найти медиану этого ряда, нужно:
1. вначале отсортировать его: [50,90,95,100,999]
2. Медианой этого ряда является - серидина ряда (по счету). Третий элемент. То есть 95-ть.
Как видим, абсолютные значения крайних элементов не влияют на "сглаженное значение". Важно просто что "они больше других", и они просто попадают "на края". Рояль играет только "серединка".
Если элементов четное количество, скажем [50,90,95,97,100,999], то медианой будет среднеарефметическое двух средних элементов (95+97)/2=96.
Втора часть - "скольжение". Предположим мы сделали новый замер: 85. Нам нужно добавить его кряду. Но, что-ра длина ряда (размер массива) остался неизменным один элемент из него нужно выкинуть. Выкидываем "самый старый" (то есть 100, в исходных данных он у нас был "самым первым замером").
Получаем: [50,90,95,999]. Добавляем к нему новый замер 85, имеем: [50,90,95,999,85] . Опять сортируем [50,85,90,95,999] и берем средние значение....
И так дальше, следующим мы будем выкидивать 50, потом 90, потом 95, потом 999... Если все новые замеры у нас будут "маленькими", то 999 так никода и не попадет в центр ряда. И этот "большой выброс в сторону" так и останется незамеченым.
Ну а можно, конечно и без скольжения... тоже "по тупому": сделал пять замеров - отсортировали, нашли середину, выкинули все сделали следующие пять замеров, отсортировали нашли их середину и т.д.
Но в этом случае приходится дольше ждать пока "медиана созрела" (при скольжении при каждом новом замере мы имеем сразу новую, актуальную медиану). И нужно сортировку реализовывать полноценную. А при скольжении... мы фактически добавляем только один элемент в уже отсортированный массив. Только один не на своем месте, следовательно сортировку можно оптимизировать под этот случай. Только один не "на на своем месте".
Хмм... В случае потенциометрического датчика (скажем положения) , я, не морочась программной реализацией, просто подбираю конденсатор, который, сглаживая дребезг, не увеличивает реакцию напряжения критично для наблюдаемого процесса. В остальных случаях, например выбросы , можно срезать дополнительным стабилитроном.
leshak, очень интересно! Хочется посмотреть как оно в деле будет :) Тогда жду рабочего примера. У меня есть нечто подобное, но базируется малость на других принципах. Вот тут я свой код и описание выкладывал, http://arduino.ru/forum/programmirovanie/rabota-s-atsp-v-spyashchem-rezhime-adc-noise-reduction сообщение №4.
Хмм... В случае потенциометрического датчика (скажем положения) , я, не морочась программной реализацией, просто подбираю конденсатор, который, сглаживая дребезг, не увеличивает реакцию напряжения критично для наблюдаемого процесса. В остальных случаях, например выбросы , можно срезать дополнительным стабилитроном.
Как писалось выше, это уже дело религии. Мне проще логическое "И" реализовать в программе, вам, видимо, будет проще аппаратно.
Хмм... В случае потенциометрического датчика (скажем положения) , я, не морочась программной реализацией,
Значит, скорее всего вы пришли в "мир ардуино" из мира "радиотехники". Вообщем это тоже "религиозный ворос". Мне, как программеру, все что "за пределами контроллера" - это гиморой. Каждая "лишняя деталька" воспринимается как "что-то мутное и если можно обойтись...".. Так что и дребезг кнопки, и потенциометр проще и быстрее "програмно" (лично мне). Конденсатора может банально не оказатся под руками... а програмно и "менять настраивать" легче....
Хотя ваш подход - не менее правильный чем мой. Тут скорее каждый выбирает область "где уверенней себя чувствует".
inspiritus, AS, leshak, товарищи, надеюсь мух от котлет все отделяют? т.е. разделяют сейчас в суждениях внешний шум, приходящий на вход и "внутримозговой" в меге :) Естессно первый лучше лечить аппаратно, а второй никак кроме как программно не вылечить.
"внутримозговой" в меге :)
А что это такое?
AS, это глубокая тема, откуда этот шум. Помехи от соседних элементов в чипе, девиация порога срабатования компаратора, шум всех активных компонентов. Всё это даёт суммарную девиацию +/- 2 отсчёта ацп при любом идеальном входящем сигнале.
AS, это глубокая тема, откуда этот шум. Помехи от соседних элементов в чипе, девиация порога срабатования компаратора, шум всех активных компонентов. Всё это даёт суммарную девиацию +/- 2 отсчёта ацп при любом идеальном входящем сигнале.
угу, полностью согласен, плюсанул...
...только вот в у топикстартера вопрос был про шумы хренового потенциометра ;)
... Точно, из радиотехники, с кучей деталек в коробочках и паяльной станцией, живущей постоянно на столе:)
Ардуинство все же не чистое программирование, а в равной мере сплав программера и электронщика, чего всем и желаю :)
Вообще нападки Т.С. на потенциометр по крайней мере странны, всё таки потенциометр не человек, ему перетаптываться сноги на ногу не нужно, с чего сопротивлению меняться, когда его не трогают. Если у Т.С. шум +/- 2 единицы, то это шум внутри меги. Если чуть больше -то возможно длинные провода или отсутствие конденсатора на ноге aref (которого на ардуино по дефолту нет). У реально иссохшегося, протёртого до дыр потенциометра скорее всего контакт будет пропадать полностью от любой лёгкой вибрации, но это значит что показание будет прыгать уже максимально.
leshak, очень интересно! Хочется посмотреть как оно в деле будет :)
Вот, выдрал из полу-заброшенного проекта и залил... https://bitbucket.org/alxarduino/leshakfilters
Правда сразу предупреждаю.. это писалось "для себя, сырое...", вообщем "внутреняя кухня в процессе".
Пример "длясебятности": размер "окна" ,только не четный должные быть (так код проще ;) И проверок этого - нет.
Минус его решения только один: чуть чуть сложнее для понимания "как оно работает".
Для честной реализации - надо еще и брать не 5 * old.value, а действительно "пять предыдущих результатов".
Неа.. тут все "честно".....
Хм... похоже все-таки вы были правы. Не совсем честная это формула (хотя и вполне достаточная для задачи ТС).
Попробовал тест написать.... не дает она ожидаемых значений. Если верить:Скользящая средняя — Википедия, то в формуле присуствует "самое старое значение":
А что-бы всегда иметь "самое старое значение", нам фактически нужно хранить в памяти "все окно целиком". То есть все-таки нужно иметь в памяти массив размером averageFactor.
Эх.... а так многообещающе выглядело. Значит "скольжение", позволяет избавиться нам от проблемы переполнения, но не от проблемы расхода памяти на больших avergeFactor.
К тому же, на больших averageFactor мы будем иметь проблему на малых значениях замеров. Когда sensorValue<averageFactor, а нас деление sensorValue/averageFactor будет давать значение меньше 1-ницы. Что после округления в int приведет к нулю. Вообщем при sensorValue<averageFactor мы будем иметь проблемму постоянного нуля на выходе. Так что, видимо, нужно, все-таки, переходить на хранение среднего в double.
Вообщем... прийдется еще похмурить мозг с этим. Что-бы добится "честности".
Вообще нападки Т.С. на потенциометр по крайней мере странны, всё таки потенциометр не человек, ему перетаптываться сноги на ногу не нужно, с чего сопротивлению меняться, когда его не трогают. Если у Т.С. шум +/- 2 единицы, то это шум внутри меги. Если чуть больше -то возможно длинные провода или отсутствие конденсатора на ноге aref (которого на ардуино по дефолту нет). У реально иссохшегося, протёртого до дыр потенциометра скорее всего контакт будет пропадать полностью от любой лёгкой вибрации, но это значит что показание будет прыгать уже максимально.
Дело в том, что в реальности этот "потенциометр" представляет из себя самодельный, ну или явно не серийный, проволочный переменный резистор. Примерно такой:
И конструктивно его поменять на что-то приличное гораздо сложнее, чем написать программное усреднение его показателей.
kiril22, ну и что , что самодельный, это ещё не повод все проблемы на него сваливать :) У вас есть какой нибудь хороший измерительный прибор? Мультиметр приличный или осциллограф, вот им проверьте меняется ли напряжение с движка резистора само собой, или нет. Если у вас скачет более 2 единиц, то и на другом приборе вы засечёте колебания более 10 милливольт.
Померить есть чем, да вот физически сделать это нереально, т.к. все это хозяйство за пару сотен км от меня. И в следующий раз когда поеду я смогу это сделать, но проще же будет мне залить прошивку с усреднением, чем искать причину скачков. Еще один факт против переменника, это то, что в процессе отладки переменное сопротивление из-за сложностей подключения заменялось рядом постоянных резисторов и скачков не было. НО! я не берусь утверждать, что вся проблема исключительно в переменнике; вполне допускаю, что присутствует и шум контроллера. Однако, если даже предположить, что шумит исключительно контроллер, разве усреднение его шума не является решением проблемы? Мне кажется, для обоих первопричин решение вполне годное.
Спасибо Вам за помощь и дискуссию.
ps: на входе микроконтреллера стоит 0,1 мкф на землю.
leshak, по поводу медианного фильтра нужна доп.консультация :) Куда там класть прочитанные значения с аналогового входа? И откуда потом результат забирать?
leshak, по поводу медианного фильтра нужна доп.консультация :) Куда там класть прочитанные значения с аналогового входа? И откуда потом результат забирать?
Два сопсоба доступны
Способ 1:
filter.registerValue(int measureValue) - класть
filter.getCurrentValue() - Забирать.
Забирать можно сколько хочешь раз (будет возвращать одно и тоже, до тех пор пока не будет "покладено" новое значение с помощью getCurrentValue()).
Способ 2:
filter.process(int measureValue) - сразу "кладет" новое значение, и возвращает "обновленное фильтрованное".
Кстати "Способ 2" показан в примере идущим с библиотекой. Только в том примере "значения" берутся из массива предопределенного, а вы будете брать из analogRead. То есть что-то типа такого:
"Это был воскресный день и я не лазил по карманам..." (С) в воскресенье отдыхать - вот мой девиз ;)
Вообещем пока светит солнце и вертолеты на головой не баражируют.... решил и фильтрацию средним тоже в виде библиотчески оформить.
Закинул туда же. https://bitbucket.org/alxarduino/leshakfilters
Заимплементил оба варианта. И упрощенный вариант (без выделения массива под хранение данных), и "честное скользящие".
Получились вот такие результаты:
Input - это входящие "сырые значения".
FilteredSimple - это фильтрованое "упрощенным способом" (примерно как в #7)
FilteredReal -это фильтрованное "по честняку"
Expected - это я "отфильтровал" руками на бумажке. Для "самопроверки".
P.S. И еще туда же добавил примерчик, где читается A0, и выводится в Serial сырое прочитанное значение, и отфильтрованное тремя способами медианой, "упрощенно", и "по честному". Так что можно сравнить что работает лучше в каждой конкретной ситуации.
leshak, Потестировал ваш фильтр :) Небольшая девиация результата к сожалению есть в любом из 3х вариантов, причём наименьшая как мне показалось у simpleAvgFilter. Скорострельность хорошая, наверное единицы миллисекунд занимает весь процесс.
leshak, Потестировал ваш фильтр :) Небольшая девиация результата к сожалению есть в любом из 3х вариантов,
Ну так не существует же "идеального фильтра" на все случае жизни. Зависит и от вида шума. Медиана - лучше работает для кратковременных импульсных помех, лучше справляется с амплитудными выбросами. А вот небольшие постоянные "плавания", она не сильно хорошо давит.
"Уровень фильтрации" можно увеличить если увеличить "размер окна". Не по 5-ти замерам делать усреднее, а скажем по 25-ти... Только расплатой за это будет что когда сигнал поменяется "по настоящему", на выходе фильтра мы увидем это "с запазданием". Чем сильнее "давим", тем медленее наша система будет реагировать на реальные события.
А еще можем комбинировать фильтры. Выход одного подавать на вход другого. скажем A0->median->avg
Тогда медиана будет нам убирать высокачастотные помехи, а avg - убирать малые "плавания".
А можем еще погуглить и найти десятки разных фильтров :) Или просто вообще потупому:
Тогда все "плавания" меньше 10-ти, будут вообще тупо игнорится.... можно сделать нелинейность реакции на отклонения, можно сделать имитацию "инерционности" и т.д. и т.п.
А еще... тут пришла подозрение, а может вам вообще не фильрация нужна? В чем вообще проблематичность того что аналоговый датчик "плавает" на 3-4 "попугая"? Как правило это "не проблема". Скажем если вы по показания датчика включаете/выключаете реле, и из-за дрожания у вас оно постоянно щелкает, так это решить нужно не фильтрацией, а гистерезисом (что и эффективней, и проще в реализации). Вообщем тут уже смотреть нужно "нафига оно вообще нужно".
А еще... тут пришла подозрение, а может вам вообще не фильрация нужна? В чем вообще проблематичность того что аналоговый датчик "плавает" на 3-4 "попугая"? Как правило это "не проблема". Скажем если вы по показания датчика включаете/выключаете реле, и из-за дрожания у вас оно постоянно щелкает, так это решить нужно не фильтрацией, а гистерезисом (что и эффективней, и проще в реализации). Вообщем тут уже смотреть нужно "нафига оно вообще нужно".
Иногда критично и +/- 1 один попугай. В вольтметре например. Я могу смирится с тем, что он врёт на свою положенную величину, но с тем, что он всё время меняет показания не могу :) Ну вот тот мой алгоритм, на который я давал ссылку я использую в зарядно-разрядном устройстве, там 1 попугай -это 6 милливольт, при рассчёте внутреннего сопротивления, когда вычисляется сопротивление из очень незначительной дельты напряжения - это весьма существенная единица, которой не хотелось бы жертвовать.
А еще... тут пришла подозрение, а может вам вообще не фильрация нужна? В чем вообще проблематичность того что аналоговый датчик "плавает" на 3-4 "попугая"? Как правило это "не проблема". Скажем если вы по показания датчика включаете/выключаете реле, и из-за дрожания у вас оно постоянно щелкает, так это решить нужно не фильтрацией, а гистерезисом (что и эффективней, и проще в реализации). Вообщем тут уже смотреть нужно "нафига оно вообще нужно".
Этот потенциометр задает параметр "установленная температура", который отображается на LCD. Так вот, там показания и прыгают, что исключительно визуально не красиво.
Округлите до 5-10ти единиц в таком случае, точность установки это кардинально не изменит. Установочные данные станут немного более дискретными, но код упростится значительно.
как округлить? есть пример?
У методики обычных округлений проблема в том, что всегда найдётся такое входное напряжение, которое попадёт в неустойчивое состояние, и результат задребезжит, польза лишь в том, что такое состояние будет в n раз реже, при округлении до n. Как показала практика из самых простых средств устранения девиации с потерей разрядности самое эффективное -это сравнение.
А чем это принципиально отличается от кода из #35? if(abs(newread-aread)>2)....
А чем это принципиально отличается от кода из #35? if(abs(newread-aread)>2)....
Идентично конечно, но этот просто нагляднее, не все хорошо понимают что такое модуль числа, и как его использовать :)
В этих двух способах есть только одна разница, у dimax есть возможность сделать границы разными, а всё остальное - философия и ползанье по асму с лупой, какой же из этих кодов и на сколько наносекунд работат быстрее
Не знаю говорили ли тут что об оверсемплинге, вот результат 16 семплов:
Можно взять 32 или 64 семпла, шумы тогда будут ещё меньше.
http://arduino.ru/forum/proekty/mnogokanalnyi-voltmetr-c-lcd-displeem-na-atmega-8#comment-57555
При 256 семплах шумов почти нету вообще, но ресурсы хавает капитально.
Подниму темку:
Я, что бы сгладить значени потенциометра, вот такой код накидал, работает на ура.
В целочисленной арифметике выражение
sensorValue = (sensorValue * (averageFactor - 1) + NewSensorValue) / averageFactor;
никогда не приблизится вверх к NewSensorValue на величину averageFactor .
Поэтому данное усреднение работает толко на float. Если в проекте не используются float, то об этом следует помнить.
Подскажите а как тот код приспособить к ультрозвуковому дачику
Тема хоть и старая но наткнулся на нее из гугла и в целом помогла но, есть ньюанс. По формуле получаются числа с точкой. например если ацп сначала 0 а потом 322 то по формуле будут сначала числа 16.1 потом 31.39
но каждый раз значения будут отбрасывать значение после точки и получиться 16 потом 31, и вконце такого счета и сглаживания ацп будет не 322 а 303= )
поэтому значение ацп предлагаю умножить на 100 а потом когда выводим делим на 100
если описанное в #52 справедливо, то это не будет работать как задумано.
Или делать вычисления с округлением, а не с "обрезанием" всего после запятой
sensorValue = ((sensorValue * (averageFactor - 1) + NewSensorValue) + averageFactor / 2) / averageFactor;
В целочисленной арифметике нет округления. Только обрезания. Округления в дробной. Если использовали дробную, то нет разницы. Без округления и так всё придёт к правильному значению. В целочисленной надо использовать стандартную формулу, а не её приближение.