Anleitung: Von der blinkenden LED zur Zugsteuerung

Übung 10:

Zu Übung 9 gab es noch keine Lösung, machen wir hier mit.

Motivation: Die Schleife loop läuft ständig, meist möchte man aber nur Teile des darin vorhandenen Programms tatsächlich ausführen. "Erst soll das passieren, dann das, danach nochwas." sind typische Formulierungen, die als Lösung eine Schrittkette (=finite state machine, =endlicher Automat) haben können. Dabei ist die Anwendung der bedingten Verzweigung von switch/case eine anschauliche Möglichkeit der Realisierung. Jeder Schritt enhält ein "Weitergehen" zum nächsten Schritt, eventuell an eine Bedingung if (bedingung) { schritt++; } geknüpft.

Die Syntaxerklärung entnimmst Du bitte "Der C++-Programmierer: 1.8.4 Fallunterscheidungen mit switch", denn da ist das viel besser beschrieben, als ich es könnte.

Lernziele: Schrittkette nutzen.

Aufgabe: Programmiere mit switch/case folgende Schritte:

  1. Setze IN1 HIGH, IN2 LOW.
  2. Warte 5 Sekunden, nutze millis().
  3. Setze IN1 LOW, IN2 HIGH.
  4. Warte 5 Sekunden.
  5. Setze IN1 LOW, IN2 LOW.
  6. Warte 5 Sekunden. Weiter bei Schritt 0.

IN1 und IN2 sind die Eingänge des Motortreibers, es können aber auch einfach LEDs angeschlossen werden.

Laaaangsam, bin noch am Verkabeln des Versuchsaufbaus. Ich melde mich, bitte etwas Geduld, ich muss ja auch noch lesen UND denken. :wink:

Der Reiz war übermächtig.....
Hier eine Variante, im älteren "Combie Stil", natürlich mit millis() und einem switch/case, im Herzen verankert.

#include <TaskMacro.h>

const byte IN1 = 13;
const byte IN2 = 12;

Task automat()
{
  taskBegin();
  pinMode(IN1,OUTPUT);
  pinMode(IN2,OUTPUT);
  
  while(1)
  {
    digitalWrite(IN1,HIGH);digitalWrite(IN2, LOW);
    taskPause(5000);
    digitalWrite(IN1, LOW);digitalWrite(IN2,HIGH);
    taskPause(5000);
    digitalWrite(IN1, LOW);digitalWrite(IN2, LOW);
    taskPause(5000);
  }
  taskEnd();
}

void setup() {   }

void loop() 
{
  automat();
}

Ähnliches Problem, mit Link zur Lib

Meinst Du, Du könntest das meiner Zielperson @schorsch55 erklären?

Vielleicht!

Durch das Kopieren wird die Feld-Variable text[] gefüllt und behalten. Dann kann man auf diese Art doch keinen Speicher sparen, oder?

Das fände ich besser, weil text[] nur temporär ist:

// https://forum.arduino.cc/t/problem-mit-struct-kontruktur-und-textubergabe-default-leer/560730

const char t0[] PROGMEM = "........";
const char t1[] PROGMEM = "BBGBBGGG";
const char t2[] PROGMEM = "G.G...B.";

struct Daten {
  const uint8_t pin;
  const uint8_t bf;
  const uint8_t ag;
  const char * data;
};

Daten weg[] =
{
  //pin          Bf          AG
  {99, 0b00000000, 0b00000000, t0},
  { 2, 0b01000000, 0b10010000, t1}, // 1. Weg
  { 3, 0b01000000, 0b10010000, t2}  // 2. Weg
};

//const byte ANZAHL = sizeof(weg) / sizeof(Daten);

void setup()
{
  Serial.begin(9600);
  Serial.println("\nµC Reset ### ### ###"); 

  for (Daten &i : weg)
  {
    Serial.print(F("pin: "));    Serial.print(i.pin);
    Serial.print(F("  Bf: "));   Serial.print(i.bf, BIN);
    Serial.print(F("  AG: "));   Serial.print(i.ag, BIN);
    char text[9];
    strlcpy_P(text, i.data, sizeof(text));
    Serial.print(F("  text: ")); Serial.print(text);
    Serial.println();               
  }
}

void loop()
{ }

Übersehe ich irgendwelche Fallstricke?

Hallo,

