Modify a PID code work with serial I2C data VS. analog

Hello! Check out this PID Thermostat code I have working at the moment with an analog input on an arduino microcontroller and a LCD to give some temp readings and display the setpoint.

The components the current code controls/reads are :

-a 2x16 LCD

-a temperature reading via voltage on the analog input (0), usually fed from a thermocouple sensor.

-a few readings from some buttons and switches . (That change the setpoint and turn the controller on/off)

-a output signal, from the arduino, to a mechanical or solid state relay that controls whatever heating or cooling device you’re playing with.

So here’s my present code with the analog input:

#include <EEPROM.h>
#include <LiquidCrystal.h>
#include <PID_v1.h> 
#include <SPI.h>

double Setpoint, Input, Output;
double aggKp=2, aggKi=1, aggKd=0, LoLIM=15, HiLIM=255; 
double consKp=1, consKi=4, consKd=3;
double SampleTime=250;
LiquidCrystal lcd(12, 11, 8, 4, 3, 2);

const int onPin = 5;     //pin to trigger heater/cooler
const int upPin = 6;     //  pin to increase temp
const int downPin = 7;   //pin to decrease temp
int buttonState = 0;     // variable for reading the pin status
int DownPushed = 0;
int UpPushed = 0;
int OnPushed = 0;
int PID_ON = 0;
int UpdScr = 0;
int inpt;
int gap;
int S_gap;
int count;
int EEPROM_ADDR;

void UpdateEEPROM() {

  int val, wr;
  val = (int) Setpoint;
  wr = val>>8;
  EEPROM.write(EEPROM_ADDR, wr);
  wr = val & 0XFF;
  EEPROM.write(EEPROM_ADDR+1, wr);

};

void LoadEEPROM() {
  int val;
  val = EEPROM.read(EEPROM_ADDR);
  val = val <<8;
  val +=EEPROM.read(EEPROM_ADDR+1);
  Setpoint = val;

};
unsigned long lastTime, lShowTime; 

//Specify the links and initial tuning parameters
PID myPID(&Input, &Output, &Setpoint, aggKp, aggKi, aggKd, DIRECT);


void setup()
{
  Serial.begin( 19200 );
  //initialize the variables we're linked to
  Input = analogRead(0);
  LoadEEPROM();
  if( (Setpoint +1.) < 0.001)
    Setpoint = 199;
  //    Setpoint = xxx;   

  // Declare inputs
  pinMode(onPin, INPUT);    // declare pushbutton as input
  pinMode(upPin, INPUT);    // declare pushbutton as input
  pinMode(downPin, INPUT);    // declare pushbutton as input

  //turn the PID on STANDBY
  myPID.SetOutputLimits(LoLIM, HiLIM);
  myPID.SetMode(AUTOMATIC);
  myPID.SetSampleTime(SampleTime);
  myPID.SetTunings(aggKp, aggKi, aggKd);

  lShowTime = lastTime = millis();

  // set up the LCD's number of columns and rows:
  lcd.begin(16,2);

  count = 0;
  EEPROM_ADDR = 30;
  
  //lcd.clear();
  lcd.print("tstat");
  lcd.setCursor( 0,1);
  lcd.print(aggKp);
  lcd.print("-");
  lcd.print((int)(aggKi) );
  lcd.print("-");
  lcd.print(aggKd);
  delay(2000);
  lcd.clear();
lcd.print("S_Rate = ");
lcd.print((int)(SampleTime));
lcd.print(" ms");
delay(1000);
   lcd.clear();
  lcd.print("Limits ");
  lcd.print((int)(LoLIM));
  lcd.print(",");
   lcd.print((int)(HiLIM));
    delay(1000);
lcd.clear();
}

