Hallo,
ich schreibe gerade ein etwas umfangreicheres Programm für einen ATmega8
(Belichtungstimer mit Auswahlmenü für eine DSLR-Kamera)
Dabei lasse ich über den Timer2 jede Millisekunde einen Interrupt
durchführen, welcher eine Menge Abfragen nach sich zieht.
(Tastenabfrage, diverse Zähler mit volatile-Variablen, Vergleiche von
structs mit 40 bit Größe, Ausgabe über LCD ...)
Nun bin ich mir nicht sicher, wie viele Arbeitsschritte ich durchführen
kann, bis die Abarbeitung länger dauert, als die Zeit bis zum nächsten
Aufruf...
Vor allem die Ausgabe von bis zu 32 Zeichen über das LCD macht mir
Gedanken - wie viele Takte braucht sowas?
Der Controller läuft momentan auf 8 MHz - was also 8000 Takte für den
Interrupt und angeschlossene Berechnungen lässt.
Wie kann ich die benötigten Takte für einen Durchlauf meines Programmes
(also je interrupt) berechnen?
Ich dachte auch schon daran zu Beginn den Zähler 1 (16 bit) zu starten
und am Ende den Stand auszulesen - warte aber noch auf eine eventuell
allgemeinere Lösung.
Dazu habe ich mein (momentan nicht lauffähiges weil nicht fertiges)
Programm hochgeladen. Das ist allerdings etwas unübersichtlich, da
bisher kein PaP existiert...
vG,
Erik
Erik Her schrieb:> Nun bin ich mir nicht sicher, wie viele Arbeitsschritte ich durchführen> kann, bis die Abarbeitung länger dauert, als die Zeit bis zum nächsten> Aufruf...
Wenn du ein Oszi hast, setz zu Beginn des Interrupts einen Pin auf high
und beim Verlassen wieder auf Low.
Am Oszibild kannste dann abschätzen wieviel Zeit dir bleibt.
Im Simulator des AVRStudio gibt es einen Zyklenzähler.
Also den Zähler bei Eintritt in den Interrupts ablesen und dann am Ende
der Verarbeitung.
Aber Du hast noch ein grundsätzliches Problem mit dem Verständnis von
Interrupts. Im Interrupt werden grundsätzlich nur die allernotwendigsten
Dinge erledigt. Im allgemeinen nur ein Flag gesetzt, das der Interrupt
aufgetreten ist und das Flag dann in der main-Schleife abgefragt.
Ausserdem sehe ich da im Interrupt noch Tastenabfragen. Das ist
kontraproduktiv, da die Entprellung fehlt.
Bitte das nächste Mal den Code mit "c"-Extension posten und nicht "txt".
Erik Her schrieb:> Vor allem die Ausgabe von bis zu 32 Zeichen über das LCD macht mir> Gedanken - wie viele Takte braucht sowas?
Schau einfach mal ins Datenblatt des LCD.
Typisch dauert ein Zeichen ~50µs, also 32 Zeichen ~1,6ms.
Ist also die vollkommen falsche Stelle, etwas auszugeben.
Ganz abgesehen davon, daß kein Mensch innerhalb 1ms etwas ablesen kann.
Alle 0,2 .. 0,5s Text auszugeben, reicht völlig.
Peter
Erik Her schrieb:> Vor allem die Ausgabe von bis zu 32 Zeichen über das LCD macht mir> Gedanken - wie viele Takte braucht sowas?
Ich mach das gern so, dass ich ein char-Feld habe, das ich mit dem
gewünschten Inhalt beschreibe.
In jedem Interrupt wird dann immer 1(!) Zeichen ausgegeben, damit
kommste bei 32 Zeichen und 1ms auf ne ordentliche Framerate und der
Interrupt bleibt schön klein.
Und warum nicht den einfachsten Weg wählen, der bewährt ist? Nämlich im
Interrupt nur ein Flag setzen, dass in der Endlosschleife der main
ausgewertet wird?
So kannst du deine Programmlogik zeitlich wunderbar von der LCD-Ausgabe
trennen.
@Floh: habe leider kein Oszi
@Grrrr: den Zyklenzähler werde ich mal ausprobieren, wenn ich Zeit habe.
Grrrr schrieb:> Ausserdem sehe ich da im Interrupt noch Tastenabfragen. Das ist> kontraproduktiv, da die Entprellung fehlt.
das IST meine Tastenentprellung! habe ich im gcc-Tutorial so in der Art
gesehen...
(ich frage alle 10 ms den Tastenzustand ab, und wenn eine Taste 10
Abfragen in Folge gedrückt war ist der Tastendruck gültig)
Peter Dannegger schrieb:> Ganz abgesehen davon, daß kein Mensch innerhalb 1ms etwas ablesen kann.> Alle 0,2 .. 0,5s Text auszugeben, reicht völlig.
ich gebe maximal ein Mal je Sekunde etwas aus - aber diese Ausgabe
dauert halt dann länger als 1 ms...
Lord Ziu schrieb:> Und warum nicht den einfachsten Weg wählen, der bewährt ist? Nämlich im> Interrupt nur ein Flag setzen, dass in der Endlosschleife der main> ausgewertet wird?
ich werde mich dann demnächst ran setzen und das Programm umschreiben um
alles Rechen-/Zeitaufwändige ins main() zu verlegen... (im Interrupt nur
flags setzen)
(dauert aber bis meine Prüfungsphase beendet ist)
obwohl ich mit der Funktion bisher sehr zufrieden bin... Aber sinnvoller
ist es sicherlich mit den flags.
Erik Her schrieb:> obwohl ich mit der Funktion bisher sehr zufrieden bin... Aber sinnvoller> ist es sicherlich mit den flags.
In deinem Programm kann man noch vieles machen. Hör nicht mit den Flags
auf.
Da du Zeiten 'logarithmisch' erhöhen bzw. erniedrigen möchtest, wäre es
zb überlegenswert, ob man nicht programmintern die Unterteilung in
Millisekunden, Sekunden, Minuten, Stunden fallen lässt und für alles
ganz einfach einen uint32_t nimmt, der generell eine Zeit in
Millisekunden abgibt.
Dazu noch ein Array, indem Zeitstufen enthalten sind.
Eine Zeit besteht dann aus einem Zahlenwert und einer Stufennummer. Die
Zeit erhöhen heisst dann ganz einfach: Die Stufe um 1 erhöhen (falls das
noch geht) und aus dem Array den entsprechenden Wert in Millisekunden
holen. Die riesigen switch-case lösen sich in Luft auf. Genauso wie die
Spezialfunktionen für Zeitvergleichen bzw. prüfen auf 0. Wenn du die
laufende Belichtungszeit nicht von 0 bis zur Vorgabe hochzählst sondern
von der Vorgabe runterzählst, bis 0 erreicht ist, vereinfacht sich der
Code in der ISR noch weiter.
Die ganze Menüsteuerung im main(), also der Teil mit state und den
Buttons, lässt sich auch in einem kurzen Array von Strukturen abbilden.
Die 3 Seiten Code dampfen sich dann auf ein paar Zeilen (weniger als 20)
ein.
Und dann möchtest du noch in deinem C-Buch nachlesen, wie man Strkturen
per Pointer übergibt. So etwas
uint8_tcompareTime_zero(TIMEa)//vergleicht eine Zeit mit 0
13
{
14
if((a.mseconds==0)
15
&&(a.seconds==0)
16
&&(a.minutes==0)
17
&&(a.hours==0))
18
return1;
19
else
20
return0;
21
}
muss dann wirklich nicht sein. Genau das ist der klassische Stil, an dem
sich die C#/Java Jünger aufreiben. Ein Musterbeispiel dafür, wie man
einen Rechner unnötig Zusatzaufwand zuschanzt der nicht sein muss.
Klar, ein Makro mit memcmp wär weniger Schreibaufwand gewesen.
Aber bist du sicher dass Zeiger so viel effizienter sind? Sind doch auch
16 bit-Werte, oder ist das beim ATmega8 anders als beim ATmega16?
Und wenn die Vergleichsfunktionen inline sind könnte es sogar in den
Registern bleiben wenn man nicht mit Zeigern arbeitet!
--> Wenn mans genau wissen will bleibt wohl nur, die Zyklen im Simulator
zu messen (mit Compileroption -O3) und zu vergleichen.
sebastians schrieb:> Aber bist du sicher dass Zeiger so viel effizienter sind? Sind doch auch> 16 bit-Werte, oder ist das beim ATmega8 anders als beim ATmega16?
Hast du dir seine TIME Struktur angesehen?
Tu das und die Frage stellt sich nicht mehr.
Von 16 Bit kann da keine Rede mehr sein.
> Und wenn die Vergleichsfunktionen inline sind könnte es sogar in den> Registern bleiben wenn man nicht mit Zeigern arbeitet!
Wenn der Compiler inlined, fallen die Zeiger genauso weg. Wenn der
Compiler aber nicht inlined ....
Und das mit den Registern vergiss gleich mal wieder. Eine TIME Struktur
hat 4 Member. Einen uint16_t und 3 Stück uint8_t. Die kriegt der
Compiler noch nicht einmal wenn er will in die Register rein.
Die hier
1
TIMEresetTime(TIMEtime)//setzt übergebene Zeit zurück
ist besonders schön. Ist nicht nur in der Verwendung mies
(Oder findest du
currentTime = resetTime( currentTime );
besonders intuitiv)
sondern auch noch mit 2 potentiellen 5 Byte Kopieraktionen besonders
effizient.
Also zu allererst: vielen Dank für die vielen Tipps!
Karl heinz Buchegger schrieb:> Da du Zeiten 'logarithmisch' erhöhen bzw. erniedrigen möchtest, wäre es> zb überlegenswert, ob man nicht programmintern die Unterteilung in> Millisekunden, Sekunden, Minuten, Stunden fallen lässt und für alles> ganz einfach einen uint32_t nimmt, der generell eine Zeit in> Millisekunden abgibt.> Dazu noch ein Array, indem Zeitstufen enthalten sind.
Das macht natürlich auch Sinn - vor allem für die Vergleiche.
Allerdings erschwert es die Ausgabe auf dem Display:
Zeiten sollen nur in EINER Einheit (h, m, s, ms) dargestellt werden.
(eine Belichtungszeit von 3600000 ms scheint mir nicht
Benutzerfreundlich)
Ganze Sekunden lassen sich noch durch Division darstellen. Aber die
Brüche entsprechen nicht den realen Zeiten. So werden 17 ms als 1/60 s
ausgegeben. (eigentlich: 1/58,8...)
Ich müsste also noch ein weiteres Array mit den entsprechenden
Ausgabezeiten erstellen...
Das würde dann wiederum die Ausgabe stark vereinfachen. (einfach nur den
Wert des jeweiligen Arrays ausgeben - ohne große Abfragen)
Die Funktion zum Auslösen und Lösen des Spiegels werde ich aber trotzdem
im ISR belassen - bei einer Belichtungszeit von 1 ms machen sich
Verzögerungen doch bemerkbar...
Karl heinz Buchegger schrieb:> Wenn du die> laufende Belichtungszeit nicht von 0 bis zur Vorgabe hochzählst sondern> von der Vorgabe runterzählst, bis 0 erreicht ist, vereinfacht sich der> Code in der ISR noch weiter.
das habe ich so gelöst, damit die Funktion nach Beendung der
Belichtungszeit noch eine weitere Sekunde wartet bevor das Display
zurück gesetzt wird. (um dem Benutzer die Chance zu geben die
eingestellt Zeit abzulesen)
Karl heinz Buchegger schrieb:> Und dann möchtest du noch in deinem C-Buch nachlesen, wie man Strkturen> per Pointer übergibt
Ich werde mir wohl erst ein Buch besorgen müssen... Habe nur nach dem
gearbeitet, was ich aus dem Tutorial und meinen bescheidenen C++
Kenntnissen (für PC) konnte.
Aber das würde sicherlich einiges beschleunigen!
Karl heinz Buchegger schrieb:> Die hier> TIME resetTime(TIME time) //setzt übergebene Zeit zurück> ist besonders schön.
das war eine Übergangslösung, da ich keine bessere Möglichkeit
gefunden habe, den struct zurück zu setzen...
Hat sich aber erledigt, wenn alles in ms gespeichert wird.
Karl heinz Buchegger schrieb:> Die ganze Menüsteuerung im main(), also der Teil mit state und den> Buttons, lässt sich auch in einem kurzen Array von Strukturen abbilden.
wie mache ich sowas?
Erik Her schrieb:> Das macht natürlich auch Sinn - vor allem für die Vergleiche.> Allerdings erschwert es die Ausgabe auf dem Display:
Ausgabe machst du aber viel seltener.
Und soviel erschwert es die Ausgabe dann auch wieder nicht.
> Ganze Sekunden lassen sich noch durch Division darstellen. Aber die> Brüche entsprechen nicht den realen Zeiten. So werden 17 ms als 1/60 s> ausgegeben. (eigentlich: 1/58,8...)
Das ist natürlich schon ein Argument. Daran hab ich wiederrum nicht
gedacht.
Ist aber so schlimm auch wieder nicht.
> Ich müsste also noch ein weiteres Array mit den entsprechenden> Ausgabezeiten erstellen...
Genau.
Dazu noch die Stufennummer der Zeit. Wenn die kleiner als eine Grenze
ist, dann wird die Ausgabe nicht aus der Millisekundenzahl errechnet,
sondern aus diesem Stringarray geholt.
Dieses "Stringarray" hast du ja jetzt im Grunde auch schon - versteckt
in den switch-cases
> Die Funktion zum Auslösen und Lösen des Spiegels werde ich aber trotzdem> im ISR belassen - bei einer Belichtungszeit von 1 ms machen sich> Verzögerungen doch bemerkbar...
Das ist ok.
Sind ja nur ein paar Port Bits umsetzen.
Aber die Ausgaben aufs LCD dauern!
>> TIME resetTime(TIME time) //setzt übergebene Zeit zurück>> ist besonders schön.> das war eine Übergangslösung, da ich keine bessere Möglichkeit> gefunden habe, den struct zurück zu setzen...> Hat sich aber erledigt, wenn alles in ms gespeichert wird.
Man würde das so machen
void resetTime( TIME * time )
{
time->Millisekunden = 0;
time->Sekunden = 0;
time->Minuten = 0;
time->Stunden = 0;
}
Aufruf:
resetTime( &irgendeine_Time_Variable );
'Pass per Pointer' ist in C eine wichtige Technik. So wie in C++ ein
'pass per reference' wichtig ist, wenn du vermeiden willst, dass von
allem und jedem ständig eine Kopie erzeugt werden muss.
Erik Her schrieb:>> Die ganze Menüsteuerung im main(), also der Teil mit state und den>> Buttons, lässt sich auch in einem kurzen Array von Strukturen abbilden.> wie mache ich sowas?
So.
Ich habe den Code noch nicht kompiliert, da können daher noch ein paar
Tippfehler drinnen sein.
Das Prinzip ist einfach.
Alles was es zu einem Menüpunkt zu sagen gibt, findet sich im Array Menu
wieder. Dort ist verzeichnet, welche Texte ausgegeben werden sollen,
welche Menüpunkte beim Drücken einer Taste 'aktiviert' werden sollen, ob
es Funktionen gibt, die auf Tastendruck ausgeführt werden sollen etc.
Einfach alles, was es zu einem Menüpunkt gibt, ist dort hinterlegt.
Jeder Menüpunkt ist im Array und wenn ein Menüpunkt dazu kommt, bedeutet
das lediglich eine neue Zeile im Array, in dem die Tasten mit den
anderen Menüpunkten (über deren Index) verknüpft werden und eventuell
(in seltenen Fällen) noch eine Spezialfunktion aufgerufen wird.
Die Funktion HandleMenu ist die 'eierlegende Wollmilchsau' die abhängig
vom aktuellen Menüpunkt (in state) und der gedrückten Taste (die kriegt
sie mit) die in diesem Array gespeicherten Informationen auswertet und
abarbeitet.
OK. In Summ ist es dann doch ein wenig länger geworden. Allerdings hast
du
* das komplette Menü an einer Stelle beisammen (im Array)
* bei Erweiterungen musst du nur einen neuen Eintrag im Array machen
und ein paar Zahlen (die Indizes) anpassen um den Punkt in die
logische Struktur des Menüs einzubinden. Mehr ist nicht notwendig
wow! ich bin beeindruckt! in dieser kurzen Zeit eine Menüstruktur zu
schreiben...
Ich habe mir gerade auch Gedanken darüber gemacht und dachte zu erst
auch an etwas ähnliches - allerdings wusste ich nicht, dass ich in einem
struct auch Funktionen speichern kann.
Das eröffnet ja ganz neue Möglichkeiten! ;-)
dieses
1
typedefvoid(*MenuFunc)(void);
habe ich noch nie gesehen...
aber hier ist es erklärt: http://en.wikipedia.org/wiki/Typedef
Die Belichtungszeiten in ms werden nun in einem Array "uint32_t
steps[28] = ..." gespeichert.
Zur Ausgabe der Zeiten werde ich nun ein Array mit JEDER Stufe (28)
erstellen, in dem der Wert (zB 15 oder 500) und die Einheit (0-3 = 1/ -
h) enthalten ist. Benötigt zwar mehr Speicherplatz (2 Byte je Stufe) als
die Division der Millisekunden - aber ich denke das es weniger
Rechenaufwand ist.
Belichtungszeiten selber (zB lzbTime) werden dann nur als int
gespeichert (0...27) was dann dem Speicherort in einem der Arrays
entspricht.
Gerade wollte ich das Menü noch folgendermaßen aufbauen:
2-dimensionales Array: menu[x][y]
x: Hauptmenü-Punkt
y: Unterpunkte
Jedes Element enthält ein int-Wert und 2 strings (Display Ausgabe)
in den y=0 Feldern ist die Zahl der Unterpunkte enthalten
in allen anderen je nach Bedarf zB Zeitstufen
Bei Tastendruck (zB "down") wird dann nur der y-Wert des gespeicherten
states zu 1 addiert... (sofern y kleiner als die Zahl der Unterpunkte)
-> Aber das funktioniert nur solange sämtliche gespeicherten Werte
Belichtungszeiten sind...
Also werde ich es wohl so ähnlich machen, wie Karl heinz Buchegger
bereits vorgeschlagen hat.
Das mit den gespeicherten Funktionen erleichtert einiges!
Erik Her schrieb:> allerdings wusste ich nicht, dass ich in einem> struct auch Funktionen speichern kann.> Das eröffnet ja ganz neue Möglichkeiten! ;-)> dieses
im struct für die einzelnen Zustände?
Einen Zeiger auf einen konstanten char, der in diesem Fall der Anfang
einer Zeichenkette ist.
> Warum definiert man das nicht einfach als string?
1
charguiText1[];
Diese Definition ist falsch. Ein String braucht eine Länge. Die muß hier
zwingend angegeben werden.
> welche Vorteile bring das?
Daß nicht in allen Instanzen der Struktur ein String stecken muß, der so
lang ist, wie der längste und daß man nicht darauf achten muß, daß man
die Stringlänge immer an die größte Länge anpaßt.
> Wie kann ich den String ausgeben?> Muss ich ihn als Zeiger oder als Variable übergeben?
1
lcd_puts(menuState[0].guiText);
oder:?
1
lcd_puts(menuState[0].*guiText);
Vergleiche mal den Typ, den lcd_puts übegeben haben will mit dem Typ von
guiText.
Übrigens: Die zweite Zeile ist Quatsch. Du solltest in deinem C-Buch
auch nachlesen, wie Arrays und Zeiger funktionieren.
in der lcd.c ist die Ausgabe der Strings so definiert:
1
voidlcd_puts(constchar*s)
2
/* print string on lcd (no auto linefeed) */
3
{
4
registercharc;
5
6
while((c=*s++)){
7
lcd_putc(c);
8
}
9
10
}/* lcd_puts */
Dann kann ich die Zeiger also direkt übergeben:
1
voidprintState(constuint8_tstate)
2
{
3
lcd_puts(menuState[state].guiText1);
4
//u.s.w.
5
}
___________
edit: wer lesen kann ist klar im Vorteil:
im menu.c von Karl heinz Buchegger war es bereits genau so definiert...
Hab ich erst jetzt gesehen. shame on me
Erik Her schrieb:> edit: wer lesen kann ist klar im Vorteil:> im menu.c von Karl heinz Buchegger war es bereits genau so definiert...> Hab ich erst jetzt gesehen. *shame on me*
Auch wenns nicht so gewesen wäre:
Arrays werden grundsätzlich so an Funktionen übergeben, dass die
Startadresse des Arrays in Form eines Pointers übergeben wird.
Für die Funktion spielt es daher gar keine Rolle, ob du ein Array hast
oder ob du 'nur' einen Pointer hast :-)
Aber in dem Fall ist ein Pointer besser. Der Compiler soll den Text
irgendwo in den Speicher packen und die Startadresse des Textes in den
Pointer packen. Wo der Text genau steht, ist dir ziemlich egal. Und da
der Text auch nicht verändert/vergrößert werden soll, braucht auch nur
der tatsächlich notwendige Speicher dafür bereitsgestellt werden. Ein
Array wäre einfach Overkill gewesen. Vor allen Dingen deshalb, weil dann
alle Menüpunkte immer dieselbe Arraygröße für ihre Texte hätten benutzen
müssen.
ich habe mich mal dran gesetzt und das komplette Programm neu
geschrieben...
Dabei habe ich mich stark an dem Beispiel von Karl heinz Buchegger
gehalten.
(vielen Dank dafür!)
Allerdings hat sich nun die Speicherplatz-Verteilung stark verändert:
Version2:
Program: 3042 bytes (37.1% Full)
Data: 617 bytes (60.3% Full)
vorher:
Program: 3766 bytes (46.0% Full)
Data: 227 bytes (22.2% Full)
getestet habe ich es noch nicht, da noch einige Kleinigkeiten fehlen...
(Anzeige während der Belichtung, usw)
Erik Her schrieb:> ich habe mich mal dran gesetzt und das komplette Programm neu> geschrieben...> Dabei habe ich mich stark an dem Beispiel von Karl heinz Buchegger> gehalten.> (vielen Dank dafür!)>> Allerdings hat sich nun die Speicherplatz-Verteilung stark verändert:> Version2:> Program: 3042 bytes (37.1% Full)> Data: 617 bytes (60.3% Full)
Damit war, llogisch gesehen, zu rechnen. Bei dir war die Menülogik
ausprogrammiert. Jetzt liegt die komplette Logik in Form von Daten im
Speicher.
Die kriegst du noch runter, indem du zb die Texte ins Flash legst
http://www.mikrocontroller.net/articles/AVR-GCC-Tutorial#Programmspeicher_.28Flash.29
Auch die restlichen Menükonstanten kann man ins Flash auslagern.