Using qsort to average readings

Hey @Ric , that’s interesting, thanks for the tip!
I’d like to use this “median filter” in my projects, but I don’t see clearly yet how to do this.

A little background: We’re building 2 new homes together with one of my sons. For the HVAC system, I’m developing a controller using data from around 30 temperature sensors: For air temperature/humidity, I choose the DHT22 ‘family’ of sensors, and for water temperatures the DS18B20 looks like the best choice.

It would be great if I could see some example of how to handle samples
Maybe it is best to use an actual example to start with…

If it can help, here is one I recently made, and I’d like to integrate a “median filter” in the analyze() function:

(My previous choice of water temperature sensor was one with PWM output, needing one I/O pin per sensor. Below is a sketch I made to read temperatures of 16 of these sensors with one Particle. I will adapt it to use the DS18B20 sensors.)


/**************************************************************
 * Duty Cycle analyzer FUNCTION for PWM temperature sensors
 Any pin can be sensor input pin: Specify in function call in the loop()
 For debugging, the serial monitor shows all temperatures on one line
 **************************************************************/

float T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, Tav, temperature; // Only variables needed in the loop() function.
int nrPins;
char resultstr[64]; // To send results to Google as string

void setup()
{
 Serial.begin(9600);
 Particle.variable("result", resultstr, STRING);
}

void loop()
{
// 1) Data collection (All 16 Photon pins!)

 nrPins=0; // Reset every time

 analyze(D0);
 T1=temperature;
 if (temperature!=0)
 {
  nrPins++;
  Serial.print("=D0 ");
 }
 analyze(D1);
 T2=temperature;
 if (temperature!=0)
 {
  nrPins++;
  Serial.print("=D1 ");
 }
 analyze(D2);
 T3=temperature;
 if (temperature!=0)
 {
  nrPins++;
  Serial.print("=D2 ");
 }
 analyze(D3);
 T4=temperature;
 if (temperature!=0)
 {
  nrPins++;
  Serial.print("=D3 ");
 }
 analyze(D4);
 T5=temperature;
 if (temperature!=0)
 {
  nrPins++;
  Serial.print("=D4 ");
 }
 analyze(D5);
 T6=temperature;
 if (temperature!=0)
 {
  nrPins++;
  Serial.print("=D5 ");
 }
 analyze(D6);
 T7=temperature;
 if (temperature!=0)
 {
  nrPins++;
  Serial.print("=D6 ");
 }
 analyze(D7);
 T8=temperature;
 if (temperature!=0)
 {
  nrPins++;
  Serial.print("=D7 ");
 }
 analyze(A0);
 T9=temperature;
 if (temperature!=0)
 {
  nrPins++;
  Serial.print("=A0 ");
 }
 analyze(A1);
 T10=temperature;
 if (temperature!=0)
 {
  nrPins++;
  Serial.print("=A1 ");
 }
 analyze(A2);
 T11=temperature;
 if (temperature!=0)
 {
  nrPins++;
  Serial.print("=A2 ");
 }
 analyze(A3);
 T12=temperature;
 if (temperature!=0)
 {
  nrPins++;
  Serial.print("=A3 ");
 }
 analyze(A4);
 T13=temperature;
 if (temperature!=0)
 {
  nrPins++;
  Serial.print("=A4 ");
 }
 analyze(A5);
 T14=temperature;
 if (temperature!=0)
 {
  nrPins++;
  Serial.print("=A5 ");
 }
 analyze(A6);
 T15=temperature;
 if (temperature!=0)
 {
  nrPins++;
  Serial.print("=A6 ");
 }
 analyze(A7);
 T16=temperature;
 if (temperature!=0)
 {
  nrPins++;
  Serial.print("=A7 ");
 }

// 2) Data processing

 // Calculate average, buffer energy etc...
 if (nrPins!=0)
 {
  Tav=(T1+T2+T3+T4+T5+T6+T7+T8+T9+T10+T11+T12+T13+T14+T15+T16)/nrPins;
  Serial.print(" T-av = ");
  Serial.println(Tav,1);
 }

 // 3) Data broadcasting to Google
 sprintf(resultstr, "{\"T1\":%lg,\"T2\":%lg,\"T3\":%lg,\"T4\":%lg,\"T5\":%lg,\"T6\":%lg,\"T7\":%lg,\"T8\":%lg}", T1, T2, T3, T4, T5, T6, T7, T8);

 // 4) Test conditions and send status & alert messages, take actions: Pumps, valves...
 if (T1 > 30) // Example...
 {
  Serial.println("T1 > 30 deg");
  Particle.publish("Status","T1 > 30 deg",60,PRIVATE);
  RGB.control(true); // start control RGB LED
  RGB.color(255, 255, 255);// RGB LED White
  delay(1000);
  RGB.control(false); // stop control RGB LED
 }
 delay(500); // Slow down!
}

