Menüs in Bascom - Tutorial

This site uses cookies. By continuing to browse this site, you are agreeing to our Cookie Policy.

  • Eine Möglichkeit, wie man auf einfache Weise in Bascom Menüs für alphanumerische Displays erstellen kann, möchte ich hier zeigen.
    Externe Programme (Menü-Editoren) werden nicht benötigt.
    Einführung


    In diesem Dokument wird beschrieben, wie Menüs einschließlich Untermenüs auf einfache Weise in BascomAVR realisiert werden können. Dabei sind die Menütexte nicht Zwangs-läuft fix vorgegeben (wie Datazeilen), sondern können durchaus auch dynamisch sein. Gemeint ist damit, dass sich z.B. ein Menü-Eintrag abhängig von einer Einstellung ändern kann. Weiterhin können die Menüs einfach und übersichtlich in Bascom direkt definiert werden. Es werden keine Tools zur Menüerstellung benötigt. Die Ausgabe des Menüs ist hierbei auf ein alphanumerisches Display zugeschnitten. Lässt sich aber auch mit ein paar Änderungen auf grafische Displays ändern.

    Um das Tutorial nachzuvollziehen zu können sollte der Code "Menu-Wecker.zip" hier herunter geladen werden.
    Menu-Wecker_0.0.1.zip

    Hilfreich ist auch die Tabelle der Zustände.
    Statemachine Wecker.pdf

    Voraussetzungen

    Diese Menü-Demo verwendet die Statemachine aus dem Lexikon. Beitrag: „Statemachine Tutorial“
    Das Prinzip und die Funktionsweise dieser Statemachine wird vorausgesetzt.
    In dieser Demo wird weiterhin ein Modul (Menu.inc) verwendet, mit dessen Hilfe die Menüverwaltung abgewickelt wird
    und auch die (dynamischen) Menütexte vorgegeben werden.
    Zur Tasten-Entprellung wird die Bascom-Funktion Debounce verwendet.
    Die anzuspringenden Routinen sind in Modul „Tasten.inc“ untergebracht.

    Programm-Beschreibung
    Es soll zur Demonstration des Menüs ein kleiner Wecker mit Alarmzeit entstehen. Hierbei ist der Fokus nicht auf einem korrekt funktionierenden Wecker, sondern auf das Menü ausgerichtet.Zur Veranschaulichung soll die folgende Schematik dienen:



    Das kleine Projekt besteht Hardware-seitig aus einem

    • Alphanumerischen Display (3x16 Zeichen)
    • und 4 Eingabetaster (Low-Aktiv)
    Auf der Softwareseite ist die Demo gegliedert in folgende Programmteile:
    • das Hauptprogramm
    • das Modul Config.inc
    • das Modul Statemachine.inc
    • das Modul Menu.inc
    • das Modul Tasten


    Im Hauptprogramm werden die obigen Module inkludiert. In der Hauptschleife läuft die Statemachine und die Tastenabfrage.
    Im Modul Config.inc können die Pins der verwendeten Tasten und des Displays angepasst werden.
    Im Modul Statemachine.inc sind die Routinen zur Statemachine sowie die Definition der verwendeten Zustände untergebracht.
    Das Modul Menu.inc ist das Menu-Management. Dies sorgt für das Anzeigen des Menü’s und das Scrollen durch die Menüeinträge.
    Das Modul Tasten.inc ist für das registrieren der Tasten-Betätigung zuständig. Gedrückte Tasten werden in einem Byte (KeyPressed) gespeichert und mittels separater Routinen abgefragt. Dieser Mechanismus hat Vorteile, da die Tasten abhängig von den Zuständen der Statemachine unterschiedliche Funktionen haben.

    Das Menü-Management

    Wie bereits beschrieben, ist das Modul „Menu.inc“ für die Darstellung und das Scrollen des Menüs zuständig. Zunächst mal einen Überblick, wie das Menümanagement funktioniert, bevor auf Details eingegangen wird.m ein Menü auf dem Display darzustellen, muss nur einmalig die Routine „selectMenu(mnuID)“ mit einer ID für das gewünschte Menü aufgerufen werden. Die mnuID‘s, die als Konstanten zu definieren sind, .dienen der Unterscheidung, welches Menü darzustellen ist. Immer wenn ein Menü aktiviert und dargestellt werden soll, oder in ein anderes Menü gewechselt werden soll, wird diese Routine mit der entsprechenden mnuID aufgerufen. Intern wird dabei das gewünschte Menü initialisiert und angezeigt.

    Um in dem nun dargestellten Menü zu scrollen sind 2 Routinen definiert. Mit Routine „setMenuUp()“ wird im dargestellten Menü nach oben gescrollt. Mit Routine „setMenuDown()“ kann nach unten gescrollt werden. Dabei wird ein Marker ‚>‘ bewegt, der auf den aktiven Menüeintrag zeigt. Ist der Marker z.B. bereits am unteren Menürand und man scrollt weiter nach unten, so werden die Menü-Einträge gescrollt.

    Mehr braucht der Anwender nicht, um Menüs anzuzeigen und darin zu scrollen.

    Intern gibt es nun in der „Menu.inc“ noch zwei weitere Routinen. Die eine Routine mit Bezeichnung „displayMenu()“ ist die eigentliche Anzeige-Routine des Displays. Die muss der Anwender jedoch nicht aufrufen.
    Die andere Routine mit Bezeichnung „getMenuText(mnuID, mnuEntry)“ ist für den Anwender allerdings sehr wichtig. Der Anwender muss in der Routine alle Menü’s und die Einträge definieren, die im Programmverwendet werden sollen.

    Anpassung der Menüs auf andere Displays
    Im Hopf von dem Modul „Menu.inc“ finden sich die folgenden Einträge.

    BASCOM Source Code

    1. ' --------------------------------------------------------
    2. ' Configuration (Größe und Position des Menüs, Zeichen als Cursor festlegen)
    3. ' --------------------------------------------------------
    4. Const mnuPositionLeft = 1 ' Position linke Seite des Menüs [Zählweise 1...]
    5. Const mnuPositionTop = 2 ' oberste Zeile in der Menü angezeigt wird [Zählweise 1...]
    6. Const mnuHeight = 1 ' Höhe des Menüs in Zeilen -1 [Zählweise ab 0]
    7. Const mnuWidth = 16 ' Breite des Menüs in Zeichen [Zählweise ab 1]
    8. Const mnuCursorChar = ">" ' Zeichen, welches die markierte Menüzeile anzeigt





    mnuPositionLeft gibt an an welcher linken Position im Display das Menü angezeigt werden soll. Die Zählweise ist wie bei „Locate y,x“ ab 1.


    mnuPositionTop gibt die obere Zeile an, ab der die Menü-Einträge dargestellt werden. In Zeile 1 ist der Menü-Titel, daher ist hier Position 2 angegeben.


    mnuHeight gibt die Höhe und damit die Anzahl der darstellbaren Menüeinträge (ohne Menü-Titel) an. Zu beachten ist, die Zählweise beginnt bei 0.


    mnuWidth gibt die Breite des Displays, bzw. des Menüs an. Beginnt das Menü ganz links und das Display kann 16 zeichen je Zeile darstellen, sollte mnuWidth=16 eingestellt werden. Ist die linke Position eingerückt, sollte mnuWidth um die Anzahl eingerückter Zeichen verringert werden. Dieser Parameter hat Auswirkungen auf interne Stringlängen, die zur Darstellung der Menüzeilen verwendet werden. Menütexte könne abgeschnitten werden.


    mnuCursorChar gibt das Zeichen an, das zur Markierung der gewählten Menüzeile verwndet wird. Hier kann jedes vom Display darstellbaren Zeichen eigetragen werden.

    Verwendet man beispielsweise ein 20x4 Display, und möchte in der 1. Zeile den Menü-Titel, und ab der 2. Zeile die Menüeinträge, bleiben 3 Zeilen für Menüeinträge. Für mnuPositionTop ist dann 2 anzugeben, denn die Menüeinträge beginnen ab Zeile 2. Da 3 Zeilen für die Menüeinträge verfügbar sind (also 0 bis 2) wird hier 2 angegeben.

    Definition von Menüs
    Menüs werden in der Routine getMenuText(mnuID, mnuEntry) definiert. Innerhalb der Routine befindet sich eine Select Case Struktur zur Unterscheidung der Menüs anhand des übergebenen Parameters mnuID. Sie hat etwa folgenden Aufbau.

    BASCOM Source Code

    1. Select Case mnuID
    2. Case mnuID_MAIN
    3. ‘Hier wird Menü mit mnuID_MAIN definiert
    4. Case mnuID_SUBMENU1
    5. ‘Hier wird Menü mit mnuID_SUBMENU1 definiert
    6. Case mnuID_SUBMENU2
    7. ‘Hier wird Menü mit mnuID_SUBMENU2 definiert
    8. End Select

    Die ID’s der Menüs sollten mit dem Prefix “mnuID_” beginnen, gefolgt von einer vernünftigen Erweiterung, die das Menü beschreibt.

    Innerhalb eines jeden Case Abschnittes wird nun das jeweilige gewünschte Menü definiert.
    Möchte man z.B. unter mnuID_MAIN (Hauptmenü) ein Menü mit drei Einträgen definieren, dann könnte die Definition beispielsweise so aussehen:

    BASCOM Source Code

    1. Case mnuID_MAIN
    2. mnuTitle = “Hauptmenu“
    3. mnuEntries = 2
    4. Select Case mnuEntry
    5. Case 0: getMenuText = ”Eintrag 1”
    6. Case 1: getMenuText = ”Eintrag 2”
    7. Case 2: getMenuText = “Eintrag 3”
    8. End Select
    9. Case mnuID_SUBMENU1
    10. ...
    Display All
    Mit “mnuTitle” wird angegeben, welche Menü-Überschrift über dem Menü anzuzeigen ist. Im Beispiel lautet die Überschrift „Hauptmenu“

    Das Menü umfasst 3 Einträge mit den Texten „Eintrag 1“ bis „Eintrag 3“. Um diese zu unterscheiden sind die in einer verschachtelten Select Case Struktur untergebracht und werden anhand des Parameters „mnuEntry“ unterschieden. Kleinster Wert ist hier 0 für den 1. Eintrag. Damit ergibt sich der höchste mnuEntry-Wert = 2 für 3 Einträge.

    Damit die Menüverwaltung weiß wie viele Einträge vorhanden sind, wird in „mnuEntries“ die Anzahl Menüeinträge -1 angegeben. Oder eben der maximale Index, der mit Case für die Einträge abgefragt wird.

    Auf diese Weise werden alle Menüs und Submenüs definiert.

    Da jedes Menü eine eindeutige ID benötigt, werden diese im Modul „Menu.inc“ im Abschnitt „Konstanten für die jeweiligen Menüs/Submenüs“ definiert.

    Ein Blick in dieses Modul sollte noch vorhandene Unklarheiten beseitigen.
    Um das eben definierte Menü nun anzuzeigen, musste man nur selectMenu(mnuID_MAIN) aufrufen. Um zu scrollen die Routinen setMenuUp() und setMenuDown() aufrufen.

    BASCOM Source Code

    1. ‘ Hauptschleife
    2. Do
    3. If getKeyLeft()=TRUE then Call selectMenu(mnuID_MAIN)
    4. If getKeyUp()=TRUE then Call setMenuUp()
    5. If getKeyDown()=TRUE then Call setMenuDown()
    6. Loop

    Ausgewählter Menü-Eintrag


    Der aktuell selektierte Menüeintrag wird in der Variablen „mnuSelectedEntry“ gespeichert. Der 1. definierte Eintrag enthält den Index Null, der nächste Eintrag den Index 1 usw.
    Um also bei drücken der Taste OK auf den gewählten Eintrag zu reagieren, kann eine Select Case Struktur verwendet werden, die auf mnuSelectedEntry zugreift. Das sieht dann exemplarisch so aus:

    BASCOM Source Code

    1. If getKeyOK()=True then ‘ Taste OK innerhalb eines Menüs gedrückt
    2. Select Case mnuSelectedEntry
    3. Case 01. Eintrag gewählt
    4. ‘ Aktion bei Eintrag 1
    5. Case 12. Eintrag gewählt
    6. ‘ Aktion bei Eintrag 2
    7. End Select
    8. End If
    Auf diese Weise kann einfach auf Menüs reagiert werden, Submenüs aufgerufen werden oder in einen anderen State gewechselt werden.

    Anmerkung zur Statemachine

    Dieses Demo verwendet eine Statemachine, um Werte (Einstellungen) zu bearbeiten, die unterschiedlichen Menüs anzuzeigen und zu bedienen. Die Funktionsweise der Statemachine setzte ich hier voraus. Wer mit der Statemachine nicht vertraut sein sollte, dem empfehle ich im Lexikon den Beitrag „Statemachine Tutorial“ mal genauer ansehen.

    Für jeden Zustand (State) der Statemachine ist eine eindeutige ID zu definieren. In der Demo sind diese Zustände im Modul „Statemachine.inc“ im Abschnitt „Zustände der Statemachine“ definiert. Die Namensdefinition der Zustände sollten mit dem Prefix „ST_“ (für STate) beginnen, um eine Eindeutige Zuordnung der Konstante als State zu erkennen.

    Für die Darstellung der Menüs werden ebenfalls Zustände verwendet. Für jedes Menü, egal ob Haupt- oder Sub-Menü, wird jeweils ein State definiert.

    Im Demo habe ich das Prefix „ST_“ für „normale“ States verwendet, während ich für Menü-States das Prefix „ST_MNU_“ (für STate MeNU) verwendet habe. Damit sind Zustände, die Menüs betreffen, von den anderen deutlich zu unterscheiden. Nach dem Prefix ist natürlich noch ein Zusatz anzubringen, der den State (Verwendung) genauer beschreibt.
    Auch hier hilft ein Blick in das Modul „Statemachine.inc“, falls noch Unklarheiten bestehen sollten.

    Statemachine Wecker.pdf

    Die obige Tabelle definiert den Ablauf der Statemachine.
    Der Start-Zustand (ST_IDLE) ist gelb markiert und entspricht der Anzeige nach dem Einschalten. In der rechten Spalte wird kurz bemerkt, was in dem Zustand passiert. Mit Taste Left kann ins Hauptmenü gewechselt werden. Mit Taste Left kommt man auch wieder zurück. Mit den Tasten Up und Down kann man sich im Menü bewegen bzw. Werte erhöhen oder verringern, etwas Ein- oder Ausschalten.
    Abhängig vom Zustand hat auch die Taste OK unterschiedliche Funktionen.
    Innerhalb eines Menüs kann z.B. ein Untermenü aktiviert werden. Oder man landet in der Bearbeitung eines Wertes.
    In obiger Tabelle ist alles genauestens aufgebröselt. Das Zeichen „->“ bedeutet einen Zustandswechsel in den angegeben Zustand. Z.B. „-> ST_MNU_MAIN“ bedeutet den Wechsel in den Zustand „ST_MNU_MAIN“

    Tastenabfrage mit Debounce

    Der Befehl Debounce hat den Vorteil, dass zum einen die Taste entprellt abgefragt werden kann und zum Anderen nur einmal pro Tastendruck auslöst. Ein weiterer Vorteil ist, dass der Befehl in Bascom bereits implementiert und einfach anzuwenden ist.

    Leider erwartet der Befehl immer eine Routine, die bei Tastenbetätigung angesprungen wird. Wenn nun aber auf eine Taste viele Funktionen zukommen wie Wert erhöhen, Hoch scrollen, Funktion einschalten etc. ,dann müssten mehrere Routinen für nur eine Taste definiert werden.

    Daher habe ich das Modul „Tasten.inc“ verwendet, in dem immer dieselben Routinen angesprungen werden. Taste Left springt immer in Routine „TasteLeft:“, TasteUp immer in Routine „TasteUp:“ usw.

    In den Routinen werden lediglich unterschiedliche Flags in der Variablen KeyPressed (as Byte) gesetzt. Auf diese Weise können erst mal bis 8 Tasten angeschlossen werden. Wir benötigen nur vier.

    Um nun im Hauptprogramm die gedrückten Tasten zu ermitteln, werden dort in der Hauptschleife unabhängig von der Statemachine die vier Debounce-Befehle für die vier Tasten abgearbeitet.

    In der Statemachine (Hauptschleife) wird dann in den unterschiedlichen Zuständen auf diese gesetzten Flags in der Variablen KeyPressed zugegriffen. Um den Zugriff zu vereinfachen und die Flags bei Abfrag zu löschen, wurden vier Abfrage-Routinen in „Tasten.inc“ implementiert.

    Alle Tasten werden dann mit den Funktionen „getKey…()“ abgefragt. Die Punkte stehen hierbei für die Tasten „Left“, „Up“, „Down“ und „OK“. Die Funktionen liefern ein TRUE zurück, wenn die Taste betätigt wurde.

    Die Vorgehensweise hat den Vorteil, dass die Tastenabfrage im Hintergund (parallel zur Statemachine) durchgeführt wird.

    Die Entscheidung fiel auf die Verwendung von Befehl Debounce, weil die Tasten ohne Interrupt-Verwendung abgefragt werden können und die Staten erneut gedrückt werden müssen, um eine weitere Aktion auszulösen.
    Alternativ wäre die Abfrage auch mit Timer im Hintergrund möglich gewesen. Auch hierbei wären die gedrückten Tasten als Flags in der Variablen KeyPressed wieder zu finden gewesen.

    Hinweise zum Ausprobieren der Demo

    Ich empfehle erst mal ein 3-Zeiliges Display mit 16 Zeichen zu verwendet um erste Erfahrungen mit dem Menümanagement zu machen.Wer keines hat, sollte ein größeres Display anschließen. Ein 20x4 Display beispielsweise. Ohne die Erfahrung sollte man auf ein 2-Zeiliges Display vorerst verzichten.
    Wer keinen Titel für die Menüs möchte, kann darauf verzichten. Muss allerdings im Code an den richtigen Schrauben drehen. Dann sind auch 2-Zeilige Menüs mit Scrollen möglich.
    Tasten und Display werden in der „Config.inc“ definiert.
    Die Darstellung der Menü’s in Modul „Menu.inc“.

    Für Fragen/ Anmerkungen habe ich immer ein offenes Ohr (PN schreiben).

    Mitch64

    149 times viewed