Verständnisfrage Timer1 ATMEGA 328P

#7665479
Lesenswert?

Hallo zusammen. Kurz etwas zu mir. Bin Rentner und möchte mich etwas in der AVR Programmierung in C weiterbilden. Beruflich hatte ich bisher noch nie etwas mit dem programmieren zu tun. Privat hat mich die Elektronik und das Programmieren schon vor über 40 Jahren gepackt. Damals kamen die TTL's aus der 74 Serie zu erschwinglichen Preisen auf den Markt. Im Selbststudium habe ich mich dann in die Elektronik und das programmieren eingearbeitet. Vom hgw. BASIC , VB3.0, BASCOM über ARDUINO komme ich nun zum C. Ich will endlich wissen, was intern wirklich passiert. Fertige Funktionen kann fast jeder verwenden, aber was dahinter steckt ist interessanter. Und um es vorweg zu nehmen: ASM passt nicht mehr in meinen alten Kopf. Das ist mir nun wirklich zu Kryptisch.

Nun zu meiner eigentlichen Frage:

Ich will mit dem Ultraschallsensor HC-SR04 rumspielen. Klar gibt es unzählige fertige Programme dafür. In BASCOM und beim ARDUINO funktioniert es relativ gut. Ich will aber selbst etwas machen. Damit verstehe ich es dann auch besser.

Ich möchte den Timmer1 als Input Capture nutzen TCCR1A bleibt auf 0x00 TCCR1B ICES1 auf 0 (für fallende Flanke ) CS10 bis 12 Prescaller einstellen TIMSK1 ICIE1 für den Input Capture Interrupt setzen TOIE1 für Überlauf setzen

Dann im Programm den Timer mit sei() starten Wenn am ICP1 PIN eine fallende Flanke erkannt wird => Sprung in ISR TIMER1 CAPT. in der ISR : -ICR1 in 16 Bit Variable speichern -Timer1 mit cli() stoppen ISR ENDE

Auswerten der Daten

Ist meine Logik so für den Programmablauf richtig?

#7665506
Lesenswert?

Ohne jetzt nochmal selber im Datenblatt die Bedeutung der Bits nachzuschauen, kann ich schonmal sagen cli und sei beeinflussen nur die Freigabe aller Interrupts, der Timer läuft weiter, ohne dass eine Interruptroutine abgearbeitet werden müsste. Er läuft weiter nach einem Überlauf, er kopiert seinen Stand in das Input Capture Register, falls er so eingestellt ist, ohne dass die CPU ihm dabei helfen müsste. Man kann sogar bei gesperrten Interrupts den Wert aus dem Input Capture Register auslesen. Die CPU unterbricht halt nur nicht die Abarbeitung des Hauptprogramms für einen Interrupt. Zähler starten und stoppen geht über ein oder mehrere Bits, die sich meist im Control Register für den Timer befinden. Bei manchen Controllern stoppt man den Timer, indem man seinen Takt im Vorteiler abschaltet.

#7665510
Lesenswert?

Ist meine Logik so für den Programmablauf richtig?

Das Konzept ist wohl noch nicht ganz vollständig: gefragt ist ja die Zeitspanne des 'Echo Pulse Output' zwischen steigender und fallender Flanke, nachdem zuvor ein 'Trigger Input to Module' auf das HC-SR04 gegeben wurde.

#7665546
Lesenswert?

Jetzt nochmal mit Datenblatt : Falls nicht nur Software von Dir läuft, schauen, dass nicht irgendein Öko Bit 3 im PRR gesetzt hat oder einfacher : es unbesehen löschen.

Wenn Du nichts ausgeben willst, ist der Waveform Generation Mode 0 vermutlich der, den Du brauchst. Also WGM10, WGM11, WGM12 und WGM13 auf 0 setzen. Du willst keine Ausgangspins vom Timer ändern lassen ? Dann setze COM1A1, COM1A0, COM1B1 und COM1B0 auf 0. Ergibt zusammen den Wert 0 für TCCR1A.

