Forum: Mikrocontroller und Digitale Elektronik Mathematisches Problem, wie ms timeout korrekt berechnen? - AVR ASM


von Björn (Gast)


Lesenswert?

Hallo,

habe ein Problem damit einen timeout von 100ms zuverlässig berechnen zu 
lassen.
Ich lasse einen timerinterrupt jede millisekunde geschehen, wo dann ein 
millisekunden Zähler um 1 erhöht wird. Sobald 1000 ms, also 1 Sekunde 
erreicht ist, wird ein Sekunden zähler erhöht und der millisekunden 
Zähler wieder auf Null gesetzt.
In meinem Programm messe ich den timeout, in dem ich die aktuelle 
ms-Zeit bei einem Ereignis in einer Variable abspeichere und bei einem 
weiteren Eregnis die gespeicherte ms-Zeit von der aktuelle ms-Zeit 
subtrahiere und das Ergebnis (die Differenz) mit dem festgesetzten 
Timeout-Wert vergleiche.

Das funktioniert z.B. unter folgender Bedingung:
 Timeout: 100ms
 alte Zeit: 450ms
 neue Zeit: 650ms
 Differenz: 650ms - 450ms = 200ms ==> timeout erreicht

Es funktioniert allerdings nicht z.B. unter dieser Bedingung:
 Timeout: 100ms
 alte Zeit: 980ms
 neue Zeit: 60ms
 Differenz: 60ms - 980ms = -920ms ==> timeout erreicht obwohl erst 80ms 
verstrichen sind.

Hat jemand eine Idee, wie ich dieses Problem möglichst elegant umgehen 
kann, ohne auch noch jedesmal die Sekunden vergleichen zu müssen und 
dann entsprechend alles berechnen zu lassen?

Ich habe den Quellcode jetzt nicht eingefügt, weil der nichts zur Sache 
tun sollte, geht ja eigentlich eher um ein mathematische Problem...

von Helmut L. (helmi1)


Lesenswert?

Du must abfragen ob die alte Zeit groesser als die neue Zeit ist.

if (alteZeit > neueZeit)
{
   Zeit = 1000-alteZeit + neueZeit;
} else
{
   Zeit = neueZeit - alteZeit;
}

Gruss Helmi

von spess53 (Gast)


Lesenswert?

Hi

Nach der Subtraktion Carry-Flag auswerten. Wenn gesetzt 1000 zu Ergebnis 
addieren.

MfG Spess

von Michael U. (amiga)


Lesenswert?

Hallo,

kommt jetzt zwar auf die Umgebung an, aber ich nahem für sowas ein 
Zählregister, das in jedem Interrupt um 1 erhöht wird.
Eintretende Ereignisse setzen das Register einfach auf 0.

Die auf das Timeout wartende Routine vergleicht nur mit der gewünschten 
timeout-Zeit und wenn erreicht oder größer -> Timeout.

Gerade in ASM; sehr sparsam

IRQ-Routine: inc timeout

Bearbeitungsroutine: clr timeout

Testroutine: cpi timeout, WARTEZEIT
             brsh timeout_da

Wäre bei Dir bei 1ms-IRQ und 100ms Timeout passend für ein Register.
Aber selbst im SRAM kosten
 push temp
 lds temp,time_reg
 inc temp
 sts time_reg,temp
 pop temp
in einer Interruptroutine meist verkraftbar.

Gruß aus Berlin
Michael

von Björn (Gast)


Lesenswert?

Danke für die 3 Lösungsansätze.

@amiga
Das hatte ich auch erst überlegt, bzw. habe ich mit dieser Methode 
vorher überprüft, ob es tatsächlich an dem überlauf liegt.
Das ist auch prinzipiell durchaus eine Idee, allerdings ist das in 
meinem Fall etwas blöd, da ich eine weitere Variable einführen muss 
(auch wenn diese, wie meine anderen auch im SRAM abgelegt wird). Den ms 
und s Zähler in der ISR brauche ich auch noch als Zeitbasis für andere 
Dinge, daher wollte ich diesen auch für den timeout verwenden.

@spess53
Die Idee mit dem Carry Flag ist auch eine Möglichkeit allerdings eher 
für 8-Bit Operationen geeignet, oder irre ich mich?.

@helmi1
Das schien mir in meinem Fall die beste Möglichkeit zu sein, ist auch 
sehr einleuchtend allerdings habe ich nun ein Problem, dass das ganze 
nicht so hinhaut, daher hier mal ein Codeschnipsel für den relevanten 
Bereich:
1
  lds XL, aktuelleZEIT_ms
2
  lds XH, aktuelleZEIT_ms + 1
3
  lds YL, alteZEIT_ms
4
  lds YH, alteZEIT_ms + 1
5
6
  cp  XL, YL                    ; aktuelleZEIT < alteZEIT ?
7
  cpc XH, YH
8
  brlo kleiner
9
10
  sub XL, YL                    ; aktuelle Zeit - alte Zeit
