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
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.
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
"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
Achso, hier ist mal ein Code, der mir relativ gut optimiert erscheint: http://www.mikrocontroller.net/attachment.php/252480/C_TAST.C Peter
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
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? :)
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.
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
@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
@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.
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
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
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.