Controlling Multiple PID Loops with One Arduino

My blog post that gets the most attention is by far the Arduino PID Temperature Controller, and I got a great suggestion from a reader about modifying that code to accommodate more than one PID loop in a single Arduino sketch.  While this is slightly more difficult than simply adding a second PID instance, it’s not too hard.  Let me explain.

I’ve started a new file on Github for this here.  It is still a work in progress, but I hope to finish it soon.

If you notice in the previous post, the run_PID function uses while loops to kill time in between switching the SSR_PIN (which is the Solid State Relay digital control switch, in case you’ve forgotten).  I’ve copied the relevant section of the original code below.

void run_PID(double Kp, double Ki, double Kd, uint16_t WindowSize, uint32_t time_interval)
{
   ...

   digitalWrite(SSR_PIN, 1);
   while(millis() - windowStartTime < time_interval * ratio);

   digitalWrite(SSR_PIN, 0);
   while(millis() - windowStartTime < time_interval);
}

This is fine for a single loop, but multiple loops couldn’t work this way.  If you had 2 copies of this running, the first instance would execute and then the second instance would execute, causing one instance to be asleep half the time.  This serial implementation would certainly mess up your loop timing.

The fix is to move the loop outside of this function and use if statements instead.  In this way, this run_PID function simply checks if the time is right to toggle SSR_PIN and doesn’t “block” other instances.  The proposed revision is below.

void run_PID(double Kp, double Ki, double Kd, uint16_t WindowSize, uint32_t time_interval)
{
   ...

   if(millis() - windowStartTime < time_interval * ratio)
   {
       digitalWrite(SSR_PIN, 1);
   }

   if(millis() - windowStartTime < time_interval)
   {
       digitalWrite(SSR_PIN, 0);
   }
}

The run_PID function could also be further split, because now that this function will be called many times, the PID equation does not need to be computed each time.

 

This is just the introduction to the revisions that need to be made to handle multiple loops.  I will add to this post as I update the PID controller in github and actually test out the code.  Thanks for reading!

 

OBDII Controller with Raspberry Pi

It’s been awhile since I last posted, but I have a new idea.  I’m always fascinated with data, and I feel like just a small portion of available data in a car is actually accessible through the dashboard.  Therefore, I am going to start working on a project to collect car sensor data using a Raspberry Pi.

I bought a OBDII reader several years ago, and I’d check out “Check Engine” light codes and other stuff with a laptop.  However with advancing technology, I don’t see any reason why I can’t use that same reader with a Raspberry Pi and send that data to my phone for displaying.  Or even to send that data into my home WiFi network to be saved for later analysis.  I’ll worry about extra add-ons later.

Here’s how I’m breaking this project down (I’ll update as I complete):

  1. Get the reader communicating with the RPi.  I currently have a Elm Scan 5 OBD scan tool, a RPi version B+, and a 2006 Toyota Highlander that I’m planning to use for this.  Ideally, all of these parts will be interchangeable if you are playing along at home, but this is what I have.
  2. Start pulling some data off the OBDII system.
  3. Set up data sharing protocol.  Not sure yet if this will be WiFi or Bluetooth.  WiFi is probably overkill, but I could probably get it to connect directly with my home network for data collection.  Bluetooth would likely be simpler for just a smartphone interface.
  4. Graph the data into something nice to look at.  Hopefully this will be done on my smartphone (Nexus 5x).

If I get all the way through this, I think it will shower me in engine data as I drive along.

UPDATE:  I have started preparing a RPi B+ with a clean Raspbian Jessie Lite image and things are going well.  I did already learn a critical lesson: the Elm Scan 5 has to be plugged into the OBD port to communicate, even though the red power LED turns on with just USB.  I spent an hour wondering why I couldn’t get any serial comms going in my office, only to get some data action when I took the unit outside and plugged it into the car.

My plan is to combine all these tricks I learn along the way into a new, finished post.

 

LED Matrices

It’s been a while since my last post, so I thought I’d share what I’ve been up to.  I am currently working with 8×8 LED Matrices to show images and patterns.  I have 2 going right now on a breadboard, but my goal is to design a controller board for each matrix so that arrays can be snapped together to form big panels.

There is a good tutorial on how these matrices work here, including a good schematic.  Keep in mind that there at 8×8 LEDs x 3 color diodes, so 192 individual diodes.  Thankfully, all of the cathodes of a given color are common per row and all of the anodes of all diodes are common per column.  This simplifies the number of pins down to 8 rows of red + 8 rows of blue + 8 rows of green + 8 columns = 32 pins.

They require a driver chip and I am using the TLC5947 on a breakout board from Adafruit, although there are many possibilities for this.

 

Arduino PID Update

I wrote a post a few months back detailing my Arduino PID controller approach.  Based on some feedback, I realized the code in that post was overly complicated as well as had some errors in it.  I am now going to detail a much simpler approach.

Previously, I used a PID controller to schedule temperature over a fixed amount of time, such as to have precise ramp up time, hold time, and cool down time.  It is worth noting that a PID controller is also useful for indefinite time control, like a thermostat.  By varying the coefficients, you can customize how fast the system reacts and not damage any equipment (this is a consideration when using mechanical relays that don’t like fast switching).

Below is my Arduino C code for maintaining a set temperature like a thermostat with a PID controller.  The MAX31855 and the PID_v1 libraries can be found on my Github page.

#include "SPI.h"
#include "MAX31855.h"
#include "PID_v1.h"

//
//Arduino Pins
//
//CSB of MAX31855 thermocouple. By using separate CSBs, multiple devices can use the same SPI bus.
byte thermo1_csb = 7;
//Pin to control relay that switches the heating element
byte temp_relay0_Pin = 0;

//
//PID calculation parameters
//
//Value read by thermocouple sensor
double temp_read1;
//Temperature that system is currently maintaining
double temp_set1 = 95;
//Control value resulting from PID calculation
double Output_T1;

//
//Library Objects
//
MAX31855 thermo;
PID PID_temp1(&amp;temp_read1, &amp;Output_T1, &amp;temp_set1, 1, 5, 0.5, DIRECT);

void setup()
{
   //Initiate thermocouple
   SPI.begin();
   thermo.setup(thermo1_csb);

   //Initiate heating element relay
   pinMode(temp_relay0_Pin, OUTPUT);
   digitalWrite(temp_relay0_Pin, LOW);
}

//This function reads the thermocouple sensor and translates the value into temp_read1
void read_sensors()
{
   thermo.read_temp();
   temp_read1 = thermo.thermocouple_temp*1.8+32;
}

//This function computes the output control value from the thermocouple reading and current set point
//and then turns the heating element on and off accordingly
void run_PID()
{
   uint16_t low_WindowSize = 0;
   uint16_t high_WindowSize = 1000;
   uint32_t windowStartTime = millis();

   //Specify the links and initial tuning parameters
   PID_temp1.SetOutputLimits(low_WindowSize, high_WindowSize);
   //PID_temp1.SetTunings(Kp, Ki, Kd);
   PID_temp1.SetMode(AUTOMATIC);

   PID_temp1.Compute();

   //This section controls the heating element
   while(millis() < (high_WindowSize-low_WindowSize) + windowStartTime) 
   {
       if(Output_T1 > (millis() - windowStartTime))
       {
          digitalWrite(temp_relay0_Pin, HIGH);
       }
       else
       {
         digitalWrite(temp_relay0_Pin, LOW);
       }
   }
}

void loop()
{
   read_sensors();
   run_PID();
}

Arduino PID Temperature Controller

I recently implemented a PID routine for controlling temperature settings.  The goal was to maintain a fixed temperature rate for a specified ramp up period, hold the temperature for a specified soak time, and then cool down at a specified rate.  This could be useful for solder reflow cycles, food baking, beer brewing, or anything that needs a controlled timing scheme for temperature control.

 

PID stands for proportional-integral-derivative and is a very popular algorithm in control systems.  In very simple terms, the temperature controller takes readings of the current state of the system and uses that data to determine whether to keep the heating element on.  I won’t get into the details of the equation or the coefficients in this post; I am merely showing an Arduino implementation.  My PID controller library on Github is here.  I based my Arduino code off of this library on Github.  This library is very good with handling the PID equation, but I felt like it could use some more description and clarification in applying the equation to ordinary Arduino peripherals, which is why I’m writing this post.

 

My system includes:

Arduino Uno

MAX31855 Thermocouple Reader

Power Switch for switching heating element, activated by digital pin.  Solid State Relays can work too.

PID_schematic

The MAX31855 is controlled through a SPI interface.  I wrote a library for communicating with this chip on Github.  The MISO and SCLK pin positions are dictated by the type of Arduino board you are using (see this).  Any digital pin can be used for CSB.  I don’t normally use the built-in SPI CSB pin (pin 10 on Uno) just because I normally am using multiple SPI devices at the same time that can’t all use pin 10.

I also just randomly picked a digital pin to control the SSR.

I have posted the full code at the bottom of the page, but I will break down some of the main functions now.

First, the function to run the PID equation.  As parameters, this function takes in the 3 PID coefficient values (Kp, Ki, Kd); WindowSize, which is the maximum value the response could potentially be; and time_interval, the amount of time before the PID equation is re-evaluated.  After setting the properties of the myPID object (part of the borrowed PID class), the output response is calculated based on the current temperature reading input and the desired temperature set point.  I had issues getting the routine to start if the set point was too low, so the init_read section kick-starts the routine for the first time_interval.  I calculated a ratio of the output response divided by the maximum response to determine how long the heating element should be on and how long it should be off in a given time_interval.

 

void run_PID(double Kp, double Ki, double Kd, uint16_t WindowSize, uint32_t time_interval)
{
   double ratio;
   uint32_t windowStartTime;
   uint8_t buttons;

   //Specify the links and initial tuning parameters
   myPID.SetOutputLimits(0, WindowSize);
   myPID.SetTunings(Kp, Ki, Kd);
   myPID.SetMode(AUTOMATIC);

   //This prevents the system from initially hanging up
   if(init_read){init_read = 0;Setpoint = Setpoint + 1;}
   else{read_temps();}

   windowStartTime = millis();

   Input = CtoF(thermo.thermocouple_temp);
   myPID.Compute();

   ratio = Output / WindowSize;

   digitalWrite(SSR_PIN, 1);
   while(millis() - windowStartTime < time_interval * ratio);

   digitalWrite(SSR_PIN, 0);
   while(millis() - windowStartTime < time_interval);
}

 

Next is the section that loops through the multiple time intervals.  Depending on the system state (ramp up, soak, cool down), the function first determines how long that state will run based on the beginning and ending temperatures, calculates the number of time intervals are in that state time, and then loops through those iterations.  In ramp up, the function determines the initial temperature reading and subtracts from the soak temperature to get the temperature delta, which along with the ramp rate can be used to find the total ramp time.  In the soak state, the length of time is already specified.  In cool down, the reverse of the ramp up is used.

void run_cycle_time(void)
{
   uint32_t initial_time = millis();
   uint32_t elapsed_time;
   double diff_time_min;
   double cycle_time;

   //This set of statements calculates time of cycle phase
   if(state == 0) //rising ramp, increasing temperature
   {
      //Calculate time remaining in rise phase based on temperature and rate
      read_temps();
      init_temp = CtoF(thermo.thermocouple_temp);
      cycle_time = (max_temp - init_temp)/ramp_rate;
   }
   else if(state == 1) //soak time
   {
      //Soak time is already determined
      cycle_time = soak_time;
   }
   else if(state == 2) //falling ramp, decreasing temperature
   {
      //Calculate time remaining in fall phase based on temperature and rate each cycle
      read_temps();
      cycle_time = (CtoF(thermo.thermocouple_temp) - init_temp)/cool_down;
   }

   //Determine time left in current phase
   elapsed_time = millis();
   diff_time_min = float(elapsed_time - initial_time) / 60000;

   while(diff_time_min < cycle_time)
   {
      if(state == 0) //rising ramp, increasing temperature
      {
          //While increasing, Setpoint increases based on elapsed time
          Setpoint = (diff_time_min * ramp_rate) + init_temp;
      }
      else if(state == 1) //soak time
      {
          Setpoint = max_temp;
      }
      else if(state == 2) //falling ramp, decreasing temperature
      {
          //While decreasing, Setpoint increases based on elapsed time
          Setpoint = max_temp - (diff_time_min * cool_down);
      }

      //Determine current temp
      read_temps();

      //Determine PID response based on current temp
      run_PID(2, 5, 1, 500, PID_interval);

      //Determine time left in current phase
      elapsed_time = millis();
      diff_time_min = float(elapsed_time - initial_time) / 60000;
   }
}

The main loop simply steps through the various states in sequence.

void loop()
{
   switch (state) {
      case 0: //Ramp Up
         run_cycle_time();
         state = state + 1;
         break;
      case 1: //Soak Time
         run_cycle_time();
         state = state + 1;
         break;
      case 2: //Cool Down
         run_cycle_time();
         state = state + 1;
         break;
      case 3: //Finished
         while(1);
    }
}

 

UPDATE:  I’ve started a new post on Controlling Multiple PID Loops with One Arduino.  Please check it out!
The full source code for PID controller

#include <PID_v1.h>
#include "MAX31855.h"

#define SPI_transfer 1

#if defined(SPI_transfer)
#include "SPI.h"
#endif

//Arduino Pins
uint8_t DHTPIN = 2;
uint8_t MAX31855_DATA = 12;
uint8_t MAX31855_CLK = 13;
uint8_t MAX31855_LAT0 = 7;
uint8_t SSR_PIN = 4;

//Temperature Cycle Settings
uint8_t state = 0;
uint16_t max_temp = 95; //in degrees F
uint8_t soak_time = 10; //in minutes
uint8_t ramp_rate = 10; //in degrees F/min
uint8_t cool_down = 10; //in degrees F/min

//PID parameters
double init_temp;
double Input, Output, Setpoint;
uint8_t init_read = 1; //flag to prevent 2 temp reads on first PID pass
uint32_t PID_interval = 5000; //time in ms to run PID interval

//Object Instantiation
MAX31855 thermo;
PID myPID(&Input, &Output, &Setpoint, 2, 5, 1, DIRECT);

void setup()
{
   pinMode(SSR_PIN, OUTPUT);

   thermo.setup(MAX31855_LAT0);
   read_temps();
}

void loop()
{
   switch (state) {
      case 0: //Ramp Up
         run_cycle_time();
         state = state + 1;
         break;
      case 1: //Soak Time
         run_cycle_time();
         state = state + 1;
         break;
      case 2: //Cool Down
         run_cycle_time();
         state = state + 1;
         break;
      case 3: //Finished
         while(1);
    }
}

void run_PID(double Kp, double Ki, double Kd, uint16_t WindowSize, uint32_t time_interval)
{
   double ratio;
   uint32_t windowStartTime;
   uint8_t buttons;

   //Specify the links and initial tuning parameters
   myPID.SetOutputLimits(0, WindowSize);
   myPID.SetTunings(Kp, Ki, Kd);
   myPID.SetMode(AUTOMATIC);

   //This prevents the system from initially hanging up
   if(init_read){init_read = 0;Setpoint = Setpoint + 1;}
   else{read_temps();}

   windowStartTime = millis();

   Input = CtoF(thermo.thermocouple_temp);
   myPID.Compute();

   ratio = Output / WindowSize;

   digitalWrite(SSR_PIN, 1);
   while(millis() - windowStartTime < time_interval * ratio);

   digitalWrite(SSR_PIN, 0);
   while(millis() - windowStartTime < time_interval);
}

void run_cycle_time(void)
{
   uint32_t initial_time = millis();
   uint32_t elapsed_time;
   double diff_time_min;
   double cycle_time;

   //This set of statements calculates time of cycle phase
   if(state == 0) //rising ramp, increasing temperature
   {
      //Calculate time remaining in rise phase based on temperature and rate
      read_temps();
      init_temp = CtoF(thermo.thermocouple_temp);
      cycle_time = (max_temp - init_temp)/ramp_rate;
   }
   else if(state == 1) //soak time
   {
      //Soak time is already determined
      cycle_time = soak_time;
   }
   else if(state == 2) //falling ramp, decreasing temperature
   {
      //Calculate time remaining in fall phase based on temperature and rate each cycle
      read_temps();
      cycle_time = (CtoF(thermo.thermocouple_temp) - init_temp)/cool_down;
   }

   //Determine time left in current phase
   elapsed_time = millis();
   diff_time_min = float(elapsed_time - initial_time) / 60000;

   while(diff_time_min &lt; cycle_time)
   {
      if(state == 0) //rising ramp, increasing temperature
      {
          //While increasing, Setpoint increases based on elapsed time
          Setpoint = (diff_time_min * ramp_rate) + init_temp;
      }
      else if(state == 1) //soak time
      {
          Setpoint = max_temp;
      }
      else if(state == 2) //falling ramp, decreasing temperature
      {
          //While decreasing, Setpoint increases based on elapsed time
          Setpoint = max_temp - (diff_time_min * cool_down);
      }

      //Determine current temp
      read_temps();

      //Determine PID response based on current temp
      run_PID(2, 5, 1, 500, PID_interval);

      //Determine time left in current phase
      elapsed_time = millis();
      diff_time_min = float(elapsed_time - initial_time) / 60000;
   }
}

double CtoF(double temp_C)
{
   double temp_F = (temp_C * 1.8) + 32;
   return temp_F;
}

void read_temps(void)
{
   thermo.read_temp();
}

 

 

Photo Booth Code

I’ve gotten a few questions about my Photo Booth, so I thought I would post more of the code to GitHib and explain it a bit. First, the code is here:

https://github.com/jowood4/photo_booth

The main program is top_photo_booth.py. When the Raspberry Pi first boots up, it immediately runs this script. As in typical tkinter Python class style, all of the methods are defined first, and then an object is instantiated at the end with a call to root.mainloop() to get the GUI running.

I used several tkinter frames as the GUI interface, many of which contained JPGs of pictures I drew with instructions to guide the user. The constructor _init_ method defines all of the frames and the JPGs to use. When a frame selected, that frame gets a .lift() and a .update() to get the GUI to immediately update.

The flow of the GUI goes like this:

1. Start Screen. This is the default screen that initially comes up as well when the GUI times out.
2. Instruction Screen. Once the user touches the screen to initiate the process, the instruction screen explains what is going to happen and how it works.
3. Preview Screen. The GUI takes pictures and continuously shows what is taken so that the user can make sure the pictures look good before they are officially taken.
4. 3-2-1. The GUI counts down to give the user a chance to get ready. The LEDs change colors with each count.
5. CHEESE. The camera takes a picture that will be saved.
6. Got it. The GUI tells the user a picture was taken and then the countdown start over until 3 pictures have been taken.
7. Look Screen. Each of the 3 recorded pictures are shown on the screen. The pictures are shown in buttons such that the user can replace any of the pictures by pushing on it.
8. Finish screen. Once the user pushes the print button, the finish screen has instructions to direct the user to pick up their printout.

I’ll explain the submodules in separate posts to keep this from getting too wordy.

USB Power Supply Arduino Shield

I want to start making some battery powered gadgets, but I really want to know how much power my circuits require before hooking up the battery and releasing them into the world.  After a bit of searching, I couldn’t find anything that would let me set my own voltage (sometimes 5V, sometimes 3.7V for batteries, sometimes something else), tell me how much current I’m using, and be cheap enough for a hobbyist like myself.  So I started drawing up a schematic.  This is what I think I can do:

– Switching regulator for good efficiency and up and down DC voltage conversion.  Highly considering LTC3112 to get a 3 to 12V range.

– Arduino shield.  This will give me lots of analog and digital I/Os.

– Can run off of the Arduino power or from an external power input.  Going to use an FDC6324 to switch between them.

– Current Sensing Circuit.

– Current Limit Circuit.  I don’t want to blow anything up with an accidental short.  I couldn’t find any integrated chips to handle the voltage range I want, to I’m going to use a power pFET.

– Programmable potentiometers so that everything can be set digitally.

I started drawing up a project in Kicad.  Here is the schematic so far usb_power.  Here is the project on Github.