Forum: Mikrocontroller und Digitale Elektronik Ultraschallsensor und ATmega 2560


von Fabio M. (fabiom)


Lesenswert?

Hallo,

ich möchte seriell die Distanz eines HC-SR04 Sensors ausgeben. Die 
Ausgabe funktioniert super. Außerdem habe ich auch eine Funktion die wie 
micros() von Arduino funktioniert. Aber statt eine Funktion ist es eine 
Variable.

Ich mache alles in Atmel Studio auf einem Arduino Mega 2560, benutze 
aber nicht die Arduino IDE und ihren Code. Manchen Code habe ich aus der 
NewPing libary kopiert und verändert.

Leider gibt mir meine Funktion aber nur falsche Werte aus. Wenn ich die 
Hand ca. 10cm vor den Sensor halte, bekomme ich den Wert 23-24. Wenn er 
kein Signal zurück bekommt gibt er 0 aus. Ich würde den richtigen 
CM-Wert erwarten.

Könnt ihr mir bitte meine Fehler sagen? Hier ist der Code.

main.c
1
 
2
#include <avr/io.h>
3
#include "Serial.h"
4
#include "Ultrasonic.h"
5
#include <util/delay.h>
6
#include "avr/interrupt.h"
7
8
int main(void) {
9
  SerialInit();
10
  UltrasonicInt();
11
  
12
  // set up timer 0
13
  TCCR0B = (1<<CS01); // Prescaler 8
14
  TIMSK0 |= (1<<TOIE0);
15
  sei();
16
  
17
  while(1) {
18
    SerialPrintNumberULong(UltrasonicRead(300, 0));
19
    SerialPrint("\n");
20
    _delay_ms(100);
21
  }
22
}

Ultrasonic.c
1
#include "Ultrasonic.h"
2
#include <util/delay.h>
3
#include <avr/interrupt.h>
4
5
unsigned long microseconds = 0;
6
7
#define US_ROUNDTRIP_CM  57     // Microseconds (uS) it takes sound to travel round-trip 1cm (2cm total), uses integer to save compiled code space
8
#define NO_ECHO          0      // Value returned if there's no ping echo within the specified MAX_SENSOR_DISTANCE or maxCmDistance
9
#define MAX_SENSOR_DELAY 5800   // Maximum uS it takes for sensor to start the ping
10
#define PING_OVERHEAD    5      // Ping overhead in microseconds (uS)
11
12
unsigned int maxEchoTime;
13
unsigned long maxTime;
14
15
bool ping_trigger() {
16
  PINL &= ~(1 << PL6);                    // Set the trigger pin low, should already be low, but this will make sure it is.
17
  _delay_ms(4);                        // Wait for pin to go low.
18
  PINL |= (1 << PL6);                    // Set trigger pin high, this tells the sensor to send out a ping.
19
  _delay_ms(15);                        // Wait long enough for the sensor to realize the trigger pin is high. Sensor specs say to wait 10uS.
20
  PINL &= ~(1 << PL6);                    // Set trigger pin back to low.
21
  
22
  if (PIND & (1 << PD0))
23
    return false;                      // Previous ping hasn't finished, abort.
24
  
25
  maxTime = microseconds + maxEchoTime + MAX_SENSOR_DELAY;  // Maximum time we'll wait for ping to start (most sensors are <450uS, the SRF06 can take up to 34,300uS!)
26
  while (PIND & (1 << PD0))                  // Wait for ping to start.
27
    if (microseconds > maxTime)
28
      return false;                    // Took too long to start, abort.
29
  maxTime = microseconds + maxEchoTime;            // Ping started, set the time-out.
30
    return true;                      // Ping started successfully.
31
}
32
33
void UltrasonicInt(void) {
34
  DDRL |= (1 << PL6);  //Trigger as OUTPUT
35
  DDRD &= ~(1 << PD0); //Echo as INPUT
36
}
37
38
unsigned long UltrasonicRead(long maxCM, uint8_t nr) {
39
  switch (nr) {
40
    case 0:
41
      if (maxCM > 0)
42
        maxEchoTime = (maxCM+1) * US_ROUNDTRIP_CM;      // Call function to set a new max sensor distance.
43
44
      if (!ping_trigger())
45
        return NO_ECHO;                    // Trigger a ping, if it returns false, return NO_ECHO to the calling function.
46
  
47
      while (PIND & (1 << PD0))                // Wait for the ping echo (Read echo)
48
        if (microseconds > maxTime)
49
          return NO_ECHO;                  // Stop the loop and return NO_ECHO (false) if we're beyond the set maximum distance.
50
      
51
      unsigned long echoTime = (microseconds - (maxTime - maxEchoTime) - PING_OVERHEAD); // Calculate ping time, include overhead.
52
      if(echoTime)
53
        return ((unsigned int) echoTime + US_ROUNDTRIP_CM / 2); //(max(((unsigned int) echoTime + US_ROUNDTRIP_CM / 2) / US_ROUNDTRIP_CM, 1)); // Convert uS to centimeters.
54
      else
55
        return 555; //(max(((unsigned int) echoTime + US_ROUNDTRIP_CM / 2) / US_ROUNDTRIP_CM, 0)); // Convert uS to centimeters
56
      break;
57
    default:
58
      return 0;
59
  }
60
}
61
62
ISR(TIMER0_OVF_vect) {
63
  microseconds++;
64
}

