Welche Entwurfsmuster kann ich verwenden, um Benutzereingaben zu verarbeiten und Aktualisierungen anzuzeigen?

7

Ich habe genug Produkte mit eingebetteten Mikrocontrollern und nicht reagierenden Benutzeroberflächen verwendet, um zu wissen, dass dadurch ein Produkt hergestellt oder beschädigt werden kann. Selbst eine geringfügige Verzögerung oder Verzögerung zwischen dem Drücken der Taste und der Aktualisierung der Anzeige oder das Ignorieren oder doppelte Zählen der Tastendrücke kann äußerst frustrierend sein.

Welche Entwurfsmuster können verwendet werden, um Benutzereingaben (über Schaltflächen) zu verarbeiten und Anzeigen zu aktualisieren, ohne diese Probleme zu verursachen?

Ich verwende derzeit die folgende Art von Muster (dies wird auf das Nötigste vereinfacht):

#include <Bounce.h>

#define GREEN_LED       6 // Pin for green LED

#define BUTTON_PIN      15 // Pin for button - uses external pull down so active high

#define DISPLAY_REFRESH_INT 100 // How many MS between display updates

Bounce button = Bounce(BUTTON_PIN, 5);

// To signal between the button read and the display update
bool ledState = false;

// Used to keep track of last display update
long displayUpdate = 0;

void setup()
{
    pinMode(GREEN_LED, OUTPUT);
    pinMode(BUTTON_PIN, INPUT);
}

void loop()
{
    // Read the button status
    if (button.update())
    {
        if (button.risingEdge())
        {
            ledState = !ledState;
        }
    }

    // Update the display periodically
    if (millis() - displayUpdate > DISPLAY_REFRESH_INT)
    {
        displayUpdate = millis();

        digitalWrite(GREEN_LED, ledState);
    }

}

Welche anderen Möglichkeiten gibt es? Lohnt es sich jemals, Pin-Interrupts zum Lesen von Tasten zu verwenden (wenn wir die Akkulaufzeit nicht berücksichtigen!)?

Cybergibbons
quelle

Antworten:

6

Die Antwort hängt genau davon ab, wie die Skizze auf die Benutzerinteraktion reagieren soll.

Externe Interrupts
Wenn die Interaktion davon abhängt, sehr genau auf die steigende oder fallende Flanke eines Eingabeereignisses zu reagieren (wie in Ihrem Beispiel), ist nach Möglichkeit ein externer Interrupt der richtige Weg.

Dies ist besonders wichtig, wenn das Eingabeereignis kürzer sein kann als die Dauer eines Anrufs an loop(). In diesem Fall können aufeinanderfolgende Versuche, die Eingabe abzufragen, die Statusänderung vollständig verfehlen. Es ist jedoch ziemlich selten, dass ein Benutzereingabeereignis so kurz ist, es loop()sei denn , die Ausführung ist recht langsam.

Externe Hardware, um Hilfe zu erhalten, z. B. durch einen Eingang, der einrastet, bis er vom Mikrocontroller gelesen und zurückgesetzt wird.

Verbessern der Abruffrequenz
Externe Interrupts sind häufig nicht möglich (z. B. weil der Chip nur eine begrenzte Anzahl unterstützt) oder sie sind für eine bestimmte Anwendung einfach übertrieben / unnötig.

In diesen Fällen kann es immer noch möglich sein, die Timing-Genauigkeit bei der Verwendung von Polling zu verbessern. Eine Möglichkeit besteht darin, alle verschiedenen Teile der Hauptschleife in separate Funktionen aufzuteilen. Bei jeder nachfolgenden Iteration von loop()wird die nächste Funktion aufgerufen und dann zur ersten umgebrochen. Zwischen jeder dieser Funktionen kann die Schaltfläche abgefragt werden. Hier ist ein einfaches Beispiel:

unsigned int g_section = 0;

void setup() { }

void pollButton()
{
    //...
}

void runSection0()
{
    //...
}

void runSection1()
{
    //...
}

void runSection2()
{
    //...
}

void loop()
{
    // Poll the button on every pass:
    pollButton();

    // Execute the next section of the main code:
    switch (g_section)
    {
    case 0: runSection0(); break;
    case 1: runSection1(); break;
    case 2: runSection2(); break;
    }

    // On the next iteration, run the next section:
    if (++g_section > 2) g_section = 0;
}

Ein wichtiges Problem bei diesem Ansatz ist, dass es sehr schwierig ist, dafür zu sorgen, dass jede dieser runSectionX()Funktionen dieselbe Ausführungszeit benötigt. Dies kann dazu führen, dass die Schaltfläche von einer Iteration zur nächsten inkonsistent abgefragt wird. Wenn es aber schnell genug ist, sollte es kein allzu großes Problem sein.

Aktualisierung der Anzeige
eine Anzeige aktualisiert ist leider oft sehr langsam, im Vergleich zu vielen anderen Operationen. Das bedeutet, dass Sie sehr vorsichtig sein müssen, wo Sie es tun. Innerhalb einer Interrupt-Serviceroutine ist dies häufig nicht angemessen, da dies andere Zeitprobleme in Ihrer Skizze verursachen kann.

Realistisch gesehen müssten Sie dies in der Hauptschleife tun. Wenn Sie es jedoch unbedingt schnell ausführen müssen, kann es in das oben angegebene Beispiel für schnellere Abfragen aufgenommen werden - dh die Anzeige wird bei jeder Iteration aktualisiert, genau wie bei der Abfrage. Sie müssen jedoch vorsichtig sein, um teilweise Aktualisierungen zu vermeiden.

Peter Bloomfield
quelle
Dies ist übrigens eine großartige Antwort. Genau die Art von Antwort, die die Site benötigt.
Cybergibbons
2

Für die Programmsteuerung habe ich für eines meiner Projekte ein Menü-Handling-Framework erstellt und die Quelle auf Github geteilt . Beachten Sie, dass dies für chipKIT gilt, der Menübehandlungscode jedoch generisch ist. Nachdem ich meine 3. oder 4. große If-else-If- und Switch-Struktur aufgebaut hatte, entschied ich, dass es einen besseren Weg geben musste. Gutschrift für die Quelle der statischen Menüstrukturen.

Ich benutze nur die Hauptschleife, um den Knopf zu überprüfen.

Ich möchte sagen, dass der erste Schritt darin besteht, einen TFT zu bekommen. Wenn Sie es versuchen, werden Sie nie zurückkehren ... Ich liebe besonders den Adafruit 1.8 "TFT mit Joystick. Arduinos sind langsam genug, dass ich mich nicht erinnern kann, Benutzereingaben entprellt zu haben.

Chris K.
quelle
Ich mag die Art und Weise, wie Sie das gemacht haben - es ist verständlich, sauber und sehr klein. Ich habe versucht, eine Bibliothek (MenuBackend) zu verwenden, aber es ist für neue Programmierer (mit Rückrufen) schwer zu verstehen und kann nicht in PROGMEM sitzen.
Cybergibbons
Ich finde, dass ein Entprellen oft nicht erforderlich ist, aber einige Schalter scheinen viel lauter zu sein als andere. Die aufrechten, kantenmontierten taktilen PCB-Schalter scheinen bei weitem die schlechtesten zu sein, vielleicht weil Benutzer diese häufig nach vorne hebeln, anstatt sie zu drücken. Das Entprellen ist so einfach, dass ich es jetzt fast immer mache.
Cybergibbons
Entschuldigung für alle Fragen - warum ein TFT? Ich arbeite derzeit mit einem 128x64-LCD, das viele Vorteile hat (hauptsächlich physisch groß). Auch billig.
Cybergibbons
1
@ Cybergibbons ah, habe keine Erwähnung einer Anzeige gesehen. Der, den ich mag, ist ein fertiger Schild, billig genug, und man kann Dinge mit Farben machen. Hier ist ein Build-Bericht meines letzten Projekts: forums.adafruit.com/…
Chris K
1

Sie können unterbrechen, anstatt abzufragen, wenn Sie die Schaltflächen in der Hardware entprellen können. Andernfalls werden Sie viel mehr Interrupts als beim Drücken von Tasten ausführen und müssen diese dennoch in der Software entprellen.

Bei der Hauptfrage kann eine Sammlung von Zustandsautomaten jedoch zumindest für die Benutzeroberfläche sehr nützlich sein und Ihnen die meisten Vorteile von Multitasking ohne den erforderlichen AM / T-Kernel bieten. kann in RAM-begrenzten Geräten leider sein, weil

Die Technik könnte auf diesen kleinen Geräten besonders nützlich sein, aber angesichts der strengen RAM-Einschränkungen ist es eine Herausforderung, die Daten Ihrer Zustandsmaschine zwischen Flash und RAM aufzuteilen, um sie anzupassen.

JRobert
quelle
0

Als Chris K habe ich auch eine statische Menübibliothek mit einer Vielzahl von Ausgabeoptionen wie Seriell, LCD, TFT usw. implementiert. Ich habe sie gestartet, um in einem Freundeprojekt zu helfen, und ich teile sie auch für Open Source Leute, die mit ähnlichen Problemen hoffen, dass Sie es nützlich finden können.

https://github.com/neu-rah/ArduinoMenu

neu-rah
quelle