Forum: Compiler & IDEs Programmspringer für Unterprogramme "schrumpfen"


von Marian S. (sja-m)


Lesenswert?

Hallo GCC-Gurus,

ich arbeite nun an meinem ersten C programm für einen Tiny13.
Dieser soll verschiedene Aufgaben erledigen (LED-Blinkfolgen), jedoch 
ohne jedes mal neu programmiert zu werden.

Bisher mache ich das so, dass ich bei einem Tastendruck eine Vatiable um 
1 erhöhe und bei überlauf (also #8 bei 7 Programmen) auf #1 zurücksetze.
Davor schreibe ich das ins EEPROM, damit beim erneuten Einschalten das 
gewählte Programm ausgeführt wird.

Nun ist der Tiny13 bekanntlich nicht der größte (heißt ja auch Tiny 
;-P).

=> wie kann ich das schrumpfen?

Ich freue mich schon auf Eure Anregungen.

Gruß
Marian
1
void progjump(void)
2
{
3
  while ( prog == 0);          //warten bis Programm ausgewählt (wenn vorasuwahl dann weglassen wegen speicherplatz)
4
  if (prog > 7)              //wenn Programmanzahl überschritten
5
  {
6
    prog = 1;              //fange wieder bei Programm #1 an
7
  }
8
  if (prog == 1)            //wenn #1 selektiert
9
  {
10
    prog1();              //gehe zu #1
11
  }
12
  if (prog == 2)            //wenn #2 selektiert
13
  {
14
    prog2();              //gehe zu #2
15
  }
16
  if (prog == 3)            //wenn #3 selektiert
17
  {
18
    prog3();              //gehe zu #3
19
  }
20
  if (prog == 4)            //wenn #4 selektiert
21
  {
22
    prog4();              //gehe zu #4
23
  }
24
  if (prog == 5)            //wenn #5 selektiert
25
  {
26
    prog5();              //gehe zu #5
27
  }
28
  if (prog == 6)            //wenn #6 selektiert
29
  {
30
    prog6();              //gehe zu #6
31
  }
32
  if (prog == 7)            //wenn #7 selektiert
33
  {
34
    prog7();              //gehe zu #7
35
  }
36
}

von nils (Gast)


Lesenswert?

Erstmal switch/case verwenden, ist übersichtlicher und je nach Compiler 
wird das besser optimiert.
Dann auf Assembler umsteigen, genau das lässt sich in C kaum 
einfacher/kleiner machen.

Guck mal wo du sonst noch an Platz sparen kannst, das ist 100%ig erst an 
letzter Stelle dran in deinen Programm ;)

von Marian S. (sja-m)


Lesenswert?

Hallo Nils,

danke für den Tipp ;-)

Hat wie Du gesagt hast nicht sehr viel gebracht (ca. 1,5% 
Speicherplatz), aber dafür ist es viel weniger Tipparbeit und wesentlich 
lesbarer.

Ich hab jetzt nicht nachgemessen aber schneller scheint es auch zu sein.

Die Überlauf-Überprüfung macht an der Stelle des Hochaddierens der 
Variable mehr sinn. Hab ich einfach verschoben.

Ich habs jetzt so gelöst:
1
void progjump(void)
2
{
3
  switch (prog)
4
  {
5
    case 1: prog1();
6
    case 2: prog2();
7
    case 3: prog3();
8
    case 4: prog4();
9
    case 5: prog5();
10
    case 6: prog6();
11
    case 7: prog7();
12
  }
13
}

Gruß
Marian

von Gasst (Gast)


Lesenswert?

Speichere die Programme in einem Array aus Funktionspointern.

von Marian S. (sja-m)


Lesenswert?

Hast Du mir dazu ein Beispiel oder ein Link wo das gut beschrieben ist?

Ich bin Anfänger was C und Programmierung angeht.
Mit Array und Pointern fange ich (noch) nichts an.

Gruß
Marian

von Rolf Magnus (Gast)


Lesenswert?

> Ich habs jetzt so gelöst:
1
void progjump(void)
2
{
3
  switch (prog)
4
  {
5
    case 1: prog1();
6
    case 2: prog2();
7
    case 3: prog3();
8
    case 4: prog4();
9
    case 5: prog5();
10
    case 6: prog6();
11
     case 7: prog7();
12
  }
13
}

Da solltest du nochmal dran arbeiten. Im Fall 1 werden alle Funktionen 
ausgefürt. Hier fehlt jeweils das break.

von Oliver (Gast)


Lesenswert?

>Dann auf Assembler umsteigen, genau das lässt sich in C kaum
>einfacher/kleiner machen.

Nö. Bleib bei C, denn genau das lässt sich in Assembler kaum
einfacher/kleiner machen.

Oliver

von Läubi .. (laeubi) Benutzerseite


Lesenswert?

Außerdem sollte der Case 7 zumindest mit dem default case zusammenfallen 
um das gleiche Verhalten wie oben zu haben + rücksetzen auf 1 fehlt!

