Balkenanzeige auf Text-LCD: Basisfragen zum Erstellen / Gebrauch von Libraries

Ziel:
Es soll auf einem LCD ein Balken angezeigt, dessen Länge "hochaufgelöst" von einem analogem Eingangssignal abhängt. "Hochaufgelöst" meint, daß bei den 57 Pixeln eines Zeichens dessen 5 Spalten als einzelne Striche über Sonderzeichen dargestellt werden. Ich also für einen Balken auf meinem 10-Spalten Display nicht 10 verschiedene Längen, sondern 510=50 darstellen kann.
Das funktioniert so auch problemlos, wenn ich im Hauptprogramm eingebetteten Code nutzeauf meinem 2 Zeilen * 10 Zeichen Text-LCD via I2C angebunden, Nutzung der Library "LiquidCrystal_I2C", Arduino Mini über IDE 1.5.5

Es gibt aber Probleme, wenn diese Routine auf eine selbstgeschriebene Lib ausgelagert wird. . Was mich verwundert, da diese von mir geschriebene Lib früher auf einem Nano auf einem 4bit parallel angebundenen LCD unter der IDE 0.22 funktionierte, und ich sie hier nur für das I2C LCD auf den neuen Libnamen umgestellt habe.)

Das LCD wird sehr unruhig (leicht flackernd), und die Sonderzeichen werden falsch oder gar nicht, gar ändernd anzeigt. Ich habe den Eindruck, das LCD wird mit ständig neuen (falschen) Sonderzeichen überschrieben wird. Die Version mit eingebettem COde ohne Lib Nutzung funktioniert Problemlos.
Wenn ich in meinem Hauptprogramm unabhängig von meiner Lib einen Wert auf das Display schreibe geht das zwar, aber das Display flackert immer noch. Auch versuchsweises Auskommentieren der Sonderzeichenzuordnungen hilft nichts gegen das Flackern.

Aufgrund der Unruhe des Displays bei Lib-Betrieb im Gegensatz zum eingebettetem Code habe ich den Verdacht, daß ich als Anfänger ein grundsätzliches Verständnisproblem beim Gebrauch einer Library habe. Mein Eindruck ist, das Display wird mehrfach initialisiert, bzw. evtl. bei jedem Aufruf. Ich hoffe, ich benutze hier die korrekten Begriffe:

Im eigentlichen Programm binde ich die LCD Liq "LiquidCrystal_I2C" ein, sowie meine Lib "LCDBar.h", initialisiere das LCD via "LiquidCrystal_I2C lcd(0x27)" und erzeuge eine Instanz meiner Lib "LCDBar Balken".

Innerhalb meiner eigenen Lib LCDBar.cpp binde ich ebenfalls die Libs "LiquidCrystal_I2C.h" und meine "LCDBar.h" ein, und initiiere dort mein LCD mit LiquidCrystal_I2C lcdB(0x27), also mit einer anderen Instanz lcdB als im Hauptprogramm lcd.
Fragen:

  • Ist denn die Doppel-Initiierung des LCD-Displays einmal mit lcd im eigentlichem Nutzprogramm, und mit lcdB in meiner Lib korrekt ? Wenn ich aber eine weglasse, oder den gleichen Namen nehme, dann gibt es Fehlermeldungen.
  • Oder muß ich in meiner Lib eine Doppeleinbindung des "LiquidCrystal_I2C.h" Headers mittels einer #ifndef endif Struktur verhindern ?
  • Oder woran könnte sonst die Unruhe im LCD, bzw. die sich ändernden Sonderzeichen liegen ?
    An der Hardware kanns ja nicht liegen, die Version ohne Lib-Gebrauch mit quasi identischem Nutzcode und Sonderzeichendefinition funktioniert ja. Und die "LiquidCrystal_I2C" Lib ist ja auch die gleiche.

Hier mein eigentliches Hauptprogramm:

/* ####################################
Das Programm zeigt auf einem 10*2 LCD Display ein Balkendiagramm von links nach rechts in abhängigkeit von einer Variable (z.B. Meßwert) an.
 Es werden Sonderzeichen definiert, um bei der 5*7 Matrix eine Feinauflösung von 5 Linien pro Zeichen zu ermöglichen
Die Startposition des Balkendiagramms und die Zeile können frei gewählt werden.
 Es wurde die Library LCDBar erstellt, mit den beiden Funktionen "Characterdefinition" zur Sonderzeichenerstellung (die teilweise gefüllten 5*7 rechtecke)
 und "BalkenLinks" um ab einer Startposition einen Balken in Abhängigkeit einer Variable erscheinen zu lassen.
Geht noch nicht so richtig, es sind zwar prinzipiell Balken zu sehen, die Sonderzeichendefinition wird aber anscheinend wiederholt überschrieben ?!
 ####################################
 */