// In this function I'd like to integrate a "median filter":

void analyze(pin_t pulsePin)
{
  RGB.control(true); // control RGB LED
  RGB.color(255, 0, 0);// RGB LED Red

  unsigned long startTime, endTime; // Variable to record the start & end time
  int nrPulses = 160;  // Even number: Number of HI+LOW pulses (Number of spaces in array)
  int periods = nrPulses/2;  // For this sensor, use a multiple of 8! (See datasheet)
  int dataCounter;
  byte storedData[nrPulses]; // Array to store the data
  // Use floating variables for calculations
  float transmissionDuration, transmissionDurationSec, totalCount, totalLowCount, totalHighCount, dutycycle, frequency;
  temperature=0;// Reset each time
  frequency=0;

  pinMode(pulsePin, INPUT_PULLUP);// Make the pin HIGH without signal

  startTime=micros(); //Start time to measure wait time
  while(digitalRead(pulsePin) == HIGH)  //Detect NO CONNECTION; Wait for LOW signal:
  {
    if ((micros()-startTime) > 10000) // If it takes longer than 10ms, quit function!
    {
     return;// Exit the function!
    }
  }
  RGB.color(0, 0, 255);// RGB LED Blue

  while(digitalRead(pulsePin) == LOW)  //Wait here until a HIGH signal is received:
  {
   startTime=micros(); //Reset start time with every recording
  }

  for(int i=0; i<nrPulses; i=i+2) // Record pulse train!
  {
   dataCounter=0; //reset the counter
   while(digitalRead(pulsePin) == HIGH)
   {
    dataCounter++;
    storedData[i]=dataCounter;
   }

   dataCounter=0; //reset the counter
   while(digitalRead(pulsePin) == LOW)
   {
    dataCounter++;
    storedData[i+1]=dataCounter;
   }
  }
  endTime=micros(); //End time of the pulse train reading

  transmissionDuration=endTime-startTime;
  transmissionDurationSec = transmissionDuration/1000000;
  frequency = periods/transmissionDurationSec;
  //Serial.print("Freq = ");
  //Serial.println(frequency,0);

  totalLowCount=0; //reset accumulator
  totalHighCount=0; //reset accumulator

  for(int i=0; i<nrPulses; i=i+2)
  {
   totalHighCount=totalHighCount+storedData[i]; // Total all HIGH pulses
   totalLowCount=totalLowCount+storedData[i+1]; // Total all LOW pulses
   //Serial.print(storedData[i]);
   //Serial.print(",");
   //Serial.println(storedData[i+1]);
  }

  totalCount=totalHighCount+totalLowCount;
  dutycycle=totalHighCount/totalCount;
  temperature = (212.77*dutycycle)-68.085;
  if (temperature!=0)
  {
    Serial.print(temperature,1);
  }
  RGB.control(false); // end RGB LED control
}

The code you have in loop could be simplified a lot by using for loops. The pin values, D0, A1, etc are integers so you can use them in a loop (D0 = 0, A0 = 10). Also, you can use printlnf to simplify your serial prints. Something like this should work,

int nrPins;
float temperature;
float temps[18]; 

void loop() {
    
    nrPins=0; // Reset every time
    
    for (int i = D0; i <= D7; i++) {
        analyze(i);
        if (temperature != 0) {
            temps[i] = temperature;
            nrPins++;
            Serial.printf("=D%d  ", i);
        }
    }
    
    
    for (int i = A0; i <= A7; i++) {
        analyze(i);
        if (temperature != 0) {
            temps[i] = temperature;
            nrPins++;
            Serial.printf("=A%d  ", i-10); // the integer value of A0 is 10
        }
    }
    
    float total = 0;
    for (int i=0; i<18; i++) {
        total += temps[i];
    }
    
    float Tav = total/nrPins;
    Serial.printlnf("T-av = %.1f", Tav);
    
    delay(2000);
}

