Forum: Compiler & IDEs Mein erster Code... ;)


von Techniker (Gast)


Angehängte Dateien:

Lesenswert?

Hallo!

Anbei ist mein erster 100% selbstgeschriebener, funktionsfähiger
C-Code.
Er soll 8 Tasten entprellen und zugleich die Art der Tastenbetätigung
erkennen. D.h. "kurz gedrückt" oder "lang gedrückt" ohne dass zuvor
die Taste als "kurz gedrückt" erkannt wird.

Wie könnte ich diesen noch optimieren, da er ja nicht gerade klein
ist...?

Wär nett, wenn ein paar mein "kleines Meisterwerk" genauer ansehen
würden!

Dazu noch eine Frage: Wie könnte ich eleganter die Variablen SWITCHES
und PRESSTYPE ohne globale Variable zur ISR und zurück übergeben?

MfG
Techniker

von Unbekannter (Gast)


Lesenswert?

Einige kleine Anmerkungen:

a.) Du solltest für Funktionen und Variablen etc. keine GROßBUCHSTABEN
verwenden. Das kannst Du zwar machen, funktioniert auch, aber es ist
Konvention, das GROßBUCHSTABEN ausschließlich für Makros benutzt
werden.

b.) Programm sollen "schön" sein. Ein paar mal öfters auf die
Return-Taste drücken und die verschachtelten Schleifen schön
formatieren, hilft dem Leser allgemein. Merke: Dem Compiler ist es egal
wie der Code formatiert ist, Menschen dagegen nicht. Du programmierst in
erster Linie für Menschen, nicht für Compiler.

c.) Variablennamen wie "i" sieht man zwar ständig, sind aber nicht
der Weisheit letzter Schluss. Verwende aussagekräftigere Namen. Z.B. in
Deinem Fall so etwas wie "taster_index" oder so.

von Techniker (Gast)


Lesenswert?

Hallo Unbekannter!

Danke für die Tipps!

Kannst du mir auch Tipps geben, wie ich den Code noch optimieren
könnte, sodass er kleiner wird?

MfG
Techniker

von Peter Dannegger (Gast)


Lesenswert?

"Kannst du mir auch Tipps geben, wie ich den Code noch optimieren
könnte, sodass er kleiner wird?"


Puh, da weiß man ja gar nicht, wo man anfangen soll.

Bei Atmel gibts ne Application note zum Optimieren.

Mal 2 Sachen:

1.
a = 1<<b;

Dafür hat der AVR keinen Befehl, d.h. er muß es umständlich in einer
Schleife machen, wenn b eine Variable ist.

Besser ist daher eine extra Maskenvariable zu nehmen, wenn b der
Schleifenzähler ist:

mask = 0x01;

und dann in der Schleife:

mask <<= 1;

Gilt aber nicht, wenn b eine Konstante ist, dann rechnet der
Präprozessor das schon vor dem Compilieren aus.


2.
Unterfunktionen in Interrupts, die nicht im gleichen Object vor dem
Interrupthandler stehen, kann der Compiler nicht analysieren.
Er muß dann notgedrungen sämtliche Register retten.

In der Regel ruft man in Interrupts gar keine Funktionen auf, wenn sie
nicht wirklich mehrfach benötigt werden, sondern fügt den Code direkt
ein.



Kannst Du nochmal was zu der Arbeitsweise Deines Codes sagen.
Wie soll das gehen, den langen Druck zu erkennen, ohne einen kurzen
Druck davor ?

Das geht doch nur dann, wenn erst beim Loslassen reagiert wird.

Ist aber sehr unergonomisch, da der Mensch eine Reaktion schon beim
Drücken erwartet.
Es gibt kräftige Leute, wenn die das nicht wissen, erhöhen die die
Druckkraft solange, bis die Taste hinten am Gerät wieder rauskommt.

Deshalb ist es immer günstiger, den Ablauf so zu planen, daß ein kurzer
Druck vor einem langen keine Rolle spielt.


Peter

von Peter Dannegger (Gast)


Lesenswert?

Achso, hier ist mal ein Code, der mir relativ gut optimiert erscheint:

http://www.mikrocontroller.net/attachment.php/252480/C_TAST.C


Peter

von Techniker (Gast)


Lesenswert?

Hallo Peter!

FUNKTIONSERKLÄRUNG:

Der Code ist eigendlich recht simpel. Reagiert wird nach 150*10ms bei
einem langen Tastendruck und nicht früher! :-)

Im Prinzip funktioniert es so, wie das im GCC-Tutorial. Beim Drücken
einer Taste wird Interruptgesteuert ein Zähler erhöht. Erreicht der
Zähler einen bestimmten Wert nicht, solange der Taster gedrückt ist,
wird der Zähler zurückgesetzt.

Hat der Zähler einen bestimmten Wert überschritten und wird vor
erreichen der oberen Zählgrenze wieder losgelassen, wird das Ereignis
als "kurzer Druck" erkannt. Erreicht der Zähler die obere Begrenzung,
so wird er sofort als langer Druck ausgewertet.

Wenn ein Tastendruck erkannt wurde, so wird das entsprechende Bit im
char SWITCHES auf 1 gesetzt und der Taster so lange nicht weiter
überprüft, bis das Hauptprogramm das Bit wieder löscht und somit auf
den erkannten Ereignis regiert hat. Welcher Tastendruck von der Routine
erkannt wurde kann das Hauptprogramm aus dem char PRESSTYPE erkennen.
Ist das entsprechende Bit low so war es eine kurze Betätigung,
andernfalls eine lange.

Um zu verhindern, dass bei einem langen druck auf den Taster unendlich
viele lange Tastendrücke erkannt werden, wird überprüft ob vorher ein
langer Tastendruck-Ereignis vorhanden war (PRESSTYPE) und wenn ja, ob
der taster wieder losgelassen wurde. Dann wird von der Routine auch das
Bit im PRESSTYPE wieder gelöscht und der Taster ist wieder "scharf"..
;-)

Kl. Nebeneffekt: Wenn man weniger als 8 taster auswerten will, setzt
man einfach die entsprechenden Bits in SWITCHES auf 1. Dadurch werden
die zugehörigen Portpins nicht überprüft! :-)

Ich hoffe, dass nun die Routine etwas verständlicher wird. Um Code zu
sparen, habe ich die Überprüfung mittels einer for-Schleife
ausgeführt...

FRAGEN:
"Dafür hat der AVR keinen Befehl, d.h. er muß es umständlich in einer
Schleife machen"

Was heisst das Konkret? Ist eine überarbeitung hier sinnvoll? Oder
spare ich mir dadurch (symbolisch gemeint) nur ein paar wenige
Bytes...


Warum ich die Routine als Funktion in die ISR einfügt habe?
Später will ich mit dem 10ms-Interrupt noch mehr machen. Ich dachte
mir, wenn ich alles direkt in die ISR packe, kennt man sich irgendwann
garnichtmehr aus... :-)

MfG
Techiker

von Techniker (Gast)


Lesenswert?

Hab grad festgestellt, dass man sich sie Zuweisung "PRESSTYPE &=
~(1<<i);" beim setzen des kurzen Tastendrucks schenken kann, da das
Bit sowieso immer 0 ist zu diesem Zeitpunkt, oder? :)

von Jens (Gast)


Lesenswert?

Ich habe das mit der Entprellung so programmiert, dass sofort beim
Drücken reagiert wird. Erst dann wird ein Zähler gestartet, der erst
nach 100ms wieder die Tasten freigibt. Der Vorteil ist, dass wenn man
längere Zeit (>100ms) nicht gedrück hat, sofort der nächste Tastendruck
erkannt wird. Wenn man einfach nur zyklisch im Timerinterrupt alle 100ms
abfragen würde, könnte es passieren, dass ein kurzes Drücken nicht
erkannt wird, weil gerade der Timerinterrupt nicht aktiv ist.

Naja, ich wahrscheinlich der größte Blödsinn aber so funktioniert es
bei recht gut und Prellen ist auch kein Thema.

von Techniker (Gast)


Lesenswert?

Hi Jens!

