DS18B20 reading delay affecting PWM function to work properly in void loop

Hello,

i am new to this forum and this is my first message, hope i am posting it right; I am a beginner to Arduino.
I have tried to search for a similiar issue i am having and have seen different earlier interesting conversations which improved my understandign, however none of them apparently solved completely my problem.

The excercise i am attempting to do is very simple and it is running with Arduino UNO wired to the following hardware :

  1. Dallas DS18B20 sensor.
  2. Fan Delta AFC0912D. This fan accept a 20kHz PWM control with variable duty cycle to set the speed.
  3. Simple 4.7k resistor and some wiring, plus a 12V battery.

The idea is the following: the user set a desired temperature to reach.
Periodically (at user decision in terms of delta time) the Dallas sensor is interrogated to read the ambient temperature and based on the desired temperature and the actual ambient temperature a kind of error is generated. The duty cycle of a PWM is then calculated based on that error and the PWM wave created.
The idea is to have a proportional type of control, however i am going to fine tune that aspect later.

I appreciated that the Dallas sensor may take up to 750ms delay to process the data reading, however this is delaying my PWM wave. Effectively the program run and the duty cycle is adjusted based on temperature, but every 800ms the fan stops and then re-start. Which is not right.

I would like to have the reading of the sensor (then the 750ms) in background, while the PWM is running. But i do not know how to do that.

Is there anyone that can help or have experienced this issue before? Posting the code if can help.

Many thanks,
Ado.

#include <OneWire.h>                   // Call out the relevant library for the temperature sensor 
#include <DallasTemperature.h>         // Call out library Dallas
#define ONE_WIRE_BUS 2                 // Data wire is plugged into pin 2 on the Arduino
OneWire oneWire(ONE_WIRE_BUS);         // Setup a oneWire instance to communicate with any OneWire devices 
DallasTemperature sensors(&oneWire);   // Pass our oneWire reference to Dallas Temperature.

float temperature;               //This variable is the actual temperature read by the sensor
float desired_T = 10;             //This is the desired temperature that the user can input to set
int T = 50;                      //This is the period of the square wave of PWM, 20kHz frequency = 1/20000 x 1 millio. This is the preferred PWM fan frequency of Delta AFC0912D, 20 KHz
float onDelayUs,offDelayUs;      //declare the on and off time variable type for the PWM
float dutyCycle;                 //PWM duty cycle in percentage
int  delayInMillis = 800;               // This variable in milliseconds will define how long time will be between two temperature readings
unsigned long earlier_time = millis();  // This variable is the earlier time bucket so that we can go and read the temperature only after 800ms
  
void setup()
{
  sensors.begin();                                    // Start up the library for the sensor
  pinMode(11,OUTPUT);                                 // Set the pin 11 as an output, since we will use it to make the PWM pulse
  Serial.begin(9600);                                 //Initialize the serial, just for trouble shooting purpose.
  sensors.requestTemperatures();                      // Send the command to get temperatures, this is the first value to kick off the programm
  temperature = sensors.getTempCByIndex(0);           //assign the value just requested to the variable called temperature
  dutyCycle=(temperature-desired_T)/temperature*100;  //Calculate the first dutycycle to kick off the fan, it will be refreshed every 800ms
  onDelayUs=T*dutyCycle/100;                          // on delay in microsecond, based on previous duty cycle calculated
  offDelayUs=T*(100-dutyCycle)/100;                   // Off delay in Microsecond, based on previous duty cycle calculated
}
 
