Reading Digital Caliper From Arduino

I have just encountered the need to read a digital dial indicator for another personal project. However, most of these digital dial indicators are “expensive” (normally cost about 30 – 100 USD), so I figure to just get a cheap digital caliper costing about 3 USD to start with. I totally understand that a cheap digital caliper might have different data protocols, but from what I gathered on the internet, most of these Chinese digital measurement tools use similar format — a clock and a data.

CaliperProofArduino output matches (closely) caliper reading. There are some discrepancy, but I think it the poor caliper that is displaying wrong data

Not able to find my USB logic analyzer and my oscilloscope is dead, I decided to use an Arduino as signal analyzer.

Background

Before diving into complex matters, let me just give you some background about the digital caliper I got. First of all, it is dirt cheap, it costs about 3 USD including shipping. Second of all, its resolution is only 0.1 mm and it has only one digit after decimal point. The worst thing is, it is made of “plastic like” material, the vendor claims that it is made of carbon fiber, but I really doubt it. Because this type of caliper uses capacitive effect, sometime, for some reason, if I hold it at end of the caliper, the readings can go crazy, jumping around and number keeps going up.

Nevertheless, it does have a data port at top of the enclosure. Before starting this project, I have searched internet about reading digital caliper and from this site, it seems clear that most of these digital caliper/scale/dial indicator output two signals — data and clock. After opening up the caliper, using a multi-meter,  I quickly find out which one is V+ and which one is V- and which are data lines — voltage on data lines is about half that of V+ and this makes sense as data and clock lines are changing rapidly.

DataWireUse multi-meter to measure voltages on each line. The data lines (SCK and SDA) usually measure at half the V+ line as their values change rapidly.

I could not deterministically  figure out which line is the clock and which one is the data, but because the clock signal should be relatively stable and the pattern for data signal will change as you move the slider. So a quick experiment shows that the yellow line seems to be the data line and the cyan line is the clock. This also coincides with other findings on the internet.

Hardware

Finding out (or guessing out) which line is which is not finished yet. The caliper uses 1.5V battery (the voltage on this caliper at moment of measure is about 1.2V, suggesting the either poor quality battery or it has been used for long time). So the signals from its data port will never exceed 1.5V and it is probably not enough for Arduino to interpret it as logic one. So level shifting is required.

CaliperLevel shifting is required. Here when SCK signal is high, the transistor T1 will be “turned on” allowing D2 to be shorted to ground. D2 and D2 will be read as logic zero. However since D2 is pulled up so when SCK is low, T1 is off and D2 will be read as logic one. Same with SDA line. This means all signals are inverted. Here D2 and D3 are digital pin 2 and 3 on Arduino and are configured as input pin with pull-up enabled.

From above circuit, even though signals are inverted, it is easy for Arduino to invert them back to normal state, ie, a LOW will be converted to HIGH, and vice versa.

Setup.jpgIt is rather easy to construct the circuit on a breadboard. Note only parts in yellow circle are used, others are left-over from other unrelated projects.

Software

The hardware part are easy and straight forward. It is the communication protocol this caliper uses is the unknown part and I need to figure it out. From what I gathered on the internet, these calipers work this way: idle state where clock is low, before transmit data, the clock goes high (an UP edge), then data line is valid when clock goes low (DOWN edge), clocking out all data in this fashion and goes back to idle state.

Since I do not have any analytical tools to help me, the first thing is to confirm the yellow line is indeed the clock line. How? Because the clock signal must be very consistent, the width of each clock pulse must be consistent, unlike data line. Also the assumption that number of clock pulses is usually mutiple of 8 (8 bits or a byte), it is easy to confirm which one is the SCK line by logging duration between UP and DOWN edges and print them out. Yes, the yellow line is indeed the clock line and surprisingly, number of clocks is 24, which means 3 bytes are clocked out. This is very different from what I gathered from internet.

The second thing is to interpret data captured. It turned out, the first byte is the low byte of a 2.5 byte (20 bits) sequence, the second byte is the high byte and the first 4 bits of the third byte are highest bits of 20 bits number. The fifth bit of the third byte is the sign indicator. The number represented by the 20 bits must be divided by 200 to arrive at a reading in millimeter display. Of course, all these were guessed based setting slider at particular positions, like zero, 1 mm, etc, the patter will reveal itself.

Conclusion

