delay funktioniert nicht richtig

pulsarus:
Über 50 Tage möchte ich keinen Ton spielen. Trotzdem noch eine kurze Frage dazu: Ich dachte den Überlauf kann man vermeiden, wenn mann statt long - unsigned long nimmt?

Probleme durch millis()-Überläufe kannst Du vermeiden, durch geschickte Variablendeklaration und Programmierung.

Aber Voraussetzung dafür ist bei Verwendung der millis() Funktion: Es muß innerhalb der Überlaufzeit von ca. 50 Tagen mindestens ein Ereignis auftreten, damit einmal der Zählerstand aktualisiert wird, sonst ist's Essig.

Da kannst Du natürlich auch wieder drum herum programmieren, beispielsweise kannst Du festlegen, dass immer der Ablauf von 1000 Millisekunden ein Ereignis ist, das per Timer ermittelt wird, und im Takt dieses Ereignisses kannst Du einen anderen Zähler weiterzählen lassen. Dann hättest Du zusätzlich zum Millisekundenzähler, der nach ca. 50 Tagen überläuft, auch einen Sekundenzähler, der erst in 50000 Tagen überläuft. Und mit diesem langsameren Zähler könntest Du dann wieder etwas timen, in dem Fall mit Sekundengenauigkeit innerhalb eines 50000-Tage-Intervalls.

So sieht jetzt der fertige Sketch aus

#define piezoPin A5
char melodie[]="1a8,2d4,6c4,PP4,1a8,2d4,6c4,PP4,PP4,6e8,8d4,8d4,PP4,6d8,6d8,6d8,8g4,6c4,PP8,5f8,7d4,7d4,PP4,7d8,PP4,5e8,4g4,4d4,4f4,4aa,0ba";
#define BPM 120
#define SECONDS_PER_MINUTE 60
#include <LiquidCrystal.h>
LiquidCrystal lcd(12,11, 5, 4, 3, 2);
int x = 0;
int y=0;
long previousMillis =0;
byte A[8] = 
{
  B00111,
  B01111,
  B11111,
  B11111,
  B11111,
  B11111,
  B11111,
  B11111
};
byte B[8] =
{
  B11111,
  B11111,
  B11111,
  B00000,
  B00000,
  B00000,
  B00000,
  B00000
};

byte C[8] = 
{
  B00111,
  B01111,
  B11111,
  B00000,
  B00000,
  B11111,
  B11111,
  B11111
};
byte D[8] =
{
  B11111,
  B11111,
  B11111,
  B11111,
  B00000,
  B00000,
  B11111,
  B00000
};


int getNotenDauer(int beatsPerMinute, char notendauer){
long factor;
switch (notendauer)
{
case '0': factor=1024;break; // ganze Note
case '1': factor=1536;break; // ganze Note punktiert
case '2': factor=512;break; // halbe Note
case '3': factor=768;break; // halbe Note punktiert
case '4': factor=256;break; // viertel Note
case '5': factor=384;break; // viertel Note punktiet
case '6': factor=128;break; // achtel Note
case '7': factor=192;break; // achtel Note punktiert
case '8': factor=64;break; // sechzehntel Note
case '9': factor=96;break; // sechzehntel Note punktiert
case 'a': factor=32;break; // 32stel Note
case 'b': factor=48;break; // 32stel Note punktiert
default: {Serial.print("Error Tondauer ");Serial.println(notendauer);}
}
return (4*factor*SECONDS_PER_MINUTE/beatsPerMinute);
}
int getTonHoehe(char oktave, char note)
{
int tonhoehe;
switch (note)
{
case 'c': tonhoehe=330;break;
case 'd': tonhoehe=349;break;
case 'e': tonhoehe=370;break;
case 'f': tonhoehe=392;break;
case 'g': tonhoehe=415;break;
case 'a': tonhoehe=440;break;
case 'h': tonhoehe=466;break;
}
switch (oktave)
{
case '1': break;
case '2': tonhoehe*=2;break;
case '3': tonhoehe*=4;break;
case '4': tonhoehe*=8;break;
case '5': tonhoehe*=16;break;
case '6': tonhoehe*=32;break;
}
return(tonhoehe);
}

