mikrocontroller.net

Forum: Mikrocontroller und Digitale Elektronik Atmega328 1-Wire mit Timer CTC/PWM


Announcement: there is an English version of this forum on EmbDev.net. Posts you create there will be displayed on Mikrocontroller.net and EmbDev.net.
von A. Z. (donvido)


Bewertung
0 lesenswert
nicht lesenswert
Hallo miteinander,

Ich bin da ein bisschen am verzweifeln. Und zwar wollte ich mir eine 
1Wire Lib schreiben, die auf Timer Interrupts setzt, da mir die 
Übertragungsdauern von bis zu 14ms (mit LogicAnalyzer gemessen) für den 
blockierenden Betrieb zu lange dauern.

Mein Problem ist nun, dass ich bereits beim Start des Timers einen 
Interrupt erhalte, der da nach meinem Verständnis nicht kommen dürfte.
Mit CTC hatte ich scheinbar das Timing nicht geschafft, deshalb bin ich 
auf PWM Mode 15 gewechselt.
Wo ist also mein Denkfehler?
// Configure Timer -> Fast PWM (Mode 15)
  TCCR1A = (1 << COM1A0)|(1 << WGM11)|(1 << WGM10); 
  TCCR1B = (1 << WGM13)|(1 << WGM12); 
  OCR1A = 0xFFFF; // avoid TOV at start of timer
  ICR1 = 0xFFFF; // should be unused
  TIFR1 |= (1 << TOV1); //clear TOV Interrupt
  TIMSK1 |= (1 << TOIE1); //enable TOV Interrupt

// start procedure
  pinMode(pin, OUTPUT);
  OCR1A = 16*480-1; // set timer interrupt to 480µs
  TCNT1 = 0; //reset timer
  TCCR1B |= (1 << CS10); //start the counter

von c-hater (Gast)


Bewertung
0 lesenswert
nicht lesenswert
A. Z. schrieb:

> Mein Problem ist nun, dass ich bereits beim Start des Timers einen
> Interrupt erhalte, der da nach meinem Verständnis nicht kommen dürfte.

Du hast das:

>   TIFR1 |= (1 << TOV1); //clear TOV Interrupt
>   TIMSK1 |= (1 << TOIE1); //enable TOV Interrupt

an der falschen Stelle erledigt. Das tut man unmittelbar bevor man das 
tut:

>   TCCR1B |= (1 << CS10); //start the counter

insbesondere, wenn man dazwischen mit solchem unnützen Vollschrott wie 
dem Arduino-Gedöhns hantiert, wo

>   pinMode(pin, OUTPUT);

Schonmal ein "paar" Takte länger dauern kann als das Äquivalent in einer 
sinnvollen Umgebung...

von c-hater (Gast)


Bewertung
0 lesenswert
nicht lesenswert
c-hater schrieb:

Ergänzend:

>>   TIFR1 |= (1 << TOV1); //clear TOV Interrupt

Das war sowieso Blödsinn. TIFR1 enthält nur strobe-Bits. Korrekt wäre:

TIFR1 = (1 << TOV1);

von A. Z. (donvido)


Bewertung
0 lesenswert
nicht lesenswert
c-hater schrieb:
> an der falschen Stelle erledigt. Das tut man unmittelbar bevor man das
> tut:
Aber wodurch wird denn der Interrupt ausgelöst, OCR1A ist zu dem 
Zeitpunkt, wo das TOIE1 Bit gesetzt wird, ungleich TCNT1 und TCNT1 läuft 
noch nicht, da CS10 noch nicht gesetzt ist.

c-hater schrieb:
> TIFR1 enthält nur strobe-Bits. Korrekt wäre:
>
> TIFR1 = (1 << TOV1);

Das nehme ich mal so mit. Muss mir so reingerutscht sein ^^.

: Bearbeitet durch User
von c-hater (Gast)


Bewertung
0 lesenswert
nicht lesenswert
A. Z. schrieb:

> Aber wodurch wird denn der Interrupt ausgelöst, OCR1A ist zu dem
> Zeitpunkt, wo das TOIE1 Bit gesetzt wird, ungleich TCNT1 und TCNT1 läuft
> noch nicht, da CS10 noch nicht gesetzt ist.

Wenn er aber bereits lief (nur so ist die Sache erklärbar)? Direkt nach 
einem  Reset würde es funktionieren.

von Peter D. (peda)


Bewertung
0 lesenswert
nicht lesenswert
A. Z. schrieb:
> Mit CTC hatte ich scheinbar das Timing nicht geschafft, deshalb bin ich
> auf PWM Mode 15 gewechselt.

Ganz schlechte Idee, in den PWM-Modi werden einige Register verzögert 
geladen.
Nimm CTC oder Normal-Mode.

von c-hater (Gast)


Bewertung
0 lesenswert
nicht lesenswert
Peter D. schrieb:

> Ganz schlechte Idee, in den PWM-Modi werden einige Register verzögert
> geladen.

Das ist so gewollt und i.d.R. auch überaus nützlich. Man muß bloß 
verstehen, was die Konsequenz ist und den Timer entsprechend benutzen...

Scheint aber hier nicht das Problem zu sein...

von A. Z. (donvido)


Angehängte Dateien:

Bewertung
0 lesenswert
nicht lesenswert
Also ich habe erstmal wieder auf CTC umgebaut und es sieht soweit auch 
ganz gut aus.

So sieht der Code für die erste Übertragung aus, also das initiale 
Reset:
  // CTC (Mode 4)
  TCCR1A = (1 << COM1A0); 
  TCCR1B = (1 << WGM12); 


  DDRB|=(1<<DDB1); // pinMode(9, OUTPUT); 
     
  OCR1A = 16*480-1;
  TCNT1 = 0; //reset timer
  TIFR1 = (1 << OCF1A); //clear OC1A Interrupt
  TIMSK1 |= (1 << OCIE1A); //enable OC1A Interrupt
  TCCR1B |= (1 << CS10); //start the timer


Jetzt bekomme ich einen Presence Pulse und kann auch ein Byte (0x33) 
übertragen, aber nach den ersten 4 Bit verpasst er OCR1A und muss 
erstmal bis zum Overflow hochzählen. Dabei unterscheidet sich der Code 
für das 5te Bit nich, bzw. ist der gleiche wie für die anderen. Andere 
Interrupt etc. sind auch nicht aktiv.

von c-hater (Gast)


Bewertung
0 lesenswert
nicht lesenswert
A. Z. schrieb:

> Jetzt bekomme ich einen Presence Pulse und kann auch ein Byte (0x33)
> übertragen, aber nach den ersten 4 Bit verpasst er OCR1A und muss
> erstmal bis zum Overflow hochzählen. Dabei unterscheidet sich der Code
> für das 5te Bit nich, bzw. ist der gleiche wie für die anderen. Andere
> Interrupt etc. sind auch nicht aktiv.

Zeig' den verdammten Code. Kann nur ein Bug sein, denn prinzipiell ist 
es kein nennenswertes Problem, OneWire asynchron zu implementieren. Ich 
habe das vor ca. 10 Jahren schon getan...

von Arduino Fanboy D. (ufuf)


Bewertung
0 lesenswert
nicht lesenswert
c-hater schrieb:
> Wenn er aber bereits lief (nur so ist die Sache erklärbar)? Direkt nach
> einem  Reset würde es funktionieren.

Im init, also vor Setup werden alle Timer in Betrieb gesetzt.
Timer0 für PWM und Millis() usw.
Timer 1 und 2 für PWM

Von daher kann es sich schon lohnen, vor einer eigenen initialisierung,
Die die Timer Register explizit zu leeren und so auch den Timer 
anzuhalten.

c-hater schrieb:
> insbesondere, wenn man dazwischen mit solchem unnützen Vollschrott wie
> dem Arduino-Gedöhns hantiert, wo
>
>>   pinMode(pin, OUTPUT);
>
> Schonmal ein "paar" Takte länger dauern kann als das Äquivalent in einer
> sinnvollen Umgebung...

