Atmega328 1-Wire mit Timer CTC/PWM

OP #6183674
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?
1
// Configure Timer -> Fast PWM (Mode 15)
2
  TCCR1A = (1 << COM1A0)|(1 << WGM11)|(1 << WGM10); 
3
  TCCR1B = (1 << WGM13)|(1 << WGM12); 
4
  OCR1A = 0xFFFF; // avoid TOV at start of timer
5
  ICR1 = 0xFFFF; // should be unused
6
  TIFR1 |= (1 << TOV1); //clear TOV Interrupt
7
  TIMSK1 |= (1 << TOIE1); //enable TOV Interrupt
8

9
// start procedure
10
  pinMode(pin, OUTPUT);
11
  OCR1A = 16*480-1; // set timer interrupt to 480µs
12
  TCNT1 = 0; //reset timer
13
  TCCR1B |= (1 << CS10); //start the counter
Gast #6183918
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...
OP #6183945
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 ^^.
Gast #6183958
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.
Gast #6184018
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...
OP #6184075
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:
1
  // CTC (Mode 4)
2
  TCCR1A = (1 << COM1A0); 
3
  TCCR1B = (1 << WGM12); 
4

5

6
  DDRB|=(1<<DDB1); // pinMode(9, OUTPUT); 
7
     
8
  OCR1A = 16*480-1;
9
  TCNT1 = 0; //reset timer
10
  TIFR1 = (1 << OCF1A); //clear OC1A Interrupt
11
  TIMSK1 |= (1 << OCIE1A); //enable OC1A Interrupt
12
  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.
Angehängte Dateien:
Gast #6184089
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...
Gast #6184097
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?
OP #6184103
Lesenswert?

1
ISR(TIMER1_COMPA_vect) {
2
  ds.event_handlerCTC();
3
}
4

5
void OneWire::write(uint8_t *data, uint8_t length = 1) {
6
  msg.data = data;
7
  msg.reading = false;
8
  msg.length = length;
9
  msg.position = 0;
10

11
  irq_flag = irq_flag_e::reset_read;
12
  busy = true;
13

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

23
void OneWire::event_handlerCTC(void) {
24
      uint16_t delay = 0;
25
      switch (irq_flag) {
26
        case irq_flag_e::end:
27
          PORTB|=(1<<PORTB5);
28
          DDRB&=~(1<<DDB1);
29
          TCCR1B &= ~(1 << CS10);
30
          TIMSK1 &= ~(1 << OCIE1A);
31
          busy = false;
32
          return;
33

34
        case irq_flag_e::reset_read:
35
          DDRB&=~(1<<DDB1);
36
          delay = 16*80-1;
37
          irq_flag = irq_flag_e::reset_high;
38
          break;
39

40
        case irq_flag_e::reset_high:
41
          // uint8_t r;
42
          // r=digitalRead(pin);
43
          TCCR1C=(1<<FOC1A);
44
          PORTB&=~(1<<PORTB1);
45
          DDRB|=(1<<DDB1);
46
          delay = 16*410-1;
47
          if (msg.length == 0){
48
            irq_flag = irq_flag_e::end;
49
          }else {
50
            if (msg.reading)
51
              irq_flag = irq_flag_e::read1;
52
            else
53
              irq_flag = irq_flag_e::write_low;
54
          }
55
          break;
56

57
        case irq_flag_e::read1: //unused
58
          break;
59

60
        case irq_flag_e::read2: //unused
61
          break;
62

63
        case irq_flag_e::write_low:
64
          if (msg.data[msg.position] & (1 << i)) {
65
            delay = 16*10-1;
66
          } else {
67
            delay = 16*60-1;
68
          }
69
          irq_flag = irq_flag_e::write_high;
70
          break;
71

72
        case irq_flag_e::write_high:
73
          if (msg.data[msg.position] & (1 << i)) {
74
            delay = 16*60-1;
75
          } else {
76
            delay = 16*10-1;
77
          }
78
          i++;
79
          if (i == 8) {
80
            i = 0;
81
            msg.position++;
82
          }
83
          if (msg.position == msg.length) {
84
            irq_flag = irq_flag_e::end;
85
          } else {
86
            irq_flag = irq_flag_e::write_low;
87
          }
88
          break;
89
      }
90
      OCR1A = delay;
91
    }
Gast #6184165
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.
OP #6184167
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
Angehängte Dateien:
Beitrag #6184200 wurde von einem Moderator gelöscht.
Gast #6185401
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.
Gast #6185417
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.
OP #6188810
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
1
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
1
//Serial.begin(115200);
und
1
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.
Angehängte Dateien:
Gast #6193798
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.
OP #6193948
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.
1
ISR(TIMER0_OVF_vect)
2
{
3
  // copy these to local variables so they can be stored in registers
4
  // (volatile variables must be read from memory on every access)
5
  unsigned long m = timer0_millis;
6
  unsigned char f = timer0_fract;
7

8
  m += MILLIS_INC;
9
  f += FRACT_INC;
10
  if (f >= FRACT_MAX) {
11
    f -= FRACT_MAX;
12
    m += 1;
13
  }
14

15
  timer0_fract = f;
16
  timer0_millis = m;
17
  timer0_overflow_count++;
18
}
Gast #6194258
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.
Gast #6194279
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.
Gast #6195478
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.
Gast #6195501
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 ...
#6195955
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.
Angehängte Dateien:

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