millis wrong time

Hi,

I’m learning on someones elses sketch Arduino programing. I have trouble understanding
why I have to divide millis by 125 to get correct seconds showing on LCD.
I belive that millis are mili seconds
Am I wrong ? Please look at my Code and help me

#include <LiquidCrystal.h>
#include "max6675.h"
#include "PID_v1R.h"  // similar to standard PID_v1, this custom library is required for full functionality
#include <avr/power.h>

#define CLK_DIV 8    // 8
// define 2 exceptions (temp and pid). TEMP_ALARM is fatal - program end.
#define TEMP_ALARM_MIN -10 // exceeds min temp limit [F]
#define TEMP_ALARM_MAX 300 // exceeds max temp limit [F]

/* PID limits should reflect actual physical capability. */
#define PID_MIN  0  // exceeds min PID limit
#define PID_MAX  255  // exceeds max PID limit

int TargetTemp = 0;
unsigned long currentTime = 0;
unsigned long startoffset;

#define TEMP_CORRECTION_OFFSET -0.0 // Thermocouple fudge factor [Fahrenheit]

// MAX6675 + Thermocouple convert Arduino Analog pins to power and communication.
// K-thermocouple only, 0-1024C range, 12-bit, 0.25C resolution, ~250K conversions/sec. max.
int thermoD0 =  15; // A1
int thermoCS =  16; // A2
int thermoCLK = 17; // A3
int vccPin =    18; // A4
int gndPin =    19; // A5

// PWM on 2 pins (+/-) using Timer 2 (16 bits)
int PWM_Pin_plus = 11;
int PWM_Pin_minus = 3;

// PID variables we connect to
double Setpoint = 100, Input = 20, Output, PV; // degrees Fahrenheit, Celsius or anything else
double PIDSAMPLETIME_MS = 1000 ;  // milliseconds
double Kp = 0.1, Ki = 0.3, Kd = 0.5, K = 1; // K is fudge factor for Output Gain or Thermal Resistance

unsigned long stepN;

// menu
int keypad_pin = A0;
int keypad_value = 0;
int keypad_value_old = 0;
char btn_push;
short mainMenuPage = 1;         // byte
short mainMenuPageOld = 1;      // byte
const int mainMenuTotal = 6;    // 5 + 1

typedef struct {                     // 5 steps in temperature profile
  char *desc;                        // description 16 char max.
  double Kp, Ki, Kd;                 // PID parameters
  double T1, T2, T3, T4, T5;         // T temperature
  unsigned long t1, t2, t3, t4, t5;  // t time [integer minutes]
} profile;

profile profiles[mainMenuTotal];
void t_PID(profile *r, double *T, unsigned long *t);

// create the objects
PID myPID(&Input, &Output, &Setpoint, Kp, Ki, Kd, DIRECT);
MAX6675 thermocouple(thermoCLK, thermoCS, thermoD0);
LiquidCrystal lcd(8, 9, 4, 5, 6, 7);  // initialize the LCD library with the numbers of the interface pins

