Making a PID code work with serial I2C input data instead of analog.
in
Integration and Hardware
•
8 months ago
Hello! Check out this PID code I have working at the moment with an analog input on an arduino microcontroller and a LCD to give some feedback.
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 button and switch readings. (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/coolerconst int upPin = 6; // pin to increase tempconst int downPin = 7; //pin to decrease tempint buttonState = 0; // variable for reading the pin statusint 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 parametersPID myPID(&Input, &Output, &Setpoint, aggKp, aggKi, aggKd, DIRECT);
void setup(){Serial.begin( 19200 );//initialize the variables we're linked toInput = analogRead(0);LoadEEPROM();if( (Setpoint +1.) < 0.001)Setpoint = 199;// Setpoint = xxx;
// Declare inputspinMode(onPin, INPUT); // declare pushbutton as inputpinMode(upPin, INPUT); // declare pushbutton as inputpinMode(downPin, INPUT); // declare pushbutton as input
//turn the PID on STANDBYmyPID.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 - emptyif( 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 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 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 busPORTC = (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);// readi2c_rep_start(dev+I2C_READ);data_low = i2c_readAck(); //Read 1 byte and then send ackdata_high = i2c_readAck(); //Read 1 byte and then send ackpec = i2c_readNak();i2c_stop();//This converts high and low bytes together and processes temperature, MSB is a error bit and is ignored for tempsdouble tempFactor = 0.02; // 0.02 degrees per LSB (measurement resolution of the MLX90614)double tempData = 0x0000; // zero out the dataint 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 parametersPID myPID(&Input, &Output, &Setpoint, aggKp, aggKi, aggKd, DIRECT);
LiquidCrystal lcd(12, 11, 8, 3, 2, 1);
const int onPin = 9; //pin to trigger heaterconst int upPin = 6; // pin to increase tempconst int downPin = 7; //pin to decrease tempint buttonState = 0; // variable for reading the pin statusint 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 inputspinMode(onPin, INPUT); // declare pushbutton as inputpinMode(upPin, INPUT); // declare pushbutton as inputpinMode(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 intotemperature 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 - emptyif( 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;
// Writei2c_start_wait(dev+I2C_WRITE);i2c_write(0x07);
// Readi2c_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 dataint 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!
1