in dem kleinen Bsp. spart man wirklich nichts das stimmt. Rückgabewert ist am Ende ein Zeiger auf das Ziel im RAM. Die RAM Ersparnis macht sich erst bemerkbar wenn man mehrere Textbausteine im Flash statt RAM vorhält und sie bei Bedarf verwendet. Dabei bleibt der RAM Bedarf konstant, weil das char Array vom Objekt konstant ist.

Einmal laut gedacht. Das char Array zerfällt zu einem Zeiger auf das erste Element, aber es wurde zusammenhängender Speicher reserviert und der Textbaustein reinkopiert.
Zeigervariante. Es wird ein Zeiger der ins Flash zeigt an den data Zeiger im struct übergeben. Danach wird der Text der am "data Zeiger" klebt ins RAM kopiert und ausgegeben. Funktioniert tuts erstmal.

Wenn ich darüber so grübel wird der Unterschied sein, meine Variante kopiert beim Instanz erstellen und verwendet dann nur noch. Deine Variante kopiert zur Laufzeit. Wird praktisch sicherlich nicht auffallen. Einen richtigen Fallstrick sehe aber nicht. Soviel zu meiner Analyse.

Anmerkung. Den ersten Kommentar mit t0 solltest du korrigieren.

Danke dafür!

@schorsch55 hat seine Weichen usw. beginnend ab 1 nummeriert, weshalb des Element 0 nicht verwendet wird. Aber ja, hier im Test ist das anders.

Das temporäre text[] und die Umkopiererei kann man sich auch ganz sparen:

Serial.print(reinterpret_cast<const __FlashStringHelper *>(i.data));

Leider funktioniert der Makro F(i.data) nicht ...
Warum, weiß ich nicht. Makros sind leider nur C, ist wohl nur die halbe Antwort

Stimmt, ich brauche die Zeichen einzeln:

const char t0[] PROGMEM = "........";
const char t1[] PROGMEM = "BBGBBGGG";
const char t2[] PROGMEM = "G.G...B.";

struct Daten {
  const uint8_t pin;
  const uint8_t bf;
  const uint8_t ag;
  const char * data PROGMEM;
};

Daten weg[] =
{
  //pin          Bf          AG
  {99, 0b00000000, 0b00000000, t0}, // nicht verwendet
  { 2, 0b01000000, 0b10010000, t1}, // 1. Weg
  { 3, 0b01000000, 0b10010000, t2}  // 2. Weg
};

//const byte ANZAHL = sizeof(weg) / sizeof(Daten);

void setup()
{
  Serial.begin(9600);
  Serial.println("\nµC Reset ### ### ###");

  for (Daten &i : weg)
  {
    Serial.print(F("pin: "));    Serial.print(i.pin);
    Serial.print(F("  Bf: "));   Serial.print(i.bf, BIN);
    Serial.print(F("  AG: "));   Serial.print(i.ag, BIN);
    Serial.print('\t');
    for (byte j = 0; j < 255; j++)
    {
      char myChar = pgm_read_byte_near(i.data + j);
      if (myChar == '\0') break;
      Serial.print(myChar); Serial.print(' ');
    }
    Serial.println();
  }
}

Ganz ohne Makro, was meinst Du?

Da du ja im Sketch schon den F-Makro verwendest, hast du Code doppelt gemoppelt.
Ein reinterpret_cast macht ja nichts, außer dem Compiler zu sagen, dass "nichts machen" genau richtig ist. (Einfach print für "Text im Flash" mit i.data als Adresse aufrufen)

Sorry, ich hatte Dich mißverstanden :blush:

Das funktioniert:
char stlg = pgm_read_byte_near(wege[weg].stellung + p);

Ohne PROGMEM:

Der Sketch verwendet 7292 Bytes (22%) des Programmspeicherplatzes. Das Maximum sind 32256 Bytes.
Globale Variablen verwenden 1026 Bytes (50%) des dynamischen Speichers, 1022 Bytes für lokale Variablen verbleiben. Das Maximum sind 2048 Bytes.

Mit PROGMEM:

Der Sketch verwendet 7246 Bytes (22%) des Programmspeicherplatzes. Das Maximum sind 32256 Bytes.
Globale Variablen verwenden 908 Bytes (44%) des dynamischen Speichers, 1140 Bytes für lokale Variablen verbleiben. Das Maximum sind 2048 Bytes.

Danke für die Idee!