//  LiquidCrystal Library
// include the library code:
#include <Wire.h>
#include <LiquidCrystal_I2C.h>
#include <LCDBar.h>
// initialize the library with the numbers of the interface pins
LiquidCrystal_I2C lcd(0x27);  // set the LCD address to 0x27

LCDBar Balken; // erzeugt eine Instanz Balken aus der Klasse LCDBar

// Variablendeklarationen für den Anzeigebalken
int Wert = 0;  // der anzuzeigende Rohwert
int MaxWert = 1000;   // der maximal anzuzeigende Wert
int sensorPin = A0;    // select the input pin for the potentiometer
int sensorValue = 0;  // variable to store the value coming from the sensor

// Definition der LCD-Größe, um sie für andere Displays zentral ändern zu können
const int LCDSpaltenzahl = 10;
const int LCDZeilenzahl = 2;


void setup()
{
  // set up the LCD's number of columns and rows:
  lcd.begin (LCDSpaltenzahl, LCDZeilenzahl);
  lcd.noCursor();
    lcd.clear();
  // Definition_Special_Characters();
//  CharacterdefinitionBar(); // für die Anzeige eines Balkendiagrammes für die Anzeige von positiven Werten
//Balken.CharacterdefinitionBar(); // für die Anzeige eines Balkendiagrammes für die Anzeige von positiven Werten
  // Alternativ:
  //   Balken.CharacterdefinitionLine(); // für  ein zentriertes Liniendiagramm für die Anzeige von positiven UND negativen Werten
}

void loop()
{
  // Lädt den analogen Eingangswert des Potis
  sensorValue = analogRead(sensorPin);
  Wert = sensorValue;
  // Zeigt den Wert als Zahl in der ersten Zeile an
  
  lcd.setCursor(0, 0);
  lcd.print("Wert:");
  lcd.print(Wert);
  lcd.print(" ");

  // -------------------------------------------------
  /*
  _Wert: der anzuzeigende Wert zwischen Null und _MaxWert. Werte außerhalb werden nicht angezeigt
  _MaxWert: der Skalenendwert, bis zu diesem wird _Wert angezeigt
  _StartPos: Die Spalte des LCD Displays, ab der der Balken anfängt. Damit kann vor dem Balken Platz geschaffen werden für Infos wie den direkten Zahlenwert oder der Variablenname
  _Zeile: Die Zeile, in der der Balken geschrieben werden soll, also "0" für die erste Zeile, und "1" für die zweite Zeile
  */
  //skalierung und Begrenzung der Werte auf die 10 Zeichen a 5 Spalten = 50 Positionen der LCD-Anzeige
  // LCDBar::BalkenLinks(int _Wert, int _MaxWert, int _StartPos, int _Zeile)
  Balken.BalkenLinks(Wert, MaxWert, 0, 1); // für die Anzeige eines Balkendiagrammes für die Anzeige von positiven Werten

  delay(50);

}

Hier der Header meiner eigenen Lib LCDBar.h:

#include "Arduino.h"
#include "LiquidCrystal_I2C.h"

#ifndef LCDBar_h
#define LCDBar_h

class LCDBar
{
public:
	LCDBar();
	void BalkenLinks(int _Wert, int _MaxWert, int _StartPos, int _Zeile);
	void StrichZentriert(int _Wert, int _MaxWert, int _StartPos, int _Zeile);
	void CharacterdefinitionBar();
	void CharacterdefinitionLine();
};
#endif

Und hier die Lib LCDBar.cpp selbst:

/*
LCDBar.h library for 
- showing a bar diagram for positive values, or 
- showing a line diagramm for positive and negative values 
 on a  LCD display
 
 Bsp.:
------------------------------------------------
 LCDBar Balken;	// erzeugt eine Instanz Balken aus der Klasse LCDBar
 
 // Variablendeklarationen für den Anzeigebalken
int Wert = 0;  // der anzuzeigende Rohwert
int MaxWert;   // der maximal anzuzeigende Wert
In void setup:
Balken.CharacterdefinitionBar(); // für die Anzeige eines Balkendiagrammes für die Anzeige von positiven Werten
In void loop:
Balken.BalkenLinks(Wert, 100, 5, 0); // für die Anzeige eines Balkendiagrammes für die Anzeige von positiven Werten 
Balken.StrichZentriert(Wert, 100, 5, 0); für  ein zentriertes Liniendiagramm für die Anzeige von positiven UND negativen Werten    
 */
