John Bradnam
Published © GPL3+

DIY Soldering Station

Build your own temperature controlled Soldering Station

IntermediateFull instructions provided8 hours599
DIY Soldering Station

Things used in this project

Hardware components

Microchip ATtiny3224
×1
SMD components
1 x TM1650 SOIC, 1 x MAX6675 SOIC, 1 x AO4406 SOIC, 2 x 0.1uF 0805, 1 x 1K 0805, 1 x 10K 0805, 1 x 1N4007 DO-213AB
×1
GX-16 Male 5 pin Socket
×1
Tactile Switch, Top Actuated
Tactile Switch, Top Actuated
14mm shaft with button tops
×2
Mini360 DC-DC HM Buck Converter Step Down Power Supply Module
×1
3-Digit 7-Segment 0.56in Common Cathode Displa
×1
Hakko 907A soldering iron
Version that uses K-Type thermocouple
×1

Software apps and online services

Arduino IDE
Arduino IDE

Hand tools and fabrication machines

3D Printer (generic)
3D Printer (generic)
Soldering iron (generic)
Soldering iron (generic)

Story

Read more

Custom parts and enclosures

STL Files

Files for 3D printing

Schematics

Schematic

PCB

Eagle files

Schematic & PCB in Eagle format

Code

SolderingStationV1.ino

Arduino
/*
HAKKO 907 SOLDERING STATION

Version 1: jbrad2089@gmail.com
  - Create code for ATtiny3224
  - MAX6675 K-Type thermocouple library
  - TM1650 driving 3 Digit display
  

 --------------------------------------------------------------------------
 Arduino IDE:
 --------------------------------------------------------------------------
  BOARD: ATtiny3224/1624/1614/1604/824/814/804/424/414/404/...
  Chip: ATtiny3224
  Clock Speed: 20MHz
  millis()/micros(): "Enabled (default timer)"
  Programmer: jtag2updi (megaTinyCore)

  ATTiny3224 Pins mapped to Ardunio Pins
 
              +--------+
          VCC + 1   14 + GND
  (SS)  0 PA4 + 2   13 + PA3 10 (SCK)
        1 PA5 + 3   12 + PA2 9  (MISO)
  (DAC) 2 PA6 + 4   11 + PA1 8  (MOSI)
        3 PA7 + 5   10 + PA0 11 (UPDI)
  (RXD) 4 PB3 + 6    9 + PB0 7  (SCL)
  (TXD) 5 PB2 + 7    8 + PB1 6  (SDA)
              +--------+
*/


#include <max6675.h>
#include <TM1650.h>
#include <EEPROM.h>
#include "button.h"

#define MAX_TEMP 400
#define MIN_TEMP 20

#define NO_IRON -1

//Time in mS to show requested temperature
#define DISPLAY_TIMEOUT 2000

// Define pins
#define LED_CLK_PIN 10  //PA3
#define LED_DIN_PIN 9   //PA2
#define SW_UP_PIN 7     //PB0
#define SW_DOWN_PIN 6   //PB1
#define HEAT_PIN 0      //PA4
#define TEMP_CLK_PIN 8  //PA1
#define TEMP_CS_PIN 2   //PA6
#define TEMP_DO_PIN 1   //PA5
#define MOVE_PIN 3      //PA7

MAX6675 thermocouple(TEMP_CLK_PIN, TEMP_CS_PIN, TEMP_DO_PIN);

/////////////////////DISPLAY AND FONT///////////////////////

TM1650 lc(LED_DIN_PIN,  //byte dataPin
          LED_CLK_PIN,  //byte clockPin
          4,            //byte number of digits
          true,         //boolean activeDisplay = true
          2             //byte intensity
);

#define BRIGHTNESS 2

#define SPACE 10
#define HYPHEN 11