It was rather easy to hack this particular digital caliper and I was able to get it working in few hours of work. As this experiment shows, this particular caliper has very different protocol from those outlined on the internet. However, the key part of the success and the most important things are:

  • detecting UP and DOWN edges of any signal
  • use timer to be able to automatically synchronize with periodical data stream
  • use timer to capture and confirm clock signal
  • once data is captured, most of the time these data means 1/200th, 1/2000th of a millimeter, just play around with the result, the pattern will emerge.
  • For other type of digital caliper, dial indicators, even though their data format might be different, the approach should be similar.

The code for Arduino

#define   SDA 3           // data pin from caliper
#define   SCK 2           // clock pin from caliper

#define   MAX_SCK   64
#define   MAX_OVF   4
#define   MAX_DIG   8

#define   EDGE_UP    0
#define   EDGE_DOWN  1

unsigned char gv_mode   = 0;

int gv_arClock[MAX_SCK] = { 0 };    // this is mostly used to record clock duration, to gain insight, useless otherwise.
int gv_ixClock          = 0;
int gv_arValue[MAX_DIG] = { 0 };

int gv_ctReset            = 0;

void setup()
{
  // put your setup code here, to run once:
  pinMode(SCK, INPUT);        // make SCK (Digital 2) as input pin
  pinMode(SDA, INPUT);        // make SDA (Digital 3) as input pin

  digitalWrite(SCK, HIGH);    // pull up SCK pin so we do not need a pullup resistor
  digitalWrite(SDA, HIGH);    // pull up SDA pin so we do not need a pullup resistor

  TCCR1A = 0x00;
  TCCR1B = 0x02;  // half us, overflow at 32ms. 03= 4us
  TCCR1C = 0x00;

  Serial.begin(115200);
}

void loop()
{
  // put your main code here, to run repeatedly:

  while(1)
  {
    if (TIFR1 & 0b00000001)
    {
      // see if it is timing out,
      if (gv_ctReset < MAX_OVF)
      {
        gv_ctReset++;
        TIFR1   = TIFR1 | 0x01;
      }
      else
      {
        // see if there are any data captured
        // from experiment, the caliper sends out
        // 24 bits data periodically
        if (gv_ixClock == 24)
        {
          // if so, print it out
          long v = 0;
          v += gv_arValue[2] & 0x1F;
          v = v << 8;
          v += gv_arValue[1];
          v = v << 8;
          v += gv_arValue[0];

          // it seems that when the 5th bit is set in 3rd byte,
          // the value is negative
          if (gv_arValue[2] & 0b00100000)
          {
            v = -v;
          }
          // value must be divided by 200 to get measurement in MM
          double d = (double) v / 200.0;
          Serial.println(d);
        }

        // timed out, re-initialize all variables and try to resync
        for(int i=0; i<3; i++)
        {
          gv_arValue[i] = 0;
        }
        gv_ixClock  = 0;
        // wait for a LOW on SCK pin,
        // since signal is inverted, we are waiting for SCK high
        gv_mode     = EDGE_UP;
        // reset TIMER1 overflow to start over
        TIFR1       = TIFR1 | 0x01;
      }
    }

    // When SCK is LOW, it means clock pin is high from the caliper.
    if (digitalRead(SCK) == LOW)
    {
       // if current mode is detecting a LOW
       // this means an LOW to HIGH edge detected
       if (gv_mode == EDGE_UP)
       {
          // reset timer 1
          TCNT1       = 0;
          TIFR1       = TIFR1 | 0x01;
          gv_ctReset    = 0;

          // wait for edge from UP to DOWN
          gv_mode     = EDGE_DOWN;
       }
    }
    else
    {
      if (gv_mode == EDGE_DOWN)
      {
        // this means a HIGH to LOW edge detected
        // which in turn means data should be read from SDA pin
        int value = digitalRead(SDA);
        // since data is inverted, we need to invert it back
        if (value == HIGH)
        {
          value = 0;
        }
        else
        {
          value = 1;
        }
        gv_arValue[gv_ixClock / 8] |= value << (gv_ixClock % 8);

        if (gv_ixClock < MAX_SCK)
        {
          gv_arClock[gv_ixClock] = TCNT1;
          gv_ixClock++;

          // reset timer and time-out
          TCNT1       = 0;
          TIFR1       = TIFR1 | 0x01;
          gv_ctReset    = 0;
        }

        // wait for next LOW to HIGH edge
        gv_mode     = EDGE_UP;
      }
    }
  }
}

2 thoughts on “Reading Digital Caliper From Arduino”

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s