void setup() {
  clock_prescale_set(clock_div_8);
  lcd.begin(16, 2);     // LCD's number of columns and rows
  lcd.setCursor(0, 0);  // Print a message to the LCD
  lcd.print("PID Temp Control");

  // PID setup
  myPID.SetMode(AUTOMATIC);
  myPID.SetSampleTime(PIDSAMPLETIME_MS);
  myPID.SetOutputLimits(PID_MIN, PID_MAX);   // True PID heating and cooling. Should reflect actual physical limits

  // MAX6675 Temp. measurement setup
  pinMode(gndPin, OUTPUT); digitalWrite(gndPin, LOW);   // GND A5
  pinMode(vccPin, OUTPUT); digitalWrite(vccPin, HIGH);  // VCC A4
  delay(1000 / CLK_DIV);

  // phase-correct PWM proportional output,  Timer 2 on pins 3, 11
  TCCR2A = _BV(COM2A1) | _BV(COM2B1) | _BV(WGM20);
  //TCCR2B = _BV(CS22);
  //TCCR2B = TCCR2B & B11111000 | B00000001;    // set timer 2 divisor to     1 for PWM frequency of 31372.55 Hz
  //TCCR2B = TCCR2B & B11111000 | B00000010;    // set timer 2 divisor to     8 for PWM frequency of  3921.16 Hz
  //TCCR2B = TCCR2B & B11111000 | B00000011;    // set timer 2 divisor to    32 for PWM frequency of   980.39 Hz
  //TCCR2B = TCCR2B & B11111000 | B00000100;    // set timer 2 divisor to    64 for PWM frequency of   490.20 Hz  <-- default
  //TCCR2B = TCCR2B & B11111000 | B00000101;    // set timer 2 divisor to   128 for PWM frequency of   245.10 Hz
  //TCCR2B = TCCR2B & B11111000 | B00000110;    // set timer 2 divisor to   256 for PWM frequency of   122.55 Hz
  TCCR2B = TCCR2B & B11111000 | B00000111;    // set timer 2 divisor to  1024 for PWM frequency of    30.64 Hz
  pinMode(PWM_Pin_plus,  OUTPUT);
  pinMode(PWM_Pin_minus, OUTPUT);

  // Serial
  Serial.begin(2400 * CLK_DIV);  // Change Serial Monitor tool datarate to 2400
  Serial.println("Arduino PID Temperature Control");

  // User Edit    "desc"    ,   Kp,  Ki,  Kd, T1,  T2,  T3,  T4,  T5,  t1,  t2,  t3,  t4,  t5 [t in integer minutes]
  profiles[0] = {"Default"  ,  0.1, 0.3, 0.5,  80,   0,   0,   0,   0, 72000, 0,   0,   0,   0};
  profiles[1] = {"Profile 1",  1.5, 0.3, 0.1, 130,  150, 120,  130,  150,  1,  1,  1,  1,  1};
  profiles[2] = {"Profile 2",  0.2, 0.3, 0.7, 160,   180,  200,  210,  0,   5,  3,  2,  1,   5};
  profiles[3] = {"Profile 3",  0.2, 0.4, 0.5, 20,   1,  30,  10,  40,   1,  50,  10,  60,   1};
  profiles[4] = {"Profile 4",  0.2, 0.4, 0.6, 20,   5,  30,   1,  40,   5,  50,   1,  60,   5};
  profiles[5] = {"Profile 5",  0.2, 0.4, 0.6, 30, 35,  30,  40,  30,   1,  1,   1,  1,   1};
}

void loop() {
  btn_push = ReadKeypad();
  WaitBtnRelease();
  MainMenuBtn();

  if (btn_push == 'S') // enter selected profile
  {
    startoffset = millis();
    myPID.SetTunings(profiles[mainMenuPage].Kp, profiles[mainMenuPage].Ki, profiles[mainMenuPage].Kd);
    t_PID(&profiles[mainMenuPage], &profiles[mainMenuPage].T1, &profiles[mainMenuPage].t1 );
    t_PID(&profiles[mainMenuPage], &profiles[mainMenuPage].T2, &profiles[mainMenuPage].t2 );
    t_PID(&profiles[mainMenuPage], &profiles[mainMenuPage].T3, &profiles[mainMenuPage].t3 );
    t_PID(&profiles[mainMenuPage], &profiles[mainMenuPage].T4, &profiles[mainMenuPage].t4 );
    t_PID(&profiles[mainMenuPage], &profiles[mainMenuPage].T5, &profiles[mainMenuPage].t5 );
    PWM_OUT(0);   // turn off forcing functions
  }
  MainMenuDisplay();

} // close loop()

