Fast PID with N DS18B20 sensor readings

Okay first post in here. Be gentle :stuck_out_tongue:

I have been working quite some time on this project and have had it with the Dallas Temperature library and it delays :stuck_out_tongue:

My SISO PID controller runs of a flow meter and is not dependent on the temperature (yet, might go for MISO controller)

My question is simple: How do I make sure the PID controller is called frequently enough (at the moment every ms, maybe a bit overkill)?

If I include my call for the DS18B20 (requestTemperatures()) my loop will be 1-2 sec delayed by the DT lib and the PID controller goes haywire.
I have included my full working code with the DS18B20 call disabled ind line 286 in a txt. Here is some king of pseudo code:

/*
 Example of DS18B20 is found here: 
 https://create.arduino.cc/projecthub/TheGadgetBoy/ds18b20-digital-temperature-sensor-and-arduino-9cc806

 Examples for PWM is found here:
 http://forum.arduino.cc/index.php?topic=114574.0
 http://forum.arduino.cc/index.php?topic=15514.15
 http://forum.arduino.cc/index.php?topic=18742.0
 http://www.circuitstoday.com/pwm-generation-and-control-using-arduino
 https://www.arduino.cc/en/Tutorial/PWM

 Example for flow meter is found here:
 http://www.instructables.com/id/How-to-Use-Water-Flow-Sensor-Arduino-Tutorial/

 Example for fan/motor/pump sense is found here:
 http://www.themakersworkbench.com/content/tutorial/reading-pc-fan-rpm-arduino

 Exsample for the SISO PID controller is found here:
 https://www.pdx.edu/nanogroup/sites/www.pdx.edu.nanogroup/files/2013_Arduino%20PID%20Lab_0.pdf
 Initial values for the PID is found here:
 http://blog.mavtechglobal.com/blog/2013/01/15/initial-settings-for-pid-controllers
 Other values for the PID controller have been found as trial and error without tuning at the time being
*/
/*
**********************************************INIT**********************************************
*/
// Include library for PID
#include <PID_v1.h>


// Include libraries for 1-wire temperature sensors
#include <OneWire.h> // 1-wire lib
#include <DallasTemperature.h> // For calculations etc.


// Something unimportant….

// Pin for PWM output signal for the pump
int controlPin;


// Variable for temperature read
unsigned int Probe;


// Variables for flow meter:
// Something unimportant….


// Variables for the PID controller
// Tuning parameters
float Kp = 1.2;                   // Variable for Proportional Gain 
float Ki = 5;                     // Variable for Initial Integral Gain 
float Kd;                         // Variable for Differential Gain 

double Setpoint, Input, Output;   // These are just variables for storing values
PID pumpPID(&Input, &Output, &Setpoint, Kp, Ki, Kd, DIRECT);  // This sets up our PID Loop
                                                            //Input is our PV (Process Variable)
                                                            //Output is our u(t) (Control voltage)
                                                            //Setpoint is our SP (Setpoint)
const int sampleRate = 1;         // Constant that determines how fast our PID loop runs [ms]

// Communication setup
// Something unimportant….

// Data wire for 1-wire is plugged into pin 23 on the Arduino MEGA 2560
#define ONE_WIRE_BUS 23

// Begin a 1-wire instance to communicate with any 1-wire devices  
// (not just Maxim/Dallas temperature ICs) 
OneWire oneWire(ONE_WIRE_BUS); 

// Begin to pass the 1-wire reference to the Dallas Temperature lib.
DallasTemperature sensors(&oneWire);





void setup()
{ 
 // Int for 25kHz PWM on PIN 2
 controlPin = 9;
 
 // Setup PWM output to zero not to start the pump.
 pinMode(9, OUTPUT);
 digitalWrite(9, LOW);
 
 // Setup Serial communnication
// Something unimportant….

 // This is the setup function where the interrupt for motor sense is attached
// Something unimportant….
 
 // Setup for measuring flow rate
 // Variables for flow meter:
 
 // Something unimportant….

 // Setup of the Sense output of the pump or any motor/fan
 // Something unimportant….

 
 // Setup of the PID controller
 // Something unimportant….

 Input = 2.0;                   // Initial input from flow meter. Set to 0 because the pump is not running at startup.
 Setpoint = 4;                  // Initial setpoint. Set at max allowed flow. (Range allowed in CSS connector: 2-4, Range possible: 1,5-9)
 pumpPID.SetMode(AUTOMATIC);      // Turns on the PID loop
 pumpPID.SetSampleTime(sampleRate);//Sets the sample rate

lastMessage = millis();        // Sets the first timestamp
 

 // Start up the DallasTemperature library 
 sensors.begin();

}






