Close

Improvement of the EC sensor

A project log for UltraTower

Grow vegetables with ultraponics tower

J GleyzesJ Gleyzes 09/12/2022 at 07:030 Comments

It is better to read the EC sensor log before reading this one.

I wanted to test the stability and accuracy of the EC sensor!

So I replaced the EC probe with a fixed resistor (to mimic the resistance of a solution).  

The EC probe was replaced by R17 a fixed resistor for testing.

Code

You can find the whole code in this log but what we are interested in today is the measurement part: 

  ///////////////////////////////////////////////////////////////////////
    // Stage 2: measure positive discharge cycle by measuring the number of clock cycles it takes
    // for pin CAPPOS to change from HIGH to LOW.
    // CAPPOS: input.
    // CAPNEG: output, low (unchanged).
    // EC: output, low.
    endCycle = 0;
    startTime = micros();
    pinMode (CP_PIN,INPUT);
    startCycle = ESP.getCycleCount();
 
    attachInterrupt(CP_PIN, isrCountEndCycle, FALLING); 
    
    digitalWrite(EC_PIN, LOW);
    
    pinMode(EC_PIN, OUTPUT);
    
    while (endCycle == 0) {                   // endCyle gets set in the ISR, when an interrupt is received.
      if (micros() - startTime > EC_TIMEOUT) { // Time out - in case sensor not connected or not in water.
        timeout = true;
        break;
      }
    }
    detachInterrupt(CP_PIN);
    if (timeout) break;
    dischargeCycles = endCycle - startCycle;
    totalCycles += dischargeCycles;

Explaination line by line :  

startCycle = ESP.getCycleCount();

This is where the measurement of the CPU cycles number starts. This function is useful for accurate timing of very short actions.

attachInterrupt(CP_PIN, isrCountEndCycle, FALLING);

A function is attached which is triggered when the capacitor is empty. This is done by the CP_PIN. Once it is triggered it executes this line:

endCycle = ESP.getCycleCount();

This is the end of the measurement which also allows you to exit the while() loop. 

Then we detach the CP_PIN from the function and calculate the number of CPU cycles.

digitalWrite(EC_PIN, LOW);
pinMode(EC_PIN, OUTPUT);

These two lines are used to ground the EC_PIN to discharge the capacitor through the probe immersed in the solution (variable resistance depending on the nutrient concentration). 

startTime = micros();

*
*
*

if (micros() - startTime > EC_TIMEOUT) { 
        timeout = true;
        break;
 } 

These lines allow you to exit the while() loop if the probe is not immersed in water.

Test to measure accuracy:

For the accuracy test I took 500 measurements of a fixed resistor of 4.7kOhm and calculated the difference between the lowest and highest value. The result is that the values can have a difference of 2000 cycles which is not accurate at all. Each line of code after the startCycle = ESP.getCycleCount() can vary the measurement so I took each arduino function and compared it with the similar Espressif.'s function

For each function we measure the execution time in cycles and if there is a difference in cycles over 500 measurements, here is the result:

We can see that the Espressif's function takes 3 times less cycles to run but both functions are very stable there is no difference in cycles over 500 measurements.

Here we can see a big difference, the arduino function can have a big variation in execution cycles.

We will fix this!

The new code:

 ///////////////////////////////////////////////////////////////////////
    // Stage 2: measure positive discharge cycle by measuring the number of clock cycles it takes
    // for pin CAPPOS to change from HIGH to LOW.
    // CAPPOS: input.
    // CAPNEG: output, low (unchanged).
    // EC: output, low.

    endCycle = 0;
    pinMode (CP_PIN,INPUT);

    timerEC = timerBegin(2, 80, true);
    timerAttachInterrupt(timerEC, &onTimer, true);
    timerAlarmWrite(timerEC, EC_TIMEOUT, false);
    timerAlarmEnable(timerEC);

    attachInterrupt(CP_PIN, isrCountEndCycle, FALLING); 
    
    startCycle = ESP.getCycleCount();
    GPIO.out_w1tc = ((uint32_t)1 << EC_PIN);
    gpio_set_direction(GPIO_NUM_27, GPIO_MODE_OUTPUT);
  
    while (endCycle == 0) {           // endCyle gets set in the ISR, when an interrupt is received.
    }
    
    timerDetachInterrupt(timerEC);
    detachInterrupt(CP_PIN);
    timerEC = NULL;
    
    
    if(endCycle == -1){
      timeoutStop = true;
      break;
      }
    
    dischargeCycles = endCycle - startCycle;
    array[i] = dischargeCycles;
   

I re-tested the 500 measurements of a fixed 4.7kOhm resistor and calculated the difference between the lowest and the highest value which gives us a difference of 200 cycles.
The improvement is enormous.

You will notice that there are no more lines of code inside the while() loop, this is great for cycles count accuracy but it must stop if the EC probe is not in a liquid. 

The stop is realized by the timerEC which executes the function onTimer() after 2000 microseconds:

hw_timer_t *timerEC = NULL;

void IRAM_ATTR onTimer(){
     endCycle = -1;
}

To further improve the accuracy I fill in an array of the 500 values then I sort them in an increasing order and finally I average them by removing the first 100 values and the last 100. This gives us a variability of 3 cycles!

Conclusion:

The arduino functions are great for reading code but for accurate and fast code execution it is sometimes better to go without.

We went from a max variability of 2000 CPU cycles to a max variability of 3 cycles! Perfect!

Code for sorting and average:

#define NUM_OF_READ_SENSOR_AVG 500
#define NUM_OF_READ_TO_DELETE 100

int32_t moyenneOfArray(int16_t  *ar){
  int32_t avg = 0;
  int16_t nb = NUM_OF_READ_SENSOR_AVG - NUM_OF_READ_TO_DELETE;
  for (int16_t i= NUM_OF_READ_TO_DELETE; i< nb; i++){
      avg += ar[i];
   }

  return avg /  (NUM_OF_READ_SENSOR_AVG - 2*NUM_OF_READ_TO_DELETE);
}


void quickSort(int16_t *ar, int16_t n){
  if (n < 2)
    return;
  int16_t p = ar[n / 2];
  int16_t *l = ar;
  int16_t *r = ar + n - 1;
  while (l <= r) {
    if (*l < p) {
      l++;
    }
    else if (*r > p) {
      r--;
    }
    else {
      int t = *l;
      *l = *r;
      *r = t;
      l++;
      r--;
    }
  }
  quickSort(ar, r - ar + 1);
  quickSort(l, ar + n - l);
}

Discussions