en
![]() | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
Android Статьи Браузерные игры Программы О сайте | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
Ультразвуковой датчик расстояний HC-SR04 и ArduinoКак всякого любознательного человека меня увлекло изучение ардуино. Приобретя центральный процессор на базе платы Arduino поневоле пришлось задуматься о периферии. Начиная с мигания простыми светодиодами. Но этого показалось мало. И началось конструирование игрушки. На старый игрушечный джип была подключена система управлениями приводами движения колес и их поворотов. Естественно все подключалось через сборку L293D драйвера двигателей, чтобы не спалить процессорную плату Arduino и обеспечить нужные токи и напряжения на электропривод автомобиля. Но на этом я не остановился и пошел дальше. Да и какой же это компьютеризированный автомобиль, если нет никакой привязки к трассе. Необходимо было организовать обратный поток данных для выбора пути движения автомобиля. Были приобретенный ультразвуковой сонар HC-SR04. Для определения направления движения пришлось установить HC-SR04 на импровизированную поворотную платформу, обеспечивающую сканирование направления и выбора правильного пути движения автомобиля. Первые результаты дали неплохие данные, автик поехал весьма бодро, самостоятельно выбирая путь. Но возникли ряд вопросов, которые заставили задуматься, насколько сбалансирована система и как она взаимодействует. Был проведен ряд первичных тестов описанных здесь. Сразу пришлось решать вопрос о точности ультразвукового датчика. Для работы с этим датчиком в Arduino имеется библиотека NewPing.h, которая позволяет измерять расстояние как в дюймах так и в сантиметрах. Я решил проверить, на сколько точный этот датчик, и погрешность его измерений. Был собран массив статистики замеров расстояний до нескольких объектов и измерена точность ультразвукового датчика. В Интернете встречаются различные ультразвуковые датчики с одинаковой маркировкой «HC-SR04», но разные по схеме и распайке. На фотографии представлен описываемый в статье датчик. Программы для построения графиков, и повторения экспериментаДля написания программ и сбора статистики через COM-порт я использовал Arduino SDK. Для построения графиков использовал Open Office Calc, но можно использовать любой другой табличный процессор для этого. Для группировки данных звуковых задержек в первых экспериментах я использовал OpenOffice Base (БД) и SQL (код для Arduino см. в sketch 1). В последнем эксперименте я написал специальнуюю программу для группировки данных в Arduino, потомучто 2 кБ памяти для обработки всей собераемой статистики до 1 часа с неподвижными объектами (sketch 2). Arduino sketch 1. В первое время использовал этот код. Собирает «сырые данные» расстояний и отправляет сразу в Serial (в виртуальный COM-порт PC) строка за строкой. После запуска ждёт 5 секунд (мигает светодиодом). Затем пишет строки с: номером измерения, временем измерения (мс), расстоянием (задержкой звука, нс). Это простой скетч, Вы должны обработать полученные данные перед построением статистического графика.
// Sonar statistic's
#include <NewPing.h> #define TRIGGER_PIN 12 // Arduino pin tied to trigger pin on the ultrasonic sensor. #define ECHO_PIN 11 // Arduino pin tied to echo pin on the ultrasonic sensor. #define MAX_DISTANCE 400 // Maximum distance we want to ping for (in centimeters). Maximum sensor distance is rated at 400-500cm. NewPing sonar(TRIGGER_PIN, ECHO_PIN, MAX_DISTANCE); // NewPing setup of pins and maximum distance. void flashLed(int second) { for (int i=0;i delay(250); digitalWrite(13,LOW); delay(250); } } void setup() { Serial.begin(9600); // Open serial monitor at 115200 baud to see ping results. Serial.println("Collecting ping's data. Wait 5 second then start..."); pinMode(13, OUTPUT); flashLed(5); Serial.println("Start ping's... Number, Time after start , ping in microseconds"); } unsigned long number=0; // number of probe //* void loop() { number++; if ( number != 0 ) { unsigned int echoTime = sonar.ping(); // microseconds unsigned long t=millis(); Serial.print(number);Serial.print("\t"); Serial.print(t);Serial.print("\t"); Serial.print(echoTime);Serial.println(); delay(50); //todo check optimal time. Recomended ower 50 ms, but print take time too. } else { Serial.println("End of numbers. Wait 5 min then again..."); flashLed(5*60); // blink 5 min then again number=0 } } //*/ // as in ping demo: /* void loop() { delay(50); // Wait 50ms between pings (about 20 pings/sec). unsigned int echoTime = sonar.ping(); // microseconds Serial.print("Ping cm,ns:\t"); Serial.print(sonar.convert_cm(echoTime)); Serial.print("\t"); Serial.println(echoTime); } //*/ Sketch 2. После первого эксперимента я заметил, что данные расстояния не очень различны и могут поместиться в маленькой 1,5 килобайтной памяти Arduino UNO. Эта программа (код) при запуске ждёт 5 секунд (мигает LED светодиодом) затем отобаржает агрегированную статистику в Serial (COM-порт). Для построения графиков Вы можете скопировать полученные данные, вставить в таблицу, отсортировать по расстоянию (времени, нс) и сразу построить график. Этот код может проводить рассчёты с ошибками в Arduino после 1 часа работы, и может потерять некоторые данные, если измеряемый объект будет двигаться и не хватит выделенной памяти (ячеек массива) для сохранения всех данных. О потере данных будет предупреждение в консоли.
// Sonar statistic's
Представленный шаблон MeasureMath может использоваться не только для обработки расстояния, но и для других измерений. Код, представленный на этой странице распространяется по условиям GNU GPL License v2.
// (C) www.AlexeyK.com 2016. This source code under GNU GPL v2 license. #include <NewPing.h> #define TRIGGER_PIN 12 // Arduino pin tied to trigger pin on the ultrasonic sensor. #define ECHO_PIN 11 // Arduino pin tied to echo pin on the ultrasonic sensor. #define MAX_DISTANCE 400 // Maximum distance we want to ping for (in centimeters). Maximum sensor distance is rated at 400-500cm. NewPing sonar(TRIGGER_PIN, ECHO_PIN, MAX_DISTANCE); // NewPing setup of pins and maximum distance. void flashLed(int second) { for (int i=0;i<second*4;i++) { digitalWrite(13,HIGH); delay(250); digitalWrite(13,LOW); delay(250); } } void setup() { Serial.begin(9600); // Open serial monitor at 115200 baud to see ping results. Serial.println("Collecting ping's data. Wait 5 second then start..."); pinMode(13, OUTPUT); flashLed(5); Serial.println("Start ping's..."); } // ------ special statistic data 'processor' - group data into arduino ------ #define TArrayIndex unsigned int // template: array size, value type: signed/unsigned int/long/float(floating point values not recomended), count type: unsigned int/long template <int STAT_MAX_DATA, typename TValue,typename TCount> class MeasureMath { private: TCount MAX_COUNT; public: //#define MAX_COUNT 65535 // depends of TArrayIndex type: byte 255 or unsigned int 65535 TValue stat_value[STAT_MAX_DATA]; // measure data TCount stat_count[STAT_MAX_DATA]; // measure count TArrayIndex stat_using; // using array size for stat_count/stat_value. unsigned long stat_start; // time of start data collected. MeasureMath() { stat_using=0; stat_start=0; MAX_COUNT=0;//std::numeric_limits<TCount>::max(); // required limits.h MAX_COUNT--; // owerflow for get max value. TArrayIndex testI=STAT_MAX_DATA+1; // check TArrayIndex: must can store value up to STAT_MAX_DATA+1 if (testI<0 || testI!=STAT_MAX_DATA+1) Serial.println("ERROR: bad index type!"); } void onNewMeasure(long value) // this method call for register new measurement. { int dataI; for (dataI=0; (dataI<stat_using) && (stat_value[dataI]!=value);dataI++); // search cell if (dataI==stat_using) { // not found - make new cell if (stat_using>=STAT_MAX_DATA) { Serial.println("ERROR: no memory for store new value!"); return; } stat_value[stat_using]=value; stat_count[stat_using]=1; stat_using++; return; } if (stat_count[dataI]>=MAX_COUNT) { Serial.print("ERROR: owerflow for value ");Serial.print(value);Serial.println(); return; } stat_count[dataI]++; } void resetStat() { // clear statistic data stat_using=0; stat_start=millis(); } unsigned long getStatCountSumm() { // get all stored measurement count unsigned long s=0; for (TArrayIndex i=0;i<stat_using;i++) s+=stat_count[i]; return s; } TValue getStatMin() { // find minimal value if (stat_using==0) { Serial.println("No data for find min value."); return 0; } TValue m=stat_value[0]; for (TArrayIndex i=1;i<stat_using;i++) if (m>stat_value[i]) m=stat_value[i]; return m; } TValue getStatMax() { // find maximal value if (stat_using==0) { Serial.println("No data for find max value."); return 0; } TValue m=stat_value[0]; for (TArrayIndex i=1;i<stat_using;i++) if (m<stat_value[i]) m=stat_value[i]; return m; } double getStatAverage() { // return average of all stored measurement if (stat_using==0) { Serial.println("No data for find average value."); return 0; } double sum=0, count=0; for (TArrayIndex i=0;i<stat_using;i++) { double cnt=stat_count[i]; sum+=(double)stat_value[i] * cnt; count+=cnt; } //Serial.print("dbgAVG Count=");Serial.print(count);Serial.print(", sum=");Serial.println(sum); return sum/count; } double getStatStdE() { // math standard error of all collect data if (stat_using==0) { Serial.println("No data for find standard error."); return 0; } double average = getStatAverage(); double sumSq=0, count=0; for (TArrayIndex i=0;i<stat_using;i++) { double cnt=stat_count[i]; double a = sq(((double)stat_value[i]-average)) * cnt; sumSq+=a; count+=cnt; } double D = sumSq / count; // dispersion return sqrt(D); // standard error } void printStat() { //unsigned long timeSt=micros(); // for check CPU performance only. Or millis(); TValue sMin=getStatMin(); TValue sMax=getStatMax(); unsigned long sCount=getStatCountSumm(); double average=getStatAverage(); double variance=getStatStdE(); //unsigned long timeEnd=micros(); Serial.print("Count: ");Serial.print(sCount); Serial.print(", min: ");Serial.print(sMin); Serial.print(", max: ");Serial.print(sMax); Serial.print(", average: ");Serial.print(average); Serial.print(", variance: ");Serial.print(variance); Serial.println(); //Serial.print("Calculate time, ns: ");Serial.println(timeEnd-timeSt); } unsigned long getCollectTime() { return millis()-stat_start; } void printStatData() { Serial.print("Collecting data stored ");Serial.print(stat_using);Serial.print(" values, collect time ");Serial.print(getCollectTime());Serial.println(" ms, data count/values:"); for (TArrayIndex i=0;i<stat_using;i++) { Serial.print(stat_count[i]);Serial.print("\t");Serial.println(stat_value[i]); } } }; MeasureMath<220, unsigned int, unsigned int> mstat; // warning: with this MeasureMath on arduino - ower 16000 measure can make arifmetical misstake. void loop() { unsigned int echoTime; for (int i=0;i<40;i++) { delay(50); // Wait 50ms between pings (about 20 pings/sec). echoTime = sonar.ping(); // microseconds mstat.onNewMeasure(echoTime); } Serial.print("Ping cm,ns:\t"); Serial.print(sonar.convert_cm(echoTime)); Serial.print("\t"); Serial.println(echoTime); if (Serial.available()>0) { int cmd = Serial.read(); // int or byte or char? if (cmd=='r' || cmd =='R') { Serial.println("Reset stat data."); mstat.resetStat(); } } mstat.printStat(); mstat.printStatData(); } Как Вы видите, в Arduino UNO достаточно памяти (1,5 кБ) для произведения не простых математических вычислений с массивами данных (≈250 элементов). И так, несколько слов по быстродействию показанного кода. Самые частые данные (дистанция) будет сохранена в первых ячейках массива, а более редкие данные в последних (потомучто теория вероятности работает). По этому большинство точек данных будет найденно быстрее для вставки данных в массив (для поиска данных в массиве последовательным перебором потребуется перебрать, в среднем, менее 1/2 размера массива). Но через долгое время в массив будет добавлено много различных элементов и вычисление суммарной статистики будет требовать больше времени (для среднего значения, среднеквадратичного отклонения, и т.д.). Измерения HC-SR04, данные и графики1. Сначала померил расстояние до деревянной поверхности (тумбочка), расстояние 22,5 см (измерил рулеткой от основания платы датчика). Получил 31212 измерений за 27 минут 18 секунд. Зафиксированная задержка звука получалась от 1199 нс до 1219 нс, средняя задержка составила 1208,55 нс. Среднеквадратичное отклонение ± 2,624 нс. См. график 1, данные в таблице 1. ![]() ![]() График 1. Распределение времени (расстояния)
2. Затем измерено расстояние до деревянной поверхности (тумбочка), расстояние 46 см. Получил 40323 измерений за 36 минут 12 секунд. Зафиксированная задержка звука получалась от 2539 нс до 2587 нс, средняя задержка составила 2552,40 нс. Среднеквадратичное отклонение ± 6,153 нс. См. график 2, данные в таблице 2. ![]() ![]() График 2. Распределение времени (расстояния)
3. Далее измерено расстояние до пластиковых твёрдых сумок, расстояние 118,7 см. Получил 8453 измерений за 8 минут. Зафиксированная задержка звука получалась от 5779 нс до 6127 нс, средняя задержка составила 5955,40 нс. Среднеквадратичное отклонение ± 51,691 нс. См. график 3, данные в таблице 3. ![]() ![]() График 3. Распределение времени (расстояния) (скрыть)Таблица 3. Сгруппированные данные измерений HC-SR04, 3 (развернуть)
4. Далее измерено расстояние до ткани (штора), расстояние 67 см. Получил 47586 измерений за 43 минуты 57 секунд. Зафиксированная задержка звука получалась от 4011 нс до 4779 нс, средняя задержка составила 4114,44 нс. Среднеквадратичное отклонение ± 60,009 нс. См. график 4, данные в таблице 4. (скрыть)Таблица 4. Сгруппированные данные измерений 4 (развернуть)
5. Далее измерено расстояние до твёрдой поверхности (стены), расстояние 291 см. Данные содержат выбросы, по этому представлю расчёты без них, а в скобках указаны с учётом выбросов. Получил 2315 (оригинал 2320) измерений за ≈ 3 минуты. Задержка звука получалась от 16715 (оригинал 11107) нс до 16883 нс, средняя задержка составила 16763,05 (оригинал 16750,94) нс. Среднеквадратичное отклонение ± 29,8355 нс (оригинал 262,29 нс). См. график 5 (выбросы удалены), данные в таблице 5; график 6 с учётом всех данных. ![]() ![]() График 5. Распределение времени (расстояния) ![]() График 6. Распределение времени (расстояния), все данные. (скрыть)Таблица 5. Сгруппированные данные измерений 5 (развернуть)
Точность измерения зависит от расстояния и формы поверхности (материала), см. сводную таблицу 6 среднеквадратичных отклонений.
Распределение на первых графиках (1, 2) были похожи на нормальное распределение, однако другие эксперименты (3 и 4) показали, что это не всегда так. К сожалению, свойство средних величин сходиться к точному значению работает только при нормальном распределением. Со сложными распределениями, и дискретными интервалами времени (4 нс), простого среднего значения будет не достаточно для микронной точности. Согласно правилу трёх сигм (3σ), каждое 370 измерение (в нормальном распределении) будет отклоняться от среднего больше, чем на 3 СКО (среднеквадратичное отклонение). Не смотря на не совсем нормальное распределение, в эксперименте 3, правило 3-х сигм соблюдается: только 9 из 8453 (0,1%) измерений превысили 155 нс (± 2,7 см). Здесь измерения проводились в неподвижном положении, без шумов моторов, и возможных скачков напряжения. Однако реальные испытания на движущийся машинке показали, что большие («невероятные») ошибки случаются, и не очень редко. Особенно с различными сложенными (маленькими) препятствиями. Так же движение машины с закреплённым датчиком добавляет новые проблемы, такие как правильное усреднение постоянно меняющихся данных, замер скорости, шумы и меняющуюся окружающую среду. Для отсечения «выбросов» советую использовать медиану, а не среднее арифметическое; вычисление медианного расстояния есть в библиотеке NewPing::ping_median(it), результат этой функции – наносекунды, которые можно будет перевести в сантиметры, либо дюймы. Малые объекты и фонЕсли подобрать объект такой площади, чтобы ультразвуковой датчик плохо различал его (площадью менее 5 см2), а за ним поставить фон, то собрав много данных можно увидеть как сам объект, так и фон. На точечном графике 7 видно расстояния (время задержки в нс) от двух объектов одновременно. ![]() График 7.Измерение ультразвуковым датчиком плохо различимого объекта и фона одновременно На графике 7 можно определить фоновый объект на расстоянии 215 см, объект перед фоном (самое большое количество точек с большим среднеквадратичным отклонением) на расстоянии 185 см, а так же одну нереальную точку — это может быть запоздавшее эхо от фона. Зависимость точности измерений от расстояния и площадиДля проверки наличия зависимости погрешности измерений от расстояния был проведён ещё один эксперимент. Несколько прямоугольных кусочка пластика перемещались от 20 см до 180см. По обработанным данным построен график 8. ![]() График 8. Зависимость среднеквадратичного отклонения от расстояния и площади поверхности (20-180 см) Данные оказались слишком зашумлены, для составления точных формул зависимости погрешности от расстояния, и недостаточно для выведения формулы зависимости среднеквадратичного отклонения от всех параметров одновременно: расстояния, площади, материала и угла отражающей поверхности. Однако, с увеличением статистических данных найти формулу зависимости от всех факторов возможно. В результате обработки данных получились формулы (таблица 7), показывающие зависимость среднеквадратичного отклонения (± нс) от расстояния (задержки звука, нс), эти формулы пригодны для отрезка 20см-160см.
Обозначение в таблицеквадрата ^2 для простоты переноса формул в электронные таблицы; чем больше коэффициент детерминации - тем лучше. В основном зависимость на отрезке с 20 см по 160 см похожа на линейную. Однако для 54см2 площади зависимость не линейная, это можно объяснить кривой формой поверхности (вогнутая пластиковая штука от бутылки была, похоже, как фокусное расстояние от зеркал можно определять). Выводы: погрешность зависит от расстояния, форма объекта влияет на погрешность. ЗаключениеБыли проведены ряд экспериментов в стационарном положении на измерении точности расстояний до предметов. Данные показались весьма обнадеживающими. Согласно приведенным замерам датчик давал показания с точностью от 0,5 см до 2-3 см, в зависимости от расстояний. Но вот заявленная дальность в 4 метра показалась неубедительной. После 2 метров начинались сильные сбои и погрешности определения расстояний. Были построены графики, представленные здесь. |
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
© www.AlexeyK.com, 2016 |