/* Solder Reflow Plate Sketch
 *  H/W - Ver 2.4
 *  S/W - Ver 1.0
 *  by Chris Halsall     */
 /* https://youtu.be/uxvzKvm6T5E  */

/* To prepare
 * 1) Install MiniCore in additional boards; (copy into File->Preferences->Additional Boards Manager URLs)
 *     https://mcudude.github.io/MiniCore/package_MCUdude_MiniCore_index.json
 * 2) Then add MiniCore by searching and installing (Tools->Board->Board Manager)
 * 3) Install Adafruit_GFX and Adafruit_SSD1306 libraries (Tools->Manage Libraries)
 */

/* To program
 *  1) Select the following settings under (Tools)
 *      Board->Minicore->Atmega328 
 *      Clock->Internal 8MHz
 *      BOD->BOD 2.7V
 *      EEPROM->EEPROM retained
 *      Compiler LTO->LTO Disabled
 *      Variant->328P / 328PA
 *      Bootloader->No bootloader
 *  2) Set programmer of choice, e.g.'Arduino as ISP (MiniCore)', 'USB ASP', etc, and set correct port.
 *  3) Burn bootloader (to set fuses correctly)
 *  4) Compile and upload
 */

#include <SPI.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
#include <EEPROM.h>

//Version Definitions
static const PROGMEM float hw = 2.4;
static const PROGMEM float sw = 1.0;

//Screen Definitions
#define SCREEN_WIDTH 128
#define SCREEN_HEIGHT 32
#define SCREEN_ADDRESS 0x3C   //I2C Address
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, -1); //Create Display

//Pin Definitions
#define mosfet 3
#define upsw 6
#define dnsw 5
#define temp 16   //A2
#define vcc 14    //A0

//Temperature Info
byte maxTempArray[] = { 140, 150, 160, 170, 180 };
byte maxTempIndex = 0;
byte tempIndexAddr = 1;

//Voltage Measurement Info
float vConvert = 52.00;
float vMin = 10.50;

//Solder Reflow Plate Logo
static const uint8_t PROGMEM logo[] = { 
  0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 
  0x00, 0x00, 0x00, 0x01, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1f, 0x00, 0x00, 0x00, 0x00, 0x00, 
  0x00, 0x00, 0x00, 0x01, 0x80, 0x00, 0x00, 0x1f, 0xc0, 0x00, 0x31, 0x80, 0x00, 0x00, 0x00, 0x00, 
  0x1f, 0xe0, 0x03, 0x01, 0x80, 0x00, 0x00, 0x30, 0x70, 0x00, 0x21, 0x80, 0x00, 0x00, 0x00, 0x00, 
  0x10, 0x20, 0x03, 0x00, 0xc7, 0x80, 0x00, 0x20, 0x18, 0xf0, 0x61, 0x80, 0x00, 0x00, 0x00, 0x00, 
  0x18, 0x00, 0x03, 0x3e, 0xcc, 0xc0, 0xc0, 0x04, 0x19, 0x98, 0x61, 0x80, 0x00, 0x00, 0x00, 0x00, 
  0x1c, 0x01, 0xf3, 0x77, 0xd8, 0xc7, 0xe0, 0x06, 0x33, 0x18, 0x61, 0x8f, 0x88, 0x00, 0x00, 0x00, 
  0x06, 0x03, 0x3b, 0x61, 0xd0, 0xc6, 0x00, 0x07, 0xe2, 0x18, 0x61, 0x98, 0xd8, 0x04, 0x00, 0x00, 
  0x01, 0xc6, 0x0b, 0x60, 0xd9, 0x86, 0x00, 0x06, 0x03, 0x30, 0xff, 0xb0, 0x78, 0x66, 0x00, 0x00, 
  0x40, 0xe4, 0x0f, 0x60, 0xdf, 0x06, 0x00, 0x07, 0x03, 0xe0, 0x31, 0xe0, 0x78, 0x62, 0x00, 0x00, 
  0x40, 0x3c, 0x0f, 0x61, 0xd8, 0x06, 0x00, 0x07, 0x83, 0x00, 0x31, 0xe0, 0x78, 0x63, 0x00, 0x00, 
  0x60, 0x36, 0x1b, 0x63, 0xc8, 0x02, 0x00, 0x02, 0xc1, 0x00, 0x18, 0xb0, 0xcc, 0xe2, 0x00, 0x00, 
  0x30, 0x33, 0x3b, 0x36, 0x4e, 0x03, 0x00, 0x02, 0x61, 0xc0, 0x0c, 0x99, 0xcd, 0xfe, 0x00, 0x00, 
  0x0f, 0xe1, 0xe1, 0x3c, 0x03, 0xf3, 0x00, 0x02, 0x38, 0x7e, 0x0c, 0x8f, 0x07, 0x9c, 0x00, 0x00, 
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 
  0x00, 0x00, 0x00, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 
  0x7f, 0x84, 0x00, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 
  0xc0, 0xe4, 0x00, 0x18, 0x38, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 
  0x04, 0x3c, 0x3c, 0x18, 0x6c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 
  0x04, 0x1e, 0x06, 0x7f, 0xc6, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 
  0x04, 0x3e, 0x03, 0x18, 0x86, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 
  0x04, 0x36, 0x7f, 0x19, 0x8c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 
  0x07, 0xe6, 0xc7, 0x19, 0xf8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 
  0x06, 0x07, 0x83, 0x18, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 
  0x06, 0x07, 0x81, 0x18, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 
  0x06, 0x06, 0xc3, 0x98, 0x70, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 
  0x02, 0x04, 0x7e, 0x08, 0x3f, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
};
static const uint8_t logo_width = 128;
static const uint8_t logo_height = 27;

//Heating Animation
static const uint8_t PROGMEM heat_animate[] = {
  0b00000001, 0b00000000,
  0b00000001, 0b10000000,
  0b00000001, 0b10000000,
  0b00000001, 0b01000000,
  0b00000010, 0b01000000,
  0b00100010, 0b01000100,
  0b00100100, 0b00100100,
  0b01010101, 0b00100110,
  0b01001001, 0b10010110,
  0b10000010, 0b10001001,
  0b10100100, 0b01000001,
  0b10011000, 0b01010010,
  0b01000100, 0b01100010,
  0b00100011, 0b10000100,
  0b00011000, 0b00011000,
  0b00000111, 0b11100000
};
static const uint8_t heat_animate_width = 16;
static const uint8_t heat_animate_height = 16;

//Tick
static const uint8_t PROGMEM tick[] = {
  0b00000000, 0b00000100,
  0b00000000, 0b00001010,
  0b00000000, 0b00010101,
  0b00000000, 0b00101010,
  0b00000000, 0b01010100,
  0b00000000, 0b10101000,
  0b00000001, 0b01010000,
  0b00100010, 0b10100000,
  0b01010101, 0b01000000,
  0b10101010, 0b10000000,
  0b01010101, 0b00000000,
  0b00101010, 0b00000000,
  0b00010100, 0b00000000,
  0b00001000, 0b00000000,
  0b01111111, 0b11100000
};
static const uint8_t tick_width = 16;
static const uint8_t tick_height = 15;

void setup() {
  
  //Pin Direction control
  pinMode(mosfet,OUTPUT);
  digitalWrite(mosfet,LOW);
  pinMode(upsw,INPUT);
  pinMode(dnsw,INPUT);
  pinMode(temp,INPUT);
  pinMode(vcc,INPUT);

  //Pull saved values from EEPROM
  maxTempIndex = EEPROM.read(tempIndexAddr) % sizeof(maxTempArray);

  //Enable Fast PWM with no prescaler
  TCCR2A = _BV(COM2A1) | _BV(COM2B1) | _BV(WGM21) | _BV(WGM20);
  TCCR2B = _BV(CS20);

  //Start-up Diplay
  display.begin(SSD1306_SWITCHCAPVCC, SCREEN_ADDRESS);
  display.clearDisplay();
  display.setTextSize(1);
  display.setTextColor(SSD1306_WHITE);
  display.setCursor(0,0);
  display.drawBitmap(0, 0, logo, logo_width, logo_height, SSD1306_WHITE);
  display.setCursor(80,16);
  display.print(F("S/W V"));
  display.print(sw, 1);
  display.setCursor(80,24);
  display.print(F("H/W V"));
  display.print(hw, 1);
  display.display();
  delay(3000);

  //Go to main menu
  main_menu();
}

void main_menu() {
  //Debounce
  while(!digitalRead(upsw) || !digitalRead(dnsw)) {  }
  
  int x = 0;  //Display change counter
  int y = 200; //Display change max (modulused below)
  while(1) {
    analogWrite(mosfet,0); //Ensure MOSFET off
    display.clearDisplay();
    display.setTextSize(1);
    display.drawRoundRect( 0, 0, 83, 32, 2, SSD1306_WHITE);

    //Button Logic
    if(!digitalRead(upsw) || !digitalRead(dnsw)) { //If either button pressed
      delay(100);
      if(!digitalRead(upsw) && !digitalRead(dnsw)) { //If both buttons pressed
        if(!heat(maxTempArray[maxTempIndex])) {
          cancelledPB();
          main_menu();
        }
        else {
          coolDown();
          completed();
          main_menu();
        }
      }
      if(!digitalRead(upsw) && maxTempIndex < sizeof(maxTempArray) - 1) { //If upper button pressed
        maxTempIndex++;
        EEPROM.update(tempIndexAddr, maxTempIndex);
      }
      if(!digitalRead(dnsw) && maxTempIndex > 0) { //If lower button pressed
        maxTempIndex--;
        EEPROM.update(tempIndexAddr, maxTempIndex);
      }
    }

    //Change Display (left-side)
    if( x < (y * 0.5)) {
      display.setCursor(3,4);
      display.print(F("PRESS BUTTONS"));
      display.drawLine( 3, 12, 79, 12, SSD1306_WHITE); 
      display.setCursor(3,14);
      display.print(F(" Change  MAX"));
      display.setCursor(3,22);
      display.print(F(" Temperature"));
    }
    else {
      display.setCursor(3,4);
      display.print(F("HOLD  BUTTONS"));
      display.drawLine( 3, 12, 79, 12, SSD1306_WHITE ); 
      display.setCursor(3,18);
      display.print(F("Begin Heating"));
    }
    x = ( x + 1 ) % y; //Display change increment and modulus
    
    //Update Display (right-side)
    display.setCursor(95,6);
    display.print(F("TEMP"));
    display.setCursor(95,18);
    display.print(maxTempArray[maxTempIndex]);
    display.print(F("C"));
    display.display();
  }
}

bool heat(byte maxTemp) {
  //Debounce
  while(!digitalRead(upsw) || !digitalRead(dnsw)) {  }
  
  //Heating Display
  display.clearDisplay();
  display.setTextSize(2);
  display.setCursor(22,4);
  display.print(F("HEATING"));
  display.setTextSize(1);
  display.setCursor(52,24);
  display.print(maxTemp);
  display.print(F("C"));
  display.display();
  delay(3000);

  //Heater Control Variables
  /*  Heater follows industry reflow graph. Slow build-up to 'warmUp' temp. Rapid ascent
   *  to 'maxTemp'. Then descent to room temperature. 
   */
  //byte maxTemp; //Declared in function call
  byte maxPWM = 0.70 * maxTemp; //Temperatures (in PWM / 255) influenced by paste temperature
  byte warmUpTemp = 0.75 * maxTemp;
  byte warmUpPWM = 0.72 * warmUpTemp;
  float t; //Used to store current temperature
  float v; //Used to store current voltage
  byte pwmVal = 0; //PWM Value applied to MOSFET
  unsigned long eTime = (millis() / 1000) + (8*60); //Used to store the end time of the heating process, limited to 8 mins

  //Other control variables
  int x = 0;  //Heat Animate Counter
  int y = 80; //Heat Animate max (modulused below)
  
  while(1) {
    //Button Control
    if(!digitalRead(upsw) || !digitalRead(dnsw)) {
      analogWrite(mosfet, 0);
      return 0;
    }

    //Check Heating not taken more than 8 minutes
    if(millis() / 1000 > eTime) {
      analogWrite(mosfet, 0);
      cancelledTimer();
    }

    //Measure Values
    t = getTemp();
    v = getVolts();

    //Reflow Profile
    if (t < warmUpTemp) { //Warm Up Section
      if (pwmVal != warmUpPWM) { pwmVal++; } //Slowly ramp to desired PWM Value
      if (v < vMin && pwmVal > 1) { pwmVal = pwmVal - 2; } //Reduce PWM Value if V drops too low but not unless it is still above 1 (avoid overflow/underflow)
    }
    else if (t < maxTemp) { //Push to maximum temp
      if (pwmVal != maxPWM) { pwmVal++; } //Slowly ramp to desired PWM Value
      if (v < vMin && pwmVal > 1) { pwmVal = pwmVal - 2; } //Reduce PWM Value if V drops too low but not unless it is still above 1 (avoid overflow/underflow)
    }
    else { //Heating Complete, return
      analogWrite(mosfet, 0);
      break;
    }
    if (pwmVal > maxPWM ) { pwmVal = maxPWM; } //Catch incase of runaway 

    //MOSFET Control
    analogWrite(mosfet, pwmVal);

    //Heat Animate Control
    display.clearDisplay();
    display.drawBitmap( 0, 3, heat_animate, heat_animate_width, heat_animate_height, SSD1306_WHITE);
    display.drawBitmap( 112, 3, heat_animate, heat_animate_width, heat_animate_height, SSD1306_WHITE);
    display.fillRect( 0, 3, heat_animate_width, heat_animate_height * (y - x) / y, SSD1306_BLACK);
    display.fillRect( 112, 3, heat_animate_width, heat_animate_height * (y - x) / y, SSD1306_BLACK);
    x = ( x + 1 ) % y; //Heat animate increment and modulus

    //Update display
    display.setTextSize(2);
    display.setCursor(22,4);
    display.print(F("HEATING"));
    display.setTextSize(1);
    display.setCursor(20,24);
    display.print(F("~"));
    display.print(v,1);
    display.print(F("V"));
    if( t >= 100 )      { display.setCursor(78,24); }
    else if ( t >= 10 ) { display.setCursor(81,24); }
    else                { display.setCursor(84,24); }
    display.print(F("~"));
    display.print(t,0);
    display.print(F("C"));
    display.display();
  }
}

void cancelledPB() { //Cancelled via push button
  //Debounce
  while(!digitalRead(upsw) || !digitalRead(dnsw)) {  }

  //Update Display
  display.clearDisplay();
  display.drawRoundRect( 22, 0, 84, 32, 2, SSD1306_WHITE );
  display.setCursor(25,4);
  display.print(F("  CANCELLED"));
  display.drawLine( 25, 12, 103, 12, SSD1306_WHITE );
  display.setCursor(25,14);
  display.println(" Push button");
  display.setCursor(25,22);
  display.println("  to return");
  display.setTextSize(3);
  display.setCursor(5,4);
  display.print(F("!"));
  display.setTextSize(3);
  display.setCursor(108,4);
  display.print(F("!"));
  display.setTextSize(1);
  display.display();
  delay(50);

  //Wait to return on any button press
  while(digitalRead(upsw) && digitalRead(dnsw)) {  }
}

void cancelledTimer() { //Cancelled via 5 minute Time Limit
  //Debounce
  while(!digitalRead(upsw) || !digitalRead(dnsw)) {  }

  //Initiate Swap Display
  int x = 0;  //Display change counter
  int y = 150; //Display change max (modulused below)

  //Wait to return on any button press
  while(digitalRead(upsw) && digitalRead(dnsw)) {
    //Update Display
    display.clearDisplay();
    display.drawRoundRect( 22, 0, 84, 32, 2, SSD1306_WHITE );
    display.setCursor(25,4);
    display.print(F("  TIMED OUT"));
    display.drawLine( 25, 12, 103, 12, SSD1306_WHITE );

    //Swap Main Text
    if( x < (y * 0.3)) {
      display.setCursor(25,14);
      display.println(" Took longer");
      display.setCursor(25,22);
      display.println(" than 5 mins");
    }
    else if( x < (y * 0.6)) {
      display.setCursor(28,14);
      display.println("Try a higher");
      display.setCursor(25,22);
      display.println(" current PSU");
    }
    else {
      display.setCursor(25,14);
      display.println(" Push button");
      display.setCursor(25,22);
      display.println("  to return");
    }
    x = ( x + 1 ) % y; //Display change increment and modulus
    
    display.setTextSize(3);
    display.setCursor(5,4);
    display.print(F("!"));
    display.setTextSize(3);
    display.setCursor(108,4);
    display.print(F("!"));
    display.setTextSize(1);
    display.display();
    delay(50);
  }
  main_menu();
}

void coolDown() {
  //Debounce
  while(!digitalRead(upsw) || !digitalRead(dnsw)) {  }
  
  float t = getTemp(); //Used to store current temperature
  
  //Wait to return on any button press, or temp below threshold
  while(digitalRead(upsw) && digitalRead(dnsw) && t > 45.00) {
    display.clearDisplay();
    display.drawRoundRect( 22, 0, 84, 32, 2, SSD1306_WHITE );
    display.setCursor(25,4);
    display.print(F("  COOL DOWN"));
    display.drawLine( 25, 12, 103, 12, SSD1306_WHITE );
    display.setCursor(25,14);
    display.println("  Still Hot");
    t = getTemp();
      if( t >= 100 ) { display.setCursor(49,22); }
      else           { display.setCursor(52,22); }
      display.print(F("~"));
      display.print(t,0);
      display.print(F("C"));
    display.setTextSize(3);
    display.setCursor(5,4);
    display.print(F("!"));
    display.setTextSize(3);
    display.setCursor(108,4);
    display.print(F("!"));
    display.setTextSize(1);
    display.display();
  }
}

void completed() {
  //Debounce
  while(!digitalRead(upsw) || !digitalRead(dnsw)) {  }

  //Update Display
  display.clearDisplay();
  display.drawRoundRect( 22, 0, 84, 32, 2, SSD1306_WHITE );
  display.setCursor(25,4);
  display.print(F("  COMPLETED  "));
  display.drawLine( 25, 12, 103, 12, SSD1306_WHITE );
  display.setCursor(25,14);
  display.println(" Push button");
  display.setCursor(25,22);
  display.println("  to return");
  display.drawBitmap( 0, 9, tick, tick_width, tick_height, SSD1306_WHITE);
  display.drawBitmap( 112, 9, tick, tick_width, tick_height, SSD1306_WHITE);
  display.display();

  //Wait to return on any button press
  while(digitalRead(upsw) && digitalRead(dnsw)) {  }
}

float getTemp(){
  float t = 0;
  for (byte i = 0; i < 100; i++){ //Poll temp reading 100 times
    t = t + analogRead(temp);
  }
  return ((t / 100) * -1.46) + 434; //Average, convert to C, and return
}

float getVolts(){
  float v = 0;
  for (byte i = 0; i < 20; i++){ //Poll Voltage reading 20 times
    v = v + analogRead(vcc);
  }
  return v / 20 / vConvert; //Average, convert to V, and return
}

void loop() {
  // Not used
}