const PROGMEM byte NUMBER_FONT[] = {
//  edpcgbfa
  0b11010111, // 0
  0b00010100, // 1
  0b11001101, // 2
  0b01011101, // 3
  0b00011110, // 4
  0b01011011, // 5
  0b11011011, // 6
  0b00010101, // 7
  0b11011111, // 8
  0b01011111, // 9
  0b00000000, // SPACE
  0b00001000  // HYPHEN
};

byte digits[] = {0,2,3,1};
unsigned long displayTime = 0;

/////////////////////PID VARIABLES///////////////////////
#define PID_REFRESH_RATE 250    //Needs to be long enough for MAX chip to stabilize
#define MIN_PID_VALUE 0
#define MAX_PID_VALUE 255       //Max PID value. You can change this. 
uint32_t pidTimeout = 0;        //Used to hold next PID period

float Kp = 6;                   //Mine was 2 - How fast the system responds (too high cause overshoot)
float Ki = 0.0025;              //Mine was 0.0025 - How fast the steady state error is removed
float Kd = 9;                   //Mine was 9 - How far into the future to predict the rate of change
float PID_Output = 0;
float PID_P, PID_I, PID_D;
float PID_ERROR, PREV_ERROR;

float temperature;              // Current temperature
int lastTemp = NO_IRON;         // Last temperature reading

/////////////////////BUTTON DEFINITION///////////////////////
void downButtonPressed(void);
void upButtonPressed(void);
Button* downButton;
Button* upButton;
Button* moveButton;

/////////////////////EPROM VARIABLES///////////////////////
#define EEPROM_ADDRESS 0
#define EEPROM_MAGIC 0x0BAD0DAD
typedef struct {
  uint32_t magic;
  uint16_t requestedTemp;
} EEPROM_DATA;

EEPROM_DATA ee;       //Current EEPROM settings

//---------------------------------------------------------------
// Hardware setup
//---------------------------------------------------------------

void setup() 
{
  lc.setupDisplay(true, BRIGHTNESS);   // set brightness level (0 - 7)

  //Initialise Buttons
  moveButton = new Button(MOVE_PIN);
  downButton = new Button(SW_DOWN_PIN);
  downButton->Repeat(downButtonPressed);
  upButton = new Button(SW_UP_PIN);
  upButton->Repeat(upButtonPressed);
  
  //Initialise other pins
  pinMode(HEAT_PIN,OUTPUT);

  //Get requested temperature from EEPROM
  readEepromData();

  displayNumber(ee.requestedTemp,false,true);
  displayTime = millis() + DISPLAY_TIMEOUT;

  // wait for MAX chip to stabilize
  delay(500);
}

//---------------------------------------------------------------
// Main program loop
//---------------------------------------------------------------

void loop() 
{
  int newTemp = lastTemp;
  
  testButtons();
  
  if (displayTime != 0 && displayTime <= millis())
  {
    displayTime = 0;
    lastTemp = 0;
    writeEepromData();
  }

  if (displayTime == 0)
  {
    if (millis() > pidTimeout)
    {
      pidTimeout = millis() + PID_REFRESH_RATE; 
      temperature = thermocouple.readCelsius();   //thermocouple.readFahrenheit()
      newTemp = isnan(temperature) ? NO_IRON : (int)round(temperature);

      if (newTemp != NO_IRON)
      {
        //Calculate PID
        PID_ERROR = ee.requestedTemp - temperature;
        PID_P = Kp*PID_ERROR;
        PID_I = PID_I+(Ki*PID_ERROR);      
        PID_D = Kd * (PID_ERROR-PREV_ERROR);
        PID_Output = max(min(PID_P + PID_I + PID_D, MAX_PID_VALUE), MIN_PID_VALUE);
        analogWrite(HEAT_PIN, PID_Output);  //Change the Duty Cycle applied to the heater
        PREV_ERROR = PID_ERROR;
      }
    }
    
    if (lastTemp != newTemp)
    {
      lastTemp = newTemp;
      displayNumber(newTemp,false,true);
    }
  }
}

//---------------------------------------------------------------
// Test if any buttons have been pressed
//---------------------------------------------------------------