von Stefan B. (Jibbed) (Gast)


Lesenswert?

Eventuell Feld mit Funktionszeigern benutzen. Siehe FAQ # 
Funktionszeiger
1
#include <stdio.h>
2
3
// Test
4
void prog1(void) { puts("prog1"); }
5
void prog2(void) { puts("prog2"); }
6
void prog3(void) { puts("prog3"); }
7
void prog4(void) { puts("prog4"); }
8
void prog5(void) { puts("prog5"); }
9
void prog6(void) { puts("prog6"); }
10
void prog7(void) { puts("prog7"); }
11
12
typedef void (*PROGFP)(void);
13
PROGFP progfp[8] = {prog1, prog1, prog2, prog3, prog4, prog5, prog6, prog7};
14
15
int main(int argc, char ** argv)
16
{
17
  unsigned int prog = 42;
18
  if (argc > 1)
19
    sscanf(argv[1], "%d", &prog);
20
  progfp[prog%8]();
21
  return 0;
22
}

von Stefan B. (Jibbed) (Gast)


Lesenswert?

Korrektur:

Das "prog wenn größer 7, dann wieder mit prog=1 anfangen" Verhalten, ist 
bei meinem Beispiel anders. Wenn man sich auf prog0...prog6 einigen 
kann, braucht man nur ein Feld mit 7 Elementen und ein %7.

von Gebhard R. (Firma: Raich Gerätebau & Entwicklung) (geb)


Lesenswert?

@Stefan

Find ich keine gute Idee. scanf ist eine längere Funktion, 
progfp[prog%8](); beinhaltet % operator, ist auch nicht gerade schnell.
Weiters wird der Code nicht gerade verständlicher. Switch ist hier 
sicher die erste Wahl.

Grüße

von Stefan B. (fluxflux-sl) (Gast)


Lesenswert?

sscanf (und puts) braucht man nicht zu benutzen. Das ist bloß der 
Testteil des Beispiels. Im Beispiel werden die Funktionen aus der 
Standard-IO-Bibliothek benutzt, damit prog (irgend)eine Zahl bekommt 
und man das Beispiel ausprobieren kann.

Man kann irgendeine andere Methode benutzen, um prog einen Testwert 
zuzuweisen. Und statt der komfortablen puts-Ausgabe kann man z.B. LEDs 
schalten.

Das Testbeispiel oben habe ich fürs Ausprobieren auf einem PC 
geschrieben. Da darf man ruhig komfortabel arbeiten. Auf den Attiny13 
mit Spartanischer Ein- und Ausgabe kann man anschliessend immer noch 
wechseln.

von nils (Gast)


Lesenswert?

>Nö. Bleib bei C, denn genau das lässt sich in Assembler kaum
>einfacher/kleiner machen.
AVR Assembler ist mir nicht geläufig, aber in 8051 ist das mit nem 
Register und einer hand voll JZ/JNZ/... und Bitoperationen gemacht...
Ein AVR-Compiler-Output zum vergleichen wäre schön, dann tipp ich das 
mal auf 8051 um oder einer der AVR-Freaks hier macht das mal kurz von 
Hand in AVR-Assembler.

von Peter D. (peda)


Lesenswert?

Marian S. schrieb:
> ich arbeite nun an meinem ersten C programm für einen Tiny13.
> Dieser soll verschiedene Aufgaben erledigen (LED-Blinkfolgen)

Dann brauchst Du nicht verschiedene Programme, sondern nur eins. Und 
dieses erzeugt die Blinkmuster aus einer Tabelle im Flash.

Wenn es ein Morsecode sein soll, dann enthält die Tabelle den Text und 
eine weitere Tabelle den Morsecode pro Zeichen.


Peter

von Marian S. (sja-m)


Lesenswert?

Hallo,

erstmal Danke für die Anregungen.
Leider verstehe ich davon nicht alles mit meinem Wissensstand.

@Rolf:
Danke für den Hinweis, in meinem C Buch steht das auch so. In meinem 
Fall jedoch führt jede dadurch aufgerufene Routine eine Endlosschleife 
durch bis der Strom weg ist.
Da ich also nie mehr in diese Auswahlroutine zurückspringe stört das 
nicht.

@Läubi:
Die Überlauf-Überprüfung mache ich jetzt im "Setupmodus", wo die 
Prog-Variable geschrieben wird. Ist dort besser eingegliedert.
Den Case7 ändere ich auf default, danke.

@Stefan:
Dein Beispiel klingt interessant, jedoch verstehe ich es nicht ganz und 
bin mir nicht sicher ob es das Richtige für mich ist.

@Peter:
Es ist schon ein bisschen komplexer als nur Blinken ;-)
Ich werte eine PWM aus und der AVR soll in Abhänigkeit dazu je nach 
"Programm" (oder besser gesagt Modus) die Leds unterschiedlich 
ansteuern.


