// 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;
}