[posted] Teensy LC based lathe DRO

A few years ago, I had a small machining project to do on my old manual lathe, and it seemed to me that it would be somewhat faster to do the work accurately if I added DROs (Digital Read Outs) to the lathe carriage and cross slide axes.

iGaging sells a line of DRO kits that while light on features, are reasonably accurate and very affordable. I bought a set and installed them on my lathe. They worked well for my project and I was satisfied with them.


Since then however, I’ve begun really feeling the limitations of these budget instruments.

First, they run from coin cell batteries, and a couple of times now I’ve gone out to use the lathe to discover the batteries were dead and needed to be replaced.

Second, they’re a bit laggy. There is about eight tenths of a second between a movement on the scales and the display showing that new position. It’s a little maddening for precision work since to hit a particular number, you have to really slowly sneak up to the number.

Third, the scales can only be set to read linear position. That makes a lot of sense for general purposes, but when mounted on the cross slide of a lathe that means it’s displaying radius. Again, that makes perfect sense, but in practice I’m often trying to hit a specific diameter, and I end up having to take measurements and divide by two. Many higher end lathe DROs let you display the cross slide as radius or diameter. The whole point of DROs is to let the machinist work more quickly and more accurately, making fewer mistakes because the computer is doing the math for you.

Fourth, the preset function (where you set the position counter to a specific value) is very slow and cumbersome since there is no keypad. It reminds me of sending a text message on an old flip phone. In practice it was so awkward and slow that I never used it.

Poking around on Youtube, I found a video where someone had gotten an Arduino to read the output of a digital caliper, and I wondered if anyone had tried that with an iGaging DRO scale. A little searching found the really nice “Touch DRO” project, where Yuiri B. has documented how to talk to a few different types of iGaging scales.

My first thought was I have to try this myself. I got out a breadboard and a Teensy LC and after a little experimentation was indeed able to read the scales.



Digging through my electronics parts box, I soldered up a board with two 7 segment 8 digit led displays, a pair of 4 character 14 segment LED displays for input feedback, a 4x6 button keypad, a pezio buzzer for audio feedback, and a shift register with 8 LEDs hooked up to it.

A little bit of software later, and I have a custom DIY DRO for my lathe.

close view.jpg

The keypad lets me toggle settings, like switching the display between imperial and metric, switching between radius and diameter display, absolute or incremental position display, and to allow easy numeric entry for presets.

There is still some lag. It takes 93 milliseconds to read both scales, but I suspect there is some internal smoothing going on inside the scale sensor and the best I can do is about a third second of lag. That’s two and a half times better than what I had before, so I’m happy about that.

This ended up being two weekends of work, but I had a lot of fun doing it and now I’ve got a much more capable and responsive DRO.

John Knoll
hi John,

nicely done.
any change sketch/diagram ia available to the general public?

Regards, Otto.
If you'd like to reproduce this yourself, here's how to read the iGaging Absolute DRO Plus scales.

These scales use the Mitutoyo 52 bit data protocol transmitted over a microUSB cable. The scale DOES NOT use USB protocol, they're just using the cables and connectors because they're cheap and readily available.

This is how the microUSB connector is wired:
VCC -> 3.3v
D- -> Clock
D+ -> Data

This code demonstrates reading from two of these scales simultaneously. It should be pretty obvious how to extend this to additional axes.

//  Name    : iGaging_Scale_reader                                
//  Author  : John Knoll 
//  Date    : 28 Dec 2020    
//  Modified: 10 Jan 2021                                  
//  Notes   : Code to read 52bit iGaging Absolute DRO sales

iGaging 52 bit scales use a micro USB cable to connect the read head to the display.
The microUSB connector DOES NOT use USB protocol. It's using the connector since they're cheap and convenient

The way this is wired:
VCC -> 3.3v
D-  -> Clock
D+  -> Data
ID  -> REQ


//pin definitions
int const xclockPin = 2; // X Clock
int const xDataPin = 3; // X Data
int const xREQPin = 4; // X REQ - Pull this down to request data from the scale

int const yclockPin = 5; // Y Clock
int const yDataPin = 6; // Y Data
int const yREQPin = 7; // Y REQ - Pull this down to request data from the scale

//variables that will store the readout 
float gX_Position,gY_Position;
unsigned long lastupdate,nextaverage;
long readcount,accumulation;
float averageReadDuration;

volatile int xBitCounter,yBitCounter;
volatile char xBit[52],yBit[52];


void setup() 
  gX_Position = gY_Position = gZ_Position = 0;
  readcount = 0;
  averageReadDuration = 0;
  accumulation = 0;

  pinMode(xREQPin, OUTPUT);
  pinMode(xclockPin, INPUT_PULLUP);
  pinMode(xDataPin, INPUT_PULLUP);

  pinMode(yREQPin, OUTPUT);
  pinMode(yclockPin, INPUT_PULLUP);
  pinMode(yDataPin, INPUT_PULLUP);
  digitalWriteFast(xREQPin, HIGH); // Set to high - Pull this low to request data
  digitalWriteFast(yREQPin, HIGH); 

  attachInterrupt(digitalPinToInterrupt(xclockPin), ReadXbit, FALLING);
  attachInterrupt(digitalPinToInterrupt(yclockPin), ReadYbit, FALLING);
  xBitCounter = yBitCounter = 0;

  lastupdate = nextaverage = millis();
  nextaverage += 1000;


