Push button to override PID control

Hi there,

I have implemented PID control to keep a photoresistor at a desired setpoint of "light level" detected by using an LED to come on if the light level drops below the setpoint and the LED will turn off if the light level detected is above the set point. This code is working as stand alone code as it should.

I was wondering if it was possible to write in code so that I could utilise a push button to override these set conditions i.e. when the light level detected is above the set point and the LED is off, would it be possible to "force" this LED on through the use of a push button? If so, what would the code for this look like?

Any help is greatly appreciated, thanks in advance.

if (ledPidOn || buttonPushed) ...
#include <PID_v1.h>
const int photores = A0; // Photo resistor input
const int pot = A1; // Potentiometer input
const int led = 9; // LED output
double lightLevel; //variable that stores the incoming light level

const int sw1pin    = A2;  // input pin: DIL switch 1 on pin A2
int sw1_state = 0;          // DIL switch 1 state (HIGH/LOW)

// Tuning parameters
float Kp=0; //Initial Proportional Gain 
float Ki=10; //Initial Integral Gain 
float Kd=0;  //Initial Differential Gain 

double Setpoint, Input, Output;  //These are just variables for storing values
PID myPID(&Input, &Output, &Setpoint, Kp, Ki, Kd, DIRECT); // This sets up our PDID Loop
//Input is our PV
//Output is our u(t)
//Setpoint is our SP
const int sampleRate = 1; // Variable that determines how fast our PID loop runs

// Communication setup
const long serialPing = 500; //This determines how often we ping our loop
// Serial pingback interval in milliseconds
unsigned long now = 0; //This variable is used to keep track of time
// placehodler for current timestamp
unsigned long lastMessage = 0; //This keeps track of when our loop last spoke to serial
// last message timestamp.

void setup(){
  lightLevel = analogRead(photores); //Read in light level
  Input = map(lightLevel, 0, 1024, 0, 255); //Change read scale to analog out scale
  Setpoint = map(analogRead(pot), 0, 1024, 0, 255);  //get our setpoint from our pot
  Serial.begin(9600); //Start a serial session
  myPID.SetMode(AUTOMATIC);  //Turn on the PID loop
  myPID.SetSampleTime(sampleRate); //Sets the sample rate
  
  Serial.println("Begin"); // Hello World!
  lastMessage = millis(); // timestamp

  
}

void loop(){
  Setpoint = map(analogRead(pot), 0, 1024, 0, 255); //Read our setpoint
  lightLevel = analogRead(photores); //Get the light level
  Input = map(lightLevel, 0, 900, 0, 255); digitalRead(A2); // Map it to the right scale, Read SW1
  myPID.Compute();  //Run the PID loop
  analogWrite(led, Output);  //Write out the output from the PID loop to our LED pin
    
  now = millis(); //Keep track of time
  if(now - lastMessage > serialPing) {  //If its been long enough give us some info on serial
    // this should execute less frequently
    // send a message back to the mother ship
    Serial.print("Setpoint = ");
    Serial.print(Setpoint);
    Serial.print(" Input = ");
    Serial.print(Input);
    Serial.print(" Output = ");
    Serial.print(Output);
    Serial.print("\n");
        
    lastMessage = now; 
    //update the time stamp. 
  }

  
}

This is my written code, I have declared the push button along with its initial state. How would I initialise the button in the setup() function before entering the command in the loop() function?

The rest depends on how you connect the button, active low/high, pull up/dn? See e.g. the Debounce example code on using a button.

Hi brattray23,
that would be my way of doing it; the example is for 2 buttons, and I don't know on which parameter you want to play, but it's a start, if it can inspire you!
(I have changed your name 'sw1pin' in ''button1Plus', I have not seen you already have named it...
.
.

#include <PID_v1.h>
const int photores = A0; // Photo resistor input
const int pot = A1; // Potentiometer input
const int led = 9; // LED output
double lightLevel; //variable that stores the incoming light level

//----------------------------
//const int sw1pin    = A2;  // input pin: DIL switch 1 on pin A2
//int sw1_state = 0;          // DIL switch 1 state (HIGH/LOW)

// Tuning parameters
float Kp=0; //Initial Proportional Gain 
float Ki=10; //Initial Integral Gain 
float Kd=0;  //Initial Differential Gain 

double Setpoint, Input, Output;  //These are just variables for storing values
PID myPID(&Input, &Output, &Setpoint, Kp, Ki, Kd, DIRECT); // This sets up our PDID Loop
//Input is our PV
//Output is our u(t)
//Setpoint is our SP
const int sampleRate = 1; // Variable that determines how fast our PID loop runs

// Communication setup
const long serialPing = 500; //This determines how often we ping our loop
// Serial pingback interval in milliseconds
unsigned long now = 0; //This variable is used to keep track of time
// placehodler for current timestamp
unsigned long lastMessage = 0; //This keeps track of when our loop last spoke to serial
// last message timestamp.


//------------------------------------------------------                                                      
#define pinButton1  A2                                 // PIN      + button   (fix your pin number here)
#define pinButton2  8                                 // PIN      - button   (fix your pin number here)

unsigned long prevmillisButtons;                      // BUTTON   debounce period
#define button1Plus  1                                // BUTTON   convention: PlusButton  value= 1
#define button2Minus 2                                // BUTTON   convention: MinusButton value= 2
byte buttonStat;                                      // BUTTON   state    buttons
byte buttonDown;                                      // BUTTON   pressed  buttons
byte buttonUp;                                        // BUTTON   released buttons
byte buttonMemo;                                      // BUTTON   memorised state buttons




void setup(){
  lightLevel = analogRead(photores); //Read in light level
  Input = map(lightLevel, 0, 1024, 0, 255); //Change read scale to analog out scale
  Setpoint = map(analogRead(pot), 0, 1024, 0, 255);  //get our setpoint from our pot
  Serial.begin(9600); //Start a serial session
  myPID.SetMode(AUTOMATIC);  //Turn on the PID loop
  myPID.SetSampleTime(sampleRate); //Sets the sample rate
  
  Serial.println("Begin"); // Hello World!
  lastMessage = millis(); // timestamp

//------------------------------------------------------
  pinMode (pinButton1, INPUT_PULLUP);                 //
  pinMode (pinButton2, INPUT_PULLUP);                 //
  
}

void loop(){
  Setpoint = map(analogRead(pot), 0, 1024, 0, 255); //Read our setpoint
  lightLevel = analogRead(photores); //Get the light level
  Input = map(lightLevel, 0, 900, 0, 255); digitalRead(A2); // Map it to the right scale, Read SW1
  myPID.Compute();  //Run the PID loop
  analogWrite(led, Output);  //Write out the output from the PID loop to our LED pin
    
  now = millis(); //Keep track of time
  if(now - lastMessage > serialPing) {  //If its been long enough give us some info on serial
    // this should execute less frequently
    // send a message back to the mother ship
    Serial.print("Setpoint = ");
    Serial.print(Setpoint);
    Serial.print(" Input = ");
    Serial.print(Input);
    Serial.print(" Output = ");
    Serial.print(Output);
    Serial.print("\n");
        
    lastMessage = now; 
    //update the time stamp. 
  }  
//------------------------------------------------------    
  if (pollButtons()) {                                // buttons pressed ?
    if (button1Plus  & buttonDown) {};                // your action here
    if (button2Minus & buttonDown) {};                // your action here
    
  }
}  





// -----------------------------------------------------
bool pollButtons() {                                  // BUTTONS : DEBOUNCE, FRONT DETECTION
  if (millis() - prevmillisButtons > 20) {            // 20 ms  buttons debounce period 
    prevmillisButtons = millis();                     //
                                                      //
    byte iii = 0;                                     // (iii and jjj are "local" variables)
    if (!digitalRead(pinButton1)) iii  = button1Plus; // 
    if (!digitalRead(pinButton2)) iii |= button2Minus;//    
    byte jjj   = buttonStat;                          //    
    buttonStat = iii & buttonMemo;                    // buttons status
    buttonDown = (jjj ^ buttonStat) & buttonStat;     // buttons is (are) just pressed
    buttonUp   = (iii ^ jjj) & jjj;                   // buttons is (are) just released
    buttonMemo = iii;                                 // buttons configuration for next use
                                                      //
    if (buttonStat | buttonDown | buttonUp) return 1; //
  }                                                   //
  return 0;                                           //
}                                                     //

Maybe you only want one switch status information (on or off), in which case a simple read as done in the line 'Input = map(lightLevel...,' is enough (but the A2 pin reading is missing something)
My example is rather intended for the management of several buttons, such as for +, - and enter for example...

Hey people,
Thank you for your replies.
I was looking to keep the code as simple as possible, keeping the same PID loop and just introducing 1 push button which drives the LED output to the maximum mapped value of 255.
The thought behind doing this was to compare this to a manual switch if the PID were to fail in its application to if the LED output was to be stuck at 0 for any given reason.

Okay, that's easy. Delete the fields I added (see //-----------------------) and modify the code like this:

replace:
const int sw1pin = A2; // input pin: DIL switch 1 on pin A2
int sw1_state = 0; // DIL switch 1 state (HIGH/LOW)
by:
#define sw1pin A2

add in setup:
pinMode (sw1pin, INPUT_PULLUP);

replace:
Input = map(lightLevel, 0, 900, 0, 255); digitalRead(A2); // Map it to the right scale, Read SW1

by:
Input = map(lightLevel, 0, 900, 0, 255);
if (!digitalRead(A2)) Output = 255;

attention, switch must be connected between A2 and Gnd

1 Like

i seriously doubt there's a need for PID (where is there any lag).

why not simply turn the LED on if the level is below some threshold and off when above some threshold (add a little hysterisis) and simply test for a button press to enable manual control, turning on the LED and disabling manual control when in manual mode

consider


#define ThreshHi     400
#define ThreshLo     (ThreshHi - 10)

const byte InpPin = A0;
const byte ButPin = A1;
const byte LedPin = LED_BUILTIN;

byte butState;
byte mode;

enum { Auto, Manual };
enum { Off = HIGH, On = LOW };

void
loop ()
{
    byte but      = digitalRead (ButPin);

    if (butState != but)  {
        butState = but;
        delay (10);     // debounce

        if (LOW == but)  {
            if (Auto == mode)  {
                mode = Manual;
                digitalWrite (LedPin, On);
            }
            else
                mode = Auto;
        }
    }

    if (Auto == mode)  {
        int inp = analogRead (InpPin);

        if (On == digitalRead (LedPin))  {
            if (ThreshHi <= inp)
                digitalWrite (LedPin, Off);
        }
        else  {
            if (ThreshLo >= inp)
                digitalWrite (LedPin, On);
        }

        Serial.println (inp);
        delay (500);
    }
}

void
setup ()
{
    Serial.begin (9600);

    pinMode (ButPin, INPUT_PULLUP);
    butState = digitalRead (ButPin);

    digitalWrite (LedPin, Off);
    pinMode (LedPin,  OUTPUT);
}

Bingo!
Thank you very much for your patience and assistance.
The PID is working as it's own entity and the push button overrides the output and sets it to max just as desired.
Now for the next task, I plan to add in an "off" override push button before adding in a 7-seg display to show a "1" when the LED is on and a "0" when the LED is off.
Still very much new to this so I appreciate all the help I can get :slight_smile: :+1:

This topic was automatically closed 180 days after the last reply. New replies are no longer allowed.