Ach...
Diese einseitige Sicht, was soll das denn...
Hier wird doch nix mit PinMode gemacht, oder?


Die paar Takte, die machen doch in den seltensten Fällen irgendwas aus.
Auch gibts ja immer noch die Möglichkeit pinMode() durch was schnelleres 
zu ersetzen, wenn es denn sein muss.

Zudem gibt es ja auch dutzende verschiedene pinMode() Implementierungen.
Für jeden "Arduino fähigen" µC eine eigene.
Aber du begnügst dich ja damit die AVR Implementierung an den Pranger zu 
stellen.
Etwas beschränkt, nicht war?

: Bearbeitet durch User
von A. Z. (donvido)


Bewertung
0 lesenswert
nicht lesenswert
ISR(TIMER1_COMPA_vect) {
  ds.event_handlerCTC();
}

void OneWire::write(uint8_t *data, uint8_t length = 1) {
  msg.data = data;
  msg.reading = false;
  msg.length = length;
  msg.position = 0;

  irq_flag = irq_flag_e::reset_read;
  busy = true;

  DDRB|=(1<<DDB1);
  
  OCR1A = 16*480-1;
  TCNT1 = 0; //reset timer
  TIFR1 = (1 << OCF1A); //clear TOV Interrupt
  TIMSK1 |= (1 << OCIE1A); //enable TOV Interrupt
  TCCR1B |= (1 << CS10); //start the timer
}

void OneWire::event_handlerCTC(void) {
      uint16_t delay = 0;
      switch (irq_flag) {
        case irq_flag_e::end:
          PORTB|=(1<<PORTB5);
          DDRB&=~(1<<DDB1);
          TCCR1B &= ~(1 << CS10);
          TIMSK1 &= ~(1 << OCIE1A);
          busy = false;
          return;

        case irq_flag_e::reset_read:
          DDRB&=~(1<<DDB1);
          delay = 16*80-1;
          irq_flag = irq_flag_e::reset_high;
          break;

        case irq_flag_e::reset_high:
          // uint8_t r;
          // r=digitalRead(pin);
          TCCR1C=(1<<FOC1A);
          PORTB&=~(1<<PORTB1);
          DDRB|=(1<<DDB1);
          delay = 16*410-1;
          if (msg.length == 0){
            irq_flag = irq_flag_e::end;
          }else {
            if (msg.reading)
              irq_flag = irq_flag_e::read1;
            else
              irq_flag = irq_flag_e::write_low;
          }
          break;

        case irq_flag_e::read1: //unused
          break;

        case irq_flag_e::read2: //unused
          break;

        case irq_flag_e::write_low:
          if (msg.data[msg.position] & (1 << i)) {
            delay = 16*10-1;
          } else {
            delay = 16*60-1;
          }
          irq_flag = irq_flag_e::write_high;
          break;

        case irq_flag_e::write_high:
          if (msg.data[msg.position] & (1 << i)) {
            delay = 16*60-1;
          } else {
            delay = 16*10-1;
          }
          i++;
          if (i == 8) {
            i = 0;
            msg.position++;
          }
          if (msg.position == msg.length) {
            irq_flag = irq_flag_e::end;
          } else {
            irq_flag = irq_flag_e::write_low;
          }
          break;
      }
      OCR1A = delay;
    }

von S. Landolt (Gast)


Bewertung
0 lesenswert
nicht lesenswert
> Also ich habe erstmal wieder auf CTC umgebaut
Wo oder wie passiert das im aktuellen Programm?

von A. Z. (donvido)


Bewertung
0 lesenswert
nicht lesenswert
S. Landolt schrieb:
>> Also ich habe erstmal wieder auf CTC umgebaut
> Wo oder wie passiert das im aktuellen Programm?

A. Z. schrieb:
> // CTC (Mode 4)
>   TCCR1A = (1 << COM1A0);
>   TCCR1B = (1 << WGM12);

von S. Landolt (Gast)


Bewertung
0 lesenswert
nicht lesenswert
Tut mir leid, ich finde es nicht im Programm von 19:15.

