Make my DRO/dendrometer do more things

Hi!
I am trying to repurpose this project
https://www.makingstuff.info/Projects/Digital_Calipers
into something else that records the data and RTC time on an SD card every 10 minutes.
The problem is that the moment I modify the code in a wat that increases the loop run time the data is no longer readable.
Please can some one try to tell me how or where in the code I could insert a "delay(100);" that does not affect the data read? I am just asking for this, I just want to understand the code and make possible to do other things while I read the calipers data.

Here is the code:


//Digital caliper code to read the value off of a cheap set of digital calipers
//By Making Stuff Youtube channel https://www.youtube.com/c/makingstuff
//This code is open source and in the public domain.

const byte clockPin = 2;  //attach to clock pin on calipers
const byte dataPin = 3; //attach to data pin on calipers

//Milliseconds to wait until starting a new value
//This can be a different value depending on which flavor caliper you are using.
const int cycleTime = 32; 

unsigned volatile int clockFlag = 0; 

long now = 0;
long lastInterrupt = 0;
long value = 0;

float finalValue = 0;
float previousValue = 0;

int newValue = 0;
int sign = 1;
int currentBit = 1;

void setup() {
  Serial.begin(115200);
 
  pinMode(clockPin, INPUT);  
  pinMode(dataPin, INPUT); 
  
  
  //We have to take the value on the RISING edge instead of FALLING
  //because it is possible that the first bit will be missed and this
  //causes the value to be off by .01mm.
  attachInterrupt(digitalPinToInterrupt(clockPin), clockISR, RISING);
}

void loop() {  
  
  if(newValue) 
  {
   if(finalValue != previousValue) {
     previousValue = finalValue;
     Serial.println(finalValue,2);     
   }
   newValue = 0;
  }
  
 //The ISR Can't handle the arduino command millis()
 //because it uses interrupts to count. The ISR will 
 //set the clockFlag and the clockFlag will trigger 
 //a call the decode routine outside of an ISR.
 if(clockFlag == 1)
 {
  clockFlag = 0;
  decode();
 } 
  
}

void decode(){
   unsigned char dataIn;
   dataIn = digitalRead(dataPin); 
   
   now = millis();
   
   if((now - lastInterrupt) > cycleTime)
   {
     finalValue = (value * sign) / 100.00;
     currentBit = 0;
     value = 0;
     sign = 1;
     newValue = 1;      
   } 
   else if (currentBit < 16 )
   {
      
     if (dataIn == 0)
     {
       if (currentBit < 16) {
          value |= 1 << currentBit;
       }  
       else if (currentBit == 20) {
          sign = -1;
       }
               
     }
     
     currentBit++;
     
   }
   
   lastInterrupt = now;
   
}

void clockISR(){
 clockFlag = 1; 
}

where is the SD code?

Moreover, I can't see the SD.h library included.

You are right, It is not included. I wanted to simplify the problem for those that tried to help.
My goal is to be able to do things like set the device to sleep write on a card or just use a delay(1000). The problem is that this breaks the timing of the readings, note that the code reads bits of information one by one after a clock flag. If you use the anything that delays this cycle then the reading is not happening. I want to understand better the code so I can add things onto it.
To begin, I would like to get help to add a delay(1000) without affecting the reading. adding the SD code will be very easy for me after that. Thank you!

Simple answer is you can't. You will have to find another way. You can't block the execution of this code and expect to get readings. If you want to add more to the code, then whatever you add will have to be non-blocking. That means no delays. Forget that function even exists. Use the Blink Without Delay style of timing.

what is the clockfrequency of the caliper?

one approach could be to setup a timer-interrupt that occurs fast = often enough to sample each bit.
and therefore the frequency of the clock-signal must be known.

It appears as if it's just a clock pin data pin arrangement with the caliper providing the clock. It would probably be possible to use SCK and MOSI and put the arduino in SPI slave mode. You can get an interrupt every time you have a full byte.

You'll just keep SS as an input held low by another pin in code or a pull-down. Turn it high to stop reading.

And if it is really possible the datasheet of your caliper or a timing diagram that shows the bit-banging would make it possible to check it

this is probablly the closest to what OP has :wink:

actually explains the calipers output...

1 Like

no sure but would this not be a better way 'read in' the bit steam... :thinking:

(compiles, NOT tested)

//Digital caliper code to read the value off of a cheap set of digital calipers

const byte clockPin = 2;  //attach to clock pin on calipers
const byte dataPin = 3; //attach to data pin on calipers

uint8_t currentBit = 0;
const uint32_t dataReady = 0xFFE00000, signBit = 0x00100000;
volatile uint32_t dataIn;
uint32_t Mask = 1, previousValue = 0, isrTime = millis();

void setup() {
  Serial.begin(115200);

  pinMode(clockPin, INPUT);
  pinMode(dataPin, INPUT);


  //We have to take the value on the RISING edge instead of FALLING
  //because it is possible that the first bit will be missed and this
  //causes the value to be off by .01mm.
  attachInterrupt(digitalPinToInterrupt(clockPin), clockISR, RISING);
}