void task_tone(){
static int notencounter;
static boolean note_an;
static long nextnote, nextpause;
if (millis()>nextnote)
{
if (!note_an)
{
if (melodie[notencounter]!='P')
{
tone (piezoPin,getTonHoehe(melodie[notencounter], melodie[notencounter+1]));
}
else
{
noTone(piezoPin); 
}
nextpause=millis()+getNotenDauer(BPM,melodie[notencounter+2]);
note_an=true;
}
}
if (millis()>nextpause)
{
if (note_an)
{
noTone(piezoPin);
notencounter+=4; // notencounter weiter
if (notencounter>=strlen(melodie))
{
notencounter=0; // melodie zuende, notencounter reset
nextnote=millis()+1000;
}
else nextnote=millis()+300; // Feste Pause von 100 ms zwischen den Noten
note_an=false; // spielen der Note is abgeschaltet
}
}
}
void task_Muster1(){
  static long interval=50;
  static long startmillis=0;
  lcd.createChar(1,A);
  lcd.setCursor(x,0);
  lcd.write(1);
  x=x+0;
  lcd.createChar(2,B);
  lcd.setCursor(x,0);
  lcd.write(2);
  lcd.createChar(3,C);
  lcd.setCursor(x,1);
  lcd.write(3);
  x=x+1;
  lcd.createChar(4,D);
  lcd.setCursor(x, y);
  lcd.write(4);
  x=x+2;
  y=y+1;
  startmillis=millis();
  if (millis()-previousMillis>interval);
 }

void setup() {
 lcd.begin(0, 2);
}
void loop() {
  task_Muster1();
  task_tone();
}

Jetzt habe ich mal versucht eine Laufschrift mit dem Ton-Code zu verbinden. Die Schrift läuft, aber der Ton gibt nur eine lang anhaltenden Ton von sich.
Was ist falsch? Kann mir jemand weiter helfen?

#define piezoPin A5
char melodie[]="1a8,2d4,6c4,PP4,1a8,2d4,6c4,PP4,PP4,6e8,8d4,"
"8d4,PP4,6d8,6d8,6d8,8g4,6c4,PP8,5f8,7d4,7d4,PP4,7d8,PP4,5e8,"
"4g4,4d4,4f4,4aa,0ba";
#define BPM 120
#define SECONDS_PER_MINUTE 60
#include <LiquidCrystal.h>
const int numCols=16;//Spalten
const int numRows=1;//Reihen
LiquidCrystal lcd(12, 11, 5, 4, 3, 2);
char* string="___hier steht ein langer text!!!!!!___";

int getNotenDauer(int beatsPerMinute, char notendauer){
unsigned long factor;
switch (notendauer)
{
case '0': factor=1024;break; // ganze Note
case '1': factor=1536;break; // ganze Note punktiert
case '2': factor=512;break; // halbe Note
case '3': factor=768;break; // halbe Note punktiert
case '4': factor=256;break; // viertel Note
case '5': factor=384;break; // viertel Note punktiet
case '6': factor=128;break; // achtel Note
case '7': factor=192;break; // achtel Note punktiert
case '8': factor=64;break; // sechzehntel Note
case '9': factor=96;break; // sechzehntel Note punktiert
case 'a': factor=32;break; // 32stel Note
case 'b': factor=48;break; // 32stel Note punktiert
}
return (4*factor*SECONDS_PER_MINUTE/beatsPerMinute);
}
int getTonHoehe(char oktave, char note)
{
int tonhoehe;
switch (note)
{
case 'c': tonhoehe=330;break;
case 'd': tonhoehe=349;break;
case 'e': tonhoehe=370;break;
case 'f': tonhoehe=392;break;
case 'g': tonhoehe=415;break;
case 'a': tonhoehe=440;break;
case 'h': tonhoehe=466;break;
}
switch (oktave)
{
case '1': break;
case '2': tonhoehe*=2;break;
case '3': tonhoehe*=4;break;
case '4': tonhoehe*=8;break;
case '5': tonhoehe*=16;break;
case '6': tonhoehe*=32;break;
}
return(tonhoehe);
}
void task_tone(){
static int notencounter;
static boolean note_an;
static unsigned long nextnote, nextpause;
if (millis()>nextnote)
{
if (!note_an)
{
if (melodie[notencounter]!='P')
{
tone (piezoPin,getTonHoehe(melodie[notencounter], melodie[notencounter+1]));
}
else
{
noTone(piezoPin); 
}
nextpause=millis()+getNotenDauer(BPM,melodie[notencounter+2]);
note_an=true;
}
}
if (millis()>nextpause)
{
if (note_an)
{
noTone(piezoPin);
notencounter+=4; // notencounter weiter
if (notencounter>=strlen(melodie))
{
notencounter=0; // melodie zuende, notencounter reset
nextnote=millis()+1000;
}
else nextnote=millis()+300; // Feste Pause von 100 ms zwischen den Noten
note_an=false; // spielen der Note is abgeschaltet
}
}
}