void t_PID(profile *r, double *T, unsigned long *t) {
  unsigned long startTime = millis();  
  unsigned long t_div = 60000;  // for accelerated testing, 1000ms/s * 60s/min = 60000 ms/min
  myPID.mySetpoint = T;
  TargetTemp = *T;


  while ((millis() - startTime) * CLK_DIV / t_div < *t) {
    PV = thermocouple.readCelsius() + TEMP_CORRECTION_OFFSET;  // Process Value (current temperature)
    Input = PV;  // uncomment to use for live operation

    if (Input <= TEMP_ALARM_MIN || Input >= TEMP_ALARM_MAX) {
      temp_alarm(Input);
    }
    LCD_update(); SER_update();  // update the displays
    myPID.Compute();
    if (Output <= PID_MIN || Output >= PID_MAX) {
      pid_error();
    }
    Input = Input + K * Output;
    PWM_OUT(K * Output);   // update the control outputs
    stepN++;
    delay(PIDSAMPLETIME_MS / CLK_DIV);
  }
}

void LCD_update() {
  currentTime = (millis() - startoffset) / 125;

  lcd.clear();
  lcd.setCursor(0, 0);
  lcd.print(currentTime / 60); // AA Minutes
  lcd.print(":");
  lcd.print((currentTime % 60) / 10); // AA Seconds
  lcd.print((currentTime % 60) % 10);  // AA Seconds
  lcd.print(" I=");
  lcd.print(myPID.GetITerm(), 0);
  lcd.print(" D=");
  lcd.print(myPID.GetDTerm(), 0);
  lcd.setCursor(0, 1);
  lcd.print("T=");
  lcd.print(Input, 1);
  lcd.print("C");
  lcd.print(" ST=");
  lcd.print(TargetTemp, 1);
  delay(100);
}

void SER_update() {


  Serial.print("step\t sec.\t PV\t in\t out\t ITerm\t DTerm\t Target\t \n");
  Serial.print(stepN);
  Serial.print("\t");
  Serial.print(currentTime);  // seconds
  Serial.print("\t");
  Serial.print(PV);
  Serial.print("\t");
  Serial.print(Input);
  Serial.print("\t");
  Serial.print(Output);
  Serial.print("\t");
  Serial.print(myPID.GetITerm());
  Serial.print("\t");
  Serial.print(myPID.GetDTerm());
  Serial.println("\t");
  Serial.print(TargetTemp);
  Serial.println("\n");
}
  unsigned long t_div = 60000;  // for accelerated testing, 1000ms/s * 60s/min = 60000 ms/min

use 60000L if you want a long int constant.

double Setpoint = 100, Input = 20, Output, PV; // degrees Fahrenheit, Celsius or anything else
double PIDSAMPLETIME_MS = 1000 ;  // milliseconds
double Kp = 0.1, Ki = 0.3, Kd = 0.5, K = 1; // K is fudge factor for Output Gain or Thermal Resistance

On ATmega's double means the same as float.

Normally it is. But the person is messing with the clock prescalar :wink:

clock_prescale_set(clock_div_8);

makes it run 1/8th of the speed.

Clock prescale set wac changed to get Low Frequency PWM

Okay, just saying what the cause is.

And as a bonus, you can lower the PWM frequency of 9/10 and 3/11 to 30Hz without messing with the system prescaler. And if you need even lower, PWM in software is very feasible. :slight_smile:

MarkT,

I have trouble understanding your answers (long time Newbie)
so be patient.

  1. If I change to long int constant, what does it mean ? please don't laugh
  2. I have Arduino Uno should I change double to float

thank you

MarkT:

  unsigned long t_div = 60000;  // for accelerated testing, 1000ms/s * 60s/min = 60000 ms/min

use 60000L if you want a long int constant.

double Setpoint = 100, Input = 20, Output, PV; // degrees Fahrenheit, Celsius or anything else

double PIDSAMPLETIME_MS = 1000 ;  // milliseconds
double Kp = 0.1, Ki = 0.3, Kd = 0.5, K = 1; // K is fudge factor for Output Gain or Thermal Resistance



On ATmega's double means the same as float.

I would like to get PWM frequency lower than 30Hz. Around 1-4 Hz. I'm controlling SSR
and I would like to minimize Zero Crossing Issues

