// pwm_driver_v3 adding TEMP_CONTROL_TYPE 2 (PID control)
// function adding pidInit(), pidRunning(), wdtInit()
#define DEBUG           // show messages like "fan on, mid", "fan off"
#define TEMP_DEBUG      // in addition to DEBUG show temp raw value every second
#include "PID_v1.h"

#define TEMP_CONTROL_TYPE 0  // should be 0 or 1 or 2(PID)

#define INPUT_EN 6     // enable input
#define INPUT_TEMP A3  // temp sensing input
#define OUTPUT_PWM 10  // pwm output

#define PWM_DUTY_MIN 65  // minimal pwm %
#define PWM_DUTY_MAX 85  // maximal pwm %

#define STARTUP_DELAY 2  // period for pumping up motor with constant ON output without pwm for better starting, seconds.

#define TEMP_THRESHOLD 620  // 16 degreeC
#define PWM_FREQ 770        // pwm frequency

#define PWM_STEP 7  // when temp is high and rising system increase pwm duty step by step by this value. Higher value - faster it ramp and slow down. 7 is ~1% duty per second.

enum { OFF = 0,
       RUNNING = 1,
       STARTUP = 2 };

volatile bool _wdtFlag = 0;
byte enButtonTmp = 0;
byte mode = OFF;
uint16_t curTemp, prevTemp = 0, dutyTmp;
byte startUpFlag = 0;
uint16_t dutyMin, dutyMax;

//***************************PID varible*******************************
// 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 storingvalues
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 = 100;  // Variable that determines how fast our PID loop runs(0.1s)
#ifdef DEBUG
// 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.
#endif
//*********************************************************************

void setup() {
  delay(500);
#ifdef DEBUG
  Serial.begin(9600);
#endif
  digitalWrite(OUTPUT_PWM, LOW);
  pinMode(OUTPUT_PWM, OUTPUT);
  wdtInit();
#ifdef DEBUG
  Serial.println("begin...");
  Serial.print("Temp control type: ");
  Serial.println(TEMP_CONTROL_TYPE, DEC);
  Serial.println();
  Serial.println();
#endif
  stopPwm();
  startPwm();
  pidInit();
}

void loop() {
  enableHandler();
  if (_wdtFlag) {
    _wdtFlag = 0;
    startUpHandler();
    tempHandler();
  }
}

void enableHandler() {
  switch (mode) {
    case OFF:
      if (digitalRead(INPUT_EN) == 1)
        enButtonTmp++;
      else
        enButtonTmp = 0;
      if (enButtonTmp > 20) {
        enButtonTmp = 0;
        mode = STARTUP;
        motorStartup();
#ifdef DEBUG
        Serial.println("Fan STARTUP");
        Serial.println();
#endif
        delay(100);
      }
      break;
    case STARTUP:
      if (startUpFlag == 0) {
        mode = RUNNING;
        startPwm();
#ifdef DEBUG
        Serial.println("Fan ON");
        Serial.println();
#endif
      }
      break;
    case RUNNING:
      if (digitalRead(INPUT_EN) == 0)
        enButtonTmp++;
      else
        enButtonTmp = 0;
      if (enButtonTmp > 20) {
        enButtonTmp = 0;
        mode = OFF;
        stopPwm();
#ifdef DEBUG
        Serial.println("Fan OFF");
        Serial.println();
#endif
        delay(100);
      }
      break;
  }
}

void tempHandler() {
  curTemp = analogRead(INPUT_TEMP);
#ifdef TEMP_DEBUG
  Serial.print("Temp raw value: ");
  Serial.println(curTemp, DEC);
#endif
  if (mode == RUNNING) {
    if (curTemp > TEMP_THRESHOLD) {
      if (curTemp >= prevTemp) {
        dutyTmp = OCR1B + PWM_STEP;
        if (dutyTmp > dutyMax)
          dutyTmp = dutyMax;
      } else {
#if TEMP_CONTROL_TYPE == 1
        dutyTmp = OCR1B - PWM_STEP / 2;
#else
        dutyTmp = OCR1B - PWM_STEP;
#endif
        if (dutyTmp < dutyMin)
          dutyTmp = dutyMin;
      }
    } else {
      dutyTmp = OCR1B - PWM_STEP;
      if (dutyTmp < dutyMin)
        dutyTmp = dutyMin;
    }
#if TEMP_CONTROL_TYPE == 2  // pid control added
    pidRunning();
#endif
    OCR1B = dutyTmp;
#ifdef DEBUG
    Serial.print("duty: ");
    Serial.println(getDuty());
    Serial.println();
#endif
  }
}

void startUpHandler() {
  if (startUpFlag)
    startUpFlag--;
}

void setDuty(uint16_t duty) {
  OCR1B = PWM_FREQ / (100 / (float)duty);
}

uint16_t getDuty() {
  return OCR1B;
}

void startPwm() {
  OCR1A = PWM_FREQ;
  OCR1B = dutyMin;
  TCNT1 = 0;
  TCCR1A = (1 << WGM11) | (1 << WGM10) | (1 << COM1B1);
  TCCR1B = (1 << CS10) | (1 << WGM13) | (1 << WGM12);
}

void stopPwm() {
  TCCR1B = 0;
  for (byte i = 0; i < 50; i++) {
    if (digitalRead(OUTPUT_PWM) == HIGH) {
      delay(2);
      digitalWrite(OUTPUT_PWM, LOW);
      break;
    }
    mode = OFF;
    delay(1);
  }
}

void motorStartup() {
  startUpFlag = STARTUP_DELAY + 1;
  digitalWrite(OUTPUT_PWM, HIGH);
}

void pidInit() {
  curTemp = analogRead(INPUT_TEMP);  //Read in temperature level
  Input = curTemp;                   //Set PID input
  Setpoint = TEMP_THRESHOLD;         //Set the Setpoin
  //get our setpoint from our pot
  myPID.SetOutputLimits(0, PWM_FREQ);
  myPID.SetMode(AUTOMATIC);         //Turn on the PID loop
  myPID.SetSampleTime(sampleRate);  //Sets the sample rate
#ifdef DEBUG
  Serial.println("Begin");  // Hello World!
  lastMessage = millis();   // timestamp
#endif
}

void pidRunning() {
  Input = curTemp;   //Set PID input
  myPID.Compute();   //Run the PID loop
  dutyTmp = Output;  //Write out the output from the PID loop to fan duty
#ifdef DEBUG
  now = millis();                        //Keep track of time
  if (now - lastMessage > serialPing) {  //If it has 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");
    if (Serial.available() > 0) {  //If we sent the program a command deal with it
      for (int x = 0; x < 4; x++) {
        switch (x) {
          case 0:
            Kp = Serial.parseFloat();
            break;
          case 1:
            Ki = Serial.parseFloat();
            break;
          case 2:
            Kd = Serial.parseFloat();
            break;
          case 3:
            for (int y = Serial.available(); y == 0; y--) {
              Serial.read();  //Clear out any residual junk
            }
            break;
        }
      }
      Serial.print(" Kp,Ki,Kd = ");
      Serial.print(Kp);
      Serial.print(",");
      Serial.print(Ki);
      Serial.print(",");
      Serial.println(Kd);            //Let us know what we just received
      myPID.SetTunings(Kp, Ki, Kd);  //Set the PID gain constants and start running
    }
    lastMessage = now;
    //update the time stamp.
  }
#endif
}

void wdtInit() {
  WDTCSR = (1 << WDCE) | (1 << WDE);
  WDTCSR = (1 << WDCE) | (1 << WDIE) | (1 << WDP2) | (1 << WDP1);
}

// wdt timer interrupt
ISR(WDT_vect) {
  _wdtFlag = true;
}
NOCOMNCVCCGNDINLED1PWRRelay Module
D0D1D2D3D4D5D6D7GNDLOGIC