#include "Arduino.h"
#include <Wire.h> 	// Wurde dazugenommen, da dies für das I2C Protokoll benötigt wird
#include "LiquidCrystal_I2C.h"
#include "LCDBar.h"

LiquidCrystal_I2C lcdB(0x27);

// Definition der LCD-Größe, um sie für andere Displays zentral ändern zu können
const int LCDSpaltenzahl = 10;
const int LCDZeilenzahl = 2;


LCDBar::LCDBar()
{
}

void LCDBar::BalkenLinks(int _Wert, int _MaxWert, int _StartPos, int _Zeile)
/* 
_Wert: der anzuzeigende Wert zwischen Null und _MaxWert. Werte außerhalb werden nicht angezeigt
_MaxWert: der Skalenendwert, bis zu diesem wird _Wert angezeigt
_StartPos: Die Spalte des LCD Displays, ab der der Balken anfängt. Damit kann vor dem Balken Platz geschaffen werden für Infos wie den direkten Zahlenwert oder der Variablenname
_Zeile: Die Zeile, in der der Balken geschrieben werden soll, also "0" für die erste Zeile, und "1" für die zweite Zeile 
*/
{
  // Die Routine geht von einem zweizeiligem Display mit je 10 Spalten aus.
  // Bei pixelgenauer Auflösung der 5*7 Charakter sind das insgesamt
  // 5 * 10 = 50 mögliche Spaltenpositionen
  lcdB.begin (LCDSpaltenzahl, LCDZeilenzahl);
  int _CPos = _StartPos;  // das ist die Cursorposition

  
  // int _WertPos = constrain( map (_Wert, 0, _MaxWert, _StartPos * 5, 50), _StartPos * 5, 50);
  int _WertPos = constrain( map (_Wert, 0, _MaxWert, _StartPos * 5, 5*LCDSpaltenzahl), _StartPos * 5, 5*LCDSpaltenzahl);

  // schreiben der Zeichen nach rechts ab der Cursorposition
  while (_CPos < _WertPos / 5)
  {
    lcdB.setCursor(_CPos, _Zeile);
    lcdB.write(4); //voll ausgefülltes Rechteck
	_CPos++;
  }
  // Feinberechnung und Anzeige der Einzelpixelspalten:
  lcdB.setCursor(_CPos, _Zeile);
  lcdB.write(_WertPos % 5);
  _CPos++;

  // jetzt bis zum Ende (Spalte 10 bei 2*10 Display) mit Leerzeichen auffüllen
  while (_CPos < 10)
  // while (_CPos < LCDSpaltenzahl)
  
  {
    lcdB.setCursor(_CPos, _Zeile);
    lcdB.write(32);
    _CPos++;
  }
}

void LCDBar::StrichZentriert(int _Wert, int _MaxWert, int _StartPos, int _Zeile)
/* 
_Wert: der anzuzeigende Wert zwischen Null und _MaxWert. Werte außerhalb werden nicht angezeigt
_MaxWert: der Skalenendwert, bis zu diesem wird _Wert angezeigt
_StartPos: Die Spalte des LCD Displays, ab der der Balken anfängt. Damit kann vor dem Balken Platz geschaffen werden für Infos wie den direkten Zahlenwert oder der Variablenname
_Zeile: Die Zeile, in der der Balken geschrieben werden soll, also "0" für die erste Zeile, und "1" für die zweite Zeile 
*/
{
  int _CPos = _StartPos;  // das ist die Cursorposition
   // Die Routine geht von einem zweizeiligem Display mit je 10 Spalten aus.
  // Bei pixelgenauer AUflösung der 5*7 Charakter sind das insgesamt
  // 5 * 10 = 50 mögliche Spaltenpositionen
  int _WertPos = constrain( map (_Wert, 0, _MaxWert, 5*(_StartPos / 2)+5*LCDSpaltenzahl/2, 5*LCDSpaltenzahl), _StartPos * 5, 5*LCDSpaltenzahl);

  // Löschen der Zeichen bis vor dem Strich
while (_CPos < _WertPos / 5)
  {
    lcdB.setCursor(_CPos, _Zeile);
    lcdB.write(32);
    _CPos++;
  }
  // Feinberechnung und Anzeige der Einzelpixelspalten:
  lcdB.setCursor(_CPos, _Zeile);
  lcdB.write(_WertPos % 5);
  _CPos++;

  // jetzt bis zum Ende (Spalte 15) mit Leerzeichen auffüllen
  while (_CPos < LCDSpaltenzahl)
  {
    lcdB.setCursor(_CPos, _Zeile);
    lcdB.write(32);
    _CPos++;
  }
  // Dann den Nullstrich ziehen
    if (_Wert > 0)
{	// Der Nullstrich wird im Zeichen links von der wahren Mitte generiert, da sonst weitere Sonderzeichen zu definieren wären
    lcdB.setCursor(_StartPos / 2 + 7, _Zeile);
    lcdB.write(4);  
}
else
{	// Der Nullstrich wird im Zeichen rechts  von der wahren Mitte generiert, da sonst weitere Sonderzeichen zu definieren wären
    lcdB.setCursor(_StartPos / 2 + 8, _Zeile);
    lcdB.write(byte(0));  
}
  // Wenn außerhalb des Anzeigebereichs  dann Anzeigen der Pfeile
  if (_Wert > _MaxWert)
  {
    lcdB.setCursor(LCDSpaltenzahl-1,_Zeile);
    lcdB.write(6);  
  }
  if (_Wert < - _MaxWert)
  {
    lcdB.setCursor(_StartPos,_Zeile);
    lcdB.write(5);  
  }
}