I’m not really sure what you’re doing in the analyze function. I assume that you want to use the median filter on storedData. To do that, you just need to sort it using qsort, then either pick the middle value or pick several of the central values, and average them. You can use qsort like this,

qsort(storedData, nrPulses, sizeof(float), compare); // sort to put any outliers on either end of the array
int center = nrPulses/2;
float avg = (storedData[center -2] + storedData[center-1] + storedData[center] + storedData[center+1])/ 4.0;  // take the average of the center 4 values

int compare (const void * a, const void * b) {
  float fa = *(const float*) a;
  float fb = *(const float*) b;
  return (fa > fb) - (fa < fb);
}
1 Like

@FiDel, you see @Ric has just pointed out the same things as I did in my PM to you :wink:

Just one note that abstracts the D0…7/A0…7 thing away

const int pinCount = 10;
const int pins[pinCount] = { D0, D2, D3, D5, D7, A1, A3, A5, A7, TX };

void loop()
{
  ...
  for (int i=0; i<pinCount; i++)
    usePin(pins[i]);
  ...
}
1 Like

@Ric and @ScruffR, thanks for your continuous help on this fantastic forum! A lot of “brainpower” comes together here! :smiley:

@Ric, thanks for your example of the magical “qsort” command! I was not aware of this yet!

Based on your example, I will try to put this “median filter” to work with my temperature sensors and come back with the results I get.


The long loop() function was not the point of my question of course but I find both your remarks interesting!

Normally, I would not use that many repetitive commands in a row, but it is a test sketch.

Purpose:

  • I make all 16 pins available to read a different PWM signal, using one analyze() function.
  • When nothing is connected, it should not create an error.
  • The serial output should only show data from pins actually receiving data.

In my final sketches, where I will use a smaller number of pins for this function, the solution of @ScruffR looks like the most practical one.
For my test with all 16 pins, indeed @Ric’s solution is very neat.

Seeing different solutions to reaching a target is very interesting and useful for me!
I’ll try out both your alternative codes asap to see if I can achieve the same results.
(That may take some time as I’m very busy these days…)

:hand: :older_man:

1 Like

Just one note that even further 'abstracts' the pinCount variable away:

const int pins[pinCount] = { D0, D2, D3, D5, D7, A1, A3, A5, A7, TX };

void loop()
{
  ...
  for (int i = 0; i < sizeof(pinCount)/sizeof(pinCount[0]); i++)
    usePin(pins[i]);
  ...
}

@ScruffR, how does the greater forum community benefit from secret solutions? :wink:

@FiDel,

Can you tell us more about the analyze() function? I'm curious to really understand. I'm starting to do a project to 'balance' my HVAC system and wonder what you are doing with the data as well. That's a lot of data points, can you provide a physical description of how you will locate them?

Interesting project!! Maybe it needs to jump off this topic.

1 Like

When I receive a PM, I usually respond to the PM in private too - and since it was pretty much the same thing as the public (just a few minutes after I responded privately), there is no secret in it.

And this was the initial point @FiDel approached me in the PM about - and hence I guess you might see more about it in the future :sunglasses:


BTW: I think there only is a benefit in the proposed solution if it's done this way (to drop the need for pinCount :wink: )

const int pins[] = { D0, D2, D3, D5, D7, A1, A3, A5, A7, TX };

void loop()
{
  ...
  for (int i = 0; i < sizeof(pins)/sizeof(pins[0]); i++)
    usePin(pins[i]);
  ...
}
1 Like

yeah, just sort of teasing you while I made my point about PM's on a forum!

:smile: you know we all appreciate your assistance!

I know it so happens that people on other forums make people afraid to ask earnest questions in open... This forum is the antithesis of all of that.

3 Likes

Thanks for your interest to this topic @BulldogLowell.
Maybe I should reply in the new discussion (saw that @Moors7 has split it off...) done :wink:

1 Like

I posted a sketch I made earlier in order to be used as example for the qsort command.
What I did in my sketch is similar but much longer indeed: "Smoothening" the temperature measurements.

  • Collect a number of samples (High & low pulses) in an array (no calculations then not to influence the time measurement)
  • Total all high and low pulses from the array
  • Calculate the (average) duty-cycle with these totals

