Debug-Monitor mit Nokia LCD Display 5110

Es gibt Situationen, in denen man seinen Arduino-Sketch debuggen möchte, aber den USB-Serial-Anschluß aus unterschiedlichen Gründen nicht benutzen kann. Für diesen Fall habe ich diesen Debugmonitor gebaut:

DebugMonitor

Dieser Monitor wird mit nur 3 Anschlüssen mit dem Master-Arduino verbunden : Ground, VCC +5V und Serial-In. Er besteht aus einem Arduino ProMicro und einem LCD Display Nokia 5110.

Als TX-Ausgabe-Pin kann im Master-Arduino jeder beliebige Pin verwendet werden. Für die Sende-Funktion ist keine Interupt-Funktionalität erforderlich. Als RX-Pin kann man ebenfalls jeden freien Pin deklarieren. Es werden vom Debug-Monitor keine Daten gesendet.

Das Display umfaßt 6 Zeilen mit 14 Zeichen.  Zahlen können in einem größeren Font dargestellt werden. In diesem Font können nur die Ziffern 0 bis 9 und das Minus-Zeichen ausgegeben werden.

Das Display rollt automatisch. Man kann also fortwährend Zeile um Zeile zum Monitor schicken. Carriage return und linefeed werden erkannt und verarbeitet.

Durch das automatische Rollen sind immer die letzten 6 Zeilen sichtbar. Das Rollen beschränkt sich jedoch auf die normale Schrift. Die Zahlen in der Großschrift werden nicht mit gerollt.

Man kann mit einer Reihe von Kommandos die Anzeige steuern: Bildschirm löschen, Cursor positionieren, Font umschalten. Auf dem Arduino ProMirco gibt es zwei Leds (gelb und grün). Diese können zusätzlich gesteuert werden : ein, aus und blinken.

