Best Practices - Ethernet Controller Stepper

Hello All,

I am working on a stepper controller that responds to commands sent through Ethernet TCP.

Here is the working code:

#include <AccelStepper.h>
const int stepPin = 9;
const int dirPin  = 8;
const int ledPin  = 13;
const int maxSpeed= 1000;
const int maxAccel= 100;
int stepVel = 500;
int stepPos = 0;
bool blocking = false;

AccelStepper stepper(1, stepPin, dirPin);

//Timing Settings
float timeStart       = 0;     //Time at beginning of interval
const int timeLimit   = 10;    //Interval Limit

//Ethernet Settings
#include <Ethernet.h>
byte mac[]      = {0x54, 0x52, 0x49, 0x41, 0x44, 0x00};
byte ip[]       = {192, 168, 2, 99};
byte gateway[]  = {192, 168, 0, 1};
byte subnet[]   = {255, 255, 0, 0};
EthernetServer server(4444);
EthernetClient client;

//Message Settings
const byte numChars = 32;
boolean newData     = false;
boolean dataReady   = false;
char receivedChars[numChars];

//Communication Switch
boolean serialOn    = true;

void setup() {
  timeStart = millis();

  if (serialOn)   configSerial();
  configEthernet();
  configStepper();
}

void configSerial()
{
    Serial.begin(9600);
    Serial.println("Ready");
}

void configEthernet()
{
    Ethernet.begin(mac, ip, gateway, subnet);
    server.begin();
    if (serialOn) Serial.println("Server Started");
}

void configStepper()
{
    stepper.setAcceleration(maxAccel);
    stepper.setMaxSpeed(maxSpeed);
}

void loop() 
{
    receive();
    if (dataReady)
    {
        parseData();
        if (serialOn) showParsedData();
        dataReady = false;
    }

    if (blocking)
      blockingRunSpeedToPosition(stepPos, stepVel);
    else
      RunSpeedToPosition(stepPos, stepVel);
}

void receive()
{
    static boolean receiving = false;
    static byte ndx = 0;
    char startChar = '<';
    char endChar   = '>';
    char rc;
  
    client = server.available();
    while(client.available() > 0 && dataReady == false)
    {
      stepper.runSpeedToPosition();
        rc = client.read();
        if (receiving)
        {
            if (rc != endChar)
            {
                receivedChars[ndx] = rc;
                ndx++;
                if (ndx>= numChars) ndx = numChars - 1;
            }
            else
            {
                receivedChars[ndx] = '\0';
                receiving = false;
                ndx = 0;
                dataReady = true;
            }
        }
        else if (rc == startChar) receiving = true;
    }
}

void parseData()
{
    char * strtokIndx;
    
    strtokIndx = strtok(receivedChars, ",");
    stepPos = atoi(strtokIndx);
    strtokIndx = strtok(NULL, ",");
    stepVel = atoi(strtokIndx);
    strtokIndx = strtok(NULL, ",");
    blocking = atoi(strtokIndx);
}

void showParsedData() 
{
     Serial.print("stepPos = ");
     Serial.println(stepPos);
     Serial.print("stepVel = ");
     Serial.println(stepVel);
     Serial.print("blocking = ");
     Serial.println(blocking);
     Serial.println("");
}


void blockingRunSpeedToPosition(int pos, int vel)
{
    int currentPos = stepper.currentPosition();
    if (vel > maxSpeed)
      vel = maxSpeed -1;
    if (pos < currentPos)
      vel = vel*-1;
    
    stepper.moveTo(pos);
    stepper.setSpeed(vel);
    while (stepper.distanceToGo() != 0)
      stepper.runSpeedToPosition();
}

void RunSpeedToPosition(int pos, int vel)
{
    int currentPos = stepper.currentPosition();
    if (vel > maxSpeed)
      vel = maxSpeed -1;
    if (pos < currentPos)
      vel = vel*-1;
      
    stepper.moveTo(pos);
    stepper.setSpeed(vel);
    stepper.runSpeedToPosition();
}

I would like to know how I could get this better setup so that the arduino could receive multiple commands (position, speed, blocking) to be stored in a queue for sequential execution.

Right now, when I have blocking set to true, the stepper will complete its move before receiving the next command. The TCP protocol seems to have a queue by default (not sure if this is on my computer side or the arduino) but I am not sure of the limits of this queue, nor do I know how to purge it if needed.

I have the option to turn blocking off so I can have the stepper respond immediately to new commands if needed. This does not work so well, because the stepper slows down while receiving the new command…