I'm struggleing to understand this function:

void analyze(pin_t pulsePin)

maybe instead of using the function to continually update a global variable, maybe you should have it just return the temperature:

float analyze(pin_t pulsePin)
{
  RGB.control(true); // control RGB LED
  RGB.color(255, 0, 0);// RGB LED Red

  unsigned long startTime, endTime; // Variable to record the start & end time
  int nrPulses = 160;  // Even number: Number of HI+LOW pulses (Number of spaces in array)
  int periods = nrPulses / 2; // For this sensor, use a multiple of 8! (See datasheet)
  int dataCounter;
  byte storedData[nrPulses]; // Array to store the data
  // Use floating variables for calculations
  float transmissionDuration, transmissionDurationSec, totalCount, totalLowCount, totalHighCount, dutycycle, frequency;
  float measuredTemperature = 0; // Declare local variable, initializing it to zero
  frequency = 0;

  pinMode(pulsePin, INPUT_PULLUP);// Make the pin HIGH without signal

  startTime = micros(); //Start time to measure wait time
  while (digitalRead(pulsePin) == HIGH) //Detect NO CONNECTION; Wait for LOW signal:
  {
    if ((micros() - startTime) > 10000) // If it takes longer than 10ms, quit function!
    {
      return;// Exit the function!
    }
  }
  RGB.color(0, 0, 255);// RGB LED Blue

  while (digitalRead(pulsePin) == LOW) //Wait here until a HIGH signal is received:
  {
    startTime = micros(); //Reset start time with every recording
  }

  for (int i = 0; i < nrPulses; i = i + 2) // Record pulse train!
  {
    dataCounter = 0; //reset the counter
    while (digitalRead(pulsePin) == HIGH)
    {
      dataCounter++;
      storedData[i] = dataCounter;
    }

    dataCounter = 0; //reset the counter
    while (digitalRead(pulsePin) == LOW)
    {
      dataCounter++;
      storedData[i + 1] = dataCounter;
    }
  }
  endTime = micros(); //End time of the pulse train reading

  transmissionDuration = endTime - startTime;
  transmissionDurationSec = transmissionDuration / 1000000;
  frequency = periods / transmissionDurationSec;
  //Serial.print("Freq = ");
  //Serial.println(frequency,0);

  totalLowCount = 0; //reset accumulator
  totalHighCount = 0; //reset accumulator

  for (int i = 0; i < nrPulses; i = i + 2)
  {
    totalHighCount = totalHighCount + storedData[i]; // Total all HIGH pulses
    totalLowCount = totalLowCount + storedData[i + 1]; // Total all LOW pulses
    //Serial.print(storedData[i]);
    //Serial.print(",");
    //Serial.println(storedData[i+1]);
  }

  totalCount = totalHighCount + totalLowCount;
  dutycycle = totalHighCount / totalCount;
  measuredTemperature = (212.77 * dutycycle) - 68.085;
  if (measuredTemperature != 0)
  {
    Serial.print(measuredTemperature, 1);
  }
  RGB.control(false); // end RGB LED control
  return measuredTemperature;
}

and make the assignments as you go.
something like this:

 T12 = analyze(A3);

instead of this:

 analyze(A3);
 T12=temperature;  //<<< this is kind of an anonymous assignment!!!

you have lots of manipulation of global variables in that function, which adds (I feel) to my difficulty in understanding what you are trying to do.

What are the Pulses?

1 Like

OK @BulldogLowell, please understand I am a 62 years old young man who started programming (C++) one year ago… During my university days (1973 - 1977), we programmed in “Pascal”, using paper cards with pencil written codes…
English is only my 4th language, which explains maybe my strange vocabulary like “pulses” which you seem not to understand. :wave: :older_man:

Some background about what I’m trying to achieve:

What I’m trying to develop is a complete home automation system for 2 adjacent homes (my son and us) , with 16 rooms, where in each room a Photon is used to receive signals of all sensors in the room and control all actuators in the room. Then all Photons in both houses talk to each other to exchange data from sensors and commands for actuators. In fact, a “distributed” home-automation system" instead of the classic “centralized home automation system”.

A big portion of my efforts goes towards organizing the energy flow in our “eco” heating system:

I appreciate your above tip to save one line in my sketch!
How I love to learn “good practice” tricks…

