Проблемы с HC-SR04 и RCWL-1670

Здравствуйте!

Столкнулся со странной проблемой ультразвуковых датчиков HC-SR04 и RCWL-1670.

Сделал измеритель уровня воды (реки) на HC-SR04. Всё было хорошо, но недели через три стали проскакивать существенные погрешности в измерениях (до метра и более). Заменил датчик, и история повторилась: сначала всё хорошо, а потом понеслась. Причём, чем дольше стоит, тем чаще ошибки.

В этом году использовал пыле-влагозащищённый RCWL-1670. Всё повторилось, причём на двух датчиках.

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

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

Кто-нибудь сталкивался с подобным? Может у этих датчиков небольшой ресурс? Может есть ещё какие-то варианты или нюансы применения?

P.S.
Снимаю показания достаточно нехитро

`digitalWrite(sensor.trigPin, HIGH) ;

delayMicroseconds(10) ;

digitalWrite(sensor.trigPin, LOW );

doubleechoTime =pulseIn(sensor.echoPin, HIGH );`

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

Добавлю - поскольку вы подозреваете, что ваш девайс постепенно дохнет, обязательно приложите четкие фото устройства

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

  1. Нарисованной схемы не существует, но суть заключается в подключении электронных компонентов к нужным пинам на контроллере.

Система состоит из:
Arduino Leonardo R3
Сетевой платы W5500 SPI, запитанной от 5В
Датчика температуры DS18B20 от 3.3В
Четырёхзнакового дисплея 74HC595
Двух ультразвуковых датчиков RCWL-1670 (по задумке должно быть три), от 5В

Логика следующая:
Измеряется температура
Последовательно снимаются показания со всех УЗ сенсоров
Определяется среднее усечённое значения для каждого датчика.
Усреднённые измерения сравниваются друг с другом и если показания близки (расходятся не более, чем на 3%), то они признаются правильными
Правильные показания ещё раз усредняются между собой, и результат скидывается REST-запросом на сервер.
Результаты фактических измерений температуры и каждого сенсора скидываются на сервер в Prometheus. Собственно, там видны все сырые показания по каждому сенсору и термометру.
Измеренный уровень воды (отклонение от нормы) выводится на экран. Также на экран ненадолго выводятся некоторые коды ошибок, например, сетевого обмена.

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

  1. Сфотографировать не могу, ибо сейчас нахожусь далеко. Когда менялись HC-SR04, то покойные выглядели как живые: чистые, без мусора и паутины.

  2. HC-SR04 врали и в плюс и в минус. Подробностей уже не помню – это было два года назад.

Сейчас RCWL-1670 врут в большую сторону, причём тремя вариантами 4300, 5500, и 6600. мм Несколько дней назад эти выбросы били миллиметров на 300 поменьше, но их было также три таких варианта.

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

  1. Кода мне не жалко, но тут почти 450 строк.
#include <Ethernet2.h>
#include “OneWire.h”
#include “DallasTemperature.h”
#include <AceSorting.h>
#include <GyverSegment.h>

// Пин, к которому подключен датчик температуры ds18b20
#define ONE_WIRE_PIN 4

// пины эхолота 0
#define HC_TRIG_0 A1
#define HC_ECHO_0 A0

// пины эхолота 1
#define HC_TRIG_1 A3
#define HC_ECHO_1 A2

// пины эхолота 2
#define HC_TRIG_2 A5
#define HC_ECHO_2 A4

//пины дисплея
#define SDI_PIN 5
#define SCLK_PIN 6
#define LOAD_PIN 7

//Количество разрядов на дисплее
#define DISP_DIGITS_COUNT 4

//Яркость дисплея. 0…15. (15 - max)
#define DISP_BRIGHTNESS 15

//Критическое расхождение в процентах между показаниями двух датчиков,
//при котором считается, что один из них врёт. Считаем от того, у которого минимальное значение.
#define CRITICAL_DIFFERENCE 3

//Время в миллисекундах между снятием показаний. Запись данных в БД будет выполняться с интервалом DELAY * MEASUREMENTS_COUNT
#define MEASUREMENT_DELAY 120000

