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.
Arduino 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.
Use 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.
Level 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.
It 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”