But what I really want to do in my sketches, is put as much as possible all commands in my functions, instead of putting it in the setup() or loop() function.

The reason is that I want to be able to combine the building blocks more simply in my “room sketches” by using functions from my “library” into more complex sketches, where I would call the functions from the loop() function.

Is that not good practice?

The sketch I pasted above is not yet a finished sketch, as it is only to test the temperature sensors, at that time PWM type output sensors (3 kHz HI & LOW “pulses”). Since then I changed my mind to use one-wire sensors…

The only reason I pasted the sketch is to see how I could use the “qsort” command to simply sort the data in my array and create a “median filter”. @Ric gave me a good idea how to do this, and I’ll try it out with every sensor from now on.

I hope you understand my sketch better now. :relaxed:

1 Like

Good, as there is no minimum age requirement here! :smile:

Sorry, I cannot understand what actually "Pulses". I gather it is a signal from a device, but I cannot understand what that is, yet.

My advice was given as a way for you to incorporate into a loop like this (compiles).

/**************************************************************
 * Duty Cycle analyzer FUNCTION for PWM temperature sensors
 Any pin can be sensor input pin: Specify in function call in the loop()
 For debugging, the serial monitor shows all temperatures on one line
 **************************************************************/

double Temp[10] = {0}; // All the temperatures into an array!! 10 in this example
double Tav; // Only variables needed in the loop() function.  you can still make this local to loop()
int pulsePins[] = { D0, D2, D3, D5, D7, A1, A3, A5, A7, TX };
char resultstr[256] = "";

void setup()
{
  Serial.begin(9600);
  for (int i = 0; i < sizeof(pulsePins) / sizeof(pulsePins[0]); i++)
  {
    pinMode(pulsePins[i], INPUT);
  }
   Particle.variable("result", resultstr, STRING);
}

void loop()
{
  // 1) Data collection (All 16 Photon pins!)
  strcpy(resultstr, "");  // start with an empty buffer
  Tav = 0;  // get ready to average what we recieve
  for (int i = 0; i < sizeof(pulsePins) / sizeof(pulsePins[0]); i++) // count our way through all of the pins
  {
    Temp[i] = analyze(pulsePins[i]);
    Tav += Temp[i];  // add all of the readings together
    char myText[10] = "";  // create a littl buffer for your reading.
    sprintf(myText, "T%d:%3.2f%d ", i, Temp[i], i < sizeof(pulsePins) / sizeof(pulsePins[0]) ? "," : ""); // Build your buffer reading by reading
    strcat(resultstr, myText); // bolt on each segment to the buffer
  }
  Tav /= sizeof(pulsePins) / sizeof(pulsePins[0]); // compute the average by dividing by number of readings
  Serial.print("temperatures:");
  Serial.println(resultstr);
  Serial.print("Average = ");
  Serial.println(Tav, 1);

  // 4) Test conditions and send status & alert messages, take actions: Pumps, valves...
  if (Temp[0] > 30.0) // Example...
  {
    Serial.println("T1 > 30 deg");
    Particle.publish("Status", "T1 > 30 deg", 60, PRIVATE);
    RGB.control(true); // start control RGB LED
    RGB.color(255, 255, 255);// RGB LED White
    delay(1000);
    RGB.control(false); // stop control RGB LED
  }
  delay(500); // Slow down!
}

// In this function I'd like to integrate a "median filter":