//Расстояни в миллиметрах до реки при нормальном уровне воды
#define ZERO_RIVER_LEVEL 3410

//Время в течение которого старое измерение считается действительным
#define MEASUREMET_VALID_WHILE 1800000

//Втечение какого времени высвечивать сообщение об ошибке
#define ERROR_VISUALISATION_DURATION 5000

const String errorDhcp(“E01”);
const String errorThermometer(“E02”);
const String errorAllSensors(“E03”);
const String errorPostTimeout(“E04”);
const String errorPostConnectionFailed(“E05”);

byte mac
 = { 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xED };  // MAC-адрес сетевой платы

// Адрес сервера
#define SERVER “1.1.1.1”
// Порт HTTP
#define PORT 80
//Путь для запроса.
#define ENDPOINT_DATA “/meteo/0”
#define ENDPOINT_ERROR “/meteo/error/0”
#define ENDPOINT_PUSHGATEWAY “/pushgateway/metrics/job/pushgateway”

//С какким идентификатором типа данных будем отправлять измеренное расстояние в БД
#define DISTANCE_TYPE 0

// Максимальное количество попыток отправить данные
#define SENDING_ATTEMPTS 2

/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////// Дальше идёт код //////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

//Количество измерений каждым датчиком. За расстояние будем брать среднее усечённое от ряда из MEASUREMENTS_COUNT измерений.
#define MEASUREMENTS_COUNT 9

//Количество эхолотов
#define SENSORS_COUNT 3

#define POST_OK 0
#define POST_TIMEOUT 1
#define POST_CONNECTION_FAILED 2

// Настройка объекта OneWire
OneWire oneWire(ONE_WIRE_PIN);

//Дисплей
Disp595Static<DISP_DIGITS_COUNT> disp(SDI_PIN, SCLK_PIN, LOAD_PIN);

// Передаем ссылку на OneWire объект DallasTemperature
DallasTemperature thermometer(&oneWire);

boolean initializationError = false;

//Время последнего измерения
unsigned long lastShotTime = 0;

//Время последнего успешного снятия измерения
unsigned long lastResultTime = 0;

//Признак того, что измерения ещё не проводились и сейчас первый раз.
bool firstShot = true;

String lastResultStr = “”;

struct Sensor {
char trigPin = 0;
char echoPin = 0;
float measurements[MEASUREMENTS_COUNT];
float distance = 0;
char differenes = 0;

Sensor(char _trigPin, char _echoPin) {
trigPin = _trigPin;
echoPin = _echoPin;
}
};

Sensor sensors
 = {
Sensor(HC_TRIG_0, HC_ECHO_0),
Sensor(HC_TRIG_1, HC_ECHO_1),
Sensor(HC_TRIG_2, HC_ECHO_2)
};

/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

void setup() {
Serial.begin(9600);  // открытие порта для связиc:\programs\arduino\distance\config.h
Serial.println(F(“Погнали!”));

//!!! Для использования статического IP раскоментировать и закоментировать инициализацию по DHCP
//Ethernet.begin(mac, ip);

// //Инициализиркем пины эхолотов
for (int i = 0; i < SENSORS_COUNT; ++i) {
pinMode(sensors[i].trigPin, OUTPUT);  // эхолот trig выход
pinMode(sensors[i].echoPin, INPUT);   // эхолот echo вход
}

// Инициализация датчика температуры
thermometer.begin();
thermometer.setResolution(12);

disp.brightness(DISP_BRIGHTNESS);

pinMode(LED_BUILTIN, OUTPUT);
}

/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