Any advice would be greatly appreciated. This is for a simple turn table.

Kind regards,
Jake

You seem to have runSpeedToPosition() scattered all over the place. It would be much better to design your program so it is only needed in one place.

Also, have you considered using the non-blocking runSpeed() which will allow the stepper movements to happen in parallel with other activity provided (of course) that the rest of your allows loop() to repeat fast enough (which is should).

If the receiving of the data for a new move cannot happen without interfering with the stepper then you have no alternative but to interleave the processes.

My own stepper program receives data over a serial connection and the data for each move occupies less than 64 bytes so it will all fit in the Serial Input Buffer. The last action before starting a move is to request the data for the next move. By the time the motors have finished the move the new data will be waiting in the buffer.

I am not familiar with the Ethernet library but I suspect a similar approach may be appropriate.

...R

Thank you for the input.

I initially had stepper.runSpeedToPosition() on in the blocking or non-blocking functions. I added it into the while loop of receive() so I could tell the stepper to keep stepping while I was receiving additional characters.

I should probably rename my functions so they don't have the same name as the accelstepper functions for clarity.

I will also look into the runSpeed() option, but if I'm not mistaken, this function does not control where the stepper is going it just says a directions and speed so I would lose my position control. I am also not too familiar with how that function is non-blocking. In the class documentation, runSpeed() has to be called as frequently as possible or at least once per step required for the speed setting.

If there is a way to run both functions (collecting data + running the motor) in parallel so they don't interrupt each other, I would be interested.

The Ethernet receive() starts a while loop if there is a new message. It then collects each character of the message (1 char per loop). This is why I added the stepper.runSpeedToPosition() in this while loops so it would call as frequently as possible even when a message is being recorded.

I would like to see how you are collection your messages if possible. I am open to other options.

Kind regards, Jake

Robin2: each move occupies less than 64 bytes so it will all fit in the Serial Input Buffer.

Does this mean that you are collecting your whole message with one command? I am receiving one character at a time and would like to know how to collect more if possible.

mjnewsum:
I should probably rename my functions so they don’t have the same name as the accelstepper functions for clarity.

I was not referring to the similarity of names (though that is confusing) but more generally to the existence of runSpeedToPosition() on several separate lines of code. There may be a case for having it in setup() but once out of setup() there should just be a single line of code with runSpeedToPosition() - or runSpeed() if you move on to that.

Does this mean that you are collecting your whole message with one command? I am receiving one character at a time and would like to know how to collect more if possible.

Yes. For a single move my PC sends a message like <450000, 9000,0,18000> which tells the Arduino that the total time for the move should be 450,000 microsecs, motorA should make a step every 9000 µsecs, motorB should not move and motorC should make a step every 18.000 µsecs. (Note that I have just invented the numbers for this reply)

The Ethernet receive() starts a while loop if there is a new message. It then collects each character of the message (1 char per loop). This is why I added the stepper.runSpeedToPosition() in this while loops so it would call as frequently as possible even when a message is being recorded.

I suspect your receive() function will work quickly (less than a millisec?) if the whole message has arrived. You could probably test for that with something like if (client.available() >= lengthOfMessage) and just return straight away if the data is not yet there.

This is an situation where I don’t need to use the style of Serial Input Basics because I know that the full message will be available or else there is a BIG PROBLEM.

…R

Thank you for the advice. I am still trying to figure out how to collect more than one char at a time.

Is there another function that I should be using instead of client.read() or can client.read() collect an array of characters if my char rc is an array?

mjnewsum: Thank you for the advice. I am still trying to figure out how to collect more than one char at a time.

Is there another function that I should be using instead of client.read() or can client.read() collect an array of characters if my char rc is an array?

I think you are confusing collecting and reading.

Collecting (using my own definition :) ) is the business of the data arriving in the buffer. Reading is the business of getting the data from the buffer. If the data is already in the buffer reading it one byte at a time will be very fast.

I have not used the Ethernet library but as far as I can see client.available() is the same as Serial.available() - it tells you how many bytes have arrived in response to the most recent GET request that you sent.

Using the concept from my own project it seems to me that you would issue the GET request as the last thing before you start the motors moving and, hopefully, by the time the movement is finished the data for the next move will have arrived and it will only take a few microseconds to read it.

The sequence in my program is

  • read the data from the buffer
  • request more data
  • get the motors moving using the data just received
  • repeat as necessary

Does that make any sense?

...R

Yes, that makes sense. I was confused about collecting and reading. I have another device that I use for TCP communication and it can read many characters in one call.

