Arduino-MATLAB communication using SerialEvent() for PID Loop

Click For Summary
SUMMARY

The discussion focuses on the integration of Arduino and MATLAB for controlling a Peltier device using a PID controller. The user reports erratic temperature behavior when setting values from MATLAB, specifically temperature overshooting and oscillation. The Arduino code utilizes the SerialEvent() function for communication, but the user experiences delays that affect performance. Recommendations include optimizing the Arduino code by reducing the use of the pow() function and adjusting the MATLAB loop to read temperature data more frequently.

PREREQUISITES
  • Understanding of Arduino programming and libraries, specifically PID_v1 and Adafruit_MCP4725.
  • Familiarity with MATLAB serial communication and data handling.
  • Knowledge of PID control theory and its application in temperature regulation.
  • Basic electronics knowledge, particularly regarding thermistors and DACs.
NEXT STEPS
  • Optimize Arduino code by replacing pow() function calls with direct multiplication for performance improvement.
  • Implement a more efficient serial reading method in Arduino to handle incoming data in bulk.
  • Modify the MATLAB loop to reduce the pause duration for more responsive temperature readings.
  • Explore advanced PID tuning techniques to stabilize the temperature control system.
USEFUL FOR

Embedded systems developers, MATLAB users interfacing with Arduino, and engineers working on temperature control applications using PID algorithms.

troubled_grad
Messages
1
Reaction score
1
Hello,

I have an arduino code for driving a peltier pile to a given set point (TEMP_SP). I am trying to input the set temperature from Matlab GUI (with different protocols, user will enter n temperatures and the time intervals, arduino will implement the PID controller and output the temperature to MATLAB every 1 seconds via serial communication.

I have a serious problem and I would appreciate any help.

My PID is working with arduino, I can send and receive data to/from matlab/arduino. I am using SerialEvent() to read the messages from MATLAB and print the responses accordingly.

The problem is that, (maybe because there is a huge delay between MATLAB and arduino), when I set a temperature from matlab, my peltier temperature jumps up and down crazily. E.g. I set 20 degrees celsius, my peltier jumps up to 35-40 degrees, then toggles around lower temperatures that are not super related to my set temperature. This does not happen when I run the PID on arduino and set the temperature directly from there.

PS: I am reading the temperature using a thermistor. I am changing the voltage using an external DAC and a shifter circuit because I need to both heat up and cool down ( the peltier terminals should change signs)

Algorithm:
-Set temperature (TEMP_SP)
-Read thermistor voltage-->convert to resistance-->convert to temperature (this is current state, T_C_CS)
-Find DAC output from PID
-SerialEvent() runs after every loop() to check if there's message coming from MATLAB and sets its flag accordingly.
-In each loop, check stringComplete flag and change the set temperature or print the current temperature.

This is my Arduino code:
C:
#include <Wire.h>
#include <Adafruit_MCP4725.h>
#include <PID_v1.h>
Adafruit_MCP4725 dac; // constructoruint32_t dac_value;// Thermistor variables
float Rref=10000; //ohms, resistance of thermistor at 25C
float B25=3977;
float TOLB=0.75;
float A=-14.6337;
float B=4791.842;
float C=-115334;
float D=-3.730535E+06;
float A1t=3.354016E-03;
float B1t=2.569850E-04;
float C1t=2.620131E-06;
float D1t=6.383091E-08;
float Vdd=5; //volts
float R_divider=10000;//ohms

//CURRENT STATE
float Vt_CS_val; //current thermistor voltage value
float Vt_CS; //current thermistor voltage
float Rt_CS;//current thermistor resistance
float T_K_CS;  //current temperature in K
float T_C_CS;  //current temperature in C
float input_CS_val;// current input from the DAC
float input_CS;// current input from the DAC

//SET POINT
float TEMP_SP=20; //desired temperature (celsius)
float TEMP_SP_K; //desired temperature (Kelvin)
float R_SP; //desired resistance for the TEMP_SP_K
float Vt_SP; //desired thermistor voltage//DAC output
float DAC_val;
float DAC_OUT;//DAC output voltage
//double PID_OUT;//Custom PID
float der = 0;
float err;
float err_p;
float integral;

//PID set
float Kp=1.0, Ki=0.003, Kd=10.0;
float output;
int state=1;

float read_voltage;
float temp_rec;
float temp_new=20;String inputString = "";         // a string to hold incoming data

String READ = "R";
boolean stringComplete = false;  // whether the string is completevoid setup() {
    Serial.begin(9600);
 
     dac.begin(0x62); // The I2C Address: Run the I2C Scanner if you're not sure
     DAC_val=2080;//2.54V
     dac.setVoltage(DAC_val, false); //819:1V,  3276:4V
  
    // reserve 200 bytes for the inputString:
    inputString.reserve(200);
 
    Serial.println('a');
    char a = 'b';
    while(a != 'a')
    {
      a = Serial.read();
    }
}

void loop() {
 

            Vt_CS_val = analogRead(A1);
            Vt_CS=5.0*Vt_CS_val/1024.0; //current voltage on thermistor, current temp
            Rt_CS=R_divider*Vt_CS/(Vdd-Vt_CS);
         
        
            //Given R, find T
            T_K_CS=pow((A1t+B1t*log(Rt_CS/Rref)+C1t*pow((log(Rt_CS/Rref)),2)+D1t*pow((log(Rt_CS/Rref)),3)),-1); //KELVIN
            T_C_CS=T_K_CS-273.15; //current celsius
     
            ///////PROCESS SET POINT
   
            TEMP_SP_K=TEMP_SP+273.15; //celsius to K
            //Given T_SP, find R_SP
            R_SP=Rref*exp(A+B/TEMP_SP_K+C/pow(TEMP_SP_K,2)+D/pow(TEMP_SP_K,3)); // R_SP
         
            // find Vt_SP
         
            Vt_SP=R_SP*Vdd/(R_SP+R_divider); //Set voltage for thermistor divider. Aim: Vt_SP=Vt_CS
         
            err = Vt_SP - Vt_CS;
            integral = integral + err;
         
            output = Kp * err + (Ki * integral) + (Kd * der );
            err_p = err;
         
            DAC_OUT = -output + 2.5;
     
             if (DAC_OUT< 1)
             DAC_OUT = 1;
             if (DAC_OUT > 4)
             DAC_OUT = 4;
           
             DAC_val=4096*DAC_OUT/5;

             dac.setVoltage((int)DAC_val, false);

            // print the answer when message arrives:

            if (stringComplete) {
           
              inputString.trim(); //remove newline characters or blanks
                          
              if( inputString==READ){
             
                  Serial.println(T_C_CS); //print the current temp
              
              }
              else //number came, take as temperature
              {
                temp_new=inputString.toFloat(); //set new temperature

                TEMP_SP=temp_new;

                Serial.println(TEMP_SP); //send the set temp back
           
              }
                  inputString = "";
                  stringComplete = false;
            }
}void serialEvent() {
 
  while (Serial.available()) {
    // get the new byte:
    char inChar = (char)Serial.read();
    // add it to the inputString:
    inputString += inChar;
    // if the incoming character is a newline, set a flag
    // so the main loop can do something about it:
    if (inChar == '\n') {
      stringComplete = true;
    }
  }
}
Here is my MATLAB code:
Matlab:
%open serial object (working)

delete(instrfind({'Port'},{'COM11'}));%clear the port
%global ard;
if(~exist('serialFlag','var'))
    [ard,serialFlag] = setupSerial('COM11');
end
disp('Connection successful');

isvalid=isvalid(ard);

%send set temperature (I am getting 28.00 back as TEMP_SP, so working)
x=28;
fprintf(ard,num2str(x));
msg=fscanf(ard);
disp(msg);

%read the temperature back every 3 seconds
while(1)
 
    fprintf(ard,'R');
    msg=fscanf(ard);
    disp(msg);
    pause(3);
end
 
Last edited by a moderator:
  • Like
Likes   Reactions: berkeman
Some thoughts, for what they're worth -- I don't have any experience working with Arduino.

You could speed up your Arduino code be replaced the calls to pow().
C:
T_K_CS=pow((A1t+B1t*log(Rt_CS/Rref)+C1t*pow((log(Rt_CS/Rref)),2)+D1t*pow((log(Rt_CS/Rref)),3)),-1); //KELVIN
.
.
.
R_SP=Rref*exp(A+B/TEMP_SP_K+C/pow(TEMP_SP_K,2)+D/pow(TEMP_SP_K,3)); // R_SP
I count 5 calls to pow() in the two lines above. In all of them you are either squaring a number or cubing it. Calls to pow() are much more expensive in terms of processor time than is ordinary multiplication.

For example, instead of doing this: result = pow(x, 3)
I would do this: result = x * x * x

I would also split both of the lines above into maybe five or six lines, using temp variables. Having such complicated calculations makes debugging a lot harder.

Also in the Arduino code you have this:
C:
while (Serial.available()) {
    // get the new byte:
    char inChar = (char)Serial.read();
    // add it to the inputString:
    inputString += inChar;
    // if the incoming character is a newline, set a flag
    // so the main loop can do something about it:
    if (inChar == '\n') {
      stringComplete = true;
    }
  }
Instead of reading one byte at a time using Serial.read(), could you read an entire line all at once? I'm guessing that there is a readln() function. Of course that would mean you would have to parse the line for its contents.

Finally, in your MATLAB code
Matlab:
%read the temperature back every 3 seconds
while(1)
   
     fprintf(ard,'R');
     msg=fscanf(ard);
     disp(msg);
     pause(3);
end[/quote]
Would it help the read the temperature more often? Three seconds is a very long time in terms of CPU operations. Your MATLAB code appears to be a message loop, reading messages and then dispatching them. If it is controlling what's happening in the Arduino, making it wait 3 seconds is probably way too long. Possibly you want the message loop to pause only a small fraction of a second (like a millisecond or maybe less), not 3 whole seconds.

Hope this helps.