*/
#include <Arduino.h>
-#include <EEPROM.h>
-#include <Ports.h> // JeeLib library
+#include <EEPROM.h> // Arduino core
+#include <Ports.h> // JeeLib library
+#include <PID_v1.h> // Arduino PID library
+#include <Statistic.h> // Statistics library
#include <avr/pgmspace.h>
#include <avr/eeprom.h>
+#include <avr/wdt.h>
#define COMMON_CATHODE // This device is common cathode
// The timer initial counter value.
static unsigned int tcnt2 = 0;
-static unsigned int led_cnt = 0;
-
// Flag to make the display flash visibly
#define STATE_NORMAL 0
#define STATE_USERPRESS 1
static long target_temp = 0;
static long last_target = 0;
+class CPID
+{
+public:
+ double mKp, mKi, mKd;
+ double mCp, mCi, mCd;
+ double mSetPoint, mPrevTime, mPrevErr;
+ CPID(double Kp, double Ki, double Kd) : mKp(Kp), mKi(Ki), mKd(Kd) {
+ mSetPoint = mPrevTime = mPrevErr = 0;
+ mCp = mCi = mCd = 0;
+ }
+ void SetPoint(double point) { mSetPoint = point; }
+ double update(double current_time, double current_value) {
+ double err = mSetPoint - current_value;
+ double dt = current_time - mPrevTime;
+ double de = err - mPrevErr;
+ mCi += err * dt;
+ if (mCi < 0.0) mCi = 0.0;
+ else if (mCi > 5000.0) mCi = 5000.0;
+ double D = 0;
+ if (dt > 0) D = de/dt;
+ mPrevTime = current_time;
+ mPrevErr = err;
+ err = (mKp * err) + (mKi * mCi) + (mKd * D);
+ if (err < 0.0) err = 0.0;
+ else if (err > 5000.0) err = 5000.0;
+ return err;
+ }
+ double GetKp() const { return mKp; }
+ double GetKi() const { return mKi; }
+ double GetKd() const { return mKd; }
+};
+
+
+double Input = 0, Output = 0, SetPoint = 0;
+int WindowSize = 5000;
+unsigned long windowStartTime;
+// original 2, 5, 1 massive overshoot and +2 degrees offset
+// 0.072, 14.8, 2.39, DIRECT);
+//PID pid(&Input, &Output, &SetPoint, 1.0, 4.0, 10.0, DIRECT);
+CPID pid(1.0, 0.0, 0.0);
+
// Serial print using FLASH strings: Serial_print(PSTR(...));
static void Serial_print(PGM_P s)
{
return;
}
- // select our digit and configure
- digitalWrite(select[digit], LOW);
- set_segments( Digits[num[digit] - 0x30] | ((digit == 1) ? 0x80 : 0) );
+ // do not use digit 0 (only showing tenths of a degree)
+ // The OR'd part selects the decimal point (after digit 2)
+ if (digit != 0) {
+ // select our digit and configure
+ digitalWrite(select[digit], LOW);
+ set_segments( Digits[num[digit] - 0x30] | ((digit == 2) ? 0x80 : 0) );
+ }
}
// Initialize timer2 as interrupt source
// Delay is (prescale / cpu_freq * 1e6)
// [@16MHz with 1024 prescaler yields 75us tick]
// So set tcnt2 = 256 - (6000us / 75us) for 6ms update
- tcnt2 = 176;
+ // was 176 for 6ms, now 190 for 5ms
+ tcnt2 = 190;
TCNT2 = tcnt2;
// 18.9e: re-enable the timer overflow interrupt flag
TIMSK2 |= (1 << TOIE2);
TCNT2 = tcnt2; // reset the timer
display(current_digit, Display);
current_digit = (current_digit + 1) % 4;
- ++led_cnt;
- if (led_cnt > 166) {
- //PORTB ^= _BV(PB5); // toggle pin 13
- led_cnt = 0;
- }
}
// We store the currently set target temperature in the EEPROM
| (EEPROM.read(1) << 8)
| (EEPROM.read(0));
}
- Serial_print(PSTR("eeprom read: "));
+ Serial.print(F("# eeprom read: "));
Serial.println(t);
return t;
}
EEPROM.write(1, (t >> 8) & 0xff);
EEPROM.write(2, (t >> 16) & 0xff);
EEPROM.write(3, (t >> 24) & 0xff);
- Serial_print(PSTR("eeprom write: "));
+ Serial.print(F("# eeprom write: "));
Serial.println(t);
}
static void setDisplay(long v)
{
+ // Only show 1 dp (input is in hundreds)
+ v = (v + 50) / 10; // round to nearest 10th degree
Display[3] = 0x30 + (v % 10);
Display[2] = 0x30 + ((v/10) % 10);
Display[1] = 0x30 + ((v/100) % 10);
setup()
{
// disable TWI, Timer1, SPI and USART0
- //PRR &= (1<<PRTWI) | (1<<PRTIM1) | (1<<PRSPI);// | (1 << PRUSART0);
+ PRR &= (1<<PRTWI); //| (1<<PRTIM1) | (1<<PRSPI);// | (1 << PRUSART0);
// Enable the internal 1.1V AREF (requires 10nF cap to GND)
analogReference(INTERNAL);
pinMode(SEL_4, OUTPUT);
Serial.begin(9600);
- Serial_print(PSTR("Slide Warmer - Copyright (c) 2013 Pat Thoyts\r\n"));
+ Serial.print(F("# Slide Warmer v1.0 - Copyright (c) 2013 Pat Thoyts\r\n"));
current_digit = 0;
memset(Display, 0x30, sizeof(Display));
target_temp = stored_target_read();
setDisplay(target_temp);
+
+ SetPoint = target_temp; // PID target
+ windowStartTime = millis();
+ //pid.SetOutputLimits(0, WindowSize);
+ //pid.SetSampleTime(200);
+ //pid.SetMode(AUTOMATIC);
+ Serial.print(F("# PID Kp="));
+ Serial.print(pid.GetKp());
+ Serial.print(F(" Ki="));
+ Serial.print(pid.GetKi());
+ Serial.print(F(" Kd="));
+ Serial.println(pid.GetKd());
+
Timer2Init();
+ wdt_enable(WDTO_1S);
}
-#define AREF 105L
-
static long getTemperature()
{
+#define AREF 105L
long v,v1;
v = analogRead(LM35_PIN);
- // v * REFERENCE) / NSTEPS
v1 = (v * AREF * 100L) / 1024L;
v = (v * 10000L) / 1024L;
- Serial.print("[");
- Serial.print(v);
- Serial.print(" ");
- Serial.print(v1);
- Serial.print("] ");
return v;
}
static void setState(byte state)
{
display_state = state;
- Serial_print(PSTR("state: "));
+ Serial_print(PSTR("# set state: "));
Serial.println(state);
}
if (target_temp < 1000) target_temp = 1000;
if (target_temp > 11000) target_temp = 11000;
setDisplay(target_temp);
- Serial_print(PSTR("target: "));
+ Serial_print(PSTR("# select target: "));
Serial.println(target_temp);
}
-MilliTimer timer0, timer1, timer2, timer3;
+static void setHeating(bool on)
+{
+ // setting pin 8 low puts the MOC led _on_
+ if (on) {
+ digitalWrite(MOC_PIN, LOW);
+ digitalWrite(LED_PIN, HIGH);
+ } else {
+ digitalWrite(MOC_PIN, HIGH);
+ digitalWrite(LED_PIN, LOW);
+ }
+}
+
+static void Serial_print_value_space(double value)
+{
+ Serial.print(value);
+ Serial_print(PSTR(" "));
+}
+
+MilliTimer timerPID, timerKeypress, timer2, timerPrint;
bool keypress_delay = false;
+bool need_debounce = true, debounce_left = false, debounce_right = false;
void
loop()
{
- long v = 0, t[NUMSAMPLES];
int n;
- if (timer1.poll(100)) {
- bool left_button = digitalRead(BUTTON_L) != HIGH;
- bool right_button = digitalRead(BUTTON_R) != HIGH;
- switch (display_state)
- {
- case STATE_NORMAL:
- if (left_button || right_button) {
- setState(STATE_USERPRESS);
- timer2.set(1000);
- }
- break;
- case STATE_USERPRESS:
- if (timer2.poll()) {
+ wdt_reset();
+
+ // key press handling
+ if (timerKeypress.poll(50)) {
+ bool left = digitalRead(BUTTON_L) != HIGH;
+ bool right = digitalRead(BUTTON_R) != HIGH;
+ if (need_debounce) {
+ debounce_left = left;
+ debounce_right = right;
+ need_debounce = false;
+ } else {
+ // debounce: button press must be true on each 50ms pass
+ bool left_button = left && debounce_left;
+ bool right_button = right && debounce_right;
+ need_debounce = true;
+ switch (display_state)
+ {
+ case STATE_NORMAL:
if (left_button || right_button) {
- setState(STATE_FLASHING);
+ setState(STATE_USERPRESS);
+ timer2.set(1000);
+ }
+ break;
+ case STATE_USERPRESS:
+ if (timer2.poll()) {
+ if (left_button || right_button) {
+ setState(STATE_FLASHING);
+ timer2.set(5000);
+ keypress_delay = true;
+ setDisplay(target_temp);
+ last_target = target_temp;
+ }
+ else {
+ setState(STATE_NORMAL);
+ }
+ }
+ break;
+ case STATE_FLASHING:
+ // avoid accepting presses for the first second
+ if (!keypress_delay) {
+ if (left_button)
+ incrementTargetTemp(-100);
+ if (right_button)
+ incrementTargetTemp(100);
+ }
+ if (left_button || right_button) // reset timeout
timer2.set(5000);
- keypress_delay = true;
- setDisplay(target_temp);
- last_target = target_temp;
+ if (keypress_delay) {
+ if (timer2.remaining() < 4000)
+ keypress_delay = false;
}
- else {
+ if (timer2.poll()) {
setState(STATE_NORMAL);
+ if (target_temp != last_target) {
+ stored_target_write(target_temp);
+ }
}
- }
- break;
- case STATE_FLASHING:
- // avoid accepting presses for the first second
- if (!keypress_delay) {
- if (left_button)
- incrementTargetTemp(-100);
- if (right_button)
- incrementTargetTemp(100);
- }
- if (left_button || right_button) // reset timeout
- timer2.set(5000);
- if (keypress_delay) {
- if (timer2.remaining() < 4000)
- keypress_delay = false;
- }
- if (timer2.poll()) {
- setState(STATE_NORMAL);
- if (target_temp != last_target) {
- stored_target_write(target_temp);
- }
- }
- break;
+ break;
+ }
+ }
+ }
+
+ long current_temp = 0;
+ Statistic stats;
+ if (true) { // timerPID.poll(200)) {
+ //long t[NUMSAMPLES];
+ unsigned long now = millis();
+
+ for (n = 0; n < NUMSAMPLES; ++n) {
+ //t[n] = getTemperature();
+ long v = getTemperature();
+ stats.add(v);
+ delay(10);
+ }
+ //for (n = 0 ; n < NUMSAMPLES; ++n) {
+ // current_temp += t[n];
+ //}
+ //current_temp /= NUMSAMPLES;
+ current_temp = static_cast<long>(stats.average());
+
+ // Use PID algorithm to determine the 'on' time.
+ SetPoint = target_temp;
+ Input = current_temp;
+ //pid.Compute();
+ pid.SetPoint(target_temp);
+ Output = pid.update(now, current_temp);
+
+ if (now - windowStartTime > WindowSize) {
+ windowStartTime += WindowSize;
+ }
+ if (Output > now - windowStartTime) {
+ setHeating(true);
+ } else {
+ setHeating(false);
}
}
- if (timer0.poll(1000)) {
+ if (timerPrint.poll(1000)) {
+ unsigned long now = millis();
+ String s;
+ char sp = ' ';
+ s.concat(now);
+ s.concat(sp);
+ s.concat(static_cast<int>(SetPoint));
+ s.concat(sp);
+ s.concat(static_cast<int>(Input));
+ s.concat(sp);
+ s.concat(static_cast<int>(Output));
+ s.concat(sp);
+ s.concat(static_cast<int>(stats.pop_stdev()));
+ s.concat(sp);
+ uint8_t sum = 0;
+ for (int n = 0; n < s.length(); ++n) {
+ sum += static_cast<uint8_t>(s[n]);
+ }
+ Serial.print(s);
+ Serial.println(sum, HEX);
+#if 0
+ Serial.print(now);
+ Serial_print(PSTR(" "));
+ Serial_print_value_space(SetPoint);
+ Serial_print_value_space(Input);
+ Serial_print_value_space(Output);
+ // pin 8 == PORTB pin 0: low for on
+ Serial.println((PORTB&1) ? "off " : "on ");
+#endif
if (display_state != STATE_FLASHING) {
- Serial.print(millis());
- Serial.print(" ");
- for (n = 0; n < NUMSAMPLES; ++n) {
- t[n] = getTemperature();
- delay(10);
- }
- for (n = 0 ; n < NUMSAMPLES; ++n) {
- v += t[n];
- }
- v /= NUMSAMPLES;
- // setting pin 8 low puts the MOC led _on_
- if (v < target_temp) {
- digitalWrite(MOC_PIN, LOW);
- digitalWrite(LED_PIN, HIGH);
- } else {
- digitalWrite(MOC_PIN, HIGH);
- digitalWrite(LED_PIN, LOW);
- }
- // pin 8 == PORTB pin 0: low for on
- Serial.print((PORTB&1) ? "off " : "on ");
- Serial.println(v);
- setDisplay(v);
+ setDisplay(current_temp);
}
}
}