void loop() {

    //The following "if" loop will execute the request temperature and read temperature from the dallas sensor only every delayInMillis time.
    //It will record the earlier_time, so that it can do the difference and go through the loop only if that difference is larger than delayInMillis
  
    if (millis()- earlier_time > delayInMillis) {
      sensors.requestTemperatures();                        //Request the temperature to the sensor
      temperature = sensors.getTempCByIndex(0);             //Read the temperature from the sensor
      dutyCycle=(temperature-desired_T)/temperature*100;    //Update a dutyCycle based on the new temperature and the desired. it is a kind of proportional control
      earlier_time = millis();                              //save into a bucket the earlier_time so that at the next loop we know when delayInMillis is passed
    }

    // the following portion of code is just the PWM control of the fan. The duty cycle should have been adjusted based
    // on the temperature reading of the "if" loop above.
    
    onDelayUs=T*dutyCycle/100;            // on delay in microsecond, calculated based on the duty cycle
    offDelayUs=T*(100-dutyCycle)/100;     // off delay in Microsecond, calculated based on the duty cycle
    digitalWrite(11, HIGH);               // Set the pin 11 to high
    delayMicroseconds(onDelayUs);         // Wait for the on time
    digitalWrite(11, LOW);                // Set the pin 11 to low
    delayMicroseconds(offDelayUs);        //wait for the off time
}
setWaitForConversion(false);

But read the documentation on the library. When you turn this option off, you need to be careful that you only ask for a temperature after the conversion has finished.

Hello MorganS,

thank you very much for the reply provided, much appreciated. My reply come only now as i just wanted to play a bit more before reverting back.

Effectively adding the line text you recommended the dynamic improves a lot, even if i am still experiencing a delay every 800ms (time between two sensor temperature readings) and the extension of the disturb is something around 25ms.
I had a look on some previous conversations about the subject and apparently it would be hard to get away from it, as seems to be relevant to the time needed to access the sensor.

I am posting two pictures from the oscilloscope showing the PWM signal sent to the fan. The signal is pretty clean with the exception that there is this disturb every time the sensor temperature is refreshed and the extension of the disturb is about 25ms.

Below the updated code.

Any insight would be appreciated.

Ado.

#include <OneWire.h>                   // Call out the relevant library for the temperature sensor 
#include <DallasTemperature.h>         // Call out library Dallas
#define ONE_WIRE_BUS 2                 // Data wire is plugged into pin 2 on the Arduino
OneWire oneWire(ONE_WIRE_BUS);         // Setup a oneWire instance to communicate with any OneWire devices 
DallasTemperature sensors(&oneWire);   // Pass our oneWire reference to Dallas Temperature.

float temperature;               //This variable is the actual temperature read by the sensor
float desired_T = 0;             //This is the desired temperature that the user can input to set
int T = 50;                      //This is the period of the square wave of PWM, 20kHz frequency = 1/20000 x 1 millio. This is the preferred PWM fan frequency of Delta AFC0912D, 20 KHz
float onDelayUs,offDelayUs;      //declare the on and off time variable type for the PWM
float dutyCycle;                 //PWM duty cycle in percentage
int  delayInMillis = 800;               // This variable in milliseconds will define how long time will be between two temperature readings
unsigned long earlier_time = millis();  // This variable is the earlier time bucket so that we can go and read the temperature only after 800ms
  
void setup()
{
  sensors.begin();                                      // Start up the library for the sensor
  sensors.setWaitForConversion(false);                  // do not wait for sensor conversion
  pinMode(11,OUTPUT);                                   // Set the pin 11 as an output, since we will use it to make the PWM pulse
  Serial.begin(9600);                                   //Initialize the serial, just for trouble shooting purpose.
  sensors.requestTemperatures();                        // Send the command to get temperatures, this is the first value to kick off the programm
  temperature = sensors.getTempCByIndex(0);             //assign the value just requested to the variable called temperature
  dutyCycle=(temperature-desired_T)/temperature*100;    //Calculate the first dutycycle to kick off the fan, it will be refreshed every 800ms
  onDelayUs=T*dutyCycle/100;                            // on delay in microsecond, based on previous duty cycle calculated
  offDelayUs=T*(100-dutyCycle)/100;                     // Off delay in Microsecond, based on previous duty cycle calculated
}
 