Den Wert für TCCR1B kannst Du Dir ja jetzt selber zusammensuchen. Ein paar Bits davon hatte ich ja schon erwähnt. Die anderen musst Du setzen, je nachdem, ob Du verhindern magst, dass extrem kurze Pulse ein ICP auslösen, welche Flanke ICP auslösen soll und wie schnell der Timer zählen soll. Wobei Takt vom T1 Pin beziehen höchstwahrscheinlich nicht das ist, was Du willst.

Und schon müsste der Zähler zählen... also die Werte in TCNT1 sollten sich ändern.

Ich würde sagen, schieb das mit den Interrupts auf später, lass also TIMSK1 auf 0 stehen.

Schreibe zum Schluss der Initialisierung 0x27U ins TIFR1.

Wenn Bit 5 vom TIFR1 gesetzt ist, hat mindestens ein ICP stattgefunden. Schreibe dann 0x20U in TIFR1, um das Bit zu löschen (klingt erstmal komisch, ist aber so und hat durchaus seinen Grund). Dann kannst Du ICR1 auslesen, um zu erfahren bei welchem Zählerstand das letzte ICP stattgefunden hat.

Wenn Bit 0 vom TIFR1 gesetzt ist, ist der Timer übergelaufen. Dieses Bit löscht Du durch schreiben von 0x01U ins TIFR1.

#7665561
Lesenswert?

dann hat man etwas Schreibarbeit gespart

Zwar wurde ja gleich zu Beginn geklärt, dass solche Überlegungen zu "kryptisch" sind, trotzdem sei darauf hingewiesen, dass es sich nicht um reine Bequemlichkeit handelt: 'For a 16-bit read, the low byte must be read before the high byte'.

#7665582
Lesenswert?

Peter H. schrieb:

im Programm den Timer mit sei() starte in der ISR -Timer1 mit cli() stoppen

Nein, so macht man das nicht.

Mit sei()/cli() kann man den Timer nicht beeinflussen. Damit kann man nur bestimmen, ob die CPU irgendwelche (Timer und alle anderen) Interrupt-Routinen anspringt oder nicht.

Durch Schreiben in TIMSK1 kann man bestimmen, ob der Timer Interrupts auslöst oder nicht, er läuft aber unabhängig von dieser Einstellung so, wie du ihn initialisiert hast.

Am besten versuchst du es zunächst mal ganz ohne Interrupts, du kannst ja in einer Schleife TIFR1 auslesen, um zu sehen, wann das Capture-Event kommt.

#7665627
Lesenswert?

Ich habe mal etwas mit dem HC-SR04 nach euren Angaben rumgespielt. Die Idee mit dem PIN Change Interrupt war gut. Da kann man ja ohne Probleme mehrere Ultraschall Sensoren einsetzen. Aber ich bin ja immer noch neugierig. Mir geistert immer noch der TIMER1 CAPT Interrupt im Kopf rum.

So stelle ich mir das vor: Ich schicke einen 10us Impuls auf den Trigger des HC. gleichzeitig stelle ich den Timer1 mit TCNT1=0 zurück. Wenn ich den Echo PIN mit ICP1 Pin verbinde (mit fallender Flanke) entsteht doch am Ende des ECHO Signals ein TIMER1 CAPT Interrupt. Im Interrupt lese ich ICR1 aus Das Ergebnis ist die Anzahl der Ticks vom Timer1. ( muß nicht mehr berechnet werden und kann direkt weiterverarbeitet werden)

Ist das OK so, oder vielleicht nur eine Spinnerei von mir

( nach dem Motto: Geht nicht, gibt es nicht oder Versuch macht kluch )

#7665636
Lesenswert?

Ja, geht; die Genauigkeit ist etwas eingeschränkt.

Ansonsten: zuerst ICP auf positive Flanke, in der ISR (Start des Echo-Signals) ICR lesen und speichern und ICP auf negative Flanke umstellen, im anschließenden Interrupt (das Ende des Echo-Signals) wieder ICR lesen und von diesem Wert den vorher gespeicherten abziehen.

