/**********************************
* Annealing Circuit *
* By SMO 11/25/2017 *
**********************************/
#include <Button.h> //note that this is a different version than the standard button help, it can be found at https://github.com/slavianp/improc/tree/master/Arduino/src/Library
#include <TicksPerSecond.h> // this can be found at https://github.com/slavianp/improc/tree/master/Arduino/src/Library
#include <RotaryEncoderAcelleration.h> // this can be found at https://github.com/slavianp/improc/tree/master/Arduino/src/Library
#include <Metro.h>
#include <elapsedMillis.h>
#include <LiquidCrystal_I2C.h>
#include <Servo.h>
#include <EEPROM.h>
#include <PinChangeInterrupt.h>
// Assign I/O pins (Traffic Cop)
#define Encoder_A_Pin 2
#define Encoder_B_Pin 3
#define Encoder_Button_Pin 4
#define Encoder_LED_BLU_Pin 5
#define Encoder_LED_GRN_Pin 6
#define Encoder_LED_RED_Pin 7
#define Relay_Output_Pin 8
#define Case_Sensor_Pin 9
#define Flow_Sensor_Pin 10
#define Drop_Servo_Pin 11
#define Solenoid_Pin 12
#define Analog_Amps_Pin A1
#define Analog_Volt_Pin A2
// Initialize I/O values
boolean Relay_State = LOW;
boolean Pushbutton_State = LOW;
boolean Case_Sensor_State = LOW;
boolean Solenoid_State = LOW;
float Analog_Pot_Val = 0;
float Analog_Volt_Val = 0;
float Analog_Amps_Val = 0;
float Max_Amps_Val = 0;
// Initialize other values
int Seq_Step_Num = 1;
int Timer_Setpt = 0;
int Timer_Display = 0;
int Load_Delay_Setpt = 0500; //Delay time in mSEC, gives time for a brass to get into position for annealing
int Drop_Delay_Setpt = 0010; //Delay time in mSEC, gives time for the servo to get into drop position
int Case_Delay_Setpt = 0500; //Delay time in mSEC, gives time for brass to drop and clear the servo
int Home_Delay_Setpt = 0010; //Delay time in mSEC, gives time for the servo to get into home position
int Drop_Pos_Setpt = 90; //in degrees
int Home_Pos_Setpt = 0; //in degrees
volatile long Flow_Total = 0;
float Flow_Rate = 0;
volatile boolean Button_Toggle_State = 0;
float Joules_Total = 0;
// Set Memory location for last time setpoint, setpoint will be saved when power is down
int eeAddress = 0;
float eeData = 0;
// Sequence Descriptions
char *Seq_Step_Desc[] = {
// 12345678901234567892 20 characters max
"Step 0; not used ",
"Step 1; press start ",
"Step 2; load case ",
"Step 3; load delay ",
"Step 4; start anneal",
"Step 5; time anneal ",
"Step 6; stop anneal ",
"Step 7; drop pos ",
"Step 8; drop delay ",
"Step 9; case clear? ",
"Step10; clear delay ",
"Step11; home pos ",
"Step12; home delay ",
"Step13; Check Flow ",
"Step14; return ",
"Step15; HIGH AMPS "
};
// Set the LCD address and 20x4 display also define extra characters
LiquidCrystal_I2C lcd(0x20, 20, 4);
char Print_Buffer[40] = "";
#if defined(ARDUINO) && ARDUINO >= 100
#define printByte(args) write(args);
#else
#define printByte(args) print(args,BYTE);
#endif
uint8_t Up_Arrow[8] = {0x04, 0x0E, 0x15, 0x04, 0x04, 0x04, 0x04, 0x00}; //Define Up Arrow for LCD
uint8_t Dn_Arrow[8] = {0x00, 0x04, 0x04, 0x04, 0x04, 0x15, 0x0E, 0x04}; //Define Down Arrow for LCD
//Configure Metro Objects
Metro LCD_Print_Data = Metro(10);
Metro Analog_Inputs_Read = Metro(10);
Metro Flow_Calculation = Metro(10000);
Metro Joules_Calculation = Metro(100);
//Configure Timers
elapsedMillis Timer_Actual;
elapsedMillis Button_Debounce;
//Configure Servos
Servo Drop_Servo;
//Configure Encoder
RotaryEncoderAcelleration Encoder_Knob;
void UpdateEncoder(){
Encoder_Knob.update();
}
/***************************
* Setup Program Begin *
***************************/
void setup() {
//Assign pin modes
pinMode(Relay_Output_Pin, OUTPUT);
pinMode(Solenoid_Pin, OUTPUT);
pinMode(Case_Sensor_Pin, INPUT_PULLUP);
pinMode(Flow_Sensor_Pin, INPUT);
pinMode(Encoder_Button_Pin, INPUT);
pinMode(Encoder_LED_BLU_Pin, OUTPUT);
pinMode(Encoder_LED_GRN_Pin, OUTPUT);
pinMode(Encoder_LED_RED_Pin, OUTPUT);
//Get last setpoint before power down to set initial encoder setpoint
EEPROM.get(eeAddress, eeData);
//Initialize Encoder
Encoder_Knob.initialize(Encoder_A_Pin, Encoder_B_Pin);
Encoder_Knob.setMinMax(50, 2000);
Encoder_Knob.setPosition(eeData); //Initialize Encoder Position with saved data
attachInterrupt(0, UpdateEncoder, CHANGE);
//Initialize LCD
lcd.begin();
lcd.backlight();
lcd.createChar(0, Up_Arrow);
lcd.createChar(1, Dn_Arrow);
//Set initial postion for Drop Servo
Drop_Servo.attach(Drop_Servo_Pin);
Drop_Servo.write(Drop_Pos_Setpt);
delay(Drop_Delay_Setpt);
Drop_Servo.write(Home_Pos_Setpt);
delay(Drop_Delay_Setpt);
//Set the flow meter pin change interrupt
attachPinChangeInterrupt(digitalPinToPinChangeInterrupt(Flow_Sensor_Pin), Flow_Rising, RISING);
//Sense positive transition of button interrupt
attachPinChangeInterrupt(digitalPinToPinChangeInterrupt(Encoder_Button_Pin), Push_Button_Toggle, RISING);
}
/***************************
* Main Program Loop Begin *
***************************/
void loop() {
//Write Digital Outputs
digitalWrite(Relay_Output_Pin, Relay_State);
digitalWrite(Solenoid_Pin, Solenoid_State);
// Read Digital Inputs
// Pushbutton_State = !digitalRead(Pushbutton_Pin); //this button is normally closed
Pushbutton_State = digitalRead(Encoder_Button_Pin); //this button is normally open
Case_Sensor_State = digitalRead(Case_Sensor_Pin); //This sensor is normally open
// Read Analog Inputs
if(Analog_Inputs_Read.check() == 1){
//To smooth analog signals use a single Pole Digital Filter [out=wt*out+(1-wt)*new]
Analog_Amps_Val = 0.50 * Analog_Amps_Val + 0.50 * ((analogRead(Analog_Amps_Pin) / 1023.0 * 5000.0 - 5000.0/2.0)/100.0);
Analog_Volt_Val = 0.75 * Analog_Volt_Val + 0.25 * (analogRead(Analog_Volt_Pin) * 0.079101); // span is 80 volts or 80/1023=0.078201 adjust as needed for accuracy
}
// Calculate Flow Rate
if(Flow_Calculation.check() == 1){
Flow_Rate = (Flow_Total / 10.0) * 0.68913; //Flow meter; 1380 pulses/Liter or 5224 pulses/gal, gph = pps * 3600/5224 or .68913
Flow_Total = 0;
}
// Read Encoder
long Encoder_Val = Encoder_Knob.getPosition();
// Calculate Timer Setpoint
Timer_Setpt = float(Encoder_Val) * 10.0;
// Calculate Joules
if(Joules_Calculation.check() == 1 & Seq_Step_Num == 5){
Joules_Total = Joules_Total + Analog_Amps_Val * Analog_Volt_Val * 0.100;
}
// Set Debounce timer to max value to avoid rollover of value
if(Button_Debounce >= 10000){
Button_Debounce = 10000;
}
// Begin Sequence Logic
switch(Seq_Step_Num){
// Step 1 Wait for pushbutton toggle ON
case(1):
//Encoder LED's are reverse logic, Zero is ON
digitalWrite(Encoder_LED_BLU_Pin, 1);
digitalWrite(Encoder_LED_GRN_Pin, 1);
digitalWrite(Encoder_LED_RED_Pin, 1);
if(Button_Toggle_State == HIGH){
//Encoder LED's are reverse logic, Zero is ON
digitalWrite(Encoder_LED_BLU_Pin, 1);
digitalWrite(Encoder_LED_GRN_Pin, 0);
digitalWrite(Encoder_LED_RED_Pin, 1);
Seq_Step_Num++;
break;
}
break;
// Step 2 Wait for Case, also store setpoint if changed
case(2):
//Only store new setpoint if it has changed
if(eeData != Timer_Setpt){
eeData = Timer_Setpt / 10.0;
EEPROM.put(eeAddress, eeData);
}
if(Case_Sensor_State == LOW){
Timer_Actual = 0;
Seq_Step_Num++;
break;
}
if(Button_Toggle_State == LOW){
Seq_Step_Num = 1;
break;
}
break;
// Step 3 Wait for Load Case Delay
case(3):
if(Case_Sensor_State == LOW & Timer_Actual >= Load_Delay_Setpt){
Seq_Step_Num++;
break;
}
if(Case_Sensor_State == HIGH){
Seq_Step_Num = 2;
break;
}
break;
// Step 4 Initialize annealing timer and Energize Relay
case(4):
Timer_Actual = 0;
Max_Amps_Val = Analog_Amps_Val;
Relay_State = HIGH;
Joules_Total = 0;
Joules_Calculation.reset();
//Encoder LED's are reverse logic, Zero is ON
digitalWrite(Encoder_LED_BLU_Pin, 1);
digitalWrite(Encoder_LED_GRN_Pin, 1);
digitalWrite(Encoder_LED_RED_Pin, 0);
Seq_Step_Num++;
break;
// Step 5 Wait for Timer at Setpoint
case(5):
Timer_Display = Timer_Actual;
Max_Amps_Val = Analog_Amps_Val;
if(Timer_Actual >= Timer_Setpt || Button_Toggle_State == LOW){
Timer_Display = Timer_Actual;
Seq_Step_Num++;
break;
}
// if the amps go high then goto step 15
if(Analog_Amps_Val > 14.0){
Seq_Step_Num = 15;
break;
}
break;
// Step 6 Denergize Relay
case(6):
Relay_State = LOW;
//Encoder LED's are reverse logic, Zero is ON
digitalWrite(Encoder_LED_BLU_Pin, 0);
digitalWrite(Encoder_LED_GRN_Pin, 1);
digitalWrite(Encoder_LED_RED_Pin, 1);
Seq_Step_Num++;
break;
// Step 7 Set Drop Servo to drop position & Reset Timer
case(7):
Timer_Actual = 0;
Drop_Servo.write(Drop_Pos_Setpt);
Solenoid_State = HIGH;
Seq_Step_Num++;
break;
// Step 8 Wait for drop position
case(8):
if(Timer_Actual >= Drop_Delay_Setpt){
Seq_Step_Num++;
break;
}
break;
// Step 9 Check for case clear & Reset Timer
case(9):
Timer_Actual = 0;
if(Case_Sensor_State == HIGH){
Seq_Step_Num++;
break;
}
break;
// Step 10 Wait for case to clear
case(10):
if(Timer_Actual >= Case_Delay_Setpt){
Seq_Step_Num++;
break;
}
break;
// Step 11 Set Servo to Home Position & Reset Timer
case(11):
Timer_Actual = 0;
Drop_Servo.write(Home_Pos_Setpt);
Solenoid_State = LOW;
Seq_Step_Num++;
break;
// Step 12 Wait for servo home
case(12):
if(Timer_Actual >= Home_Delay_Setpt){
Seq_Step_Num++;
break;
}
break;
// Step 13 Check for flow
case(13):
if(Flow_Rate >= 4.0 || Button_Toggle_State == LOW){
Seq_Step_Num++;
break;
}
break;
// Step 14 Return to Step 1
case(14):
if(Button_Toggle_State == HIGH){
digitalWrite(Encoder_LED_BLU_Pin, 1);
digitalWrite(Encoder_LED_GRN_Pin, 0);
digitalWrite(Encoder_LED_RED_Pin, 1);
Seq_Step_Num = 2;
break;
}
else{
Seq_Step_Num = 1;
break;
}
break;
// Step 15 HIGH AMPS
case(15):
Relay_State = LOW;
//Encoder LED's are reverse logic, Zero is ON
digitalWrite(Encoder_LED_BLU_Pin, 0);
digitalWrite(Encoder_LED_GRN_Pin, 1);
digitalWrite(Encoder_LED_RED_Pin, 1);
if(Button_Toggle_State == LOW){
Seq_Step_Num = 7;
break;
}
break;
}
// Write to LCD Display except for last 150mSec of timer to prevent overshoot from writing to I2C, didn't want the complexity of a timer interrupt
if(LCD_Print_Data.check() == 1 && (Timer_Actual <= Timer_Setpt - 150 || Seq_Step_Num != 5)){
//1st LCD Row
lcd.home();
lcd.print("Set;");
lcd.setCursor(4,0);
dtostrf(float(Timer_Setpt)/1000, 5, 2, Print_Buffer);
lcd.print(Print_Buffer);
lcd.print(" Act ");
dtostrf(float(Timer_Display)/1000, 6, 3, Print_Buffer);
lcd.print(Print_Buffer);
//2nd LCD Row
lcd.setCursor(0,1);
dtostrf(Analog_Volt_Val, 4, 1, Print_Buffer);
lcd.print(Print_Buffer);
lcd.print("V");
lcd.setCursor(6,1);
dtostrf(Joules_Total, 5, 0, Print_Buffer);
lcd.print(Print_Buffer);
lcd.print("J");
lcd.setCursor(12,1);
if(Seq_Step_Num == 5){
dtostrf(Analog_Amps_Val, 6, 1, Print_Buffer);
lcd.print(Print_Buffer);
lcd.print(" A");
}
else{
dtostrf(Max_Amps_Val, 6, 1, Print_Buffer);
lcd.print(Print_Buffer);
lcd.printByte(0); //Print Up Arrow
lcd.print("A");
}
//3rd LCD Row
lcd.setCursor(0,2);
lcd.print("F; ");
dtostrf(Flow_Rate, 4, 1, Print_Buffer);
lcd.print(Print_Buffer);
lcd.print(" gph");
lcd.setCursor(13,2);
if(Case_Sensor_State == LOW){
lcd.print("Case; Y");
}
else{
lcd.print("Case; N");
}
//4th LCD Row
lcd.setCursor(0,3);
lcd.print(Seq_Step_Desc[Seq_Step_Num]);
}
}
/***************************
* Flow Meter Interrupt *
***************************/
void Flow_Rising(void){
Flow_Total++;
}
/*********************************
* Positive Transition Sense *
*********************************/
void Push_Button_Toggle(void){
if(Button_Debounce >= 1000){
if(Button_Toggle_State == HIGH && Button_Debounce >= 100){
Button_Toggle_State = LOW;
}
else{
Button_Toggle_State = HIGH;
}
Button_Debounce = 0;
}
}