void loop() {
  uint32_t temp = dataIn;

  if (temp & dataReady)
  {
    if (previousValue != temp) {
      previousValue = temp;
      float measurement = (((temp << 1) & (~dataReady)) >> 2) / 100.0;
      if (temp & signBit) measurement *= -1;
      Serial.println(measurement);
    }
  }

}

void clockISR() {

  if (millis() - isrTime > 32) currentBit = 0; //time between 2 data streamss output approxx 115ms

  isrTime = millis();

  if (currentBit++ < 24) {
    if (currentBit == 1) dataIn = 0;
    if (digitalRead(dataPin)) dataIn |= Mask;
    Mask <<= 1;
  }

  if (currentBit == 24) {
    Mask = 1;
    currentBit = 0;
    dataIn |= dataReady;
  }

}

IMHO is would then be easier to add other bits of codes (like saving to SD card) to it...

one approach instead of using delay is to only 'save to SD card' on every count of the received measurement ie based on the link shared in post #9, you should get approximately 8 measurements per second so if you want to save one measurement every second then only save every 8th measurement.

hope that helps...

in another tutorial people said it is 100miliseconds . When the code is running "as is" I get a reading every 131,5 miliseconds. If I alter that timing more than a milisecond I dont get a reading

yes, right now that is what i am doing and it does the job but it was not as staright forward as you you and I thought. However this solution does not allow me to power down the devices and save battery. I know how to make all the components sleep, that is why I am only asking people to help me understand how to insert a "delay(1000)"

If I understood your approach, you doubled the "cycletime" and hope that this will delay the measurement. I tried this because I understood the code the same way as you but it does not work, the reading becomes 0.

I have an oscilloscope and I have messed around with the code a lot. I understand somewhat the code, that is why is so puzzling that anything I try is not working

it's hard to understand what you want here as you are not sharing any code (or even some form or pseudo code!)

If you are able to make ur devices sleep, then why not 'wake' them up every 1000ms and then capture/save the first reading received after 'wakeup' and then go back to sleep?

just and idea...

There are programming-tecniques that you do not yet know and understanding them requires to learn something new.

This programming-technique is not a variation of basic commands. It is something new. And this new needs some time to learn.

The code that you posted uses an interrupt that is caused by the signal-change of the clock-signal.

This single line of code is executed each time the clocksignal gos from LOW to HIGH = RISING

User @sherzaad posted a code-version that does not only set a flat but does reading in the bits of the dataline.

Did you at least test sherzaads code?
You should really test it and report what results you get.

There are 10 ways how some kind of "alter timing" can be coded.
It is crucial that you post your attempts what you have really coded.

You want to have this functionality

and you are asking for inserting a delay

it is not done this way
take a reading
make the microcontroller freeze for 1000 milliseconds
take a reading
make the microcontroller freeze for 1000 milliseconds
take a reading
make the microcontroller freeze for 1000 milliseconds
..
it is done this way:
take a reading as fast as you can (with the exact timing as in your code
check if more than 1000 milliseconds of time have passed by
when REALLY 1000 milliseconds have passed by store value on SD-card
if not just drop the reading

This means you take a lot of readings but you just drop all readings
that are "too early"
Only if 1000 milliseconds of time have really passed by you do something with the reading.

In this way the timing for reading stays the same as it already works
but you don't freeze the microcontroller with a dleay(1000)
you check very fast and very very often
have 1000 milliseconds passed by.

You can read the details how this is coded in this tutorial linked below
.You will still have some questions just ask them.

best regards Stefan

A different approach is to setup an interrupt that is not invoked on an IO-pin changing from LOW to HIGH but that is invoked once every x milliseconds.

For setting up how many milliseconds depends on at what time-intervalls the clock-pulses occur and how long one clock pulse is.
This timer-interrupt must occur fast enough to detect each HIGH-time of the clock-pulses.

example-numbers: the clock-pulse is HIGH for 30 milliseconds
The timer-interrupt must be invoked minimum once every 15 milliseconds. Better every 10 milliseconds.

It depends on your microcontrollers own clock-frequency and on the frequency of the DRO-meter if this method works.
This is the reason why I asked for the frequency of the clock-signal.
And you sould clearly post what exact type of microcontroller you are using. Because setting-up such a timer-interrupt is different for each microcontroller-type.

best regards Stefan

Actually that probably would be 'easiest' way for OP to get what he wants :wink:

so as a psuedo code @jmluscher

if (millis() -oldtime > 1000){
 attachInterrupt(digitalPinToInterrupt(clockPin), clockISR, RISING);  //enable data capture
//(remove call from 'setup')`
}

. . .
and once for 'newValue' is ready

if(newValue) {
    
    //save finalValue to SD card 

   detachInterrupt(digitalPinToInterrupt(clockPin))  // disable data capture
   newValue = 0;
}

hope that helps...

Did this get explored at all? Based on the code that was presented at the beginning it seemed like this was a standard sort of read-the-bit-when-the-clock-changes sort of thing. The SPI port can take all four modes, so all you need to know is when you're supposed to read the bit in the timing diagram so you can know which of the four modes to select.

Did anyone get the timing information? I didn't notice it anywhere in the thread.

post #9