void loop()
{
  // Something unimportant….

   if (sensors.getDeviceCount() >= 0)
   {
     // Printing the temperatures
     Serial.print("Temperature ");
     Serial.print(0+1);
     Serial.print(" is: "); 
     Serial.println(sensors.getTempCByIndex(0));

     Serial.print("Temperature ");
     Serial.print(1+1);
     Serial.print(" is: "); 
     Serial.println(sensors.getTempCByIndex(1));
  
   }
   else
   {
     Serial.println("No temperature sensors to read!");
   }
   // Something unimportant….
  lastMessage = nowMS; //update the time stamp. 
  }
}
// Something unimportant….
// 2x Interrupts

I have found plenty of posts about the DT lib being slow and how to make it faster, however it still uses time which I like to use elsewhere :stuck_out_tongue:
Can I call my PID with an interrupt of a fast timer instead and how?

Main.txt (14.5 KB)

MBW:
My question is simple: How do I make sure the PID controller is called frequently enough (at the moment every ms, maybe a bit overkill)?

First thing is to figure out how often the PID code needs to be called. For example there is no need to call it if there is no new data available for it to work with. It is also pointless calling the PID code more frequently than the device being controlled can respond. For example most heaters respond slowly. I have a project controlling the speed of a small DC motor and the PID code is called once per revolution when new speed data is available. The time for a revolution can vary between about 4 and 30 millisecs (at different speeds) and it comfortably holds the speed within 1% of the target.

If I include my call for the DS18B20 (requestTemperatures()) my loop will be 1-2 sec delayed by the DT lib and the PID controller goes haywire.

I have not used them but from reading other Threads I believe the problem is with the library rather than the device. I think you need to write your own code to get data from the device and send a request for a new reading but don’t wait for the data. Keep checking to see if the new data is ready and only collect it when it is. That way the device won’t interfere with the rest of the program.

…R

Thx for the fast reply

I have included my new code in a txt.

I have made an interrupt with timer4 and now the only thing being delayed by the DT lib is my output to Serial and some nonessential sensor inputs.

And yes, what I have read about the DT lib, one should write some other code for requesting data and then reading data when ready. The read time of an DS18B20 can be below 20ms if only a single sensor is present. and the average time to get data with 4 DS18B20’s can be 50ms.

I like the idea about letting one of my other interrupts run the PID. Like whenever I receive the input from my flowmeter. However, I only calculate the input/flowrate every ~1000ms which is the input for the PID.

I’ll try to rearrange my calculations into an interrupt and see if that will change anything. However, this will be postponed for tomorrow

Main.txt (15 KB)

Depending on the resolution you request, the DS18B20 can take as long as 750 milliseconds to provide a value after you request a temperature.

9-bit (1/2 ¬įC resolution) 93.75 milliseconds
10-bit (1/4 ¬įC resolution) 187.5 milliseconds
11-bit (1/8 ¬įC resolution) 375 milliseconds
12-bit (1/16 ¬įC resolution) 750 milliseconds

You are NOT going to get fresh data every few milliseconds so there is no point in updating the PID that often.

Also, if you have not changed the sample time, the PID library default is 100 milliseconds. Just calling it every millisecond won't get you a faster sample time. It will just return 'false' if the sample time has not arrived yet.

void SetSampleTime(int);
// sets the frequency, in Milliseconds, with which the PID calculation is performed.  default is 100


bool PID::Compute()
{
   if(!inAuto) return false;   unsigned long now = millis();
   unsigned long timeChange = (now - lastTime);
   if(timeChange>=SampleTime)
   {

Check out the non-blocking wrapper I wrote for the DS18B20 library:

The sensor still takes just a long to come up with a reading, but you can do other stuff in the meantime. You can also just look at the source code in my library and implement the same techniques in your program.

@MBW: The DT library provides non-blocking reads of the sensors too.

In setup() after sensors.begin() add sensors.setWaitForConversion(false).
Then, in loop after sensors.requestTemperatures(), you can call sensors.isConversionComplete() which returns false until the conversion has completed.
This allows you to do the PID as needed while waiting for the conversion to complete.

Pete

MBW:
I like the idea about letting one of my other interrupts run the PID

I don't.

The PID computations take far longer than is appropriate for an ISR.

And what happens if any of the computations within the PID code assume that interrupts are enabled?

...R

PID works best if input sampling and output happen at regular intervals, and a general rule of thumb is that the interval should be about 1/10 of the system response time constant.

What is your system time constant?

Users/john/Documents/Arduino/sketch_may31a/sketch_may31a.ino: In function 'void loop()':
/Users/john/Documents/Arduino/sketch_may31a/sketch_may31a.ino:346:34: warning: comparison is always true due to limited range of data type [-Wtype-limits]
     if (sensors.getDeviceCount() >= 0)
                                  ^

Perhaps you meant ">0"?