Hallo Club,
ich habe ein Problem mit einen 16-Bit Timer beim ATmega48 mit 4Mhz
Quarz. Alle 2 bis 3 Sek. hat der Timer einen "Schluckauf", sodaß eine
normalerweise gleichmäßig blinkende LED unregelmäßig blinkt (Periode zu
kurz). Auch eine Periodenmessung bestätigt das.
Ersma der Code:
1
// Variablendefinition, Bits und Flags
2
unionBitfield
3
{
4
unsignedintallbits;
5
struct
6
{
7
unsignedcharsleep:1;// 1 = sleep()-Fkt. aktiv
8
[weitere15Variablen,Typchar]
9
};
10
};
11
12
staticvolatileunionBitfieldflag;
13
14
staticvolatileunsignedintwaittime;
15
16
17
// 16-Bit Timer Compare Match mit quasi Autoreload, wird alle 0.5 ms aufgerufen
18
ISR(SIG_OUTPUT_COMPARE1A)
19
{
20
if(flag.sleep)// wird in sleep()-Fkt. (rück)gesetzt
21
waittime++;
22
}
23
24
25
// 16-Bit Timer1, 0.5 ms Wartezeit
26
voidsleep(unsignedintwait)
27
{
28
waittime=0;
29
flag.sleep=1;
30
31
do
32
{
33
asmvolatile("NOP");
34
}while(waittime<wait);
35
36
flag.sleep=0;
37
//TCNT1 = 0;
38
}
39
40
41
intmain(void)
42
{
43
// Initialisierung des 16-Bit Timers, Interrupt alle 0,5 ms bei 4MHz Quarz
44
TCNT1=0;// Timer Counter Register initialisieren
45
TCCR1B=(1<<WGM12)|(1<<CS10);// Prescaler = 1, CTC Modus
46
OCR1A=1999;// Compare Match Register, gemessene Zeit: 0,5ms @4MHz
47
TIFR1=1<<ICF1;
48
TIMSK1=1<<OCIE1A;// Interruptfreigabe bei Compare Match
49
50
flag.allbits=0;
51
52
wdt_disable();
53
54
// zur Vollständigkeit halber
55
UART_init(UART_BAUD_SELECT(4800,F_CPU));
56
TWI_SlaveInit(SLAVE_ADR);
57
58
sei();
59
60
while(1)
61
{
62
TWI_Start();
63
64
UCSR0B=0;// disable UART
65
UCSR0C=0;
66
DDRD=0xFF;
67
PORTD&=0xFD;
68
PORTC=0xF7;
69
while(1)
70
{
71
sleep(400);
72
PORTD|=0x02;// PORTD1 auf 1
73
PORTC=0xFF;
74
sleep(400);
75
PORTD&=0xFD;// PORTD1 für 200ms auf 0
76
PORTC=0xF7;
77
}
78
}
79
}
Diese Proggi sollte nichts weiters machen als eine LED an PORTC blinken
zu lassen, gleichzeitig wird auch PORTD1 geändert, an dem ich gemessen
habe.
Was mir bei der Fehlersuche aufgefallen ist, daß die Periodendauer sich
sehr stark ändert, wenn ich OCR1A um 1 ändere. Hier mal die Meßwerte.
Deutlich zu sehen an den OCR1A-Werten von 1998 bis 2000, abweichende
Zeilen habe ich mal mit nem '?' markiert.
1
/*
2
ist soll
3
OCR1A | t(ms) | t(ms)
4
-------+---------+-------
5
1000 200,235 200
6
1400 229,796 ? 280
7
1500 300,228 300
8
1998 327,861 ? 399
9
1999 400,023 400
10
2000 238,189 ? 400
11
2999 492,015 ? 600
12
3000 600,211 600
13
*/
Woher kommt das?
Ich zum messen in den Code nur den OCR1A-Wert geändert und gemessen.
Irgendwas ist da mit der sleep()-Geschichte in der inneren
while(1)-Schleife faul, meine ich, aber finde nicht raus, was es ist.
Wenn ich in der sleep()-Fkt. die auskommentierte Zeile TCNT0=0
aktiviere, ist der "Schluckauf" weg, aber in die Tabelle oben ändert
sich nichts an den Meßwerten.
Ratlos
Hegy
ich würde testweise JEDEN Zugriff auf waittime mit cli() sei()
einschliessen (nur im INT nicht)
und nebenbei
do
{
asm volatile("NOP");
}while(waittime < wait);
verkürzen zu
while(waittime < wait);
supi, so gehtz!
Nur warum müssen unbedingt alle Interrupts gesperrt werden bei diesem
Vergleich? waittime wird 'nur' alle 0,5ms hochgezählt, der Vergleich in
der do...while-Schleife ist wesentlich schneller und selbst wenn während
des Vergleichens waittime wiederrum eins hochgezählt werden sollte, ist
das Limit erreicht und die do...while-Schleife wird verlassen, das Bit
flag.sleep wird auf 0 gesetzt und damit wird waittime in der ISR nicht
mehr hochgezählt.
Wenn ich in der Schleife das cli() auskommentiere und OCR1A mit 1998
initialisiere erhalte ich eine Periodendauer von 327ms (soll 399ms), bei
aktiviertem cli() sind's 399,8ms, was rein rechnerisch auch past.
Nur den Sinn von cli() an dieser Stelle verstehe ich nicht.
ich sag es mal kurz:
nichtsequenzieller/überschneidender Zugriff auf eine 16Bit-Variable auf
einer 8Bit-Architektur
(die lange Version mußt Du Dir ergrübeln)
Nee du, ergrübel mir nix, ist schon klar, auf einem 8-Bitter mit 16 Bit
rumzukacheln ist schon etwas Hallas (=Aufwand), schlimmer wirds bei
Fließkommazahlen. Wer es mal in plain Assembler gemacht hat.....
Oder steckt da noch mehr hinter?
Mal die Assembler Listings zum Vergleich dazu.
1
neueVersionBuggy-Version
2
3
movwr18,r24movwr18,r24
4
sts0x0113,r1sts0x0113,r1
5
sts0x0112,r1sts0x0112,r1
6
ldsr24,0x0102ldsr24,0x0102
7
orir24,0x02orir24,0x02
8
sts0x0102,r24sts0x0102,r24
9
clinop
10
ldsr24,0x0112ldsr24,0x0112
11
ldsr25,0x0113ldsr25,0x0113
12
cpr24,r18cpr24,r18
13
cpcr25,r19cpcr25,r19
14
brcc.+4brcs.-16
15
seildsr24,0x0102
16
rjmp.-20andir24,0xFD
17
seists0x0102,r24
18
ldsr24,0x0102sts0x0085,r1
19
andir24,0xFDsts0x0084,r1
20
sts0x0102,r24ret
21
ret
22
23
56Byteslang54Byteslang
Eigentlich kein Riesenunterschied, aber der Unterschied von
alle-0,5ms-Variable-hochzählen und der Ausführungszeit von 54/56 Bytes
Assemblercode ohne zeitaufwendige Befehle (z.B. div, mul beim 8086) ist
groß und immernoch ist mir nicht klar, warum genau hier der Hund
begraben liegt.
Hättest du den ersten Satz vollständig hingeschrieben, dann würde man
sehen, daß das eine Unterthema nichts mit dem anderen Unterthema zu tun
hat. Zuerst ging es um 16-Bit Operationen auf 8-Bit Prozessoren und
unten dann darum, warum eine im us Bereich dauerne Operation
(sleep-Funktion einmalig durchlaufen) durch einen wesentlich längeren
Vorgang (hochzählen einer Variable im 0,5ms Takt) derart behindert wird,
daß es hier zu Fehlfunktionen kommen kann.