• This Forum is for adults 18 years of age or over. By continuing to use this Forum you are confirming that you are 18 or older. No content shall be viewed by any person under 18 in California.

Annealing machine arduino based

A few videos of an Arduino based annealing machine that was based off of Gina1's ideas and many others. I was going to just add this to Gina1's thread but the code gets really long and others might find it distracting. Anyone wanting to share code or make comments on better ways of doing things feel free.

This code uses an encoder knob to do all the user input. You can adjust the setpoint at any time, even while annealing.
Press the knob to start and the light turns green which means the machine is ready to load a case.
The light turns red when annealing. You can press the button while annealing and it will abort the process. This is useful if you do know what time to enter, enter a longer time and turn the lights in the room down low. Start the process and simply wait for the case just to start glowing, press the button. The actual time will be the time you want to adjust the setpoint at.

I'm using a DFRobot Romeo BLE Arduino board, this is overkill but I already had it, the bluetooth makes programming easy also. If your just using a plain old Arduino then I would recommend the following;
DFRobot Gravity IO Expansion Shield V7.1
You can find it on Amazon for about $17, it will make life easier as far as plugging everything in.



 
Last edited:
This is the code, it has not been optimized. Also the library for the encoder is an older one, you will need to know a little to get it into newer versions of the Arduino IDE. The newer encoder libraries were all missing something, such as acceleration or not being able to set a preset value. Also the Joules calculation could only be solved every 100 msec. I tried using it as the setpoint versus using time but realized it was less accurate because of the solve time and lack of any interrupt. The code is also setup for a solenoid, not a servo, although a servo can be used. I started out with the servo and it works as well. The solenoid just seems the better path in the long run.

Code:
/**********************************
 * 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;
  }
}
 
Last edited:
it looks like you have a servo to drop the case (Servo Drop_Servo), you use anything special there? How does the Case_Sensor_Pin work (what sort of sensor is attached to pin 9)?
 
it looks like you have a servo to drop the case (Servo Drop_Servo), you use anything special there? How does the Case_Sensor_Pin work (what sort of sensor is attached to pin 9)?

Currently the code is modified for a solenoid. The code can easily be adjusted for a servo which was my original design. The servo worked fine but was just jerry-rigged, I used JB weld to attach a phenolic board to one of the standard horn shaft attachments that was long enough to get the servo away from the inductor. I did not use a dedicated servo board so I designed it such that it could rotate 360 degrees without hitting anything.

The sensor I used was the following;
2167-ND Adafruit IR Break Beam Sensor 3mm LED
Cost was about $1.95 at Digikey, you do have to crimp on your own 2-pin and 3-pin DuPont connectors.

The voltage sensor can be made but if you want you can use the following board;
Octopus Voltage Divider Brick (Cable Included)
The current sensor is the following;
Gravity Analog 20A Current Sensor (Cable Included)
Both can be purchased at the Robotshop.com

The solenoid driver is;
IRF520 MOSFET Driver Module Arduino
Amazon has them, you will need a diode or some other protective flyback device across the solenoid coil.
 

Upgrades & Donations

This Forum's expenses are primarily paid by member contributions. You can upgrade your Forum membership in seconds. Gold and Silver members get unlimited FREE classifieds for one year. Gold members can upload custom avatars.


Click Upgrade Membership Button ABOVE to get Gold or Silver Status.

You can also donate any amount, large or small, with the button below. Include your Forum Name in the PayPal Notes field.


To DONATE by CHECK, or make a recurring donation, CLICK HERE to learn how.

Forum statistics

Threads
168,445
Messages
2,255,281
Members
81,324
Latest member
Skb2706pa
Back
Top