Forum: Mikrocontroller und Digitale Elektronik 65 Variablen in ISR runterzählen schneller machen


von MOBA 2. (Gast)


Lesenswert?

Hallo & frohes Neues!

ich habe ein Problem. Lange habe ich jetzt rumoptimiert und gemacht und 
getan (hat auch was gebracht), aber der Brocken ist mir jetzt erst 
aufgefallen.

Folgendes: Ich habe 2 ISR (1x Signaldetection am Pin, 1x Timer).

Im Timer mache ich div Sachen, das ist erstmal so okay.
Eine jedoch nicht. Für das Hauptprogramm benötige ich div. 
Timer-Variablen die im ms-Takt gezählt werden (Zeitablauf).


Dazu taktet der ISR im 1 kHz-CTC => 1ms / Interrupt.

Jetzt zähle ich dort die Variablen runter, im Programm kann ich dann 
einen Wert (bspw. 500 in eine Variable) eintragen, prüfen und nach einer 
halben Sekunde ist das Ziel erreicht. Das sind 16-Bit Variablen da ich, 
ohne Einfluss, Werte von 1ms bis hin zu 5 sek brauche.

Lange Rede kurzer Sinn: Das sind 65 Variablen, 16-Bit. Ich habe eine 
Schleife gemacht, so:
1
ISR(...)
2
{
3
   for (i = 0; i < 65; i++) if (timer[i]) timer[i]--;
4
}


Das dauert gräßlich lange, so lange das das ~20kHz Signal am Portpin 
verlangsamt wird, bekommt nicht mehr alles mit. Ich weiß nicht wo die 
Grenze liegt dass es wieder "flüssig" läuft, müsste man testen ich denke 
20-30 werden gehen. Ich brauche aber alle.


Hat jmd. eine Idee wie man das Klug lösen kann? Mir fällt nichts ein.

von Kevin (Gast)


Lesenswert?

Nur einen einzigen Zähler I in die ISR, der aufwärts zählt.

Das Abwärtszählen dann im Mainloop. Mit jedem Durchlauf abwärts zählen, 
I-Mal insgesamt.

I muss dann volatile sein.

timer[] aber nicht mehr. :)

von Kevin (Gast)


Lesenswert?

bzw. nicht I-Mal, sondern so lange wie --I>0 gilt.

Achso, du musst das Ganze in ein cli-sei-Konstrukt reinbasteln (atomarer 
Zugriff).

von MOBA 2. (Gast)


Lesenswert?

Kevin schrieb:
> Nur einen einzigen Zähler I in die ISR, der aufwärts zählt.
>
> Das Abwärtszählen dann im Mainloop. Mit jedem Durchlauf abwärts zählen,
> I-Mal insgesamt.
>
> I muss dann volatile sein.
>
> timer[] aber nicht mehr. :)


Danke für die Antwort, ich teste es asap.
Ich hoffe nur, dass das nicht alles zu lange dauert, da 64k fast voll 
sind mit reiner Rechnerei und Logik, der hat schon extrem viel zu tun 
obwohl 20Mhz Takt vorhanden sind. Ich teste es jetzt gleich direkt.

von MOBA 2. (Gast)


Lesenswert?

Kevin schrieb:
> bzw. nicht I-Mal, sondern so lange wie --I>0 gilt.
>
> Achso, du musst das Ganze in ein cli-sei-Konstrukt reinbasteln (atomarer
> Zugriff).

Das wäre aber schlecht, weil die anderen ISR sind extrem wichtig weil 
die Signale erzeugen und detektieren müssen wo ein Jitter für die 
Signalzeiten eingehalten werden muss was im kleinen us-Bereich liegt.



Wie meinst du das jetzt?

Ich habe es so verstanden:

1 Variable im ISR (master_cnt_timer). In der ISR hochzählen bis 64.
2. In der loop meine timer[master_cnt_timer] runterzählen wenn timer[..] 
> 0 (quasi das was bis jetzt in der isr stand).
3. Wenn master_cnt_timer = 64 in der loop 0 setzen nachdem letzte Var 
gezählt wurde, richtig?!


EDIT: Macht keinen Sinn dann habe ich auch wieder einen Zeitversatz da 
drin.

Ich verstehe dich nicht wirklich....

von Benjamin S. (recycler)


Lesenswert?

Ich lasse mal den Kommentar zu der Anzahl aus.

Normalerweise kostet ein Jump sehr viel.
Besser wäre, wenn du "function unrolling" machen würdest, dh. du 
decrementierst jede Variable einzeln.

so etwa
1
if (timer[0]) timer[0]--;
2
if (timer[1]) timer[1]--;
3
if (timer[2]) timer[2]--;
4
if (timer[3]) timer[3]--;
5
if (timer[4]) timer[4]--;
6
...

Du hast aber imm noch eine zweiten Jump drinnen (beim if).
Dann wäre noch die Addition im der Variable (*timer + i), die zweimal 
ausgeführt wird. Das könnte man auch noch optimieren.

so etwa:
1
int *pos = timer
2
3
if (*pos) *pos--;
4
pos++;
5
if (*pos) *pos--;
6
pos++;
7
if (*pos) *pos--;
8
pos++;
9
if (*pos) *pos--;
10
pos++;
11
...

Ansonste müsste man das noch in ASM ansehen und dann ggf. umschreiben.

Wie eingangs gesagt, überlege, ob du das mit den 65 Variablen nicht 
anders machst und ggf. zusammenfasst.

von Kevin (Gast)


Lesenswert?

1
volatile uint8_t I;
2
3
// ISR:
4
I++;
5
6
// Main loop:
7
...
8
if (I) {
9
 I--;
10
 for (uint8_t i = 0; i < 65; i++) if (timer[i]) timer[i]--;
11
}
12
...

So in etwa. Blöd wird's nur, wenn I überläuft.

von MOBA 2. (Gast)


Lesenswert?

Kevin schrieb:
>
1
volatile uint8_t I;
2
> 
3
> // ISR:
4
> I++;
5
> 
6
> // Main loop:
7
> ...
8
> if (I) {
9
>  I--;
10
>  for (uint8_t i = 0; i < 65; i++) if (timer[i]) timer[i]--;
11
> }
12
> ...
>
> So in etwa. Blöd wird's nur, wenn I überläuft.

Achso meinst du das, jaja das könnte ich mal testen. Danke dir.
Das mit dem Überlauf ist ein Problem, ggf. auch 16-Bit rauf gehen, das 
müsste eigentlich machbar sein. Ich schaue mal.

von Kevin (Gast)


Lesenswert?

Also wenn 255 schon nicht ausreichen, wird es immer weiter steigen... 
Die Mainloop darf nicht zu lahmarschig werden.

von MOBA 2. (Gast)


Lesenswert?

Benjamin S. schrieb:
> so etwa:
>
>
1
int *pos = timer
2
> 
3
> if (*pos) *pos--;
4
> pos++;
5
> if (*pos) *pos--;
6
> pos++;
7
> if (*pos) *pos--;
8
> pos++;
9
> if (*pos) *pos--;
10
> pos++;
11
> ...
>
> Ansonste müsste man das noch in ASM ansehen und dann ggf. umschreiben.
>
> Wie eingangs gesagt, überlege, ob du das mit den 65 Variablen nicht
> anders machst und ggf. zusammenfasst.


Optimieren kann ich leider nichts mehr, weil alle Prozesse (1 Prozess 
braucht bis zu 2 Timer) theoretisch parallel ablaufen bzw. angestoßen 
werden können. Ich hatte noch 6 Timer drin das habe ich anderes gelöst, 
die restlichen gehen nur so.

An deine Idee habe ich auch schon gedacht, aber für 65 Variablen das 
wird ja ein Tipp-Spaß ;) Super...! Deswegen habe ich hier nachgefragt, 
das wird aber mein Plan J.

von Horst (Gast)


Lesenswert?

Schau doch mal in den erzeugten ASM-Code, was der Compiler da macht. Zum 
Beispiel ob und wie er die if-Anweisung optimiert und wie er das 
Dekrement macht.

In ASM wären das

2x lds
2x subs
1x brmi
2x sts

Sind 7 oder 6 Takte, bei 8MHz also etwa 1µsec pro Wert, ingesamt also 
65µsec. Wird schon knapp für einen 20-kHz-Timer. ;-) Wird also in C 
nicht besser sein.

Den 20kHz-Takt nicht per Timer-Interrupt, sondern per Waveform-Generator 
oder PWM erzeugen?

Die Timer-Routine aufteilen, statt 1msec alle 1/8 msec aufrufen, einen 
counter inc, beim ersten Aufruf alle Werte von timer[0..7] dec, beim 
zweiten alle timer[8..15] usw. Gibt halt einen zeitlichen Versatz 
zwischen den einzelnen Werten.

In der Timer-Routine nur ein Flag setzen. Im Hauptprogramm wenn das Flag 
gesetzt ist die Timer[0..64] runterzählen, danach das Flag wieder 
löschen. Die andere Timerroutine darf da ruhig reingrätschen, solange 
sie nicht zu lange dauert und das Runterzählen innerhalb einer 
Millisekunde beendet ist.

Ich würde das letzte machen.

von MOBA 2. (Gast)


Lesenswert?

Horst schrieb:
> Schau doch mal in den erzeugten ASM-Code, was der Compiler da macht. Zum
> Beispiel ob und wie er die if-Anweisung optimiert und wie er das
> Dekrement macht.
>
> In ASM wären das
>
> 2x lds
> 2x subs
> 1x brmi
> 2x sts
>
> Sind 7 oder 6 Takte, bei 8MHz also etwa 1µsec pro Wert, ingesamt also
> 65µsec. Wird schon knapp für einen 20-kHz-Timer. ;-) Wird also in C
> nicht besser sein.
>
> Den 20kHz-Takt nicht per Timer-Interrupt, sondern per Waveform-Generator
> oder PWM erzeugen?
>
> Die Timer-Routine aufteilen, statt 1msec alle 1/8 msec aufrufen, einen
> counter inc, beim ersten Aufruf alle Werte von timer[0..7] dec, beim
> zweiten alle timer[8..15] usw. Gibt halt einen zeitlichen Versatz
> zwischen den einzelnen Werten.
Jap das hatte ich schon, dass ist leider nicht erträglich.

> In der Timer-Routine nur ein Flag setzen. Im Hauptprogramm wenn das Flag
> gesetzt ist die Timer[0..64] runterzählen, danach das Flag wieder
> löschen. Die andere Timerroutine darf da ruhig reingrätschen, solange
> sie nicht zu lange dauert und das Runterzählen innerhalb einer
> Millisekunde beendet ist.

> Ich würde das letzte machen.
Das hatte Kevin vorgeschlagen. Sowas ärgert mich immer das ich nicht 
selbst drauf gekommen bin - ist immer wenn man so kompliziert denkt....
Aber das klappt bis jetzt super.

Die CPU läuft mit 20 Mhz.

An einem Pin liegt ein 20khz Signal was detectiert werden muss.
20x Soft-PWM (mit Fading)
20x Servo
1x Signalerzeugung also 1x Takt + 1x Daten)
4x HW-PWM
2x ADC was umgeschaltet werden muss (da der Mega644 ja nur eine Unit 
hat....)
1x PID-Regler

Läuft aber jetzt mit Kevins Idee. Ich lasse das mal laufen und schaue 
mal, bleibt wohl unter 20 vom Wert der Counter-Variable.

von MOBA 2. (Gast)


Lesenswert?

Kevin schrieb:
> Also wenn 255 schon nicht ausreichen, wird es immer weiter steigen...
> Die Mainloop darf nicht zu lahmarschig werden.

Reicht! Super Idee! Danke dir.

Je nach Auslastung geht es wohl, was ich auf die schnelle getestet 
habe), hoch bis ca. 50.

von adönis (Gast)


Lesenswert?

timer als verkettete sortierte liste.
absolutzeit zum auslösen speichern.
bei isr absolutzeit hochzählen und nur erstes Element vergleichen.
wenn Zeit erreicht Liste aktualisieren und Zielzeitstempel neu setzen.

kostspielig ist dabei das einfügen an die richtige Position.

von A. S. (Gast)


Lesenswert?

Also eigentlich macht man Timer doch lokal und bezieht sie auf einen 
systicker.

1 Bit ob gestartet, 16bit für den  Vergleichswert.

Beim Starten startbit = 1, Vergleichswert=systicker+Laufzeit.

Beim Test:
Wenn startbit UND systicker-vergleichswert>0 DANN startbit löschen und 
tun was zu tun ist.

Wichtig ist dabei nur, dass die Auswertung ausschließlich über 
Differenzen mit Überlauf geht. Dann gehen auch 15bit Vergleichswert und 
das startbit passt mit rein

von Walter T. (nicolas)


Lesenswert?

Bist Du Dir sicher, daß nicht eine einzige globale, freilaufende, 
vorzeichenlose Zählvariable ausreicht?

Das Timing wird dann mit Merker-Variablen durch vorzeichenrichtige 
Differenzbildung gewonnen. Für ein primitives Delay sähe das ungefähr so 
aus:
1
uint16_t glcounter = 0;
2
3
void ISR(void)
4
{
5
    glcounter++;
6
}
7
8
9
bool isTimeout_thisFunctionNeedsATimer(int16_t time)
10
{
11
    static uint16_t savecounter = 0;
12
    int16_t diff = glcounter - savecounter;
13
    if( diff > time )
14
    {
15
        savecounter = glcounter;
16
        return true;
17
    }
18
    else
19
    {
20
        return false;
21
    }    
22
}


P.S.: Hat sich mit dem Vorgängerposting gedoppelt.

: Bearbeitet durch User
von georg (Gast)


Lesenswert?

MOBA 2. schrieb:
> Jetzt zähle ich dort die Variablen runter, im Programm kann ich dann
> einen Wert (bspw. 500 in eine Variable) eintragen, prüfen

Eben: irgendwann irgendwo in den Tiefen deiner Software musst du ja 
prüfen ob der Software-Timer X abgelaufen ist, d.h. ob Tx == 0. Da 
kannst du genausogut  einen einzigen durchlaufenden Timer verwenden und 
jeweils die Zielzeit speichern, also musst du prüfen ob Timer = 
Endzeitpunkt. Das ist auch nicht aufwendiger und du musst nur 1 Timer 
incrementieren statt 65 decrementieren.

Georg

von W.P. K. (elektronik24)


Lesenswert?

georg schrieb:
> Eben: irgendwann irgendwo in den Tiefen deiner Software musst du ja
> prüfen ob der Software-Timer X abgelaufen ist, d.h. ob Tx == 0. Da
> kannst du genausogut  einen einzigen durchlaufenden Timer verwenden und
> jeweils die Zielzeit speichern, also musst du prüfen ob Timer =
> Endzeitpunkt. Das ist auch nicht aufwendiger und du musst nur 1 Timer
> incrementieren statt 65 decrementieren.


Genauso mache ich es auch bei Aufgaben die viele Zeitstempel benötigen! 
Interrupt alle  ms aufrufen, eine Variable zählt die ms von 0 bis 999 
aufwärts und springt dann wieder auf 0 und zählt die Sekunden des Tages 
aufwärts also von 0 bis 84399 ... oder z.B. nur alle 10 Sekunden (meist 
reicht diese Genauigkeit aus) dann von 0 bis 8439). Damit lassen sich 
dann alle Zeitstempel des Tages im gewünschten Raster leicht setzen und 
abfragen.
Das ist imho wesentlich eleganter.

von A. S. (Gast)


Lesenswert?

W.P. K. schrieb:
> Variable zählt die ms von 0 bis 999 aufwärts und springt dann wieder auf
> 0 und zählt die Sekunden des Tages aufwärts also von 0 bis 84399 .

Bis auf ganz wenige Spezialfälle ist das Murx.

Ein 16 oder 32 Bit Counter ist, sobald man das Prinzip verstanden hat, 
wesentlich einfacher, schneller, robuster, kleiner. Einfach weil der 
Zugriff meist atomar und der Überlauf egal ist.

Ich kriege jedesmal die Krise, wenn ich irgendwo ein explizites 
Überlaufhändling sehe. Und nur sehr selten funktioniert es.

von Peter D. (peda)


Lesenswert?

MOBA 2. schrieb:
> Das dauert gräßlich lange, so lange das das ~20kHz Signal am Portpin
> verlangsamt wird,

Timerinterrupts gehören zu den wenigen, die ihr Flag beim Eintritt 
löschen. Daher kannst Du ihn als ISR_NOBLOCK definieren und somit andere 
Interrupts zulassen. Die 65 Berechnungen haben dann 1ms Zeit.

von Jacko (Gast)


Lesenswert?

Wie machst du denn "die 20 kHz am Port-Pin"???
Mit Delay, oder nop-Ketten??? MURKS!

Jeder 8-Pin-µC hat >= 2 Timer - Nimm einen für die 20 kHz, die er
nach Voreinstellung per Hardware unbeirrt erzeugt.

Da du wohl einen 4-Pin-µC (?) mit nur 1 Timer hast, erzähl doch
mal, wie da über 2 Drähte zeitnah 65 verschiedene Aktionen zur
Umwelt geliefert werden? - Oder von der Umwelt zum µC...

Schwer vorstellbar.

von MOBA 2. (Gast)


Lesenswert?

Jacko schrieb:
> Wie machst du denn "die 20 kHz am Port-Pin"???
> Mit Delay, oder nop-Ketten??? MURKS!
>
> Jeder 8-Pin-µC hat >= 2 Timer - Nimm einen für die 20 kHz, die er
> nach Voreinstellung per Hardware unbeirrt erzeugt.
>
> Da du wohl einen 4-Pin-µC (?) mit nur 1 Timer hast, erzähl doch
> mal, wie da über 2 Drähte zeitnah 65 verschiedene Aktionen zur
> Umwelt geliefert werden? - Oder von der Umwelt zum µC...
>
> Schwer vorstellbar.

Wie denkst du denn das ich das wohl mache wenn "parallel" dazu bis zu 20 
Servos laufen, 20x Soft-PWM und eine ganze PID-Reglung samt Steuerung 
und ein 5 kHz Signal noch erzeugt werden muss ;). Es steht auch oben, 
Mega644P @ 20Mhz.


Es handelt sich bei dem erzeugten Singal (5 kHz um 2 Leitungen, Takt und 
Daten). Das andere Signal (20 kHz) ändert seine Pulsbreite, 0 hat eine 
andere Länge als die 1. Das muss übrigends eingelesen werden nicht 
ausgegeben.

Die Timer (65) sind für interne Abläufe (Fading für die PWMs, Servos, 
etc.. etc...

Beitrag #5265095 wurde vom Autor gelöscht.
von Horst (Gast)


Lesenswert?

A. K. schrieb im Beitrag #5265095:
> indem die Aktivitäten nach Restzeit sortiert sind.

Wenn das Variablen für Dimmerwerte sind, können die ja geändert werden. 
Dann müsstest Du die jedes Mal umsortieren.

Kommt halt drauf an, wie und wann diese Variablen modifiziert werden.

von MOBA 2. (Gast)


Lesenswert?

Horst schrieb:
> A. K. schrieb im Beitrag #5265095:
>> indem die Aktivitäten nach Restzeit sortiert sind.
>
> Wenn das Variablen für Dimmerwerte sind, können die ja geändert werden.
> Dann müsstest Du die jedes Mal umsortieren.
>
> Kommt halt drauf an, wie und wann diese Variablen modifiziert werden.

Unvorhersehbar, das äußere Signal gibt an, was angeschaltet, 
abgeschaltet wird, Impulsanschaltung bspw. etc... Dafür sind die Timer.

Aber wie gesagt, es gibt viele Wege zum Ziel, das jetzige von Kevin 
läuft sehr gut.

Die Werte für PWM sind eher die Zeiten für das Fading nicht die 
Dimmwerte als sich.

von Codix (Gast)


Lesenswert?

Nur eine Anregung zu diesem Thema:
Falls Du kein EEPROM Zugriff hast,
dann könntest Du das EEAR Register verwenden. Registerzugriffe sind 
einen Tacken schneller.
In einem Projekt habe ich die main mit der ISR wie folgt gekoppelt.
1
ISR(TIMER1_CAPT_vect){
2
3
if ( EEAR == 0){// Signaling only if processing in main is finished!
4
          // main clears that when all is done.
5
....
6
}
7
}
8
int main(void)
9
{
10
11
 if ( EEAR ){ // Timer signal ?
12
 .
13
 .
14
 .
15
 .
16
 EEAR = 0;
17
}
18
}

von Jacko (Gast)


Lesenswert?

Ja nun - da hast du doch 3 Zähler!

20 kHz mit variablem Duty-Cycle würde ich mit einem PWM-fähigen
Timer per CTC (Frequenz) und Compare-Match (Duty-Cycle) erzeugen.
- Wenn der nicht alle 25, oder 50 µs umprogrammiert werden muss,
ist sein Ablauf doch grundsätzlich von der Länge irgendwelcher
anderen IRS-Routinen unabhängig.

Willst du die 20 kHz frequenz/phasen-modulieren???
Das hab ich auch schon gemacht - (allerdings nur mit 19,6 kHz):
Da hebt man in anderen IRS-en nach den zeitlich allerwichtigsten
Sachen halt die Sperre für andere Interrupts (ASM: SEI-Befehl)
auf.

Allerdings ist die Mod-Frequenz meist deutlich kleiner, als die
Trägerfrequenz (20 kHz).

Wo wird es denn nun wirklich eng?

von Jacko (Gast)


Lesenswert?

Habe noch mal nachgelesen - ÜBERWACHST du die 20 kHz samt
Duty-Cycle?

Da gilt aber Ähnliches: Die Hardware-Erfassung muss mit
Timer-Capture erfolgen - die zeitgerechte Bearbeitung durch
frühzeitige Freigabe anderer Interrupts.

Auch keine Hexerei.

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.