Die Kommandos werden in ein Start- und ein Endezeichen eingeschlossen (im Beispiel „$“ und  „#’“). Dazwischen  können mehrere Kommandos stehen, sie werden durch ein Trennzeichen (im Beispiel „,“) getrennt.

Beispiel :

mySerial.print(“$CLS,Y3,X15,LGEE#Hallo Welt“);

Der Bildschirm wird gelöscht, auf Zeile 4, X-Position Pixel 16 positioniert und die gelbe Led eingeschaltet. Dann wird „Hallo Welt“ ausgegeben.

Im Programm kann man die Vereinbarung der Steuerzeichen Start- Ende- und Trennzeichen leicht ändern. Um keine druckenden Zeichen zu verlieren, bieten sich kleine Zahlen an, die im Print-Statement des Mutter-Arduino oktal geschrieben werden. Das sieht dann z.B. so aus “ …  \001CLS\002  …“ usw.
Die Kommandos und die Anschlußbelegung des Arduino ProMicro und Nokia LCD 5110 sind im Coding dokumentiert.

Hier das Coding (ohne Fonts) :

//----------------------------------------------------------------------
// Debug Monitor mit LCD Nokia 5110
//----------------------------------------------------------------------
//
// Vorlage zum Ansteuern des Display war ein Programm von Nathan Seidle, 
// Spark Fun Electronics; vom 17.07.2011; Beerware license
//
// Ich schulde ihm Dank und ein großes Bier ;-)
//
//
// PinBelegung Arduino Pro Micro : 
// Achtung es gibt verschiedene Versionen des Nokia Displays! 
// Unbedingt Pinbelegung vergleichen !!
//
// Tx0    
// Rx1    Serial Eingang zum TX des Master Arduino
// Gnd    
// Gnd    
// D2     
// D3     SCLK Clk LCD Pin 7  Clock
// D4     SDIN Din LCD Pin 6  MOSI
// D5     DC       LCD Pin 5  Command-Control
// D6     RST      LCD Pin 4  Reset
// D7     SCE CE   LCD Pin 3  Chip enabled
// D8     
// D9     

// RAW
// Gnd    Gnd LCD Pin 2
// RST
// VCC    VCC LCD Pin 1
// A3
// A2
// A1
// A0
// D15     
// D14     
// D16     
// D10     

// Zusätzlich :
// LCD LED Pin 8  über 470 Ohm an Vcc
// VCC und Gnd zum Master Arduino


//----------------------------------------------------------------------
// Nokia LCD-Display 5110

#define PIN_SCE   7 
#define PIN_RESET 6 
#define PIN_DC    5 
#define PIN_SDIN  4 
#define PIN_SCLK  3 

#define LCD_COMMAND 0 
#define LCD_DATA  1


//----------------------------------------------------------------------
// Zeichengroesse im Textmode : 
#define ZEICHEN_WW 6
#define ZEICHEN_HH 8

// Displaygröße in Pixel
#define LCD_WW     84
#define LCD_HH     48

//Displaygröße in Anzahl Zeichen (Textmode) 
#define ANZ_ZEICHEN LCD_WW/ZEICHEN_WW
#define ANZ_ZEILEN  LCD_HH/ZEICHEN_HH

// Index Grenzen
#define MAX_ZEICHEN_IDX ANZ_ZEICHEN-1
#define MAX_ZEILEN_IDX  ANZ_ZEILEN-1

//----------------------------------------------------------------------
// Scrollbuffer 
char ScrollBuffer[ANZ_ZEILEN][ANZ_ZEICHEN];
byte ScrollFlag = 0;

//----------------------------------------------------------------------
/// Die Fonts sind im Include definiert daher :
extern uint8_t ASCII[][5];
extern uint8_t NUMS[]; 

//----------------------------------------------------------------------
// Input Mode 0 = Zeichen einlesen und anzeigen, 
//            1 = Kommando einlesen
#define IM_TEXT 0
#define IM_CMD 1
byte InputMode = IM_TEXT;

#define FONT_TEXT 0
#define FONT_BIGNUM 1
byte DisplayFont = FONT_TEXT;

char RecvChar;
byte Cursor_x;  // Pixel
byte Cursor_y;  // Zeilen a 8 Pixel

//----------------------------------------------------------------------
// gruene und gelbe LED steuern
#define LED_AUS   0
#define LED_EIN   1
#define LED_BLINK 2

byte led_mode_gelb  = LED_AUS;
byte led_mode_gruen = LED_AUS;
unsigned int Next_Led_blink = 0;


//----------------------------------------------------------------------
// Kommandos interpretieren
#define CMD_MAXLEN 8
char cmdBuffer[CMD_MAXLEN];
byte cmdPointer;
#define CMD_START     '$'  
#define CMD_SEPARATOR ','
#define CMD_ENDE      '#'

// Kommandos z.B. $CLS,Y2#  = Bildschirm löschen 
//                            und auf Zeile 3 positionieren
// ohne CrLf senden !

// CLS   Clear screen
// Xnn   X-Position auf Pixel nn (0-83)
// Yn    Y-Position auf Zeile  n (0-5)
// FT    Font Text
// FN    Font Nummern gross
// LGNE  gruene LED ein
// LGNA  gruene LED aus
// LGNB  gruene LED blinken
// LGEE  gelbe LED ein
// LGEA  gelbe LED aus
// LGEB  gelbe LED blinken


//----------------------------------------------------------------------
// Routinen declarieren
void LCDInit(void);
void LCDClear(void);
void gotoXY(int x, int y);
void LCDCharacterText(char character);
void LCDCharacterBigNumber(char character);
void LCDWrite(byte data_or_command, byte data);

byte PickZahl(char * p);
void LED_Handling();
void clr_scroll_buffer();
void scroll();
void init_cmdBuffer();

//----------------------------------------------------------------------
void setup()
{
  Serial1.begin(9600);

  LCDInit(); 
  delay(100);
  LCDClear();
  delay(100);
  Next_Led_blink = millis()+300;
}

//----------------------------------------------------------------------
void loop() { 
  byte tmp_byte;
  if (Serial1.available()){
    RecvChar = Serial1.read();
    if (InputMode == IM_TEXT){
      switch (RecvChar){
      case CMD_START:
        init_cmdBuffer();
        InputMode = IM_CMD;
        break;
      case 13: // Wagenruecklauf
        Cursor_x=0;
        gotoXY(Cursor_x,Cursor_y);
        break;
      case 10: // Zeilenvorschub
        if(ScrollFlag == 1){
          scroll();
        }
        if( Cursor_y<MAX_ZEILEN_IDX){
          Cursor_y++;
          gotoXY(Cursor_x,Cursor_y);
        } 
        else {
          Cursor_y = MAX_ZEILEN_IDX;
          ScrollFlag = 1;
        }
        break;
      default :
        if (DisplayFont == FONT_TEXT ){
          if(ScrollFlag == 1){
            scroll();
          }
          LCDCharacterText(RecvChar);
        }
        else {  
          LCDCharacterBigNumber(RecvChar);
        }
      }
    } 
    else {
      // im Command-Modus 
      if (RecvChar == CMD_ENDE || RecvChar == CMD_SEPARATOR){
        if (RecvChar == CMD_ENDE){        
          InputMode = IM_TEXT;
        }
        if (strncmp(cmdBuffer,"CLS",3)==0){
          LCDClear();
        }
        if (strncmp(cmdBuffer,"FT",2)==0){
          DisplayFont = FONT_TEXT;
        }
        if (strncmp(cmdBuffer,"FN",2)==0){
          DisplayFont = FONT_BIGNUM;
        }
        if (cmdBuffer[0]=='X'){
          tmp_byte = PickZahl(&cmdBuffer[1]);
          if (tmp_byte<(LCD_WW - ZEICHEN_WW)){
            Cursor_x=tmp_byte;
            gotoXY(Cursor_x,Cursor_y);
          }
        }
        if (cmdBuffer[0]=='Y'){
          tmp_byte = PickZahl(&cmdBuffer[1]);
          if (tmp_byte<=MAX_ZEILEN_IDX){
            Cursor_y=tmp_byte;
            gotoXY(Cursor_x,Cursor_y);
          }
        }
        if (strncmp(cmdBuffer,"LGNA",4)==0){
          led_mode_gruen = LED_AUS;
        }
        if (strncmp(cmdBuffer,"LGNE",4)==0){
          led_mode_gruen = LED_EIN;
        }
        if (strncmp(cmdBuffer,"LGNB",4)==0){
          led_mode_gruen = LED_BLINK;
        }
        if (strncmp(cmdBuffer,"LGEA",4)==0){
          led_mode_gelb = LED_AUS;
        }
        if (strncmp(cmdBuffer,"LGEE",4)==0){
          led_mode_gelb = LED_EIN;
        }
        if (strncmp(cmdBuffer,"LGEB",4)==0){
          led_mode_gelb = LED_BLINK;
        }
        init_cmdBuffer();
      }
      else{
        if (cmdPointer < (CMD_MAXLEN-1)){
          cmdBuffer[cmdPointer++]=RecvChar;
        }
      }
    }
  }

  LED_Handling();
}

//----------------------------------------------------------------------
// 1 oder 2 Stellige Zahl einlesen
byte PickZahl(char * p){

  byte tmp = 0;
  if (*p>='0' && *p<='9') tmp = *p-'0';
  p++;
  if (*p>='0' && *p<='9') tmp = tmp*10 + *p-'0';

  return tmp;
}
//----------------------------------------------------------------------
// LED schalten, ein / aus / blinken (300ms) 
void LED_Handling(){

  static byte blinky = 0;
  if (millis()<Next_Led_blink) return;

  Next_Led_blink = millis()+300;

  blinky = 1- blinky;

  if (led_mode_gelb == LED_BLINK){
    if (blinky) RXLED0;
    else RXLED1;
  }
  if (led_mode_gelb == LED_AUS) RXLED0;
  if (led_mode_gelb == LED_EIN) RXLED1;

  if (led_mode_gruen == LED_BLINK){
    if (blinky) TXLED0;
    else TXLED1;
  }
  if (led_mode_gruen == LED_AUS) TXLED0;
  if (led_mode_gruen == LED_EIN) TXLED1;

}  

//----------------------------------------------------------------------
void clr_scroll_buffer(){
  byte z,s;

  for (z=0;z<=MAX_ZEILEN_IDX;z++)
    for (s=0;s<=MAX_ZEICHEN_IDX;s++)
      ScrollBuffer[z][s]=' ';
      
  ScrollFlag=0;
}

//----------------------------------------------------------------------
void scroll(){
  byte z,s;
  int index;
  char character;

  // alle Zeilen um eine Zeile nach oben
  for (z=1;z<=MAX_ZEILEN_IDX;z++){  
    gotoXY(0,z-1); 
    for (s=0;s<=MAX_ZEICHEN_IDX;s++){
      character=ScrollBuffer[z][s];
      ScrollBuffer[z-1][s]=character;
      for (index=0; index<5; index++){
        LCDWrite(LCD_DATA, ASCII[character - 0x20][index]);
      }
      LCDWrite(LCD_DATA, 0x00); 
    }
  }

  // unterste Zeile löschen
  for (s=0;s<=MAX_ZEICHEN_IDX;s++)
    ScrollBuffer[MAX_ZEILEN_IDX][s]=' ';

  gotoXY(0,MAX_ZEILEN_IDX); 
  for (index=0; index<LCD_WW; index++){
    LCDWrite(LCD_DATA, 0x00);

  }

  Cursor_y=MAX_ZEILEN_IDX;
  gotoXY(Cursor_x,Cursor_y); 
  ScrollFlag = 0;
}

//----------------------------------------------------------------------
void init_cmdBuffer(){
  int i;
  cmdPointer = 0;
  for (i=0;i<CMD_MAXLEN;i++) cmdBuffer[i]=0;
}

//----------------------------------------------------------------------
void LCDInit(void) {
  // This sends the magical commands to the PCD8544

  // Configure control pins
  pinMode(PIN_SCE, OUTPUT);
  pinMode(PIN_RESET, OUTPUT);
  pinMode(PIN_DC, OUTPUT);
  pinMode(PIN_SDIN, OUTPUT);
  pinMode(PIN_SCLK, OUTPUT);

  // Send command to commands to the PCD8544 
  // Reset the LCD to a known state
  digitalWrite(PIN_RESET, LOW);
  digitalWrite(PIN_RESET, HIGH);


  // configurate the LCD
  LCDWrite(LCD_COMMAND, 0x21); //Tell LCD that extended commands follow
  LCDWrite(LCD_COMMAND, 0xB0); //Set LCD Vop (Contrast): Try 0xB1(good @ 3.3V) or 0xBF if your display is too dark
  LCDWrite(LCD_COMMAND, 0x04); //Set Temp coefficent
  LCDWrite(LCD_COMMAND, 0x14); //LCD bias mode 1:48: Try 0x13 or 0x14
  LCDWrite(LCD_COMMAND, 0x20); //We must send 0x20 before modifying the display control mode
  LCDWrite(LCD_COMMAND, 0x0C); //Set display control, normal mode. 0x0D for inverse
}

//----------------------------------------------------------------------
void LCDClear(void) {

  for (int index = 0 ; index < (LCD_WW * LCD_HH / 8) ; index++)
    LCDWrite(LCD_DATA, 0x00); // LCD löschen

  clr_scroll_buffer();

  Cursor_x=0;
  Cursor_y=0;

  gotoXY(Cursor_x,Cursor_y); 


}

//----------------------------------------------------------------------
void gotoXY(int x, int y) {
  LCDWrite(0, 0x80 | x);  // Column
  LCDWrite(0, 0x40 | y);  // Row  
}

//----------------------------------------------------------------------
void LCDCharacterText(char character) {

  if (character>=' ' && character <= 0x7f ){
    for (int index = 0 ; index < 5 ; index++)
      LCDWrite(LCD_DATA, ASCII[character - 0x20][index]);

    LCDWrite(LCD_DATA, 0x00); //Senkrechter Abstandsstrich

    // Zeichen in Scroll-Tabelle eintragen
    if (Cursor_y <= MAX_ZEILEN_IDX && (Cursor_x/ZEICHEN_WW) <= MAX_ZEICHEN_IDX) ScrollBuffer[Cursor_y][Cursor_x/ZEICHEN_WW] = character;

    // Position mitrechnen
    Cursor_x+=ZEICHEN_WW;
  }
}

//----------------------------------------------------------------------
void LCDCharacterBigNumber(char character) {
  // Nur Ziffern '0' bis '9' und '-' sind definiert.
  // 12 Pixel breit und 16 Pixel hoch
  // Keine Berücksichtigung für Scrolling 

  char myChar;

  if (Cursor_y>0){
    if (character=='-')myChar = '9'+1;
    else myChar = character;
    if (myChar>='0' && myChar <= '9'+1){

      // Obere Hälfte ausgeben 
      gotoXY(Cursor_x,Cursor_y-1);
      for (int index = 0 ; index < 12 ; index++)  
        LCDWrite(LCD_DATA, NUMS[(myChar - 48)*24+index]);

      // Untere Hälfte ausgeben 
      gotoXY(Cursor_x,Cursor_y);
      for (int index = 0 ; index < 12 ; index++)  
        LCDWrite(LCD_DATA, NUMS[(myChar - 48)*24+index+12]);

      Cursor_x+=12;
    }
  }
}

//----------------------------------------------------------------------
void LCDWrite(byte data_or_command, byte data) {
  //There are two memory banks in the LCD, data/RAM and commands. This 
  //function sets the DC pin high or low depending, and then sends
  //the data byte
  digitalWrite(PIN_DC, data_or_command);
  //Send the data
  digitalWrite(PIN_SCE, LOW);
  shiftOut(PIN_SDIN, PIN_SCLK, MSBFIRST, data);
  digitalWrite(PIN_SCE, HIGH);
}

Das Coding zum download:

Ausblick : Man kann natürlich noch mehr Ausgabe-Elemente einbauen und über Kommandos steuern z.B. weitere Leds oder einen Lautsprecher oder Summer usw.