Pinsteuerung durch ASCII-Codes

Hallo,

ich bin neu auf dem Gebiet der Arduino Programmierung und habe nur Kentnisse der Grundlagen von C-Programmierung.

Mein Problem:

Ich möchte als Beispiel folgende seriellen Code verarbeiten:

CURR 150mA oder TIME 60min

Falls ein Stromwert über die Serielleschnittstelle gesendet wird, sollen an ein R2R-Netzwerk als DA-Wandler die passenden Pins (1-10) geöffnet werden oder eben nicht. Dabei kann der Strom zwischen 0 und 1000mA liegen.

Wenn ein Zeitwert übergeben wird, soll ein Timer entsprechend eingestellt werden.

Somit ist zunächst unklar, welcher Befehl gesendet wurde (das sollte man mit einer einfachen If-Abfrage noch hin bekommen). Aber weiterhin fehlt mir noch ein Lösungsansatz, wie ich die Zahlenwerte passend auswerte. Da ich ja nicht weiß, wie viele gesendet werden und mit welcher Wertigkeit ich sie dann beaufschlagen muss.

Als Grundlage dient mir aktuell folgender Code, womit ich zunächst eine LED an und aus machen kann.

char i;

void setup()
{
pinMode(2,OUTPUT);
Serial.begin(9600);
}

void loop ()
{
if(Serial.available())
{ i = Serial.read();
} if (i=='1')
{ digitalWrite (2,HIGH);
}
else
{ digitalWrite (2,LOW);
}
}

Herzlichen Dank für eure Hilfe

Soweit ich weiss kann man ein R2R Netzwerk nicht mit 1000mA belasten.
Aber die könnte ein Arduino ja sowieso niemals liefern.

Das Dekodieren der seriellen Übertragung ist bei dem Projekt dein kleinstes (weil einfach lösbares) Problem.

Um ein Konstantstrom- und/oder Konstantspannungs-Netzteil zu bauen musst du wesentlich mehr Aufwand treiben,
aber dabei kann ich nicht groß helfen, ich bin kein E-Techniker.

const char pmsCurr[] PROGMEM = "CURR";
const char pmsTime[] PROGMEM = "TIME";
const char pmsVolt[] PROGMEM = "VOLT";
const char pmsStop[] PROGMEM = "STOP";

const char * const sTable[] PROGMEM = { pmsCurr, pmsTime, pmsVolt, pmsStop };

void cmdCurr(const char* parms);
void cmdTime(const char* parms);
void cmdVolt(const char* parms);
void cmdStop(const char* parms);

typedef void (*pvfu)(const char* parms);

const pvfu fTable[] PROGMEM = { cmdCurr, cmdTime, cmdVolt, cmdStop };

void setup() {
  Serial.begin(250000);
  Serial.print(F("Ich kenne folgende Kommandos:"));
  for (byte i = 0; i < sizeof(sTable) / sizeof(sTable[0]); i++) {
    Serial.write(' ');
    Serial.print((__FlashStringHelper*)pgm_read_word(sTable + i));
  }
  Serial.println();
}

void loop() {
  serialHandler();
}

void processCmd(const char* buf)
{
  for (byte i = 0; i < sizeof(sTable) / sizeof(sTable[0]); i++) {
    const char* sadr = pgm_read_word(sTable + i);
    byte len = strlen_P(sadr);
    if (!strncasecmp_P(buf, sadr, len)) {
      (*((pvfu)pgm_read_word(fTable + i)))(buf + len);
      return;
    }
  }
  Serial.write('\'');
  Serial.print(buf);
  Serial.println(F("' nicht erkannt"));
}

void dumpUsage(const char* name, const char* parms) {
  Serial.print(F("exec "));
  Serial.print((__FlashStringHelper*)name);
  Serial.print(F("(\""));
  Serial.print(parms);
  Serial.println(F("\")"));
}

void cmdCurr(const char* parms) {
  unsigned long value = strtoul(parms, (char**)&parms, 0);
  while (*parms == ' ') {
    parms++;
  }
  if (*parms == 'A') {
    value *= 1000;
  }
  Serial.print(F("Current: "));
  Serial.print(value);
  Serial.println(F(" mA"));
}
void cmdTime(const char* parms) {
  unsigned long value = strtoul(parms, (char**)&parms, 0);
  while (*parms == ' ') {
    parms++;
  }
  if (!strncasecmp_P(parms, PSTR("min"), 3)) {
    value *= 60000L;
  } else if (!strncasecmp_P(parms, PSTR("se"), 2)) {
    value *= 1000;
  } else if (!strncasecmp_P(parms, PSTR("st"), 2)) {
    value *= 60 * 60000L;
  } else if (!strncasecmp_P(parms, PSTR("ze"), 2)) {
    value *= 100;
  } else if (!strncasecmp_P(parms, PSTR("hu"), 2)) {
    value *= 10;
    //  } else if (!strncasecmp_P(parms, PSTR("mil"), 3)) {
  } else {
    value *= 1000;
  }
  Serial.print(F("Time: "));
  Serial.print(value);
  Serial.println(F(" ms"));
}

void cmdVolt(const char* parms) {
  unsigned long value = strtoul(parms, (char**)&parms, 0);
  while (*parms == ' ') {
    parms++;
  }
  if (*parms == 'V') {
    value *= 1000;
  }
  Serial.print(F("Voltage: "));
  Serial.print(value);
  Serial.println(F(" mV"));
}

void cmdStop(const char* parms) {
  dumpUsage(pmsStop, parms);
}

void serialHandler()
{
  const byte sCBMax = 30;
  static char sCBuffer[sCBMax];
  static byte buffIndex = 0;

  byte inChar;
  bool doCheck = false;

  while (Serial.available()) {
    inChar = Serial.read();
    if (inChar == 13) {
      doCheck = true;
    } else if (inChar != 10) {
      if ((buffIndex == 0) && isWhitespace(inChar)) {
        continue;
      }
      sCBuffer[buffIndex++] = inChar;
      doCheck = (buffIndex == (sCBMax - 1));
    }
    if (doCheck) {
      sCBuffer[buffIndex] = 0;
      doCheck = false;
      if (buffIndex != 0) {
        processCmd(sCBuffer);
        buffIndex = 0;
      } else {
        Serial.println();
      }
    }
  }
}
CURR 150mA
TIME 60min
Ich kenne folgende Kommandos: CURR TIME VOLT STOP
Current: 150 mA
Time: 3600000 ms

Whandall:
Soweit ich weiss kann man ein R2R Netzwerk nicht mit 1000mA belasten.
Aber die könnte ein Arduino ja sowieso niemals liefern.

Nein nein, mein R2R Netzwerk liefert nur eine entsprechende Spannung für einen Operationsverstärker, der wiederum einen Transistor schaltet. Die passende Spannung versorgt einen Widerstand, der den Strom in der eigentlichen Schaltung einstellt. Aber das ist nicht das Problem.

Mir geht es darum, dass ich einen String aus Zahlenwerten übergebe im Bereich von 0-1000.
Die ASCII-Werte für die einzelnen Zahlen speicher ich mir in ein Feld. Soweit so gut.

char input[16];
char current;
int incount = 0;
bool lineComplete = false;

void setup() {
  // put your setup code here, to run once:
Serial.begin(9600);
}

void loop() {
  // put your main code here, to run repeatedly:
while( (Serial.available()) & (incount <40) & (!lineComplete))
{
  current = Serial.read();
  if(current != 65)                        // Abschluss "A"
  {
    input[incount] = current;
    incount++;
  }
  else
  {
    input[incount] = '\0';
    lineComplete = true; 
  }
}

Nun geht es darum, dieses Feld auszuwerten und bei einer Auflösung von 1024 (bei 10 Pins) stufenweise das R2R-Netzwerk zu steuern.

Ich weis zur Zeit nicht weiter, wie ich die einzelnen Zellen der Felder behandeln muss, damit ich die passende Wertigkeit habe und dementsprechend binär die Pins setzten kann.

Bsp: Eingabe: 150mA

Wert = {1,5,0,m,A}
jetzt frage ich nach dem Zeichen "m" das Feld ab, wenn ich weiß an welcher Stelle es kommt, weiß ich auch wie viele Zahlen davor stehen.
mit welcher Funktion bekomme ich nun aus dem Feld ein passenden binären Code?
nach meinen Überlegungen möchte ich folgendes machen:
0x31 AND 0x0F (damit ich nur noch die Zahlen binär habe)
dann mit der entsprechenden Wertigkeit umrechnen:
0x640x01 + 0x0A05 + 00
daraus müsste nun der binärcode folgen...
welche Funktionen muss ich dafür verwenden?

Am besten verwendet man ein LF oder CR als Endzeichen

C String nach int geht mit atoi() (ascii to integer):
http://www.cplusplus.com/reference/cstdlib/atoi/

Solche Konvertierungsfunktionen gibt es auch für andere Datentypen

Was gefällt dir an meinem Beispiel nicht?

Mach es dir nicht unnötig schwer:

Ein Buchstabe, der sagt was kommt, eine Zahl ( aus mehreren Ziffern ), Zeilenende-Zeichen. Fertig.
Der Dimensionstext und lange Kennungen machen es nur unnötig kompliziert.

Ob du die Zahl mitatoioder mit

if (inchar >= '0' && inchar <= '9') wert = wert*10 + inchar - '0';

bestimmst, bleibt dir überlassen.
Die ASCII-Code-Tabelle brauchst du nicht zu lernen.
Einzelne Buchstaben kannst du schön in einer switch Anweisung abfragen.

cmd = Serial.read(); // 'V' oder 'I' oder 'T'
...
// wenn Zeile fertig gelesen ist und wert decodiert ist 
switch (cmd) {
   case 'I': doStrom(wert); break;
   case 'T': doTime(wert); break;
   ...
}

Hier eine Variante die die Eingabe auf die r2r-Ausgänge ausgibt.
Alle Bits an entspricht 5V.

const char pmsCurr[] PROGMEM = "CURR";
const char pmsTime[] PROGMEM = "TIME";
const char pmsVolt[] PROGMEM = "VOLT";
const char pmsStop[] PROGMEM = "STOP";

const char * const sTable[] PROGMEM = { pmsCurr, pmsTime, pmsVolt, pmsStop };

void cmdCurr(const char* parms);
void cmdTime(const char* parms);
void cmdVolt(const char* parms);
void cmdStop(const char* parms);

typedef void (*ptrToVoidFunctionWithCharPParameter)(const char* parms);

const ptrToVoidFunctionWithCharPParameter fTable[] PROGMEM = { cmdCurr, cmdTime, cmdVolt, cmdStop };

const byte r2rLadder[] = { 2, 3, 4, 5, 6, 7, 8, 9 };  // die kleinen Wertigkeiten zuerst

void setup() {
  for (byte idx = 0; idx < sizeof(r2rLadder); idx++) {
    pinMode(r2rLadder[idx], OUTPUT);
  }
  Serial.begin(250000);
  Serial.print(F("Ich kenne folgende Kommandos:"));
  for (byte i = 0; i < sizeof(sTable) / sizeof(sTable[0]); i++) {
    Serial.write(' ');
    Serial.print((__FlashStringHelper*)pgm_read_word(sTable + i));
  }
  Serial.println();
}

void loop() {
  serialHandler();
}

void processCmd(const char* buf)
{
  for (byte i = 0; i < sizeof(sTable) / sizeof(sTable[0]); i++) {
    const char* sadr = pgm_read_word(sTable + i);
    byte len = strlen_P(sadr);
    if (!strncasecmp_P(buf, sadr, len)) {
      (*((ptrToVoidFunctionWithCharPParameter)pgm_read_word(fTable + i)))(buf + len);
      return;
    }
  }
  Serial.write('\'');
  Serial.print(buf);
  Serial.println(F("' nicht erkannt"));
}

void dumpUsage(const char* name, const char* parms) {
  Serial.print(F("exec "));
  Serial.print((__FlashStringHelper*)name);
  Serial.print(F("(\""));
  Serial.print(parms);
  Serial.println(F("\")"));
}

void cmdCurr(const char* parms) {
  unsigned long value = strtoul(parms, (char**)&parms, 0);
  while (*parms == ' ') {
    parms++;
  }
  if (*parms == 'A') {
    value *= 1000;
  }
  Serial.print(F("Current: "));
  Serial.print(value);
  Serial.println(F(" mA"));
}

void cmdTime(const char* parms) {
  unsigned long value = strtoul(parms, (char**)&parms, 0);
  while (*parms == ' ') {
    parms++;
  }
  if (!strncasecmp_P(parms, PSTR("min"), 3)) {
    value *= 60000L;
  } else if (!strncasecmp_P(parms, PSTR("se"), 2)) {
    value *= 1000;
  } else if (!strncasecmp_P(parms, PSTR("st"), 2)) {
    value *= 60 * 60000L;
  } else if (!strncasecmp_P(parms, PSTR("ze"), 2)) {
    value *= 100;
  } else if (!strncasecmp_P(parms, PSTR("hu"), 2)) {
    value *= 10;
    //  } else if (!strncasecmp_P(parms, PSTR("mil"), 3)) {
  } else {
    value *= 1000;
  }
  Serial.print(F("Time: "));
  Serial.print(value);
  Serial.println(F(" ms"));
}

void cmdVolt(const char* parms) {
  unsigned long value = strtoul(parms, (char**)&parms, 0);
  while (*parms == ' ') {
    parms++;
  }
  if (*parms == 'V') {
    value *= 1000;
  }
  Serial.print(F("Voltage: "));
  Serial.print(value);
  Serial.println(F(" mV"));
  if (value > 5000) {
    value = 5000;
  }
  unsigned int ladderValue = map(value, 0, 5000, 0, (1 << sizeof(r2rLadder)) - 1);
  Serial.print(F("Muster: 0b"));
  Serial.println(ladderValue, BIN);
  int mask = 1;
  for (byte idx = 0; idx < sizeof(r2rLadder); idx++) {
    digitalWrite(r2rLadder[idx], ladderValue & mask);
    mask <<= 1;
  }
}

void cmdStop(const char* parms) {
  dumpUsage(pmsStop, parms);
}

void serialHandler()
{
  const byte sCBMax = 30;
  static char sCBuffer[sCBMax];
  static byte buffIndex = 0;

  byte inChar;
  bool doCheck = false;

  while (Serial.available()) {
    inChar = Serial.read();
    if (inChar == 13) {
      doCheck = true;
    } else if (inChar != 10) {
      if ((buffIndex == 0) && isWhitespace(inChar)) {
        continue;
      }
      sCBuffer[buffIndex++] = inChar;
      doCheck = (buffIndex == (sCBMax - 1));
    }
    if (doCheck) {
      sCBuffer[buffIndex] = 0;
      doCheck = false;
      if (buffIndex != 0) {
        processCmd(sCBuffer);
        buffIndex = 0;
      } else {
        Serial.println();
      }
    }
  }
}
Ich kenne folgende Kommandos: CURR TIME VOLT STOP
Voltage: 5000 mV
Muster: 0b11111111
Voltage: 2500 mV
Muster: 0b1111111
Voltage: 256 mV
Muster: 0b1101

Whandall:
Was gefällt dir an meinem Beispiel nicht?

Sorry, hatte gestern nicht mehr gesehen, dass du geantwortet hast. erstmal Mega Dank! für deine Bemühungen. Was du da in kurzer Zeit auf die Beine gestellt hast, top!

Ich blicke nur leider nicht 100%tig hinter deinem Code.
Ich kann zwar bei einzelnen Funktionen erahnen, was passiert... aber ein kurzer Erklärungskommentar wäre hilfreich, denn ich möchte schon verstehen was ich programmiere und nicht nur einfach kopieren.

itzler:
aber ein kurzer Erklärungskommentar wäre hilfreich, denn ich möchte schon verstehen was ich programmiere

"Mit seriellen Kommandos Werte setzen" zerfällt in drei Teilprobleme

  • Sammeln der hereinkommenden Zeichen, bis eine ganze Zeile eingegangen ist
  • Erkennen welches Kommando gegeben wurde und Analyse der eventuellen Parameter
  • Ausgeben des eingegebenen Wertes auf dem R2R Netztwerk
  1. serialHandler() sammelt und ruft processCmd() mit der fertigen Zeile auf.

  2. processCmd vergleicht den Anfang der Zeile (ohne Berücksichtigung von Groß- und Kleinschreibung)
    mit den vorgegebenen Kommandos (die um RAM zu sparen in PROGMEM untergebracht sind),
    bei Erfolg wird die entsprechende Kommando-Funktion (wieder über eine Tabelle in PROGMEM) aufgerufen.
    Die Kommandos wandeln die Zahl und die Einheit in eine Basiseinheit um (mA, mV, ms).

  3. Im Fall von "Volt" wird das Ergebnis auf den Bereich von 0-5000mV beschränkt,
    dann werden die Bits auf den entsprechenden R2R Pins ausgegeben (via Maske und Pin-Array).

itzler:
Hallo,

ich bin neu auf dem Gebiet der Arduino Programmierung und habe nur Kentnisse der Grundlagen von C-Programmierung.

Mein Problem:

Ich möchte als Beispiel folgende seriellen Code verarbeiten:

CURR 150mA oder TIME 60min

Werden diese Texte zeilenweise gesendet, also am Ende noch mit irgendwelchen Steuerzeichen, die "dasZeilenende" signalisieren, also beispielsweise das Steuerzeichen für "Carriage return "(CR, ASCII-13) und/oder Linefeed(LF, ASCII-10)?

So wie es die Arduino-IDE mit dem Serial.println() Befehl macht?

Serial.println("Hallo") sendet insgesamt sieben Zeichen:

  • die fünf Zeichen des Wortes "Hallo" und zusätzlich Carriage Return und Linefeed als Steuerzeichen ,

"println ist ein Kürzel für "print line), daher am Ende die Steuerzeichen.

Und die Steuerzeichen CR (dezimal13 und LF (dezimal0) sind eine Remineszenz an das Zeitalter de Fernschreiber und elektromechanischen Schreibmaschinen vor dem Computerzeitalter.

Das Steuerzeichen CR (Wagenrücklauf) fährt das Druckwerk ganz an den linken Rand der Seite und das Steuerzeichen LF (line feed), schiebt das Papier um eine Zeile vorwärts.

Und wenn Du heute etwas über Serial empfangen möchtest, dann müßtest Du über diese alten Drucker-Steuerzeichen ggf. immer noch Bescheid wissen:
CURR 150mA oder TIME 60min

Ist das letzte Zeichen jedes Kommandos der Großbu chstabe 'A oder der Kleinbuchstabe 'n, oder folgen danach noch Steuerzeichen, die das Ende des Kommandos bzw. der Zeile signalisieren? Oder möchtest Du die Kommandos im "seriellen Monitor" der IDE von Hand eintragen und senden, und kannest es demzufolge eim seriellen Monitor selbst einstellen, ob und mit welchen Steuerzeichen eine gesendete Zeile beendet werden soll, die mit dem seriellen Monitor gesendet wird?

Whandall:
"Mit seriellen Kommandos Werte setzen" zerfällt in drei Teilprobleme

  • Sammeln der hereinkommenden Zeichen, bis eine ganze Zeile eingegangen ist
  • Erkennen welches Kommando gegeben wurde und Analyse der eventuellen Parameter
  • Ausgeben des eingegebenen Wertes auf dem R2R Netztwerk

Danke, das habe ich jetzt soweit verstanden :wink:

jurs:
Werden diese Texte zeilenweise gesendet, also am Ende noch mit irgendwelchen Steuerzeichen, die "dasZeilenende" signalisieren, also beispielsweise das Steuerzeichen für "Carriage return "(CR, ASCII-13) und/oder Linefeed(LF, ASCII-10)?

Das mit dem Zeilenende ist auch soweit klar. Im Endeffekt kann ich das selber alles vorschreiben bzw. entsprechend programmieren so wie ich es möchte.

Es ist so, dass ich mit dem Arduino und dem daran angeschlossenem R2R-Netzwerk mit OP und Transistor im Grunde ein Netzteil ersetzten möchte/muss. Da dieses Netzteil für meine Anwendung "zulangsam" ist.

aktuell sieht der Befehl für die Stromeinstellung wie folgt aus:

:CURR 150mA "CR" aber ich könnte auch nur die Zahlenwerte übergeben.
Am Ende muss ich nur unterscheiden können, ob ich einen Strom (zwischen 0-1000mA) oder eine Zeit eingestellt habe.

Mein Programm benutzt CR als Zeilentrenner, ignoriert LF und Blanks und Tabs an der ersten Position.

Warum hast du einen Doppelpunkt am Anfang eingeführt?
Um es den Helfern schwerer zu machen?

itzler:
Ich möchte als Beispiel folgende seriellen Code verarbeiten:

CURR 150mA oder TIME 60min

itzler:
aktuell sieht der Befehl für die Stromeinstellung wie folgt aus:

:CURR 150mA "CR" aber ich könnte auch nur die Zahlenwerte übergeben.

Whandall:
Mein Programm benutzt CR als Zeilentrenner, ignoriert LF und Blanks und Tabs an der ersten Position.

Warum hast du einen Doppelpunkt am Anfang eingeführt?
Um es den Helfern schwerer zu machen?

Nein, den habe ich nicht eingeführt. Das ist die Befehlsvorgabe für das vorher verwendete Netzteil!

Aber wie schon geschrieben, bin ich bei der neuen Befehlsübergabe flexibel!

ich denke am einfachsten wäre es, wenn ich als Beispiel C150 oder T60 jeweils als String übergebe. C= Stromwert (in mA) und T= Zeitwert (in min).

so kann ich dann mit dem ersten Zeichen die Fallunterscheidung machen.
Als Endzeichen wird dann wieder ein CR mit geschickt.

wenn ich dein Programm mal teste, passiert leider nix bei mir... habe ich evtl. Einstellungen oder liberys vergessen? bei mir spuckt der monitor direkt am anfang ein Viereck und U aus, das wars.

Ich würde mal die Baudrate auf die verwendete stellen, dann sieht man mehr.

Serial.begin(250000);

itzler:
Nein, den habe ich nicht eingeführt. Das ist die Befehlsvorgabe für das vorher verwendete Netzteil!

Gut, dann ändere ich meine Frage, warum hast du den Doppelpunkt anfangs unterschlagen?

ich denke am einfachsten wäre es, wenn ich als Beispiel C150 oder T60
...
so kann ich dann mit dem ersten Zeichen die Fallunterscheidung machen.

Ja. Einfach switch/case auf das erste Zeichen und dann atoi(buffer + 1) für die Konvertierung.

Whandall:
Gut, dann ändere ich meine Frage, warum hast du den Doppelpunkt anfangs unterschlagen?

weil ich den Grundaufbau des zu übertragenen Befehls selber definieren kann, da ich nicht mehr an den Befehlssatz des Netzgerätes gebunden bin.

Ich wollte mal die Variante mit und ohne PROGMEM vergleichen, um zu sehen, was das bringt, leider meckert der Compiler (UNO, IDE 1.6.5, Sketch aus #7):

In function 'void processCmd(const char*)':
39: error: invalid conversion from 'uint16_t {aka unsigned int}' to 'const char*' [-fpermissive]
invalid conversion from 'uint16_t {aka unsigned int}' to 'const char*' [-fpermissive]

const byte r2rLadder[] = { 2, 3, 4, 5, 6, 7, 8, 9 };  // die kleinen Wertigkeiten zuerst

void setup() {
  for (byte idx = 0; idx < sizeof(r2rLadder); idx++) {
    pinMode(r2rLadder[idx], OUTPUT);
  }
  Serial.begin(9600);
  Serial.println(F("Ich kenne folgende Kommandos: CURR TIME VOLT STOP"));
}

void loop() {
  serialHandler();
}

void processCmd(const char* buf)
{
  if (memcmp( buf, "CURR", 4) == 0) {
    cmdCurr(buf + 4);
    return;
  }
  if (memcmp( buf, "TIME", 4) == 0) {
    cmdTime(buf + 4);
    return;
  }
  if (memcmp( buf, "VOLT", 4) == 0) {
    cmdVolt(buf + 4);
    return;
  }
  if (memcmp( buf, "STOP", 4) == 0) {
    cmdStop(buf + 4);
    return;
  }
  Serial.write('\'');
  Serial.print(buf);
  Serial.println(F("' nicht erkannt"));
}

void cmdCurr(const char* parms) {
  unsigned long value = strtoul(parms, (char**)&parms, 0);
  while (*parms == ' ') {
    parms++;
  }
  if (*parms == 'A') {
    value *= 1000;
  }
  Serial.print(F("Current: "));
  Serial.print(value);
  Serial.println(F(" mA"));
}

void cmdTime(const char* parms) {
  unsigned long value = strtoul(parms, (char**)&parms, 0);
  while (*parms == ' ') {
    parms++;
  }
  if (!strncasecmp_P(parms, PSTR("min"), 3)) {
    value *= 60000L;
  } else if (!strncasecmp_P(parms, PSTR("se"), 2)) {
    value *= 1000;
  } else if (!strncasecmp_P(parms, PSTR("st"), 2)) {
    value *= 60 * 60000L;
  } else if (!strncasecmp_P(parms, PSTR("ze"), 2)) {
    value *= 100;
  } else if (!strncasecmp_P(parms, PSTR("hu"), 2)) {
    value *= 10;
    //  } else if (!strncasecmp_P(parms, PSTR("mil"), 3)) {
  } else {
    value *= 1000;
  }
  Serial.print(F("Time: "));
  Serial.print(value);
  Serial.println(F(" ms"));
}

void cmdVolt(const char* parms) {
  unsigned long value = strtoul(parms, (char**)&parms, 0);
  while (*parms == ' ') {
    parms++;
  }
  if (*parms == 'V') {
    value *= 1000;
  }
  Serial.print(F("Voltage: "));
  Serial.print(value);
  Serial.println(F(" mV"));
  if (value > 5000) {
    value = 5000;
  }
  unsigned int ladderValue = map(value, 0, 5000, 0, (1 << sizeof(r2rLadder)) - 1);
  Serial.print(F("Muster: 0b"));
  Serial.println(ladderValue, BIN);
  int mask = 1;
  for (byte idx = 0; idx < sizeof(r2rLadder); idx++) {
    digitalWrite(r2rLadder[idx], ladderValue & mask);
    mask <<= 1;
  }
}

void cmdStop(const char* parms) {
  for (byte idx = 0; idx < sizeof(r2rLadder); idx++) {
    digitalWrite(r2rLadder[idx], LOW);
  }
  Serial.print(F("Stopp "));
  Serial.println(parms);
}

void serialHandler()
{
  const byte sCBMax = 30;
  static char sCBuffer[sCBMax];
  static byte buffIndex = 0;

  byte inChar;
  bool doCheck = false;

  while (Serial.available()) {
    inChar = Serial.read();
    if (inChar == 13) {
      doCheck = true;
    } else if (inChar != 10) {
      if ((buffIndex == 0) && isWhitespace(inChar)) {
        continue;
      }
      sCBuffer[buffIndex++] = inChar;
      doCheck = (buffIndex == (sCBMax - 1));
    }
    if (doCheck) {
      sCBuffer[buffIndex] = 0;
      doCheck = false;
      if (buffIndex != 0) {
        processCmd(sCBuffer);
        buffIndex = 0;
      } else {
        Serial.println();
      }
    }
  }
}

Der Sketch verwendet 4.740 Bytes (14%) des Programmspeicherplatzes. Das Maximum sind 32.256 Bytes.
Globale Variablen verwenden 265 Bytes (12%) des dynamischen Speichers, 1.783 Bytes für lokale Variablen verbleiben. Das Maximum sind 2.048 Bytes.

Da fehlt ein Cast, erstaunlicherweise produziert das auf meiner 1.8.1 keine Warnungen.

    const char* sadr = (const char*)pgm_read_word(sTable + i);
const char pmsCurr[] PROGMEM = "CURR";
const char pmsTime[] PROGMEM = "TIME";
const char pmsVolt[] PROGMEM = "VOLT";
const char pmsStop[] PROGMEM = "STOP";

const char * const sTable[] PROGMEM = { pmsCurr, pmsTime, pmsVolt, pmsStop };

void cmdCurr(const char* parms);
void cmdTime(const char* parms);
void cmdVolt(const char* parms);
void cmdStop(const char* parms);

typedef void (*ptrToVoidFunctionWithCharPParameter)(const char* parms);

const ptrToVoidFunctionWithCharPParameter fTable[] PROGMEM = { cmdCurr, cmdTime, cmdVolt, cmdStop };

const byte r2rLadder[] = { 2, 3, 4, 5, 6, 7, 8, 9 };  // die kleinen Wertigkeiten zuerst

void setup() {
  for (byte idx = 0; idx < sizeof(r2rLadder); idx++) {
    pinMode(r2rLadder[idx], OUTPUT);
  }
  Serial.begin(250000);
  Serial.print(F("Ich kenne folgende Kommandos:"));
  for (byte i = 0; i < sizeof(sTable) / sizeof(sTable[0]); i++) {
    Serial.write(' ');
    Serial.print((__FlashStringHelper*)pgm_read_word(sTable + i));
  }
  Serial.println();
}

void loop() {
  serialHandler();
}

void processCmd(const char* buf)
{
  for (byte i = 0; i < sizeof(sTable) / sizeof(sTable[0]); i++) {
    const char* sadr = (const char*)pgm_read_word(sTable + i);
    byte len = strlen_P(sadr);
    if (!strncasecmp_P(buf, sadr, len)) {
      (*((ptrToVoidFunctionWithCharPParameter)pgm_read_word(fTable + i)))(buf + len);
      return;
    }
  }
  Serial.write('\'');
  Serial.print(buf);
  Serial.println(F("' nicht erkannt"));
}

void dumpUsage(const char* name, const char* parms) {
  Serial.print(F("exec "));
  Serial.print((__FlashStringHelper*)name);
  Serial.print(F("(\""));
  Serial.print(parms);
  Serial.println(F("\")"));
}

void cmdCurr(const char* parms) {
  unsigned long value = strtoul(parms, (char**)&parms, 0);
  while (*parms == ' ') {
    parms++;
  }
  if (*parms == 'A') {
    value *= 1000;
  }
  Serial.print(F("Current: "));
  Serial.print(value);
  Serial.println(F(" mA"));
}

void cmdTime(const char* parms) {
  unsigned long value = strtoul(parms, (char**)&parms, 0);
  while (*parms == ' ') {
    parms++;
  }
  if (!strncasecmp_P(parms, PSTR("min"), 3)) {
    value *= 60000L;
  } else if (!strncasecmp_P(parms, PSTR("se"), 2)) {
    value *= 1000;
  } else if (!strncasecmp_P(parms, PSTR("st"), 2)) {
    value *= 60 * 60000L;
  } else if (!strncasecmp_P(parms, PSTR("ze"), 2)) {
    value *= 100;
  } else if (!strncasecmp_P(parms, PSTR("hu"), 2)) {
    value *= 10;
    //  } else if (!strncasecmp_P(parms, PSTR("mil"), 3)) {
  } else {
    value *= 1000;
  }
  Serial.print(F("Time: "));
  Serial.print(value);
  Serial.println(F(" ms"));
}

void cmdVolt(const char* parms) {
  unsigned long value = strtoul(parms, (char**)&parms, 0);
  while (*parms == ' ') {
    parms++;
  }
  if (*parms == 'V') {
    value *= 1000;
  }
  Serial.print(F("Voltage: "));
  Serial.print(value);
  Serial.println(F(" mV"));
  if (value > 5000) {
    value = 5000;
  }
  unsigned int ladderValue = map(value, 0, 5000, 0, (1 << sizeof(r2rLadder)) - 1);
  Serial.print(F("Muster: 0b"));
  Serial.println(ladderValue, BIN);
  int mask = 1;
  for (byte idx = 0; idx < sizeof(r2rLadder); idx++) {
    digitalWrite(r2rLadder[idx], ladderValue & mask);
    mask <<= 1;
  }
}

void cmdStop(const char* parms) {
  dumpUsage(pmsStop, parms);
}

void serialHandler()
{
  const byte sCBMax = 30;
  static char sCBuffer[sCBMax];
  static byte buffIndex = 0;

  byte inChar;
  bool doCheck = false;

  while (Serial.available()) {
    inChar = Serial.read();
    if (inChar == 13) {
      doCheck = true;
    } else if (inChar != 10) {
      if ((buffIndex == 0) && isWhitespace(inChar)) {
        continue;
      }
      sCBuffer[buffIndex++] = inChar;
      doCheck = (buffIndex == (sCBMax - 1));
    }
    if (doCheck) {
      sCBuffer[buffIndex] = 0;
      doCheck = false;
      if (buffIndex != 0) {
        processCmd(sCBuffer);
        buffIndex = 0;
      } else {
        Serial.println();
      }
    }
  }
}
Der Sketch verwendet 4024 Bytes (13%) des Programmspeicherplatzes. Das Maximum sind 30720 Bytes.
Globale Variablen verwenden 227 Bytes (11%) des dynamischen Speichers, 1821 Bytes für lokale Variablen verbleiben. Das Maximum sind 2048 Bytes.
 unsigned int ladderValue = map(value, 0, 5000, 0, (1 << sizeof(r2rLadder)) - 1);
  Serial.print(F("Muster: 0b"));
  Serial.println(ladderValue, BIN);
  int mask = 1;
  for (byte idx = 0; idx < sizeof(r2rLadder); idx++) {
    digitalWrite(r2rLadder[idx], ladderValue & mask);
    mask <<= 1;
  }

ich verstehe das grob, dass der eingegebene Spannungswert auf den Bereich von 0-5000 aufgeteilt wird und der daraus entstehende Binärcode an die pins übertragen wird, richtig?

wenn ich den bereich von 0-1000 wähle und jetzt 150mA eingebe, wird dieser Wert doch dann für diesen Bereich explizit in einen Binärcode gewandelt der dann die passende Pinbelegung sendet, oder?