von A. Z. (donvido)


Bewertung
0 lesenswert
nicht lesenswert
Das wird einmal vor Aufruf der write Funktion ausgeführt.
TCCR1A = (1 << COM1A0); // CTC (Mode 4)
TCCR1B = (1 << WGM12);
uint8_t data = 0x33;
ds.write(&data);
while(1);

: Bearbeitet durch User
von S. Landolt (Gast)


Bewertung
0 lesenswert
nicht lesenswert
Etwas stört mich noch dieses
  TIMSK1 |= (1 << OCIE1A); //enable TOV Interrupt
aber eigentlich wäre das die Domäne des Fanboys: hat Arduino vielleicht 
weitere Timer1-Interrupts freigegeben?

von Arduino Fanboy D. (ufuf)


Bewertung
0 lesenswert
nicht lesenswert
S. Landolt schrieb:
> aber eigentlich wäre das die Domäne des Fanboys: hat Arduino vielleicht
> weitere Timer1-Interrupts freigegeben?

Nö...
Timer1 wird nur für PWM vorbereitet!

von c-hater (Gast)


Bewertung
0 lesenswert
nicht lesenswert
A. Z. schrieb:

> Das wird einmal vor Aufruf der write Funktion ausgeführt.

Zeige ein vollständiges (aber so gut wie möglich minimiertes) Programm, 
welches das Problem aufzeigt, wenn du ernsthaft Hilfe erwartest.

Alles andere ist eine grobe Missachtung der potentiell Hilfewilligen.

Wer potentiell Hilfewillige grob missachtet, ist es nicht Wert, dass ihm 
geholfen wird...

So einfach ist das.

von A. Z. (donvido)


Angehängte Dateien:

Bewertung
0 lesenswert
nicht lesenswert
A. Z. schrieb:
> case irq_flag_e::reset_high:
>           // uint8_t r;
>           // r=digitalRead(pin);
>           TCCR1C=(1<<FOC1A);
>           PORTB&=~(1<<PORTB1);
>           DDRB|=(1<<DDB1);
>           delay = 16*410-1;
>           if (msg.length == 0){
>             irq_flag = irq_flag_e::end;


Interessanterweise sieht das Ergebnis besser aus, wenn ich das Timing im 
Resetbereich anpasse und 420µs statt 410µs warte

: Bearbeitet durch User
von A. Z. (donvido)


Bewertung
0 lesenswert
nicht lesenswert
c-hater schrieb:
> vollständiges (aber so gut wie möglich minimiertes)

Nichts anderes hatte ich vor.

Beitrag #6184200 wurde von einem Moderator gelöscht.
von A. Z. (donvido)


Angehängte Dateien:

Bewertung
0 lesenswert
nicht lesenswert
c-hater schrieb:
> Zeige ein vollständiges (aber so gut wie möglich minimiertes) Programm

Ich habs nochmal in einer Datei zusammengefasst, falls es hilft.

@c-hater: Vielleicht hatest du lieber woanders, wenn du keine Lust hast 
sachlich zu bleiben?

von S. Landolt (Gast)


Bewertung
0 lesenswert
nicht lesenswert
Frage eines Assemblerprogrammierers: ist es zulässig, das Hauptprogramm 
in setup zu packen? Oder anders gefragt: was passiert im Arduino 
eigentlich zwischen setup und loop?

von Arduino Fanboy D. (ufuf)


Bewertung
0 lesenswert
nicht lesenswert
S. Landolt schrieb:
> Frage eines Assemblerprogrammierers: ist es zulässig, das
> Hauptprogramm
> in setup zu packen? Oder anders gefragt: was passiert im Arduino
> eigentlich zwischen setup und loop?

Falls du AVR Arduinos meinst, verlierst du nur serialEvent()
Siehe: 
https://github.com/arduino/ArduinoCore-avr/blob/master/cores/arduino/main.cpp

Bei ESP und andere Exoten kann das durchaus anders aussehen.

: Bearbeitet durch User
von S. Landolt (Gast)


Bewertung
0 lesenswert
nicht lesenswert
Danke.
Ich bezog mich auf das gezeigte Programm und den ATmega328. Ich verstehe 
einfach nicht, weshalb es "stottert" wie zu Beginn, oder jetzt läuft, 
weil, von außen betrachtet, irgendwo 6720 statt 6560 Takte eingestellt 
werden.

von A. Z. (donvido)


Angehängte Dateien:

Bewertung
0 lesenswert
nicht lesenswert
Also ich habe das jetzt nochmal etwas kondensiert, kann mir das 
Timingproblem aber immernoch nicht erklären.

Ich bekomme insgesamt 3 verschiedene Ergebnisse, je nach dem ob die 
Zeile 126 mit
Serial.begin(115200);
auskommentiert ist oder nicht und ob as Delay in case 
irq_flag_e::reset_high: auf 420µs oder 410µs gesetzt wird.

Mit
//Serial.begin(115200);
und
delay = 16 * 420 - 1;

scheints zu passen.
Die Timingprobleme treten auch nur im ersten Byte auf.

Pin 13 (B5) wird getoggelt um darauf zu triggern und zu sehen, wann der 
Interrupt kommt.

: Bearbeitet durch User
von S. Landolt (Gast)


Bewertung
0 lesenswert
nicht lesenswert
Also für mich sieht es so aus, als ob Arduino manchmal die 160 Takte 
nicht reichen; wundert mich zwar, denn 160 ist viel (in Assembler). 
Vielleicht gerät dieses millis dazwischen; ich würde es probeweise 
abwürgen per TIMSK0=0 und schauen, was passiert.

von A. Z. (donvido)


Bewertung
0 lesenswert
nicht lesenswert
S. Landolt schrieb:
> TIMSK0=0

Das scheints geswesen zu sein.
Da das Problem mit einer kurzen Übertragung nur schwer reproduzierbar 
ist, habe ich die Übertragung mal auf 80 Bytes aufgeweitet.
Mit TIMSK0=0 kann jedes einzelne Byte wie gewünscht übertagen werden, 
ohne kommt es zu 2 Überläufen und 5 zu kurzen Timeslots.

S. Landolt schrieb:
> denn 160 ist viel

Das hatte ich eigentlich auch gedacht, zumal die Timer 0 ISR jetzt auch 
nicht unbedingt lang ist.
ISR(TIMER0_OVF_vect)
{
  // copy these to local variables so they can be stored in registers
  // (volatile variables must be read from memory on every access)
  unsigned long m = timer0_millis;
  unsigned char f = timer0_fract;

  m += MILLIS_INC;
  f += FRACT_INC;
  if (f >= FRACT_MAX) {
    f -= FRACT_MAX;
    m += 1;
  }

  timer0_fract = f;
  timer0_millis = m;
  timer0_overflow_count++;
}

von S. Landolt (Gast)


Bewertung
0 lesenswert
nicht lesenswert
Nun, letzten Endes bräuchte man von beidem das Assemblerlisting, und 
dann hieße es Takte zählen.

von A. Z. (donvido)


Angehängte Dateien:

Bewertung
0 lesenswert
nicht lesenswert
Ich hab jetzt nicht sonderlich viel Erfahrung mit Assembler, aber wenn 
ich mich nicht verrechnet habe, benötigt die Timer0 ISR 98.5 Takte.
Das ist natürlich im Vergleich zu 160 Takten CTC ne ganz schöne Ansage.

von S. Landolt (Gast)


Bewertung
0 lesenswert
nicht lesenswert
Grenzfallbetrachtung: gleich zu Beginn der Timer0-Interruptbearbeitung 
(millis) erfolgt der Timer1-CTC-Interrupt; letzterer muss nun warten, 
bis erstere abgearbeitet ist (98 T), dann ein Befehl im Hauptprogramm 
(typisch rjmp mit 2 T), nun kommt erst die Timer1-ISR bis zum Setzen 
von OCRA1=delay (? T). Das alles muss innerhalb der 160 Takte erfolgen, 
sonst ist TCNT1 bereits über 160 gelaufen und dreht eine Ehrenrunde mit 
65536 Takten.

von S. Landolt (Gast)


Bewertung
0 lesenswert
nicht lesenswert
PS:
Die scheinbare Abhängigkeit von serial.begin oder den 420 us kommt 
daher, dass dabei zwischen Timer0 und Timer1 unterschiedliche 
Zeitversätze auftreten. Aber der Fehler tritt früher oder später immer 
auf, deshalb war es auch sinnvoll, die Übertragung mal deutlich zu 
verlängern.

von S. Landolt (Gast)


Bewertung
0 lesenswert
nicht lesenswert
Eine vage Idee: den Write-time-slot mit seinen 70 us als Ganzes 
betrachten, Timer1-Modus 12, Interrupt auf Overflow. OCR1A auf den Wert 
für 10 bzw. 60 us setzen, das Toggeln erfolgt dann ja automatisch 
zeitlich korrekt; ICR1 auf den TOP-Wert für 70 us, hierfür den Interrupt 
aktivieren, wenn dieser dann um die 100 Takte der 
Arduino-millis-Timer0-ISR überschritten wird, bleibt man trotzdem noch 
deutlich unter den (lt. Datenblatt DS18B20) zulässigen 120 us. Müsste 
man mal ausprobieren.
  Read wurde bislang ja noch nicht diskutiert.

von S. Landolt (Gast)


Bewertung
0 lesenswert
nicht lesenswert
Korrektur: geht doch nicht, wäre dasselbe Problem.
Aber: völlig ohne CTC, Interrupt auf OCR1B mit 70 us, in der COMPB-ISR 
das zweite Toggeln des Ausgangs OC1A von Hand, d.h. per FOC1A, und 
TCNT1=0 setzen. So irgendwie ...

von Peter D. (peda)


Angehängte Dateien:

Bewertung
1 lesenswert
nicht lesenswert
Anbei mal der komplette Code aus einen 8051-Projekt.
Die Funktion OnwiReadRom wird nur im Init benötigt, d.h. sie wartet, bis 
sie fertig ist.
Die anderen Funktionen sind nicht blockierend. Sie liefern ein Bit 
zurück, ob sie fertig sind.
Der Zugriff auf den 1-Wire-Bus erfolgt im Hintergrund im T0-Interrupt 
mit einer Statemaschine.
Für die Zeiten <15µs wird der Interrupt nicht verlassen. Nur die 60µs 
und 480µs werden vom Timer erzeugt.
Benutzt wird T0 im 16Bit-Mode. Der T0-Interrupt bekommt die höchste 
Priorität zugewiesen, d.h. kann alle anderen Interrupts unterbrechen. 
Der 8051 hat ja den Vorteil, daß man bis zu 4 Prioritäten vergeben kann.
Der AT89C51 läuft mit 20MHz / 6 = 3,3MIPS.

Antwort schreiben

Die Angabe einer E-Mail-Adresse ist freiwillig. Wenn Sie automatisch per E-Mail über Antworten auf Ihren Beitrag informiert werden möchten, melden Sie sich bitte an.

Wichtige Regeln - erst lesen, dann posten!

  • Groß- und Kleinschreibung verwenden
  • Längeren Sourcecode nicht im Text einfügen, sondern als Dateianhang

Formatierung (mehr Informationen...)

  • [c]C-Code[/c]
  • [avrasm]AVR-Assembler-Code[/avrasm]
  • [code]Code in anderen Sprachen, ASCII-Zeichnungen[/code]
  • [math]Formel in LaTeX-Syntax[/math]
  • [[Titel]] - Link zu Artikel
  • Verweis auf anderen Beitrag einfügen: Rechtsklick auf Beitragstitel,
    "Adresse kopieren", und in den Text einfügen




Bild automatisch verkleinern, falls nötig
Bitte das JPG-Format nur für Fotos und Scans verwenden!
Zeichnungen und Screenshots im PNG- oder
GIF-Format hochladen. Siehe Bildformate.

Mit dem Abschicken bestätigst du, die Nutzungsbedingungen anzuerkennen.