Arduino reads digital caliper

Digital calipers have become available at very low prices. Some models even feature a serial port. For any passionate hacker this poses the immediate question: can Arduino read such calipers? The short answer is yes.

There is a type of digital caliper which is commonly sold on internet auctions for as little as 10-15€ and which features the required serial port. It hides behind a little plastic sliding cover on the upper side of the instrument. I bought one of these and after some testing luckily discovered that it uses a data protocol already described in detail on the net. I found this contribution particularly useful:

http://pcbheaven.com/exppages/Digital_Caliper_Protocol/?topic=chinesebcd

This is my caliper with the slide cover of the serial port opened

 

 

 

 

 

 

A close-up view of the serial port showing the four pins: 1.5V power supply, clock, data and ground.

 

 

 

 

 

 Here the most important facts about the caliper's data transmission:

This is all we need to know to operate the caliper in the millimetre mode, in inch mode things are slightly different.

Before decoding the caliper data, we need to raise the logic level of both data and clock lines from 1.5 to 5V. I have achieved this with the following simple transistor circuit:

However, this little circuit which we need on both the data and clock line not only raises the voltage of the signal, but it also inverts the logic levels. This is not a major problem as we can easily deal with it by adjusting the Arduino code accordingly.

 

Hardware setup:

The caliper’s data and clock lines are connected to 2 Arduino digital pins via the described level shifting circuit.

 

Arduino code:

The code first identifies the longer period when clock is HIGH, which separates the consecutive series of 24 bits. Then it reads the logic level of the data line each time the clock changes from HIGH to LOW and assembles the individual bits to the resulting value, considering also the positive or negative sign as expressed by bit 21.

Note that HIGH and LOW in this code are inverted since our interface circuit has inverted the original logic levels.

With this code and the described setup you should be able to read the caliper data on the serial monitor of your PC.

int i;

int sign;

long value;

float result;

int clockpin = 4;  

int datapin = 5;

unsigned long tempmicros;

 

 

void setup() {

  Serial.begin(9600);

  pinMode(clockpin, INPUT);

  pinMode(datapin, INPUT);

}

 void loop () {

  while (digitalRead(clockpin)==HIGH) {} //if clock is LOW wait until it turns to HIGH

  tempmicros=micros();

  while (digitalRead(clockpin)==LOW) {} //wait for the end of the HIGH pulse

  if ((micros()-tempmicros)>500) { //if the HIGH pulse was longer than 500 micros we are at the start of a new bit sequence

    decode(); //decode the bit sequence

  }

}

 

void decode() {

  sign=1;

  value=0;

  for (i=0;i<23;i++) {

    while (digitalRead(clockpin)==HIGH) { } //wait until clock returns to HIGH- the first bit is not needed

    while (digitalRead(clockpin)==LOW) {} //wait until clock returns to LOW

    if (digitalRead(datapin)==LOW) {

      if (i<20) {

        value|= 1<<i;

      }

      if (i==20) {

        sign=-1;

      }

    }

  }

  result=(value*sign)/100.00;    

  Serial.println(result,2); //print result with 2 decimals

  delay(1000);

}                                                                                                                                                                                           

Update:

Here a very useful comment and integration to the project which I received from Ian H. Thank you Ian for your contribution! Here's what he wrote:

I had to reverse HIGH and LOW inputs, to match the comments

  while (digitalRead(clockpin)==HIGH) {} //if clock is LOW wait until it turns to HIGH

  tempmicros=micros();

  while (digitalRead(clockpin)==LOW) {} //wait for the end of the HIGH pulse

became

  while (digitalRead(clockpin)==LOW) {} //if clock is LOW wait until it turns to HIGH

  tempmicros=micros();

  while (digitalRead(clockpin)==HIGH) {} //wait for the end of the HIGH pulse

that worked perfectly with my caliper.

But more interestingly I came up with a novel approach to the logic level problem. I used three 10k resistors to make a voltage divider across the arduino 5v supply. That gave, 0V --|10k|-- 1.66V --|10K|-- 3.32V --|10K|-- 5V. I ran the caliper supply voltage from the 3.32V division and the caliper ground from the 1.66V division. This gave the caliper a close to nominal 1.66V supply but meant that the arduino would see its logic level shifting from 1.6V to 3.2V, just about perfect for the digital input logic levels. It works very well and is a little simpler than the transistor solution, though that is obviously most robust.


further update:

Fenna Pel reworked the code for use with a rapsberry pico W, adding some calculation options for handling offset, sign inversion and calibration. Thank you Fenna!
Here is the result of her work:


float CaliperDecode(int CDclockpin, int CDdatapin){

  /*  

    the nitty-gritty of this function is based on the code in this article:

    https://sites.google.com/site/marthalprojects/home/arduino/arduino-reads-digital-caliper

    i reworked that code to be contained in a single function that returns the current caliper

    value as a float.


    To use this function, 

    declare the float variable that will recieve the value returned by this function

    in setup, prepare the input pins that connect to the caliper


    in the referenced article there is a suggestion to use a combination of resistors to power

    the caliper and place the 1,5v logic levels of the caliper in regions that the microcontroller

    understands.

    I used 2 10k potmeters in series to do that and set the caliper ground with one and the caliper

    supply voltage with the other. I connected the caliper to a raspberry pi pico W that has 

    3.3v logic

    © Fenna Pel, april 2023

  */

  short CDi;

  unsigned long CDvalue = 0;

  float CDresult = 0;

  unsigned long CDmicros = 0;

  int CDsign = 1;

  bool CDDATA;

  // tweak these values to suit your digital caliper/microcontroller combination

  float CDoffset = -10485.74; //start with this value set to 0. the return value of this function should be offset by this variable

  short CDfactor = 1; // start with this value set to 1. u8g2log option setRedrawMode(1) may require CDfactor = 2

  float CDcalibration = -0.01; // start with this value set to 0.00 

  const int CDstartpulselength = 500; // this value came from the code of Martin and i found no reason to change it

  const int CDsigninvert = 1; // this should be -1 or 1. If the value comes inverted, change CDsigninvert

  while (digitalRead(CDclockpin) == LOW) {} // invert for active HIGH caliper logic

  CDmicros = micros();

  while (digitalRead(CDclockpin) == HIGH) {} // invert for active HIGH caliper logic

  if ((micros() - CDmicros) > CDstartpulselength) {

    for (CDi = 0;CDi < 23;CDi++) {

      while (digitalRead(CDclockpin) == LOW) {} // invert for active HIGH caliper logic

      while (digitalRead(CDclockpin) == HIGH) {} // invert for active HIGH caliper logic

      CDDATA = !digitalRead(CDdatapin); // un-invert for active HIGH caliper logic

      // do the nitty-gritty of data decoding:

      if (CDDATA && (CDi < 20)) {

        CDvalue |= 1 << CDi; 

      }

      if (CDDATA && (CDi == 20)) {

        CDsign = CDsign * -1;

      }

/*    

      // these bits probably handle the mm/inch setting and the 5th digit in inch setting (the small 5)

      // as i have no need for these, i leave them be. My caliper defaults to mm setting

      if (CDDATA && (CDi == 21)) {

        // decode databit 21

      }

      if (CDDATA && (CDi == 22)) {

        // decode databit 22

      }

      if (CDDATA && (CDi == 23)) {

        // decode databit 23

      }

*/      

    }

  }

  CDsign = CDsign * CDsigninvert;

  CDresult = ((CDvalue / 100.00 + CDoffset) * CDfactor + CDcalibration) * CDsign;

  return CDresult;

}


Contact:

Email: thamart63@gmail.com