Forum: Mikrocontroller und Digitale Elektronik ATmega: Zähler begrenzen -> wird aber wegoptimiert


von F. P. (pl504)


Lesenswert?

Hi!

Ich möchte vermeiden, daß der Timer0 überläuft. Er soll aber nicht 
zurückgesetzt oder angehalten, sondern nur begrenzt werden. Damit würde 
ich zwei Fliegen mit einer Klappe schlagen: Timer läuft nicht über und 
der Timer-Wert wird in seiner Höhe begrenzt.

Dazu schreib ich in der Hauptschleife einfach:
1
if(TCNT1 > 25000)
2
    TCNT1 = 25000;

Problem: Das wird irgendwie vom Compiler (WinAVR) wegoptimiert, sobald 
ich die Optimierung einschalte.

Wie kann man das verhindern bzw. intelligenter lösen?

von Peter II (Gast)


Lesenswert?

F. P. schrieb:
> Problem: Das wird irgendwie vom Compiler (WinAVR) wegoptimiert, sobald
> ich die Optimierung einschalte.

woher weist du das?

hast du in ASM Listing geschaut?

von Sven B. (scummos)


Lesenswert?

Für sowas kann man generell das "volatile"-Keyword verwenden, dann wird 
das nicht wegoptimiert.

von flo (Gast)


Lesenswert?

F. P. schrieb:
> Timer0

> TCNT1

Du solltest dich entscheiden, Timer 0 oder 1.

von Hopper (Gast)


Lesenswert?

was soll denn deiner Meinung nach mit dem Timer sonst passieren wenn er 
nicht zurückgesetzt oder angehalten wird?
Wenn du den einmal initialisiert hast, dann läuft der hald einfach...

von Peter II (Gast)


Lesenswert?

Sven B. schrieb:
> Für sowas kann man generell das "volatile"-Keyword verwenden, dann wird
> das nicht wegoptimiert.

TCNT1 sollte schon volatile sein.

von F. P. (pl504)


Lesenswert?

Peter II schrieb:
> woher weist du das?
>
> hast du in ASM Listing geschaut?
Im Disassembler hab ich das gesehen.

flo schrieb:
> Du solltest dich entscheiden, Timer 0 oder 1.
Ups, ich meine natürlich den Timer1 mit seinen 16 Bit.

von flo (Gast)


Lesenswert?

Am besten zeigst du mal Code und ASM-Listing.

von Karl H. (kbuchegg)


Lesenswert?

Das ganze Ansinnen klingt nicht logisch.
Wozu soll das gut sein?

Normalerweise würde man das wohl so machen, das man mit einer Compare 
Unit den Timer darauf 'scharf macht', bei einem bestimmten Zählerstand 
einen Interrupt auszulösen. In der zugehörigen ISR würde man dann dem 
Timer einfach den Vorteiler abschalten wodurch der Timere steht.

Allerdings ist aich das Ständige Starten und Stoppen eines Timers 
meistens ein Zeichen dafür, dass man etwas ganz grundsätzliches nicht 
wirklich verstanden hat.

Also: Aus einer höheren Warte gesehen. Warum denkst du, du müsstest den 
Timerwert 'einfrieren'? Was ist die Aufgabenstellung, die so etwas 
erfordert?

von F. P. (pl504)


Lesenswert?

Aufgabenstellung:
Ich messe die Periodendauer eines Rechtecksignals (Interrupt auf 
fallende Flanke) und gebe ein Rechtecksignal nach einer beliebigen 
Zeitverzögerung mit gleichbleibender High-Zeit wieder aus.

Forderung:
Die Ausgabe soll nur bis zu einer festgelegten maximalen Periodendauer 
arbeiten, z.B. TCNT1 < 25000. Ist die Periodendauer länger, soll der 
Ausgang inaktiv bleiben.

Lösungsansatz:
Timer1 setze ich in der CAPT-ISR auf null. Die Zeitverzögerung übernimmt 
OCR1A: Beim COMPA-Interrupt wird der Ausgang auf eins und anschließend 
in der Main-Schleife nach einer festen Verzögerungszeit wieder auf null 
gesetzt.

Problem:
Durch den automatischen Timer-Überlauf bei zu großer Periodendauer des 
Meßsignals wird zwangsweise ein Ausgangssignal bei jedem Überlauf 
erzeugt.

Daher dachte ich, es ist am einfachsten, wenn man den Zähler gar nicht 
erst die Chance gibt, überzulaufen.

von Karl H. (kbuchegg)


Lesenswert?

F. P. schrieb:

> Daher dachte ich, es ist am einfachsten, wenn man den Zähler gar nicht
> erst die Chance gibt, überzulaufen.