void loop() {

    //The following "if" loop will execute the request temperature and read temperature from the dallas sensor only every delayInMillis time.
    //It will record the earlier_time, so that it can do the difference and go through the loop only if that difference is larger than delayInMillis
  
    if (millis()- earlier_time > delayInMillis) {
      temperature = sensors.getTempCByIndex(0);               //Read the temperature from the sensor
      dutyCycle=(temperature-desired_T)/temperature*100;      //Update a dutyCycle based on the new temperature and the desired. it is a kind of proportional control
      if (dutyCycle > 100){                                   //This if loop is there to prevent the dutyCycle to exceed the max value of 100%
         dutyCycle = 100;
      }
      earlier_time = millis();              //save into a bucket the earlier_time so that at the next loop we know when delayInMillis is passed
      onDelayUs=T*dutyCycle/100;            // on delay in microsecond, calculated based on the duty cycle
      offDelayUs=T*(100-dutyCycle)/100;     // off delay in Microsecond, calculated based on the duty cycle  
      sensors.requestTemperatures();        //Request the temperature to the sensor 
    }

    // the following portion of code is just the PWM control of the fan. The duty cycle should have been adjusted based
    // on the temperature reading of the "if" loop above.
    
    digitalWrite(11, HIGH);               // Set the pin 11 to high
    delayMicroseconds(onDelayUs);         // Wait for the on time
    digitalWrite(11, LOW);                // Set the pin 11 to low
    delayMicroseconds(offDelayUs);        //wait for the off time
}

Wave_PWM.pdf (323 KB)

@OP

An alternative approach:
1. Create 20 kHz PWM signal at DPin-9 (OC1A) using Mode-10 Mode of TC1 Module.
pwmMode10x.png
Figure-1: Mode-10 PWM signal using TC1

2. Adjust duty cycle 'on the fly' and on interrupt basis depending on the difference of the 'set point' and 'actual point' of the temperatures.

3. The Skeleton Codes for generating 20 kHz PWM signal at DPin-9

void setup()
{
  pinMode(9, OUTPUT);   //DPin-9 will deliver PWM signal
  TCCR1A = 0xA2;
  TCCR1B = 0x11;
  ICR1 = 800;
  TCNT1 = 0x0000;
  OCR1A = 160;         //just an initial value

  TIMSK1 = _BV(ICIE1);     //Match at TOP's interrupt is enabled
  sei();                              //global interrupt bit is mad active

}

void loop() 
{
  //put your codes here
}

ISR(TIMER1_CAPT_vect)   //update OCR1A to regulate duty cycle
{
   // load OCR1A register depending on the error signal
}

pwmMode10x.png

Thanks GolamMostafa,

even if the code portion you posted is far beyond my current capability to understand it...

Ado.

@Ado_1
(Sometimes, we have to take 'working codes' as asset (without knowing how it works) to bring the experiment into a good shape; later on, we may spend time to learn what we have missed.)

1. Let us create a 20 kHz PWM signal at DPin-9 of UNO with 50% duty cycle by uploading the following sketch. If you have an oscilloscope, you can connect it at DPin-9 of UNO to see the clean signal.

void setup()
{
 Serial.begin(9600);
 pinMode(9, OUTPUT);   //DPin-9 will deliver PWM signal
 TCCR1A = 0xA2;        //setting for Mode-10 PWM
 TCCR1B = 0x11;        //setting for Mode-10 PWM
 ICR1 = 800;           //determines 20 kHz PWM
 TCNT1 = 0x0000;       //initial count
 OCR1A = 400;         //just an initial duty cycle 50% = ICR1*0.50
}

void loop() 
{
 
}

2. You may notice in the Sketch of Step-1 that we can vary the duty cycle (ON-period) of the PWM signal by changing the content of the OCR1A Register from 0 (0% duty cycle = ICR10.00) to 800 (100% duty cycle = ICR11.00). This dependency of duty cycle on the content of OCR1A Register is depicted below in Fig-1. The horizontal short dotted line goes up and down as the content of OCR1A changes and hence the duty cycle of the PWM signal and not the frequency.
pwmMode10x.png
Figure-1: Timing diagram of TC1 (Timer/Counter 1) based Mode-10 20 kHz PWM signal

3. You can drive your motor with this PWM signal provided that the duty cycle changes when there is a difference between your set point (reference temperature) and the actual point (temperature measured by DS18B20 sensor).

4. Upload the following codes to measure environment temperature by DS18B20 sensor and show it on Serial Monitor. The sketch will also show the computed dutyCycle of the Serial Monitor.

