Search This Blog

Showing posts with label pwm. Show all posts
Showing posts with label pwm. Show all posts

Monday, 12 May 2014

Using the Arduino PID Library for position control of X and Y axis on RepScrap printer

I've updated the test code I'm using to manage my X and Y axis DC motor / linear encoder closed loop controller. 



I am currently using the Arduino PID Library by Brett Beauregard  for this, and having great success.  Videos to come tomorrow. 

I am *NOT* going to explain what PID is, or how PID works.... I couldn't possibly do it justice.  I'll simply point you to Brett's wonderful explanation:



In the following example, I set up two axis, X and Y, each using a DC motor run from the Adafruit Motor Shield V2.  This shield provides PWM control for up to four separate DC motors via I2C communications.

I then set up two Quadrature encoders, one for each axis, using the Hardware Interrupts 0 and 1 (Arduino digital pins 2 and 3) and high speed digital port reads for one half of each encoder, and then validate the state of the other phase pin of the encoder during the interrupt routine: 
Graciously borrowed from http://forum.arduino.cc/index.php?topic=41615.20;wap2

The ZERO endstop for each axis is set up using the Arduino PinChangeInterrupt library watching a pin attached to a photo-interrupter.


I would certainly accept any advice on a proper sequence to initialize each axis to the ZERO endstop.

Right now, I arbitrarily send the carriage forward for 100ms assuming this is enough time to get on the positive side of the endstop, if we were beyond it.  Then I set my current position to the maximum possible location, and start travelling back to the endstop, knowing that once I actually reach it, the interrupt routine will Zero out my position, and initialize my PID setpoint to zero as well, thus stopping travel at ZERO. 
Is there a more efficient way of doing this? 


Inside the loop portion of my code, I run the PID controls as per the library, providing motor speed control via the Adafruit motor class, and periodically check to see if both X and Y axis have reached their goal.  At which time, I randomly select a new target for each.  When I get to the real application of this, the random selection of X and Y axis targets will be replaced by GRBL coordinates. 


And without further ado, here is my working code for precise position control in X and Y axis using the Arduino PID library:

/***************************************************************************************
*  Lin_Enc_02.ino   05-12-2014   unix_guru at hotmail.com   @unix_guru on twitter
*  http://arduino-pi.blogspot.com
*
*  This sketch allows you to run two salvaged printer carriages for X/Y axis using their 
*  linear encoder strips for tracking. 
*  This example uses the Arduino PID Library found at:
*  https://github.com/br3ttb/Arduino-PID-Library/archive/master.zip
*
*  Hardware Interrupt 0 on Digital pin2 is used to determine X-Axis position
*  Hardware Interrupt 1 on Digital pin3 is used to determine Y-Axis position
*  PinchangeInterrupt is used to identify the Zero Endstop for X and Y axis

*****************************************************************************************/

#include <Wire.h>
#include <Adafruit_MotorShield.h>
#include "utility/Adafruit_PWMServoDriver.h"
#include <PID_v1.h> 
#include <PinChangeInt.h>


#define frontstop = 100                          // Right most encoder boundary
#define backstop = 3600                         // Left most encoder boundary


// Create the motor shield object with the default I2C address
Adafruit_MotorShield AFMS = Adafruit_MotorShield(); 

// Select which 'port' M1, M2, M3 or M4. In this case, M1
Adafruit_DCMotor *XaxisMotor = AFMS.getMotor(1);
Adafruit_DCMotor *YaxisMotor = AFMS.getMotor(2);


const int XaxisENCPinA = 2;                  // X-AXIS  encoder 1 on pins 2 and 4
const int XaxisENCPinB = 4;
const int XaxisENDSTOP = 10;               // Endstop photointerrupter for X-Axis
volatile double XaxisENCPos = 0;

const int YaxisENCPinA = 3;                  // Y-AXIS  encoder 2 on pins 3 and 5
const int YaxisENCPinB = 5;
const int YaxisENDSTOP = 11;               // Endstop photointerrupter for Y-Axis
volatile double YaxisENCPos = 0;


double XaxisSpd,  YaxisSpd;                  // Carriage speed from 0-255
double XaxisPos, YaxisPos;                   // Current Carriage position

/*working variables for PID routines*/
// Tuning parameters
float KpX=0,  KpY=0;                          //Initial Proportional Gain 
float KiX=10, KiY=10;                         //Initial Integral Gain 
float KdX=0,  KdY=0;                          //Initial Differential Gain 

double XaxisSetpoint, YaxisSetpoint;      // Taget position for carriage

// Instantiate X and Y axis PID controls
PID XaxisPID(&XaxisPos, &XaxisSpd, &XaxisSetpoint, KpX, KiX, KdX, DIRECT); 
PID YaxisPID(&YaxisPos, &YaxisSpd, &YaxisSetpoint, KpY, KiY, KdY, DIRECT); 
const int sampleRate = 1; 

long int reportTime;

void setup() {
  Serial.begin(115200);
  Serial.println("Linear Encoder Test  05-12-2014");

  AFMS.begin();  // Set up Motors
  
  XaxisMotor->run(BACKWARD);                  // Bring carriage to home position. 
  XaxisMotor->setSpeed(70); 
  delay(100);                                                // Get endstop limiter working here
  XaxisMotor->run(FORWARD);                    // Bring carriage to home position. 
  XaxisMotor->setSpeed(0); 
  

  YaxisMotor->run(BACKWARD);                  // Bring carriage to home position. 
  YaxisMotor->setSpeed(70); 
  delay(100);                                                // Get endstop limiter working here
  YaxisMotor->run(FORWARD);                    // Bring carriage to home position. 
  YaxisMotor->setSpeed(0); 
  
  attachInterrupt(0, doXaxisENC, CHANGE);     // encoder pin on interrupt 0 (pin 2)
  attachInterrupt(1, doYaxisENC, CHANGE);     // encoder pin on interrupt 1 (pin 3)

  PCintPort::attachInterrupt(XaxisENDSTOP,doXaxisEndstop,FALLING); //X-axis Endstop ISR
  PCintPort::attachInterrupt(YaxisENDSTOP,doYaxisEndstop,FALLING); //Y-axis Endstop ISR

  randomSeed(analogRead(0));                          // Used to select random setpoints for testing

  XaxisPID.SetMode(AUTOMATIC);                //Turn on the PID loop 
  XaxisPID.SetSampleTime(sampleRate);         //Sets the sample rate 

  YaxisPID.SetMode(AUTOMATIC);                //Turn on the PID loop 
  YaxisPID.SetSampleTime(sampleRate);         //Sets the sample rate 

  reportTime = millis()+2000;
}

void loop() {
uint8_t oldSREG = SREG;                           // Store interrupt status register

  cli();
  XaxisPos = XaxisENCPos;  
  YaxisPos = YaxisENCPos;
  SREG = oldSREG;                                    // Restore interrupt status register
  

  // Temporary to create random X and Y axis setpoints for testing
  if(millis() > reportTime) {                               // Only validate this every 2 seconds
    if(XaxisPos == XaxisSetpoint && YaxisPos == YaxisSetpoint) {   
      // If both X-axis and Y-axis have reached their target - get new targets
      XaxisSetpoint =  random(200,3500);             // Keep target within bounds of Endpoints
      YaxisSetpoint =  random(200,3500);             // Keep target within bounds of Endpoints
    }    
    reportTime = millis()+2000;
}
  
  
  // Manage X-axis positioning
  XaxisPID.Compute();                          //Run the PID loop 
  if(XaxisSetpoint < XaxisPos) XaxisMotor->run(BACKWARD);  // Determine direction of travel
  else  XaxisMotor->run(FORWARD);      
  XaxisMotor->setSpeed(XaxisSpd);              // Apply PID speed to motor


  // Manage Y-axis positioning
  YaxisPID.Compute();                          //Run the PID loop 
  if(YaxisSetpoint < YaxisPos) YaxisMotor->run(BACKWARD);  // Determine direction of travel
  else  YaxisMotor->run(FORWARD);      
  YaxisMotor->setSpeed(YaxisSpd);              // Apply PID speed to motor

}


/***************************************************************************************
The following code was taken from   http://forum.arduino.cc/index.php?topic=41615.20;wap2
to utilize the fast port based encoder logic.  Thank you Lefty!
please go there for a full explanation of how this works.  I have truncated the comments 
here for brevity.

***************************************************************************************/

void doXaxisENC() {                                  // ************** X- AXIS ****************
    if (PIND & 0x04) {                              // test for a low-to-high interrupt on channel A, 
        if ( !(PIND & 0x10)) {                      // check channel B for which way encoder turned, 
           XaxisENCPos = ++XaxisENCPos;               // CW rotation
          }
        else {
           XaxisENCPos = --XaxisENCPos;               // CCW rotation
          }
    }
    else {                                          // it was a high-to-low interrupt on channel A
        if (PIND & 0x10) {                          // check channel B for which way encoder turned, 
           XaxisENCPos = ++XaxisENCPos;               // CW rotation
           }
        else {
           XaxisENCPos = --XaxisENCPos;               // CCW rotation
        }
     }
}                                                   // End of interrupt code for encoder #1


                                                   
void doYaxisENC(){                                  // ************** X- AXIS ****************
  if (PIND & 0x08) {                                // test for a low-to-high interrupt on channel A, 
     if (!(PIND & 0x20)) {                          // check channel B for which way encoder turned, 
      YaxisENCPos = ++YaxisENCPos;                  // CW rotation
     }
     else {
      YaxisENCPos = --YaxisENCPos;                  // CCW rotation
     }
  }
  else {                                            // it was a high-to-low interrupt on channel A
     if (PIND & 0x20) {                             // check channel B for which way encoder turned, 
      YaxisENCPos = ++YaxisENCPos;                  // CW rotation
      }
     else {
      YaxisENCPos = --YaxisENCPos;                  // CCW rotation
     }
  }
}                                                   // End of interrupt code for encoder #2


void doXaxisEndstop() {
  XaxisENCPos=0;                                    // X-Axis Endstop indicates ZERO position 
}

void doYaxisEndstop() {
  YaxisENCPos=0;                                    // Y-Axis Endstop indicates ZERO position 
}

            https://github.com/michaeljball/RepScrap

Updated diagram for reference:



References:

DIYDrones: Tutorial series for new Arduino PID library
http://brettbeauregard.com/blog/2011/04/improving-the-beginners-pid-introduction/
http://robotics.stackexchange.com/questions/1232/how-can-i-use-the-arduino-pid-library-to-drive-a-robot-in-a-straight-line
Tim Wescott's PID without a PHD
http://abigmagnet.blogspot.ca/2008/10/dc-motor-control-part-one.html
http://www.pdx.edu/nanogroup/sites/www.pdx.edu.nanogroup/files/2013_Arduino%20PID%20Lab_0.pdf

https://www.youtube.com/watch?v=ZZYgZjMnGXU
https://www.youtube.com/watch?v=wbmEUi2p-nA
http://blog.solutions-cubed.com/pid-motor-control-with-an-arduino/
http://forum.arduino.cc/index.php/topic,45169.0.html

http://playground.arduino.cc/Code/PIDLibrary
http://playground.arduino.cc/Code/PIDAutotuneLibrary

Arduino Playground: PinChangeInterrupt Library
https://code.google.com/p/arduino-pinchangeint/downloads/list

LetsMakeRobots: PID Control by Big Face
LetsMakeRobots: PID Tutorials for Line Following by Enigmerald
LetsMakeRobots: PID without a PHD by BDK6
LetsMakeRobots: Using Motor Encoders to Control Speed by Oddbot








Wednesday, 23 April 2014

Followup on 3D printer from Scavenged DC Motor / Encoders Blog

One word...  Wow!

You guys are both supportive and educational at the same time.  

Yes... many of you either told me not to bother because I was simply reinventing the wheel (I actually appreciate that the most because many of you provided links to prior art!), or that I was just plain foolish to attempt this... (but if you are going to criticize, back it with facts/links)...

But MANY of you (over 100 as of this writing) have provided me with positive reinforcement, and sent me links and articles showing others who have succeeded on this journey in one way or another.

I just want to talk a minute about the differences between a "Stepper Motor"  a "DC Motor" and a "Servo Motor".  

I've linked each of the above to Wikipedia so that I don't have to go into great detail of how each actually works.  I'm more interested in the differences each presents in the context of fine grain positioning control.


A Stepper motor has multiple coils that when energised in a certain sequence, produces defined accurate "steps" of the shaft. The NEMA 17 Stepper Motor shown here, seems to be the DIY 3D printer industry's  favorite. It provides 1.8° per step accuracy. As long as your software can initialize the linear travel with endstop switches, mechanical or optical, then it will know at all times where along the axis it was left. There is little issue with "drift".  When you cut the power to a stepper motor, it stays exactly where you placed it. Yes, of course you can "push" the carriage without power but for the most part an energized step will place the shaft in a known spot.  Speed is managed by the frequency in which you cycle the steps.


A DC motor, on the other hand is pretty much "free running".  You apply a DC voltage to the winding, and the shaft will spin in one direction.  Reverse the polarity and the shaft will spin in the other direction.  Torque is based on electric current capabilities, and is a factor of the wire gauge and number of windings.  Speed is related to the voltage applied.  Most DC motors in a control context are driven via Pulse Width Modulation or PWM, but still have no feedback as to the position of the shaft, or in my case linear carriage.  When you remove power from a DC motor, it will coast until it stops.  To remedy this, you need to employ Dynamic Braking. Accuracy is non Existent.

 

A Servo Motor, starts with a DC motor, but provides a feedback mechanism for the angle of rotation.  The hobbyist style servos shown to the right here, rely on a potentiometer, attached to the motor shaft by gears.  The position of the shaft is directly related to the value of the potentiometer. 

Commercial Servo Motors will employ magnetic, electric, or optical Encoders to sense rotational (or linear) movement, and provide this feedback to a servo controller. 

 A Servo Motor is basically a closed loop system that provides direct feedback to the controller upon any movement of the shaft.  Accuracy is a product of the resolution of the encoder in use.






To that end, what I am proposing in my project, is to make a set of Linear Servo Motors.
I will drive them with Pulse Width Modulation as a DC motor.  I will provide feedback, monitoring the encoder position through Interrupts, and thus essentially create a linear servo motor.  In addition, I will apply the appropriate functions to slow the carriage in advance of the destination so as not to overrun the target. Photo interrupters will be used at each end of the carriage for periodic self calibration.


Here are a few of the links shared with me:

Here is an example of a Thing-o-matic makerbot 3d printer with DC motors and linear encoders:




Apparently the GeckoDrive is a popular solution in this space, it takes the outputs meant for a stepper, and manages a DC motor / encoder loop.




Then there's Rapy, a DC motor powered 3D printer made in Korea
It uses two DC Gear motors and one Rotary Optical Encoder per axis.




Somewhat unrelated, but a very interesting belt drive linear motion system using 80/20 extrusion as the linear rail.











More to read:

Converting an Ordinary DC Motor to a Servomotor
http://www.motioncontroltips.com/category/encoders/l-encoders/
https://www.youtube.com/watch?v=wBnbQrs6JsQ
http://blog.machinekit.io/p/machinekit_16.html?m=1
(http://www.geckodrive.com/geckodrive-brush-dc-drives/g320x.html
http://www.geckodrive.com/gecko/images/cms_files/images/G320XStepDirectionCircuit.jpg \
Makeatronics: Building a 3D printer
Makeatronics: 3D Printer Motor Control - Part 1
https://groups.google.com/forum/#!topic/makerbot/hgdUMBRkU38
http://martingautron.com/inputs/books/diy/3d-print.html
https://www.youtube.com/user/ENKTechnologies
GeckoDrive: Step Motor Basics Guide


Sunday, 10 November 2013

The case for lurking on LMR before starting on a large (ish?) robotics venture...

Ok... so let me start with...

 I've been humbled by the sheer simplicity with which some of these bots on LMR (Lets Make Robots) are made.  Functional and clean.



The one article that has made me rethink my strategy the most is  Oddbot's article Pushing the limits with encoders

In my Robot here, I use a RaspBerry Pi to talk to two Arduino's via Serial over USB.  The premise being that the USB was already there for programming... might as well use it for communications. Then one of the Arduino's is also an I2C master to various sensors, that it then translates into serial feeds for the RPI.... hmmmmm....  can anyone say "inefficient"? 

I *had* investigated the option of using the RPI as a I2C master, and the two Arduino's, along with all of the I2C sensors would become slaves... looking back, I passed up on this quickly because I would have had to build or buy a level converter.... really?

I've spent probably close to $500-$600 on this thing so far... and yet a few bucks... or more likely an hour at the soldering iron made my decision for me.


I've struggled for WEEKS on timing issues related to getting commands accurately into the Arduino running the motors (using the Motorshield V3) which uses two PWM drivers, as well as the two servos .... The UNO fully consumes Timer 0/1/2 to take care of these, and so all code has to be devoid of "delay()" or "millis()"....  Sending a Serial command formated with specific start character, as well as a command UID to be used to validate completion of the command back to the RPI.

So.....

I'm going to step back for a week... regroup... reassess what functions are best suited to which processor, and clean it up.

Raspberry PI being I2C Master.   Arduino UNO retaining Motor control as well as Wheel encoder and IR proximity, but as an I2C slave as opposed to "Serial - hope-it-gets-interpreted-properly".
As I was simply replaying the I2C sensors via serial for the RPI to interpret, store, react, anyway.... This will be a workload removed from the UNO.
I will still dedicate the Arduino FIO to managing the Sonar POD servos and MAXSonar EZ1s, for now... but will communicate with the RPI as an I2C slave.

I may ultimately be able to eliminate one of the Arduino's .... but we'll save that for the next robot.

Any suggestions, tips, words of kindness... laughter...  bring it on...


References:

http://blog.oscaraConnect Raspberry Pi and Arduino with Serial USB Cable
Raspberry Pi and Arduino Connected Using I2C
Raspberry Pi and Arduino Connected Over Serial GPIO
Step by Step Guide on Making an I2C Slave Device with an Arduino
Arduino.cc: Arduino as I2C Slave ?
Instructables: I2C between Arduino's
http://digitalcave.ca/resources/avr/arduino-i2c.jsp
Raspberry Pi master controls Arduino UNO slaves via I2C
I2C communication between a RPI and a Arduino