I built it on August of 2018. As of now (April of 2020) still using it for my soldering works since then, though it misses two features I originally planned.
We need an additional buck converter to convert the 24V supply down to 6.5V to power the Arduino. I used LM2596 based buck converter board brought from AliExpress. Please note it needs to be connected to RAW power input pin of Arduino. Connecting 6.5V supply to Arduino's 5V will damage it.
I used Arduino Pro Mini running with 16MHz clock. It has MF58 NTC to measure room temperature, which is require to measure correct temperature of soldering iron. Resistor NR1 is used to form a voltage divider with NTC. The resistor of same value of NTC, 10K, is used. The NTC resistance will be changed depending on room temperature, the voltage drop is calculated with analog input put A0. Please refer readRoomTemperature() function in the program for the logic I used to convert the voltage drop to temperature.
It also has a buzzer with NPN transistor to drive it. However, it did not work well and I did not try much on it either.
Please note that it has provision for input marked with "REED SWITCH". It was designated to help detect placing of soldering iron on stand. The information is planned to use to save power by reducing or by cutting supply to iron depending on duration of iron in stand.
I already included the feature in program. However, I could not implement it hardware level. It requires me to have soldering iron stand having some sort of sensor to detect presence of iron in stand.
I think Infra-Red LED with detector would most suitable for this.
My initial idea was to use Reed switch with a magnet attached on stand, but I found Reed switches are not that reliable from my tests. Also it required me to modify soldering iron unit to incorporate Reed switch. There are two problems with that. First, I need to place Reed switch near to heating element, which of course will fail eventually because of the heat. Second, I need to incorporate an extra wire for Reed switch signal. Standard Hakko 9000M style soldering iron has 5 wires and all already used. So, I would be require to change the cable to incorporate extra wire and I cannot directly use standard soldering irons in that case.
I hope, one day I will include this feature in my station. Probably with IR LED as mentioned above.
This part is used to interface with the buttons and rotary encoder. This is not really necessary to have this part. But it helps to improve microcontroller performance by handling imperfections of contacts within buttons and rotary encoder. Otherwise microcontroller would get interrupted multiple times in each button press.
74HC14 IC is used here, which is Schmitt-Trigger Inverter.
This part is used to provide PWM signal to gate of MOSFET. This part is also not necessary to have as default PWM frequency of Arduino is under 1KHz (490 Hz and 980 Hz depending on pins in Arduino Uno/Mini/Nano). We can just use 330Ω or 470Ω resistors to replace it. However I wanted to try a MOSFET driver. I chose circuit design from Figure 4 in Tahmid'd blog post "Low-Side MOSFET Drive Circuits and Techniques - 7 Practical Circuits".
Thermocouple within the soldering iron produce certain amounts of voltage in milli volt range depending on its temperature. That is too little and cannot be read by Arduino directly. This thermocouple amplifier amplifies the signals from thermocouple to suitable level for the Arduino to read.
It has two stages, both configured in non-inverting way. Gain of first stage is:
Similarly, gain of second stage is:
Thus total gain will be:
There are reasons for me to set the gain as 161. I planned to drive soldering iron up to 600°C. A K Type Thermocouple will produce 24.905mV at 600°C. Multiplied by the gain, it will become 4V. Supply voltage for op-amp will be 5V. Amplifying signal near to supply voltage is not good idea unless it is rail-rail op-amp.
Two Zener diodes ZD1 and ZD2 are to protect op-amps and microcontroller, in case if thermocouple wire touch 24V supply line to the heating element.
Resistor TCR1 and capacitor TCC1 together forms low pass filter. Without them, thermocouple reading will jump frequently.
We should choose op-amps suitable for reading very small signals. Those usually marked as "Instrumentation Amplifiers". They allow to set gain from 1 to 10,000 with single resistor and will have very low input offset voltage. Popular choices in that category are INA128 from Texas Instruments and AD620 from Analog Devices. However, they are expensive, usually has cost 10 times as much as normal op-amps. I had tried purchasing INA128 once, but seller did not deliver it as it went out of stock.
After further research on Internet, I chose OP07C for the job. It has low input voltage offset, with maximum of 60μV.
The program developed for Arduino is given below. Please note it was originally written to support multiple soldering irons. So, you may encounter some constructs indicating iron number. I did not remove them.
#include<PinChangeInterrupt.h>#include<PinChangeInterruptBoards.h>#include<PinChangeInterruptPins.h>#include<PinChangeInterruptSettings.h>#include<Wire.h>#include<LiquidCrystal_I2C.h>#include<EEPROM.h>int16_tEEPROMReadInt16bit(intaddress){int16_tlsb=EEPROM.read(address);int16_tmsb=EEPROM.read(address+1);return((lsb<<0)&0xFF)+((msb<<8)&0xFFFF);}voidEEPROMWriteInt16bit(intaddress,int16_tvalue){uint8_tlsb=(value&0xFF);uint8_tmsb=((value>>8)&0xFF);EEPROM.write(address,lsb);EEPROM.write(address+1,msb);}/** * To create objects helping to execute code by a specified interval. */classTimeDivision{/** * Last time the code is executed by this time division. */unsignedlonglast_time;public:/** * Initialize by passing the gap required */TimeDivision(uint16_tgap){this->last_time=0;}/* * Tells if this is time to execute. */boolisTimeToExecute(uint16_ttime_gap){return(bool)((millis()-this->last_time)>=time_gap);}/** * Set the gap start time to now. */voidset(){this->last_time=millis();}};/** * Address to 2 byte storage. */#define IRON_TEMPERATURE_STORAGE_ADDRESS 0/** * Configurable data associate with an iron which can be saved in EEPROM */structIronData{int16_ttemperature;};/** * Minimum PWM signal. */constintMINIMUM_POWER=0;/** * Maximum PWM signal. */constintMAXIMUM_POWER=255;/** * Minimum temperature allowed, so user cannot go below it. */#define MINIMUM_TEMPERATURE 0.0/** * Maximum temperature allowed, so user cannot go above it. */#define MAXIMUM_TEMPERATURE 550.0/** * Increment/decrement step to use when turning rotary encoder. */#define TEMPERATURE_INCREMENT_STEP 1/** * Time interval to update display */#define DISPLAY_REFRESH_TIME 500/** * Time interval to refresh irons. */#define IRON_REFRESH_TIME 250classPIDController{public:/** Proportional **/floatKp=4;/** Integral **/floatKi=0.2;/** Derivative **/floatKd=1.0;/** * Previous error so it can be used in next calculation. */floatlast_error=0.0;/** * Integration error. */floati_error=0.0;floatlast_input=0.0;floatoutputSum=0.0;/** * Time when previous calculations are made. */unsignedlonglast_time=0;PIDController(){// Ensure PID controller is in reset state.this->reset();}/** * Reset PID controller. */voidreset(){this->last_time=millis();this->i_error=0.0;this->last_error=0.0;this->last_input=0.0;this->outputSum=0.0;}/** * Perform PID calculation and give the result. * * @param float current_temperature * Current temperature on iron tip. * @param float target_temperature * Temperature to be maintained at iron tip. */intgetChange(floatcurrent_temperature,floattarget_temperature){unsignedlongnow=millis();// Change in time.unsignedlongdt=now-this->last_time;// Proportionalfloatp_error=target_temperature-current_temperature;// Integralthis->i_error+=(p_error+this->last_error)*(float)dt;// Derivativefloatd_error=(p_error-this->last_error)/(float)dt;// PID output = Proportional gain x current error + Integral gain * past error + Derivative gain x future errorintoutput=(int)(((float)this->Kp*p_error)+((float)this->Ki*this->i_error)+((float)this->Kd*d_error));// Record current errorthis->last_error=p_error;// Record current timethis->last_time=now;returnoutput;}/** * PID algorithm implementation taken from Arduino * PID library: https://github.com/br3ttb/Arduino-PID-Library/blob/9b4ca0e5b6d7bab9c6ac023e249d6af2446d99bb/PID_v1.cpp#L58 */intcompute(floatcurrent_temperature,floattarget_temperature){floatoutput=current_temperature;unsignedlongnow=millis();unsignedlongtimeChange=(now-this->last_time);// Compute all the working error variablesdoubleerror=target_temperature-current_temperature;doubledInput=(current_temperature-this->last_input);this->outputSum+=(this->Ki*error);// Add Proportional on Measurement.this->outputSum-=this->Kp*dInput;if(this->outputSum>MAXIMUM_POWER){this->outputSum=MAXIMUM_POWER;}elseif(this->outputSum<MINIMUM_POWER){this->outputSum=MINIMUM_POWER;}doublenew_output;new_output=this->Kp*error;/*Compute Rest of PID Output*/new_output+=this->outputSum-this->Kd*dInput;if(new_output>MAXIMUM_POWER){new_output=MAXIMUM_POWER;}elseif(new_output<MINIMUM_POWER){new_output=MINIMUM_POWER;}/*Remember some variables for next time*/this->last_input=current_temperature;this->last_time=now;return(int)new_output;}};/** * Iron is not in use and not powered. */#define IRON_STATE_STOPPED 0/** * Iron is in low temperature mode. */#define IRON_STATE_STANDBY 1/** * User has put iron on holder after use. */#define IRON_STATE_ON_HOLDER 2/** * Iron is being actively used. */#define IRON_STATE_ACTIVE 3/** * Maximum time in milliseconds an iron can be in standby mode. * On expire, iron will be powered off. */#define MAXIMUM_STANDBY_TIME 10000/** * Maximum time in milliseconds an iron can be in on holder with its set temperature.. * On expire, iron will be put in stand by mode. */#define MAXIMUM_ON_HOLDER_TIME 10000///*** IRON 1 CONNECTED PINS ***//////** * Arduino pin connected to iron stop button. */#define IRON0_STOP_BUTTON 2/** * Arduino pin connected to iron start button. */#define IRON0_START_BUTTON 3/** * Arduino pin connected to iron reed switch. */#define IRON0_REED_SWITCH_PIN 4/** * Arduino pin connected to iron heating element control. */#define IRON0_HEATER_CONTROL_PIN 5/** * Arduino pin connected to iron thermocouple amplified output. */#define IRON0_THERMOCOUPLE_READING_PIN A6// Pin connected to NTC thermistor.#define NTC_THERMISTOR_PIN A0/** * Data tables for computing temperature from thermocouple voltage readings. * @see https://srdata.nist.gov/its90/download/type_k.tab */floatthermocouple_k_tab1[]={0.0000000E+00,2.5173462E+01,-1.1662878E+00,-1.0833638E+00,-8.9773540E-01,-3.7342377E-01,-8.6632643E-02,-1.0450598E-02,-5.1920577E-04,0.0000000E+00,};floatthermocouple_k_tab2[]={0.000000E+00,2.508355E+01,7.860106E-02,-2.503131E-01,8.315270E-02,-1.228034E-02,9.804036E-04,-4.413030E-05,1.057734E-06,-1.052755E-08,};floatthermocouple_k_tab3[]={-1.318058E+02,4.830222E+01,-1.646031E+00,5.464731E-02,-9.650715E-04,8.802193E-06,-3.110810E-08,0.000000E+00,0.000000E+00,0.000000E+00,};/** * Get room temperature. * @return float */floatreadRoomTemperature(){// See http://www.circuitbasics.com/arduino-thermistor-temperature-sensor-tutorial/intadc=analogRead(NTC_THERMISTOR_PIN);// TODO: Ensure resistance is 10K for both resistor and thermistor.floatntc_resistance=10000.0*(1024.0/(float)adc-1.0);// Calculate temperature with B parameter equation:// https://en.wikipedia.org/wiki/Thermistor#B_or_%CE%B2_parameter_equation// T = 1 / ( 1/ T0 + 1/B log(R/R0)// Need to deduct 273.15 since equation calculation is in Kelvin.return1/((1/298.15)+(1/3950.0)*log(ntc_resistance/10000.0))-273.15;}/** * Class representing soldering iron. */classIron{public:/** * Iron index. */uint8_tiron_number;/** * Pin to which PWM signal for heater element to be applied. */uint8_theater_control_pin;/** * Analog input pin in which we will read amplified signal from thermocouple. */uint8_tthermoucouple_reading_pin;/** * Digital input pin in which reed switch is connected. */uint8_treed_input_pin;/** * Digital input pin where start button for the iron is connected. */uint8_tstart_button_pin;/** * Digital input pin where start button for the iron is connected. */uint8_tstop_button_pin;/** * Current temperature to be maintained on tip. */floattarget_temperature;/** * Temperature to be maintained on tip while iron is in active state. */floatset_temperature;/** * Indicates whether set_temperature value is changes since it is stored in storage. */boolset_temperature_changed=false;/** * Timestamp when set_temperature changed. */unsignedlongset_temperature_changed_time=0;/** * Iron's current state. * Initially all irons will be in stopped state. */uint8_tstate=IRON_STATE_STOPPED;/** * Starting time of active state. */unsignedlongstate_start_time=0;/** * Current PWM signal being applied to iron. * Initially no power will be applied. */intpower=0;/** * Current state of Reed switch. */uint8_treed_state=0;/** * PID controller for the iron. */PIDController*pid_controller=NULL;/** * heater_control_pin - PWM pin * thermoucouple_reading_pin - Analog input pin * reed_input_pin - Digital pin * start_button_pin - Digital pin * stop_button_pin - Digital pin */Iron(uint8_tiron_number,uint8_theater_control_pin,uint8_tthermoucouple_reading_pin,uint8_treed_input_pin,uint8_tstart_button_pin,uint8_tstop_button_pin){this->iron_number=iron_number;this->heater_control_pin=heater_control_pin;this->thermoucouple_reading_pin=thermoucouple_reading_pin;this->reed_input_pin=reed_input_pin;this->start_button_pin=start_button_pin;this->stop_button_pin=stop_button_pin;this->target_temperature=0.0;// It will be overwritten by saved value.this->set_temperature=250;}/** * Initialize iron on device start. */voidinit(){pinMode(this->heater_control_pin,OUTPUT);pinMode(this->reed_input_pin,INPUT_PULLUP);pinMode(this->start_button_pin,INPUT_PULLUP);pinMode(this->stop_button_pin,INPUT_PULLUP);this->pid_controller=newPIDController();// Get current status of Reed switch.this->reed_state=digitalRead(this->reed_input_pin);this->readTemperatureSetting();// Ensure iron is stopped initially.this->stop();}/** * Get temperature stored in permanence storage. */voidreadTemperatureSetting(){intaddress=IRON_TEMPERATURE_STORAGE_ADDRESS+this->iron_number;this->set_temperature=EEPROMReadInt16bit(address);// Make sure read temperature is within the allowed range.if(this->set_temperature>MAXIMUM_TEMPERATURE){this->set_temperature=MAXIMUM_TEMPERATURE;}elseif(this->set_temperature<MINIMUM_TEMPERATURE){this->set_temperature=MINIMUM_TEMPERATURE;}}/** * Store temperature in permanence storage. */voidwriteTemperatureSetting(){intaddress=IRON_TEMPERATURE_STORAGE_ADDRESS+this->iron_number;EEPROMWriteInt16bit(address,this->set_temperature);}/** * Stop iron. */voidstop(){this->power=0;// Immediately cut power to iron.this->applyPower();this->pid_controller->reset();this->state=IRON_STATE_STOPPED;this->state_start_time=millis();}/** * Change state of the iron. */voidsetState(uint8_tnew_state){this->state=new_state;this->state_start_time=millis();}/** * Put iron in low temperature mode. */voidputInStandBy(){this->target_temperature=150;this->setState(IRON_STATE_STANDBY);}/** * To be called when user puts iron on holder. */voidputOnHolder(){this->target_temperature=this->set_temperature;this->setState(IRON_STATE_ON_HOLDER);}/** * Activate iron for soldering. */voidactivate(){this->target_temperature=this->set_temperature;this->setState(IRON_STATE_ACTIVE);}/** * Apply PWM signal to the iron. */voidapplyPower(){analogWrite(this->heater_control_pin,this->power);}/** * Apply correct PWM signal to the iron. */voidmaintainTemperature(){//int change = this->pid_controller->getChange(this->readTemperature(), this->target_temperature);// Utilize PID controller to calculate the power to be applied.//this->power += change;this->power=this->pid_controller->compute(this->readTemperature(),this->target_temperature);if(this->power>MAXIMUM_POWER){this->power=MAXIMUM_POWER;}elseif(this->power<MINIMUM_POWER){this->power=MINIMUM_POWER;}this->applyPower();}/** * Read temperature sensor data and identify current tip temperature. */floatreadTemperature(){// Read from ADC pin which connected to amplified signal from// thermocouple.floatvoltage=analogRead(this->thermoucouple_reading_pin)*(5.0/1023.0);// 161 is the gain of our OP07C based signal amplifier, ( (2000 Ohm / 100 Ohm) + 1 ) * (1000 Ohm / 150 Ohm) + 1) = 161returnIron::thermocouple_k_mv_to_temperature(voltage*1000/161.0)+readRoomTemperature();}/** * Increment the set temperature. */voidincrementSetTemperature(){if(this->set_temperature==MAXIMUM_TEMPERATURE){// Just return doing nothing if temperature already maximum.return;}else{this->set_temperature+=TEMPERATURE_INCREMENT_STEP;// Not to set more than allowed.if(this->set_temperature>MAXIMUM_TEMPERATURE){this->set_temperature=MAXIMUM_TEMPERATURE;}// Sync set temperature to target temperature// if iron is either active or just put on holder.if(this->state==IRON_STATE_ACTIVE||this->state==IRON_STATE_ON_HOLDER){this->target_temperature=this->set_temperature;}this->set_temperature_changed=true;this->set_temperature_changed_time=millis();}}/** * Decrement the set temperature. */voiddecrementSetTemperature(){if(this->set_temperature==MINIMUM_TEMPERATURE){// Return doing nothing if temperature is already at minimum.return;}else{this->set_temperature-=TEMPERATURE_INCREMENT_STEP;// Not to set lower than allowed.if(this->set_temperature<MINIMUM_TEMPERATURE){this->set_temperature=MINIMUM_TEMPERATURE;}// Sync set temperature to target temperature if iron is being actively used.if(this->state==IRON_STATE_ACTIVE||this->state==IRON_STATE_ON_HOLDER){this->target_temperature=this->set_temperature;}this->set_temperature_changed=true;this->set_temperature_changed_time=millis();}}/** * It will get called by ISR listening to Reed switch pin. */voidreedStateChanged(){// TODO: Check possibility of direct port read to improve performance: https://jeelabs.org/2010/01/06/pin-io-performance/this->reed_state=digitalRead(this->reed_input_pin);if(this->reed_state&&this->state==IRON_STATE_ACTIVE){// Reed switch is closed and iron is being used.this->putOnHolder();}elseif((!this->reed_state)&&(this->state==IRON_STATE_ON_HOLDER||this->state==IRON_STATE_STANDBY)){// Reed switch is open and iron is either just put on holder or in stand-by mode.this->activate();}}/** * Whether the iron is on stand or not. */boolisOnHolder(){returnthis->reed_state;}/** * Convert thermocouple milli-voltage reading to temperature in Celsius. * @see https://srdata.nist.gov/its90/download/type_k.tab * * @param mv * Voltage in milli-volts. * @return float * Temperature in Celsius. */staticfloatthermocouple_k_mv_to_temperature(floatmv){float*c;if(mv>=-5.891&&mv<=0.0){c=thermocouple_k_tab1;}elseif(mv>0.0&&mv<=20.644){c=thermocouple_k_tab2;}elseif(mv>20.644&&mv<=54.886){c=thermocouple_k_tab3;}else{return-1.0;}floatt=0.0;for(intp=0;p<10;p++){t+=*(c+p)*pow(mv,p);}returnt;}};/** * Initial heating up of calibration process. * User needs to put the thermocouple in contact with iron tip. */#define CALIBRATOR_STATE_INITIAL_HEATING_UP 1#define CALIBRATOR_STATE_THERMOCOUPLE_SET_UP 2#define CALIBRATOR_STATE_COOLING 3#define CALIBRATOR_STATE_FINAL_HEATING 4#define CALIBRATOR_HEATING_UP_TEMPERATURE 400.0/** * Class holding calibrator logic. */classIronCalibrator{public:Iron*iron;uint8_tstate;booluser_ready;IronCalibrator(Iron*i){this->iron=i;this->iron->set_temperature=CALIBRATOR_HEATING_UP_TEMPERATURE;this->state=CALIBRATOR_STATE_INITIAL_HEATING_UP;this->user_ready=false;this->iron->activate();}voidperformCalibration(){if(this->state==CALIBRATOR_STATE_INITIAL_HEATING_UP){this->iron->maintainTemperature();if(this->iron->readTemperature()>=CALIBRATOR_HEATING_UP_TEMPERATURE&&this->user_ready){}}}};// Various states of application.// Irons are being used for soldering.#define APPLICATION_STATE_USING_IRONS 1// One or both irons are being calibrated.#define APPLICATION_STATE_CALIBRATING_IRON 2// I2C address of the display device.#define DISPLAY_I2C_ADDRESS 0x3C// Pin connected to buzzer.#define BUZZER_PIN 10#define BEEP_SINGLE_DURATION 50// Beep codes.#define BEEP_NOTHING 0#define BEEP_SINGLE 1#define BEEP_SINGLE_LONG 2/** * Representation of rotary encoder state. */structRotaryEncoder{boola_set;boolb_set;};// Buffer to print one line on LCD.charbuffer[20];TimeDivision// Refresh display by 500 milli seconds.display_refresh_time=TimeDivision(500),// Process irons in each 250 milli seconds.iron_process_time=TimeDivision(250),// Time gap between beeps.beep_time=TimeDivision(250);/** * Class representing the entire device. */classApplication{public:// TODO: Change once ready.staticconstintnumber_of_irons=1;Iron*irons[Application::number_of_irons];/** * Iron currently being configured/set up. * First iron will be the one always active initially. */uint8_tactive_iron=0;/** * 20x4 character LCD display. */LiquidCrystal_I2Cdisplay=LiquidCrystal_I2C(0x27,20,4);/** * Indicates application state change. Mostly used for clearing display. */boolstate_changed=false;/** * State of the application/device. * Whether iron is being used, calibrated, etc. */uint8_tstate;/** * Current state of rotary encoder. */RotaryEncoderrotary_encoder;/** * Calibrator object. * It will be created only as required. */IronCalibrator*calibrator;/** * To indicate to issue beep(s). */uint8_tbeep=0;uint8_tbeep_stage=0;Application(){this->state=APPLICATION_STATE_USING_IRONS;this->calibrator=NULL;}/** * Perform station initialization including irons. */voidinit(){// Initialize first iron.this->irons[0]=newIron(0,IRON0_HEATER_CONTROL_PIN,IRON0_THERMOCOUPLE_READING_PIN,IRON0_REED_SWITCH_PIN,IRON0_START_BUTTON,IRON0_STOP_BUTTON);this->irons[0]->init();// Make the first iron as active one.this->active_iron=0;// Initialize LCD.this->display.init();this->display.backlight();}/** * Set the active iron being configured or managed. */voidsetActiveIron(uint8_tnum){this->active_iron=num%2;}/** * Allow to change application state. */voidsetState(uint8_tnew_state){this->state=new_state;// Display needs to be cleared completely since things on// screen are going to change entirely.this->state_changed=true;}/** * Show normal front page. */voiddisplayFrontPage(){// Show active iron.if(this->number_of_irons==1){// Show both current and set temperatures.this->display.setCursor(0,0);sprintf(buffer,"%03d",round(this->irons[this->active_iron]->readTemperature()));this->display.print(buffer);sprintf(buffer,"/%03d",round(this->irons[this->active_iron]->set_temperature));this->display.print(buffer);this->display.print((char)223);// Print degree (°) symbol.this->display.print("C");if(this->irons[this->active_iron]->set_temperature_changed){this->display.print("*");}else{this->display.print(" ");}// Show status of the iron.this->display.setCursor(0,1);if(this->irons[this->active_iron]->state==IRON_STATE_STOPPED){this->display.print("Stopped ");}elseif(this->irons[this->active_iron]->state==IRON_STATE_STANDBY){this->display.print("Stand by ");}elseif(this->irons[this->active_iron]->state==IRON_STATE_ON_HOLDER){this->display.print("On holder");}else{this->display.print("Active ");}// Show power level being applied to iron.this->display.setCursor(0,2);// TODO: Make icon.sprintf(buffer,"Power %03d",this->irons[this->active_iron]->power);this->display.print(buffer);this->display.setCursor(0,3);// TODO: Make icon to show this.this->display.print(this->irons[this->active_iron]->reed_state);}}/** * Display calibration pages. */voiddisplayCalibrationPage(){if(!this->calibrator){this->display.setCursor(0,0);this->display.print("Ready for Calibration");this->display.setCursor(0,1);this->display.print((char)126);}else{if(this->calibrator->state==CALIBRATOR_STATE_INITIAL_HEATING_UP){this->display.setCursor(0,0);this->display.print("Heating up...");this->display.setCursor(0,1);this->display.println("Iron tip is heating up");// this->display.println("Put solder on tip and");// this->display.println("place thermocouple in");// this->display.println("contact.");// this->display.println("Press iron start butt-");// this->display.println("on when ready.");}}}/** * Show the correct screen. */voidrender(){// Refresh screen as necessary.if(display_refresh_time.isTimeToExecute(DISPLAY_REFRESH_TIME)){if(this->state_changed){this->display.clear();this->state_changed=false;}if(this->state==APPLICATION_STATE_USING_IRONS){this->displayFrontPage();}elseif(this->state==APPLICATION_STATE_CALIBRATING_IRON){this->displayCalibrationPage();}}}/** * This function will be called continuously while irons are being used. */voidprocessIrons(){if(iron_process_time.isTimeToExecute(IRON_REFRESH_TIME)){if(this->state==APPLICATION_STATE_USING_IRONS){unsignedlongtime_now=millis();for(inti=0;i<Application::number_of_irons;i++){switch(this->irons[i]->state){caseIRON_STATE_STOPPED:// TODO: What to do with stopped iron?break;caseIRON_STATE_STANDBY:if((time_now-this->irons[i]->state_start_time)>=MAXIMUM_STANDBY_TIME){// Iron spend enough time in stand by mode.// Let's turn it off to save power and for safety.this->irons[i]->stop();}else{this->irons[i]->maintainTemperature();}break;caseIRON_STATE_ON_HOLDER:if((time_now-this->irons[i]->state_start_time)>=MAXIMUM_ON_HOLDER_TIME){// Iron spend enough time in on holder mode.// Let's put it in stand by mode with reduced heating.this->irons[i]->putInStandBy();}else{this->irons[i]->maintainTemperature();}break;caseIRON_STATE_ACTIVE:default:this->irons[i]->maintainTemperature();break;}// Save set temperature if user changed it.// It is delayed saving to avoid writing continuously to EEPROM// which may shorten its lifetime.if(this->irons[i]->set_temperature_changed&&((time_now-(this->irons[i]->set_temperature_changed_time))>10000)){this->irons[i]->writeTemperatureSetting();this->irons[i]->set_temperature_changed=false;}}}elseif(this->state==APPLICATION_STATE_CALIBRATING_IRON){if(this->calibrator){this->calibrator->performCalibration();}}}}/** * Process requests to make beeps. */voidmakeBeep(){switch(this->beep){caseBEEP_SINGLE:// Handle single beep.if(this->beep_stage==0){digitalWrite(BUZZER_PIN,HIGH);this->beep_stage++;beep_time.set();}// Stop the beep if met the duration limit.elseif(this->beep_stage==1&&beep_time.isTimeToExecute(BEEP_SINGLE_DURATION)){digitalWrite(BUZZER_PIN,LOW);this->beep_stage--;// Finished handling the beep so reset beep to BEEP_NOTHINGthis->beep=BEEP_NOTHING;}break;caseBEEP_NOTHING:default:break;}}/** * It will get called when rotary encoder being turned clock wise direction. */voidrotaryEncoderTurningClockwise(){if(this->state==APPLICATION_STATE_USING_IRONS){this->irons[this->active_iron]->incrementSetTemperature();}}/** * It will get called when rotary encoder being turned anti-clock wise direction. */voidrotaryEncoderTurningAntiClockwise(){if(this->state==APPLICATION_STATE_USING_IRONS){this->irons[this->active_iron]->decrementSetTemperature();}}/** * It will get called by ISR when rotary encoder button is pressed. */voidrotaryEncoderButtonPressed(){if(this->state==APPLICATION_STATE_USING_IRONS){this->setState(APPLICATION_STATE_CALIBRATING_IRON);// TODO: Make it to render changes immediately.}elseif(this->state==APPLICATION_STATE_CALIBRATING_IRON){this->setState(APPLICATION_STATE_USING_IRONS);}}/** * It will get called by ISR when start button is pressed. */voidironStartButtonPressed(uint8_tiron_num){if(this->state==APPLICATION_STATE_USING_IRONS){// Make iron active if it is not already.if(this->active_iron!=iron_num){this->active_iron=iron_num;}elseif(this->irons[iron_num]->state==IRON_STATE_STOPPED){if(this->irons[iron_num]->isOnHolder()){// Iron is stopped and on stand.// Thus make iron as user just put on stand.// So it will become in stand by mode and not heated fully.this->irons[iron_num]->putInStandBy();}else{// Iron is not in stand.// Thus make iron ready for soldering.this->irons[iron_num]->activate();}}elseif(this->irons[iron_num]->state==IRON_STATE_STANDBY){// Iron is in stand by mode and user wants to heat it for soldering.// Make as if iron is just put on stand.this->irons[iron_num]->putOnHolder();}}elseif(this->state==APPLICATION_STATE_CALIBRATING_IRON){if(!this->calibrator){this->calibrator=newIronCalibrator(this->irons[iron_num]);}}}/** * It will get called by ISR when stop button is pressed. */voidironStopButtonPressed(uint8_tiron_num){if(this->state==APPLICATION_STATE_USING_IRONS){if(this->irons[iron_num]->state==IRON_STATE_ACTIVE||this->irons[iron_num]->state==IRON_STATE_STANDBY){// Always stop iron.this->irons[iron_num]->stop();}}}};/** * Arduino pin connected to rotary encoder pin A. */#define ROTARY_ENCODER_PIN_A 7 // PORTD 7/** * Arduino pin connected to rotary encoder pin B. */#define ROTARY_ENCODER_PIN_B 6 // PORTD 6/** * Arduino pin connected to rotary encoder button. */#define ROTARY_ENCODER_BUTTON 8// One and only application object.Applicationapp=Application();///<--- Interrupt Service Routines (ISRs) --->////** * ISR for rotary encoder pin a */voiddo_encoder_a(){// Low to High transition?// TODO: Check possibility of direct port read to improve performance: https://jeelabs.org/2010/01/06/pin-io-performance///if (digitalRead(ROTARY_ENCODER_PIN_A) == HIGH) {if(bitRead(PIND,7)){app.rotary_encoder.a_set=true;if(!app.rotary_encoder.b_set){// Rotating anti-clockwise.app.rotaryEncoderTurningAntiClockwise();}}// High-to-low transition?// TODO: Check possibility of direct port read to improve performance: https://jeelabs.org/2010/01/06/pin-io-performance///if (digitalRead(ROTARY_ENCODER_PIN_A) == LOW) {else{app.rotary_encoder.a_set=false;}}/** * ISR for rotary encoder pin a */voiddo_encoder_b(){// Low-to-high transition?// TODO: Check possibility of direct port read to improve performance: https://jeelabs.org/2010/01/06/pin-io-performance///if (digitalRead(ROTARY_ENCODER_PIN_B) == HIGH) {if(bitRead(PIND,6)){app.rotary_encoder.b_set=true;if(!app.rotary_encoder.a_set){// Rotating Clockwise.app.rotaryEncoderTurningClockwise();}}// High-to-low transition?// TODO: Check possibility of direct port read to improve performance: https://jeelabs.org/2010/01/06/pin-io-performance///if (digitalRead(ROTARY_ENCODER_PIN_B) == LOW) {else{app.rotary_encoder.b_set=false;}}/** * Interrupt service routine. * Rotary encode button has been pressed. */voidrotary_encoder_button_pressed(){app.rotaryEncoderButtonPressed();}/** * ISR processing iron0 start button event. */voidiron0_start_button_pressed(){app.ironStartButtonPressed(0);}/** * ISR processing iron0 stop button event. */voidiron0_stop_button_pressed(){app.ironStopButtonPressed(0);}/** * ISR processing iron0 reed switch state change. */voidiron0_reed_switch_state_changed(){app.irons[0]->reedStateChanged();}/** * Set up device for operation. */voidsetup(){// Digital pins for rotary encoderpinMode(ROTARY_ENCODER_PIN_A,INPUT);pinMode(ROTARY_ENCODER_PIN_B,INPUT);pinMode(ROTARY_ENCODER_BUTTON,INPUT);attachPCINT(digitalPinToPCINT(ROTARY_ENCODER_PIN_A),do_encoder_a,CHANGE);attachPCINT(digitalPinToPCINT(ROTARY_ENCODER_PIN_B),do_encoder_b,CHANGE);attachPCINT(digitalPinToPCINT(ROTARY_ENCODER_BUTTON),rotary_encoder_button_pressed,RISING);pinMode(IRON0_START_BUTTON,INPUT_PULLUP);pinMode(IRON0_STOP_BUTTON,INPUT_PULLUP);pinMode(IRON0_REED_SWITCH_PIN,INPUT_PULLUP);attachPCINT(digitalPinToPCINT(IRON0_START_BUTTON),iron0_start_button_pressed,RISING);attachPCINT(digitalPinToPCINT(IRON0_STOP_BUTTON),iron0_stop_button_pressed,RISING);attachPCINT(digitalPinToPCINT(IRON0_REED_SWITCH_PIN),iron0_reed_switch_state_changed,CHANGE);Serial.begin(9600);app.init();}voidloop(){app.processIrons();app.render();app.makeBeep();}
I had planned to include calibration feature in this program but I did not work on it yet. It would require an external and accurate temperature sensor. However no modification would be required in soldering station circuit. So, we can implement this feature just by modifying the program.
These are external libraries this program depends on. Library packages are provided in case if the libraries ever go out of Internet from their original locations!