I think I see how I can restructure the code so it can collect data while telling the motor to run. My problem seemed to be that I was reading the message in a while loop, so my motor was not being told to run.

I'll see if I can get this running smoother. Also, I see that I need to slow down my sender quite a bit. My other device can collect and read data much much faster, so I need to avoid over sending to the arduino.

Thanks for the help! Kind regards, Jake

#include <AccelStepper.h>
const int stepPin = 9;
const int dirPin  = 8;
const int ledPin  = 13;
const int maxSpeed= 1000;
const int maxAccel= 100;
int stepVel = 500;
int stepPos = 0;
bool blocking = false;

AccelStepper stepper(1, stepPin, dirPin);

//Timing Settings
float timeStart       = 0;     //Time at beginning of interval
const int timeLimit   = 10;    //Interval Limit

//Ethernet Settings
#include <Ethernet.h>
byte mac[]      = {0x54, 0x52, 0x49, 0x41, 0x44, 0x00};
byte ip[]       = {192, 168, 2, 99};
byte gateway[]  = {192, 168, 0, 1};
byte subnet[]   = {255, 255, 0, 0};
EthernetServer server(4444);
EthernetClient client;

//Message Settings
const int lenMessage = 32;
const byte numChars = lenMessage;
boolean newData     = false;
boolean dataReady   = false;
char receivedChars[numChars];
boolean alreadyConnected = false;
boolean received = false;

char startChar = '<';
char endChar   = '>';
char rc;
boolean receiving = false;
byte ndx = 0;

//Communication Switch
boolean serialOn    = true;

void setup() {
  timeStart = millis();

  if (serialOn)   configSerial();
  configEthernet();
  configStepper();
}

void configSerial()
{
    Serial.begin(9600);
    Serial.println("Ready");
    while(!Serial)
      ;
}

void configEthernet()
{
    Ethernet.begin(mac, ip, gateway, subnet);
    server.begin();
    if (serialOn) Serial.println("Server Started");
}

void configStepper()
{
    stepper.setAcceleration(maxAccel);
    stepper.setMaxSpeed(maxSpeed);
}


void loop() 
{
    
    receive();
    if (received)
        stepper.runSpeedToPosition();
        received = false;
    
    if (dataReady)
    {
        parseData();
        stepperPosition(stepPos, stepVel);
        if (serialOn) showParsedData();
        dataReady = false;
        received = true;
    }

    if (receiving)
      client.write("0");
    else
      client.write("1");
}

void receive()
{
    client = server.available();

    if (client)
    {
      if (!alreadyConnected)
      {
        client.flush();
        Serial.println("New Client");
        client.println("Hello, Client!");
        alreadyConnected = true;
      }
      if (client.available()>0 && dataReady == false && ndx < numChars)
      {
          rc = client.read();
          if (receiving)
          {
              if (rc != endChar)
              {
                  receivedChars[ndx] = rc;
                  ndx++;
              }
              else
              {
                  receivedChars[ndx] = '\0';
                  receiving = false;
                  ndx = 0;
                  dataReady = true;
              }
          }
          else if (rc == startChar) 
              receiving = true;
      }
      else
        ndx = 0;
    }
}

void parseData()
{
    char * strtokIndx;
    
    strtokIndx = strtok(receivedChars, ",");
    stepPos = atoi(strtokIndx);
    strtokIndx = strtok(NULL, ",");
    stepVel = atoi(strtokIndx);
    strtokIndx = strtok(NULL, ",");
    blocking = atoi(strtokIndx);
}

void showParsedData() 
{
     Serial.print("stepPos = ");
     Serial.println(stepPos);
     Serial.print("stepVel = ");
     Serial.println(stepVel);
     Serial.print("blocking = ");
     Serial.println(blocking);
     Serial.println("");
}

void stepperPosition(int pos, int vel)
{
    int currentPos = stepper.currentPosition();
    if (vel > maxSpeed)
      vel = maxSpeed -1;
    if (pos < currentPos)
      vel = vel*-1;
      
    stepper.moveTo(pos);
    stepper.setSpeed(vel);
}

I modified the code so now I am telling the stepper to runToPosition() as often as possible (after I get the first position). I am also collecting data without the while loop and it seems to be working well.

This version does not have the blocking option. I’m still thinking about how I want to handle that so I don’t get stuck in a similar situation where I can’t read from the client while moving.

Thanks for all the help!
Jake

Try moving the code in line 79

stepper.runSpeedToPosition();

to a new line immediately after line 94 and change it to

stepper.runSpeed();

I can't figure from your program how the client requests more data - which lines of code do that?

...R