void LCDBar::CharacterdefinitionBar()
{
  // kreieren der Sonderzeichen für den Balken
  byte links[8] = {
    B10000,
    B10000,
    B10000,
    B10000,
    B10000,
    B10000,
    B10000,
  };
  byte linksMitte[8] = {
    B11000,
    B11000,
    B11000,
    B11000,
    B11000,
    B11000,
    B11000,
  };

  byte Mitte[8] = {
    B11100,
    B11100,
    B11100,
    B11100,
    B11100,
    B11100,
    B11100,
  };

  byte Mitterechts[8] = {
    B11110,
    B11110,
    B11110,
    B11110,
    B11110,
    B11110,
    B11110,
  };
  byte rechts[8] = {
    B11111,
    B11111,
    B11111,
    B11111,
    B11111,
    B11111,
    B11111,
  };
 
  // Zuordnung der 5 selbstdefinierten Zeichen zu den insgesamt
  // 8 möglichen Sonderzeichen des LCD Displays
  lcdB.createChar(0, links);
  lcdB.createChar(1, linksMitte);
  lcdB.createChar(2, Mitte);
  lcdB.createChar(3, Mitterechts);
  lcdB.createChar(4, rechts);
 
}  
void LCDBar::CharacterdefinitionLine()
{
// kreieren der Sonderzeichen für den Strich
byte links[8] = {
  B10000,
  B10000,
  B10000,
  B10000,
  B10000,
  B10000,
  B10000,
};
byte linksMitte[8] = {
  B01000,
  B01000,
  B01000,
  B01000,
  B01000,
  B01000,
  B01000,
};

byte Mitte[8] = {
  B00100,
  B00100,
  B00100,
  B00100,
  B00100,
  B00100,
  B00100,
};

byte Mitterechts[8] = {
  B00010,
  B00010,
  B00010,
  B00010,
  B00010,
  B00010,
  B00010,
};
byte rechts[8] = {
  B00001,
  B00001,
  B00001,
  B00001,
  B00001,
  B00001,
  B00001,
};
byte Pfeillinks[8] = {
  B00000,
  B00100,
  B01100,
  B11111,
  B01100,
  B00100,
  B00000,
};
byte Pfeilrechts[8] = {
  B00000,
  B00100,
  B00110,
  B11111,
  B00110,
  B00100,
  B00000,
};
// Zuordnung der 5 selbstdefinierten Zeichen zu den insgesamt
// 8 möglichen Sonderzeichen des LCD Displays

  lcdB.createChar(0, links);
  lcdB.createChar(1, linksMitte);
  lcdB.createChar(2, Mitte);
  lcdB.createChar(3, Mitterechts);
  lcdB.createChar(4, rechts);
  lcdB.createChar(5, Pfeillinks);
  lcdB.createChar(6, Pfeilrechts);
 
}

Nur mal so nebenbei, die Arudino IDE 1.5.5 ist eine Betaversion und eigentlich Hauptsächlich für die 32Bit Prozessoren von Atmel geeignet.
Wieso verwendest du nicht die normale IDE die für die "kleinen" Arduinos als stabil freigegeben ist ?

Edit 1:
Ich habe den Beitrag überarbeitet, hier stand Müll :wink:

Wieso verwendest du nicht die normale IDE die für die "kleinen" Arduinos als stabil freigegeben ist ?

Ich hatte das so verstanden, als daß die Version 1.5. dann der zukünftige Haupt-Fork wird. Und da ich eh von der Version 0.22 komme, wollte ich gleich auf die aktuelle Version umsteigen.
Ich habs jetzt überprüft, und das Programm auch unter der Stable 1.0.5 Version getestet: Leider gleiches Ergebnis.

Benutzt irgendjemand die "LiquidCrystal_I2C" Library, und setzt sie erfolgreich zusammen mit eigenen Libraries ein ?
Ich würde gerne wissen, wo ich den Fehler mache, oder ob die Library einen Bug hat. Ich vermute ersteres.

Tütenflieger:
Fragen:
Ist denn die Doppel-Initiierung des LCD-Displays einmal mit lcd im eigentlichem Nutzprogramm, und mit lcdB in meiner Lib korrekt ?

Nein.

Die Anzeige ist unruhig weil Du sie immer wieder neu schreibst.
Du mußt :

  1. den Balken nur neu schreiben, wenn sich der Wert ändert.
  2. nicht immer den ganzen Balken neu schreiben sondern nur den Teil der sich ändert.
    Grüße Uwe

@ Jurs

Fragen:
Ist denn die Doppel-Initiierung des LCD-Displays einmal mit lcd im eigentlichem Nutzprogramm, und mit lcdB in meiner Lib korrekt ?

Nein.

Aber was soll ich dann machen ? Wenn ich eine Initiierung weglasse, dann meckert der Compiler. Und beim Setzen eines gleichen Namens auch.

@uwefed

Die Anzeige ist unruhig weil Du sie immer wieder neu schreibst.

Das verstehe ich, und muß mich da nochmal hinter den Code klemmen. Da ich aber letztendlich über eine Menüstruktur mehrere Parameter einstellen und via Balken darstellen möchte, wird das sehr komplex herauszufinden was sich nun jeweils gerade geändert hat. Mich wundert nur, daß bei der Lösung ohne Benutzung einer Lib das Bild ganz ruhig steht, und es wird dort genausooft aktualisiert.

Du kannst in einer Variablen den alten Wert zwischenspeichern dieser wird in der Funktion der Bibliothek dann geupdatesd.
Grüße Uwe

Tütenflieger:
Aber was soll ich dann machen ? Wenn ich eine Initiierung weglasse, dann meckert der Compiler. Und beim Setzen eines gleichen Namens auch.

Der Compiler meckert, wenn Du syntaktisch falschen Code schreibst.

Wenn Du logisch falschen Code schreibst, der aber syntaktisch korrekt ist, ist das dem Compiler egal.

Für funktionierende Programme mußt Du syntaktisch und logisch korrekten Code schreiben.

Was in Deinem Fall das beste Vorgehen ist, wäre im Einzelfall zu prüfen. So von Deiner Beschreibung her hört es sich so an, als wenn Du die Klasse LiquidCrystal_I2C um neue Funktionen erweitern möchtest. Dazu fällt mir als erstes das Stichwort "Vererbung" ein:

D.h, von der Basisklasse LiquidCrystal_I2C abgeleitet soll eine neue Klasse LCDBar erstellt werden, die
a) alles das kann, was LiquidCrystal_I2C auch kann und
b) darüber hinaus zusätzliche Funktionen für eine Balkenanzeige hat

Deine Library verwendet dann die originale Basisklasse, um eine erweiterte Spezialklasse zu definieren.

In Deinem Sketch verwendest Du dann die erweiterte Klasse, mit den ererbten Eigenschaften und Funktionen sowie den neuen Funktionen.

@jurs

So von Deiner Beschreibung her hört es sich so an, als wenn Du die Klasse LiquidCrystal_I2C um neue Funktionen erweitern möchtest. Dazu fällt mir als erstes das Stichwort "Vererbung" ein:
Deine Library verwendet dann die originale Basisklasse, um eine erweiterte Spezialklasse zu definieren.
In Deinem Sketch verwendest Du dann die erweiterte Klasse, mit den ererbten Eigenschaften und Funktionen sowie den neuen Funktionen.

Ich vermute, daß ist es. Meine letzten objektorientierten Programmiererfahrungen mit C++ liegen halt 20 Jahre zurück. Und damals fand ich es schon nicht trivial.
Ich hoffte zugegebenermaßen, beim Arduino drumherum zu kommen...