Ich prüfe deshalb ja auch alle 10ms. Zudem müssen min. 5*
hintereinander der Taster als gedrückt erkannt werden. Andernfalls wird
es als Preller gewertet und ignoriert.

Und da der (normale) Mensch kaum schneller als 10x pro sek einen Taster
betätigen kann, ist mir das auch egal! ;-)

(Bei meinem Code könnte er sogar noch schneller als 10Hz tasten...)

---------

@Peter:
Mich würde interessieren, wie du das mit der Maskenvariable meinst!?!?
Wie müsste ich davür den Code ändern?
(Möche von Anfang an lernen codesparend und effizient zu
programmieren!)

Danke!

Gruß,
Techniker

von Techniker (Gast)


Lesenswert?

@Peter:

WOW!!
Hab jetzt die (1<<i) durch die mask-Methode ersetzt.
Ergebnis: über 25% weniger Code!!!  :-O

Was ist von mir noch ungünstig gelöst? :)

Wäre es eleganter so wie jetzt mit globalen Variablen, oder
doch besser mit einer Funktion, der man die Position der Variablen
(Pointer) übergibt, deren Wert dann die Funktion ändern kann...?

MfG
Techniker

von Unbkannter (Gast)


Lesenswert?

@Techniker:

Du bewegst Dich beim Programmieren immer im Spannungsfeld zwischen
möglichst einfach und exakt auf die Aufgabe zugeschnitten auf der einen
Seite und möglichst universel auf der anderen Seite.

Am Anfang fehlt Dir definitiv die Erfahrung, festzustellen wann es
Quick&Dirty sein kann/darf/muss und wenn es möglichst universel (und
manchmal umständlich und kompliziert) sein darf/soll/muss.

Daher mein Rat: Am Anfang immer auf die konkrete Aufgabe konzetrieren.
Später kannst Du dann mit mehr weitsicht arbeiten.

Und noch ein Tip zum "Optimieren": Es bring relativ wenig, einfach so
ins "Grüne herein" zu optimieren. Optimiert wird, wenn man ein
Performance-Problem entdeckt. Und damit man genau weiß, wo man
optimieren muss, also wo es sich lohnt, verwendet man entsprechende
Profiler.

Profiler sind Werkzeuge die messen in welchen Teilen eines Programmes
die meiste Rechenzeit verbraucht wird oder der größte Speicherbedarf
besteht.

von Techniker (Gast)


Lesenswert?

Hallo Unbkannter! :)

Mit dem Anwendungsfall hast du natürlich recht! :)

Wenn ich aber z.B. den Tipp von Peter betrachte, kann ich schon im
vorherein wesentliche Dinge beachten!

Im konkreten Beispiel spare ich mir dadurch ein Viertel (!!) des
Codes!
:)

Was würdest du von meinem Vorschlag halten die Werte nicht mehr über
globale Variablen, sondern indirekt per Pointer zu ändern?

Wäre dies sinnvoller? Oder nicht? Und warum?

PS: Hatte gerade festgestellt, dass der Code einen kl. Bug enthält! :)
In der IF-Abfrage für den kurzen Tastendruck steht "COUNTS[i] = 0;".
Dieser Ausdruck gehört aus der IF-Schleife raus und in die nächste
Zeile, damit der Ausdruck auf jedenfall durchlaufen wird! Sonst hat die
ganze Entprellroutine wenig sinn... ;)

Gruß,
Techniker

von Thomas (Gast)


Lesenswert?

In welchem Ausmaß lassen sich diese Optimierungen der C-Programme
überhaupt noch auf andere Prozessoren übertragen?
Dass Bits schieben schneller als eine Division dürfte auf jedem
Prozessor gelten. Aber ob z.B. eine do-while Schleife auf jedem
Prozessor schneller ist als eine while{}-Schleife?
Auch im Bereich Pointeroperationen sind sich die verschiedenen
Prozessoren doch schon sehr verschieden.

Meistens gehen solche Optimierungen schon sehr auf die Lesbarkeit, und
wenns wirklich auf das letzte Quentchen Geschwindigkeit oder Platz
ankommt kann man auch gleich in Assembler programmieren.

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.