Android Статьи Браузерные игры Программы О сайте
 

Ультразвуковой датчик расстояний HC-SR04 и Arduino

Как всякого любознательного человека меня увлекло изучение ардуино. Приобретя центральный процессор на базе платы Arduino поневоле пришлось задуматься о периферии. Начиная с мигания простыми светодиодами. Но этого показалось мало. И началось конструирование игрушки. На старый игрушечный джип была подключена система управлениями приводами движения колес и их поворотов. Естественно все подключалось через сборку L293D драйвера двигателей, чтобы не спалить процессорную плату Arduino и обеспечить нужные токи и напряжения на электропривод автомобиля. Но на этом я не остановился и пошел дальше. Да и какой же это компьютеризированный автомобиль, если нет никакой привязки к трассе. Необходимо было организовать обратный поток данных для выбора пути движения автомобиля. Были приобретенный ультразвуковой сонар HC-SR04.

Для определения направления движения пришлось установить HC-SR04 на импровизированную поворотную платформу, обеспечивающую сканирование направления и выбора правильного пути движения автомобиля. Первые результаты дали неплохие данные, автик поехал весьма бодро, самостоятельно выбирая путь. Но возникли ряд вопросов, которые заставили задуматься, насколько сбалансирована система и как она взаимодействует. Был проведен ряд первичных тестов описанных здесь. Сразу пришлось решать вопрос о точности ультразвукового датчика.

Для работы с этим датчиком в Arduino имеется библиотека NewPing.h, которая позволяет измерять расстояние как в дюймах так и в сантиметрах. Я решил проверить, на сколько точный этот датчик, и погрешность его измерений.

Был собран массив статистики замеров расстояний до нескольких объектов и измерена точность ультразвукового датчика.

HC-SR04

Фотография HC-SR04

В Интернете встречаются различные ультразвуковые датчики с одинаковой маркировкой «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   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... 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
// (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();
}
Представленный шаблон MeasureMath может использоваться не только для обработки расстояния, но и для других измерений. Код, представленный на этой странице распространяется по условиям GNU GPL License v2.

Как Вы видите, в Arduino UNO достаточно памяти (1,5 кБ) для произведения не простых математических вычислений с массивами данных (≈250 элементов). И так, несколько слов по быстродействию показанного кода. Самые частые данные (дистанция) будет сохранена в первых ячейках массива, а более редкие данные в последних (потомучто теория вероятности работает). По этому большинство точек данных будет найденно быстрее для вставки данных в массив (для поиска данных в массиве последовательным перебором потребуется перебрать, в среднем, менее 1/2 размера массива). Но через долгое время в массив будет добавлено много различных элементов и вычисление суммарной статистики будет требовать больше времени (для среднего значения, среднеквадратичного отклонения, и т.д.).

Измерения HC-SR04, данные и графики

1. Сначала померил расстояние до деревянной поверхности (тумбочка), расстояние 22,5 см (измерил рулеткой от основания платы датчика). Получил 31212 измерений за 27 минут 18 секунд. Зафиксированная задержка звука получалась от 1199 нс до 1219 нс, средняя задержка составила 1208,55 нс. Среднеквадратичное отклонение ± 2,624 нс. См. график 1, данные в таблице 1.

график, см график, нс

График 1. Распределение времени (расстояния)

Таблица 1. Сгруппированные данные измерений 1
nscmcount
119921,0350941
120321,105262558
120721,1754414186
121121,2456114146
121521,31579256
121921,3859625

2. Затем измерено расстояние до деревянной поверхности (тумбочка), расстояние 46 см. Получил 40323 измерений за 36 минут 12 секунд. Зафиксированная задержка звука получалась от 2539 нс до 2587 нс, средняя задержка составила 2552,40 нс. Среднеквадратичное отклонение ± 6,153 нс. См. график 2, данные в таблице 2.

график, см график, нс

График 2. Распределение времени (расстояния)

Таблица 2. Сгруппированные данные измерений 2
nscmcount
253944,543861
254344,61404111
254744,684216973
255144,7543924105
255544,824566516
255944,8947497
256344,964913
256745,0350940
257145,10526730
257545,175441436
257945,24561301
258345,315797
258745,385963

3. Далее измерено расстояние до пластиковых твёрдых сумок, расстояние 118,7 см. Получил 8453 измерений за 8 минут. Зафиксированная задержка звука получалась от 5779 нс до 6127 нс, средняя задержка составила 5955,40 нс. Среднеквадратичное отклонение ± 51,691 нс. См. график 3, данные в таблице 3.

график, см график, нс

График 3. Распределение времени (расстояния)

(скрыть)Таблица 3. Сгруппированные данные измерений HC-SR04, 3 (развернуть)

Таблица 3. Сгруппированные данные измерений HC-SR04, 3
nscmcount
5779101,391
5783101,462
5787101,532
5807101,8814
5811101,954
5827102,238
5831102,3063
5835102,3799
5839102,4410
5847102,582
5851102,657
5855102,72236
5859102,79275
5863102,868
5875103,073
5879103,1488
5883103,21121
5887103,2813
5895103,421
5899103,4913
5903103,56182
5907103,63500
5911103,7078
5915103,772
5927103,98114
5931104,05464
5935104,12278
5939104,197
5947104,337
5951104,40270
5955104,471625
5959104,54936
5963104,6110
5971104,752
5975104,8259
5979104,89917
5983104,96546
5987105,0431
5991105,111
5999105,2554
6003105,32244
6007105,39167
6011105,462
6023105,679
6027105,74348
6031105,81167
6035105,8819
6047106,0911
6051106,16105
6055106,23126
6059106,303
6063106,371
6067106,441
6075106,5857
6079106,6577
6083106,7211
6095106,931
609910715
6103107,0727
6107107,145
6111107,211
6127107,493

4. Далее измерено расстояние до ткани (штора), расстояние 67 см. Получил 47586 измерений за 43 минуты 57 секунд. Зафиксированная задержка звука получалась от 4011 нс до 4779 нс, средняя задержка составила 4114,44 нс. Среднеквадратичное отклонение ± 60,009 нс. См. график 4, данные в таблице 4.

график, нс график, см

График 4. Распределение времени (расстояния)

(скрыть)Таблица 4. Сгруппированные данные измерений 4 (развернуть)

Таблица 4. Сгруппированные данные измерений 4
nscmcount
401170,371
401570,4415
401970,5146
402370,5814
403170,721
403570,7911
403970,86684
404370,931465
404771625
405171,076
405571,141
405971,2150
406371,282064
406771,357168
407171,421964
407571,4944
407971,565
408371,636
408771,703025
409171,779286
409571,842935
409971,9125
410371,981
410772,0511
411172,12674
411572,191927
411972,261122
412372,336
412772,402
413172,476
413572,54329
413972,612967
414372,68498
414772,7538
415172,822
415572,893
415972,96431
416373,041112
416773,11704
417173,188
417973,322
418373,39106
418773,462959
419173,53885
419573,6016
419973,671
420373,743
420773,81146
421173,88730
421573,95425
421974,028
423174,2376
423574,30554
423974,37649
424374,4415
425574,657
425974,72212
426374,79232
426774,8660
427975,074
428375,14146
428775,21369
429175,2846
430775,56119
431175,63262
431575,7065
431975,771
433175,9820
433576,0570
433976,1238
435576,404
435976,4727
436376,5413
438776,962
441177,391
443177,742
443577,802
443977,881
445978,232
447978,581
448378,652
451179,142
453179,491
453579,563
455579,911
463981,391
465181,601
465981,744
467982,092
468382,161
4731833
477983,842

5. Далее измерено расстояние до твёрдой поверхности (стены), расстояние 291 см. Данные содержат выбросы, по этому представлю расчёты без них, а в скобках указаны с учётом выбросов. Получил 2315 (оригинал 2320) измерений за ≈ 3 минуты. Задержка звука получалась от 16715 (оригинал 11107) нс до 16883 нс, средняя задержка составила 16763,05 (оригинал 16750,94) нс. Среднеквадратичное отклонение ± 29,8355 нс (оригинал 262,29 нс). См. график 5 (выбросы удалены), данные в таблице 5; график 6 с учётом всех данных.

график, см график, нс

График 5. Распределение времени (расстояния)

график

График 6. Распределение времени (расстояния), все данные.

(скрыть)Таблица 5. Сгруппированные данные измерений 5 (развернуть)

Таблица 5. Сгруппированные данные измерений 5
nscmcount
11107194,861
11111194,931
11135195,351
11179196,121
11187196,261
16715293,251
16719293,3224
16723293,39129
16727293,46201
16731293,53125
16735293,6012
16739293,671
16743293,744
16747293,81148
16751293,88358
16755293,95280
16759294,0263
16763294,095
16771294,2330
16775294,30202
16779294,37286
16783294,44133
16787294,5121
16791294,582
16799294,7251
16803294,7963
16807294,8649
16811294,9313
16815295,001
16823295,149
16827295,217
16831295,2813
16835295,356
16839295,421
16847295,563
16851295,6330
16855295,7022
16859295,7713
16863295,841
16875296,055
16879296,122
16883296,191

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

Таблица 6. СКО во всех экспериментах
Расстояние, смСреднее расстояние, нсСКО, нсСКО, смПримечания
122,51208,552,624± 0,046дерево
2462552,46,153± 0,108дерево
3118,75955,451,691± 0,907пластик / сумка
4674114,4460,009± 1,053штора / ткань
529116763,05
(оригинал 16750,94)
29,836
(оригинал 262,29)
± 0,523
(оригинал ± 4,602)
стена