#include <OneWire.h>
#include <DallasTemperature.h>
#define ONE_WIRE_BUS 2
OneWire oneWire(ONE_WIRE_BUS);
DallasTemperature sensors(&oneWire);

float temperature;     //temp measured by sensor
float desired_T = 10.00; //This is the desired temperature that the user can input to set

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

void loop()
{
  sensors.requestTemperatures();            //Request the temperature to the sensor
  temperature = sensors.getTempCByIndex(0); //Read the temperature from the sensor
  float dutyCycle = (temperature - desired_T) / temperature;// * 100; //Update a dutyCycle
  Serial.println(temperature, 2);  //shows room temp with 2-digit after decimal point
  Serial.println(dutyCycle, 2);     //shows duty cycle with 2-digit after decimal point
  delay(1000);
}

5. Now, create codes so that the variable dutyCycle of the sketch of Step-4 is functionally related with OCR1A. It can be easily done this way:

OCR1A = ICR1*0.10;    //duty cycle of 20 KHz PWM signal is 10%
==> OCR1A = ICR1*dutyCycle; //duty cycle of PWM is dutyCycle% (temperature - desire_T)/temperature

6. Connect DS18B20 with DPin-2 and Motor with DPin-9 and then upload the following sketch which is a combined form of the codes of Step-1, Step-4, and Step-5.

#include <OneWire.h>
#include <DallasTemperature.h>
#define ONE_WIRE_BUS 2
OneWire oneWire(ONE_WIRE_BUS);
DallasTemperature sensors(&oneWire);

float temperature;     //temp measured by sensor
float desired_T = 10.00; //This is the desired temperature that the user can input to set

void setup()
{
  sensors.begin();
  Serial.begin(9600);
  genertae20kPwm();        //genertaing 20 kHz PWM at DPin-9
}

void loop()
{
  sensors.requestTemperatures();            //Request the temperature to the sensor
  temperature = sensors.getTempCByIndex(0); //Read the temperature from the sensor
  float dutyCycle = (temperature - desired_T) / temperature;// * 100; //Update a dutyCycle
  Serial.println(temperature, 2);  //shows room temp with 2-digit after decimal point
  Serial.println(dutyCycle, 2);     //shows duty cycle with 2-digit after decimal point
  OCR1A = ICR1*dutyCycle;
  delay(1000);
}

void genertae20kPwm()
{
  pinMode(9, OUTPUT);   //DPin-9 will deliver PWM signal
  TCCR1A = 0xA2;        //setting for Mode-10 PWM
  TCCR1B = 0x11;        //setting for Mode-10 PWM
  ICR1 = 800;           //determines 20 kHz PWM
  TCNT1 = 0x0000;       //initial count
  OCR1A = ICR1*0.50;    //400;just an initial duty cycle 50% = ICR1*0.50
}

7. Gently heat up the DS18B20 sensor and observe that the Motor speed changes. I have seen the change of the duty cycle in my oscilloscope.

pwmMode10x.png

Hi GolamMostafa,

thank you very much for the time you have spent helping me out, i am very impressed.
I went through the varius detailed steps and i have understood most of it. Have spent a bit of time this afternoon to try to understand the rational behind you proposal and why your code was working and mine not.

In practice i have launched the code and it works perfectly and the response is very nice and smooth without any delay. Appear to be very professional. I will now improve the control aspect trying to add a derivative part to the controller.

If you don't mind i will describe below my take away from this experience. If does happen you spare a few minutes more, would be good to understand if i am heading in the right direction.

Basically in my earlier code, the void loop function was having a dual scope: support the PWM wave generation and accessing th sensor to read. Any kind of delay within the void loop function (coming in this case from the Dallas sensor) was having a knock on effect on the cleanliness of the PWM signal, which was compromised for that amount of time spent to access the sensor reading.
In the code you suggested is the timer/conter register that is scaled starting from the clock frequency of 16MHz down to the 20kHz desired and this run anyway in the background without any chance to be distrurbed by the void loop content.
Only thing is done is to refresh at every sensor reading the OCR1A so that the duty cycle is tuned accordingly.
Is anything of this right at all?

Thanks,
Regards,
Ado.

Thanks for the excellent level of understanding of the program codes.