DCC++ Wi-Fi Receiver Trackside or Onboard

Simon Mitchell Oct 30, 2016

  1. Simon Mitchell

    Simon Mitchell TrainBoard Member

    113
    105
    7
    Excellent, thanks Steve.

    Haven't been to Wakefield yet, it's on my list of places to visit next time I'm in the U.K..
     
    Scott Eric Catalano likes this.
  2. UK Steve

    UK Steve TrainBoard Member

    453
    683
    12
    Simon

    Well, keep us posted if you get that working. I'm sure you've realised that one bit of code is almost central to the rest.

    It certainly is in my ESP throttle.

    It is important that it executes rapidly, and is not blocked with other stuff you will have going on.
    To that end, try to shape everything with that in mind and keep all other functions as short as possible.

    Poor decisions from here, and you might start to encounter unexplained crashing of your ESP's.

    Do some research on the yield() function, as you have to keep the chips wifi functionality happy in tandem with your code.
     
    Scott Eric Catalano likes this.
  3. Simon Mitchell

    Simon Mitchell TrainBoard Member

    113
    105
    7
    OK, I think I got that bit to work. Tried to stay away from 'String' functions per above. It isn't quite spot on - if I enter 3 command strings, eg <t 1 5565 20 1><t 1 5565 30 1><t 1 5565 40 1>, it will skip the middle command. However you have certainly shown me how to index int/byte in the string.vI'm not sure yet how I want to handle the other command types, so I'm leaning towards trying to use the switch t, f etc shown above.

    Code:
    #define INPUT_SIZE 31
    
    void setup() {
      // initialize serial port:
      Serial.begin(19200);
    }
    
    void loop() {
      if (Serial.available())
    
      {
        char instring[INPUT_SIZE + 1]; //SM trying to create a string
        byte size = Serial.readBytes(instring, INPUT_SIZE);
        instring[size] = 0; //final character added to string
        Serial.println(instring); //just some de-bugging progress, print the raw string
        char delimiters[] = "<t >\r\n";
        char* valPosition = strtok(instring, delimiters);
        int tndx[] = {0, 0, 0, 0};//Was 3 x byte, upgraded to 4 x int.
    
        for (int i = 0; i < 4; i++) {
          tndx[i] = atoi(valPosition);
          valPosition = strtok(NULL, delimiters);
          if (i == 3) {
            byte thisReg = tndx[0];
            int thisLoco = tndx[1];
            byte thisSpeed = tndx[2];
            byte thisDir = tndx[3];
            //do something with the values
            Serial.print(" thisReg ");
            Serial.print(thisReg);
            Serial.print(" thisLoco ");
            Serial.print(thisLoco);
            Serial.print(" thisSpeed ");
            Serial.print(thisSpeed);
            Serial.print(" thisDir ");
            Serial.println(thisDir);
    
          }
        }
      }
    }
    
     
    Scott Eric Catalano likes this.
  4. UK Steve

    UK Steve TrainBoard Member

    453
    683
    12
    OK,

    Some stuff for you to improve things.

    I tried your version on an ESP and despite some mods I did, it still runs incredibly slow :( so much for 'C' strings then.

    So back to Arduino String class.

    This version runs very fast :)
    Oh, and do get into the habit of not doing much in loop().
    I've put a cycle counter in to show how loop() performs. Try adding code for other stuff and see how it gets slowed down.
    Edit: this is more true of the slower Arduinos, not so much on 32bit ESP's

    As for f and T stuff, well writing variants of this code for each type and some clever sorting of the marker chars/String to the relevant function and you're done. Edit: See next post. :)

    This will be my last coding hurrah for your project, I don't want to write the whole thing :)
    I'll be glad to offer advice though, and I'm interested in your progress.

    Steve.
    Code:
    uint32_t count = 0;
    uint16_t interval = 1000;
    uint32_t previousMillis = 0;
    String instring;
    
    
    void hertzCounter() {
    
      previousMillis = millis();
      Serial.print(count);
      Serial.println(" Hz");
      count = 0;
    }
    
    void parseCmdString() {
    
      while (Serial.available()) {
        char inChar = (char)Serial.read();
        instring += inChar;
        if (inChar == '>') {
          Serial.println(instring); //just some de-bugging progress, print the raw string   
          yield();
          char Instring[21];
          instring.toCharArray(Instring, 21);
          instring = "";
          char delimiters[] = "<t >\r\n";
          char* valPosition = strtok(Instring, delimiters);
          int tndx[] = {0, 0, 0, 0};//Was 3 x byte, upgraded to 4 x int.
    
          for (int i = 0; i < 4; i++) {
            tndx[i] = atoi(valPosition);
            valPosition = strtok(NULL, delimiters);
            if (i == 3) {
              byte thisReg = tndx[0];
              int thisLoco = tndx[1];
              byte thisSpeed = tndx[2];
              byte thisDir = tndx[3];
              //do something with the values
              Serial.print(" thisReg ");
              Serial.print(thisReg);
              Serial.print(" thisLoco ");
              Serial.print(thisLoco);
              Serial.print(" thisSpeed ");
              Serial.print(thisSpeed);
              Serial.print(" thisDir ");
              Serial.println(thisDir);
              yield();
            }
          }
        }
      }
    }
    void setup() {
      // initialize serial port:
      Serial.begin(115200);
    }
    
    void loop() {
    
      count ++;
    
      if (millis() - previousMillis >= interval) {
        hertzCounter();
      }
      parseCmdString();
    }
     
    Last edited: Jan 16, 2017
    Scott Eric Catalano likes this.
  5. UK Steve

    UK Steve TrainBoard Member

    453
    683
    12
    So, parseCmdString() would just deal with String building and pointing

    Code:
    while (Serial.available()) {
        char inChar = (char)Serial.read();
        instring += inChar;
        if (inChar == '>') {
           if (instring.startsWith("<t") processThrottle();
           if (instring.startsWith("<f") processFunction();
           if (instring.startsWith("<T") processTurnout();//Although you wouldn't need this on a loco     
        }
    }
    Add appropriate space if your throttles send in the "< t" format
     
    Scott Eric Catalano likes this.
  6. UK Steve

    UK Steve TrainBoard Member

    453
    683
    12
    Hi Simon,

    I wasn't at all happy with the lack of performance when 'reading in' 'C' strings.

    I guess you found your example here http://arduino.stackexchange.com/questions/1013/how-do-i-split-an-incoming-string

    After some tinkering, I came up with my own version, which makes sense to me anyway.
    Thing is, it works very fast, which is what we want. As well it should, there is much less overhead if we can avoid Arduino String.

    I would run with this version :)

    The caveat though is that .startsWith() is another Arduino String function.

    You can however do something like

    if (instring[2] == 't') blabla;//Remember the index starts from 0 and you need to count any spaces.

    Steve.

    Update: For the record my non scientific timings suggest it takes 1ms or less to execute the code below, given 1 command to process. This gets a little longer if there are several sent at once.

    Code:
    uint32_t count = 0;
    uint16_t interval = 1000;
    uint32_t previousMillis = 0;
    
    void hertzCounter() {
      previousMillis = millis();
      Serial.print(count);
      Serial.println(" Hz");
      count = 0;
    }
    
    void parseCmdString() {
     
      char instring [21];
      byte ndx = 0;
    
      while (Serial.available()) {
     
        char inChar = (char)Serial.read();
        instring[ndx] = inChar;
        ndx ++;
        if (inChar == '>') {
          instring[ndx] = '\0';
          ndx = 0;
          Serial.println(instring); //just some de-bugging progress, print the raw string  
          yield();          
          char delimiters[] = "<t >\r\n";
          char* valPosition = strtok(instring, delimiters);
          int tndx[] = {0, 0, 0, 0};//Was 3 x byte, upgraded to 4 x int.
    
          for (int i = 0; i < 4; i++) {
            tndx[i] = atoi(valPosition);
            valPosition = strtok(NULL, delimiters);
            if (i == 3) {
              byte thisReg = tndx[0];
              int thisLoco = tndx[1];
              byte thisSpeed = tndx[2];
              byte thisDir = tndx[3];
              //do something with the values
              Serial.print(" thisReg ");
              Serial.print(thisReg);
              Serial.print(" thisLoco ");
              Serial.print(thisLoco);
              Serial.print(" thisSpeed ");
              Serial.print(thisSpeed);
              Serial.print(" thisDir ");
              Serial.println(thisDir);
              yield();
            }
          }  
        }
      }
    }
    
    void setup() {
      // initialize serial port:
      Serial.begin(115200);
    }
    
    void loop() {
    
      count ++;
    
      if (millis() - previousMillis >= interval) {
        hertzCounter();
      }
     
      parseCmdString();
    }
     
    Last edited: Jan 16, 2017
    Scott Eric Catalano likes this.
  7. Simon Mitchell

    Simon Mitchell TrainBoard Member

    113
    105
    7
    Thanks Steve. I'll play and learn. Really appreciate the guidance. Pretty sure after all the uploads to the little WeMos D1 that it will be life expired when I'm done learning!!
     
    Scott Eric Catalano likes this.
  8. Simon Mitchell

    Simon Mitchell TrainBoard Member

    113
    105
    7
    Thanks again Steve.

    This bit of code
    Code:
       char inChar = (char)Serial.read();
        instring[ndx] = inChar;
        ndx ++;
        if (inChar == '>') {
          instring[ndx] = '\0';
          ndx = 0;
    
    I haven't sorted out fully as yet, it was returning ">" but none of the rest of the string. I haven't focused on it as it only came into play with multiple commands without an "\r" or "\n" and I don't know if JMRI/DCC++ do this.

    I tried the instring[2] == t, but that was a bit inflexible, eg there is a possibility of "< t" in the commands spec. Could do "if (instring[2] == t || instring[3] ==t)" but I figured that was similar to "if (srchr(instring, 'f'))"

    I've ported over the rest of the structure and hot tips, and extended the huge nest of if statements to cover function commands. I'm presently having fun with that as they can be
    • < f 3 191> for F0-F12
    • < f 3 222 255> for F13-F20
    • < f 3 223 255> for F20-F28
    and the srttok doesn't like to expect i ==3 when there is no 4th field to examine!

    I've not uploaded my new sketch, I really appreciate your help, certainly don't want you doing the whole thing. It is starting to take up some real estate!!
     
    Scott Eric Catalano likes this.
  9. UK Steve

    UK Steve TrainBoard Member

    453
    683
    12
    Hi Simon,

    The code as I posted, is a working sketch that will compile and run on the ESP.

    Code:
    void parseCmdString() {
     
      char instring [21];
      byte ndx = 0;  <----The position of this variable is important
    
      while (Serial.available()) {
     
        uint32_t startTime = millis();
        char inChar = (char)Serial.read();  
        instring[ndx] = inChar;
        ndx ++;  
        if (inChar == '>') {
          instring[ndx] = '\0';
          ndx = 0; 
    Note where byte ndx is positioned. It sound like you have moved it and the count is getting reset to 0 on each char read.

    Most throttle designs do not send \r or \n with the command. You should emulate that also in testing.

    In tests I was throwing a whole bunch of 12 commands sequentially with no line breaks or spaces between them. The code had no trouble sorting it all out :)

    Again Gregg will provide a method in Base Station code to do the sorting and parsing of all DCC++ commands.
    That would be my first port of call, as you are in effect reverse engineering.

    As for the rest of the code, well my 10 channel throttle design is running over 800 lines, this stuff can get seriously complex :)

    Steve.
     
    Last edited: Jan 17, 2017
    Scott Eric Catalano likes this.
  10. Simon Mitchell

    Simon Mitchell TrainBoard Member

    113
    105
    7
    Hmm..
    I went back to the direct copy of your sketch that I made this morning and tried to run as a stand alone. I dropped out the subsequent strtok stuff as that was throwing an error, and Serial.println(instring); was returning > in the serial monitor. Also "uint32_t startTime = millis();" isn't utilised as far as I can see, so I get a scrolling hz value.

    I'm concerned that this is tying up your time!

    Code:
    uint32_t count = 0;
    uint16_t interval = 1000;
    uint32_t previousMillis = 0;
    
    void hertzCounter() {
      previousMillis = millis();
      Serial.print(count);
      Serial.println(" Hz");
      count = 0;
    }
    
    void parseCmdString() {
      char instring [21];
      byte ndx = 0;
    
      while (Serial.available()) {
        uint32_t startTime = millis();
        char inChar = (char)Serial.read();
        instring[ndx] = inChar;
        ndx ++;
        if (inChar == '>') {
          instring[ndx] = '\0';
          ndx = 0;
          Serial.println(instring); //just some de-bugging progress, print the raw string  
          yield();       
    /*      char delimiters[] = "<t >\r\n";
          char* valPosition = strtok(instring, delimiters);
          int tndx[] = {0, 0, 0, 0};//Was 3 x byte, upgraded to 4 x int.
    
          for (int i = 0; i < 4; i++) {
            tndx[i] = atoi(valPosition);
            valPosition = strtok(NULL, delimiters);
            if (i == 3) {
              byte thisReg = tndx[0];
              int thisLoco = tndx[1];
              byte thisSpeed = tndx[2];
              byte thisDir = tndx[3];
              //do something with the values
              Serial.print(" thisReg ");
              Serial.print(thisReg);
              Serial.print(" thisLoco ");
              Serial.print(thisLoco);
              Serial.print(" thisSpeed ");
              Serial.print(thisSpeed);
              Serial.print(" thisDir ");
              Serial.println(thisDir);
              yield();
            }
          } */
        }
      }
    }
    
    void setup() {
      // initialize serial port:
      Serial.begin(115200);
    }
    
    void loop() {
    
      count ++;
    
      if (millis() - previousMillis >= interval) {
        hertzCounter();
      }
      parseCmdString();
    }
    
     
    Scott Eric Catalano likes this.
  11. UK Steve

    UK Steve TrainBoard Member

    453
    683
    12
    You're right about "uint32_t startTime = millis();"

    That shouldn't be in there, it was a later addition to time the function, however I was just posting that as an incomplete snippet.

    I'll reload the sketch to my ESP and get back to you.

    Don't get too concerned about my time, if I have some you'll get a reply and if I'm busy probably not :)
     
    Scott Eric Catalano likes this.
  12. UK Steve

    UK Steve TrainBoard Member

    453
    683
    12
    So, I loaded the sketch, connected a Serial Monitor and here are the results.

    Note the long string of back to back commands ready to go (9 total) setting is no lf or cr.

    CaptureSM2.PNG

    Hit send, and a few milliseconds later we get..

    Code:
    89557 Hz
    <t 1 5565 20 1>
     thisReg 1 thisLoco 5565 thisSpeed 20 thisDir 1
    Time: 0 ms
    <t 1 5565 30 1>
     thisReg 1 thisLoco 5565 thisSpeed 30 thisDir 1
    Time: 1 ms
    <t 1 5565 40 1>
     thisReg 1 thisLoco 5565 thisSpeed 40 thisDir 1
    Time: 6 ms
    <t 1 5565 20 1>
     thisReg 1 thisLoco 5565 thisSpeed 20 thisDir 1
    Time: 6 ms
    <t 1 5565 30 1>
     thisReg 1 thisLoco 5565 thisSpeed 30 thisDir 1
    Time: 6 ms
    <t 1 5565 40 1>
     thisReg 1 thisLoco 5565 thisSpeed 40 thisDir 1
    Time: 7 ms
    <t 1 5565 20 1>
     thisReg 1 thisLoco 5565 thisSpeed 20 thisDir 1
    Time: 6 ms
    <t 1 5565 30 1>
     thisReg 1 thisLoco 5565 thisSpeed 30 thisDir 1
    Time: 6 ms
    <t 1 5565 40 1>
     thisReg 1 thisLoco 5565 thisSpeed 40 thisDir 1
    Time: 6 ms
    85079 Hz
    
    Nothing wrong with that ?

    Which board are you selecting in Arduino IDE ?
     
    Last edited: Jan 17, 2017
    Scott Eric Catalano likes this.
  13. Simon Mitchell

    Simon Mitchell TrainBoard Member

    113
    105
    7
    Hi Steve,
    Board selected is "WeMos D1 R2 & mini, 80 MHz, 921600, 4M (3M SPIFFS)". No line ending in serial monitor

    I get;
     
    Scott Eric Catalano likes this.
  14. UK Steve

    UK Steve TrainBoard Member

    453
    683
    12
    Simon,

    Something is crashing your stack, that's a more serious code error.

    Try compile for another board, say NodeMCU 1.0 (ESP-12E Module) and drop the upload speed to 115200

    Here's the code again, just in case you've introduced some error, I note from the serial output you're still only getting the end char '>'

    You could try making byte ndx a global by moving its declaration to the list at the top of the sketch.

    Code:
    uint32_t count = 0;
    uint16_t interval = 1000;
    uint32_t previousMillis = 0;
    
    void hertzCounter() {
      previousMillis = millis();
      Serial.print(count);
      Serial.println(" Hz");
      count = 0;
    }
    
    void parseCmdString() {
    
      char instring [21];
      byte ndx = 0;
    
      while (Serial.available()) {
    
        uint32_t startTime = millis();
        char inChar = (char)Serial.read();
        instring[ndx] = inChar;
        ndx ++;
        if (inChar == '>') {
          instring[ndx] = '\0';
          ndx = 0;
          Serial.println(instring); //just some de-bugging progress, print the raw string
          yield();
          char delimiters[] = "<t >\r\n";
          char* valPosition = strtok(instring, delimiters);
          int tndx[] = {0, 0, 0, 0};//Was 3 x byte, upgraded to 4 x int.
    
          for (int i = 0; i < 4; i++) {
            tndx[i] = atoi(valPosition);
            valPosition = strtok(NULL, delimiters);
            if (i == 3) {
              byte thisReg = tndx[0];
              int thisLoco = tndx[1];
              byte thisSpeed = tndx[2];
              byte thisDir = tndx[3];
              //do something with the values
              Serial.print(" thisReg ");
              Serial.print(thisReg);
              Serial.print(" thisLoco ");
              Serial.print(thisLoco);
              Serial.print(" thisSpeed ");
              Serial.print(thisSpeed);
              Serial.print(" thisDir ");
              Serial.println(thisDir);
              Serial.print("Time: ");
              Serial.print(millis() - startTime);
              Serial.println(" ms");
              yield();
            }
          }
        }
      }
    }
    
    void setup() {
      // initialize serial port:
      Serial.begin(115200);
    }
    
    void loop() {
    
      count ++;
    
      if (millis() - previousMillis >= interval) {
        hertzCounter();
      }
    
      parseCmdString();
    }
     
    Scott Eric Catalano likes this.
  15. Simon Mitchell

    Simon Mitchell TrainBoard Member

    113
    105
    7
    I did a direct copy and paste, changed to NodeMCU 1.0, 115200 upload speed, same result. Arduino 1.6.12 on a little VAIO POS laptop. Bummer, because your result looks great, and would save a whole bunch of code in my 'clean sheet' sketch. I'll keep prodding and poking it..
     
    Scott Eric Catalano likes this.
  16. UK Steve

    UK Steve TrainBoard Member

    453
    683
    12
    What are you using to send the commands?

    Is the baud set at 115200?

    I note you use different baud rate in your earlier posts.
     
    Scott Eric Catalano likes this.
  17. Simon Mitchell

    Simon Mitchell TrainBoard Member

    113
    105
    7
    Just the arduino serial monitor

    yes, baud set at 115200, I was running 19200 but changed to what you were running.
     
  18. UK Steve

    UK Steve TrainBoard Member

    453
    683
    12
    Hi Simon,

    I had a little time to tinker with crash scenarios.

    It seems to be very robust. Only something truly bonkers produces a melt down like yours.

    For instance < t 104674 154783 134926 1014 > I get......
    Code:
    93104 Hz
    93101 Hz
    93097 Hz
    < t 104674 154783 134926 1014 >
    
    Exception (28):
    epc1=0x40204c06 epc2=0x00000000 epc3=0x00000000 excvaddr=0x00000000 depc=0x00000000
    Note it got the whole array and printed before the crash though, even though array length initialised at 21 char's.

    Even when things have gone outside the set parameters we still get something, note the 'this' values from <t 65535 65535 65535 65535> but it parsed out without crashing !

    Code:
    88468 Hz
    88467 Hz
    88468 Hz
    <t 65535 65535 65535 65535>
     thisReg 255 thisLoco 65535 thisSpeed 255 thisDir 143
    Time: 1 ms
    88423 Hz
    88459 Hz
    So I'm puzzled as to why your WeMos won't do the same. Keep me posted.

    If anyone can do some tests on their ESP's, we would be pleased to get some feedback.

    Steve.
     
    Scott Eric Catalano likes this.
  19. Simon Mitchell

    Simon Mitchell TrainBoard Member

    113
    105
    7
    I shut down everything last night. An ESP8266 update came through, no joy with sscanf, which would have been really nice!
    tried all sorts of things today, and didn't get a result. Not to worry, I'm making progress anyway..appreciate the help!
     
    Scott Eric Catalano likes this.
  20. Simon Mitchell

    Simon Mitchell TrainBoard Member

    113
    105
    7
    I had a closer look at this again today. I think the strtok solution may work, even though it means more code in my sketch.
     
    Scott Eric Catalano likes this.

Share This Page