void setup() {
  lcd.begin(numCols, numRows);
}

void task_schrift (){
static unsigned long start;
static unsigned long duration=700;
static unsigned long duration1=400;
  int lenght = strlen (string);
  if(lenght<numCols)
  lcd.print (string);
  else{
    int pos;
    pos<numCols;
    pos++;
    lcd.print(string[pos]);
     unsigned long start = millis();
      while (millis()-start<duration);
    pos=1;
    while(pos<=lenght-numCols)
    {
      lcd.setCursor(0,0);


  for(int i=0;i<numCols;i++)
  
    lcd.print(string[pos+i]);
     unsigned long start = millis();
  while (millis()-start<duration1);
    pos=pos+1;
    }}}
 
  void loop(){
    task_tone();
    task_schrift();
  }

pulsarus:
Was ist falsch? Kann mir jemand weiter helfen?

So eine Zeile wie
while (millis()-start<duration1);
ist natürlich um keinen Deut besser als ein delay, denn in dieser Zeile hält die übrige Codeausführung an, bis die angegebene Verzögerungszeit abgelaufen ist.

Willst Du den Text denn vollkommen unabhängig von den Noten in einem eigenen Task durchlaufen lassen?

Grundsätzlich hast Du ja diese Möglichkeiten:

  • Text wird synchron zu den abgespielten Noten angezeigt
  • Text läuft unabhängig von der Melodie in einem eigenen Task
    Du versuchst Dich gerade an der zweiten Variante, was aber noch nicht funktioniert, da Du immer noch blockierenden Code zusammenschreibst.

Ich würde gerne, um den Code später noch für andere Varianten laufen zu lassen, den Text unabhängig von der Melodie, in einem eigenen Task laufen lassen. Wie muss ich den Code verändern? Ich habe schon so viel mit while und if rumprobiert, komme aber zu keinem Ergebnis.

pulsarus:
Ich würde gerne, um den Code später noch für andere Varianten laufen zu lassen, den Text unabhängig von der Melodie, in einem eigenen Task laufen lassen. Wie muss ich den Code verändern? Ich habe schon so viel mit while und if rumprobiert, komme aber zu keinem Ergebnis.

Im Prinzip mußt Du das genau so machen wie beim Abspielen der Noten: Kooperatives Multitasking.

Die Funktion darf nicht warten, bis irgendeine Zeit abgelaufen ist und so lange in einer Schleife verbringen, sondern die Logik ist genau so wie beim Abspielen der Noten. Nur eben nicht mit Noten und Lautsprecher, sondern mit Text und Display:

Wenn der Text erstmalig angezeigt wird, wird sofort festgelegt, wann zeitmäßig die nächste Aktualisierung stattfinden muß. Und danach prüft die Funktion bei jedem nachfolgenden Aufruf erstmal, ob es schon Zeit ist, irgendeine Aktion auszuführen. Wenn nein, Funktion sofort beenden ohne was zu tun. Und wenn die Zeit erreicht ist, dann wird der angezeigte Text verändert und wieder die neue Zeit festgelegt, wann die nächste Aktion zur Ausführung fällig ist. Und so weiter und so fort.

