Wouter
Wouter

Reputation: 2653

Arduino/ESP8266 Using interrupts to take samples

I'm trying to measure the discharge time of a capacitor using a NodeMCU board. See complete sketch below. This code works fine, however I want to improve it to get to better time scales, by using the ESP.getCycleCount() function and interrupts for the timing. The part of interest is this:

startTime = micros();
while (digitalRead(capPos) == HIGH) {
  delayMicroseconds (1);
}
endTime = micros();

The while loop I want to rewrite into some form of interrupt based function, listening to a falling edge of the capPos pin, replacing the above function with something like this:

startTime = micros();
attachInterrupt(digitalPinToInterrupt(capPos), dischargeInterrupt, FALLING);
}


void dischargeInterrupt() {

endTime = micros();
detachInterrupt(digitalPinToInterrupt(capPos));

After that the original code continues.

Te problem I have is how to take the required 100 samples. When setting the interrupt after the startTime is taken, this routine will finish and do the next of the 100 iterations. Instead it should wait: for the interrupt to come, then for the rest of the routine to finish, as per original sketch. Being rather new to interrupts, I have no idea where to start with that part.

So what has to be done: - loop() calls getEC()

The main purpose of this change is to make the timing more accurate: react instantly on pin dropping to LOW and use a much higher resolution for time. The current microsecond resolution does the job but it's a serious limitation.

Here my complete, working, sketch:

//  capacitor based TDS measurement

// pin D5 C+ - 330 ohm resistor----------|------------|       
//                                       |            |
//                                        cap        EC probe or
//                                       |           resistor (for simulation)
// pin D6 C- ----------------------------|            |
//                                                    |
// pin A0 EC -----------------------------------------|

#include <Average.h>

int capPos = D5;  //C+
int capNeg = D6;  //C-
int EC = D7;      //EC

float CAP = 47; // capacity in nF
#define calibration 150 // a calibration factor to link time with EC.

void setup() {
  Serial.begin(9600);
}

void loop () {
  float EC = getEC(); // get the EC as mS/cm.
  Serial.println (", EC: " + String(EC) + " mS/cm");
  delay(100);
}

float getEC() {

  int samples = 100;              // number of EC samples to take and average.
  unsigned long startTime;        // the time stamp (in microseconds) the measurement starts.
  unsigned long endTime;          // the time stamp (in microseconds) the measurement is finished.
  unsigned int dischargeTime;    // the time it took for the capacitor to discharge.
  Average<unsigned int> discharge(samples); // Take measurements on both the positive and negative cycles.
  unsigned int chargeDelay = 500;         // The time (in microseconds) given to the cap to fully charge/discharge - about 10x RC is a good value.

  int startLevel; // analog level of the pin.
  int endLevel;
  pinMode(A0, INPUT);

  for(int i=0; i<samples; i++) { // take <samples> measurements of the EC.

    // Stage 1: fully charge capacitor for positive cycle.
    // C+ high, C- low, EC disconnected.
    pinMode (EC, INPUT);
    pinMode (capPos,OUTPUT);
    digitalWrite (capPos, HIGH);
    pinMode (capNeg, OUTPUT);
    digitalWrite (capNeg, LOW);
    delayMicroseconds(chargeDelay);

    // Stage 2: positive side discharge; measure time it takes.
    // C+ disconnected, C- low, EC low.
    pinMode (capPos,INPUT); //set C+ to input to keep voltage from grounding a discharging thru this output pin
    pinMode (EC, OUTPUT); 
    digitalWrite (EC, LOW);

    // Measure time until capPos goes LOW. Can't use pulseIn() here as the pin will be high already.
    startTime = micros();
    while (digitalRead(capPos) == HIGH) {
      delayMicroseconds (1);
    }
    endTime = micros();

    // handle potential overflow of micros() just as we measure, this happens every 70 minutes.
    if (endTime < startTime) dischargeTime = 4294967295 - startTime + endTime;
    else dischargeTime = endTime - startTime;
    discharge.push(dischargeTime);

    // Stage 3: fully charge capacitor for negative cycle. C+ low, C- high, EC disconnected.
    pinMode (EC, INPUT); 
    pinMode (capPos,OUTPUT);
    digitalWrite (capPos, LOW);
    pinMode (capNeg, OUTPUT);
    digitalWrite (capNeg, HIGH);
    delayMicroseconds(chargeDelay);

    // Stage 4: negative side charge; don't measure as we just want to balance it the directions.
    // C+ disconnected, C- low, EC low.
    pinMode (capPos,INPUT); //set C+ to input to keep voltage from grounding a discharging thru this output pin
    pinMode (EC, OUTPUT); 
    digitalWrite (EC, HIGH);
    delayMicroseconds(dischargeTime);

  }
  float dischargeAverage = discharge.mean();
  Serial.print("Discharge time: ");
  Serial.print(dischargeAverage);

  // Calculate EC from the discharge time.

  return dischargeAverage;
}

Upvotes: 0

Views: 4289

Answers (1)

Wouter
Wouter

Reputation: 2653

So, got this answered myself.

For the time resolution: you can get a much more exact time by using ESP.getCycleCount() which counts processor cycles - on my 80 MHz NodeMCU board that's 12.5 ns per cycle or 80 cycles per microsecond. I should have mentioned that in the first part.

Interrupts: this is something I misunderstood. Now I solved it by having the main function wait in a loop until a timeout is reached (set to 1 millisecond, normal expected times are in the 1-100 microseconds range), or until a global variable is set by the interrupt function. So now I'm measuring in the 12.5 nanosecond resolution!

One thing that's missing from this sketch is a correction for program time taken to deal with the timing: the time it takes from dropping the value on the EC pin to starting to count, and the time it takes from receiving the interrupt to stopping the count. If that overhead is say 100 cycles, that would be 1.25 microseconds, which is well within my measurement time.

// capacitor based TDS measurement

// pin D5 C+ - 330 ohm resistor----------|------------|       
//                                       |            |
//                                        cap        EC probe or
//                                       |           resistor (for simulation)
// pin D6 C- ----------------------------|            |
//                                                    |
// pin A0 EC -----------------------------------------|

#include <Average.h>

int capPos = D5;  //C+
int capNeg = D6;  //C-
int EC = D7;      //EC
unsigned long startCycle;
unsigned long endCycle;
#define CYCLETIME 12.5 // the time it takes in nanoseconds to complete one CPU cycle (12.5 ns on a 80 MHz processor)

float CAP = 47; // capacity in nF
#define calibration 150 // a calibration factor to link time with EC.

void setup() {
  Serial.begin(9600);
}

void loop () {
  float EC = getEC(); // get the EC as mS/cm.
  Serial.println (", EC: " + String(EC) + " mS/cm");
  delay(500);
}

float getEC() {

  int samples = 100;              // number of EC samples to take and average.
  unsigned long startTime;        // the time stamp (in microseconds) the measurement starts.
  unsigned long endTime;          // the time stamp (in microseconds) the measurement is finished.
  unsigned int dischargeTime;    // the time it took for the capacitor to discharge.
  Average<unsigned int> discharge(samples); // The sampling results.
  unsigned int chargeDelay = 500;         // The time (in microseconds) given to the cap to fully charge/discharge - about 10x RC is a good value.
  unsigned int timeout = 1;  // discharge timeout in milliseconds - if not triggered within this time, the EC probe is probably not there.

  int startLevel; // analog level of the pin.
  int endLevel;
  pinMode(A0, INPUT);

  for(int i=0; i<samples; i++) { // take <samples> measurements of the EC.

    // Stage 1: fully charge capacitor for positive cycle.
    // C+ high, C- low, EC disconnected.
    pinMode (EC, INPUT);
    pinMode (capPos,OUTPUT);
    digitalWrite (capPos, HIGH);
    pinMode (capNeg, OUTPUT);
    digitalWrite (capNeg, LOW);
    delayMicroseconds(chargeDelay);

    // Stage 2: positive side discharge; measure time it takes.
    // C+ disconnected, C- low, EC low.
    startCycle = ESP.getCycleCount();
    pinMode (capPos,INPUT); //set C+ to input to keep voltage from grounding a discharging thru this output pin
    pinMode (EC, OUTPUT); 
    digitalWrite (EC, LOW);

    // Use cycle counts and an interrupt to get a much more precise time measurement, especially for high-EC situations.
    endCycle = 0;
    startTime = millis();
    attachInterrupt(digitalPinToInterrupt(capPos), capDischarged, FALLING);
    while (endCycle == 0) {
      if (millis() > (startTime + timeout)) break;
    }
    detachInterrupt(digitalPinToInterrupt(capPos));
    if (endCycle == 0) dischargeTime = 0;
    else {

      // Handle potential overflow of micros() just as we measure, this happens about every 54 seconds
      // on a 80-MHz board.
      if (endCycle < startCycle) dischargeTime = (4294967295 - startCycle + endCycle) * CYCLETIME;
      else dischargeTime = (endCycle - startCycle) * CYCLETIME;
      discharge.push(dischargeTime);
    }

    // Stage 3: fully charge capacitor for negative cycle. C+ low, C- high, EC disconnected.
    pinMode (EC, INPUT); 
    pinMode (capPos,OUTPUT);
    digitalWrite (capPos, LOW);
    pinMode (capNeg, OUTPUT);
    digitalWrite (capNeg, HIGH);
    delayMicroseconds(chargeDelay);

    // Stage 4: negative side charge; don't measure as we just want to balance it the directions.
    // C+ disconnected, C- high, EC high.
    pinMode (capPos,INPUT); //set C+ to input to keep voltage from grounding a discharging thru this output pin
    pinMode (EC, OUTPUT); 
    digitalWrite (EC, HIGH);
    delayMicroseconds(dischargeTime/1000);

  }
  float dischargeAverage = discharge.mean();
  Serial.print("Discharge time: ");
  Serial.print(dischargeAverage);

  // Calculate EC from the discharge time.

  return dischargeAverage;
}

// Upon interrupt: register the cycle count of when the cap has discharged.
void capDischarged() {
  endCycle = ESP.getCycleCount();
  detachInterrupt(digitalPinToInterrupt(capPos));
}

Upvotes: 1

Related Questions