Während ich hier schreibe kommt mir noch ne andere Idee:

Bei meinen Programmen handelt es sich um "Mini-Programme".
Es läuft im Prinzip so ab:

1. Watchdog aus
2. Initialisierung //also DDRB,PORTB,IRQ,Timer...
3. Wenn Taster grdrückt ->Setup-Routine //verändert prog, eepom und 
endet im reset per watchdog
4. eeprom in prog-variable schreiben
5. entsprechendes Programm aufrufen
6. Programm in endlosschleife ausführen

Macht es Sinn diese Mini-Programme in jeweils eine Switch-Case-Funktion 
zu schreiben?

z.B. So:
1
void progjump(void)
2
{
3
  switch (prog)
4
  {
5
    case 1:
6
    {
7
      MiniProgramm 1 mit PiPaPo und etc. ...//Funktion in Endlosschleife
8
    };
9
    case 2:
10
    {
11
      MiniProgramm 2 mit PiPaPo und etc. ...//Funktion in Endlosschleife
12
    };
13
    case 3:
14
    {
15
      MiniProgramm 3 mit PiPaPo und etc. ...//Funktion in Endlosschleife
16
    };
17
   ...
18
  }
19
}

Es leidet zwar massiv die Lesbarkeit aber von meinem Verständnis her 
spare ich mir die ganzen Sprungmarken und damit hoffentlich ein paar 
Byte.

Gruß und Danke
Marian

von Oliver (Gast)


Lesenswert?

Die Angelsachsen drücken das so aus:
"Premature optimization is the root of all evil"
"Never optimize without profiling first"

Also: Lass den Quatsch. Schreib ein sauberes, lesbares Programm, und 
wenn du dann feststellst, daß es nicht in den Programmspeicher passt, 
versuche zu verstehen, welche Programmteile unnötig Programmspeicher 
benötigen, und optimiere zielgerichtet.

Bei deinen sieben Funktionsaufrufe ist es zwar eventuell möglich, 7-10 
Byte Programmspeicher einzusparen, aber im Rest des Programms dürfte 
weit mehr Potential schlummern.

Oliver

von Tom M. (tomm) Benutzerseite


Lesenswert?

Marian S. schrieb:
> Es leidet zwar massiv die Lesbarkeit aber von meinem Verständnis her
> spare ich mir die ganzen Sprungmarken und damit hoffentlich ein paar
> Byte.

Vergiss es, damit schiesst du dir leider ins eigene Bein. Der Compiler 
wird die Funktionen ggf. selbständig "inline" einbauen, dadurch gewinnst 
du nix.

Wenn du die Funktion wirklich inline möchtest, kannst du sie auch als 
"inline" deklarieren. Ich überlasse das aber meistens dem Compiler, die 
optimale Variante (gcc -O) rauszutüfteln.

Doch wie meine Vorposter schon gesagt haben, generier dir erstmal ein 
Assembler Listing und prüfe, wo die grossen Platzfresser sind.

Happy Hacking! :-)

von Karl H. (kbuchegg)


Lesenswert?

Marian S. schrieb:

> Es läuft im Prinzip so ab:
>
> 1. Watchdog aus
> 2. Initialisierung //also DDRB,PORTB,IRQ,Timer...
> 3. Wenn Taster grdrückt ->Setup-Routine //verändert prog, eepom und
> endet im reset per watchdog
> 4. eeprom in prog-variable schreiben
> 5. entsprechendes Programm aufrufen
> 6. Programm in endlosschleife ausführen

Da gibt es doch noch einiges an Potential.
Zb. ist es zielich sinnlos, jedem einzelnen deiner "Programme" eine 
Endlosschleife zu spendieren. Diese n-Endlossschleifen kannst du auch in 
eine einzige Endlosschleife in main() herausziehen. Das bringt dir schon 
mehr, als das ganze Gepfriemel in der Abfragestruktur.

Je nachdem was deine 'Programme' tatsächlich tun, gibt es wahrscheinlich 
auch eine Möglichkeit mehrere dieser 'Programme' zusammenzulegen und die 
Unterschiede durch verschiedene Werte in Variablen auszudrücken.

> Es ist schon ein bisschen komplexer als nur Blinken ;-)
> Ich werte eine PWM aus und der AVR soll in Abhänigkeit dazu
> je nach "Programm" (oder besser gesagt Modus) die Leds
> unterschiedlich ansteuern.

Da von dieser Auswertung in deinem Code-Schnipsel nichts zu sehen ist, 
gehe ich davon aus, dass entweder
*) (Gott bewahre) jedes 'Programm' seine eigene Ausertung hat
*) oder jedes 'Programm' die Auswertung aufruft
Auch hier wieder: zieht man diesen gemeinsamen Teil aus jedem Programm 
raus und verlagert ihn ins main() so spart auch das schon wieder Platz.