septillion:
Okay, just saying what the cause is.

And as a bonus, you can lower the PWM frequency of 9/10 and 3/11 to 30Hz without messing with the system prescaler. And if you need even lower, PWM in software is very feasible. :slight_smile:

  1. It's not really wrong but you could have used 60000UL instead of 't_div'. Although I do like the readability of having the variable.

  2. No need to change it, only don't expect a double to be different from a float.

And 4Hz is sooooo slow. Just time it in software using millis() :slight_smile:

novio8:
I would like to get PWM frequency lower than 30Hz. Around 1-4 Hz.

As mentioned, you can do that in firmware without messing with the hardware. A millis-based approach is more than adequate. Read this: Using millis() for timing. A beginners guide - Introductory Tutorials - Arduino Forum

MarkT:

  unsigned long t_div = 60000;  // for accelerated testing, 1000ms/s * 60s/min = 60000 ms/min

use 60000L if you want a long int constant.

You don't need the L as long as everything there is too big to fit in 16 bit. You only need the L when you have a bunch of 16 bit values and some intermediate or final value that needs 32 bits. This line is fine without the L. The compiler knows 60000 isn't an int.

If you wrote

  unsigned long t_div = 60 * 1000;

Then you would get the wrong answer (some negative number) unless you put an L.

MarkT:

  unsigned long t_div = 60000;  // for accelerated testing, 1000ms/s * 60s/min = 60000 ms/min

use 60000L if you want a long int constant.

Delta_G:
Then you would get the wrong answer (some negative number) unless you put an L.

Missed it by one letter.

For that calculation it makes no difference. But yeah, you get the same problem again when you increase the numbers again. So better to do the calculation in the same type as what you store it aka UL.

gfvalvo:
As mentioned, you can do that in firmware without messing with the hardware. A millis-based approach is more than adequate. Read this: https://forum.arduino.cc/index.php?topic=503368.0

For two days I’m tryng to change my Code from PWM to millis, no success
or there is a simple way that I’m missing

#include <LiquidCrystal.h>
#include "max6675.h"
#include "PID_v1R.h"  // similar to standard PID_v1, this custom library is required for full functionality
#include <avr/power.h>

#define CLK_DIV 8    // 8
// define 2 exceptions (temp and pid). TEMP_ALARM is fatal - program end.
#define TEMP_ALARM_MIN -10 // exceeds min temp limit [F]
#define TEMP_ALARM_MAX 300 // exceeds max temp limit [F]

/* PID limits should reflect actual physical capability. */
#define PID_MIN  0  // exceeds min PID limit
#define PID_MAX  255  // exceeds max PID limit

int TargetTemp = 0;
unsigned long currentTime = 0;
unsigned long startoffset;

#define TEMP_CORRECTION_OFFSET -0.0 // Thermocouple fudge factor [Fahrenheit]

// MAX6675 + Thermocouple convert Arduino Analog pins to power and communication.
// K-thermocouple only, 0-1024C range, 12-bit, 0.25C resolution, ~250K conversions/sec. max.
int thermoD0 =  15; // A1
int thermoCS =  16; // A2
int thermoCLK = 17; // A3
int vccPin =    18; // A4
int gndPin =    19; // A5

// PWM on 2 pins (+/-) using Timer 2 (16 bits)
int PWM_Pin_plus = 11;
int PWM_Pin_minus = 3;

// PID variables we connect to
double Setpoint = 100, Input = 20, Output, PV; // degrees Fahrenheit, Celsius or anything else
double PIDSAMPLETIME_MS = 1000 ;  // milliseconds
double Kp = 0.1, Ki = 0.3, Kd = 0.5, K = 1; // K is fudge factor for Output Gain or Thermal Resistance

unsigned long stepN;

// menu
int keypad_pin = A0;
int keypad_value = 0;
int keypad_value_old = 0;
char btn_push;
short mainMenuPage = 1;         // byte
short mainMenuPageOld = 1;      // byte
const int mainMenuTotal = 6;    // 5 + 1

