Go Down

Topic: Anzeige im I2C Display per Knopfdruck ändern (Read 3742 times) previous topic - next topic

supersonix

Hallo,

ich glaube, ich hab eine Denkblockade  :smiley-red:

Eine LED mittesl Tastendruck ein und aus zu schalten ist ja ok, aber das ganze funktioniert mit der Anzeige auf einem LCD Display nicht!?

Es soll per Tastendruck ( 7 Tasten ) angezeigt werden, welche Taste gedrückt ist, diese Anzeige bis  3 sec. gehalten werden und dann wieder in eine Default Anzeige "springen"
Die hinter der Taste verborgenen  Funktionen sollen natürlich auch ausgeführt werden.

Swicht / Case Anweisung?

Könnt ihr mir helfen?

Vielen Dank
Sven

michael_x

#1
Jun 17, 2013, 10:32 am Last Edit: Jun 17, 2013, 10:36 am by michael_x Reason: 1
Vermutlich Denkblockade:
sowohl LED wie Display ( egal ob I2C oder parallel) behalten ihren Anzeigezustand wenn man gar nichts macht,
sollte also kein Unterschied sein.

Man kann das Ganze hochtrabend "State Machine" nennen:

-> Der "Normalfall" ist, dass nichts los ist : nix tun
-> Die Anzeige-Haltezeit ist gerade abgelaufen : - default Anzeige -
-> Es war mal eine Taste gedrückt worden, die 3 Sekunden sind aber noch nicht um,
    ob die zugehörige Funktion noch läuft und evtl. weitere Aktionen erfordert, ist eine andere Frage, die du noch nicht gestellt hast.
    ( sonst nix tun )
-> Eine Taste ist (noch) gedrückt, den Start haben wir aber schon gemerkt.  ( nix tun )
-> Eine Taste wird gedrückt, und wir sind im Normalzustand:
    Los gehts: Ende des Normalzustands, Aktion starten, LCD Anzeige
-> Eine Abbruch-Taste oder eine andere Taste als die zu der aktuellen Aktion gehörige wird gedrückt:
    Ein Fall, von dem du noch nichts geschrieben hast:
     a) Abbruch gibts nicht
     b) Eine Aktion soll immer erst fertig laufen
     c) andere Reaktion wäre besser
     ...

Der Trick ist, sich den Zustand ("State") zu merken, und meist nichts tun, dann reagiert loop sofort, wenn doch etwas passiert.



Wenn du die Taster einzeln von verschiedenen Pins liest, bringt dir eine switch - case Anweisung wohl nicht viel,
wenn du 7 oder 8 Taster als ein byte ( z.B. von einem atmega Port oder über einen I2C Expander oder aus einem Schieberegister) liest, bietet sich ein switch natürlicherweise an.

supersonix

Ich denke: Default Anzeige --> Tastendruck ( Aktion soll immer erst fertig laufen ) Anzeige, für 3 sec. danach wieder default Anzeige, was abgeardeitet wird / welche Taste gedrückt wurde --> fertig.

Die Taster sind/ sollen jeweils an einem dig. PIN liegen.

:~

Danke
Sven


Serenifly

#3
Jun 17, 2013, 12:44 pm Last Edit: Jun 17, 2013, 04:45 pm by Serenifly Reason: 1
Wenn du auf das LCD ein Leerzeichen schreibst wird nichts angezeigt. Das ist als String " " oder ASCII Code 0x20.

Du musst dann deine Sachen draufschreiben und dann nach 3 Sekunden auf die gleichen Adressen Leerzeichen. Wenn du sonst nichts anzeigst kannst du auch lcd.clear() machen, aber das löscht alles und dauert lange. Wenn dein Text 7 Zeichen lang ist kannst du natürlich gleich lcd.print("       ") machen. Davor natürlich die Adresse auf das erste Zeichen stellen, das der Display-Controller die Adresse bei jedem Schreiben automatisch inkrementiert.

Wenn du irgendeine Anzeige hin-und-her schalten willst (manuell und nicht zeitgesteuert) musst du dir wie oben gesagt den Zustand einmal in einer bool-Variablen merken. Ich habe das gerade mit einem Häkchen wo ich eine Funktion ein und ausschalten kann. Sieht dann bei mir so aus:

Code: [Select]

if (_input.check)
{
   _lcd.printByte(0x20);      //Haken ist gesetzt -> löschen

}
else
{
    _lcd.printByte(CHECK_MARK);
}

_input.check = !_input.check;




Bei mir ist die ganze Sache in einer riesigen if/else/switch Konstruktion.

Ich gebe Zeiten für einen Alarm ein. Also habe ich diese Zustände. Wie oben gesagt eine State-Machine. Man kann das natürlich auch eine richtige Klasse schreiben und alles objekt-orientiert machen. Das muss aber auf einem Mikrocontroller nicht unbedingt sein.

#define TIME_H10    0
#define TIME_H1      1
#define TIME_M10    2
#define TIME_M1      3
#define TIME_PROG  4
#define TIME_CHECK 5

Dann eine Variable "uint8_t _timeDigitIndex = TIME_H10". Die Zehner-Stelle ist meine Ausgangssituation. Wenn ich dann auf die nächste Ziffer schalte (geht bei mir mit der Stern-Taste, aber man kann es auch automatisch machen) muss ich nur "_timeDigitIndex++" machen.

Ich will dann z.B. abfragen, dass man die Zehnerstellen nur von 0-2 schreiben kann. Geht dann einfach so:
Code: [Select]

if(_timeDigitIndex == TIME_H10)
{
   if(key <= '2')
  {
      _input.h10 = key - 0x30;   //Umwandlung ASCII -> Dezimal
      _lcd.print(key);
      _lcd.moveCursorLeft();     //macht das Auto-Inkrement rückgängig. Ich will nicht immer alle Zeiten neu eingeben müssen
   }
}


Darüber liegt nochmal eine Abfrage in welcher Menüfunktion ich mich befinde. Dafür gibts "uint8_t _menuFunction" und dann defines wie:
#define MENU_CURSOR       0
#define MENU_SET_ALARM  1

Ich will dann mit der Raute-Taste die Zeit oder einen der Alarms bestätigen:

Code: [Select]


if(_menuFunction == MENU_SET_ALARM || _menuFunction == MENU_SET_TIME)
{
   if(key == '#')
   {
       if(_menuFunction == MENU_SET_ALARM)
       {
          ...
       }
   }
   else if(key >= '0')
   {
       //hier ist der Code von oben wie ich Abfrage auf welcher Stelle ich bin
   }
{


Switch ist ja nur ein einfacher lesbares if/else. Aber auch nicht so toll wenn du viele Anweisungen ausführst, da es gleich unübersichtlich wird. Das mache ich für den Cursor, da ich da den Code in andere Methode ausgelagert habe.

Code: [Select]

if(_menuFunction == MENU_CURSOR)
{
  switch(key)
  {
    case '2':
        printCursor(CURSOR_UP);
        break;
    case '4':
printCursor(CURSOR_LEFT);
       break;
    case '6':
printCursor(CURSOR_RIGHT);
        break;
    case '8':
 printCursor(CURSOR_DOWN);
break;
    case '#':
    case '5':
      _menuFunction = MENU_SET_ALARM;    //hier wird dann in einen anderen Menü Zustand umgeschaltet und wenn das nächste mal eine Taste
                                                            //gedrückt wird hat sie eine andere Funktion
      break;
   }
}


Wenn du für jede Taste nur eine Methode und die Anzeige aufrufst kannst du das auch mit einem if/if else/if else... machen. Du kannst z.B. eine Methode schreiben, der die Nummer/ID der Taste übergibst. Die Methode setzt dann die Nummer in eine Display Adresse um und schreibt die entsprechende Stelle.

Wegen dem Timing der Anzeige musst du halt aufpassen, da du das nicht mit Warten/Delay machen kannst. Schau dir das Beispiel "BlinkWithoutDelay" an wie man eine Zeit zählt und dabei noch andere Sachen erledigt. Ein oneshot Timer (der nur einmal läuft und dann neu gestartet werden muss) wäre auch eine elegante Möglichkeit. Der würde dann die entsprechenden Display-Stellen löschen wenn er seine Zeit erreicht hat.

supersonix

#4
Jun 17, 2013, 02:34 pm Last Edit: Jun 17, 2013, 03:14 pm by supersonix Reason: 1
Uijuijui... jetzt ist meine Denkblockade noch schlimmer  :smiley-red:

VG
Sven

Serenifly

#5
Jun 17, 2013, 02:59 pm Last Edit: Jun 17, 2013, 04:42 pm by Serenifly Reason: 1
Bei mir gibt es Menü mit mehreren Zeilen, das man mit einem Cursor abfahren kann und das verschiedene Funktionen hat. Das wird bei dir etwas Overkill sein. Ist aber eine praktische Anwendung der Idee die darüber gepostet wurde.

Orientiere dich für den Anfang mal an dem letzten Code-Ausschnitt. Das reicht um mehrere Tasten abzufragen und entsprechend der Taste verschiedene Dinge zu machen. Wenn du deine Tasten abfragt (ich nehme an den Code hast du schon für die LED und kannst das einfach mit mehr if-Abfragen erweitern) und eine gedrückt wird übergibst du einer Methode die Tasten-Nummer. Auf diese wird dann geswitcht.

So ähnlich:
Code: [Select]

void testTaste(int taste)
{
    switch(taste)
    {
     case 1:
         methodeTaste1();
         showTaste(taste);
         break;
     case 2:
      .
      .
   }
}


Code: [Select]

showTaste(int taste)
{
    _lcd.setCursor(taste, 0)      //setzt die Spalte in Abhängigkeit von der Taste
    _lcd.print(taste)                //schreibt den Wert der Taste aufs Display
}


Natürlich kannst du das switch auch weglassen und den Code direkt in die Methode schreiben in der du die Tasten-Pins abfragt :) Für das bisschen ist das hier eigentlich Unsinn. Aber so zeitkritisch sind wir hier nicht. Das ist Geschmacks- und Übersichtssache. Letztlich ist es eine Frage wie komplex deine Abfragen und Aktionen sind. Bei dir ist es noch einfach und du kannst daher vieles zusammen lassen ohne es in eigene Methoden aufzudröseln. Ich würde aber mindests die Print-Funktion (showTaste) auslagern, da dieser Teil am ehesten geändert wird.

Bei dir wäre eher sowas angebracht. Pseudocode (kein C)!
Code: [Select]

If Pin1 = LOW
{
         methodeTaste1();
         showTaste(1);
}
else if Pin2 = LOW
{
         methodeTaste2();
         showTaste(2);
}
.
.


Jedenfalls ist Trick dann ist das du zur gleichen Zeit einen Timer starten musst (in Software oder Hardware), der deine 3 Sekunden hoch zählt (oder über millis() abfragt ob 3000ms vergangen sind). Siehe eben "BlinkWithoutDelay" für die Grundidee. Wenn die dann erreicht sind, schreibst du auf die gleiche Displayadresse ein Leerzeichen oder löscht die ganze Zeile.

Das geht davon aus dass du die Tasten auf unterschiedlichen Display-Adressen anzeigen willst. Das kommt dann darauf wie du dein Display genau organisiert hast. Du kannst natürlich auch alle auf der gleichen Position schreiben. Oder du zeigst je nach Taste anderen Text an. Deshalb ist das mit der showTaste Methode flexibler. Kann ja sein dass du das mal ändern willst.

Beim Tastendruck die millis-Zeit für die Taste zu speichern und dann in der main loop mit der aktuellen millis-Zeit zu vergleichen wäre auch möglich.
Wenn man es schafft da 7 Zeiten getrennt abzufragen, kann man auch 7 Tasten getrennt anzeigen und alle haben ihre eigenen Ausschaltzeiten. Ein erneuter Druck auf eine Taste startet den Timer praktisch bei 0. Du musst du halt wissen wieviel Luxus du willst :p

Sowas vielleicht:
Code: [Select]

if (taste1Zeit + 3000 < millis())
{
    //Leerzeichen auf entsprechende Adresse schreiben
}


Dieser Code wird allerdings immer ausgeführt. Auch wenn keine Taste gedrückt wurde. Da kann man dann noch eine bool Variable setzen und das nur machen wenn wirklich gedrückt wurde:
Code: [Select]

if ((taste1Zeit + 3000 < millis()) && taste1Pressed)
{
    //Leerzeichen auf entsprechende Adresse schreiben

    taste1Pressed = false;
}


Das ist nur grober Code und ich habe nichts getestet! Für den Anfang kannst du ja mal mit jedem Tastendruck die Anzeige umschalten. Den Code dafür habe ich oben schon hingeschrieben (gaaaanz am Anfang. Nur noch die Adressierung hinzufügen!). Erst wenn das läuft versuche die Timer Geschichte.

Go Up