double analyze(pin_t pulsePin)
{
  RGB.control(true); // control RGB LED
  RGB.color(255, 0, 0);// RGB LED Red

  unsigned long startTime, endTime; // Variable to record the start & end time
  int nrPulses = 160;  // Even number: Number of HI+LOW pulses (Number of spaces in array)
  int periods = nrPulses / 2; // For this sensor, use a multiple of 8! (See datasheet)
  int dataCounter;
  byte storedData[nrPulses]; // Array to store the data
  // Use floating variables for calculations
  float transmissionDuration, transmissionDurationSec, totalCount, totalLowCount, totalHighCount, dutycycle, frequency;
  double temperature = 0.0;// Reset each time
  frequency = 0;

  pinMode(pulsePin, INPUT_PULLUP);// Make the pin HIGH without signal

  startTime = micros(); //Start time to measure wait time
  while (digitalRead(pulsePin) == HIGH) //Detect NO CONNECTION; Wait for LOW signal:
  {
    if ((micros() - startTime) > 10000) // If it takes longer than 10ms, quit function!
    {
      break;// Exit the function!
    }
  }
  RGB.color(0, 0, 255);// RGB LED Blue

  while (digitalRead(pulsePin) == LOW) //Wait here until a HIGH signal is received:
  {
    startTime = micros(); //Reset start time with every recording
  }

  for (int i = 0; i < nrPulses; i = i + 2) // Record pulse train!
  {
    dataCounter = 0; //reset the counter
    while (digitalRead(pulsePin) == HIGH)
    {
      dataCounter++;
      storedData[i] = dataCounter;
    }

    dataCounter = 0; //reset the counter
    while (digitalRead(pulsePin) == LOW)
    {
      dataCounter++;
      storedData[i + 1] = dataCounter;
    }
  }
  endTime = micros(); //End time of the pulse train reading

  transmissionDuration = endTime - startTime;
  transmissionDurationSec = transmissionDuration / 1000000;
  frequency = periods / transmissionDurationSec;
  //Serial.print("Freq = ");
  //Serial.println(frequency,0);

  totalLowCount = 0; //reset accumulator
  totalHighCount = 0; //reset accumulator

  for (int i = 0; i < nrPulses; i = i + 2)
  {
    totalHighCount = totalHighCount + storedData[i]; // Total all HIGH pulses
    totalLowCount = totalLowCount + storedData[i + 1]; // Total all LOW pulses
    //Serial.print(storedData[i]);
    //Serial.print(",");
    //Serial.println(storedData[i+1]);
  }

  totalCount = totalHighCount + totalLowCount;
  dutycycle = totalHighCount / totalCount;
  temperature = (212.77 * dutycycle) - 68.085;
  if (temperature != 0)
  {
    Serial.print(temperature, 1);
  }
  RGB.control(false); // end RGB LED control
  return temperature;
}

that's a few lines less...

not tested!!

Helpful?

1 Like

Thanks for your compilable sketch @BulldogLowell, but it is the same idea that both @Ric and @ScruffR developed earlier :relaxed:

I will indeed use it for the purpose of testing 16 sensors with a Photon (or even more with the electron…) For my “room sketches” however, I will use the pin by pin commands as there will usually be only a few pins used for Temp/Hum monitoring.

Some more explanation about the pulses I use in my sketch:
The PWM sensors I used earlier are outputting a continuous “pulse train” (4 kHz) and the Duty Cycle is proportional with the temperature:

I sample a few hundred “pulses” to reduce the error (increase the accuracy).
Hope this explains it sufficiently.

@BulldogLowell: There are more interesting tricks in your sketch than I spotted first, thanks!
I will try to use these tricks when I need them. Great!

Q: Can you integrate the qsort function in the analyze() function so that the resulting temperature is a median average of the array values?

No tricks, just some experience.

Thanks for showing what the Pulses are, now I understand!!

So before we implement qsort() can we simplify analyse(), and maybe make it a bit more readable? Perhaps like this:

/**************************************************************
 * Duty Cycle analyzer FUNCTION for PWM temperature sensors
 Any pin can be sensor input pin: Specify in function call in the loop()
 For debugging, the serial monitor shows all temperatures on one line
 **************************************************************/

//double Temp[16] = {0}; // All the temperatures into an array!!
double Tav; // Only variables needed in the loop() function.
int pulsePins[] = { D0, D2, D3, D5, D7, A1, A3, A5, A7, TX };
double Temp[10] = {0}; // All the temperatures into an array!!  // 10 in this example
char resultstr[256] = "";

void setup()
{
 Serial.begin(9600);
 for (int i = 0; i < sizeof(pulsePins)/sizeof(pulsePins[0]); i++)
 {
   pinMode(pulsePins[i], INPUT_PULLUP);
 }
  Particle.variable("result", resultstr, STRING); // Moved
}