void testButtons()
{
  //Single press buttons
  if (moveButton->Pressed())
  {
    moveButtonPressed();
  }
  
  //Don't need to check result of pressed since the button handler will invoke its repeat function
  upButton->Pressed();
  downButton->Pressed();
}

//---------------------------------------------------------------
// Handle Move button
//---------------------------------------------------------------

void moveButtonPressed()
{
  
}

//---------------------------------------------------------------
// Handle DOWN button
//---------------------------------------------------------------

void downButtonPressed()
{
  if (ee.requestedTemp > MIN_TEMP)
  {
    ee.requestedTemp--;
    displayNumber(ee.requestedTemp,false,true);
  }
  displayTime = millis() + DISPLAY_TIMEOUT;
}

//---------------------------------------------------------------
// Handle UP button
//---------------------------------------------------------------

void upButtonPressed()
{
  if (ee.requestedTemp < MAX_TEMP)
  {
    ee.requestedTemp++;
    displayNumber(ee.requestedTemp,false,true);
  }
  displayTime = millis() + DISPLAY_TIMEOUT;
}

//---------------------------------------------------------------------
// Write number to display
//  s - SHOW constant
//  num - (0 to 99) 
//  leadingZeros - true to have leading zeros
//  on - true to show digit, false to show blank
//---------------------------------------------------------------

void displayNumber(int num, bool leadingZeros, bool on)
{
  if (num == NO_IRON)
  {
    displayNan(on);
  }
  else
  {
    num = max(min(num, 999), 0);
    for (int i = 0; i < 3; i++)
    {
      if (on && (num > 0 || i == 0 || leadingZeros))
      {
        displayDigit(i, num % 10);
      }
      else
      {
        displayDigit(i, SPACE);
      }
      num = num / 10;
    }
  }
}

//---------------------------------------------------------------------
// Write hyphens to signify that iron isn't connected
//  on - true to show hyphen, false to show blank
//---------------------------------------------------------------

void displayNan(bool on)
{
  for (int i = 0; i < 3; i++)
  {
    displayDigit(i, (on) ? HYPHEN : SPACE);
  }
}
//---------------------------------------------------------------------
// Write digit to display
//  digit - digit to write to (0 - left most to 3 - right most)
//  num - (0 to 9) or SPACE
//---------------------------------------------------------------

void displayDigit(int digit, int number)
{
  byte segments = pgm_read_byte(&NUMBER_FONT[number & 0x0F]);
  lc.setSegments(segments, digits[2-digit]);
}

//---------------------------------------------------------------
// Write the EepromData structure to EEPROM
//---------------------------------------------------------------

void writeEepromData()
{
  //This function uses EEPROM.update() to perform the write, so does not rewrites the value if it didn't change.
  EEPROM.put(EEPROM_ADDRESS,ee);
}

//---------------------------------------------------------------
// Read the EepromData structure from EEPROM, initialise if necessary
//---------------------------------------------------------------

void readEepromData()
{
  //Eprom
  EEPROM.get(EEPROM_ADDRESS,ee);
  if (ee.magic != EEPROM_MAGIC)
  {
    ee.magic = EEPROM_MAGIC;
    ee.requestedTemp = 300;
    writeEepromData();
  }
}

Button.h

C Header File
/*
Class: Button
Author: John Bradnam (jbrad2089@gmail.com)
Purpose: Arduino library to handle buttons
*/
#pragma once
#include "Arduino.h"

#define DEBOUNCE_DELAY 10

//Repeat speed
#define REPEAT_START_SPEED 500
#define REPEAT_INCREASE_SPEED 50
#define REPEAT_MAX_SPEED 50

class Button
{
	public:
	//Simple constructor
	Button(int pin);
	Button(int name, int pin);
	Button(int name, int pin, int analogLow, int analogHigh, bool activeLow = true);

  //Background function called when in a wait or repeat loop
  void Background(void (*pBackgroundFunction)());
	//Repeat function called when button is pressed
  void Repeat(void (*pRepeatFunction)());
	//Test if button is pressed
	bool IsDown(void);
	//Test whether button is pressed and released
	//Will call repeat function if one is provided
	bool Pressed();
	//Return button state (HIGH or LOW) - LOW = Pressed
	int State();
  //Return button name
  int Name();

	private:
		int _name;
		int _pin;
		bool _range;
		int _low;
		int _high;
		bool _activeLow;
		void (*_repeatCallback)(void);
		void (*_backgroundCallback)(void);
};

Button.cpp

C/C++
/*
Class: Button
Author: John Bradnam (jbrad2089@gmail.com)
Purpose: Arduino library to handle buttons
*/
#include "Button.h"

Button::Button(int pin)
{
  _name = pin;
  _pin = pin;
  _range = false;
  _low = 0;
  _high = 0;
  _backgroundCallback = NULL;
  _repeatCallback = NULL;
  pinMode(_pin, INPUT_PULLUP);
}

Button::Button(int name, int pin)
{
  _name = name;
  _pin = pin;
  _range = false;
  _low = 0;
  _high = 0;
  _backgroundCallback = NULL;
  _repeatCallback = NULL;
  pinMode(_pin, INPUT_PULLUP);
}

Button::Button(int name, int pin, int analogLow, int analogHigh, bool activeLow)
{
  _name = name;
  _pin = pin;
  _range = true;
  _low = analogLow;
  _high = analogHigh;
  _activeLow = activeLow;
  _backgroundCallback = NULL;
  _repeatCallback = NULL;
  pinMode(_pin, INPUT);
}

//Set function to invoke in a delay or repeat loop
void Button::Background(void (*pBackgroundFunction)())
{
  _backgroundCallback = pBackgroundFunction;
}

//Set function to invoke if repeat system required
void Button::Repeat(void (*pRepeatFunction)())
{
  _repeatCallback = pRepeatFunction;
}

  bool Button::IsDown()
{
	if (_range)
	{
		int value = analogRead(_pin);
		return (value >= _low && value < _high);
	}
	else
	{
		return (digitalRead(_pin) == LOW);
	}
}

//Tests if a button is pressed and released
//  returns true if the button was pressed and released
//	if repeat callback supplied, the callback is called while the key is pressed
bool Button::Pressed()
{
  bool pressed = false;
  if (IsDown())
  {
    unsigned long wait = millis() + DEBOUNCE_DELAY;
    while (millis() < wait)
    {
      if (_backgroundCallback != NULL)
      {
        _backgroundCallback();
      }
    }
    if (IsDown())
    {
  	  //Set up for repeat loop
  	  if (_repeatCallback != NULL)
  	  {
  	    _repeatCallback();
  	  }
  	  unsigned long speed = REPEAT_START_SPEED;
  	  unsigned long time = millis() + speed;
      while (IsDown())
      {
        if (_backgroundCallback != NULL)
        {
          _backgroundCallback();
        }
    		if (_repeatCallback != NULL && millis() >= time)
    		{
    		  _repeatCallback();
    		  unsigned long faster = speed - REPEAT_INCREASE_SPEED;
    		  if (faster >= REPEAT_MAX_SPEED)
    		  {
    			  speed = faster;
    		  }
    		  time = millis() + speed;
    		}
      }
      pressed = true;
    }
  }
  return pressed;
}

//Return current button state
int Button::State()
{
	if (_range)
	{
		int value = analogRead(_pin);
		if (_activeLow)
		{
			return (value >= _low && value < _high) ? LOW : HIGH;
		}
		else 
		{
			return (value >= _low && value < _high) ? HIGH : LOW;
		}
	}
	else
	{
		return digitalRead(_pin);
	}
}

//Return current button name
int Button::Name()
{
	return _name;
}

MAX6675_library-1.1.2.zip

Arduino
MAX6675 Arduino Library
No preview (download only).

TM1650-1.1.0.zip

Arduino
TM1650 Arduino library
No preview (download only).

Credits

John Bradnam

John Bradnam

145 projects • 177 followers

Comments