Search This Blog

Saturday, February 26, 2011

Position sensor for Arduino XYZ CNC Carving Machine

This is a sub post of the larger XYZ machine post project.  This piece is about an Arduino based module that reads rotary encoders and displays on an LCD the current machine position;

A couple photos of the breadboard version.  Note the three encoders are under the display, this prototype wasn't really meant to be used for anything.  Of course the permanent version will have the encoders attached to the XYZ machine and not on a breadboard.

This picture (pardon the rotation) shows the display, you can just see one of the encoders on the breadboard below the display.  This picture was taken before I added the math to print out converted inches, it just shows the raw encoder count.

I might also later have at least one button to set the home position, or I may just rely on the Arduino reset button to set 0,0,0.  Some sort of home/limit switch setup might be nice too.

The encoders are attached to the ends of the drive screws for the platform.  I used a short piece of flexible plastic tubing from the hardware store, because I couldn't find set screw shaft couplers the right size for any reasonable price.  The hose gives me a little flex, so it should be OK.  It stretches over the encoder shaft and the drive shaft.

Using rotary encoders from sparkfun. They are inexpensive, I hope they hold up to the job over time.  They are really for human  input knobs.  
Later I swapped with some slightly different encoders from Digikey
I changed because these didn't have detents, which tended to snap the encoders to certain values.  Also these had threaded shafts and were easier to mount.  For $1.18 why not get the best?

Some example rotary encoder code that I started with is here:

The example code worked out of the box, I modified it to read three encoders at the same time. 
It makes use of port manipulation to read all the input simultaneously.  This page helped
However, this guy must have been a programmer for a living, the program was basically 2 lines and very hard to understand.   I wrote my version with more comments and a little less pizazz.  Hopefully more understandable.

Got the three encoder code working, included it below, it was a bit tricky.   

I added the LCD readout, attempting to keep the code delays as small as possible so we don't skip and encoder positions.  The LCD is hooked up pretty much like all the Arduino examples, using 4 bit wide parallel mode.  I like to use pins 7 6 5 4 3 2 because it keeps all the LCD pins on the same Arduino connector.   You might note in the photo there is a adafruit arduino prototype board and some headers that I use to stack the LCD on top of the Arduino and map the pins.   I'm not going to repeat the LCD interfacing stuff in this post.   The Optrex display i used is cost effective but a bit quirky, it has the tendency to print garbage if you give it commands too fast.

The code is after the break:

/* Rotary encoder readout for XYZ CNC machine */

// Encoders connected as follows
// X encoder PIN A - Analog 0
// X encoder PIN B - Analog 1
// Y encoder PIN A - Analog 2
// Y encoder PIN B - Analog 3
// Z encoder PIN A - Analog 4
// Z encoder PIN B - Analog 5
// Pin C of all encoders is grounded, center pin of
// thus variable PINC will read this entire register simultaneously
// this avoids missing states of the encoder

//Variables to set the counts/inch
#define TH_PER_IN  20 //lead screw threads per inch
#define CT_PER_REV  48 //encoder counts per revolution
// Thus the accuracy of the measurement 1 inch /(TH_PER_IN*CT_PER_REV)

//Variable to reverse the sign if the machine and encoder don't agree with your sense of things
#define REFLECTX -1
#define REFLECTY -1
#define REFLECTZ 1

//Based on
//Expanded to 3 encoders and written to be more clear and less over elegant
#include <LiquidCrystal.h>
LiquidCrystal lcd(7, 6, 5, 4, 3, 2);

unsigned int olddata;
unsigned int newdata;
char newX;
char newY;
char newZ;
char oldX;
char oldY;
char oldZ;
long int counterX = 0;
long int counterY = 0;
long int counterZ = 0;
// table of meaning of states, input is two bits old value, two bits new value
// the encoder has either moved +1, -1 or stayed in the same place
int8_t enc_states[] = {0,-1,1,0,1,0,0,-1,-1,0,0,1,0,1,-1,0};

void setup()
  /* Setup encoder pins as inputs */
  DDRC = B00000000;   //Sets input direction all to analog inputs
  PORTC = B11111111;   //Sets pull ups on the analog pins to 1
  Serial.begin (115200);
  counterX = 0;
  counterY = 0;
  counterZ = 0;

  pinMode(13 ,OUTPUT);  //turn on backlight
  digitalWrite(13, HIGH); //turn on backlight
  delay(500); //Optrex display needs to rest after a begin
  delay(100); //Optrex display needs to rest after setting cursor
  lcd.print("XYZ CNC Position");
  lcd.print("X: ");
  lcd.print("Y: ");
  lcd.print("Z: ");

void loop()
  //This loop needs to run fast to avoid missing encoder states
  newdata = PINC;

  if( newdata != olddata ) {
    newX = newdata & 0x03;  //mask out just the X bits
    oldX = olddata & 0x03;  //mask out just the X bits
    oldX = oldX << 2;  //move over to form address to table
    counterX += enc_states[oldX + newX] ;  //increment or decrement counter from table
    Serial.print(" X:");
    Serial.print(counterX, DEC);
    lcd.print(1000*counterX/(TH_PER_IN*CT_PER_REV*REFLECTX), DEC);
    lcd.print(" mils    ");

    newY = newdata & 0x0C;   //mask out just the Y bits
    newY = newY >> 2;  //shift to LSBs
    oldY = olddata & 0x0C;   //mask out just the Y bits
    counterY += enc_states[oldY + newY] ;
    Serial.print(" Y:");  
    Serial.print(counterY, DEC);
    lcd.print(1000*counterY/(TH_PER_IN*CT_PER_REV*REFLECTY), DEC);
    lcd.print(" mils    ");
    newZ = newdata & 0x30;   //mask out just the Z bits
    newZ = newZ >> 4;  //shift to LSBs
    oldZ = olddata & 0x30;   //mask out just the Z bits
    oldZ = oldZ >> 2;  //shift to form address to table
    counterZ += enc_states[oldZ + newZ] ;
    Serial.print(" Z:");
    Serial.println(counterZ, DEC);
    lcd.print(1000*counterZ/(TH_PER_IN*CT_PER_REV*REFLECTZ), DEC);
    lcd.print(" mils    ");
  olddata = newdata;


  1. Hello,

    I need some help measuring the distance one axis travels on my Lathe but I have no idea how to do it, the screw has some slop so the encoder on the screw won't work.
    I've looked at hall effect sensors and I'm really lost on how I achieve accurate measurement mechanically, I have a bar magnet from HomeDepot and the hall effect sensors, how do I read one MM, do i cut grooves in the bar magnet?
    I've looked at and tried Ultrasonic but it's inaccurate and it lags a bit too, any suggestions would be greatly appreciated.
    Also no LCD needed, the Arduino Serial Monitor is good enough for me, and again it's only one axis I need read back from.
    please email me if you like, crob.09 at live dot ca.
    Thanks in advance for your help and time.

  2. Multiply and divide are among the slowest things a computer can do and you have 3 multiplys and one divide in your print statement. On top of that all terms are constants. I would suggest the whole thing be replaced with one divide. In your setup, multiply the three terms then divide that by 1000. Then in the print statement just divide the new counter value by that new constant from setup.