-> Bei Neueinsteigern findet sich eigentlich fast immer etwas, das es 
einem erlaubt, sonnvoll Speicher zu sparen und sei es eine int-Rechnung, 
die genausogut auch in unsigned char gemacht werden könnte. Da musst du 
nicht auf Biegen und Brechen wilde Konstruktionen machen. Benutze erst 
mal das Potential das du hast.

Speicher sparen _/_ Laufzeit erhöhen _/_ oder generell einfach nur 
'Optimieren' beginnt immer damit, dass man sich die globale Struktur des 
Programms ansieht und dort ansetzt und nicht indem man einzelne 
Anweisungen durch andere ersetzt.

von Marian S. (sja-m)


Lesenswert?

@Tom

Ok, dann lasse ichs besser :-)

@Karl heinz Buchegger

Da gibts bestimmt sogar ne ganze Menge Potential :-P
Hab z.B. rausgefunden, dass:
1
OCR0A = 0xFF
2
OCR0B = 0xFF
manchmal ganze 2 Byte mehr braucht als:
1
OCR0A = 0xFF
2
OCR0B = OCR0A
Komisch ist aber, dass das nur manchmal der Fall ist und manchmal sogar 
6 Byte größer ausfällt. Da verstehe einer den Compiler ;-P

Meinen Watchdog-Reset im Setup-Modus habe ich durch ein einfaches 
"return;" (zurück zur main) ersetzt. Da stand ich voll aufm Schlauch 
mit'm Hündchen.

Den Punkt mit den vielen Endlosschleifen werde ich gerne mal umsetzen.
Danke für den Tipp.
Wenn ich nur eine Schleife in die Main setze muss ich vermutlich die 
"break" in meine "Switch/case"-Geschichte einbauen damit der AVR nicht 
alle nachfolgenden Programme "durchrasselt"... richtig?

Und weils gerade so schön ist Fragen zu stellen:
Bisher schreibe ich die LED-Zustände direkt in die Register OCR0A und 
OCR0B.

Bringt es was die Zustände erst in 2 Variablen zu schreiben und vor dem 
nächsten Schleifendurchlauf aus der Variable in die Register zu 
schieben?

Es kämen dann ja 2 Variablen dazu, und die "Variable in Register 
schiebe"-Funktion.
Die Register würden ja nur durch die Variablennamen ersetzt werden.

Gruß und Danke
Marian

von Mark B. (markbrandis)


Lesenswert?

Karl heinz Buchegger schrieb:
> Speicher sparen _/_ Laufzeit erhöhen _/_ oder generell einfach nur
> 'Optimieren'

Laufzeit erhöhen? :-)

von Karl H. (kbuchegg)


Lesenswert?

Mark Brandis schrieb:
> Karl heinz Buchegger schrieb:
>> Speicher sparen _/_ Laufzeit erhöhen _/_ oder generell einfach nur
>> 'Optimieren'
>
> Laufzeit erhöhen? :-)

LOL.
Tippfehler

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


Lesenswert?

> 5. entsprechendes Programm aufrufen
Was kann denn an diesen "Programmen" so unterschiedlich sein, wenn sie 
doch alle die selbe Hardware benutzen?

In einer Waschmaschine gibt es ja auch mehrere "Programme":  Wolle, 
Kochwäsche, Synthetik...
Diese "Programme" haben aber nichts mit dem "Steuerprogramm" zu tun, das 
im uC die "Programmschritte" Wasser einlassen, Heizen, Trommel 
schnell/langsam/rechts/links... abarbeitet.

>>>>>> Dieser soll verschiedene Aufgaben erledigen (LED-Blinkfolgen)
Dann sind die Blinkfolgen deine "Programme", die Steuersoftware muß 
eigentlich nur eine Tabelle abarbeiten, wo drinsteht, wann welche LED an 
ist.

von Marian S. (sja-m)


Lesenswert?

Laufzeit erhöhen?

-> Na Klar! z.B. bei batteriebetriebenen Anwendungen :-P

Die Programme sind nicht soo unterschiedlich.
Das eigentliche Programm besteht ja aus main, dem Programmspringer, den 
LED-Ansteuer-Programmen und einer IRQ-Routine.

Die IRQ-Routine wertet mein Signal aus und stellt es dem Rest in einer 
uint8_t variable zur Verfügung.

Der AVR klappert quasi nach dem Einschalten nur ein ausgewähltes der 
Programme ab und setzt die LEDs entsprechend der Variable.

Ich hab das jetzt schon auf ca. 940 Byte geschrumpft und konnte noch ein 
8. Programm (oder Modus / Funktion) implementieren.

Das beste ist aber:
Es funktioniert so wie ich es will! :D

Jetzt nur noch ein bisschen feintunen und es ist "Perfekt".

Danke für Eure Hilfe an dieser Stelle.
Ich hoffe dieser Thread ist auch für andere Anfänger hilfreich.


Gruß Marian

von Karl H. (kbuchegg)


Lesenswert?

Marian S. schrieb:

> Die Programme sind nicht soo unterschiedlich.