void loop()
{
// 1) Data collection (All 16 Photon pins!)
strcpy(resultstr, "");  // start with an empty buffer
Tav = 0;  // get ready to average what we recieve
for (int i = 0; i < sizeof(pulsePins)/sizeof(pulsePins[0]); i++)  // count our way through all of the pins
{
  Temp[i] = analyze(pulsePins[i]);
  Tav += Temp[i];  // add all of the readings together
  char myText[10] = "";  // create a littl buffer for your reading.
  sprintf(myText, "T%d:%3.2f%d ", i, Temp[i], i < sizeof(pulsePins)/sizeof(pulsePins[0])? "," : "");  // Build your buffer reading by reading
  strcat(resultstr, myText); // bolt on each segment to the buffer
}
Tav /= sizeof(pulsePins)/sizeof(pulsePins[0]); // compute the average by dividing by number of readings
Serial.print("temperatures:");
Serial.println(resultstr);
Serial.print("Average = ");
Serial.println(Tav,1);

 // 4) Test conditions and send status & alert messages, take actions: Pumps, valves...
 if (Temp[0] > 30.0) // Example...
 {
  Serial.println("T1 > 30 deg");
  Particle.publish("Status","T1 > 30 deg",60,PRIVATE);
  RGB.control(true); // start control RGB LED
  RGB.color(255, 255, 255);// RGB LED White
  delay(1000);
  RGB.control(false); // stop control RGB LED
 }
 delay(500); // Slow down!
}

// In this function I'd like to integrate a "median filter":

double analyze(pin_t pulsePin)
{
  double temperature = 0.0;// Local variable
  const int num_pulses = 160;
  int periodDutyCycle[num_pulses] = {0};
  RGB.control(true); // control RGB LED
  RGB.color(255, 0, 0);// RGB LED Red
  for (int i = 0; i < num_pulses; i++)  // get num_pulses samples of HIGH and LOW pulses
  {
    int highPulseTime = 0;
    while(highPulseTime == 0)
    {
      highPulseTime = pulseIn(pulsePin, HIGH);
    }
    int lowPulseTime = 0;
    while(lowPulseTime == 0)
    {
      lowPulseTime = pulseIn(pulsePin, LOW);
    }
    periodDutyCycle[i] = highPulseTime / (highPulseTime + lowPulseTime);  // save individual period duty cycle
  }
  int dutyCycle = 0;
  for(int i = 0; i < num_pulses; i++)
  {
    dutyCycle += periodDutyCycle[i];
  }
  dutyCycle /= num_pulses;
  temperature = (212.77 * dutyCycle) - 68.085;
  RGB.control(false); // end RGB LED control
  return temperature;
}

not tested, but compiles.

helpful?

1 Like

That’s neat @BulldogLowell, thanks!
I will check it out with sensors as soon as I can find some time and I’ll be back… :wink:

1 Like

[quote=“BulldogLowell, post:17, topic:20824”]
Particle.variable(“result”, resultstr, STRING);
[/quote]I don’t believe this line should be in the for loop in setup(). :wink:

1 Like

right!! I’ll fix it!!

2 Likes

great,

I added a timeout in the case where the sensor is not producing any PWM signal (not tested obviously): you could even extend it to report a bad sensor...

double analyze(pin_t pulsePin)
{
  double temperature = 0.0;// Local variable
  const int num_pulses = 160;
  const int timeout = 25;  // we should see nearly 100 pulses at 4kHz in 25milliseconds
  int periodDutyCycle[num_pulses] = {0};
  RGB.control(true); // control RGB LED
  RGB.color(255, 0, 0);// RGB LED Red
  for (int i = 0; i < num_pulses; i++)  // get num_pulses samples of HIGH and LOW pulses
  {
    bool goodRead = true;
    int startReadTime = millis();
    int highPulseTime = 0;
    while(highPulseTime == 0)
    {
      highPulseTime = pulseIn(pulsePin, HIGH);
      if(millis() - startReadTime > timeout)
      {
        goodRead = false;
        break;
      }
    }
    int lowPulseTime = 0;
    startReadTime = millis();
    while(lowPulseTime == 0)
    {
      lowPulseTime = pulseIn(pulsePin, LOW);
      if(millis() - startReadTime > timeout)
      {
        goodRead = false;
        break;
      }
    }
    if(goodRead)  // will only do the math if you have received values, otherwise it will stay zero...
    {
      periodDutyCycle[i] = highPulseTime / (highPulseTime + lowPulseTime);  // save individual period duty cycle
    }
  }
  int dutyCycle = 0;
  int num_goodReads = 0;
  for(int i = 0; i < num_pulses; i++)
  {
    if(periodDutyCycle[i] != 0)
    {
      dutyCycle += periodDutyCycle[i];
      num_goodReads++;
    }
  }
  dutyCycle /= num_goodReads;
  temperature = (212.77 * dutyCycle) - 68.085;
  RGB.control(false); // end RGB LED control
  return temperature;
}