PS: Das geht auch (wie mehrfach vorgeschlagen) ohne Interrupt, also nur durch Abfrage (und explizite Löschung) von TIFR1.ICF1, aber viel einfacher wird es dadurch nicht.

PPS: Fällt mir jetzt erst auf:

... nach euren Angaben rumgespielt. Die Idee mit dem PIN Change Interrupt war gut

Finde ich nicht, wo steht das?

#7665663
Lesenswert?

S. L. schrieb:

Ja, geht; die Genauigkeit ist etwas eingeschränkt.

Ansonsten: zuerst ICP auf positive Flanke, in der ISR (Start des Echo-Signals) ICR lesen und speichern und ICP auf negative Flanke umstellen, im anschließenden Interrupt (das Ende des Echo-Signals) wieder ICR lesen und von diesem Wert den vorher gespeicherten abziehen.

2 Fragen dazu:

  1. Wieseo ist die Genauigkeit eingeschränkt?
  2. Wenn ich direkt ICP auf fallende Flake einstelle und direkt mit dem Start des Triggersignals Timer1 mit TCNT1=0 zurücksetze z.B. PB1=1; TCNT1=0; _delay_us(10); PB1=0; dürfte ich doch nur sehr wenige Systemtakte verlieren. (Nur die Dauer für das Rücksetzen von TCNT1)
#7665699
Lesenswert?

Matthias S. schrieb:

Denke dran, in dieser ISR den Interrupt selber zu sperren, weil nur das erste Echo das ist, was du messen möchtest. Erst für den nächsten Messzyklus den CAPT IRQ wieder freigeben.

Das brauche ich nicht unbedingt, da ich die Messzyklen erst wieder nach der kompletten Abarbeitung der Auswertung der Ergebnisse selbst im Programm wieder starte. Die automatisierten Messzyklen in den Programmen im Netz haben mich immer gestört. Die haben mir immer in meine Auswertung gepfuscht und die Ergebnisse gestört. Trotzdem schalte ich nach der ISR den ICIE1 wieder aus (falls Störimpule auf der Echoleitung auftreten) und kurz vor dem Triggerimpuls wieder ein.

Danke für den Tip!

#7665712
Lesenswert?

S. L. schrieb:

Oh nein, das Triggersignal ist keineswegs der Beginn des 'Echo Pulse Output', dazwischen liegt der '8 Cycle Sonic Burst' und dessen Auswertung, und damit eine Zeitspanne, über die das Datenblatt nichts aussagt.

Oh, vielen Dank für die Hilfe. Kann ich einige Referenzmessungen durchführen und den Mittelwert des Fehlers vom Ergebnis abziehen? Dadurch könnte ich dann die Impulse zwischen Timerstart und Start des Echo Signals ermitteln Oder macht es doch mehr Sinn, den Echo PIN mit Pinchange Interrupt zu überwachen. D.h. Start Timer ( 1. Interrupt) bei steigender Flanke Stop Timer (2. Interrupt) bei fallender Flanke

#7665720
Lesenswert?

Ich kann Ihrem Gedankengang nicht ganz folgen (bin auch zu müde). Nur soviel, gerade ausprobiert: bei meinem Exemplar liegt zwischen pos. Flanke des Triggersignals und pos. Flanke des Echos eine Zeitspanne von rund 2.2 ms, unabhängig von der Objektentfernung.

PS: also tatsächlich 2,2 Millisekunden

(Firma: FBI) #7666008
Lesenswert?

Wie das geht seht exact beschrieben in der Microchip AppNote "AVR135: Using Timer Capture to Measure PWM Duty Cycle". Nichts anderes ist das. Ein alter Hut. Neuere Chips der NULL-Serie haben dafür eine HW Funktion (Pulse Width detection).

