Close

After A Long Break

A project log for Motion Controller

An update to "not GRBL" but using an ISR.

agpcooperagp.cooper 07/14/2018 at 08:530 Comments

Getting Back Up To Speed

It appears as though the code is waiting to be testing. It has been a long time so I need to review what I have written.

Serial Initialisation

The initialisation of the serial interface is always tricky with the Arduino, here is my code:

  // Indicate ready
  Serial.begin(9600);
  while (!Serial);
  Serial.println("Motion Control V1.0");
  Serial.flush();

The first line is pretty standard and for this application I don't want to go any faster than 9600 baud. The motion controller is pretty processor intensive so not a great idea to sent data too quickly.

The second line is almost essential on Linux PCs (no so much on Windows PCs). Without this line the first serial print is often (i.e. always) garbled. Basically the code has to wait until the Serial system is ready. On Windows PCs a 200 ms wait seems to work better.

The function of flush() as changed over time but at the moment it just waits until the output has been sent. It has no function on the input. In the past I would calculate the required delay for the text length to ensure that the serial has been sent before continuing. Why wait? If you send too much data and overflow the buffer then data will be lost.

For "gCodeSender" the ready message needs to be modifed to something like:

Serial.println("Grbl v0.1a");

Flow Control

In "Not GRBL" I used software handshakes (i.e. XON/XOFF) as I could only process one command at a time. With a ring buffer I should be able to use the "ok" protocol.

The variable "okay" is incremented upon completion of the motion (in the motion control ISR):

        ...
        steps--;
        if (steps==0) okay++;
        ...

and decremented each time it is printed to the serial port (in the main loop):

void loop()
{
  // Flow control
  if (okay>0) {
    Serial.println("ok");
    cli();okay--;sei();
  }
}

Serial Event

I am using serialEvent() to decode the gCode and save the "primative" comands to the ring buffer. It is fairly complicated:

void serialEvent()
{
  // Rember between calls
  static long xNext=0;
  static long yNext=0;
  static long zNext=0;
  static long fNext=Feed;
  // Reset upon each call
  long mNext=-1;
  bool set=false;
  long fQuery;
  long xQuery;
  long yQuery;
  long zQuery;
  long nQuery;
  char inData[63];
  int bRead;

  // Get command string
  bRead=Serial.readBytesUntil('\n',inData,63);
  if (bRead>1) {
    bRead--;
    inData[bRead]='\0'; // End of string
    
    // Clean up command string
    for (int i=0;i<bRead;i++) {
      // To upper case
      if ((inData[i]>='a')&&(inData[i]<='z')) inData[i]-=32;
      if ((inData[i]>='0')&&(inData[i]<='9')) {
        // Integer numbers
      } else if (inData[i]=='-') {
        // Sign
      } else if (inData[i]=='.') {
        // Decimal
      } else if (inData[i]=='?') {
        // Ready
      } else if (inData[i]=='X') {
        // X axis movement
      } else if (inData[i]=='Y') {
        // Y axis movement
      } else if (inData[i]=='Z') {
        // Z axis movement
      } else if (inData[i]=='F') {
        // Feed rate
      } else if (inData[i]=='M') {
        // M Code
      } else if (inData[i]=='$') {
        // Status
      } else if (inData[i]=='!') {
        // Pause
      } else if (inData[i]=='~') {
        // Resume
      } else if (inData[i]=='@') {
        // Set current position as origin
      } else {
        // Clear character
        inData[i]=' ';
      }
    }
    
    // Decode motion commands
    for (int i=0;i<bRead;i++) {
      if (inData[i]=='X') {
        xNext=(long)(atof(inData+i+1)*xStepsPerMM);
        set=true;
      } else if (inData[i]=='Y') {
        yNext=(long)(atof(inData+i+1)*yStepsPerMM);
        set=true;
      } else if (inData[i]=='Z') {
        zNext=(long)(atof(inData+i+1)*zStepsPerMM);
        set=true;
      } else if (inData[i]=='F') {
        fNext=atol(inData+i+1);
        if (fNext<1) fNext=1;
        if (fNext>4095) fNext=4095;
        set=true;
      } else if (inData[i]=='M') {
        mNext=atol(inData+i+1);
        set=true;      
      }
    }
    // Push motion commands onto stack (no check if full)
    if (set) {
      // Push must not be interrupted
      cli();
      // Psuedo M-Codes
      if ((mNext==2)||(mNext==30)) {
        // Go Home (predefined)
        if (Spindle<8) PORTD&=~(1<<Spindle); else if (Spindle<14) PORTB&=~(1<<Spindle-8);
        xNew[head]=xCurrent;
        yNew[head]=yCurrent;
        zNew[head]=zHome*zStepsPerMM;
        mCode[head]=mNext;
        feed[head]=Feed;
        if (++head>=stackSize) head=head-stackSize;           
        xNext=xHome*xStepsPerMM;
        yNext=yHome*yStepsPerMM;
        zNext=zCurrent;
        mNext=mNext;
        fNext=Feed;
      }
      xNew[head]=xNext;
      yNew[head]=yNext;
      zNew[head]=zNext;
      feed[head]=fNext;
      mCode[head]=mNext;
      if (++head>=stackSize) head=head-stackSize;
      sei();
    }
    
    // Decode immediate commands
    for (int i=0;i<bRead;i++) {
      if (inData[i]=='?') {
        // Return queue length (used for flow control)
        Serial.flush();
        queue=head-tail;
        if (queue<0) queue=queue+stackSize;
        Serial.println(queue);
      } else if (inData[i]=='$') {
        // Return motion control status
        if ((steps==0)||(paused)) {
          cli();
          nQuery=steps;
          xQuery=xCurrent;
          yQuery=yCurrent;
          zQuery=zCurrent;
          fQuery=fCurrent;
          sei();
          Serial.flush();
          if (paused) {
            Serial.println("$Paused");
          } else {
            if (steps>0) {
              Serial.println("$Running");
            } else {
              Serial.println("$Stopped");            
            }
          }
          Serial.print("$Queue ");Serial.println(queue);
          Serial.print("$Steps ");Serial.println(nQuery);
          Serial.print("$Feed ");Serial.println(fQuery);
          Serial.flush();
          Serial.print("$X ");Serial.println(xQuery);
          Serial.print("$Y ");Serial.println(yQuery);
          Serial.print("$Z ");Serial.println(zQuery);
        }            
      } else if (inData[i]=='!') {
        // Pause motion
        paused=true;
      } else if (inData[i]=='~') {
        // Resume motion
        paused=false;
      } else if (inData[i]=='@') {
        // Reset as axes origin
        if ((steps==0)||(paused)) {
          cli();
          xCurrent=0;
          yCurrent=0;
          zCurrent=0;
          head=0;
          tail=0;
          xNew[0]=0;
          yNew[0]=0;
          zNew[0]=0;
          feed[0]=Feed;
          mCode[0]=-1;
          steps=0;
          sei();
        }
      }
    }
  }
}

Okay I worked It Out

I worked out where I was up to. I have not worked out how to "stack" the commands. I left the code with a simple one instruction at a time, issue and "ok" when done. So I need to add "stack" control code but otherwise it is ready for testing.

So it looks like a review of the serialEvent() subroutine, testing and add stack control code.

AlanX

Discussions