11
  sbc XH, YH
12
13
  rjmp  vergleich
14
15
 kleiner:
16
  ldi temp, LOW(1000)           ; (1000 - alte Zeit) + aktuelle Zeit)
17
  ldi temp_2, HIGH(1000)
18
  sub temp, YL
19
  sbc temp_2, YH
20
  add XL, temp
21
  adc XH, temp_2
22
23
 vergleich:
24
  lds temp, LOW (timeout)       ; Differenz > timeout ?
25
  lds temp_2, HIGH (timeout)
26
  cp  XL, temp
27
  cpc XH, temp_2
28
  brlo keinTIMEOUT
29
  breq keinTIMEOUT
30
31
;   .
32
;   . (Führe etwas aus)
33
;   .
34
35
 keinTIMEOUT:
36
;   .
37
;   . (Führe etwas anderes aus)
38
;   .

Das müsste doch so die Umsetzung von amiga sein?

von spess53 (Gast)


Lesenswert?

Hi

>@spess53
>Die Idee mit dem Carry Flag ist auch eine Möglichkeit allerdings eher
>für 8-Bit Operationen geeignet, oder irre ich mich?.


Nein. geht auch bei 16 Bit.

                      ldi r16,Low(20)
                      ldi r17,High(20)    ; neue Zeit

                      Ldi r18,Low(980)
                      ldi r19,High(980)   ; alte Zeit

                      sub r16,r18
                      sbc r17,r19         ; Subtrakion

                      brcc cccc

                      ldi r18,Low(1000)
                      ldi r19,High(1000)

                      add r16,r18
                      adc r17,r19         ; 1000 addieren

cccc:                 cpi r16,100   ; fertig


MfG Spess

von Bobby (Gast)


Lesenswert?

Lass als Zeitgeber einfach (zusätzlich) ein Int hochzählen.
Das wird zwar irgendwann überlaufen, aber der Windowstimer macht das
auch.

Du brauchst zwei Funktionen:

1) timer_start speichert den aktuellen Wert.

2) timeout (x) sagt, ob seit dem Start die Zeit x verstrichen ist
oder noch nicht.

Dazu bildest Du einfach nur die Differenz zwischen
gespeichertem Zeitpunkt und dem aktuellen Zeitgeberwert und
prüfst ab, ob der Wert <= 0 ist. Das
funktioniert auch bei einem Zählerüberlauf.

Nachteil ist eigentlich nur, dass die Auflösung durch die
Zählerbreite beschränkt ist.

So meine bescheidene Erfahrung...

von Sinusgeek (Gast)


Lesenswert?

> allerdings ist das in
> meinem Fall etwas blöd, da ich eine weitere Variable einführen muss
> (auch wenn diese, wie meine anderen auch im SRAM abgelegt wird). Den ms
> und s Zähler in der ISR brauche ich auch noch als Zeitbasis für andere
> Dinge, daher wollte ich diesen auch für den timeout verwenden.

Diese Variable brauchst Du doch sowiso. Dabei ist es unerheblich, ob Du 
darin den Termin des Timeouts (als feste Zeit zum Vergleichen) oder 
einen weiteren Zähler führst.

Ich würde es ähnlich wie Spess machen, vermutlich im 16ms-Raster, da ich 
aus dem 1ms-Int (nutze ich meist für LCD-update und Drehgeber-Abfrage) 
oft noch einen 16ms-Takt zur Tasten-Entprellung ableite. Dadurch reicht 
es dann meist auch achtbittig.

~

von Björn (Gast)


Lesenswert?

@spess53
Danke, habe vorerst deinen Vorschlag eingebaut!!!
Funktioniert sogar jetzt beides, der Schnippel, den ich gepostet hatte 
(war noch ein Flüchtigkeitsfehler an anderer Stelle :-) ) und das was du 
geschrieben hast, wobei dein Schnippel noch um einiges kürzer ist.

@Bobby und sinusgeek
Auch euch nochmal vielen Dank. Natürlich führen mehrere Wege nach Rom, 
die Hauptsache ist, man kommt an. Ich möchte jetzt gar nicht beurteilen 
welche Methode besser ist (sofern man das überhaupt kann), funktionieren 
tun sie sicher alle.
Meine Entscheidung für helmis und spess Lösung war hauptsächlich die, 
dass sich das ungefähr mit dem deckt, was ich mir "vorgestellt" hatte.
Außerdem möchte ich die ISR vorerst wirklich nicht weiter auf blähen, 
auch wenn eine weitere Variable nicht all zu viel Ausführungszeit und 
SRAM Speicher belegt, vielleicht kommt da später noch was anderes mit 
rein.

Falls trotzdem noch jemand veranschaulichen kann, dass es eine 
wesentlich effizientere Lösung gibt, bin ich dafür offen. Ansonsten habt 
ihr mir schonmal ein ganzes Stück weiter geholfen,  zumindest 
funktioniert nun dieser Teil einwandfrei - Dankeschön!

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.