Nenn deine 'Programme' besser 'Funktionen'.
Ein Programm ist üblicherweise alles zusammen.
Was du machst ist: Du wählst eine Lichtshow aus mehreren 
unterschiedlichen aus. Für jede Lichtshow ist eine Funktion zuständig.

Deine Nomenklatur ist da etwas in der Grauzone.
Einen Videorekorder 'programmiert' man auch nicht in dem Sinne, in dem 
hier im Forum der Begriff 'Programm' verstanden wird. Der Begriff 'Daten 
eingeben' trifft es aus unserer Sicht besser, denn an der eigentlichen 
Programmierung des Videorecorders ändert man ja nichts. Man stellt nur 
Werte zur Verfügung.

> Ich hab das jetzt schon auf ca. 940 Byte geschrumpft und konnte noch ein
> 8. Programm (oder Modus / Funktion) implementieren.

Die Frage ist an dieser Stelle, ob es nicht möglich ist, all diese 
Lightshows nur mit einem einzigen Verfahren abzuhandeln und die 
tatsächliche zu erzielende 'Show' dadurch zu erreichen, indem man 
unterschiedliche Werte vorgibt.

zb. Schnelles und langsames Blinken unterscheiden sich ja nur in 2 
Zahlenwerten (Dauer der Hellphase, Dauer der Dunkelphase). Der 
eigentliche 'Algorithmus' ist ja in beiden Fällen derselbe. Und da das 
so ist kann man dann mit genau demselben Verfahren und anderen 
Zahlenwerten ebenfalls erreichen: Langes Blinken mit kurzen Pausen, 
Kurzes Blinken mit langen Pausen. Mit einem leicht abgeändertem 
Verfahren könnte man auch Doppelblinken etc. leicht erreichen. Die 
C-Funktion bleibt immer die gleiche, nur die Zahlenwerte ändern sich. 
Und damit steigt logischerweise auch der Speicherbedarf im wesenlichen 
nur um diese zusätzlichen Zahlenwerte.

Die Frage, die sich dabei stellt, lautet zb:
Ist es möglich eine Art Anweisungstabelle der auszuführenden Show 
anzulegen, die von einer Abarbeitungsfunktion ausgeführt wird. 
Unterschiedliche Shows sind dann einfach nur unterschiedliche Tabellen 
und das Mäuseklavier (oder was auch immer du zur Anwahl der Show 
benutzt) schiebt einfach nur eine andere Tabelle in die 
Auswertefunktion.

von Marian S. (sja-m)


Lesenswert?

Ok, da habe ich mich misverständlich ausgedrückt.

Mit Programm meinte ich ein vom Benutzer auswählbares 
"Verhaltensmuster".
Modus dürfte das richtige Wort dafür sein... hoffe ich.
Mir ist klar, dass man unter Programm das "Ganze" versteht.

Die Modi wechsle ich beim Einschalten der Schaltung nach der 
Initialisierung.
Wenn ein Taster gedrückt ist, geht der AVR in den Setupmodus.
Hier blinkt eine LED im Sekundentakt auf (in einer Unterroutine die auch 
von manchen Modi benutzt wird) und wechselt zum jeweils nächsten Modus.
Wird der Taster losgelassen, wird die Modus-Nummer im EEPROM gespeichert 
(fürs nächste Einschalten) und der AVR kehrt zur main zurück und führt 
den Modus über den "Programmspringer" aus.

Die Schleifen habe ich jetzt alle aufgelöst und das über die main 
gemacht. Das hat nochmal einige Bytes eingebracht.
-> Danke nochmal für den Tipp

Ich verstehe nicht genau, wie Du das mit der Tabelle meinst.
Bei mir handelt es sich nicht um feste Blinkfolgen.
Ich bin immer abhängig von dem Signal, das wird entsprechend dem Modus 
analysiert und dann erst über die LEDs ausgegeben.

Ein oder Zwei Routinen (Modi) kann ich noch zu einer durch Einbau einer 
zusätzlichen Abfrage zusammenlegen, da die die selbe Analyse vornehmen, 
jedoch eine der LEDs invertiert ausgegeben wird.

Mal schauen, ich finde jedes mal was Neues ;-P
Macht aber auch Spass wenn man sieht wie das Prgramm immer weiter 
schrumpft und gleichzeitig immer mehr kann :D

von Rene B. (themason) Benutzerseite


Lesenswert?

>Ich verstehe nicht genau, wie Du das mit der Tabelle meinst.
>Bei mir handelt es sich nicht um feste Blinkfolgen.
>Ich bin immer abhängig von dem Signal, das wird entsprechend dem Modus
>analysiert und dann erst über die LEDs ausgegeben.