void loop()
{
  Input = analogRead(0);



  buttonState = digitalRead(onPin);

  if (buttonState == HIGH) {
    // turn HEATER on, but prime first:
    myPID.SetMode(AUTOMATIC);
    PID_ON =1;                
  } 
  else {
    myPID.SetMode(MANUAL);
    Output = 0;
    PID_ON =0;
  }

  myPID.Compute();
  analogWrite(10,Output);
  // I would not change these lines, because you are expecting 250 ms for a "push"
  // that is if you hold button for more then 1/4 second, 
  if(digitalRead(upPin)==HIGH) {
    if (millis()-lastTime >= 250) {
      Setpoint+=5;
      UpdateEEPROM();
      lastTime=millis();
      UpdScr = 1;
    }
  }

  if(digitalRead(downPin)==HIGH) {
    if (millis()-lastTime >= 250) {
      Setpoint-=5;
      UpdateEEPROM();
      lastTime=millis();
      UpdScr = 1;
    }
  }

  //and output to LCD

  if( (millis() - lShowTime > 100 ) || UpdScr ) {
    UpdScr = 0;
    lcd.setCursor(0,0);
    //if heater is on - show *
    //if not - empty
    if( PID_ON ==1 ) {
      lcd.print("*");
    }
    else {
      lcd.print(" ");
    }
    lcd.print("SET.TEMP-> ");
    lcd.print((int)(Setpoint) );
    lcd.setCursor( 0,1);
    lcd.print(" AIR.TEMP-> ");
    inpt = (inpt *5 + Input)/6;
    count = (count +1)%6;

    if(count == 0) {
      if( inpt < 100) lcd.print( " ");
      lcd.print( inpt );
    }


    lShowTime = millis();  
  }

}

The code works fantastic with everything right now. However, what I am trying to do now is make the PID code able to work with serial data that this new infrared temp sensor that I have outputs, instead of using the old analog temp signal that was fed into the analog(0) input. A friend suggested I refactor the input portion of code… “restructuring the existing body of code, altering its internal structure without changing its external behavior”.

He said, “create and use a function that returns the input reading” kind of like so:

static double GetInput( void )

{

return( analogRead( 0 ) );

}

and also said," Replace this (appears twice in your sketch)…"

Code:

Input = analogRead(0);

…with this…

Code:

Input = GetInput();

So… I guess I need to refactor my old PID code to dance with the new sensor that uses the “serial I2C communication” (aka Two Wire Interface).

Here’s a sample of a code the new sensor is supposed to work with:

#include <i2cmaster.h>


void setup(){
   Serial.begin(9600);
   Serial.println("Setup...");
   
   i2c_init(); //Initialise the i2c bus
   PORTC = (1 << PORTC4) | (1 << PORTC5);//enable pullups
}

void loop(){
    int dev = 0x5A<<1;
    int data_low = 0;
    int data_high = 0;
    int pec = 0;
    
    i2c_start_wait(dev+I2C_WRITE);
    i2c_write(0x07);
    
    // read
    i2c_rep_start(dev+I2C_READ);
    data_low = i2c_readAck(); //Read 1 byte and then send ack
    data_high = i2c_readAck(); //Read 1 byte and then send ack
    pec = i2c_readNak();
    i2c_stop();
    
    //This converts high and low bytes together and processes temperature, MSB is a error bit and is ignored for temps
    double tempFactor = 0.02; // 0.02 degrees per LSB (measurement resolution of the MLX90614)
    double tempData = 0x0000; // zero out the data
    int frac; // data past the decimal point
    
    // This masks off the error bit of the high byte, then moves it left 8 bits and adds the low byte.
    tempData = (double)(((data_high & 0x007F) << 8) + data_low);
    tempData = (tempData * tempFactor)-0.01;
    
    float celcius = tempData - 273.15;
    float fahrenheit = (celcius*1.8) + 32;

    Serial.print("Celcius: ");
    Serial.println(celcius);

    Serial.print("Fahrenheit: ");
    Serial.println(fahrenheit);

    delay(1000); // wait a second before printing again
}

from the sensor guide: http://bildr.org/2011/02/mlx90614-arduino/

One of the first differences I notice is the command indication the serial communication speed :

My code —> “Serial.begin( 19200 );”

Sensor Example—> “Serial.begin( 9600 );”

