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
voidprogjump(void)
2
{
3
while(prog==0);//warten bis Programm ausgewählt (wenn vorasuwahl dann weglassen wegen speicherplatz)
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 ;)
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:
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
>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
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.
@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
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.
>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.
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
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
voidprogjump(void)
2
{
3
switch(prog)
4
{
5
case1:
6
{
7
MiniProgramm1mitPiPaPoundetc....//Funktion in Endlosschleife
8
};
9
case2:
10
{
11
MiniProgramm2mitPiPaPoundetc....//Funktion in Endlosschleife
12
};
13
case3:
14
{
15
MiniProgramm3mitPiPaPoundetc....//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
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
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! :-)
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.
@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
> 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.
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
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.
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
>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.
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
voidprog1(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
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.
@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.
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 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.
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.
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 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 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.
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
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