aber letztenendes sind es blinkabfolgen die aufgerufen bzw ausgeführt 
werden. und da hast du wenn du eine tabelle verwendest noch richtig 
richtig einsparpotential.
mit tabelle ist gemeint das du eine abfolge von bytes (byte-array im 
flash z.b.) hast die eine bestimmte struktur haben. dabei sagt das erste 
byte z.b. "led an/led aus/blinkfolge ende" und ein (oder zwei) weitere 
bytes bestimmen wie lang die led an oder aus ist bevor der nächste 
schritt ausgeführt wird. was du selbst dann bei einem "blinkfolge ende" 
machst, ob du die folge dann wiederholst oder wieder in dein setup 
programm springst bleibt dabei dir überlassen.
der vorteil ist du hast nur ein einziges unterprogramm das sich um das 
abarbeiten des blinkens kümmert, und diesem unterprogramm übergibts du 
einfach einen zeiger auf deine blinkabfolge (also auf das erste byte der 
tabelle in der die blinkabfolge steht die du blinken lassen willst).
den eingesparten prorgammcode für deine anderen programme steht dir 
unmittelbar für blinkabfolgen zur verfügung.

von Marian S. (sja-m)


Lesenswert?

Danke Rene für Deine Erklärung.

Ja, bei verschiedenen Blinkfolgen, oder auch z.B. Morsecodes (Hat erst 
jetzt Klick gemacht) ist so eine Tabelle sehr praktisch.

In meinem Fall schalte ich die LEDs nur ein, oder aus. Mal eine, mal 
zwei.
Es werden in dem Sinne keine sich wiederholenden Blinkfolgen ausgegeben.

Ich denke es macht keinen Unterschied, ob ich die Register für die LEDs 
direkt schreibe, oder erst die Werte einer Variablen übergebe und in der 
main-Schleife die Variablen in die Register schiebe.

Der meiste Code steckt bei mir in der Auswertung des Signals.
Z.B:
1
void prog1(void)
2
{
3
  if (Signal > 80)
4
  {
5
    OCR0A = (0xFF - OCR0A);
6
    OCR0B = OCR0A;
7
    while(Signal > 75);
8
  }
9
}

Mehr an LED-Operationen habe ich nicht. Es geht immer nur um die 
Signalauswertung. Diese behsteht eigentlich nur aus if, else und oder 
while. Mehr habe ich da nicht.

Besser mal anders gefragt:
Was benötigt weniger Speicher, einen Wert in eine Variable zu schreiben 
oder direkt in ein Ausgaberegister?

Was ich bisher von asm mitbekommen habe ist, dass ich einen Wert erst in 
ein Arbeitsregister (z.B. ldi r16, wert *) schreiben muss und dieses 
dann erst auf dem Port ausgeben kann (z.B. out portb, r16 *). Also sind 
dazu 2 Schritte nötig.

Benötigt eine Variable auch 2 Schritte, oder reicht da Einer?

* [Ich erhebe keinen Anspruch auf die richtige Schreibweise der 
Beispiele. Bin nämlich kein ASM-Mensch].

Gruß
Marian

von Karl H. (kbuchegg)


Lesenswert?

Marian S. schrieb:

> Ja, bei verschiedenen Blinkfolgen, oder auch z.B. Morsecodes (Hat erst
> jetzt Klick gemacht) ist so eine Tabelle sehr praktisch.
>
> In meinem Fall schalte ich die LEDs nur ein, oder aus. Mal eine, mal
> zwei.
> Es werden in dem Sinne keine sich wiederholenden Blinkfolgen ausgegeben.

Es kommt eben immer auch darauf an, was du tatsächlich machst. Zb war ja 
im Originalbeitrag von einem Signal noch keine Rede.

Dort klang alles noch nach (im Prinzip)
Eine Lichterkette, die 5 unterschiedliche Blink'programme' hat und 
nacheinander alle abarbeiten soll.
(Hab letzte Woche genau so eine gekauft und ins Fenster gepappt)


> Besser mal anders gefragt:
> Was benötigt weniger Speicher, einen Wert in eine Variable zu schreiben
> oder direkt in ein Ausgaberegister?

falsche Frage.

> Was ich bisher von asm mitbekommen habe ist, dass ich einen Wert erst in
> ein Arbeitsregister (z.B. ldi r16, wert *) schreiben muss und dieses
> dann erst auf dem Port ausgeben kann (z.B. out portb, r16 *). Also sind
> dazu 2 Schritte nötig.
>
> Benötigt eine Variable auch 2 Schritte, oder reicht da Einer?

Das ist nicht dein Bier.
Das ist Sache des Compilers, das zu regeln.

Du kümmerst dich darum welchen Wert du in welches Ausgaberegister haben 
willst. Wie der Wert dann auf Assemblereben tatsächlich dort hin kommt, 
ist Sache des Compilers.

von Rene B. (themason) Benutzerseite


Lesenswert?

@marian