Распределение на первых графиках (1, 2) были похожи на нормальное распределение, однако другие эксперименты (3 и 4) показали, что это не всегда так. К сожалению, свойство средних величин сходиться к точному значению работает только при нормальном распределением. Со сложными распределениями, и дискретными интервалами времени (4 нс), простого среднего значения будет не достаточно для микронной точности.

Согласно правилу трёх сигм (3σ), каждое 370 измерение (в нормальном распределении) будет отклоняться от среднего больше, чем на 3 СКО (среднеквадратичное отклонение). Не смотря на не совсем нормальное распределение, в эксперименте 3, правило 3-х сигм соблюдается: только 9 из 8453 (0,1%) измерений превысили 155 нс (± 2,7 см).

Здесь измерения проводились в неподвижном положении, без шумов моторов, и возможных скачков напряжения. Однако реальные испытания на движущийся машинке показали, что большие («невероятные») ошибки случаются, и не очень редко. Особенно с различными сложенными (маленькими) препятствиями. Так же движение машины с закреплённым датчиком добавляет новые проблемы, такие как правильное усреднение постоянно меняющихся данных, замер скорости, шумы и меняющуюся окружающую среду. Для отсечения «выбросов» советую использовать медиану, а не среднее арифметическое; вычисление медианного расстояния есть в библиотеке NewPing::ping_median(it), результат этой функции – наносекунды, которые можно будет перевести в сантиметры, либо дюймы.

Малые объекты и фон

Если подобрать объект такой площади, чтобы ультразвуковой датчик плохо различал его (площадью менее 5 см2), а за ним поставить фон, то собрав много данных можно увидеть как сам объект, так и фон. На точечном графике 7 видно расстояния (время задержки в нс) от двух объектов одновременно.

два объекта для HC-SR04

График 7.Измерение ультразвуковым датчиком плохо различимого объекта и фона одновременно

На графике 7 можно определить фоновый объект на расстоянии 215 см, объект перед фоном (самое большое количество точек с большим среднеквадратичным отклонением) на расстоянии 185 см, а так же одну нереальную точку — это может быть запоздавшее эхо от фона.

Зависимость точности измерений от расстояния и площади

Для проверки наличия зависимости погрешности измерений от расстояния был проведён ещё один эксперимент. Несколько прямоугольных кусочка пластика перемещались от 20 см до 180см. По обработанным данным построен график 8.

графики зависимости точности измерений от расстояния до объекта ультразвуковым методом, наносекунды

График 8. Зависимость среднеквадратичного отклонения от расстояния и площади поверхности (20-180 см)

Данные оказались слишком зашумлены, для составления точных формул зависимости погрешности от расстояния, и недостаточно для выведения формулы зависимости среднеквадратичного отклонения от всех параметров одновременно: расстояния, площади, материала и угла отражающей поверхности. Однако, с увеличением статистических данных найти формулу зависимости от всех факторов возможно. В результате обработки данных получились формулы (таблица 7), показывающие зависимость среднеквадратичного отклонения (± нс) от расстояния (задержки звука, нс), эти формулы пригодны для отрезка 20см-160см.

Таблица 7. Формулы погрешности ультразвукового датчика в нс
Площадь, см2формула, нсR2 (коэффициент детерминации)
112,50,00587*x-4,440,959
112.52,1571*exp(0,0004838*x)0,903
542,4872e-6*x^2-0,01333*x+27,3680,982
54f(x)=0,00773*x-7,5930,754
360,00628*x+0,9270,958
361,78845e-7*x^2+0,00442*x+4,6680,961

Обозначение в таблицеквадрата ^2 для простоты переноса формул в электронные таблицы; чем больше коэффициент детерминации - тем лучше. В основном зависимость на отрезке с 20 см по 160 см похожа на линейную. Однако для 54см2 площади зависимость не линейная, это можно объяснить кривой формой поверхности (вогнутая пластиковая штука от бутылки была, похоже, как фокусное расстояние от зеркал можно определять).

Выводы: погрешность зависит от расстояния, форма объекта влияет на погрешность.

Заключение

Были проведены ряд экспериментов в стационарном положении на измерении точности расстояний до предметов. Данные показались весьма обнадеживающими. Согласно приведенным замерам датчик давал показания с точностью от 0,5 см до 2-3 см, в зависимости от расстояний. Но вот заявленная дальность в 4 метра показалась неубедительной. После 2 метров начинались сильные сбои и погрешности определения расстояний. Были построены графики, представленные здесь.

 
© www.AlexeyK.com, 2016