typedef struct {                     // 5 steps in temperature profile
  char *desc;                        // description 16 char max.
  double Kp, Ki, Kd;                 // PID parameters
  double T1, T2, T3, T4, T5;         // T temperature
  unsigned long t1, t2, t3, t4, t5;  // t time [integer minutes]
} profile;

profile profiles[mainMenuTotal];
void t_PID(profile *r, double *T, unsigned long *t);

// create the objects
PID myPID(&Input, &Output, &Setpoint, Kp, Ki, Kd, DIRECT);
MAX6675 thermocouple(thermoCLK, thermoCS, thermoD0);
LiquidCrystal lcd(8, 9, 4, 5, 6, 7);  // initialize the LCD library with the numbers of the interface pins

void setup() {
  clock_prescale_set(clock_div_8);
  lcd.begin(16, 2);     // LCD's number of columns and rows
  lcd.setCursor(0, 0);  // Print a message to the LCD
  lcd.print("PID Temp Control");

  // PID setup
  myPID.SetMode(AUTOMATIC);
  myPID.SetSampleTime(PIDSAMPLETIME_MS);
  myPID.SetOutputLimits(PID_MIN, PID_MAX);   // True PID heating and cooling. Should reflect actual physical limits

  // MAX6675 Temp. measurement setup
  pinMode(gndPin, OUTPUT); digitalWrite(gndPin, LOW);   // GND A5
  pinMode(vccPin, OUTPUT); digitalWrite(vccPin, HIGH);  // VCC A4
  delay(1000 / CLK_DIV);

  // phase-correct PWM proportional output,  Timer 2 on pins 3, 11
  TCCR2A = _BV(COM2A1) | _BV(COM2B1) | _BV(WGM20);
  //TCCR2B = _BV(CS22);
  //TCCR2B = TCCR2B & B11111000 | B00000001;    // set timer 2 divisor to     1 for PWM frequency of 31372.55 Hz
  //TCCR2B = TCCR2B & B11111000 | B00000010;    // set timer 2 divisor to     8 for PWM frequency of  3921.16 Hz
  //TCCR2B = TCCR2B & B11111000 | B00000011;    // set timer 2 divisor to    32 for PWM frequency of   980.39 Hz
  //TCCR2B = TCCR2B & B11111000 | B00000100;    // set timer 2 divisor to    64 for PWM frequency of   490.20 Hz  <-- default
  //TCCR2B = TCCR2B & B11111000 | B00000101;    // set timer 2 divisor to   128 for PWM frequency of   245.10 Hz
  //TCCR2B = TCCR2B & B11111000 | B00000110;    // set timer 2 divisor to   256 for PWM frequency of   122.55 Hz
  TCCR2B = TCCR2B & B11111000 | B00000111;    // set timer 2 divisor to  1024 for PWM frequency of    30.64 Hz
  pinMode(PWM_Pin_plus,  OUTPUT);
  pinMode(PWM_Pin_minus, OUTPUT);

  // Serial
  Serial.begin(2400 * CLK_DIV);  // Change Serial Monitor tool datarate to 2400
  Serial.println("Arduino PID Temperature Control");

  // User Edit    "desc"    ,   Kp,  Ki,  Kd, T1,  T2,  T3,  T4,  T5,  t1,  t2,  t3,  t4,  t5 [t in integer minutes]
  profiles[0] = {"Default"  ,  0.1, 0.3, 0.5,  80,   0,   0,   0,   0, 72000, 0,   0,   0,   0};
  profiles[1] = {"Profile 1",  1.5, 0.3, 0.1, 130,  150, 120,  130,  150,  1,  1,  1,  1,  1};
  profiles[2] = {"Profile 2",  0.2, 0.3, 0.7, 160,   180,  200,  210,  0,   5,  3,  2,  1,   5};
  profiles[3] = {"Profile 3",  0.2, 0.4, 0.5, 20,   1,  30,  10,  40,   1,  50,  10,  60,   1};
  profiles[4] = {"Profile 4",  0.2, 0.4, 0.6, 20,   5,  30,   1,  40,   5,  50,   1,  60,   5};
  profiles[5] = {"Profile 5",  0.2, 0.4, 0.6, 30, 35,  30,  40,  30,   1,  1,   1,  1,   1};
}