ohne deine auswerung zu kennen ... kleiner tipp.
du hast oben meine ich etwas von pwm auswertung geschrieben.
mache diese auswertung (nach möglichkeit ohne (!!) while schleife) im 
interrupt, sodass du im hauptprogramm eine variable abfragen kannst die 
dir anzeigt das ein neuer gültiger wert anliegt und in einer zweiten der 
wert selbst. das hört sich im ersten moment etwas komplizierter an, aber 
erleichtert dir fehlersuche und wartbarkeit enorm.
durch diesen mechanismus hast du gleichzeitig auch eine schnittstelle 
mit der du die blink-routinen selbst überprüfen kannst, oder aber auf 
deine signalauswertung anders reagieren kannst.

von Marian S. (sja-m)


Lesenswert?

Na dann überlassi ich die "Arbeit" mal dem Compiler.

Die Auswertung hab ich mich für nen Anfänger recht geschickt angestellt 
denke ich:
1
//PWM einlesen
2
3
ISR (PCINT0_vect)            //pin-change-irq
4
{
5
  if (PINB & (1<<PB2))          //bei steigender Flanke (=Hi)
6
  {
7
    beg = TCNT0;            //Kopiere Timerwert in beginn-variable
8
  }
9
  else                  //bei fallender Flanke (=Low)
10
  {
11
    if (TCNT0 > beg)          //wenn timer nicht überlaufen
12
  {
13
    pwm = (TCNT0 - beg);      //pwm = Timer jetzt - Anfang
14
    }
15
    else                //wenn Timer zwischendurch überlaufen
16
    {
17
      pwm = ((256 - beg) + TCNT0);  //pwm = Rest vor Überlauf + Timer jetzt
18
    }
19
  }
20
}
Die Variable "beg" wird nur für die Auswertung genutzt, in der Variable 
"pwm" wird dann das Ergebnis der Auswertung den anderen Routinen 
bereitgestellt.

Den Timer kann ich leider nicht reseten, da darüber auch die PWM für die 
LEDs generiert wird. Daher die aufwändige Lösung mit der 
Überlauf-Überprüfung.

Bitte zerreißt mir mein Gedankengut nicht allzu schamlos in der Luft ;-)
Es funktioniert schliesslich:D

Über nen Verbesserungsvorschlag bin ich natürlich dennoch Dankbar.

Gruß
Marian

von Jeff (Gast)


Lesenswert?

Von der sache mit dem funktionspointer array kann ich nur abraten. 
Aufgrund der trennung von RAM und FLASH(Programm) Speicher und der 
verschiedenen wortbreiten (8 vs 16Bit) muss man seeeeeeeehr aufpassen 
wie man so einen array initialisiert, um mit dem avr-gcc nicht ins 
nirvana zu springen. Die Dokumentation zur avr-libc, besonders 
bezueglich der verschiedenen addressierungs-makros ist da sehr zu 
empfehlen.

von Karl H. (kbuchegg)


Lesenswert?

Marian S. schrieb:

> Den Timer kann ich leider nicht reseten, da darüber auch die PWM für die
> LEDs generiert wird. Daher die aufwändige Lösung mit der
> Überlauf-Überprüfung.
>

Noch ein Tip:
Wenn du unsigned rechnest, musst du dir um den Überlauf keine Gedanken 
machen. Einfach Ende minus Anfang rechnen und es kommt in allen Fällen 
das Richtige heraus solange es nur 1 Überlauf gab.

Aber selbst wenn du den Überlauf selbst abprüfen willst:
1
    if (TCNT0 > beg)          //wenn timer nicht überlaufen
2
  {
3
    pwm = (TCNT0 - beg);      //pwm = Timer jetzt - Anfang
4
    }
5
    else                //wenn Timer zwischendurch überlaufen
6
    {
7
      pwm = ((256 - beg) + TCNT0);  //pwm = Rest vor Überlauf + Timer jetzt
8
    }
9
  }

(das hängt jetzt klarerweise von der Prescalereinstellung des Timers ab)
denk immer daran, dass der Timer unabhängig vom restlichen Programm im 
Hintergrund weiterzählt. Läuft der Timer mit Full-Speed, dann kann es 
sein, dass du hier
    if (TCNT0 > beg)          //wenn timer nicht überlaufen
noch keinen Überlauf festgestellt hast .... tick, tick, tick (die 
Abfrage braucht ja auch ein wenig Zeit in der der Timer unter Umständen 
um 1 oder mehr weiterzählt) und du hier
    pwm = (TCNT0 - beg);      //pwm = Timer jetzt - Anfang
tatsächlich einen Überlauf hast, den du vorher nicht festgestellt hast, 
weil der Timerwert gerade an der Grenze war.

-> beim Eintritt in die ISR den Timerwert in einer Variablen sichern und 
dann nur noch mit der Variablen arbeiten, damit du während der 
Funktionsabarbeitung immer mit demselben Timerwert rechnest, selbst wenn 
der Timer im Hintergrund schon weitergezählt hat.

von Karl H. (kbuchegg)


Lesenswert?

Jeff schrieb:
> Von der sache mit dem funktionspointer array kann ich nur abraten.
> Aufgrund der trennung von RAM und FLASH(Programm) Speicher und der
> verschiedenen wortbreiten (8 vs 16Bit) muss man seeeeeeeehr aufpassen
> wie man so einen array initialisiert, um mit dem avr-gcc nicht ins
> nirvana zu springen.

Ähm. Nein.
Da musst du eigentlich auf gar nichts aufpassen.
Funktionspointer definieren.
Funktionsadresse zuweisen
Indirekt aufrufen.

Das geht alles ohne irgendwelche Spezialmakros benutzen zu müssen. In 
Assembler: ja, man muss aufpassen. In C: nein, erledigt alles der 
Compiler.

von Oliver (Gast)


Lesenswert?

>Von der sache mit dem funktionspointer array kann ich nur abraten.
>Aufgrund der trennung von RAM und FLASH(Programm) Speicher und der
>verschiedenen wortbreiten (8 vs 16Bit) muss man seeeeeeeehr aufpassen
>wie man so einen array initialisiert, um mit dem avr-gcc nicht ins
>nirvana zu springen.

Mumpitz.

Ein Pointer ist ein Pointer, ein Array ist ein Array, ein Pointerarray 
ist ein Pointerarray, und ein Funktionspointer ist ein Funktionspointer. 
Ob so ein Funktionspointer 8 bit 64 bit, oder sonstwie breit ist, weiß 
der Compiler. Das braucht einen überhaupt nicht zu interessieren. Du 
definierst ein Array von Funktionspointern, fertig.

Die Trennung von Data- und Programmspeicher ist da, macht aber ebenfalls 
überhaupt nichts. Der Compiler kann das alleine unterscheiden.

Alles nicht kompliziert, sondern auch auf dem AVR zunächst ganz normales 
Standard-C.

Will man die Funktionspointer-Arrays ins Flash legen, kommt das 
AVR-übliche Vorgehen mit Flash-Daten hinzu. Da muß man jetzt 
berücksichtigen, daß so ein Funktionspointer 16 bit hat, und den per 
pgm_read_word() aus dem Speicher holen.

Oliver

von Rene B. (themason) Benutzerseite


Lesenswert?

>>Von der sache mit dem funktionspointer array kann ich nur abraten.
>>Aufgrund der trennung von RAM und FLASH(Programm) Speicher und der
>>verschiedenen wortbreiten (8 vs 16Bit) muss man seeeeeeeehr aufpassen
>>wie man so einen array initialisiert, um mit dem avr-gcc nicht ins
>>nirvana zu springen.

>Mumpitz.

dem schließe ich mich an.
funktionszeiger sind ne feine sache. und man muß nicht mehr darauf 
aufpassen als wenn man nen string aus dem flash ausliest oder andere 
strukturen.
und das was man mit funktionszeigern alles machen kann ist schon ne sehr 
schöne sache.

von Peter D. (peda)


Lesenswert?

Rene Böllhoff schrieb:
> funktionszeiger sind ne feine sache. und man muß nicht mehr darauf
> aufpassen als wenn man nen string aus dem flash ausliest oder andere
> strukturen.
> und das was man mit funktionszeigern alles machen kann ist schon ne sehr
> schöne sache.

Stimmt.

Hier mal ein praktisches Beispiel:

Beitrag "Wartezeiten effektiv (Scheduler)"


Peter

von Marian S. (sja-m)


Lesenswert?

Karl heinz Buchegger schrieb:
> Noch ein Tip:
> Wenn du unsigned rechnest, musst du dir um den Überlauf keine Gedanken
> machen. Einfach Ende minus Anfang rechnen und es kommt in allen Fällen
> das Richtige heraus solange es nur 1 Überlauf gab.
Super Tipp, danke! So schrumpft der Code nochmals

>...Läuft der Timer mit Full-Speed, dann kann es
> sein, dass ...
> tatsächlich einen Überlauf hast, den du vorher nicht festgestellt hast,
> weil der Timerwert gerade an der Grenze war.
Genau das hat dann auch das "Flackern" meiner LEDs in seltenen 
Situationen verursacht.

> -> beim Eintritt in die ISR den Timerwert in einer Variablen sichern und
> dann nur noch mit der Variablen arbeiten, ...
Die überlegung hab ich auch gemacht, um den Laufzeitunterschied zu 
beseitigen.

Resultat: Kleiner, Schneller, Übersichtlicher->
1
//PWM einlesen
2
3
ISR (PCINT0_vect)            //pin-change-irq
4
{
5
  tmp = TCNT0;              //Timer-Wert sichern
6
  if (PINB & (1<<PB2))          //bei steigender Flanke (=Hi)
7
  {
8
    beg = tmp;              //Kopiere gesicherten Timerwert in begvariable
9
  }
10
  else                  //bei fallender Flanke (=Low)
11
  {
12
    rcpwm = (tmp - beg);        //rcpwm = Timer Ende - Timer Anfang
13
  }
14
}

Danke und Gruß
Marian

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.