Guten Tag!
Ich lerne z.Z. mit einer Versuchsplatine auf Basis ATMega1284.
Timer0 ist für ISR je 1 ms eingestimmt. in ISR wird ein Zähler
inkrementiert.
In Hauptprogramm wird Zähler geprüft und wenn 1000 erreicht, wird auf 0
gesetzt und einige Handlungen gemacht.
Code:
1
// asm_timer0.S
2
#include <avr/io.h>
3
4
.extern flag_1ms
5
.global TIMER0_COMPA_vect
6
7
TIMER0_COMPA_vect:
8
push r0
9
in r0,0x3f
10
push r24
11
12
lds r24,flag_1ms
13
subi r24,-1
14
sts flag_1ms,r24
15
lds r24,(flag_1ms + 1)
16
sbci r24,-1
17
sts (flag_1ms + 1),r24
18
19
pop r24
20
out 0x3f,r0
21
pop r0
22
23
reti
1
// timer0.c
2
#include"timer0.h"
3
volatileu16flag_1ms=0;
1
// timer0.h
2
#ifndef TIMER0_H
3
#define TIMER0_H
4
5
#include"../main.h"
6
#include<avr/interrupt.h> // Fuer Interrupts
7
8
externvolatileu16flag_1ms;
9
10
#define PULS_FREQ 1000
11
12
#define INIT_PULS ((F_CPU/PULS_FREQ)/64 - 1)
13
14
staticinlinevoidtimer_init_timer0(void){
15
/* CTC-Mode */
16
TCNT0=0;
17
OCR0A=INIT_PULS;
18
TCCR0A=(1<<WGM01);
19
TCCR0B=((1<<CS01)|(1<<CS00));/* Start mit 1/64 Prescaler */
20
TIMSK0=(1<<OCIE0A);
21
}
22
#endif /* TIMER0_H */
Hauptprogramm:
1
while(1){
2
if(flag_1ms==1000){
3
flag_1ms=0;
4
usw.
Diese Variante funktioniert wie erwartet
1
while(1){
2
if(flag_1ms>999){
3
flag_1ms=0;
4
usw.
Hier aber wird alles merkbar öfter als 1 Sek gemacht, so um 10%.
Ich habe vermutet, das Problem kommt, da ISR gelegentlich zwischen Lesen
und Schreiben von einzelnen Bytes von 16-bit-Variable kommt.
1
while(1){
2
volatileu16tmpflag;
3
cli();
4
tmpflag=flag_1ms;
5
sei();
6
tmpflag-=1000;
7
if(!(SREG&(1<<0))){
8
flag_1ms=0;
9
usw.
Diese Variante arbeitet wie erwartet.
Die Frage: warum stören ISR bei dem Vergleich
1
if(flag_1ms>999){
und nicht stören bei dem Vergleich
1
if(flag_1ms==1000){
?
In beiden Fällen wird doch 2-Bytes-Variable verarbeitet?
Viele Grüße!
Maxim B. schrieb:> Guten Tag!> Ich lerne z.Z. mit einer Versuchsplatine auf Basis ATMega1284.> Timer0 ist für ISR je 1 ms eingestimmt. in ISR wird ein Zähler> inkrementiert.
Und wozu die Assembleroutine? Kann man machen, ist bei 1kHz aber eher
überflüssig.
> In Hauptprogramm wird Zähler geprüft und wenn 1000 erreicht, wird auf 0> gesetzt und einige Handlungen gemacht.
Lies mal was zum Theme Interrupt, volatile und atomare Operationen.
Falk B. schrieb:> Und wozu die Assembleroutine? Kann man machen, ist bei 1kHz aber eher> überflüssig.
ISR sollte so schnell wie möglich gehen. Mit rein C braucht ISR 37
Cycle, mit Assembler nur 24. Grund: ich benutze r1 und r25 in ISR nicht.
Auch SREG wird in r0 während ISR bleiben. Wozu macht C-Compiler diese
überflüssigen push-pop ? Nur um alles langsamer zu machen?
> Lies mal was zum Theme Interrupt, volatile und atomare Operationen.
Mich interessiert nicht die Problematik von ISR zwischen mehreren
Bytes-Lesen-Schreiben, sondern warum bei dem Vergleich "==" ISR nicht
stören und bei dem Vergleich ">" stören?
Was aber atomare Operationen betrifft: wie kann ich ISR nur innerhalb
Vergleichs "if(flag_1ms > 999)" aussetzen, ohne bei dem ganzen {} ?
Egal, benutze ich cli(); oder sei();, oder auch mit ISR_BLOCK - das gilt
ja für ganze?
Maxim B. schrieb:> flag_1ms = 0;
Du darfst diese Variable nur in der ISR verändern/beschreiben.
Niemals in der ISR **und** in der Mainloop!
Neim Tipp: mach aus dem flag_1ms einen uint32_t Zähler namens millis,
zähle den jede ms um 1 hoch und mach es dann so, wie es beim Arduino mit
den millis() geht.
Maxim B. schrieb:> ISR sollte so schnell wie möglich gehen.
Eine ISR soll so schnell wie nötig(!!) gehen.
Aber einfach mal mit Zahlen: wenn die ISR wegen der Verwendung von
C-Code 50 Maschinentakte länger braucht, dann ist das verschwindend
wenig Zeit, die du sparrt, denn in dieser 1 ms hat der µC bei 16 MHz
immerhin 16000 Befehle ausgeführt. Und somit hättest du durch das
Einsparen der 100 Takte gerade mal 50/16000 = 0,3% der Rechenleistung
eingespart.
Wenn diese fehlende Rechenleistung von 0,3% der Gesamtrechenleistung zu
Problemen führt, dann ist der Prozessor wohl eh' zu knapp bemessen.
Maxim B. schrieb:> warum stören ISR bei dem Vergleich> if(flag_1ms > 999){> und nicht stören bei dem Vergleich> if(flag_1ms == 1000){
Dazu müsste man einen Blick in Assembler Listing werfen. Es kann gut
sein, dass beide Fälle fehlerhaft sind, aber das Auftreten des Fehlers
unterschiedlich wahrscheinlich ist.
Du hast ja schon erkannt, dass man eigentlich bei Lesen des Zählers die
Interrupts sperren muss damit die ISR nicht dazwischen stört.
Ich gebe dir mal ein Extrembeispiel. Stelle dir vor, die ISR erhöht den
Zähler von 255 (0x00ff) nach 256 (0x0100). Stelle dir weiter vor, dass
das Hauptprogramm im selben Moment den Zählerstand prüft, und zwar
zuerst die oberen 8 Bit der Variable und dann die unteren. Dazwischen
stört der Interrupt. Dann liest das Hauptprogramm den Wert 0 (0x0000),
was völlig falsch ist.
Maxim B. schrieb:> warum bei dem Vergleich "==" ISR nicht stören und bei dem Vergleich ">"> stören
999 ist 0x03E7, 1000 ist 0x03E8. Wenn die ISR während des Vergleichs
0xE7 auf 0xE8 ändert, bleibt aber 0x03 gleich, also entstehen nie üble
Zwischenzustände die den ==1000-Vergleich betreffen könnten.
Beim Vergleich >999 ist es anders. Da kann es passieren, dass die ISR
von 0x2FF nach 0x300 zählt, der Vergleich sich aber die 0xFF vor dem
Inkrementieren und dann die 0x03 nsch dem Inkrementieren holt, und dann
ist das Ergebnis >999.
LG, Sebastian
Maxim B. schrieb:> wie kann ich ISR nur innerhalb Vergleichs "if(flag_1ms > 999)"> aussetzen, ohne bei dem ganzen {}
Kopiere dir flag_1ms in eine zweite Variable. Schütze dieses Kopieren
mit cli()/sei().
Das Setzen auf Null musst du auch schützen.
LG, Sebastian
Lothar M. schrieb:> Maxim B. schrieb:>> flag_1ms = 0;> Du darfst diese Variable nur in der ISR verändern/beschreiben.> Niemals in der ISR **und** in der Mainloop!>> Neim Tipp: mach aus dem flag_1ms einen uint32_t Zähler namens millis,> zähle den jede ms um 1 hoch und mach es dann so, wie es beim Arduino mit> den millis() geht.
Danke für Tipp.
Ich möchte aber die langsame Arduino-Variante lieber vermeiden.
Übrigens, Compiler macht aus dem
Die Assembler-Variante macht den Job deutlich schneller und sparsamer:
1
while(1){
2
asmvolatile("cli");
3
asmvolatile("lds r24,flag_1ms");
4
asmvolatile("lds r25,(flag_1ms+1)");
5
asmvolatile("sei");
6
asmvolatile("subi r24,0xE8");
7
asmvolatile("sbci r25,0x03");
8
if(!(SREG&(1<<0))){
12 CPU-Cycle pro Schleife statt 24, gerade dort, wo es um die Zeit geht!
Ohne "volatile" ging reine C-Variante leider nicht: "tmpflag -= 1000;"
wurde von Compiler wegoptimiert.
Stefan F. schrieb:> Maxim B. schrieb:>> volatile u16 tmpflag;>> Das tmpflag braucht nicht volatile zu sein.
Dann wird "tmpflag -= 1000;" wegoptimiert - ich habe ausprobiert.
Maxim B. schrieb:> Dann wird "tmpflag -= 1000;" wegoptimiert - ich habe ausprobiert.
Offenbar benutzt du tmpflag für nichts. Dass "if(!(SREG & (1<<0)))"
etwas damit zu tun hat, kann der C Compiler nicht erkennen. SO schlau
ist er nicht. Schreibe einfach in C hin, was du machen willst, dann
optimiert er es auch korrekt. Also
if tmpflag>1000 {...}
Niemert schrieb im Beitrag #7346249:
> Arduino braucht timer0 für delay und millis und sowas.> Nimm timer1
Ich mache kein Arduino. Die Platine habe ich in China bestellt nach
meinem Entwurf. Arduino kennt leider kein debug, und ich habe mir vor 4
Jahren JTAG ICE II gekauft. Dafür ist diese Platine bestimmt.
Das Spielzeug habe ich reaktiviert, da ich für einen Projekt
MIDI-Sniffer brauche. Für solche Zwecke paßt diese Platine gut: für
internen Aufgaben wird nur SPI benutzt, aber auch SPI kann extern
benutzt werden. I2C und beide USARTs stehen frei.
Für diese Platine habe ich auch MIDI-Modul gemacht.
Lothar M. schrieb:> Du darfst diese Variable nur in der ISR verändern/beschreiben.> Niemals in der ISR **und** in der Mainloop!
Stimmt nicht! Mit einer atomaren Operation geht das!
Stefan F. schrieb:> Maxim B. schrieb:>> Dann wird "tmpflag -= 1000;" wegoptimiert - ich habe ausprobiert.>> Offenbar benutzt du tmpflag für nichts. Dass "if(!(SREG & (1<<0)))"> etwas damit zu tun hat, kann der C Compiler nicht erkennen. SO schlau> ist er nicht. Schreibe einfach in C hin, was du machen willst, dann> optimiert er es auch korrekt. Also>> if tmpflag>1000 {...}
Ich habe ausprobiert. Ja, das geht. Sogar 4 Bytes weniger, ansonsten ist
Code nun sehr ähnlich. Ich habe vergeblich den Compiler beschimpft :)
Mit Asm:
Lothar M. schrieb:> Aber einfach mal mit Zahlen: wenn die ISR wegen der Verwendung von> C-Code 50 Maschinentakte länger braucht, dann ist das verschwindend> wenig Zeit, die du sparrt, denn in dieser 1 ms hat der µC bei 16 MHz> immerhin 16000 Befehle ausgeführt. Und somit hättest du durch das> Einsparen der 100 Takte gerade mal 50/16000 = 0,3% der Rechenleistung> eingespart.>> Wenn diese fehlende Rechenleistung von 0,3% der Gesamtrechenleistung zu> Problemen führt, dann ist der Prozessor wohl eh' zu knapp bemessen.
Problem: es werden auch andere ISR kommen, z.B. von USART. Je kürzer
sind alle ISR, um so weniger wahrscheinlich, daß ich etwas verliere. Und
Hauptschleife wird nicht immer auf eine Sekunde gesetzt. Es kann sein,
daß sie auch mal 400 ms dauert... Natürlich ab 255 ms kein Problem, da
Zähler-Variable nur 1 Bytes braucht.
Ich machte früher oft so, daß nach Timer-ISR mehrere Variablen geprüft
werden, je nach Bedarf. Einige Aufgaben sollten jede 10 ms gemacht
werden, anderen jede 100 ms, einige auch mal je 1000 ms... Jetzt geht es
mir um Prinzip: ich möchte eine sicher funktionierende Hauptschleife,
die auch dann stabil bleibt, wenn einige Aufgaben mehrere ms brauchen
(so wie Arbeit mit LCD mit IL9341 zum Beispiel)...
Was IL9341 betrifft, habe ich übrigens eine Idee: separaten
Mikrocontroller. Dann wird Haupt-Mikroconttroller nur gelegentlich Daten
schicken, die LCD-Mikroconroller in FIFO nimmt und ohne Eile alle
Zeichen auf dem Bildschirm macht... Aber das ist noch Zukunftsmusik. Die
Platine hängt noch irgendwo in Hong Kong :)
Die Platine hat mehrere Bod-Varianten (einschließlich synchron), wozu
zwei Quarze, 14,7456 und 16 MHz, umgeschalet werden. Ich bin gespannt,
was mir gelingt...
Maxim B. schrieb:> ich möchte eine sicher funktionierende Hauptschleife,> die auch dann stabil bleibt, wenn einige Aufgaben mehrere ms brauchen
Dann würde ich in der Hauptschleife überhaupt nicht mit Zeiten
hantieren, sondern das den einzelnen Tasks überlassen. Und ich würde den
Millisekunden-Zähler niemals zurück setzen.
Lösungsansatz: http://stefanfrings.de/multithreading_arduino/index.html
Stefan F. schrieb:> Maxim B. schrieb:>> ich möchte eine sicher funktionierende Hauptschleife,>> die auch dann stabil bleibt, wenn einige Aufgaben mehrere ms brauchen>> Dann würde ich in der Hauptschleife überhaupt nicht mit Zeiten> hantieren, sondern das den einzelnen Tasks überlassen. Und ich würde den> Millisekunden-Zähler niemals zurück setzen.>> Lösungsansatz: http://stefanfrings.de/multithreading_arduino/index.html
Danke!
Ich habe auch einen selbstgebackenen Planer, vor 4 Jahren gemacht...
Nach einer Pause muß ich alles erinnern... Dort werden die Aufgaben in
FIFO gestellt, zusammen mit Takt-Zahl, wann die Aufgabe zu machen ist.
Auch Parameter für diese Funktionen werden in FIFO gespeichert (als u32,
die auch jede Funktion nach ihr Vorlieben versteht, z.B. als 2x u16 oder
als s8, u8 und s16 nach Bedarf), es gibt auch verschiedene
Prioritäten... In Timer-ISR wird dort 1-Byte-Variable inkrementiert.
Zuerst setzte ich in ISR NACKED nur ein Bit in GPIOR0, aber dann habe
ich Gefahr erkannt, daß einige Aufgaben mehr Zeit brauchen können als
Timer-ISR-Takt. Damit Verpaßte nachgeholt wird, wenn auch später, ist
eine Variable besser.
Manchmal ist so was aber zu aufwendig. Als Beispiel: ich habe zwei Timer
für UV-Platinenbelichter gemacht. Zuerst mit Hauptschleife, dann mit dem
Planer. Beide arbeiten identisch, nur Code mit Planer deutlich größer.
Ich möchte noch mal fragen.
Ich habe Logik anders gemacht. Nun wird alles in der Hauptschleife
gezählt, Var. flag_1ms hat nun 1 Byte und dient dazu, verpaßte ISR
(falls Bearbeitung in der Schleife mal zu lange dauert, z.B. eine Zeile
auf LCD geschrieben) zu speichern. Deshalb wird in der Hauptschleife
flag_1ms nicht mehr auf Null gesetzt, sondern decrementiert.
Problem: wenn ich C-Variante schreibe:
1
if(flag_1ms){
2
flag_1ms--;
3
usw.
wird Variable zweimal gelesen: bei Vergleich und für Decrement. Und
natürlich kann sie durch ISR zwischen zwei Lesen geändert werden.
Eine funktionierende Variante mit Inline-Assembler sieht so aus:
1
asmvolatile(
2
"cli \n\t"
3
"lds r24,flag_1ms \n\t"
4
"tst r24 \n\t"
5
"brne .+4 \n\t"
6
"sei \n\t"
7
"rjmp .-14 \n\t"
8
"dec r24 \n\t"
9
"sts flag_1ms,r24 \n\t"
10
"sei \n\t"
11
);
Die Frage: kann man so etwas mit C machen? Genau gesagt, nur 1x aus SRAM
lesen und zwischen Lesen und Decrement mit cli ?
Stefan F. schrieb:> Maxim B. schrieb:>> Die Frage: kann man so etwas mit C machen?> cli()> if(flag_1ms)> {> flag_1ms--;> sei();> ...> } else {> sei();> }
Vielen Dank!!!
Zwar wird Variable hier doch zweimal gelesen.
1
+00001E9A: 9180011E LDS R24,0x011E Load direct from data space
2
+00001E9C: 2388 TST R24 Test for Zero or Minus
3
+00001E9D: F409 BRNE PC+0x02 Branch if not equal
4
+00001E9E: C0BD RJMP PC+0x00BE Relative jump
5
68: flag_1ms--;
6
+00001E9F: 9180011E LDS R24,0x011E Load direct from data space
7
+00001EA1: 5081 SUBI R24,0x01 Subtract immediate
8
+00001EA2: 9380011E STS 0x011E,R24 Store direct to data space
Maxim B. schrieb:> (falls Bearbeitung in der Schleife mal zu lange dauert, z.B. eine Zeile> auf LCD geschrieben) zu speichern. Deshalb wird in der Hauptschleife> flag_1ms nicht mehr auf Null gesetzt, sondern decrementiert.
Warum, ist doch nur ne zusätzliche Fehlerquelle!
Da eh nur für den Worst-Case gedacht, reicht es doch das Flag sofort am
Anfang des "Task" zu löschen, das fängt auch EINEN IRQ ab.
Maxim B. schrieb:> wird Variable zweimal gelesen: bei Vergleich und für Decrement. Und> natürlich kann sie durch ISR zwischen zwei Lesen geändert werden.
Und? Da geht doch nichts verloren! Ist doch egal, ob in der IRQ vor oder
nach dem decrement, inkrementiert wird.
Maxim B. schrieb:> Zwar wird Variable hier doch zweimal gelesen.
Weil sie volatile ist. Vielleicht kannst du das volatile in diesem Fall
weg lassen, weil direkt vorher ein cli() steht. Da bin ich nicht ganz
sicher. Kannst ja mal ausprobieren und dann den Assembler Code prüfen.
Oder du schreibst es so:
Teo D. schrieb:> Warum, ist doch nur ne zusätzliche Fehlerquelle!> Da eh nur für den Worst-Case gedacht, reicht es doch das Flag sofort am> Anfang des "Task" zu löschen, das fängt auch EINEN IRQ ab.>
Wenn z.B. etwas gezählt wird und ein Zählen wird wegen einzige zu lange
Schleife verpaßt, wird Zählen nachgeholt: zweimal gemacht, bis Variable
wieder 0 ist. Äußerlich passiert dann nichts. Wird Variable einfach
gelöscht, dann wird auf eins weniger gezählt. Auch wenn so was nur in
einem von 100000 Schleifenzyklen passiert, wird das ärgerlich.
> Und? Da geht doch nichts verloren! Ist doch egal, ob in der IRQ vor oder> nach dem decrement, inkrementiert wird.
Perfection gibt es leider bei dem Compiler nicht: er versucht immer
wieder überflüssige Befehle zu machen... Traurig...
Maxim B. schrieb:> Perfection gibt es leider bei dem Compiler nicht: er versucht immer> wieder überflüssige Befehle zu machen... Traurig...
Ja sieht man, ich war auch gerade enttäuscht. Bei meinem obigen Beispiel
liest der die Variable jetzt nur noch einmal, aber insgesamt ist der
Code trotzdem nicht kürzer geworden.
Stefan F. schrieb:> Maxim B. schrieb:>> Zwar wird Variable hier doch zweimal gelesen.>> Weil sie volatile ist. Vielleicht kannst du das volatile in diesem Fall> weg lassen, weil direkt vorher ein cli() steht. Da bin ich nicht ganz> sicher. Kannst ja mal ausprobieren und dann den Assembler Code prüfen.>
Das hat funktioniert! Ich dachte, volatile ist für ISR auf Assembler
notwendig, um Var zu sehen. Aber nicht wirklich.
1
67: if(flag_1ms){
2
+00001E9A: 9180011E LDS R24,0x011E Load direct from data space
3
+00001E9C: 2388 TST R24 Test for Zero or Minus
4
+00001E9D: F409 BRNE PC+0x02 Branch if not equal
5
+00001E9E: C0BB RJMP PC+0x00BC Relative jump
6
68: flag_1ms--;
7
+00001E9F: 5081 SUBI R24,0x01 Subtract immediate
8
+00001EA0: 9380011E STS 0x011E,R24 Store direct to data space
9
69: sei();
10
+00001EA2: 9478 SEI Global Interrupt Enable
11
70:
12
***
13
121: sei();
14
+00001F5A: 9478 SEI Global Interrupt Enable
15
+00001F5B: CF3D RJMP PC-0x00C2 Relative jump
16
121:
Nun noch 2 Bytes mehr als mit Inline-Assembler, wegen zusätzlichen
Sprung bei else :)
Maxim B. schrieb:> Das hat funktioniert! Ich dachte, volatile ist für ISR auf Assembler> notwendig, um Var zu sehen. Aber nicht wirklich.
Da habe ich noch ein bisschen Angst. Mache da mal eine Wiederholschleife
drumherum und kontrolliere, ob die Variable dann immer noch bei jedem
Schleifendurchlauf aus dem RAM gelesen wird.
Stefan F. schrieb:> Maxim B. schrieb:>> Das hat funktioniert! Ich dachte, volatile ist für ISR auf Assembler>> notwendig, um Var zu sehen. Aber nicht wirklich.>> Da habe ich noch ein bisschen Angst. Mache da mal eine Wiederholschleife> drumherum und kontrolliere, ob die Variable dann immer noch bei jedem> Schleifendurchlauf Zugriff aus dem RAM gelesen wird.
Ich habe einfach schrittweise mit JTAGICE durch Assembler-Listing
gekuckt. Bequem, diese Möglichkeit zu haben. Alles wirklich korrekt. Na,
2 Bytes kann ich dem Compiler verzeihen :)
Für mich ist aber wichtig, beide Möglichkeiten zu kennen. Selten, aber
passiert, daß man Assembler braucht. Bekannte Beispiel: für WS2812. Oder
auch wenn man Bitfolge in Byte ändern muß, ist das mit Assembler um
mehrfaches sparsamer als rein mit C.
Maxim B. schrieb:> Ich habe einfach schrittweise mit JTAGICE durch Assembler-Listing> gekuckt. Bequem, diese Möglichkeit zu haben.
Ja. Wobei du das auch in gewissem Umfang mit dem Simulator machen
kannst.
> Für mich ist aber wichtig, beide Möglichkeiten zu kennen.> Selten, aber passiert, daß man Assembler braucht.
Es ist gut, ein bisschen Assembler zu können, um genau solche Kontrollen
machen zu können. Dann kann man im Fehlerfall verstehen, woran es
scheitert und was man dagegen tun kann.
Maxim B. schrieb:> Deshalb wird in der Hauptschleife flag_1ms nicht mehr auf Null gesetzt,> sondern decrementiert.
Was ist an "in der Hauptschleife nur lesen, nicht ändern" so schwer zu
verstehen? Das Problem sind immer noch die Read-Modify-Write Zugriffe.
Teo D. schrieb:> Und? Da geht doch nichts verloren! Ist doch egal, ob in der IRQ vor oder> nach dem decrement, inkrementiert wird.
Doch, es geht was verloren. Der Ablauf dazu: die Variable hat den Wert 5
und wird in der Hauptschleife (zum Dekrementieren) in ein lokales
Register gelesen. Das Register hat nun den Wert 5. Und genau jetzt
kommt der Interrupt, unterbricht die Haputschleife, liest die selbe
Variable mit dem Wert 5, zählt sie um 1 auf **6** hoch und schreibt sie
wieder zurück an ihren Speicherplatz. Damit ist der Interrupt fertig.
Die unterbrochene Hauptschleife kommt wieder dran, zählt den
Registerwert 5 um 1 runter und schreibt den Wert **4** an den selben
Speicherplatz. Rumpeldipumpel, ein Interrupt ging flöten.
Deshalb wird in der Hauptschleife der Zählerstand nur gelesen. Und dann
vergleicht man ihn mit einer lokalen Variable, die dann so lange
hochgezählt wird, bis sie den selben Wert wie die
Interruptzählervariable hat:
1
uintisrv,locv=0;
2
3
ISR:
4
isrv++;
5
reti
6
7
MAINLOOP:
8
if(isrv!=locv){
9
machwas();
10
locv++;
11
}
Maxim B. schrieb:>> Neim Tipp: mach aus dem flag_1ms einen uint32_t Zähler namens millis,>> zähle den jede ms um 1 hoch und mach es dann so, wie es beim Arduino mit>> den millis() geht.> Danke für Tipp.> Ich möchte aber die langsame Arduino-Variante lieber vermeiden.
Du sollst nicht den Arduino-Code nehmen, sondern die Idee verstehen: der
millis() Zähler wird nie zurückgesetzt.
Simulator ist natürlich erste Wahl. Problem kommt, wenn am Anfang von
Programm so etwas wie init von LCD steht, mit großen Verzögerungen. Bis
Simulator durchkommt... JTAGICE kommt über solchen Stellen blitzschnell.
Dafür aber keine Anzeige von verbrauchten CPU-Zyklen...
Lothar M. schrieb:> Du sollst nicht den Arduino-Code nehmen, sondern die Idee verstehen: der> millis() Zähler wird nie zurückgesetzt.
Danke!
Einzges, was mir hier Gedanken macht: Var bei millis darf nicht unter 4
Bytes sein. Und 4 Bytes zu lesen kostet Zeit, gerade an der Stelle, wo
cli möglichst kurz sein darf.
Lothar M. schrieb:> Teo D. schrieb:>> Und? Da geht doch nichts verloren! Ist doch egal, ob in der IRQ vor oder>> nach dem decrement, inkrementiert wird.> Doch, es geht was verloren. ....
Ja schon, .... nur der Fehler liegt doch schon darin, das die
Abarbeitung länger dauert als der Time-Slot. Wenn da nicht das Abfangen
eines IRQ reicht, wird das doch eh zum Irrer-Ivan!
Eigentlich darf das NIEMALS eintreten!
Teo D. schrieb:> Ja schon, .... nur der Fehler liegt doch schon darin, das die> Abarbeitung länger dauert als der Time-Slot.
Manchmal ist das nicht zu vermeiden. Wichtig daß die Verarbeitung
durchschnittlich weniger dauert. Dann können einige seltene Überstunden
geduldet werden, wenn ISR nicht vergessen gehen. Hier kann man für
ISR-Zähler sowiso nicht weniger als 1 Byte nehmen, deshalb bis ca. 255
ISR können nachgeholt werden. In der Praxis wird das selten über 2.
Ich habe übrigens schon vor einigen Zeit LCD-Programmmodul mit
Möglichkeit über Puffer zu scheiben gemacht. Puffer aus dem Programm
schnell gefüllt und dann automatisch ohne Eile, ein Zeichen oder
Zeilenwechsel pro Millisekunde... Aber das kostet für 20x4 LCD schon
satten 80 Bytes, für AVR nicht ganz wenig... Deshalb ist manchmal
sinnvoll, einige längeren Verzögerungen dulden zu können.
Maxim B. schrieb:> Einzges, was mir hier Gedanken macht: Var bei millis darf nicht unter 4> Bytes sein. Und 4 Bytes zu lesen kostet Zeit, gerade an der Stelle, wo> cli möglichst kurz sein darf.
Du bist ein Schattenkämpfer. Du willst krampfhaft Probleme lösen, die
noch gar nicht da sind und mötglicherweise gar nicht auftauchen.
https://www.mikrocontroller.net/articles/AVR-GCC-Codeoptimierung#Prinzipien_der_Optimierung
Du machst es genau falsch herum.
Falk B. schrieb:> Du machst es genau falsch herum.
Finde ich jetzt nicht so schlimm, denn dabei findet er heraus, warum
alle anderen es anders angehen. Danach wird er es wie alle anderen
machen.
Falk B. schrieb:> Du machst es genau falsch herum.
So lebe ich. Ich mache vieles falsch... Gestern habe ich bei
Schluss-Amen eine falsche Taste gedruckt... Schande... Gut, daß die
Menschen in Thüringen so viel Toleranz haben!
Stefan F. schrieb:> Danach wird er es wie alle anderen> machen.
Das mache ich nie. Wie alle anderen. Das widerspricht meiner
Persönlichkeit und meinem Beruf.
Stefan F. schrieb:> Scheint zu gehen. Glück gehabt.
ist kein Glück, ist so beabsichtigt. cli() macht eine
Optimziation/Compiler/Memory-Barrier, der Compiler muss den Wert danach
neu lesen.
Wenn man die Zugriffe sauber atomar kapselt, braucht es die
"volatile"-Krücke nicht.
Maxim B. schrieb:> Var bei millis darf nicht unter 4 Bytes sein.
Wie schon wiederholt gesagt: mach deine eigenen "millis" mit einem
einzigen Byte. Es geht nur um die Idee, die dahinter steckt.
Maxim B. schrieb:> Stefan F. schrieb:>> es wie alle anderen machen.> Das mache ich nie.
Eigenartige Einstellung. Wenn ich erkenne, dass jemand die Aufgabe schon
optimal gelöst hat, dann ist es doch nicht besonders vorteilhaft, nach
der zweitbesten Lösung zu suchen.
EDIT:
Maxim B. schrieb:> dachte, volatile ist für ISR auf Assembler notwendig, um Var zu sehen.
Volatile ist nötig, um dem Compiler mitzuteilen, dass er die Variable
oder das Register bei jedem Lesezugriff erneut einlesen muss und
eben nicht mit einer lokalen Kopie arbeiten darf. Und natürlich ist in
einem Read-Modify-Write auch ein Lese-Zugriff.
Mal nur diesen einfachen Code genommen:
1
uint8_tnv;// nicht volatile globale Variable
2
:
3
nv=0;
4
nv++;
5
nv++;
6
nv++;
7
nv++;
8
nv++;
Wel die Variable nicht volatil ist und sich deshalb nicht "im
Hintergrund" ändern kann, kann der Compiler (wenn man es ihm in den
Optimierungseinstellungen erlaubt) aus diesen 5 Zeilen einfach eine
einzige machen:
1
nv=5;
Daraus wird dann sowas:
1
LDI R24,5
2
STS 0x0111,R24
Wenn der Code jetzt aber so aussieht:
1
volatileuint8_tnv;// volatile globale Variable
2
:
3
nv=0;
4
nv++;
5
nv++;
6
nv++;
7
nv++;
8
nv++;
Dann muss der Compiler zuerst den Wert am RAM-Speicherplatz auf 0
setzen, danach muss er diesen Wert wieder vom RAM lesen, ihn
inkrementieren und wieder schrieben. Dieses vom RAM lesen,
Inkrementieren und ins RAM schreiben muss er weitere 4 mal machen. Denn
der Wert an dieser RAM-Speicherzelle (oder der Registerinhalt) kann sich
inzwischen ja geändert haben.
Das Ganze wird also dank des volatilen Verhaltens von nv eher länglich:
Lothar M. schrieb:> Maxim B. schrieb:>> Var bei millis darf nicht unter 4 Bytes sein.> Wie schon wiederholt gesagt: mach deine eigenen "millis" mit einem> einzigen Byte. Es geht nur um die Idee, die dahinter steckt.>
Wenn ich richtig verstehe, sollte Zähler von "millis" genug groß sein,
um während der Arbeit von Gerät, von Einschalten bis zu Ausschalten,
keine Wiederholung kommt? Wie kann man sonst Differenz eingeben?
Wenn ich das Gerät z.B. bis zu 8 Stunden betreiben möchte, und
Millisekunden als Takt, dann brauche ich bis zu 1000*60*60*8=28800000 =
0x1B77400 zu zählen, schon 4 Bytes notwendig?
Maxim B. schrieb:> Wenn ich richtig verstehe, sollte Zähler von "millis" genug groß sein,> um während der Arbeit von Gerät, von Einschalten bis zu Ausschalten,> keine Wiederholung kommt? Wie kann man sonst Differenz eingeben?
Nein. Es muss nur groß genug sein, dass die größte Zeitspanne, die du
abmessen willst, reinpasst.
Wenn der Zähler überläuft und dann wieder bei 0 anfängt, ist das kein
Problem. Wenn du die Zeitvergleiche richtig gemacht hast.
Εrnst B. schrieb:> Nein. Es muss nur groß genug sein, dass die größte Zeitspanne, die du> abmessen willst, reinpasst.>> Wenn der Zähler überläuft und dann wieder bei 0 anfängt, ist das kein> Problem. Wenn du die Zeitvergleiche richtig gemacht hast.
Dann sollten 2 Bytes reichen. 1 Byte reicht aber in keinem Fall.
Maxim B. schrieb:> Dann sollten 2 Bytes reichen. 1 Byte reicht aber in keinem Fall.
Für flag_1ms reicht doch auch 1 Byte. Dann sollte das auch für das
Durchzählen reichen:
1
volatile uint8_t flag_1ms;
2
3
ISR(TIMER0_COMPA_vect) {
4
flag_1ms++;
5
}
6
7
void main () {
8
flag_1ms = 0;
9
// Initialise TIMER0 to trigger COMPA every 1ms
10
[...]
11
while (1) {
12
static uint8_t last_after5ms = 0;
13
if (flag_1ms-last_after5ms>=5) {
14
last_after5ms += 5;
15
// Do something every 5ms
16
}
17
static uint8_t last_after15ms = 0;
18
if (flag_1ms-last_after15ms>=15) {
19
last_after15ms = flag_1ms;
20
// Do something every 15ms
21
}
22
[...]
23
}
24
}
Es muss hierbei nur garantiert sein, dass ein Durchlauf durch die
while-Schleife nie länger als 255ms dauert.
Übrigens: Die Stelle "Do something every 5ms" versucht eventuelle
Verzögerungen aufzuholen; die Stelle "Do something every 15ms" tut das
nicht, sondern wartet immer mindestens 15ms.
LG, Sebastian
Hier darf Schleife nie über 1 ms sein, sonst wird ISR verloren.
Um ISR nicht zu verlieren, sollte Variable, die in ISR incrementiert
wird, in der Hauptschleife decrementiert werden und nicht auf Null
gesetzt.
Hätte mir eine Millisekunde gereicht, würde ich bei ISR keine Variable
nehmen sondern ein Bit in GPIOR0, das ist von Natur aus atomar. Dann
wird das etwa so aussehen:
Maxim B. schrieb:> Wenn ich richtig verstehe, sollte Zähler von "millis" genug groß sein,> um während der Arbeit von Gerät, von Einschalten bis zu Ausschalten,> keine Wiederholung kommt?
Nö. Der Zähler muss nur groß genug sein, um die größten Intervalle
abzudecken, die man messen will. Ein einzelner Überlauf während der
Messung stört nicht, wenn man die Zeitspanne mit einer Subtraktion
berechnet. Vor zwei Tagen hatte ich auf einen Artikel verlinkt, der
bereits darauf hinwies.
Stefan F. schrieb:
Vor zwei Tagen hatte ich auf einen Artikel verlinkt, der
> bereits darauf hinwies.
Ja, ich habe dein Link gespeichert, danke! Nur muß ich das alles noch
aufmerksam lesen :)
Maxim B. schrieb:> Hier darf Schleife nie über 1 ms sein, sonst wird ISR verloren.> Um ISR nicht zu verlieren, sollte Variable, die in ISR incrementiert> wird, in der Hauptschleife decrementiert werden und nicht auf Null> gesetzt.
Nö, wieso? Weder auf Null setzen noch dekrementieren ist nötig.
Stattdessen merkst du dir die früheren Zählerwerte, und subtrahierst
diese vom jetzigen Wert. Und das funktioniert auch über die
Überlaufgrenzen hinweg.
LG, Sebastian
Maxim B. schrieb:> 1 Byte reicht aber in keinem Fall.
Was macht dein Programm 255 ms lang?
Maxim B. schrieb:> FLAGG |= (1<<F_PULS); // Flag fuer Puls einsetzen
Und dann das einzelne Bit in der Hauptschleife per Read-Modify-Write
wieder zurücksetzen?
Was hast du an meinem Tipp "In der Hauptschleife nur lesen!" nicht
verstanden?
Du bist da offensichtlich viel zu sehr in deiner Assembler-Flag-Welt,
als dass du die Einfachheit und die Geradlinigkeit des von mir
vorgeschlagenen Weges sehen würdest. Mein Tipp: denk nochmal ausgiebig
über den miilis() Mechanismus nach. Man muss das nicht am ersten Tag
vollumfänglich erkennen. Wenn ich ehrlich bin: ich habe auch ein paar
Irrwege/Umwege gemacht. Deine Versuche waren auch dabei.
Sebastian schrieb:> Nö, wieso? Weder auf Null setzen noch dekrementieren ist nötig.> Stattdessen merkst du dir die früheren Zählerwerte, und subtrahierst> diese vom jetzigen Wert. Und das funktioniert auch über die> Überlaufgrenzen hinweg.
Nein, der Maxim muss immer alles anders, vor allem eckiger machen!
Sebastian W. schrieb:> muss hierbei nur garantiert sein, dass ein Durchlauf durch die> while-Schleife nie länger als 255ms dauert.
Nicht ganz: integral Promotion vermasselt Dir den Überlauf. Daher
* unsigned int als Datentyp
* ODER Ergebnis auf uint8_t casten
* ODER Ergebnis zwischenspeichern
Lothar M. schrieb:> Und dann das einzelne Bit in der Hauptschleife per Read-Modify-Write> wieder zurücksetzen?
Nein.
1
// Bedienung von Puls 1 ms
2
if( FLAGG & (1<<F_PULS) ){
3
104c: f0 9b sbis 0x1e, 0 ; 30
4
104e: fe cf rjmp .-4 ; 0x104c <main+0x2a>
5
FLAGG &= ~(1<<F_PULS); // Flag fuer Puls = 0
6
1050: f0 98 cbi 0x1e, 0 ; 30
Auch ISR wird mit nur sbi auskommen. Deshalb braucht man dafür nichts
pushen und popen. Und das ist von Natur aus atomar.
GPIOR0 paßt für Bearbitung von ISR in der Haupttschleife sehr gut.
Leider sind GPIOR1 und GPIOR2 bei ATMega1284 nicht mehr in Bit-Bereich,
aber mit GPIOR0 kann man das machen (es gibt aber Kontroller, wo alle
GPIORs in Bit-Bereich stehen, so wie AT90PWM3B). So dürfen diese ISR
(bis zu acht) NAKED sein und deshalb sehr kurz. Ich habe so auch Int2
bearbeitet: dort hängt DS3234. Für RTC sagt ISR nur, daß die Daten
erneuert werden, und auch wenn 1x ISR verpaßt wird, passiert nichts.
Wenn die Verkürzung der Timer-ISR das absolute Ziel ist:
Es geht auch ohne. Stell einen passenden Pre-Scaler ein, verwende direkt
den TCNT1 als Zeitbasis...
Der wird dann ganz von alleine hochgezählt, und auch beim Auslesen der
16-Bit hilft dir die Hardware, du kannst den Wert ohne IRQ-Sperre lesen.
Und statt Zeiträume/Intervalle mit einem in der ISR gesetzten Flag
abzuwarten, könntest du in der main-loop z.B. auch stattdessen ein
Output-Compare-Flag abfragen, und dir die Intervalle über die
OCR-Register einstellen...
Also: Statt dass die Timer-Hardware ein Flag im Register setzt, die CPU
als Reaktion darauf eine ISR aufruft, die ISR das Flag löscht und
stattdessen ein Flag im RAM setzt, und dann das Hauptprogramm das Flag
im RAM ausliest: Einfach direkt im Hauptprogramm das
Hardware-Register-Flag auslesen...
Εrnst B. schrieb:> Also: Statt dass die Timer-Hardware ein Flag im Register setzt, die CPU> als Reaktion darauf eine ISR aufruft, die ISR das Flag löscht und> stattdessen ein Flag im RAM setzt, und dann das Hauptprogramm das Flag> im RAM ausliest: Einfach direkt im Hauptprogramm das> Hardware-Register-Flag auslesen...
Da haste dich aber verrannt....
Egal, hier gibts ja eh nur Kraut und Rüben!
Maxim B. schrieb:> 1050: f0 98 cbi 0x1e, 0 ; 30
Es stimmt zwar, das CBI an sich ist nicht unterbrechbar ist.
Das Ganze funktioniert aber nur, wenn die Variable dauerhaft und
garantiert in einem Register liegt. Das kannst du nur bei winzigen
Mini- oder Demo-Progrämmchen mit wenig Spericherbedarf und wenig
Funktionsumfang erwarten. Und das Verriegeln einer solchen Variablen in
ein Register auf C-Ebene ist zumindest unschön.
Nochmal: du willst unbedingt die Assembler-Denkweise "Ich muss alles bis
aufs letzte Bit selber unter Kontrolle haben!" auf eine höher
abstrahierende Programmiersprache übertragen. Das ist der falsche
Ansatz, denn dann musst du den Compiler zu Dingen zwingen, die er "von
sich aus" anders gelöst hätte.
Aber auch die Compilerbauer sind nicht auf der Brennsuppe
dahergeschwommen und haben solche alltäglichen Funktionen natürlich
ebenfalls gelöst. Nur eben abstrakter, anders und portabler.
Mein (durchaus erprobter) Ansatz ist es, den Code möglichst ohne
Verwendung von speziellen unportierbaren oder schlecht portierbaren
Geschichten wie "nacked" ISR zu schreiben. Und wenn sich im Verlauf der
Entwicklung herauskristallisiert, dass mir die Rechenzeit knapp wird,
erst dann überlege ich, ob (auch im Hinblick auf Erweiterungen) eine
händisch hingebastelte Optimierung oder ein größerer µC die bessere
Lösung ist.
Teo D. schrieb:> Εrnst B. schrieb:>> Also: Statt dass die Timer-Hardware ein Flag im Register setzt, die CPU>> als Reaktion darauf eine ISR aufruft, die ISR das Flag löscht und>> stattdessen ein Flag im RAM setzt, und dann das Hauptprogramm das Flag>> im RAM ausliest: Einfach direkt im Hauptprogramm das>> Hardware-Register-Flag auslesen...>> Da haste dich aber verrannt....
Nur in dem Sinne, dass der TO etwas über ISRs lernen wollte ;-)
Ansonsten ist der Ansatz absolut richtig!
Maxim B. schrieb:> GPIOR0 paßt für Bearbitung von ISR in der Haupttschleife sehr gut.
Kann man machen. Aber bitte nur zur Kommunikation von ISRs mit dem Rest,
weil die selbstverständlich als volatile qualifiziert sind und damit
jeder Zugriff wie in der abstrakten Maschine erfolgt, was mögliche
Optimierungen verhindert.
Lothar M. schrieb:>> 1050: f0 98 cbi 0x1e, 0 ; 30> Es stimmt zwar, das CBI an sich ist nicht unterbrechbar ist.> Das Ganze funktioniert aber nur, wenn die Variable dauerhaft und> garantiert in einem Register liegt. Das kannst du nur bei winzigen> Mini- oder Demo-Progrämmchen mit wenig Spericherbedarf und wenig> Funktionsumfang erwarten.
Nein. Wenn man die GPIO Register oder ein sonstiges Register benutzt,
das gerade frei ist und per sbi/cbi erreichber ist, ist das garantiert.
Die ATXmega haben sogar extra 8 Stück davon!
> Nochmal: du willst unbedingt die Assembler-Denkweise "Ich muss alles bis> aufs letzte Bit selber unter Kontrolle haben!"
In der Tat. Ein Kontrollfetischist, der das Gesamtproblem bzw. Ziel
längst aus den Augen verloren hat. Wenn er es denn je im Auge hatte.
Vermutlich geht es hier nur um den Egotrip und nicht um die Lösung eines
realen Problems.
Wilhelm M. schrieb:> Ansonsten ist der Ansatz absolut richtig!
Nein, das löst das "Problem" des TOs nicht, den er hat es noch nicht mal
verstanden/gelesen! Du scheinbar ebenso wenig.
Das "Durch die Brust ins Auge", kommt hier nirgends vor! ... Wat'n
Glück, is schon "Chaos" genug hier. :)
Maxim B. schrieb:> GPIOR0 paßt für Bearbitung von ISR in der Haupttschleife sehr gut.> Leider sind GPIOR1 und GPIOR2 bei ATMega1284 nicht mehr in Bit-Bereich,> aber mit GPIOR0 kann man das machen (es gibt aber Kontroller, wo alle> GPIORs in Bit-Bereich stehen, so wie AT90PWM3B).
Du solltest stattdessen auf einen moderneren wie etwa die AVR-DA/DB/DD
Serie wechseln. Da gibt es auch eine RTC oder PIT für solche Aufgaben.
Teo D. schrieb:> Nein, das löst das "Problem" des TOs nicht,
Warum? Es ging um eine Timer-ISR, die eine Variable für eine Zeitbasis
hochzählt. Ob man die jetzt "millis", "systick", "time", "ticker" oder
sonstwie nennt, ist ja egal.
Und genau das stumpfe Hochzählen eines 16-Bit Wertes, der bei Überlauf
wieder bei 0 beginnt, kann der Timer auch in Hardware, ganz ohne ISR.
Εrnst B. schrieb:> nd genau das stumpfe Hochzählen eines 16-Bit Wertes, der bei Überlauf> wieder bei 0 beginnt, kann der Timer auch in Hardware, ganz ohne ISR.
Nur so als Hinweis, auch wenn es nicht von Belang ist, weil der TO ja
etwas über ISRs lernen will: verzichtet man auf ISRs und damit auf
volatile memory-barrier Int-Sperren, ist für den Compiler das ganz
Programm wieder zu einem schönen sequentiellen Programm geworden. Wenn
dann noch alle Funktionen inline sind (z.B. templates) (gemeint ist
hier, dass der Compiler in der TU den Code sieht, sofern nicht LTO
angewendet wird), für das i.A. zu wesentlich kleinerem (und besser
optimiertem Code), weil "über alles" optimiert werden kann.
Aber Zugriffe auf den Timer Counter sind ohne
volatile/cli/memory-barrier nicht atomar. Damit bekommt man wieder die
oben demonstrierten Probleme beim Lesezugriff.
Steve van de Grens schrieb:> Aber Zugriffe auf den Timer Counter sind ohne> volatile/cli/memory-barrier nicht atomar. Damit bekommt man wieder die> oben demonstrierten Probleme beim Lesezugriff.
volatile-access/memory-barrier haben nichts mit Atomarität zu tun.
memory-barrier hat nichts mit volatile-access zu tun.
Es hilft nichts, wenn man alles planlos in eine Suppe wirft.
Steve van de Grens schrieb:> Aber Zugriffe auf den Timer Counter sind ohne> volatile/cli/memory-barrier nicht atomar. Damit bekommt man wieder die> oben demonstrierten Probleme beim Lesezugriff.
Mmh. Auf dem ATMega1284 sind Zugriffe auf die 16-bit TCNTn-Register
atomar: "16.3 Each 16-bit timer has a single 8-bit register for
temporary storing of the high byte of the 16-bit access. [...] When the
low byte of a 16-bit register is read by the CPU, the high byte of the
16-bit register is copied into the temporary register in the same clock
cycle as the low byte is read."
LG, Sebastian
Lothar M. schrieb:> Nochmal: du willst unbedingt die Assembler-Denkweise "Ich muss alles bis> aufs letzte Bit selber unter Kontrolle haben!" auf eine höher> abstrahierende Programmiersprache übertragen. Das ist der falsche> Ansatz, denn dann musst du den Compiler zu Dingen zwingen, die er "von> sich aus" anders gelöst hätte.>
Das stimmt.
In einem Orchester gibt es nur ein Dirigent, anders funktioniert es in
der Musik kaum. Ich bin so erzogen: ich möchte alles unter Kontrolle
haben :)
Sebastian W. schrieb:> Mmh. Auf dem ATMega1284 sind Zugriffe auf die 16-bit TCNTn-Register> atomar:
Nein, sind sie nicht. Das zitierte Feature dient nur dazu, auf
Timer-Register Glitch-frei zugreifen zu können.
> "16.3 Each 16-bit timer has a single 8-bit register for> temporary storing of the high byte of the 16-bit access. [...] When the> low byte of a 16-bit register is read by the CPU, the high byte of the> 16-bit register is copied into the temporary register in the same clock> cycle as the low byte is read."
Wenn allerdings zwischen den beiden Zugriffen eine ISR ausgeführt wird,
die auf das gleiche Timer-Register zugreift, dann bekommt man Probleme
wie bei "normalen" 16-Bit Variablen auch — es sei denn, man trifft
entsprechende Vorkehrungen.
Wilhelm M. schrieb:> Du solltest stattdessen auf einen moderneren wie etwa die AVR-DA/DB/DD> Serie wechseln. Da gibt es auch eine RTC oder PIT für solche Aufgaben.
Ich habe gestern für Probe auf zweite Win7 letzte Atmel Studio
installiert (obwohl ich weiß, daß das sehr schlechtes IDE ist), um zu
kucken, ob ich dort bequem mit AVR-DA probieren könnte.
Leider habe ich festgestellt, das für diese AVR keinen Simulator
existiert. Man muß immer mit einer physischen Platine arbeiten. Einfach
Programmabschnitt ausprobieren, wie mit ATMega1284 u.Ä. üblich, kann man
mit AVR-DA nicht.
Vielleicht werde ich irgendwann in der Zukunft etwas mit AVR-DA machen,
aber erst wenn ich viel sicherer mit C werde.
Im Moment überdeckt die Reihe ATMEGA324-644-1284 all mein Bedarf. Meine
laufende Projekte drehen sich um MIDI, dafür reichen die Möglichkeiten
von diesen Controller.
Εrnst B. schrieb:> Also: Statt dass die Timer-Hardware ein Flag im Register setzt, die CPU> als Reaktion darauf eine ISR aufruft, die ISR das Flag löscht und> stattdessen ein Flag im RAM setzt, und dann das Hauptprogramm das Flag> im RAM ausliest: Einfach direkt im Hauptprogramm das> Hardware-Register-Flag auslesen...
Das hat Nachteil, daß Hardware-Register-Flag von Hardware gelöscht sein
kann. Das enspricht meinem Lebenskonzept als "Kontrollfetischist" nicht
:)
Maxim B. schrieb:> Εrnst B. schrieb:>> Einfach direkt im Hauptprogramm das Hardware-Register-Flag auslesen...> Das hat Nachteil, daß Hardware-Register-Flag von Hardware gelöscht sein> kann.
Das Flag wird nur gelöscht, wenn:
* Eine entsprechende IRQ ausgeführt wird (was du ja machst), oder
* Das Flag von Hand gelöscht wird.
Du willst also nicht, dass die Hardware das Flag zurücksetzt, verwendest
aber ein Schema, das genau das mnacht.
Johann L. schrieb:> Du willst also nicht, dass die Hardware das Flag zurücksetzt, verwendest> aber ein Schema, das genau das mnacht.
Ein bißchen anders. In lösche das Flag in der Hauptschleife, während
auch Reaktion auf ISR stattfindet. Sinn: Hauptschleife darf schmerzlos
von anderen ISR unterbrochen werden. ISR selbst bleibt möglichst kurz,
um ISR einander am wenigstens störten...
Übrigens, ich habe die Variante mit GPIOR0 nur als eine Möglichkeit
erwähnt. Für ISR, die für Zeit verantwortlich sind, favorisiere ich doch
die Variante, wo ISRs gezählt werden, damit auch verpaßte nachgearbeitet
werden.
Für Anwendungen wie Ereignisse (so wie auf meiner Platine DS3234
angeschlossen wurde. Dort bedeutet ISR nur, daß die Zeitdaten abgelesen
sein KÖNNTEN, d.h. einmal verpaßt macht gar nichts. Es sei denn, über
ISR von DS3234 wird auch Systemtiming gemacht, das ist auch möglich)
reicht Flag in GPIOR aus.
Maxim B. schrieb:> Ein bißchen anders. In lösche das Flag in der Hauptschleife, während> auch Reaktion auf ISR stattfindet. Sinn: Hauptschleife darf schmerzlos> von anderen ISR unterbrochen werden. ISR selbst bleibt möglichst kurz,> um ISR einander am wenigstens störten...
Genau das kannst du doch auch mit dem Flag im Timer-Register machen.
Reagiere in der main-loop darauf, lösche es da. Dann hast du keine
Timer-ISR, die irgendwas unterbrechen würde, und andere ISR stören das
Ganze auch nicht...
Maxim B. schrieb:> einmal verpaßt macht gar nichts.
genau das Verhalten hast du mit dem "nativen" Flag auch... wenn die Loop
nicht dazukommt es abzuarbeiten, bleibt es eben gesetzt, und ein zweiter
Compare-Match ändert nichts daran.
Hilft dir aber, wenn wie oben vermutet dein Lernziel "möglichst viele
ISR verwenden, egal wie sinnvoll" ist, nicht weiter.
Kannst es ja im Hinterkopf behalten, falls du mal in einer realen
Anwendung in eine ähnliche Situation kommst.
Εrnst B. schrieb:> Maxim B. schrieb:> Genau das kannst du doch auch mit dem Flag im Timer-Register machen.> Reagiere in der main-loop darauf, lösche es da. Dann hast du keine> Timer-ISR, die irgendwas unterbrechen würde, und andere ISR stören das> Ganze auch nicht...
Und wenn Timerüberlauf zweimal oder dreimal kommt? Ich kann das wissen
nur wenn ISR gezählt werden.
> Maxim B. schrieb:>> einmal verpaßt macht gar nichts.>> genau das Verhalten hast du mit dem "nativen" Flag auch... wenn die Loop> nicht dazukommt es abzuarbeiten, bleibt es eben gesetzt, und ein zweiter> Compare-Match ändert nichts daran.
Bei externen ISR von DS3234 kommt dann ein Problem: ISR-Pin wird so
lange tief gehalten, bis Kommunikation mit DS3234 stattfindet. Das
könnte stören, besonders wenn andem gleichen ISR-Pin mehrere Slaaven
sitzen.
Maxim B. schrieb:> könnte stören, besonders wenn andem gleichen ISR-Pin mehrere Slaaven> sitzen.
Slaaven? Oder vielleicht auch Mongolen? Oder Ostgoten?
Maxim B. schrieb:> Und wenn Timerüberlauf zweimal oder dreimal kommt?
gerade eben hast du noch erzählt, dass das dann egal wäre?
Maxim B. schrieb:> ISR-Pin wird so> lange tief gehalten, bis Kommunikation mit DS3234 stattfindet.
Ja, und? den Pin kannst du auch in deiner Main-Loop abfragen, ohne
ISR... Vor allem wenn du die eigentliche Abfrage eh aus der Main-Loop
heraus machst.
1
while(true){
2
....
3
if(bit_is_clear(DS_PIN,DS_PIN_NR))query_ds3243()
4
....
5
}
Was bringt es dir, den Pin-Status zuerst in einer ISR in ein Flag
umzukopieren, wenn Abfragen des IO-Registers im Hauptprogramm billiger
wäre?
Hallo Maxim,
Maxim B. schrieb:> In einem Orchester gibt es nur ein Dirigent, anders funktioniert es in> der Musik kaum.
So weit, so gut.
Dein ursprüngliches Problem war ja, dass dir von Zeit zu Zeit Ereignisse
verlorengingen bzw. (beim Vergleich auf >999) zu häufig ausgeführt
wurden. Für dieses Problem wurden einige Lösungen genannt:
1) Variablen volatile deklarieren (hattest du schon gemacht).
2) Während des Zugriffs auf mit ISR geteilte Variablen größer als 1 Byte
außerhalb der ISR die Interrupts sperren, sowohl beim Lesen als auch
beim Schreiben. Alternativ 1-Byte-Variablen verwenden, falls
ausreichend.
3) Für Lese/Abfrage/Modifiziere-Konstrukte von mit ISR geteilten
Variablen außerhalb der ISR die Interrupts von Anfang bis Ende sperren,
falls ein zwischenzeitiger Interrupt (und entsprechende dortige
Modifikation der gemeinsamen Variable) die Programmlogik zerstört. Gilt
auch für 1-Byte-Variablen. Allerdings wird von Logik, bei der geteilte
Variablen von zwei Stellen aus verändert werden, abgeraten.
4) Anstelle von 1), 2) und/oder 3) einen TCNTn so laufen lassen, dass
dessen TOVn in der Hauptschleife detektiert und zurückgesetzt werden
kann. Dies ermöglich u.U. aber nur die Prüfung ob ein Überlauf
aufgetreten ist, nicht wie häufig.
5) Anstelle von 4) einen TCNTn ganz langsam laufen lassen, und die
Erhöhung dieses TCNTn in der Hauptschleife detektieren (z.B. durch
Vergleich mit einem gespeicherten vorherigen Wert von TCNTn). Hierbei
können auch mehrfache Erhöhungen erkannt werden.
Allerdings befreit dich all dies nicht von der Notwendigkeit
kooperativen Multitasking, die maximale Dauer eines
Hauptschleifendurchlaufs (und also jeder einzelnen Aktion im
Hauptschleifenkontext) so zu begrenzen, dass keine anderen eventuell
notwendigen nebenläufigen Aktionen dadurch behindert werden. Nur so hast
du "alles unter Kontrolle".
Und dazu muss man halt für jede einzelne Aktion deren maximale Dauer
ausrechnen, und gegebenenfalls in kleinere Teile aufteilen, wie zum
Beispiel die Bedienung des Displays. Denn selbst wenn du einen
Koprozessor dafür nutzt, muss doch dennoch die Kommunikation mit diesem
im Rahmen der genannten Maximalzeit vonstatten gehen.
LG, Sebastian
Εrnst B. schrieb:> Maxim B. schrieb:>> Und wenn Timerüberlauf zweimal oder dreimal kommt?>> gerade eben hast du noch erzählt, dass das dann egal wäre?>
Nein. DAS wäre nicht egal. Egal wäre das nur bei ISR, die für Systemtakt
nicht verantwortlich sind.
Maxim B. schrieb:> Nein. DAS wäre nicht egal. Egal wäre das nur bei ISR, die für Systemtakt> nicht verantwortlich sind.
Aber statt der Systemtakt-ISR kannst du direkt den Timer-Wert lesen. Per
Prescaler den Timer in einer passenden Geschwindigkeit laufen lassen.
Dann passiert der Überlauf bei 2¹⁶, weiter oben meintest du, das wäre
ausreichend (bei ~1kHz). Mit dem Prescaler kannst du dann zwischen
besserer Auflösung oder höherer Maximal-Zeit zwischen den Überläufen
wählen...
Das umrechnen von Minuten und Sekunden in Timer-Ticks ist dann nicht
mehr ganz so einfach, aber da der Compiler nicht mit 10 Fingern geboren
wurde, hat er garkein Problem damit auch mit schrägen Werten zu rechnen.
Εrnst B. schrieb:> Aber statt der Systemtakt-ISR kannst du direkt den Timer-Wert lesen. Per> Prescaler den Timer in einer passenden Geschwindigkeit laufen lassen.
Ja, ich kann auch das...
Nur 1-ms-Takt ist so bequem...
Außerdem möchte ich mit Timer-Wert Tempo bei MIDI wechseln.
Für mich ist interessant: reicht 5-ms-Takt, um die Zeit bei einem
Sequenzer zwischen MIDI-Befehlen zu speichern? Oder wird bei 32-Passagen
doch Unregelmäßigkeit zum Hören?
3 Bytes MIDI dauern etwa 1 ms, deshalb viel darunter zu gehen hat wenig
Sinn. Aber darüber?
Das kann vielleicht nur Versuch zeigen.
Um klar zu machen: es geht mir hier um Urlaub. Damit mein Pfarrer mal
ohne mich GDs machen kann.
Maxim B. schrieb:> Das könnte stören, besonders wenn...
Das erinnert mich an den "Schuh des Manitu", wo es heißt "Was, wenn
einer von uns die Masern bekommt?"
Du zerrst hier"Probleme" an Land, die beim System- oder Softwaredesign
ganz einfach berücksichtigt werden können.
Wenn das eine gemeinsame Interruptleitung ist, dann muss die
Interruptroutine eben so oft durchlaufen werden, bis die Leitung inaktiv
wird.
Maxim B. schrieb:> Nur 1-ms-Takt ist so bequem...
dann bleib halt dabei.
Oben hast du den Eindruck gemacht, du musst unbedingt die letzten paar
Taktzyklen aus deinen ISR-Routinen rausquetschen. Und weniger als 0 (in
Worten: "Null") Takte bei Verzicht auf die ISRs geht halt nicht, deshalb
der Vorschlag...
Musst du als Programmierer wissen, wo ein Interrupt Sinn macht, und wo
ein simples Abfragen aus dem Hauptprogramm besser ist.
Gerade die INT-Leitung deines DS3234 wäre ein Paradebeispiel... nur weil
das Datenblatt die so bezeichnet, muss der µC die noch lange nicht per
ISR auswerten. Vor allem wenn du die Leitung eh erst später im
Hauptprogramm zurücksetzt, wenn du den Chip ausliest.
Herzlichen Dank an alle, die mir hier geholfen haben! Ich habe viel
neues erlernt.
Ich werde nun die verschiedene Möglichkeiten ausprobieren. Genau dafür
habe ich die Platine gemacht: um zu probieren.