Ultrasonic.h
1
#ifndef ULTRASONIC_H_
2
#define ULTRASONIC_H_
3
4
#include "avr/io.h"
5
#include <stdbool.h>
6
7
extern unsigned long microseconds;
8
9
//Init of all Ultrasonics
10
void UltrasonicInt(void);
11
12
//Read an Ultrasonic Sensor
13
unsigned long UltrasonicRead(long max, uint8_t nr);
14
15
bool ping_trigger();
16
17
#endif /* ULTRASONIC_H_ */

von Stefan F. (Gast)


Lesenswert?

Dein Mikrosekunden-Timer ist viel zu schnell. So schnell kann der AVR 
die ISR gar nicht ausführen. Außerdem muss die Variable als volatile 
gekennzeichnet werden.

Ich schlage einen anderen wesentlich simpleren Lösungsansatz ohne Timer 
und ohne diese umfangreiche Mathematik vor: 
http://stefanfrings.de/hc-sr04/index.html

von Fabio M. (fabiom)


Lesenswert?

Stefanus F. schrieb:
> Dein Mikrosekunden-Timer ist viel zu schnell. So schnell kann der AVR
> die ISR gar nicht ausführen. Außerdem muss die Variable als volatile
> gekennzeichnet werden.
>
> Ich schlage einen anderen wesentlich simpleren Lösungsansatz ohne Timer
> und ohne diese umfangreiche Mathematik vor:
> http://stefanfrings.de/hc-sr04/index.html

Vielen Dank. Es funktioniert. Aber das Problem ist, dass ich ca. 10 
Sensoren benutzen möchte und das spätere Programm in 10-20ms Takt 
arbeiten soll. Alle 3-6 Takte soll er dann die Sensoren abfragen. Dafür 
ist die Methode dann nicht die beste. Kannst du mir sagen wie ich bei 
meinem Code den Timer langsamer stellen kann?

von Joachim B. (jar)


Lesenswert?

Fabio M. schrieb:
> Aber das Problem ist, dass ich ca. 10
> Sensoren benutzen möchte und das spätere Programm in 10-20ms Takt
> arbeiten soll.

dann schmeisse die delay raus und baue das zu einer state machine um

Arduino wait without delay wäre was zum LESEN
https://playground.arduino.cc/Learning/BlinkWithoutDelayDe

https://de.wikipedia.org/wiki/Endlicher_Automat
https://www.mikrocontroller.net/articles/Statemachine

von Stefan F. (Gast)


Lesenswert?

Fabio M. schrieb:
> Kannst du mir sagen wie ich bei
> meinem Code den Timer langsamer stellen kann?

Das ergäbe wenig Sinn. 10µs wären vielleicht machbar, aber dann wird die 
Messung schon recht ungenau. Das problem ist auch, dass du mit zu vielen 
Interrupts riskierst, dass dein Hauptprogramm zu langsam wird und daher 
das Ergebnis ungenau wird.

Ich nehme mal an, dass die 10 Sensoren räumlich getrennt sind und 
gleichzeitig arbeiten können. Dann würde ich so vorgehen:

Triggere alle 10 Sensoren gleichzeitig, lass dann die 58µs Schleife so 
oft wiederholen, bis alle Sensoren das Echo erfasst haben (oder in den 
Timeout gelaufen sind). Du kannst für jeden Sensor einzeln die Zeit 
speichern. Etwa so:
1
        
2
        int16_t cm=0;  
3
4
        // Trigger a measurement
5
        TRIGGER_ON;
6
        _delay_us(10);
7
        TRIGGER_OFF;
8
        
9
        // Wait for response
10
        // Assuming that all sensors need the same time
11
        while (!ECHO_SIGNAL_SENSOR1);
12
  
13
        // Measure pulse width of all 10 sensors
14
        while (cm<300)
15
        {
16
            if (ECHO_SIGNAL_SENSOR1) value1=cm;
17
            if (ECHO_SIGNAL_SENSOR2) value2=cm;
18
            if (ECHO_SIGNAL_SENSOR3) value3=cm;
19
            if (ECHO_SIGNAL_SENSOR4) value4=cm;
20
            if (ECHO_SIGNAL_SENSOR5) value5=cm;
21
            if (ECHO_SIGNAL_SENSOR6) value6=cm;
22
            if (ECHO_SIGNAL_SENSOR7) value7=cm;
23
            if (ECHO_SIGNAL_SENSOR8) value8=cm;
24
            if (ECHO_SIGNAL_SENSOR9) value9=cm;
25
            if (ECHO_SIGNAL_SENSOR10) value10=cm;
26
            _delay_us(57);
27
            cm++;
28
        }

Die 58µs habe ich auf 57 reduziert, um die Zeit auszugleichen, die für 
die ganzen if-Abfragen drauf geht. Vielleicht muss man sogar noch etwas 
mehr abziehen. Das kannst du ja mal ausmessen oder anhand des 
Assembler-Listings ausrechnen.

Das Prinzip müsste so klar sein, oder?

von Fabio M. (fabiom)


Lesenswert?

Ja, vielen Dank.

von Stefan F. (Gast)


Lesenswert?

Joachim B. schrieb:
> dann schmeisse die delay raus und baue das zu einer state machine um

Ja, das wäre auch denkbar, dann braucht es allerdings einen Timer der im 
58µs Raster hoch zählt.

Schwierig stelle ich mir dann allerdings vor, den richtigen Moment für 
den Beginn der Messung abzupassen. Ich meine den Moment, wo das Echo 
Signal auf High geht.

Ich bemerke grade einen Fehler: bei den 10 if Abfragen muss ein ! rein, 
weil das Echo Signal der Sensoren auf Low geht, wenn die Messung beendet 
ist.

von Fabio M. (fabiom)


Lesenswert?

Stefanus F. schrieb:
> Joachim B. schrieb:
>> dann schmeisse die delay raus und baue das zu einer state machine um
>
> Ja, das wäre auch denkbar, dann braucht es allerdings einen Timer der im
> 58µs Raster hoch zählt.
>
> Schwierig stelle ich mir dann allerdings vor, den richtigen Moment für
> den Beginn der Messung abzupassen. Ich meine den Moment, wo das Echo
> Signal auf High geht.
>
> Ich bemerke grade einen Fehler: bei den 10 if Abfragen muss ein ! rein,
> weil das Echo Signal der Sensoren auf Low geht, wenn die Messung beendet
> ist.

Welchen Timer bzw. Prescaler sollte ich dafür wählen? Kann mir jemand 
auf die Sprünge helfen?

von my2ct (Gast)


Lesenswert?

Fabio M. schrieb:
> Welchen Timer bzw. Prescaler sollte ich dafür wählen? Kann mir jemand
> auf die Sprünge helfen?

Probiers aus, denk selber nach.

Welchen statistischen Fehler hat die Länge deines Eingangssignals?
Das ist der Maßstabe für deine Auflösung beim Timer. Die Zeit viel 
genauer zu messen ist oft wenig sinnvoll, weil die letzten Bits dann nur 
noch Hausnummern enthalten.

Wie lang ist deine längste Pulsdauer vom US-Geber?
Das muss dein Timer noch auszählen können.

u.s.w.

von Stefan F. (Gast)


Lesenswert?

Fabio M. schrieb:
> Welchen Timer bzw. Prescaler sollte ich dafür wählen? Kann mir jemand
> auf die Sprünge helfen?

Ich denke jetzt nur mal laut, ohne ins Datenblatt zu schauen:

8MHz Systemtakt geteilt durch Prescaler 8 ergibt einen Timer-Takt von 
1MHz (1µs). Wenn man den Timer jetzt so einstellt, dass er immer von 
Null bis 58 zählt und dann einen Interrupt auslöst, wird die ISR alle 
58µs aufgerufen.

58µs Intervalle ergeben Messwerte mit 1cm Auflösung. 29µs Intervalle 
ergeben Messwerte mit 0,5cm Auflösung. Noch höhere Auflösung macht 
keinen Sinn, weil die Messung alleine schon aufgrund von 
Temperaturschwankungen viel mehr vom Soll abweicht.

> Wie lang ist deine längste Pulsdauer vom US-Geber?

Je nach Händler werden da 29ms (5 Meter) bis 41ms (7 Meter) angegeben. 
Ich weiß aber aus Erfahrung, dass diese Sensoren nur bis etwa 17ms (3 
Meter) zuverlässig funktionieren. Diesen Wert hatte der TO in seinem 
ersten Programmentwurf auch als Limit festgelegt.

Da fällt mir gerade etwas auf: Fabio, du willtest im 10-20ms Intervall 
messen. Aber so schnell sind die Sensoren gar nicht. Zwischen den 
Messungen soll man mindestens 60ms warten.

von Fabio M. (fabiom)


Lesenswert?

Reicht für diese Anwendung ein 8bit Timer oder muss ich ein 16bit Timer 
nehmen. Mein Sensor kann bis max ca. 3Meter messen.

Stefanus F. schrieb:
> Da fällt mir gerade etwas auf: Fabio, du willtest im 10-20ms Intervall
> messen. Aber so schnell sind die Sensoren gar nicht. Zwischen den
> Messungen soll man mindestens 60ms warten.

Deshalb soll er nur jeden 6. Takt auslesen bzw. immer wenn er bereit 
ist.

von Stefan F. (Gast)


Lesenswert?

Fabio M. schrieb:
> Reicht für diese Anwendung ein 8bit Timer oder muss ich ein 16bit Timer
> nehmen. Mein Sensor kann bis max ca. 3Meter messen.

Wie viele Bits braucht man, um bis 58 zu zählen? Du schaffst das!

von Fabio M. (fabiom)


Lesenswert?

Ok, ich habe mal angefangen es umzusetzen. Ich habe den Timer0 (8Bit) 
benutzt. Dieser zählt von 0-58, und das immer. Aber was mir noch nicht 
klar ist, wann ich trigger bzw. lesen muss.

von Wolfgang (Gast)


Lesenswert?

Stefanus F. schrieb:
> Noch höhere Auflösung macht keinen Sinn, weil die Messung alleine
> schon aufgrund von Temperaturschwankungen viel mehr vom Soll abweicht.

Das ist kein Argument. Der ATmega kann mit einem Quarz laufen und die 
Abhängigkeit der Schallgeschwindigkeit von der Temperatur ist bekannt, 
kann also ohne weiteres berücksichtigt werden, wenn man sich noch einen 
Temperatursensor spendiert.

Da kommt es auf die Anforderungen an.

von Fabio M. (fabiom)


Lesenswert?

Solche Genauigkeit brauche ich nicht. Ein Zentimeter genau ist schon 
mehr als nur perfekt.

Ich habe jetzt mal ein bisschen rumprobiert. Leider klappt es noch nicht 
so. Er gibt mir immer 0 aus. Und nach ca. 10 Sekunden ist irgendetwas so 
schief gelaufen, dass er nur noch nicht erkannte Zeichen ausgibt. Den 
Fehler konnte ich selbst leider nicht finden. Hier ist der Code:
1
#include <avr/io.h>
2
#include "Serial.h"
3
#include <util/delay.h>
4
#include "avr/interrupt.h"
5
#include <stdbool.h>
6
7
volatile uint8_t usZumNachtsenCM = 0;
8
volatile bool Trigger = true;
9
10
// The Ultrasonic sensor is connected to Port D2 and D3
11
#define TRIGGER_ON   { DDRL |= (1<<PL6); PORTL |= (1<<PL6); }
12
#define TRIGGER_OFF  { PORTL &= ~(1<<PL6); }
13
#define ECHO_SIGNAL  ( PIND & (1<<PD0) )
14
15
int16_t cm = 0;
16
17
int main(void) {
18
  SerialInit();
19
20
  sei();
21
22
  while(1) {
23
    SerialPrintNumber(cm);
24
    SerialPrintln("");
25
  
26
         _delay_ms(100);
27
    
28
  }
29
}
30
31
volatile bool ready = true;
32
volatile bool newTrigger = true;
33
ISR(TIMER1_OVF_vect) {
34
  newTrigger = false;
35
  if (usZumNachtsenCM == 0 && ready) {
36
    TRIGGER_ON;
37
    newTrigger = true;
38
  } 
39
  else if (usZumNachtsenCM == 10 && ready) {
40
    TRIGGER_OFF;
41
    ready = false;
42
    newTrigger = true;
43
  } 
44
  
45
  if (ECHO_SIGNAL && ((usZumNachtsenCM == 58 && !newTrigger) || (usZumNachtsenCM == 68 && newTrigger))) {
46
    ready = false;
47
    usZumNachtsenCM = 0;
48
    cm++;
49
  } else {
50
    ready = true;
51
  }
52
  usZumNachtsenCM++;
53
}

von Stefan F. (Gast)


Lesenswert?

Wenn dies das ganze Programm ist, wird die ISR niemals aufgerufen. Der 
Timer wird gar nicht gestartet. Außerdem hast du munter Mikrosekunden 
und 58µs Intervalle durcheinander gewürfelt.

Ich schlage vor, dass Du mal im Datenblatt das Kapitel für den Timer 0 
mindestens 3 mal durchliest. Ziel ist, dass der Timer alle 58µs einen 
Interrupt auslöst.

Dann lies meine Einleitung zu Zustandsautomaten: 
http://stefanfrings.de/net_io/protosockets.html (die obere Hälfte). In 
meinem Buch 
http://stefanfrings.de/mikrocontroller_buch/Einstieg%20in%20die%20Elektronik%20mit%20Mikrocontrollern%20-%20Band%203.pdf 
Band 3 Kapitel 9.1.4 findest du ein konkretes Anwendungsbeispiel mit 
einer LED Anzeige.

Das Starten der Messung und das Messen der Pulsbreite solltest du 
außerhalb der ISR machen, sonst läufst du ganz schnell Gefahr, dass 
deine ISR selbst länger als 58µs benötigt und du dann wieder das gleiche 
Problem bekommst, wie in deinem ersten Beitrag. Vergiss nicht, dass du 
am Ende 10 Sensoren abfragen willst.

Alle Variablen, die in ISR geändert und außerhalb der ISR gelesen werden 
(oder umgekehrt) müssen volatile sein. Außerdem muss man außerhalb der 
ISR beim Zugriff auf Variablen größer als 8bit vorher cli() und danach 
sei() aufrufen, damit die Variable nicht während des Zugriffes durch die 
ISR verändert wird. Der Zugriff auf diese Variablem ist relativ 
ineffizient, deswegen sollte man nicht unnötig viele verwenden.

Die Vorgehensweise ist so:

Die ISR soll alle 58µs vom Timer aufgerufen werden und einfach nur eine 
Variable hochzählen. Nennen wir sie uint32_t systick (8bit wäre zu 
klein).
1
uint32_t volatile systick=0;
2
3
ISR(TIMER1_OVF_vect) 
4
{
5
    systick++;
6
}

Dann schreibst du dir noch eine Funktion, mit der du systick sicher 
lesen kannst ohne dass die ISR dazwischen funkt:
1
uint32_t getSystick() 
2
{ 
3
    uint32_t temp;
4
    cli();
5
    temp=systick;
6
    sei();
7
    return temp;
8
}
;

In der Hauptschleife sendest du das Trigger Signal an den Sensor und 
kopierst die Variable "systick" in eine Variable "started" die unbedingt 
den gleichen Typ (uint32_t) haben muss, wie systick.
1
uint32_t started;
2
3
TRIGGER_ON;
4
_delay_us(10);
5
TRIGGER_OFF;
6
started=getSystick();


Dann wartest du, bis die Messung beendet ist.
1
if (!ECHO_SIGNAL)
2
{
3
    cm = getSystick() - started;
4
}

Selbst wenn systick zwischendurch einmal überläuft (von 0xFFFF auf 0) 
kommt dabei trotzdem das richtige Ergebnis heraus.

Jetzt hast du geschrieben, dass dein Hauptprogramm irgend etwas im 10ms 
Intervall machen soll. Du musst also abfragen können, ob inzwischen 10ms 
verstrichen sind. Das macht man so:
1
uint32_t anfang = getSystick();
2
3
... irgendwas machen
4
5
uint32_t jetzt=getSystick();
6
if (jetzt-anfang > 10000/58)
7
{
8
    // es sind 10ms verstrichen
9
}


Um blöden Sprüchen von den erfahrenen Entwicklern zuvor zu kommen:

a) Anstatt cli() und sei() kann man auch die Makros aus der <atomic.h> 
verwenden. 
https://www.nongnu.org/avr-libc/user-manual/group__util__atomic.html 
Manche Leute denken, dass der Code auf diese Weite besser auf andere 
Mikrocontroller portierbar ist.

b) In Zustandsautomaten empfiehlt es sich, Enumeration anstatt Zahlen 
für den Zustand zu benutzen. Denn dann hat man sprechende Namen und man 
kann leichter etwas mittendrin einfügen.

von Stefan F. (Gast)


Lesenswert?

Wolfgang schrieb:
> Da kommt es auf die Anforderungen an.

Das habe ich schon berücksichtigt. Wer einen HC-SR04 verwendet hat keine 
hohen Anforderungen. Wenn der halbwegs korrekte Zentimeter-Werte 
liefert, kann man sich schon glücklich schätzen.

Wer mehr Genauigkeit braucht als +/-1 cm braucht, muss einen anderen 
Sensor verwenden. Das wird dem Fabio angesichts des Preises des Sensors 
sicher klar sein.

von Wolfgang (Gast)


Lesenswert?

Fabio M. schrieb:
> Solche Genauigkeit brauche ich nicht. Ein Zentimeter genau ist schon
> mehr als nur perfekt.

Bei einer Temperaturänderung von 20 K weicht der Messungwert schon bei 
einer Strecke von 30 Zentimeter um mehr als einen Zentimeter vom wahren 
Wert ab.

von Fabio M. (fabiom)


Lesenswert?

Ich muss euch allen schon einmal vielmals Danken. Es funktioniert jetzt. 
Ich habe den 10ms Takt erstmal weggelassen und nur 1 Sensor benutzt, 
aber ich kann es ja noch hinzufügen. Ich habe leider eine 10cm Toleranz. 
Das kann ich aber ja mit der Zeit anpassen. Trotzdem könntet ihr noch 
einmal über meinen Code schauen?
1
#include <avr/io.h>
2
#include <avr/interrupt.h>
3
#include <util/delay.h>
4
#include "Serial.h"
5
6
//Variablen für die Zeit
7
volatile unsigned long USTimePerCM;
8
unsigned long cm = 0;
9
#define TRIGGER_ON   { DDRL |= (1<<PL6); PORTL |= (1<<PL6); }
10
#define TRIGGER_OFF  { PORTL &= ~(1<<PL6); }
11
#define ECHO_SIGNAL  ( PIND & (1<<PD0) )
12
13
unsigned long getUSTimePerCM() {
14
  unsigned long temp;
15
  cli();
16
  temp = USTimePerCM;
17
  sei();
18
  return temp;
19
}
20
21
int main()
22
{
23
  SerialInit();
24
  DDRB |= (1 << PB7);         //Builtin LED
25
26
    // Timer 0 konfigurieren
27
    TCCR0A = (1<<WGM01); // CTC Modus
28
    TCCR0B |= (1 << CS01); //Prescaler 8
29
    // ((1000000/8)/1000) = 125
30
  //prescaler: 8 116
31
  
32
    OCR0A = 116-1;
33
  
34
    // Compare Interrupt erlauben
35
    TIMSK0 |= (1<<OCIE0A);
36
37
    // Global Interrupts aktivieren
38
    sei();
39
40
  while(1) {
41
    unsigned long started;
42
    TRIGGER_ON;
43
    _delay_us(10);
44
    TRIGGER_OFF;
45
    started = getUSTimePerCM();
46
    while(!ECHO_SIGNAL);
47
    while(ECHO_SIGNAL)
48
    {
49
      cm = getUSTimePerCM() - started;
50
    }
51
    SerialPrintNumberULong(cm);
52
    SerialPrintln("");
53
    _delay_ms(60);
54
  }
55
}
56
57
ISR (TIMER0_COMPA_vect) {
58
  USTimePerCM++;
59
}

von Stefan F. (Gast)


Lesenswert?

Fabio M. schrieb:
> Ich habe leider eine 10cm Toleranz.

Das klingt lustig. Erkläre mal, was du damit ausdrücken wolltest, ich 
komme nicht dahinter.

von Fabio M. (fabiom)


Lesenswert?

Der Wert ist immer ungefähr 10cm höher.

von Stefan F. (Gast)


Lesenswert?

Fabio M. schrieb:
> Der Wert ist immer ungefähr 10cm höher.

Das ist mehr als erwartet - sehr viel mehr.

Versuche mal, diese beiden Zeilen zu vertauschen:
1
started = getUSTimePerCM();
2
while(!ECHO_SIGNAL);

Begründung: Die Breite des Echo-Pulses (High Pegel) soll gemessen 
werden, nicht die Dauer vom Trigger bis zum Ende des Echo-Pulses.

von Fabio M. (fabiom)


Lesenswert?

Perfekt! Jetzt ist es auf den Zentimeter genau.

von Stefan F. (Gast)


Lesenswert?

Und jetzt 10 Sensoren:
1
    while(!ECHO_SIGNAL1); // assuming that all sensors need the same time
2
3
    started = getUSTimePerCM();
4
    uint8_t count=0;
5
    while(count<0)
6
    {
7
      if (!ECHO_SIGNAL1)
8
      {
9
        cm1 = getUSTimePerCM() - started;
10
      }
11
      if (!ECHO_SIGNAL2)
12
      {
13
        cm2 = getUSTimePerCM() - started;
14
      }
15
      ...
16
    }




Bist du sicher, dass du keine Zustandsautomaten verwenden willst? Der 
Vorteil wäre, dass du dann während der Messung noch was anderes machen 
könntest.

von Fabio M. (fabiom)


Lesenswert?

Stefanus F. schrieb:
> Bist du sicher, dass du keine Zustandsautomaten verwenden willst? Der
> Vorteil wäre, dass du dann während der Messung noch was anderes machen
> könntest.

Doch, aber ich bin mir noch nicht 100% sicher wie er funktionieren soll. 
Wie Zustandsautomaten funktionieren können weiß ich. Aber nicht in 
diesem Fall.

Ich habe bis jetzt leider erst einen Sensor zu testen, die anderen 
werden noch geliefert.

: Bearbeitet durch User
von Fabio M. (fabiom)


Lesenswert?

Stefanus F. schrieb:
> Bist du sicher, dass du keine Zustandsautomaten verwenden willst? Der
> Vorteil wäre, dass du dann während der Messung noch was anderes machen
> könntest.

Meinst du das als Zustandsautomat? Da es nur 2 Zustände hat habe ich es 
mit einer if-Abfrage gelöst.
1
  while(1) {
2
    if ((getUSTimePerCM() / 59)*1000 - lastChecked >= 60) {
3
      lastChecked = getUSTimePerCM();
4
      unsigned long started;
5
      TRIGGER_ON;
6
      _delay_us(10);
7
      TRIGGER_OFF;
8
      
9
      while(!ECHO_SIGNAL);
10
      
11
      started = getUSTimePerCM();
12
      while(ECHO_SIGNAL) {
13
        cm = getUSTimePerCM() - started;
14
        if (getUSTimePerCM() - started > 350)
15
        {
16
          cm = 350;
17
          break;
18
        }
19
      }
20
      SerialPrintNumberULong(cm);
21
      SerialPrintln("");
22
    }
23
  }

von Stefan F. (Gast)


Lesenswert?

So ist gar nicht gut:
> if ((getUSTimePerCM() / 59)*1000 - lastChecked >= 60) {

Überlege mal, wie viel Rechenzeit wiegen dieser Zeile bei jedem 
Schleifendurchlauf drauf geht. Du hast da eine Division und eine 
Multiplikation bei jedem Schleifendurchlauf.

Besser so:

> if (getUSTimePerCM() - lastChecked >= 60*1000/58) {

Der Unterschied ist, dass in diesem Fall die Multiplikation und die 
Division beide vom Compiler vorberechnet werden können. Die Kosten zur 
Laufzeit sind 0.

60*1000/58 = 1034.

Das würde ich in eine definition auslagern, um dem Ausdruck einen 
sprechenden Namen zu geben:
1
#define TIME_60_MS = 60*1000/58;
2
if (getUSTimePerCM() - lastChecked >= TIME_60_MS) {
3
   ...
4
}

von Wolfgang (Gast)


Lesenswert?

Stefanus F. schrieb:
> So ist gar nicht gut:
>> if ((getUSTimePerCM() / 59)*1000 - lastChecked >= 60) {
>
> Überlege mal, wie viel Rechenzeit wiegen dieser Zeile bei jedem
> Schleifendurchlauf drauf geht.

Das ist primär erstmal kein Rechenzeitproblem. Solange die Rechenung 
derart herbe Rundungsfehler produziert, gibt es noch ganz andere Gründe, 
bei einer Ganzzahlrechnung über die Reihenfolge der Rechenschritte 
nachzudenken.

von Stefan F. (Gast)


Lesenswert?

Während du auf das Echo Signal wartest ist das Programm aber immer noch 
blockiert. Stelle Dir mal vor, du möchtest parallel dazu die Tasten 
eines Bedienfeldes abfragen oder eine LED blinken lassen. Das geht so 
nicht. Da musst du noch mehr Zustände einführen. Ein Zustandsautomat 
darf nur eine einzige Wiederholschleife enthalten, und zwar die 
main-loop. Und delays sind tabu.
1
int main()
2
{
3
   init_io_pins();
4
   init_timer();
5
   init_whatever();
6
   for(;;) // forever
7
   {
8
      measure_distances();
9
      query_buttons();
10
      blink_leds();
11
   }
12
}
1
void measure_distances()
2
{
3
    static enum {WAIT_60MS, TRIGGER_START, TRIGGER_TIME, WAIT_ECHO, MEASURE} status=WAIT_60MS;
4
    uint32_t lastChecked=0;
5
    uint32t echoStart=0;
6
    uint32_t triggerStart=0;
7
    
8
    switch (status)
9
    {
10
        case WAIT_60MS:
11
            if (getUSTimePerCM() - lastChecked >= TIME_60_MS) // 60ms elapsed
12
            {
13
                status=TRIGGER_START;
14
            }
15
            break;
16
17
        case TRIGGER_START:
18
            TRIGGER_ON;
19
            triggerStart=getUSTimePerCM();
20
            status=TRIGGER_TIME;
21
            break;
22
23
        case TRIGGER_TIME:
24
            if (getUSTimePerCM() - triggerStart >= 1) // 58µs elapsed
25
            {
26
                TRIGGER_OFF;
27
                status=WAIT_ECHO;
28
            }
29
            break;
30
31
        case WAIT_ECHO:
32
            if (ECHO_SIGNAL1) // echo signal begins (HIGH)
33
            {
34
                echoStart=getUSTimePerCM();
35
                echoCount=0;
36
                finishedBits=0;
37
                status=MEASURE;
38
            }
39
            break;
40
41
        case MEASURE:
42
            int needToWait=0;
43
            uint32_t now=getUSTimePerCM();
44
            if (ECHO_SIGNAL1)
45
            {
46
                needToWait=1;
47
            }
48
            else
49
            {
50
                cm1=now-echoStart;
51
            }
52
            if (ECHO_SIGNAL2)
53
            {
54
                needToWait=1;
55
            }
56
            else
57
            {
58
                cm2=now-echoStart;
59
            }
60
            ...
61
            if (ECHO_SIGNAL10)
62
            {
63
                needToWait=1;
64
            }
65
            else
66
            {
67
                cm10=now-echoStart;
68
            }
69
70
            if (needToWait==0) // All sensors are done (LOW)
71
            {
72
                lastChecked=now;
73
                status=WAIT_60MS;
74
            }
75
            break;
76
        }    
77
    }
78
}

Ich habe das jetzt einfach schnell runter getippt. Könnte Logik- und 
Syntaxfehler enthalten.

Der Triggerimpuls ist dieses mal 58µs lang, weil dies das kleinste 
messbare Intervall mit dem Timer ist.

Bitte melde dich an um einen Beitrag zu schreiben. Anmeldung ist kostenlos und dauert nur eine Minute.
Bestehender Account
Schon ein Account bei Google/GoogleMail? Keine Anmeldung erforderlich!
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.