Am einfachsten ist es, die Dinge voneinander zu trennen. Deine Aufgabe 
besteht aus 2 Teilaufgaben
* ausmessen der Periodendauer der Rechteckschwingung
* Erzeugung eines Pules nach einer beliebigen Verzögerungszeit, 
getriggert von der Flanke des Rechtecksignals.


Behandle die Ding auch so.

Nicht immer kann man den Timer als alleinigen Zeitgeber bzw. Zeitzähler 
benutzen. Wenn 'beliebig' auch wirklich beliebig bedeutet, dann muss 
eine Verzögerungszeit von einer halben Stunde auch möglich sein. Das 
aber wirst du mit dem Timer alleine, ohne zusätzliche Zählstufen nicht 
hinkriegen.

Und nein. Du brauchst dazu den Timer nicht zu limitieren. Machst du ja 
mit deiner Armbanduhr auch nicht, wenn du Zeiten stoppen musst. Wenn der 
Skiläufer am Start wegfährt, dann merkst du dir die NUmmer der Sekunde 
in der das passiert und wenn er unten durchs Ziel fährt, dann merkst du 
dir die Sekundennummer wann er im Ziel angekommen ist. Die Differenz ist 
die Laufzeit. Ist er bei 23 oben weggefahren und bei 51 durchs Ziel 
gegangen, dann war er 51-23 gleich 28 Sekunden unterwegs. Und diese 
Differenz kann man darauf hin überprüfen ob sie kleiner oder größer als 
zb 19 ist. Wenn sie kleiner ist, dann geht ein Licht an (bzw. dann 
startest du die Verzögerung für die Pulsgenerierung), ist sie nicht 
kleiner, dann passiert eben nichts.
Aber zu keinem Zeitpunkt musstest du an deiner Armbanduhr rumfummeln um 
den Sekundenzeiger laufend zurückzusetzen.

von Ulrich H. (lurchi)


Lesenswert?

Unabhängig von der Aufgabe sollte der Zugriff auf das Timerregister 
nicht wegoptimiert werden. Wie sieht denn der komplette Code aus ?

von Helmut L. (helmi1)


Lesenswert?

F. P. schrieb:
> Dazu schreib ich in der Hauptschleife einfach:if(TCNT1 > 25000)
>     TCNT1 = 25000;

Und wie soll das funktionieren?

Das Programm kommt an die Anweisung vorbei und setzt das Counterregister 
auf 25000 zurueck.  Danach laueft der Timer einfach weiter... bis 
irgendwann mal wieder das Programm an die Anweisung vorbeikommt. Ist er 
dann zufaellig uebergelaufen und dabei unter 25000, passiert ... nix. 
Wenn du den Ueberlauf abfangen willst gibt es den Timeroverflow 
Interrupt. Da kannst du dem Timer soviel Stellen geben wie du moechtest.

von Cyblord -. (cyblord)


Lesenswert?

Mal wieder ein DAU der die Fehler seines vermurksten Codes dem Compiler 
unterschieben will. Dabei ist das "wegoptimieren" immer wieder gerne 
genommen. Gleich nach dem "offensichtlichen" Bug im Compiler.
Die Optimierung kann man übrigens auch abschalten, aber dann wäre der 
Fehler vermutlich immer noch da. Müsste man dann halt auf die 
Bug-Theorie ausweichen.

von Peter D. (peda)


Lesenswert?

Am einfachsten sind solche Aufgaben, wenn man den Timer durchlaufen läßt 
(und am genauesten).

Das Timeout macht man einfach mit dem Compare-B:
1
OCR1B = ICR1 + 25000;
Ist beim nächsten TIMER1 CAPT das OCF1B gesetzt, löscht man es und 
erzeugt keinen Impuls.
Ansonsten:
1
OCR1A = ICR1 + DELAY;
und Ausgang auf high beim nächsten Compare-A konfigurieren.

Dann noch im TIMER1 COMPA:
1
OCR1A += DURATION;
und Ausgang auf low beim nächsten Compare-A konfigurieren.

Fertig.

von F. P. (pl504)


Lesenswert?

Habe mich jetzt nochmal genau mit den PWM-Modi befaßt und bin zu 
folgender Strategie gelangt:

Es wäre doch ein Witz, wenn man die Timer nicht besser für sich arbeiten 
lassen könnte. Die schnelle PWM scheint mir doch wunderbar geeignet:

# Als Ausgang wird /OC1A verwendet, dadurch setzt der Timer ihn 
automatisch 1, wenn der Compare-Match auftritt.
# Überlauf ist kein Thema: OCR1A auf MAX setzen, dann bleibt der Ausgang 
auf 0
# Timer wird automatisch zurückgesetzt beim ICR1-Interrupt