Ich kriegs nicht hin. Habe den ganzen Tag verschiedenste Sachen ausprobiert. Bis hierher bin ich gekommen:

#include <LiquidCrystal.h>
const int numCols=16;//Spalten
const int numRows=1;//Reihen
LiquidCrystal lcd(12, 11, 5, 4, 3, 2);
char* string="___hier steht ein sehr langer text!!!!!!___";

void task_text(){
static int positionCounter; ??? den Counter wollte ich irgendwie nutzen, um den Cursor weiterzusetzen, um den Text zu scrollen
static unsigned long starttext;

unsigned long starttext=millis();
if (millis()    ??? hiermit wollte ich festlegen, wann zeitmäßig die nächste Aktualisierung stattfinden soll
lcd.setCursor(0,0);
  lcd.print(string);
  lcd.setCursor(0,0);

 }

void setup() {
  lcd.begin(numCols, numRows);
}
 
  void loop(){
    task_text();
  }

Ich weiß jetzt nicht, wie ich den Text scrollen kann (bisher sehe ich nur einen Teil von ihm auf dem Display). Mit lcd.scrollDisplayLeft wäre sehr bequem, aber das geht wohl nur mit delay? Ansonsten ginge evtl. diese for Schleife:for(int i=0;i<numCols;i++)? Oder stoppt sie den Code? Oder eben wie im Code angedacht mit einem Counter, aber wie?

pulsarus:
Ich weiß jetzt nicht, wie ich den Text scrollen kann

Durch nicht-blockierende Funktionsaufrufe, ohne delay(), und ohne andere blockierende Funktionsaufrufe.

Im Prinzip genau so wie beim Abspielen von Noten: Bei jedem Ereignis wird festgelegt, wann das nächste Ereignis stattfinden soll.

Wenn die Zeit für das Ereignis gekommen ist, findet eine Aktion statt. Sonst eben nicht.

#include <LiquidCrystal.h>
const int numCols=16;//Spalten
const int numRows=1;//Reihen
LiquidCrystal lcd(12, 11, 5, 4, 3, 2);
// LiquidCrystal lcd(8, 9, 4, 5, 6, 7); // Sainsmart keypad LCD
char* string="                ___hier steht ein sehr langer text!!!!!!___";

void task_text()
{
  char currenttext[17];
  static unsigned long nextscroll;
  static int positionCounter; 
  if (millis()>nextscroll)
  {
    nextscroll=millis()+200; 
    strlcpy(currenttext,&string[positionCounter],sizeof(currenttext));
    lcd.setCursor(0,0);
    lcd.print(currenttext);
    for (int i=strlen(currenttext);i<16;i++) lcd.print(" ");
    positionCounter++;
    if (strcmp(currenttext,"")==0) positionCounter=0;
//    Serial.println(currenttext);
  }
}


void setup() {
  Serial.begin(9600);
  lcd.begin(numCols, numRows);
  lcd.clear();
}
 
void loop()
{
    task_text();
}

Das sieht so ganz logisch aus. Vielen Dank!
Aber auf diese zwei Zeilen wäre ich nicht gekommen:
strlcpy(currenttext,&string[positionCounter],sizeof(currenttext));
if (strcmp(currenttext,"")==0) positionCounter=0;
Könntest Du sie mir bitte erklären? Die Funktionen strlcpy (stringcoppy vermute ich) und strcmp kenne ich nicht. Gibt es dafür noch eine andere Ausdrucksform?

pulsarus:
Das sieht so ganz logisch aus. Vielen Dank!
Aber auf diese zwei Zeilen wäre ich nicht gekommen:
strlcpy(currenttext,&string[positionCounter],sizeof(currenttext));
if (strcmp(currenttext,"")==0) positionCounter=0;
Könntest Du sie mir bitte erklären? Die Funktionen strlcpy (stringcoppy vermute ich) und strcmp kenne ich nicht. Gibt es dafür noch eine andere Ausdrucksform?

strlcpy ist eine Standardfunktion zum Kopieren eines Strings "mit Längenbegrenzung". Die Längenbegrenzung ist in dem Fall die Größe des aufnehmenden Ziel-Strings, der in der Größe so bemessen ist, dass er genau den Inhalt einer LCD-Zeile aufnehmen kann (currenttext[17], für 16 Zeichen plus Nullzeichen als Stringende-Zeichen).

strcmp ist die Standardfunktion zum Vergleichen von zwei Strings.

Da in meinem Code mit strlcpy immer Zeichen vom Ausgangsstring ab der aktuellen Scrollposition in den Zielstring kopiert werden, wird der Zielstring immer kürzer, wenn sich der positionCounter dem Ende des Ausgangsstrings nährt. So lange, bis der Zielstring zum Anzeigen ein Leerstring "" ist, was mit strcmp verglichen wird. Wenn so weit nach rechts gescrollt wurde, ist der Scrollvorgang zu Ende, der positionCounter wird wieder auf 0 gesetzt und der Scrollvorgang beginnt erneut.

Und mit diesem Code werden, falls der Zielstring kürzer ist als die LCD-Textzeile, die Zeichen am Ende der Zeile mit Leerzeichen aufgefüllt:
for (int i=strlen(currenttext);i<16;i++) lcd.print(" ");

Wenn Du Funktionen wie strlcpy und selbst strcmp nicht kennst, Dein scheinst Du eine völlige Unkenntnis über die der Arduino-Software zugrundeliegende Standard-Library zu haben, mit allen verwendbaren Funktionen. Die Standard-Library für Arduino ist die "AVR libc". Sämtliche Module, die zu dieser Library mit C-Standardfunktionen gehören, findest Du hier aufgelistet:
http://www.nongnu.org/avr-libc/user-manual/modules.html
Wenn Du Dich zu den einzelnen Modulen durchklickst, z.B. zu den Stringfunktionen avr-libc: <string.h>: Strings dann findest Du dort die Funktionsdeklarationen und kurze Funktionsbeschreibungen.

Wenn Dir dann immer noch unklar ist, was die Funktion macht und wie sie verwendet wird, kannst Du diese Funktion googeln: Es handelt sich um absolute Standardfunktionen, die teilweise seit den siebziger Jahren des letzten Jahrhunderts unverändert sind, die in fast allen C-Compilern in der Library integriert sind und die daher in allen Anfängerbüchern über C/C++-Programmierung und auf diversen Anfängerseiten über C/C++-Programmierung immer wieder erklärt werden.

Eine sehr schöne Seite zum Nachschlagen von Funktionen aus der AVR libc wäre auch z.B. www.cplusplus.com, wo es zu fast jeder Standardfunktion immer eine schöne Referenzseite gibt, z.B.:
http://www.cplusplus.com/reference/cstring/strcmp/
strlcpy ist dort allerdings nicht erklärt, sondern nur das recht ähnliche strncpy:
http://www.cplusplus.com/reference/cstring/strncpy/
strncpy behandelt aber nicht das abschließende Nullzeichen korrekt, so dass ich in dem Fall strlcpy verwende.
Wenn Du Dich für AVR libc Funktionen interessierst, die auf www.cplusplus.com nicht in der Referenz erklärt sind, und wenn Dir die Erklärung auf www.nongnu.org nicht ausreicht, mußt Du die Funktion eben an anderer Stelle googlen. Oder Du verwendest strncpy statt strlcpy und behandelst das abschließende Nullzeichen im kopierten String selbst.

Ich habe bisher die Grundlagen von Processing "studiert" und etwas PureData, da kamen solche Funktionen nicht vor. Da Du mir die Links aufgelistet hast (vielen Dank dafür!) werde ich mich jetzt mal damit beschäftigen und mir ein Anfängerbuch über C/C++ holen. Nochmal vielen Dank für die umfangreiche Hilfe.
Super Forum!!!

(Bis zum nächsten mal)