Reflow Oven

From Hamsterworks Wiki!

Jump to: navigation, search

This FPGA Project was completed on Christmas Day 2013 - my wife gave me the mini-oven as a present. Love you honey!

I wanted to assemble surface mount components, and to do this I need a reflow oven (the fry-pan method is just too crude!). Everybody on the internet seems to build their own, so I set about building one using a FPGA. Everything has been implemented inside the FPGA, without there being any software in the control loop.

The temperature control is achieved using a Finite State Machine and a preset profile (60 seconds preheat between 120C and 145C, then peak at 225C for 10 seconds.

Cool-down is achieved by opening the door when the 7-segment display flashes.

Mains switching is performed by a Solid State Relay wired in-line with an extension core.

The RS232/USB converter on the FPGA board is used to supply data to a host for logging, but it is not required for operation.

Reflow blocks.png

Here is the setup in progress - USB microscope, oven, tweezers, FPGA controller....

Reflow setup.jpg

Happiness is a warm reflow oven - here cooking a batch of two:

Reflow in progress.jpg

And here is the fruits of my labour:

Eight led wing.jpg

I think I need practice - this looks to be the effect of too much paste:

Too much paste.jpg

On the third run I think I have it sorted:

Third run.jpg

Contents

Components used

If you don't have any of this at hand it will cost around NZ$300.

Custom reflow wing for Papilio FPGAs

This project is a keeper, so I've designed and ordered a PCB. I'll update the site and have about 15 to give away. You will need to supply the through-hole components, the SSR and the Adafruit Thermocouple+Amp.

Reflow wing.png

Boards are here, ready to assemble and test:

Reflow wing pcbs.jpg

The heating controller

Rather than a full PID controller this is a much simpler implementation.

It consists of six setpoints, each having three parameters:

  • A target temperature
  • A maximum for the rate of change
  • The minimum time to stay in this state.

The logic is simple - if the temperature is less than the target, and the rate of change does not exceed the maximum then turn the elements on.

The current rate of change is calculated by keeping a history for the last four seconds (16 sampling periods).

----------------------------------------------------------------------------------
-- Engineer: Mike Field <hamster@snap.net.nz>
-- 
-- Create Date:    2013-12-20
-- Module Name:    heating_controller - Behavioral 
----------------------------------------------------------------------------------
library IEEE;
use IEEE.STD_LOGIC_1164.ALL;
use IEEE.NUMERIC_STD.ALL;

entity heating_controller is
   generic ( frequency : natural);
   
    Port ( clk      : in  STD_LOGIC;
           reset    : in  STD_LOGIC;
           fault    : in  STD_LOGIC;
           state    : out  STD_LOGIC_VECTOR (2 downto 0);
           power    : out  STD_LOGIC;
           finished : out  STD_LOGIC;
           temp     : in  STD_LOGIC_VECTOR (12 downto 0));
end heating_controller;

architecture Behavioral of heating_controller is
   signal counter              : unsigned(28 downto 0) := (others => '0');
   signal quarter_second       : std_logic := '0';
   signal quarter_second_count : unsigned(11 downto 0) := (others => '0');
   signal history              : unsigned(15*13-1 downto 0) := (others => '0');
   signal trend                : unsigned(12 downto 0);   -- in 1/4  degress
   signal goal_rate_of_change  : unsigned(12 downto 0);   -- in 1/16 degrees per second
   signal goal                 : unsigned(12 downto 0);   -- in 1/4  degress
   signal oldest_temp          : unsigned(12 downto 0);   -- in 1/4  degress
   signal min_time             : unsigned(12 downto 0);   -- in 1/4  second
   signal countdown            : unsigned(12 downto 0);   -- in 1/4  second
   signal stage                : unsigned(2 downto 0);   
begin

   state       <= std_logic_vector(stage);
   oldest_temp <= history(history'high downto history'high-12);  -- oldest temp is 4 seconds ago
   trend       <= oldest_temp + goal_rate_of_change;             -- add 16 x rate of change to the oldest temp
   
process(stage)
   begin
      case stage is 
         -- Heat to 100 degrees pretty quickly
         when "000" =>   
            goal_rate_of_change <= to_unsigned(  3, 9) & "0000";  -- in 1/16ths of a degree per second 
            goal                <= to_unsigned(100,11) & "00";  -- in quarter degrees
            min_time            <= to_unsigned(  0,11) & "00";  -- in in 1/4th of seconds
            finished            <= '0';

         -- Heat to 120 degrees slowly
         when "001" =>   
            goal_rate_of_change <= to_unsigned(   1, 9) & "0000";  -- in 1/16ths of a second
            goal                <= to_unsigned( 120,11) & "00";  -- in quarter degrees
            min_time            <= to_unsigned(   0,11) & "00";  -- in in 1/4th of seconds
            finished            <= '0';
            
         -- Slowly climb to 145 degrees for 60 seconds
         when "010" =>
            goal_rate_of_change <= to_unsigned(  0, 9) & "1000";  -- in 1/16ths of a second
            goal                <= to_unsigned(145,11) & "00";   -- in quarter degrees
            min_time            <= to_unsigned( 60,11) & "00";  -- in in 1/4th of seconds
            finished            <= '0';

         -- Ramp up to 217 degrees @ 3 degrees per second
         when "011" =>
            goal_rate_of_change <= to_unsigned(  3, 9) & "0000";  -- in 1/16ths of a second
            goal                <= to_unsigned(217,11) & "00";    -- in quarter degrees
            min_time            <= to_unsigned(  0,11) & "00";    -- in in 1/4th of seconds
            finished            <= '0';

         -- Glide up to 225 degrees @ 1 degrees per second
         when "100" =>
            goal_rate_of_change <= to_unsigned(  1, 9) & "0000";  -- in 1/16ths of a second
            goal                <= to_unsigned(225,11) & "00";    -- in quarter degrees
            min_time            <= to_unsigned(  0,11) & "00";    -- in in 1/4th of seconds
            finished            <= '0';

         -- Hold at 225 degrees for 10 seconds 
         when "101" => 
            goal_rate_of_change <= to_unsigned(  1, 9) & "0000"; -- in 1/16ths of a second
            goal                <= to_unsigned(225,11) & "00";   -- in quarter degrees
            min_time            <= to_unsigned( 10,11) & "00";   -- in in 1/4th of seconds
            finished            <= '0';

         -- cool down / shutoff  @  -1 degree/s
         when others =>  
            goal                <= (others => '0');
            goal_rate_of_change <= to_unsigned( 4096-(6*16),13);
            min_time            <= to_unsigned(      0,13);  -- in in 1/4th of seconds
            finished            <= '1';
      end case;
   end process;
   


process(clk) 
   begin
      if rising_edge(clk) then 
         -- do we ened to rest
         if min_time = 0  then
            countdown <= (others => '0');
         elsif countdown > min_time-1 then
            countdown <= min_time-1;
         end if;
         
         if quarter_second = '1' then
            quarter_second_count <= quarter_second_count +1; -- for timing events
            
            history <= history(history'high-13 downto 0) & unsigned(temp);

            -- do we need the heater on?
            if unsigned(temp) < goal and unsigned(temp) < trend and fault = '0' then
               power <= '1';
            else
               power <= '0';
            end if;         
         
            -- have we reached the setpoint?
            if unsigned(temp) >= goal and countdown = 0 then
               if stage /= "111" then
                  stage <= stage+1;
                  countdown <= (others => '1');
               end if;            
            end if;         
            
            -- Timing for state changes
            if countdown > 0 then
               countdown <= countdown-1;
            end if;            
         end if;
         
         if counter = (frequency / 4)-1 then 
            counter <= (others => '0');
            quarter_second <= '1';
         else
            counter <= counter+1;
            quarter_second <= '0';
         end if;
       
      end if;
   end process;
end Behavioral;

Full project source

File:Reflow source.zip

Test run

Here is the output of the second test run, so it is starting at about 50C - I didn't have a long enough buffer set on my first test run:

Reflow profile.png

And this is the profile of the first run when actually reflowing a board - it is peaking a little high so needs a little tuning:

First reflow graph.png

Actual vs reflow limits

After reading http://www.microsemi.com/document-portal/doc_download/131105-solder-reflow-leadfree.pdf I put together the longest & highest temperature vs shortest & lowest temperature, and graphed one of my logs. It looks like it is all OK - the cool-down could be steeper - maybe I should point a fan at the open oven:

Reflow limits.png

Personal tools