Sunday, 9 August 2015

Asynchronous Enhancements

I've been making several enhancements to Armdroid Library to support Asynchronous operation.  This means its now possible to drive all motors, and still do other processing at the same time - for example listening to the serial port for stop requests etc.

You may recall in my last post, when driving motors for very long durations, its simply not possible to interrupt things until all motors reached their target positions.  This has been a limitation for sometime - and with hindsight, I really wish I had implemented the library slightly differently in the first place.

Advanced users will welcome these changes, although you can still use the existing drive methods which have been retained for compatibility with existing sketches/projects.

GitHub: https://github.com/Armdroid/Armdroid-Arduino-Library

The new drive method is:
 void driveMotorsAsynchronous()

and complimented by the following control methods:
ArmAsyncState getAsyncState()
bool isRunning()
bool Start(MTR_CHANNELS target)
bool Pause()
bool Resume()
bool Stop()

To asynchronously drive motors, you must call driveMotorsAscynchronous() in your sketch loop() function.  This handles timings and pulses stepper motors when ArmAsyncState = ASYNC_DRIVE_RUNNING.  To enter this state, you call Start() with arguments to specify the target position for each motor channel.
Multiple calls to Start() are possible, but the current implementation does not wait for the existing target positions to be reached before running to new targets.  This behavior was intentional as you can always implement a data structure to cache requests as necessary.

To cancel running motors at any time, call the Stop() method which cancels the asynchronous run request, and stops all motors dead.

Likewise, Pause()/Resume() allows you to pause and resume movements, but does not cancel the asynchronous run request.

The asynchronous extensions implements a simple state machine, so the ArmAsyncState enumeration has been provided to allow programmers to evaluate the state at any time.

To demonstrate the concept - the following example shows how to send commands over serial and allows users to cancel movements, at any time, no matter how lengthy the motor operations are.

You can send bytes to the Arduino from any software that can access the computer serial port.

Code

 /*  
  * Asynchronous Drive Demonstration  
  * Copyright Richard Morris 2015. All Rights Reserved  
  * http://armdroid1.blogspot.co.uk  
  *  
  */  
 #include "Armdroid.h"  
 // initialize Armdroid library:  
 Armdroid myArm( 2, 3, 4, 5, 6, 7, 8, 9 );  
 // variables for receiving and decoding commands:  
 unsigned int rxCmdPos;  
 int rxCmdVal;  
 int rxCmdArg[5];  
 // variable to store previous state:  
 ArmAsyncState previous;  
 void setup()  
 {  
  Serial.begin(9600);  
  myArm.setSpeed(120);  
  myArm.torqueMotors(true);  
 }  
 void loop()  
 {  
  if (!Serial) {  
   // while the serial stream is not open, do nothing:  
   while(!Serial);  
   Serial.println(F("Welcome, Armdroid!"));  
   // reset command variables  
   rxCmdPos = rxCmdVal = 0;  
   memset(rxCmdArg, 0, sizeof(rxCmdArg));  
  }  
  if (Serial.available()) {  
   const char ch = Serial.read();  
   if (isDigit(ch))  
    rxCmdVal = (rxCmdVal * 10) + (ch - '0'); // accumulate value  
   else if (ch == '-')  
    rxCmdVal = rxCmdVal * -1;  
   else if (ch == ',') {  
    rxCmdArg[rxCmdPos++] = rxCmdVal;      // shift received value into  
                          // arguments array  
    if (rxCmdPos == 6)  
     rxCmdPos = 0;              // wrap argument index  
    rxCmdVal = 0;               // reset accumulator  
   }  
   else if (ch == 'D') {  
    Serial.println( F("drive motors") );  
    MTR_CHANNELS channels = { rxCmdArg[0], rxCmdArg[1], rxCmdArg[2], rxCmdArg[3], rxCmdArg[4], rxCmdVal };  
    myArm.Start(channels);  
    // reset accumulator  
    rxCmdPos = rxCmdVal = 0;  
   }  
   else if (ch == 'P') {  
    Serial.println( F("pause running motors") );  
    myArm.Pause();  
   }  
   else if (ch == 'C') {  
    Serial.println( F("continue driving motors") );  
    myArm.Resume();  
   }  
   else if (ch == 'S') {  
    Serial.println( F("stop dead all motors!") );  
    myArm.Stop();  
   }  
  }  
  // method called every loop iteration:  
  myArm.driveMotorsAsynchronous();  
  // report state changes:  
  ArmAsyncState current = myArm.getAsyncState();  
  if (current != previous) {  
       Serial.print( F("DRIVE STATUS = ") );  
       switch (current)  
       {  
            case ASYNC_DRIVE_STOPPED:  
                 Serial.println( F("STOPPED") );  
                 break;  
            case ASYNC_DRIVE_RUNNING:  
                 Serial.println( F("RUNNING") );  
                 break;  
            case ASYNC_DRIVE_PAUSED:  
                 Serial.println( F("PAUSED") );  
                 break;  
       }  
       previous = current;  
  }  
 }  

for example - drive base motor 2,500 steps counter-clockwise
issue:   0,0,0,0,0,2500D
at any time, send (P)ause, (C)ontinue, or (S)top

I'm currently working on a revised specification for the Armdroid Serial Protocol to include these changes and streaming modes.   In the meantime, I've included this demonstration (AsyncDemo) in the examples directory.