void loop() 
  unsigned long elapsed,currenttime;

  if (!ReadScales()) Serial.println("Communication timeout");
  currenttime = millis();
  elapsed = currenttime - lastupdate;
  lastupdate = currenttime;
  accumulation += elapsed;
  if (currenttime > nextaverage)
    averageReadDuration = ((float)accumulation) / ((float)readcount);
    Serial.print("Average duration of read: ");
    accumulation = readcount = 0;
    nextaverage = currenttime + 1000;
    Serial.print("X Position: ");
    Serial.print (gX_Position);
    Serial.print("  Y Position: ");
    Serial.println (gY_Position);

// Scale reading routine.
// The 52 bit communication protocol used by the iGaging displays uses a pretty clow clock.
// To read all 52 bits, the scale requires 93 milliseconds.  That's about 1/500th second per bit.
// on a 48Mhz Teensy LC, 1/500 second is an eternity.  While I can't speed up the clock of the scale,
// I can speed up reading all scales simulatneously
// The 52 bit protocol requires 3 data lines: 1 output and 2 inputs
// REQ pin - output.  Pull this low to start the process.  Pulling it down asks the scale to start sending data.
// Clock pin - input.  Wait for this to toggle.  It starts low, goes high for a moment, and when it goes low again, you can read the data pin.
// Data pin - input.  When the clock goes low, read the incoling bit on this pin.
// Data format is 13 4 bit "characters".  Each 4 bit character is an integer or a flag.
// character 1 - 3 ignore.  These are always 1s
// character 4 - Sign.  If this value is 8, what follows is a negative number.  Otherwise it's positive.
// character 5 - Highest place.  On these scales this is the thousands place.
// character 6 - hundreds place
// character 7 - tens place
// character 8 - ones place
// character 9 - tenths place
// character 10 - hundredths place
// character 11 - the decimal point location.  For my purposes, this can be ignored because the scales I'm talking to always return '2'
// character 12 - inch/metric.  I'm ignoring this since the scales I'm talking to are always metric
// For these scales, this number is in millimeters.
// There are two interrupt service routines, ReadXbit() and ReadYbit() that are set to be called on the falling edge of the clock lines.
// The routines read the data and stash it away in xBit[] and yBit[]
// This routine starts by pulling the REQ lines low.  That tells the scales we want to recieve data.
// We then wait while the ISRs read the data.  Reads are done when the bit counters indicate that we've read all 52 bits.
// If the scales don't send the data within 1 second, we time out and return false.
// Set the REQ lines high to tell the scales we're done.
// Assemble the characters into a floating point number.

boolean ReadScales(void)
  unsigned long Timeout;
  int xdigit,xbit,whichbit,Xnum,Ynum;
  boolean isXneg,isYneg;
  boolean donereading;
  xBitCounter = yBitCounter = 0;
  Xnum = Ynum = 0;
  donereading = isXneg = isYneg = false;
  Timeout = millis() + 1000;
  // Request data from scales
  // loop until the reads are done
  while (!donereading)
    if ((xBitCounter == 52) && (yBitCounter == 52)) donereading = true;  // Check to see if both reads are done
    if (millis() > Timeout) // we've waited a second with no reply.  Time out.
      gX_Position = 0; // set the output variables to zero.
      gY_Position = 0;
      digitalWriteFast(xREQPin, HIGH); // take the REQ pins down
      digitalWriteFast(yREQPin, HIGH);
      return false; // signal failure of read
  // Tell the scales we're done
  digitalWriteFast(xREQPin, HIGH);
  digitalWriteFast(yREQPin, HIGH);
  // assemble the digits
  gX_Position = 0;
  gY_Position = 0;
  for (whichbit = 0; whichbit < 52; whichbit++)
    xdigit = whichbit>>2; // what digit are we building?
    xbit = whichbit % 4; // which bit are we on?
    if (xBit[whichbit]) bitWrite(Xnum, xbit, 1);
    if (yBit[whichbit]) bitWrite(Ynum, xbit, 1);
    if (xbit == 3) // We've finished with this digit. Process it.
      switch (xdigit)
        case 4: // this is the sign.  If either number is negative, remember that.  Negate the result after we've finished building it.
          if (Xnum == 8) isXneg = true;
          if (Ynum == 8) isYneg = true;
        case 5:
          gX_Position += Xnum * 1000.0;
          gY_Position += Ynum * 1000.0;
        case 6:
          gX_Position += Xnum * 100.0;
          gY_Position += Ynum * 100.0;
        case 7:
          gX_Position += Xnum * 10.0;
          gY_Position += Ynum * 10.0;
        case 8:
          gX_Position += Xnum;
          gY_Position += Ynum;
        case 9:
          gX_Position += Xnum / 10.0;
          gY_Position += Ynum / 10.0;
        case 10:
          gX_Position += Xnum / 100.0;
          gY_Position += Ynum / 100.0;
      Xnum = Ynum = 0; // zero out the accumulators
  if (isXneg) gX_Position = -gX_Position;
  if (isYneg) gY_Position = -gY_Position;
  return true;


void ReadXbit() 
    if (xBitCounter < 52 ) xBit[xBitCounter++] = digitalReadFast(xDataPin);


void ReadYbit() 
    if (yBitCounter < 52 ) yBit[yBitCounter++] = digitalReadFast(yDataPin);

I also did one some years ago. It used a T3.2 and transmitted the count data via a Websocket server into the LAN/WLAN. The application was running in a browser (HTML5) so, anything which is able to launch a browser could be used as display. I used it for a couple of months on my lathe but finally I found out that tablets are not really made to be mounted on oily lathes :))

The stepper in the video has a 40'000 steps/rev encoder on the axis, the shown count rate was about 800'000 cts/sec.