Hallo,
nachdem ich vor kurzem ein 4x16 Display zusammen mit einer I2C Schnittstelle in Betrieb genommen habe, musste eine einfache Menüsteuerung her.
Die vorhandenen LCD Menüs waren mir alle zu komplex und vor allem wollte ich die Steuerung mit einem Rotary Encoder vornehmen.
Das Ergebnis wollte ich euch nicht vorenthalten, vielleicht gibt es ja weitere Interessenten.
Doch zunächst zu meinem Aufbau, den ich zunächst zur Entwicklung genutzt habe (mit Angabe der Bezugsquelle):
Arduino IDE022
Arduino Mega2560
4x16 LCD Display LCD-Modul TC1604A-01 online kaufen | Pollin.de
I2C LCD Display Controller Bausatz LCD/I²C-Modul online kaufen | Pollin.de
- hier die Anpassungen der Library beachten!
Rotary Encoder Encoder PANASONIC EVEQDBRL416B online kaufen | Pollin.de
Die Menueeinträge werden direkt im Code vorgenommen. Wenn das Display weniger Zeilen als Menueeinträge hat, werden die Menueeinträge durchgescrollt.
Ich habe den Code so gebaut, dass er auch mit einem Standard LCD Display funktioniert, dafür den #define Eintrag auskommentieren. Getestet wurde das mit einem 2x16 Standarddisplay. Bei einem Standarddisplay gehen nicht alle Funktionen verglichen zum I2C Display. Die im Code verwedeten unterschiedlichen Funktionen wurden deshalb auch in #ifdef Statements eingebunden.
Mit dem Encoder scrollt man durch die Menueeinträge (auf und ab). Wenn der letzte Menüeintrag erreicht wird, springt die Anzeige wieder nach vorne und umgekehrt. Ein Druck auf den Encoder wählt den Menüeintrag aus und nachfolgend können davon abhängige Programmteile angesteuert werden.
Der Code ist ausführlich kommentiert und sollte so auch von Anfängern verstanden werden. Zum debuggen sind noch Funktionen für die serielle Schnittstelle im Code (momentan auskommentiert).
Sicher kann man vieles besser, einfacher oder eleganter machen. Ich zähle auf jeden Fall noch lange nicht zu den Profis.
An dieser Stelle vielen Dank an Nick Gammon, von ihm stammt der Code für den Encoder.
Hier mein Code:
// Simple menu for LCD displays.
// Author: Reinhard Nickels 02.01.2012
// Selection of menu item with a rotary encoder (eg. http://www.pollin.de/shop/dt/Njg2OTU3OTk-/Bauelemente_Bauteile/Passive_Bauelemente/Potis_Trimmer_Encoder/Encoder_PANASONIC_EVEQDBRL416B.html)
// Rotary encoder example based on http://arduino.cc/forum/index.php/topic,62026.msg449681.html#msg449681 from Nick Gammon
// Wiring: Connect common pin of encoder to ground.
// Connect pin A (one of the outer ones) to a pin that can generate interrupts (eg. D2)
// Connect pin B (the other outer one) to another free pin (eg. D5)
// Code was developed for I2C LCD displays, selection should be done with #define I2CDISPLAY value.
// Debug with serial interface can be activated with define DEBUG
//#define DEBUG
int dummy; // this a workaround to avoid error messages with #define and #ifdef, see http://code.google.com/p/arduino/issues/detail?id=206
#define I2CDISPLAY
#define MAXLINES 4 // defines the number of display lines
#define LCD_CHARACTERS 16
#ifdef I2CDISPLAY
#include <Wire.h>
#include <LiquidCrystal_I2C.h>
#define LCD_ADRESS 0x27
LiquidCrystal_I2C lcd(LCD_ADRESS,LCD_CHARACTERS,MAXLINES); // I2C LCD address is 0x27
#else
#include <LiquidCrystal.h>
LiquidCrystal lcd(12, 11, 5, 4, 3, 2); // RS, E, D4, D5, D6, D7 - the standard connection
#endif
#define PINA 2
#define PINB 7
#define PUSHP 3
#define INTERRUPT 0 // that is, pin 2
#define INTERRUPTB 1 // that is, pin 3
volatile boolean turned; // rotary was turned
volatile boolean fired; // knob was pushed
volatile boolean up; // true when turned cw
int CursorLine = 0;
int DisplayFirstLine = 0;
char* MenueLine[] = {"0. Hello","1. Test","2. BlaBla","3. something","4. pre Bot Line","5. End"};
int MenueItems;
// Interrupt Service Routine for a change to pushpin
void isrp ()
{
if (!digitalRead (PUSHP))
fired = true;
} // end of isr
// Interrupt Service Routine for a change to encoder pin A
void isr ()
{
if (digitalRead (PINA))
up = digitalRead (PINB);
else
up = !digitalRead (PINB);
turned = true;
} // end of isr
void setup ()
{
digitalWrite (PINA, HIGH); // enable pull-ups
digitalWrite (PINB, HIGH);
digitalWrite (PUSHP, HIGH);
attachInterrupt (INTERRUPT, isr, CHANGE); // interrupt 0 is pin 2
attachInterrupt (INTERRUPTB, isrp, FALLING); // interrupt 5 is pin 18
#ifdef I2CDISPLAY
lcd.init();
lcd.backlight();
#else
lcd.begin(LCD_CHARACTERS, MAXLINES);
#endif
lcd.setCursor(0,0);
lcd.print("Testing Menue!");
lcd.setCursor(0,1);
lcd.print("SW Ver 1.0");
delay(1500);
MenueItems = sizeof(MenueLine)/sizeof(MenueLine[0]);
#ifdef DEBUG
Serial.begin (9600);
Serial.println("Testing Menue!........");
Serial.print("Number of Menue Items ");
Serial.println(MenueItems);
#endif
lcd.clear();
print_menue();
#ifdef I2CDISPLAY
lcd.cursor_on();
#else
lcd.cursor();
#endif
lcd.blink();
} // end of setup
void loop ()
{
if (turned)
{
if (up)
move_up();
else
move_down();
turned = false;
}
else if (fired)
{
selection();
fired = false;
} // end if fired
} // end of loop
void print_menue() {
lcd.clear();
for (int i=0;i<MAXLINES;i++) {
lcd.setCursor(0,i);
lcd.print(MenueLine[DisplayFirstLine + i]);
}
lcd.setCursor(0,(CursorLine-DisplayFirstLine));
}
void move_down() {
if (CursorLine == (DisplayFirstLine+MAXLINES-1)) {
DisplayFirstLine++;
}
if (CursorLine == (MenueItems-1)) {
CursorLine = 0;
DisplayFirstLine = 0;
}
else {
CursorLine=CursorLine+1;
}
print_menue();
}
void move_up() {
if ((DisplayFirstLine == 0) & (CursorLine == 0)) {
DisplayFirstLine = MenueItems-MAXLINES;
}
else if (DisplayFirstLine == CursorLine) {
DisplayFirstLine--;
}
if (CursorLine == 0) {
CursorLine = MenueItems-1;
}
else {
CursorLine=CursorLine-1;
}
print_menue();
}
void selection() {
// integrate here your calls to selected sub (eg. with switch/case)
#ifdef DEBUG
Serial.print("Menueline ");
Serial.print(CursorLine);
Serial.println(" selected");
Serial.print("..this is Menuetext ");
Serial.println(MenueLine[CursorLine]);
#endif
lcd.clear();
lcd.print("You selected....");
lcd.setCursor(0,1);
lcd.print("Menue ");
lcd.print(CursorLine);
delay(1000);
print_menue();
}
Wenn man den konditionalen Code wegnimmt, bleibt ein gut überschaubarer Anteil übrig. Da die oben beschriebene HW Ausstattung meine Entwicklungsumgebung darstellt, wird dieser Code sicher bei mir noch oft zum Einsatz kommen. Im übrigen will ich nicht mehr auf die I2C LCD Ansteuerung verzichten. 4 Drähte statt 8 und zusätzlich programmgesteuertes Backlight - da bleiben Pins frei und der Breadboard Aufbau übersichtlich.
Wie immer: jeder Kommentar ist willkommen.
Gruß Reinhard