void loop() {
  btn_push = ReadKeypad();
  WaitBtnRelease();
  MainMenuBtn();

  if (btn_push == 'S') // enter selected profile
  {
    startoffset = millis();
    myPID.SetTunings(profiles[mainMenuPage].Kp, profiles[mainMenuPage].Ki, profiles[mainMenuPage].Kd);
    t_PID(&profiles[mainMenuPage], &profiles[mainMenuPage].T1, &profiles[mainMenuPage].t1 );
    t_PID(&profiles[mainMenuPage], &profiles[mainMenuPage].T2, &profiles[mainMenuPage].t2 );
    t_PID(&profiles[mainMenuPage], &profiles[mainMenuPage].T3, &profiles[mainMenuPage].t3 );
    t_PID(&profiles[mainMenuPage], &profiles[mainMenuPage].T4, &profiles[mainMenuPage].t4 );
    t_PID(&profiles[mainMenuPage], &profiles[mainMenuPage].T5, &profiles[mainMenuPage].t5 );
    PWM_OUT(0);   // turn off forcing functions
  }
  MainMenuDisplay();

} // close loop()

void t_PID(profile *r, double *T, unsigned long *t) {
  unsigned long startTime = millis();  
  unsigned long t_div = 60000;  // for accelerated testing, 1000ms/s * 60s/min = 60000 ms/min
  myPID.mySetpoint = T;
  TargetTemp = *T;


  while ((millis() - startTime) * CLK_DIV / t_div < *t) {
    PV = thermocouple.readCelsius() + TEMP_CORRECTION_OFFSET;  // Process Value (current temperature)
    Input = PV;  // uncomment to use for live operation

    if (Input <= TEMP_ALARM_MIN || Input >= TEMP_ALARM_MAX) {
      temp_alarm(Input);
    }
    LCD_update(); SER_update();  // update the displays
    myPID.Compute();
    if (Output <= PID_MIN || Output >= PID_MAX) {
      pid_error();
    }
    Input = Input + K * Output;
    PWM_OUT(K * Output);   // update the control outputs
    stepN++;
    delay(PIDSAMPLETIME_MS / CLK_DIV);
  }
}

void LCD_update() {
  currentTime = (millis() - startoffset) / 125;

  lcd.clear();
  lcd.setCursor(0, 0);
  lcd.print(currentTime / 60); // AA Minutes
  lcd.print(":");
  lcd.print((currentTime % 60) / 10); // AA Seconds
  lcd.print((currentTime % 60) % 10);  // AA Seconds
  lcd.print(" I=");
  lcd.print(myPID.GetITerm(), 0);
  lcd.print(" D=");
  lcd.print(myPID.GetDTerm(), 0);
  lcd.setCursor(0, 1);
  lcd.print("T=");
  lcd.print(Input, 1);
  lcd.print("C");
  lcd.print(" ST=");
  lcd.print(TargetTemp, 1);
  delay(100);
}

void SER_update() {


  Serial.print("step\t sec.\t PV\t in\t out\t ITerm\t DTerm\t Target\t \n");
  Serial.print(stepN);
  Serial.print("\t");
  Serial.print(currentTime);  // seconds
  Serial.print("\t");
  Serial.print(PV);
  Serial.print("\t");
  Serial.print(Input);
  Serial.print("\t");
  Serial.print(Output);
  Serial.print("\t");
  Serial.print(myPID.GetITerm());
  Serial.print("\t");
  Serial.print(myPID.GetDTerm());
  Serial.println("\t");
  Serial.print(TargetTemp);
  Serial.println("\n");
}

Try working out the concept in a MCVE first. If you get how to do it it's probably easy to implement :slight_smile: