Ultraschallsensor - HC-SR04 teilweise falsche Messwerte

Hej,

eventuell hat hier noch jemand einen Rat für mich und kann mir dieses unterschiedliche Verhalten der Sensorik erklären. Für mein Projekt verwende ich unter anderem den Ultraschallsensor HC-SR04 und habe auf meiner Regelstrecke teilweise mit meinem eigenen Code üble Blinde Flecken, was eine Regelung mit dem Sensor nicht möglich macht.
Dazu mal ein Vergleich. Hier Abgebildet das Messergebnis einer Sprungantwort mit meinem eigenen Code:

Mein Code sieht wie folgt aus:

#include "HC_SR04.h"

HC_SR04& HC_SR04::getInstance(uint8_t ball_radius) {
    static HC_SR04 _instance;

    if (_instanceUltrasonicCreated == false) {
        _instance.setBallRadius(ball_radius);
        _instance.setTemperature();

        _instanceUltrasonicCreated = true;
    }
    return _instance;
}

bool    HC_SR04::setOffset(void) {
    uint16_t  counter      = 0;
    uint16_t  distance     = 0;
    uint16_t  distance_old = 0;

    unsigned long timeout = millis() + 10000U;

    while (counter < 250U) {
        if (millis() > timeout) {
            Serial.println("Initialization for ultrasonic sensor failed!");
            return false;
        }

        unsigned long cycle_time = millis() + 10U; // 10ms per measurement cycle

        distance = getDistancePure();
        (0U < distance && distance < 150U) && (distance_old - 1 <= distance || distance <= distance_old + 1) ? counter++ : counter = 0;
        distance_old = distance;
        while (millis() < cycle_time) {}
    }

    _offset = static_cast<uint8_t>(distance);
    _distance = _ball_radius;
    _echo_counter = 0U;

    Serial.println("Temperature: " + String(_temperature));
    Serial.println("Velocity: " + String(_ultrasonic_speed));
    Serial.println("Offset ultrasonic sensor: " + String(_offset));
    return true;
}

void    HC_SR04::setUltrasonicSpeed(double temperature) {
    _ultrasonic_speed = sqrt(1.402F * 8.3145F * (273.15F + temperature) / 0.02896F);
}

void HC_SR04::setupSensor(void) {
    stopTimerEcho();

    // Timer/Counter Control Registers
    TCCR4A = 0;
    TCCR4B = _BV(ICNC4) | _BV(ICES4); // Input Capture Noise Canceler and rising edge
    TCCR4C = 0;

    // Timer/Counter Interrupt Mask Register
    TIMSK4 = _BV(ICIE4) | _BV(TOIE4); // Input Capture and Timer Overflow Interrupt

    _SET_GPIO_DIRECTION_ULTRASONIC_ECHO();
    _SET_GPIO_DIRECTION_ULTRASONIC_TRIGGER();
}

void   HC_SR04::startTimerEcho(void){
    _echo_counter = 0;
    _echo_timeout = false;

    TCNT4   = 0;            // reset counter value timer 4
    TCCR4B |= _BV(ICES4);   // capture ICR4 on rising edge at first
    TCCR4B |= _BV(CS40);    // prescaler = 1


    _measurement_completed = false;

    _ULTRASONIC_TRIGGER_START();
}

void   HC_SR04::stopTimerEcho(void){
    TCCR4B &= ~( _BV(CS42) | _BV(CS41) | _BV(CS40) );
    TCNT4   = 0;
    _ULTRASONIC_TRIGGER_STOP();

    _echo_timeout = true;
    if (_measurement_completed == false) {
        _echo_counter = 0;
    }
}

void HC_SR04::isrEchoCapture(void) {
    if (_instanceUltrasonicCreated) {
        HC_SR04& _instance = HC_SR04::getInstance();

        if (TCCR4B & _BV(ICES4)) {
            _instance._echo_counter = ICR4;                                 // capture rising edge
        }
        else {
            _instance._echo_counter = ICR4 - _instance._echo_counter;       // capture falling edge
            if (_instance._echo_timeout == false) _instance._measurement_completed = true;
        }

        TCCR4B &= ~( _BV(ICES4) );
    }
}

void HC_SR04::isrEchoTimeout(void) {
    if (_instanceUltrasonicCreated) {
        HC_SR04& _instance = HC_SR04::getInstance();
        _instance.stopTimerEcho();
    }
}

/*      SETTER      */

void    HC_SR04::setBallRadius(uint8_t ball_radius) {
    _ball_radius = ball_radius;
}

void    HC_SR04::setTemperature(void) {
    float voltage       = (analogRead(A0) >> 2) / 255.0F * 5.0F; 
    float temperature   = (voltage - 0.75F) / 0.01F + 25.0F;
    float alpha         = 0.05F;

    _temperature = (1.0F - alpha) * _temperature + alpha * temperature;
    setUltrasonicSpeed(_temperature);
   
    //Serial.println("Temperature: " + String(analogRead(A0) >> 2));
    //Serial.println("Temperature: " + String(_temperature) + " -> Ultrasonic speed: " + String(_ultrasonic_speed)); 
}

/*      GETTER      */

uint8_t HC_SR04::getBallRadius(void) const {
    return _ball_radius;
}

float  HC_SR04::getTemperature(void) const {
    return _temperature;
}

float  HC_SR04::getUltrasonicSpeed(void) const {
    return _ultrasonic_speed;
}

uint8_t HC_SR04::getOffset(void) const {
    return _offset;
}

uint16_t HC_SR04::getDistancePure(void) {
    if (_ULTRASONIC_CHECK_LEVEL_ECHO()) return 0;
    
    setTemperature();

    startTimerEcho();

    while (_echo_timeout == false) {}
    
    uint32_t distance_local;
    distance_local  = _echo_counter;
    distance_local *= static_cast<uint16_t>(_ultrasonic_speed); 
    distance_local /= 32000U;

    return static_cast<uint16_t>(distance_local);
}

/*      FUNCTIONALITY       */

uint16_t HC_SR04::getDistance(void) {
    unsigned long delaySensor = millis() + 10U; 
    uint16_t distance_local   = getDistancePure();

    while (millis() < delaySensor) {};

    if (distance_local >= _offset) {
        distance_local   = _ball_radius + (distance_local - _offset);
        (_ball_radius <= distance_local && distance_local <= 450U - _ball_radius) ? _distance = distance_local : _distance;
    }
    else {
        return 500;
    }

    return _distance;
}

float HC_SR04::getDistanceSimulink(void) {
    static uint8_t counter = 0;
    static uint8_t measurement_failed = 0;
    //static bool    measurement_completed_local = false;

    if (counter == 0U) {         // 0ms
        if (_ULTRASONIC_CHECK_LEVEL_ECHO()) {
            counter = 0; 
            stopTimerEcho();

            if (measurement_failed < 100) {
                measurement_failed++;
            }
            else return 0xFFFF;

            return _distance / 1000.0F;
        }

        if (_echo_counter != 0) {
            measurement_failed = 0;

            uint32_t distance_local;
            distance_local  = _echo_counter;
            distance_local *= static_cast<uint16_t>(_ultrasonic_speed);  
            distance_local /= 32000U;

            if (distance_local >= _offset) {
                distance_local  = _ball_radius + (distance_local - _offset);
                if (_ball_radius <= distance_local && distance_local <= 450U - _ball_radius) {
                    _distance = static_cast<uint16_t>(distance_local);
                }; 
            }
        }
        else {
            if (measurement_failed < 100) {
                measurement_failed++;
            }
            else return 0xFFFF;
        }

        startTimerEcho();
    }

    counter < 4U ? counter++ : counter = 0U;  // count 4 times -> 10ms measurement cycle
    return _distance / 1000.0F;
}

bool HC_SR04::beginUltrasonic(void) {
    Serial.println("Begin ultrasonic sensor calibration.");
    
    for (uint8_t i = 0; i < 100; i++) setTemperature();

    setupSensor();
    return setOffset();
}

Der PAP ist so:

  1. beginUltrasonic()
  2. alle 2 ms getDistanceSimulink() aufrufen.

Wenn ich das ganze allerdings mit den in Simulink integrierten Bausteinen teste, erhalte ich eine schöne Messkurve, wenn gleich die Messwerte deutlich daneben liegen.


Ich erhalte zwar teilweise immernoch diese Artefakte und die lassen sich mit nem Oszi auch sehen aber bei weitem nicht mehr in dem Ausmaß. Hat jemand dafür eine Erklärung? Verbesserungsvorschläge sind auch Willkommen.
Man könnte natürlich noch den Messwert an sich prüfen ob er valide sein kann oder nicht aber erstmal würde ich gerne verstehen woher überhaupt diese starke Abweichung kommt.

Bist Du sicher, das Du keine Echos bekommst, die aus der vorherigen Messung stammen?

Das bricht Dir früher oder später das Genick.
Und das ebenfalls an anderen Stellen im Code.

Relativ. Die Problematik ist mir grundsätzlich bekannt und hatte ich auch lange Probleme mit. Allerdings messe ich dasselbe Bild bei einer statischen Messung und wesentlich längeren Pausen zwischen den Messungen. Der Schall braucht max 3,5ms für Hin- und Rückweg. Die Regelstrecke ist nicht ganz 0,6m lang. Es wird alle 10ms eine Messung durchgeführt, sollte also genug Zeit zwischen den Messungen sein. Daher würde ich jetzt erstmal Reflexionen ausschließen. Problematisch ist eher die Kugel mit dem Ultraschall richtig zu erfassen.


Was soll mir daran das Genick brechen? Ist doch lediglich ein Timeout sofern die Kalibrierung vor dem Start fehlschlägt. setOffset() wird ja lediglich ein Mal aufgerufen bevor die kontinuierliche Messung beginnt.

Bin für Verbesserungen wie gesagt offen. Sehe jedoch keine bisher.

Hast Du schon mal eine etwas längere "Pingdauer" versucht?
Ich habe ein paar HC-SR04 die erst ab 12 Micros Pingzeit vernünftig arbeiten.

Der Trigger Pin geht nach dem der Timer in den OFV läuft und gestoppt wird wieder auf HIGH. Heiß er ist min 6ms high und geht für die Messung auf LOW. 6ms sollten denke ich mehr als genug sein.

Der Überlauf von millis().
In der Zeit von 10000ms(!) greift Dein Timeout definitiv nie.

Mal überlegt eine ganz andere Technik einzusetzen?
Bei 0,6m Regelstrecke...
Such mal nach VL53L0X.

hmm ok berechtigter Einwand. Wie könnte man sowas umgehen? Ich meine unsinged long ist halt schon verdammt groß und bisher keine probleme damit gehabt.
Edit: Ich hab grade mal geschaut und das maximum sind 49 Tage. So lange soll das Teil ja gar nicht laufen. Denke damit ist der Hinweis hinfällig.

Der Sensor ist schon bereits verbaut. Geht aber unter anderem darum verschiedene Sensorik mit ihrer Geschwindigkeit zu untersuchen. Trotzdem danke für den Hinweis.

Dann mach weiter wie bisher.
Für mich ist dann aber auch Schluß hier.

Das hab ich keineswegs gesagt nur was ändert das, wenn der Mikrocontroller niemals 49 Tage laufen wird? somit wird es doch zu keinem überlauf von millis kommen nach meinem Verständnis.
Abgesehen davon habe ich doch lediglich meine Gedanken dazu geäußert. Wenn dem nicht so ist, kann man das doch begründet argumentieren. Meine Meinung dazu.

This topic was automatically closed 180 days after the last reply. New replies are no longer allowed.