Here’s the modification that was suggested but I can’t seem to get it to work:

/**
* Infrared Thermometer MLX90614
* by Jaime Patarroyo
* based on 'Is it hot? Arduino + MLX90614 IR Thermometer' by bildr.blog
* 
* Returns the temperature in Celcius and Fahrenheit from a MLX90614 
* Infrared Thermometer, connected to the TWI/I²C pins.
*/


#include <i2cmaster.h>
#include <EEPROM.h>
#include <LiquidCrystal.h>
#include <PID_v1.h> 
#include <SPI.h>
  
double Setpoint, Input, Output;
double aggKp=1, aggKi=3, aggKd=0; 
double consKp=0.5, consKi=5, consKd=3;
double LoLIM=5, HiLIM=255; 
double SampleTime=250;

//Specify the links and initial tuning parameters
PID myPID(&Input, &Output, &Setpoint, aggKp, aggKi, aggKd, DIRECT);



LiquidCrystal lcd(12, 11, 8, 3, 2, 1);

const int onPin = 9;     //pin to trigger heater
const int upPin = 6;     //  pin to increase temp
const int downPin = 7;   //pin to decrease temp
int buttonState = 0;     // variable for reading the pin status
int DownPushed = 0;
int UpPushed = 0;
int OnPushed = 0;
int PID_ON = 0;
int UpdScr = 0;
int inpt;
int gap;
int S_gap;
int count;
int EEPROM_ADDR;

void UpdateEEPROM() {

  int val, wr;
  val = (int) Setpoint;
  wr = val>>8;
  EEPROM.write(EEPROM_ADDR, wr);
  wr = val & 0XFF;
  EEPROM.write(EEPROM_ADDR+1, wr);

};

void LoadEEPROM() {
  int val;
  val = EEPROM.read(EEPROM_ADDR);
  val = val << 8;
  val |=EEPROM.read(EEPROM_ADDR+1);
  Setpoint = val;

};
unsigned long lastTime, lShowTime; 


  int deviceAddress = 0x5A<<1;    // From MLX906114 datasheet's, 0x5A is 
                                // the default address for I²C communication.
                                // Shift the address 1 bit right, the 
                                // I²Cmaster library only needs the 7 most 
                                // significant bits for the address.

void setup() {
  Serial.begin(19200);           // Start serial communication at 19200bps.

  i2c_init();                               // Initialise the i2c bus.
  PORTC = (1 << PORTC4) | (1 << PORTC5);    // Enable pullups.
  
  Input = GetInput();
  LoadEEPROM();
  if( (Setpoint +1.) < 0.001)
    Setpoint = 199;
  //    Setpoint = xxx;   

  // Declare inputs
  pinMode(onPin, INPUT);    // declare pushbutton as input
  pinMode(upPin, INPUT);    // declare pushbutton as input
  pinMode(downPin, INPUT);    // declare pushbutton as input
  
  //turn the PID on STANDBY.   When triggered, IDEALLY start the output at its max and let the PID adjust it from there.. This STILL needs to be added to the code.
  myPID.SetOutputLimits(0, 255);
  myPID.SetMode(AUTOMATIC);
  myPID.SetSampleTime(SampleTime);
  myPID.SetTunings(aggKp, aggKi, aggKd);

  lShowTime = lastTime = millis();

  // set up the LCD's number of columns and rows:
  lcd.begin(16,2);

  count = 0;
  EEPROM_ADDR = 30;

  lcd.print("Settings");
  lcd.setCursor( 0,1);
  lcd.print(aggKp);
  lcd.print("-");
  lcd.print((int)(aggKi) );
  lcd.print("-");
  lcd.print(aggKd);
  delay(3000);
  lcd.clear();
  lcd.print("S_Rate = ");
  lcd.print((int)(SampleTime));
  lcd.print(" ms");
    delay(2000);
    lcd.clear();
}

void loop() {

  /* Read's data from MLX90614 with the given address, transform's it into 
    temperature in Celcius and store's it in the Celcius variable. */
  Input = GetInput( deviceAddress );

  buttonState = digitalRead(onPin);

  if (buttonState == HIGH) {
    myPID.SetMode(AUTOMATIC); //Turn on heat.  Adjust output acordinglly via myPIDs computations.  
    PID_ON =1;                
  } 
  else {
    myPID.SetMode(MANUAL); // Turn off heat. 
    Output = 0;
    PID_ON =0;
  }

  myPID.Compute();
  analogWrite(10,Output);
  
  
  
  // Setpoint control section.
  // I would not change these lines, because you are expecting 250 ms for a "push"
  // that is if you hold button for more then 1/4 second, 
  if(digitalRead(upPin)==HIGH) {
    if (millis()-lastTime >= 250) {
      Setpoint+=5;
      UpdateEEPROM();
      lastTime=millis();
      UpdScr = 1;
    }
  }

  if(digitalRead(downPin)==HIGH) {
    if (millis()-lastTime >= 250) {
      Setpoint-=5;
      UpdateEEPROM();
      lastTime=millis();
      UpdScr = 1;
    }
  }

  //and output to LCD

  if( (millis() - lShowTime > 100 ) || UpdScr ) {
    UpdScr = 0;
    lcd.setCursor(0,0);
    //if heater is on - show *
    //if not - empty
    if( PID_ON ==1 ) {
      lcd.print("*");
    }
    else {
      lcd.print(" ");
    }
    lcd.print("SET.TEMP-> ");
    lcd.print((int)(Setpoint) );
    lcd.setCursor( 0,1);
    lcd.print("SENS.TEMP-> ");
    inpt = (inpt *5 + Input)/6;
    count = (count +1)%6;

    if(count == 0) {
      if( inpt < 100) lcd.print( " ");
      lcd.print( inpt );
    }


    lShowTime = millis();  
  }

}

static float GetInput(int address) {
  int dev = address;
  int data_low = 0;
  int data_high = 0;
  int pec = 0;

  // Write
  i2c_start_wait(dev+I2C_WRITE);
  i2c_write(0x07);

  // Read
  i2c_rep_start(dev+I2C_READ);
  data_low = i2c_readAck();       // Read 1 byte and then send ack.
  data_high = i2c_readAck();      // Read 1 byte and then send ack.
  pec = i2c_readNak();
  i2c_stop();

  // This converts high and low bytes together and processes temperature, 
  // MSB is a error bit and is ignored for temps.
  double tempFactor = 0.02;       // 0.02 degrees per LSB (measurement 
                                  // resolution of the MLX90614).
  double tempData = 0x0000;       // Zero out the data
  int frac;                       // Data past the decimal point

  // This masks off the error bit of the high byte, then moves it left 
  // 8 bits and adds the low byte.
  tempData = (double)(((data_high & 0x007F) << 8) + data_low);
  tempData = (tempData * tempFactor)-0.01;
  float celcius = tempData - 273.15;
  
  // Returns temperature un Celcius.
  return celcius;
}

Any ideas? Thanks in advance!

Hi I am a complete noob at this but I am trying to do something very similar to your project. I want to use the Mx90614 to control a cooling fan, preferably with a PID control. The item to be cooled will have quite a large latent heat load since it is a stainless steel bar. I

Have you been able to get your controller to finally work with the MX90614 sensor?

Thanks 1Low37

I never did get it to work… I think because I found out that the sensor was faulty/ DOA. It never worked once.

I need to get a new one that’s guaranteed working and try again.

However, I did find another fantastic resource. check out http://www.element14.com

Its an awesome forum for stuff like this.

Found this thread while trying to create a PID heater controller. I was able to get the temperature sensor portion to work, however having some difficulty with the PID piece. As anyone any additional information on getting this code to work?

Went to Brett’s Blog and found lots of good input on the library, but looking more for the practical hardware implementation.

Thanks

Here’s what I just got working nicely with the aforementioned code. The arduino is receiving serial temperature data and the new LCD is also serial. I haven’t cleaned the code up much

#include <SoftwareSerial.h>
#include <SPI.h>
#include "Adafruit_MAX31855.h"
#include <PID_v1.h> 
// Attach the serial display's RX line to digital pin 2
SoftwareSerial mySerial(0,2); // pin 2 = TX, pin 3 = RX (unused)

int thermoCLK = 5;
int thermoCS = 4;
int thermoDO = 3;

// Initialize the Thermocouple
Adafruit_MAX31855 thermocouple(thermoCLK, thermoCS, thermoDO);

double Setpoint, Input, Output;
double Kp=4, Ki=1, Kd=2, LoLIM=10, HiLIM=255;
double SampleTime=250;
//Specify the links and initial tuning parameters
PID myPID(&Input, &Output, &Setpoint, Kp, Ki, Kd, DIRECT);

const int onPin = 8;     //pin to trigger heater
const int upPin = 9;     //  pin to increase temp
const int downPin = 11;   //pin to decrease temp
int buttonState = 0;     // variable for reading the pin status
int DownPushed = 0;
int UpPushed = 0;
int OnPushed = 0;
int PID_ON = 0;
int UpdScr = 0;
unsigned long lastTime, lShowTime; 

void clearLcd(){
  mySerial.write(254);
  mySerial.write(128); 
  mySerial.write("                "); // clear display
  mySerial.write("                ");
}

void goToStartOfLineOne(){
  mySerial.write(254);
   mySerial.write(128); 
}
void goToStartOfLineTwo(){
   mySerial.write(254);
   mySerial.write(192); 
}
void setup()
{
  mySerial.begin(9600); // set up serial port for 9600 baud
   
  Setpoint = 575;
  char tempstring[10], setstring[10]; // create string arrays

// Declare inputs
  pinMode(onPin, INPUT);    // declare pushbutton as input
  pinMode(upPin, INPUT);    // declare pushbutton as input
  pinMode(downPin, INPUT);    // declare pushbutton as input
 
  //turn the PID on STANDBY
  myPID.SetOutputLimits(LoLIM, HiLIM);
  myPID.SetMode(AUTOMATIC);
  myPID.SetSampleTime(SampleTime);
  myPID.SetTunings(Kp, Ki, Kd);

  goToStartOfLineOne();
  clearLcd();
  mySerial.write("Version 2.0");
  delay(1000);
  clearLcd();
  goToStartOfLineOne();
  mySerial.write("SetPoint= ");
  goToStartOfLineTwo(); 
  mySerial.write("AirTemp=");
}

char tempstring[10], setstring[10]; // create string arrays

void loop()
{
  int set = Setpoint;
  int temp = thermocouple.readFarenheit(); // read temp of TC in degrees F
  Input = thermocouple.readFarenheit();   
    
    buttonState = digitalRead(onPin);

  if (buttonState == LOW) {
    // turn HEATER on:
    myPID.SetMode(AUTOMATIC);
    PID_ON =1;   
  } 
  else {
    myPID.SetMode(MANUAL);
    Output = 0;
    PID_ON =0;
  }

  myPID.Compute();
  analogWrite(5,Output);
  
    if( (millis() - lShowTime > 100 ) || UpdScr ) {
    UpdScr = 0;
    mySerial.write(254); // cursor to 7th position on first line
    mySerial.write(143);
   if( PID_ON ==1 ) {
       mySerial.write("*");
    }
    else {
      mySerial.write(" ");
    }
    
    lShowTime = millis();
    }
  int temp_val = thermocouple.readFarenheit(); 

  sprintf(tempstring,"%4d",set); // create strings from the numbers
  sprintf(setstring,"%4d",temp_val); // right-justify to 4 spaces
   
    mySerial.write(254); // cursor to 7th position on first line
    mySerial.write(137);

    mySerial.write(tempstring); // write out the TEMP value
    
    mySerial.write(254); // cursor to 7th position on second line
    mySerial.write(201);

    mySerial.write(setstring); // write out the TEMP value
    
 

  
  
}