https://www.microchip.com/content/dam/mchp/documents/OTH/ApplicationNotes/ApplicationNotes/Atmel-8014-Using-Timer-Capture-to-Measure-PWM-Duty-Cycle_ApplicationNote_AVR135.pdf

#7666232
Lesenswert?

S. L. schrieb:

Oh nein, das Triggersignal ist keineswegs der Beginn des 'Echo Pulse Output', dazwischen liegt der '8 Cycle Sonic Burst' und dessen Auswertung, und damit eine Zeitspanne, über die das Datenblatt nichts aussagt.

PS: Zeitdiagramm angehängt

Der Text in diesem Zeitdiagramm ist sehr sehr mißverständlich.

Der HC-SR04 funktioniert so: Man erzeugt einen Trigger-Impuls von ~10us Dauer. Dies veranlasst den HC-SR04 seine 8 Ultraschallwellen auszustoßen, und danach Echo auf HIGH zu setzen. Nach einer Weile läuft das Ultraschallecho beim HC-SR04 ein und nach der Erkennung der 8 Wellen setzt der HC-SR04 Echo wieder auf LOW. Die Laufzeit des Ultraschallsignals entspricht also der Zeit zwischen Echo LOW->HIGH und Echo HIGH->LOW.

Die genaueste Art und Weise diese Laufzeiten mit einem Atmega328P zu ermitteln ist über ICP. Interrupts sind dafür oft nicht nötig, wenn man die Wartezeit bis zum Abbruchkriterium akzeptieren kann.

Mein Code dazu sieht in etwa so aus:

1
#define DIST_DDR DDRD
2
#define DIST_PORT PORTD
3
#define DIST_TRIGGER (1<<PIND6) // TRIGGER 
4
#define DIST_ECHO (1<<PIND6) // PD6 is used as ICP1. The ECHO signal is connected to PD6 via 1k (TRIGGER is connected directly).
5

6
struct Hcsr04 {
7
  uint32_t sum;
8
  uint8_t num, bad;
9
} hcsr04;
10

11
void timer1_setup () {
12
  TCCR1A = 0;
13
  TCCR1B = (1<<CS11);         // Prescaler 8, 2 ticks per μs at 16MHz.
14
  TCCR1C = 0;
15
}
16

17
void hcsr04_measure () {
18
  DIST_DDR |= DIST_TRIGGER; // Make DIST_TRIGGER OUTPUT
19
  DIST_PORT &= ~DIST_TRIGGER;  // OUTPUT LOW
20
  delayMicroseconds(10);           // Output LOW explicitely.
21
  DIST_PORT |= DIST_TRIGGER;   // OUTPUT HIGH
22
  delayMicroseconds(10);           // Wait long enough for the sensor to realize the trigger pin is high. Sensor specs say to wait 10μs.
23
  DIST_PORT &= ~DIST_TRIGGER;  // OUTPUT LOW
24
  delayMicroseconds(10);            // Output LOW explicitely.
25
  DIST_DDR &= ~DIST_ECHO; // Switch to INPUT
26
  delayMicroseconds(10);            // Give echo some time to go low
27
  TCNT1 = 0;
28
  TCCR1B |= (1<<ICES1);            // Trigger on rising edge of ICP1. 
29
  TIFR1 |= (1<<ICF1) | (1<<TOV1); // Clear flags by writing 1
30
  while ((TIFR1 & ((1<<ICF1)|(1<<TOV1))) == 0) continue; // Wait for rising edge or overflow (after 32ms)
31
  if (TIFR1 & (1<<ICF1)) {
32
    uint16_t stick = ICR1;                  // Retrieve timer value stored at rising edge
33
    TCCR1B &= ~(1<<ICES1);        // Trigger on falling edge of ICP1
34
    TIFR1 |= (1<<ICF1);              // Clear flag by writing 1
35
    while ((TIFR1 & ((1<<ICF1)|(1<<TOV1))) == 0) continue; // Wait for falling edge or overflow (after 32ms)
36
    if (TIFR1 & (1<<ICF1)) {
37
      uint16_t etick = ICR1;                 // Retrieve timer value stored at falling edge
38
      hcsr04.sum += etick - stick;
39
      hcsr04.num++;
40
    } else {
41
      hcsr04.bad++;
42
    }
43
  } else {
44
    hcsr04.bad++;
45
  }
46
}

LG, Sebastian

#7666368
Lesenswert?

Nach unzähligen Fehlversuchen habe ich mal meinen Ultraschallsensor an das Oszi gehangen. Siehe da, es sendet kein Echo. Also ein funktionierenden genommen und siehe da es kommt zumindest mal ein Ergebnis raus. Hier ist meine Version für den Ultraschallsensor. Verbesserungsvorschläge nehme ich gerne entgegen. Ich muss sie nur mit meinem begrenzten Wissen verstehen können.

void HC_SR04_start () { TIMSK1 |=(1<<ICIE1); // Interrupt Starten PORTD |=(1<<PORTD0); // Trigger einschalten _delay_us(10); // Trigger dauer PORTD &=(1<<PORTD0); // Trigger ausschalten } void Timer1_init () { TCCR1A = 0x00; // nicht gebraucht
TCCR1B |=(1<<CS11); // Prescaller von 8 TCCR1B |=(1<<ICES1); // steigende Flanke TCCR1C = 0x00; // nicht gebraucht TIMSK1 |=(1<<ICIE1); // Interrupt Starten } ISR (TIMER1_CAPT_vect) { if (start==1) { TCNT1=0; // Timer zurücksetzen TCCR1B &=
(1<<ICES1); // umstellen aud fallende Flanke start=0; // für Interrupt } else { TIMSK1 &=~(1<<ICIE1); // Interrupt Stoppen Dezimalwert=ICR1; // Timerstand übergeben TCCR1B |= (1<<ICES1); // umstellen auf steigende Flanke start=1; // für Interrupt } } In der Main rufe ich nur sporadich die HR... Funktion auf und lasse dann die Variable Dezimalwert auf meiner LCD Funktion ausgeben.

#7666385
Lesenswert?

Peter H. schrieb:

Verbesserungsvorschläge nehme ich gerne entgegen. Ich muss sie nur mit meinem begrenzten Wissen verstehen können.

Für den Anfang ganz OK.

void Timer1_init () { TCCR1A = 0x00; // nicht gebraucht TCCR1B |=(1<<CS11); // Prescaller von 8 TCCR1B |=(1<<ICES1); // steigende Flanke TCCR1C = 0x00; // nicht gebraucht TIMSK1 |=(1<<ICIE1); // Interrupt Starten }

ISR (TIMER1_CAPT_vect) { if (start==1) { TCNT1=0; // Timer zurücksetzen TCCR1B &=~(1<<ICES1); // umstellen aud fallende Flanke start=0; // für Interrupt }

Bei Input Capture muss man den Timer nicht anhalten, man läßt ihn einfach laufen. Die Differenz aus STOP-START Wert ist immer korrekt, auch beim Überlauf, solange die Differenz kleiner als der halbe, maximale Zählerstand ist, hier bei Timer 1 816 Bit) 32768.

else { TIMSK1 &=~(1<<ICIE1); // Interrupt Stoppen Dezimalwert=ICR1; // Timerstand übergeben TCCR1B |= (1<<ICES1); // umstellen auf steigende Flanke start=1; // für Interrupt } }

In der Main rufe ich nur sporadich die HR... Funktion auf und lasse dann die Variable Dezimalwert auf meiner LCD Funktion ausgeben.

Hoffentlich gesichert über volatile und mit atomarem Zugriff, siehe Interrupt.

Beitrag "Re: Atmega/Arduino - Input Capture funktioniert nicht"

Antwort schreiben

Bitte melde dich an, um einen Beitrag zu schreiben.

oder

Mit Google-Account einloggen

Die Registrierung ist kostenlos und dauert nur eine Minute.

Jetzt registrieren