Das Problem mit der definierten High-Zeit des Ausgangssignals ließe sich 
doch ganz einfach dadurch lösen, indem man nach dem OCR1A-Interrupt eine 
Warteschleife startet, an deren Ende das DDR-Register auf 0 gesetzt 
wird.
Wieder auf 1 setzt man es einfach in der ICR1-ISR, dann ist der Ausgang 
ja wieder vom Timer auf null gesetzt und würde nicht wieder auf 1 gehen. 
Ganz so genau muß die High-Zeit auch nicht eingehalten werden; 
entscheidender ist die exakte Verzögerungszeit, was durch den PWM-Modus 
gegeben ist.

Ich muß bloß nochmal genau schauen, ob es Probleme gibt, weil das 
ICR1-Register nicht doppelt gepuffert ist.

von stefan us (Gast)


Lesenswert?

Wenn ich einen laufenden Timer per Software ändere, riskiere ich dann 
nicht Race conditions? Was passiert, wenn die Software einen konkreten 
Wert in das Zählregister schreibt, während es "von der anderen Seite" 
einen zähltakt erhält? Die Taktung findet immerhin nicht in allen Bits 
zeitgleich statt.

Bei diskreter Elektronik würde ich sowas jedenfalls gar nicht erst 
versuchen. Da würde ich einen laufenden Timer höchsten resetten aber 
sicher nicht mit einem konkreten Wert laden.

von Lothar M. (Firma: Titel) (lkmiller) (Moderator) Benutzerseite


Lesenswert?

stefan us schrieb:
> Wenn ich einen laufenden Timer per Software ändere, riskiere ich dann
> nicht Race conditions? Was passiert, wenn die Software einen konkreten
> Wert in das Zählregister schreibt, während es "von der anderen Seite"
> einen zähltakt erhält? Die Taktung findet immerhin nicht in allen Bits
> zeitgleich statt.
Doch, auf dieser Ebene sind heutige uC synchron. Speziell dafür gibt es 
ja auch ein Schattenregister, das ein Byte des 16-Bit-Zählers 
zwischenspeichert...

Allerdings verhindert niemand, dass nicht (sofort) danach der Zähler 
einfach weiterzählt. Sinnvollerweris müsste der Zähler bei 25000 einfach 
gestoppt werden.

Aber auch ich fasse (wie Peter) sehr ungern einen Zähler an. Die Zähler 
werden bei mir initialisiert, gestartet und dann die Differenz zum 
letzten Zählerwert oder Startwert berechnet. Einzig beim (leicht 
erkennbaren) Zählerüberlauf muss evtl. noch ein wenig von Hand 
nachgearbeitet werden....

von F. P. (pl504)


Lesenswert?

Mal zwischendurch ne ganz blöde Frage zu Inline-Assembler:
1
SBI DDRB, DDB1
krieg ich ums Verrecken nicht in das C-Programm eingebunden:
1
asm volatile("sbi %0, %1 \n\t" :: "I" (_SFR_IO_ADDR(DDRB)), "I" (DDB1));
produziert 2x die Fehlermeldung

"Error: constant value required"

Was ist da falsch?

von chris (Gast)


Lesenswert?

F. P. schrieb:
> Mal zwischendurch ne ganz blöde Frage zu Inline-Assembler:

blöde Frage von mir: Wozu denn das bitte?
1
DDRB |= (1<<DDB1);

Daraus sollte der Compiler exakt deinen Code erzeugen, also wozu da 
irgendwas von Hand reinpfriemeln?

von Peter D. (peda)


Lesenswert?

F. P. schrieb:
> # Timer wird automatisch zurückgesetzt beim ICR1-Interrupt

Nö.
Das geht nur in SW, ergibt also einen Jitter (Interruptlatenz).

F. P. schrieb:
> indem man nach dem OCR1A-Interrupt eine
> Warteschleife startet, an deren Ende das DDR-Register auf 0 gesetzt
> wird.

Ummpf, warten im Interrupt.
Und was willst Du mit einem schwachen Pullup bzw. Tristate?

Ist Dir mein Lösungsvorschlag zu einfach, zu genau und zu wenig Code?

von Peter D. (peda)


Lesenswert?

Lothar Miller schrieb:
> Einzig beim (leicht
> erkennbaren) Zählerüberlauf muss evtl. noch ein wenig von Hand
> nachgearbeitet werden....

Nö.
Solange alle Zeitintervalle <65535 sind, kann der Überlauf einem 
herzlich egal sein.
Und das sind sie, da ja nach 25000 der Timeout zuschlagen soll.

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.