300 mm DRO with Arduino (ATTiny85 and Nano)
In this video I show you a nice DRO with a great range (300 mm) and good resolution (0.01 mm).
I “hacked” the DRO and was able to hijack its data and clock signal and convert it into human readable values using an ATTiny85 microcontroller. I made my own tiny display unit for this DRO and the principles I used is applicable to similar DROs and digital calipers as well as any Arduino-compatible microcontrollers.
Schematics
Arduino (ATTiny 85) source code
Notice: I slightly modified the code as compared to the one I showed in the video. Based on some suggestions I improved the synchronization with the clock signal as well as I only refresh the display when the value is changed.
//The code uses the following Attiny85 core: https://github.com/SpenceKonde/ATTinyCore int dro_bits[24]; //array for the 24 bits char str[7]; //Array for the char "string" unsigned long droTimer = 0; //update frequency unsigned long buttonTimer = 0; //debouncing int clk = 4; //Blue wire int data = 3; //Red wire int button = 1; //button pin float convertedValue = 0.0; float resultValue = 0.0; float previousresultValue = 0.0; float tareValue = 0.0; #include <TinyI2CMaster.h> #include <Tiny4kOLED.h> void setup() { pinMode(clk, INPUT_PULLUP); pinMode(data, INPUT_PULLUP); pinMode(button, INPUT_PULLUP); oled.begin(128, 64, sizeof(tiny4koled_init_128x64br), tiny4koled_init_128x64br); oled.clear(); oled.setFontX2(FONT8X16P); oled.setCursor(0, 0); oled.print("Dist:"); oled.on(); } void loop() { readEncoder(); readButton(); printLCD(); } void readButton() { if (digitalRead(button) == 0) { if (millis() - buttonTimer > 300) //software "debounce" { tareValue = convertedValue; //use the most recent conversion value as the tare value buttonTimer = millis(); } } } void printLCD() { //It checks if the value has been changed. If yes, it updates the display. if (resultValue != previousresultValue) { dtostrf(resultValue, 0, 2, str); //float to char[] oled.setCursor(0, 4); oled.clearToEOL(); //erase the previous value from the display oled.setCursor(0, 4); oled.print(str); //print the new value previousresultValue = resultValue; } } void readEncoder() { //This function reads the encoder //I added a timer if you don't need high update frequency if (millis() - droTimer > 1000) //if 1 s passed we start to wait for the incoming reading { convertedValue = 0; //set it to zero, so no garbage will be included in the final conversion value //This part was not shown in the video because I added it later based on a viewer's idea unsigned long syncTimer = 0; //timer for syncing the readout process with the DRO's clock signal bool synchronized = false; // Flag that let's the code know if the clk is synced while (synchronized == false) { syncTimer = millis(); //start timer while (digitalRead(clk) == HIGH) {} //wait until the clk goes low //Time between the last rising edge of CLK and the first falling edge is 115.7 ms //Time of the "wide high part" that separates the 4-bit parts: 410 us if (millis() - syncTimer > 5) //if the signal has been high for more than 5 ms, we know that it has been synced { //with 5 ms delay, the code can re-check the condition ~23 times so it can hit the 115.7 ms window synchronized = true; } else { synchronized = false; } } for (int i = 0; i < 23; i++) //We read the whole data block - just for consistency { while (digitalRead(clk) == LOW) {} // wait for the rising edge dro_bits[i] = digitalRead(data); while (digitalRead(clk) == HIGH) {} // wait for the falling edge } //Reconstructing the real value for (int i = 0; i < 20; i++) //we don't process the whole array, it is not necessary for our purpose { convertedValue = convertedValue + (pow(2, i) * dro_bits[i]); //Summing up all the 19 bits. //Essentially: 1*[i] + 2*[i] + 4*[i] + 8*[i] + 16 * [i] + .... } if (dro_bits[20] == 1) { //don't touch the value (stays positive) } else { convertedValue = -1 * convertedValue; // convert to negative } convertedValue = (convertedValue / 100.0); //conversion to mm //Division by 100 comes from the fact that the produced number is still an integer (e.g. 9435) and we want a float //The 100 is because of the resolution (0.01 mm). x/100 is the same as x*0.01. //The final result is stored in a separate variable where the tare is subtracted //We need a separate variable because the converted value changes "on its own scale". resultValue = convertedValue - tareValue; droTimer = millis(); } }
Arduino (Nano) source code
I decided to add a normal Arduino-based code as well so you can use the same code directly without any modifications and optimizations on the popular Arduino boards such as the Arduino Nano or Uno…etc. Enjoy!
//This code is more general, written for Arduino (I tested it with Nano) int dro_bits[24]; // For storing the data bit. bit_array[0] = data bit 1 (LSB), bit_array[23] = data bit 24 (MSB). char str[7]; //Array for the char "string" unsigned long droTimer = 0; //update frequency (the DRO is checked this often) unsigned long buttonTimer = 0; //debouncing int clk = 4; //Blue int data = 5; //Red int button = 6; //button pin float convertedValue = 0.0; //raw conversion value (bit to mms) float resultValue = 0.0; //final result value: conversion value - tare value (if there is any taring) float previousresultValue = 0.0; //temporary storage to be able to register a change in the value float tareValue = 0.0; //tare value to set a new zero point anywhere (using a button) #include <Wire.h> //--Display--------------------------------------------- #include <Adafruit_GFX.h> #include <Adafruit_SSD1306.h> #define SCREEN_WIDTH 128 // OLED display width, in pixels #define SCREEN_HEIGHT 64 // OLED display height, in pixels #define OLED_RESET -1 // Reset pin Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET); void setup() { Serial.begin(9600); Serial.println("DRO reading Arduino"); //test message to see if the serial works pinMode(clk, INPUT_PULLUP); pinMode(data, INPUT_PULLUP); pinMode(button, INPUT_PULLUP); if (!display.begin(SSD1306_SWITCHCAPVCC, 0x3C)) { Serial.println(F("SSD1306 allocation failed")); for (;;); // Don't proceed, loop forever } display.display(); display.begin(SSD1306_SWITCHCAPVCC, 0x3C); display.setTextSize(3); display.setTextColor(SSD1306_WHITE); display.clearDisplay(); // Clear display display.setCursor(0, 0); // Start at top-left corner display.println("Dist:"); display.display(); //The text "Dist: " becomes a permanent part of the display } void loop() { readEncoder(); readButton(); printLCD(); } void printLCD() { //The display is only updated if the value of the DRO changes. if (resultValue != previousresultValue) { display.setTextSize(3); display.setCursor(0, 40); display.setTextColor(WHITE, BLACK); //This line together with the next erases the previous number display.println(" "); //Print the corresponding current display.setTextColor(SSD1306_WHITE); display.setCursor(0, 40); display.println(resultValue); //Print the corresponding current display.display(); previousresultValue = resultValue; //save the most recent reading } } void readButton() { if (digitalRead(button) == 0) //The button should pull the circuit to GND! (It is pulled up by default) { if (millis() - buttonTimer > 300) //software "debounce" { tareValue = convertedValue; //use the most recent conversion value as the tare value buttonTimer = millis(); } } } void readEncoder() { //This function reads the encoder //I added a timer if you don't need high update frequency if (millis() - droTimer > 1000) //if 1 s passed we start to wait for the incoming reading { convertedValue = 0; //set it to zero, so no garbage will be included in the final conversion value //This part was not shown in the video because I added it later based on a viewer's idea unsigned long syncTimer = 0; //timer for syncing the readout process with the DRO's clock signal bool synchronized = false; // Flag that let's the code know if the clk is synced while (synchronized == false) { syncTimer = millis(); //start timer while (digitalRead(clk) == HIGH) {} //wait until the clk goes low //Time between the last rising edge of CLK and the first falling edge is 115.7 ms //Time of the "wide high part" that separates the 4-bit parts: 410 us if (millis() - syncTimer > 5) //if the signal has been high for more than 5 ms, we know that it has been synced { //with 5 ms delay, the code can re-check the condition ~23 times so it can hit the 115.7 ms window synchronized = true; } else { synchronized = false; } } for (int i = 0; i < 23; i++) //We read the whole data block - just for consistency { while (digitalRead(clk) == LOW) {} // wait for the rising edge dro_bits[i] = digitalRead(data); //Print the data on the serial Serial.print(dro_bits[i]); Serial.print(" "); while (digitalRead(clk) == HIGH) {} // wait for the falling edge } Serial.println(" "); //Reconstructing the real value for (int i = 0; i < 20; i++) //we don't process the whole array, it is not necessary for our purpose { convertedValue = convertedValue + (pow(2, i) * dro_bits[i]); //Summing up all the 19 bits. //Essentially: 1*[i] + 2*[i] + 4*[i] + 8*[i] + 16 * [i] + .... } if (dro_bits[20] == 1) { //don't touch the value (stays positive) Serial.println("Positive "); } else { convertedValue = -1 * convertedValue; // convert to negative Serial.println("Negative "); } convertedValue = (convertedValue / 100.0); //conversion to mm //Division by 100 comes from the fact that the produced number is still an integer (e.g. 9435) and we want a float //The 100 is because of the resolution (0.01 mm). x/100 is the same as x*0.01. //The final result is stored in a separate variable where the tare is subtracted //We need a separate variable because the converted value changes "on its own scale". resultValue = convertedValue - tareValue; //Dump everything on the serial Serial.print("Raw reading: "); Serial.println(convertedValue); Serial.print("Tare value: "); Serial.println(tareValue); Serial.print("Result after taring: "); Serial.println(resultValue); Serial.println(" "); droTimer = millis(); } }