compiles as part of this:

/**************************************************************
 * Duty Cycle analyzer FUNCTION for PWM temperature sensors
 Any pin can be sensor input pin: Specify in function call in the loop()
 For debugging, the serial monitor shows all temperatures on one line
 **************************************************************/

//double Temp[16] = {0}; // All the temperatures into an array!!
double Tav; // Only variables needed in the loop() function.
int pulsePins[] = { D0, D2, D3, D5, D7, A1, A3, A5, A7, TX };
double Temp[10] = {0}; // All the temperatures into an array!!  // 10 in this example
char resultstr[256] = "";

void setup()
{
 Serial.begin(9600);
 for (int i = 0; i < sizeof(pulsePins)/sizeof(pulsePins[0]); i++)
 {
   pinMode(pulsePins[i], INPUT_PULLUP);
 }
 Particle.variable("result", resultstr, STRING);
}

void loop()
{
// 1) Data collection (All 16 Photon pins!)
strcpy(resultstr, "");  // start with an empty buffer
Tav = 0;  // get ready to average what we recieve
for (int i = 0; i < sizeof(pulsePins)/sizeof(pulsePins[0]); i++)  // count our way through all of the pins
{
  Temp[i] = analyze(pulsePins[i]);
  Tav += Temp[i];  // add all of the readings together
  char myText[10] = "";  // create a littl buffer for your reading.
  sprintf(myText, "T%d:%3.2f%d ", i, Temp[i], i < sizeof(pulsePins)/sizeof(pulsePins[0])? "," : "");  // Build your buffer reading by reading
  strcat(resultstr, myText); // bolt on each segment to the buffer
}
Tav /= sizeof(pulsePins)/sizeof(pulsePins[0]); // compute the average by dividing by number of readings
Serial.print("temperatures:");
Serial.println(resultstr);
Serial.print("Average = ");
Serial.println(Tav,1);

 // 4) Test conditions and send status & alert messages, take actions: Pumps, valves...
 if (Temp[0] > 30.0) // Example...
 {
  Serial.println("T1 > 30 deg");
  Particle.publish("Status","T1 > 30 deg",60,PRIVATE);
  RGB.control(true); // start control RGB LED
  RGB.color(255, 255, 255);// RGB LED White
  delay(1000);
  RGB.control(false); // stop control RGB LED
 }
 delay(500); // Slow down!
}

// In this function I'd like to integrate a "median filter":

double analyze(pin_t pulsePin)
{
  double temperature = 0.0;// Local variable
  const int num_pulses = 160;
  const int timeout = 25;  // we should see nearlyu 100 pulses at 4kHz in 25milliseconds
  int periodDutyCycle[num_pulses] = {0};
  RGB.control(true); // control RGB LED
  RGB.color(255, 0, 0);// RGB LED Red
  for (int i = 0; i < num_pulses; i++)  // get num_pulses samples of HIGH and LOW pulses
  {
    bool goodRead = true;
    int startReadTime = millis();
    int highPulseTime = 0;
    while(highPulseTime == 0)
    {
      highPulseTime = pulseIn(pulsePin, HIGH);
      if(millis() - startReadTime > timeout)
      {
        goodRead = false;
        break;
      }
    }
    int lowPulseTime = 0;
    startReadTime = millis();
    while(lowPulseTime == 0)
    {
      lowPulseTime = pulseIn(pulsePin, LOW);
      if(millis() - startReadTime > timeout)
      {
        goodRead = false;
        break;
      }
    }
    if(goodRead)
    {
      periodDutyCycle[i] = highPulseTime / (highPulseTime + lowPulseTime);  // save individual period duty cycle
    }
  }
  int dutyCycle = 0;
  int num_goodReads = 0;
  for(int i = 0; i < num_pulses; i++)
  {
    if(periodDutyCycle[i] != 0)
    {
      dutyCycle += periodDutyCycle[i];
      num_goodReads++;
    }
  }
  dutyCycle /= num_goodReads;
  temperature = (212.77 * dutyCycle) - 68.085;
  RGB.control(false); // end RGB LED control
  return temperature;
}
1 Like