void loop() {

if (millis() - lastShotTime >= MEASUREMENT_DELAY || firstShot) {
lastShotTime = millis();
firstShot = false;

if (Ethernet.begin(mac)) {  // Инициализируем Ethernet по DHCP. Если ОК, едем дальше
  printNetworkInfo();       // Выведем инфу о сети
} else {                    //Если не договорились с DHCP-сервером
  Serial.println(F("Не договорились с DHCP-сервером! Работы не будет!"));
  showError(errorDhcp);
  return;
}

// Запрос температуры
thermometer.requestTemperatures();

//На получение температуры у него уходит до 750 мс. Надо подождать.
//Иначе вернёт старое значение.
delay(1000);

// Чтение температуры в градусах Цельсия
float temperature = thermometer.getTempCByIndex(0);

if (temperature == DEVICE_DISCONNECTED_C) {
  Serial.println(F("Ошибка: датчик температуры отвалился! Всё идёт по бороде."));
  showError(errorThermometer);
  return;
}

EthernetClient ethClient;

String temperatureMetrics = String("# TYPE core_temp gauge\nvodomer_temperature") + "  " + String(temperature) + "\n";
sendPostRequest(ethClient, SERVER, PORT, ENDPOINT_PUSHGATEWAY, temperatureMetrics.c_str());

//Делаем измерения всеми датчиками
for (int measurementNo = 0; measurementNo < MEASUREMENTS_COUNT; ++measurementNo) {
  for (char sensorIndex = 0; sensorIndex < SENSORS_COUNT; ++sensorIndex) {
    doMeasurement(sensors[sensorIndex], temperature, measurementNo);

    if (measurementNo == MEASUREMENTS_COUNT - 1) {
      calcAvgDistance(sensors[sensorIndex]);
      sensors[sensorIndex].differenes = 0;
    }
    delay(1000);
  }
}

//Выведем показания с каждого сонара и отправим их метрики куда следует
for (int i = 0; i < SENSORS_COUNT; ++i) {
  String str = "Sensor_" + String(i) + " " + String(sensors[i].distance);
  Serial.println(str);

  String metrics = String("# TYPE core_temp gauge\nvodomer_sensor_") + String(i) + "  " + String(sensors[i].distance) + "\n";
  sendPostRequest(ethClient, SERVER, PORT, ENDPOINT_PUSHGATEWAY, metrics.c_str());
}

//Сравниваем полученные значения
for (char first = 0; first < SENSORS_COUNT - 1; ++first) {
  for (char second = first + 1; second < SENSORS_COUNT; ++second) {
    compareDistance(sensors[first], sensors[second]);
  }
}

float sum = 0;
char validMeasurements = 0;
for (int i = 0; i < SENSORS_COUNT; ++i) {
  if (sensors[i].differenes != SENSORS_COUNT - 1) {
    sum += sensors[i].distance;
    ++validMeasurements;
  }
}

if (validMeasurements > 0) {
  float distance = sum / validMeasurements;

  String data = String(distance);

  Serial.print(data.c_str());

  // Отправляем POST-запрос
  byte postRes;
  for (int i = 0; i < SENDING_ATTEMPTS; ++i) {
    postRes = sendPostRequest(ethClient, SERVER, PORT, ENDPOINT_DATA, data.c_str());
    if (postRes == POST_OK)
      break;
  }

  switch (postRes) {
    case POST_OK:
      break;
    case POST_TIMEOUT:
      showError(errorPostTimeout);
      break;
    case POST_CONNECTION_FAILED:
      showError(errorPostConnectionFailed);
      break;
  }

  lastResultTime = millis();
  lastResultStr = String(round((ZERO_RIVER_LEVEL - distance) / 10));
  showLastMeasurement();

  printMeteo(temperature, distance);  //Пишем инфу в COM-порт
}

if (validMeasurements != SENSORS_COUNT) {
  //Значит были ошибки. Пошлём на сервер номера сбоивших сенсоров.
  for (int i = 0; i < SENSORS_COUNT; ++i) {
    if (sensors[i].differenes == SENSORS_COUNT - 1) {
      String json = "{\"sensorId\":" + String(DISTANCE_TYPE) + ", \"sonarNumber\": " + String(i) + "}";
      for (int i = 0; i < SENDING_ATTEMPTS; ++i) {
        if (sendPostRequest(ethClient, SERVER, PORT, ENDPOINT_ERROR, json.c_str()))
          break;
      }
    }
  }
  Serial.println(F("Были ошибки эхолотов"));
}

}
}

/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

void doMeasurement(Sensor &sensor, float temperature, char measurementNo) {
//led_light(100);

// импульс 10 мкс
digitalWrite(sensor.trigPin, HIGH);
delayMicroseconds(10);
digitalWrite(sensor.trigPin, LOW);

// измеряем время ответного импульса в микросекундах
//PulseIn возвращает unsigned long, но мы приводим к double,
//чтобы дальнейшие вычисления отработали, с повышенной точностью
double echoTime = pulseIn(sensor.echoPin, HIGH);

//считаем расстояние и возвращаем
//Скорость звука в воздухе при 0℃ равняется 331 м/с. С повышением температуры на 1° увеличивается скорость на 0,6 м/с.
sensor.measurements[measurementNo] = echoTime * (331. + temperature * .6) / 2000.;
Serial.println(sensor.measurements[measurementNo]);
}

/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

//Считаем distance, как среднее усечённое (отбрасываем минимальные и максимальные значения, а средние усредняем).
void calcAvgDistance(Sensor &sensor) {

ace_sorting::shellSortKnuth(sensor.measurements, MEASUREMENTS_COUNT);

float sum = 0;
for (int i = MEASUREMENTS_COUNT / 3; i < MEASUREMENTS_COUNT - MEASUREMENTS_COUNT / 3; ++i) {
sum += sensor.measurements[i];
}

sensor.distance = sum / (MEASUREMENTS_COUNT / 3);
// Serial.print(F("dist - "));
// Serial.print(sensor.distance);
}

/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

//Входит ли разница между значениями в заданный диапазон?
void compareDistance(Sensor &first, Sensor &second) {
float minDistance = min(first.distance, second.distance);
float difference = fabs(first.distance - second.distance) / (minDistance / 100.);

if (difference > CRITICAL_DIFFERENCE) {
++first.differenes;
++second.differenes;
}
}

/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

void showError(const String &str) {
showOnDisplay(str);
delay(ERROR_VISUALISATION_DURATION);
showLastMeasurement();
}

/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

void showLastMeasurement() {
if (millis() - lastResultTime < MEASUREMET_VALID_WHILE)
showOnDisplay(lastResultStr);
else {
// String dbg = "delta  " +  String (millis() - lastResultTime) + " lstResTm " + String(lastResultTime);
// Serial.println(dbg);
String str = “”;
for (int i = 0; i < DISP_DIGITS_COUNT; i++)
str += ‘-’;

showOnDisplay(str);

}
}

/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
void showOnDisplay(const String &str) {
Serial.println(F(“draw on display”));
disp.clear();
disp.setCursor(DISP_DIGITS_COUNT - str.length());
disp.print(str);
disp.update();
Serial.println(F(“end draw”));
}

/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

//Вывод данных в COM-порт
void printMeteo(float temperature, float distance) {
Serial.println("Расстояние до воды: " + String(distance));
Serial.println("Температура: " + String(temperature));
}

/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

//Отправка запроса POST

byte sendPostRequest(EthernetClient &client, const char* server, uint16_t port, const char* endpoint, const char* data) {
Serial.println(F(“Connecting to server”));
//Serial.println(server);

// Подключаемся к серверу
if (client.connect(server, port)) {
Serial.println(F(“Connected to server”));

// Формируем HTTP-заголовки
client.print("POST ");
client.print(endpoint);
client.println(" HTTP/1.1");
client.print("Host: ");
client.println(server);
client.println("Content-Type: application/json");
client.print("Content-Length: ");
client.println(strlen(data));
client.println("Connection: close");
client.println();
client.println(data);

//    Serial.println(F(“Request sent”));

// Ждём ответа (таймаут 5 секунд)
unsigned long timeout = millis();
while (client.available() == 0) {
  if (millis() - timeout > 5000) {
    Serial.println(F("Timeout, no response!"));
    client.stop();
    return POST_TIMEOUT;
  }
}

// Читаем ответ сервера (если нужно)
while (client.available()) {
  char c = client.read();
  Serial.write(c);
}

client.stop();
return POST_OK;

} else {
Serial.println(F(“Connection failed”));
return POST_CONNECTION_FAILED;
}
}

/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

//Вывод нашего айпишника, маски подсети, шлюза и DNS-сервера
void printNetworkInfo() {
Serial.print(F("IP-адрес: "));
Serial.println(Ethernet.localIP());

Serial.print(F("Маска подсети: "));
Serial.println(Ethernet.subnetMask());

Serial.print(F("Шлюз: "));
Serial.println(Ethernet.gatewayIP());

Serial.print(F(“DNS-сервер: “));
Serial.println(Ethernet.dnsServerIP());
Serial.println(””);
}

#endif

Я в свое время сделал на HC-SR04 датчик для включения света. Он менял яркость при приближении. Поскольку я не отбрасывал значения и не усреднял, то заметил, что точность значений зависит от давления воздуха. При разных давлениях точность менялась. Ну и температура влияла на сам датчик. И что интересно - сквозняк дома влиял сильно на работу датчика.

@Кириллл Если выяснится, что проблема не в коде, может стоит ставить реально герметичные датчики типа автомобильных на парктрониках? Гляжу, они сильно подешевели.

вы, наверно, удивитесь, но 450 строк это просто крохотный скетч даже для вашей задачи

Это не повод выкладывать без схемы. Если готовой схемы нет, значит надо ее нарисовать.

+1 Скорее всего дело именно в датчиках. На HC стоит свой контроллер. В даташите посмотрел, что там разброс параметров порядком отличаются.

Еще добавлю: если дивайс перенести из влажной атмосферы обратно в сухую, через какое-то время работоспособность восстанавливается?

Словесное описание не заменяет саму схему.
Мы никуда не торопимся: вполне можем подождать, пока Вы нарисуете схему. Но в любом случае, ссылаясь на “отсутствие схемы”, Вы допускаете бестактность.

Только они на данном этапе и представляют интерес. Любые обработанные данные (даже просто умноженные на константу) не нужны.

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

Тогда Вы поторопились. Когда сможете нарисовать схему и сделать фотографию, возвращайтесь со всем этим на форум, а пока обсуждать нечего.

“Выбросы” интересуют не в пересчитанном виде, а в приходящих с датчика цифрах.

Тут немного про работу HC и замену модуля.

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

Там еще в коде стоит внимательнее разобраться. Мне кажется ради усреднения пары датчиков совсем необязательно было создавать столько структур и методов. Там банально может быть опечатка

Вряд ли. Я пробовал выключать свой датчик и работоспособность восстанавливалась на какое то время.

Тем более, что человек написал

А это уже о многом говорит, что ТС не дурак.

у вас тот же проект что у ТС с его же кодом?

Код практически аналогичный. А вот проявление неисправности очень похожее.

@Diskless , а можно поподробнее про ваше изделие? Как часто вы снимали показания? Сколько оно прожило? Как вели себя ошибки измерения?

В моём случае не наблюдается связи между состоянием атмосферы и поведением датчиков: влажность ежедневно в течение суток меняется от 35% до 90%, температура ночью опускается до 0, давление гуляет туда-сюда, ну и ветер на улице то есть, то нет. При этом ошибки измерений во времени распеределяются равномерно.

Сейчас график расхождения показаний между двумя сенсорами выглядит так. Это с 20 марта. Пришлось немного подфотошопить из-за сброса счётчика ошибок вследствие перезагрузок сервера. Да, сейчас нет третьего сенсора, чтобы видеть это отдельно график ошибок по каждому сенсору, но по графикам измерений понятно, что примерно 2 из 3-х ошибок даёт второй датчик.

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

Это снятые показания за 2.5 часа со второго сенсора. Видны выбросы трёх размеров: на 4, 5 и 6 метров. Конечно, их легко отфильтровать, но тут интересен не математический аппарат обработки данных, а хотелось бы понять в чём аппаратная проблема: может у УЗ датчиков ограниченный ресурс, может нельзя так просто взять и прикрутить их к чему-то, а надо создать какие-то комфортные для них условия или что-то ещё.

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

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