AUTOMATIXARON
DIY Reflow Oven
Introduction
One of my favourite activities as an engineer is to design PCBs. I've started the production using the good-old toner transfer method for through-hole assemblies. Over time I've managed to create PCBs with two layers using this method, but with the evolution of integrated circuits from DIP to QFN and passive components from THT to 0608 and then to 0402 SMD, this method started to be a bit uncomfortable.
Then I've learned about the mighty Positive 20 spray and the UV exposure method. This method was quick and easy when the spray was fresh and did not produce a bubbly orange peel-y surface.
​
In retrospect, even my soldering method was lacking. I've started with a soldering gun (no temperature control), solder tin without flux core, and the cheapest, most acidic flux gel that I could find (no judging, back then, I did not know better). Now I have a nice soldering station with separate tips for leaded and lead-free solder, a hot air station and an excellent solder wire with multi-core flux combined with water-soluble no-clean flux.
​
Nowadays, the production of PCBs is cheap ($2 for five pieces of 4 layered boards + shipping + taxes). I only opt for the DIY PCB production if I need a quick prototype board. Nothing beats the one day turnaround time from the copper laminate to the etched and soldered board.
​
Regarding the assembly: PCB factories started to offer component assemblies for prototype boards, which is a nice feature, but usually, the assembly is done for a minimum of 5 boards which is troublesome if one uses expensive parts and only needs one or two boards.
​
This fact brings me to the current page. I wanted to design a reflow oven out of a cheap electric oven for prototype manufacturing at home.
​
WARNING! Great DANGER! This project works with mains voltage and high currents.
Always triple check your connections and the isolation.
Try this project only if you know what you are doing.
Do not send hate mails if the current killed you!
​
Required components (Overall)
-
An electric oven (surprise). With two or preferably four heating elements. If you can snatch one with halogen quartz based elements, then even better, it will have a faster response time. Otherwise, the classic heating element also works but you have to adjust the profiles for slower ramps.
-
An EMI filter (these are cheap, but I include a design for those interested in the build).
-
A Solid State Relay which can withstand the power requirement of the heating elements (similarly, see the design notes posted below).
-
An industrial PID block with built-in auto-tune method (alternatively a micro controller with display and buttons).
-
Temperature sensor(s) based on the capabilities of the chosen controller.
-
Insulation for the oven: ceramic fiber wool for the outside of the heating compartment, reflective sheet for the inside.
-
A lot of Kapton (or Koptan for the cheap version) tape.

Here we have the connection diagram for the final product.
There is a switch with neon lamp, and a fuse. The power line goes into the EMI filter block, the earth is connected to the chassis of the oven.
The live wire is connected to the solid state relay and to the heating elements in series.
​
The bottom block is the controller module. It gets the supply from the filtered mains line, the output is connected to the SSR (with matching polarity), and the termocouple is connected to the sensor input (also with matching polarity).
​
I've got this oven, and measured the current consumption and power rating. It's a 900W little oven and it can eat around 4A of current on max settings.
So I've bought a 10A switch and 10A quick blow fuse for PCB mount, and started to disassemble the bad boy. I guess no more warranty?
EMI filter
The reflow oven will be connected to mains power line, and we don't want to redirect any switching noise to potentially confuse any sensitive equipment. EMI can be conducted (traveling through conductor wires) and radiated as magnetic fields. The EMI filter is used to cancel the first one, while a good shielding is effective to cancel the second one. Since EMI noise is at much higher frequencies than the mains frequency, we need some sort of low-pass filter.
EMI noise can be divided in two categories: Common mode noise (CMN) and differential mode noise (DMN). The CMN current has the same phase on both power lines, so we can suppress the noise by using two inductors on a common core and polarization. Since the CMN current returns via the ground connector, we can also use two

capacitors from the power lines to the ground. Since we connect the power lines to the ground we must use Y capacitors to ensure an open circuit at failure. There are Y1 and Y2 caps, the former is double isolated and the latter is single isolated.
​
DMN or line-to-line noise is considered as the noise between the live and neutral wire, with 180° phase shift between them. We can use a capacitor placed between the power lines to suppress the DMN. We can use X capacitors, which become short circuit in case of failure, which blows the fuse, but won't electrocute the user. If the power line has high amount of DMN, differential suppression inductors are needed. The resistor is used to discharge the capacitors when the power is disconnected.
​
There are EMI filters incorporated in power entry modules, or as separate blocks like this one or this one. Alternatively one could build an EMI filter on a single sided PCB using scrapped components:

Here's my EMI design. I've used a single capacitor (C2) for DMN suppression while L1, C1, C3 are used for CMN suppression. The discharge resistor is placed on the output side, and the fuse is incorporated in the PCB since only that type of fuse holder was available at the time of building. Also, I'm using spade terminals, since they are nice, sturdy, insulated and the wire part can be used without solder.

And this is the PCB design. As you can see, I used copper pours for tracks, and I left big clearance between the power signals.
I will mount these PCBs on a PLA 3D printed rack, but even so, I applied a generous amount of insulating coating so that no accidental shocking could happen (Just don't forget to cover the terminal before spraying).
Solid state relay
We would like to control the nigh power of the heating elements preferably with a small powered signal from a controller. For this reason we need to use a "switch" which translates the low power signal into high power action.
One of such switching devices is the electro-mechanical relay. They have inherent galvanic isolation, most of them can switch high currents, and they work with relatively low control voltage ( 4 - 24 V). The downside is that the contacts are mechanical and have a short life span and they are prone to arcing at high powers which means high switching losses, and the switching time is relatively slow.
Another switching device uses semiconductor elements and is called solid state relay (SSR). The relay can be a BJT, FET, IGBT, Thyristor or TRIAC out of which the first four are used for DC and the last for AC applications. The isolation is done using an optocoupler which contains an LED and depending on the application an photodiode, phototransistor, Thyristor or TRIAC.
These devices have faster switching speeds, longer life time and lower switching losses.
If you want to use a pre-made industrial grade SSR, look for a 10A / min 250V AC relay like this one. If you would like to make one, continue reading this part.
There are two types of TRIAC-based SSRs: with and without zero crossing detection. Zero crossing is the point where the signal changes sign. SSRs without ZC can switch anytime, at any speed. SSRs with ZC can only be switched only when the load voltage is close to zero. This limits the switching frequency to a maximum of twice the mains frequency (that would be 2x50 Hz in my case), but greatly reduces the switching noises. Here's a nice description of SSR without ZC also called as phase angle control.

This schematic shows my SSR implementation. We have a MOC3041 optoisolator with zero cross detection and TRIAC driver output. According to the datasheet, the IR LED inside the MOC must be driven with 15mA of current. This together with the forward voltage of 1.3V and supply voltage of 3.3V gives a current limiting resistance of 133Ω (well I did not have any 133Ω resistor, but the maximum current rating is 30mA, so I went with a value of 100Ω which gives a driving current of 20mA). The red LED1 is there only to show the status of the relay while the oven is not assembled. Since I only had a dim red LED I applied the same 20mA driving current (this LED can be omitted in the final design).
​
On the other side of the MOC is the high power part. I used a BT139-600 TRIAC which can switch a 16A current source at 600V (heat-sink with isolated silicone sleeve is recommended). The R2/C1 is a snubber pair, which is mandatory for inductive loads. It can be omitted when we use this SSR to swtch resistive heating elements.
And here is the board design.
Similarly as in the case of the EMI, I was using thick copper pours as traces to accommodate the high current demands.
This time the input and output are screw terminals.
​
Don't forget that we want to switch the live part of the power line (and don't mind the green color on the board).

The controller
There are complete PID controllers out there, like this one, or this one. But where's the fun without a bit of suffering?
I wanted to design a custom controller based on the STM32F103, but at the time of the design (2021) there was a pesky IC shortage, so I made an ugly veroboard/BluePill clone based controller. When/IF the price and availability of the components becomes more hobby friendly, I might revisit this project.
Untill then, let's look at the control board.
​
Supply: the BluePill Clone expects a 5V DC supply, so I've used one of these isolated AC/DC converters.
The user interface consists of a 8x2 alphanumeric LCD and a rotary encoder with push button.
The temperature input: I've added an LM35 to measure the "room" temperature in the electronics compartment, and used a MAX6675 breakout board with two K-type thermocuples in parallel with a 10nF capacitor.
The micro-controller is directly connected to the SSR and to an active piezo buzzer, to give sound signals when the profile is finished.

I've used a perfboard, and I was too lazy to create a proper schematic so here's a drawing in AronCad. The controller is supplied with +5V DC, which is filtered by a 10uF electrolytic capacitor. The user interface consists of a 8x2 alphanumeric LCD with Hitachi controller and an Alps rotary encoder . The backlight LED needs a current limiting resistor, the value of the resistor is calculated with the previously shown formula based on the datasheet (100Ω). The potentiometer controls the contrast, so set it as desired. I will implement a 4bit communication, so D4, D5, D6, D8 data pins and RS, RW, E control pins are wired to the micro. This could be done to any pins, but I've chose consecutive pins on the same ports so that I can send the data in a single instruction (in contrast to the single pin commands provided by HAL).
​
The encoder has a built-in momentary switch, and acts as a dual switch itself. So I've connected the encoder A, B pins to a timer input. We don't need pull-up resistors, since we can enable them in code. Similarly the button is connected to a digital input pin. We need a decoupling capacitor so that we don't have false triggers.
​
The LM35 temperature sensor has a filtering capacitor on its supply, and I've connected the output to an ADC pin.
​
The piezo buzzer and the SSR control terminal is connected to timer pins.
​
The MAX6675 uses a read only SPI protocol, so I've connected the data bus to one of the STM's hardware SPI pins. The other SPI port is used for external EEPROM.
​
The STM32F103 only has internal RAM and FLASH, which makes storing and modifying temperature profiles pretty annoying. This is why I added a big 25AA256 IC, which is a 256Kb (32KB) EEPROM. I think that can easily fit every profile that I will ever need.
The control firmware
I was debating with myself whether to use RtOS or Bare-Metal approach. The GUI + Profile tracking needs a finite state machine and a somewhat precise timing. Then again, the states are straight forward, and there are only three events: encoder movement, button press and timer interrups. This can be done with a few conditional statements, some interrupt requests and some global flags. Looking back from my 80% full FLASH, I'm glad that I did not opt to use the RtOS :).
Periphery setup:
One could argue that the factory provided HAL is a bloatware. One would be totally right, but you can't beat the prototyping speed using the Graphical configurator + HAL provided by STM32CubeMX (If you have your own HAL optimized in C++ then that is another thing. Maybe I'll try to make my own HAL one day).
​
We need to enable the high speed clock input in the System Core / RCC, and the debug capabilities under the System Core / SYS window.
In the Clock Configuration windows we need to set the input frequency to 8MHz (on board external crystal). Consecutively we set the system clock (SYSCLK) to 72MHz, and the program will calculate the required PLL register values.
Enable the IN9 channel of the ADC1, set the sampling time to 13.5 Cycles (this is a SAR ADC so leaving a short sampling time would result in a big conversion error). Also enable the ADC1 global interrupt.
We will use TIM1 as the main timing source, so let's set it up to firing interrupts with 1ms period. E.g. Clock Source = Internal Clock, Prescaler=71, Counter Period=999. We need to activate the TIM1 update interrupt.
​
We will set TIM2 for PWM output. Clock Source = Internal Clock, Channel3/Channel4 = PWM Generation CH3/CH4, Prescaler = 17, Counter Period = 39999. This gives a PWM frequency of 100 Hz. We will change this latter on to suit our needs.
​
I've set TIM3 for a soft switch denounce. Enable internal clock, Prescaler = 0, Counter Period = 359, so it will have a period of 5us.
​
I need a timer to be able to respond to the rotary encoder events. The last free timer is TIM4, so I will use that one. I could've used the input capture capability of one of the previous timers, but the STM already has a nice Encoder mode. Combined Channels = Encoder Mode, Prescaler = 0, Counter Period = 65535, Encoder Mode = T1 and T2 (which increments and decrements the counter register on both A and B channels of the Encoder).
​
Next I've set up two different SPI peripherals for the sensor and the memory.
SPI1: Mode = Receive Only Master, Data Size = 16Bits, First Bit = MSB First, Prescaler = 128 (563 Kb/s), CPOL = Low, CPHA = 2 Edge.
SPI2: Mode = Full-Duplex Master, Data Size = 8Bits, First Bit = MSB First, Prescaler = 64 (563 Kb/s), CPOL = Low, CPHA = 1 Edge.
I've opted for soft chip select signal (PA15 for SPI1, PB12 for SPI2). You could use the built-in hardware NSS, but it will be an open drain configuration, so you will need to add an external pull-up resistor to the line.
​
The GPIOs used: PC13 as output for the on-board LED, PB8 as external interrupt with rising edge trigger for the push button, PA0 to PA6 are outputs for the LCD.
PB6 and PB7 are the Encoder inputs, so we need to enable the internal pull-up resistors.
​
The configuration is done, we can generate the code.
The next step is to test every periphery (which I leave to the reader), and to write the firmware for the MCU.
​
​
Firmware capabilities and implementation:
What do we want from our firmware?
-
to be able to measure the internal and external temperatures accurately
-
to be able to track a constant and ramping temperature reference
-
to have a user "friendly" menu for the process state
-
to be able to create, store and recall temperature profiles
-
to fit in the Flash of the STM32 micro even in unoptimized state :)
1. Temperature measurement:
First, let's take some measurements. I've set the control PWM to 5Hz/30%, and I've got a chaotic temperature reading of random 0, 400 and some plausible values which then settled on 0 degrees C. It took me a few days of debugging to figure this out: The MAX6675 ties the TC- pin to GND according to the datasheet. The metal shielding of the thermocouples are also connected to TC-. The shielding touches the ovens metal chassis, which is earthed. This elaborate connection created a ground loop in a switching + 50Hz environment, with a sensor that could detect changes in the uV range. Solution: Get some Kapton tape, and insulate something. You could insulate the earth connection, leaving your setup not so safe, or you could insulate the thermocouples from the chassis. Evidently I recommend the second procedure.
​
​

The figure above presents a snippet of the thermal profile. I've observed that at some seldom random points there are noise spikes. These spikes are one sample long, and differ greatly from the actual temperature value. We must filter the noise out. I've chosen a 3 sample based median filter, since the linear filters would include some of the noise effect.
The median filter requires a FIFO type buffer. We must sort the content of the buffer after pushing the new value and popping the old ones. If we've chosen a buffer with odd elements, then we can return the middle element, otherwise we take the middle two and calculate an average.
​
I've chosen the buffer length of 3 because, well it's the simplest one, so I can write the whole algorithm using only conditional statements, and because I don't have to implement averaging.

If you want to design a filter with more than three elements, the explicit conditional method quickly becomes unreasonable complex. Here is an implementation for larger buffers. The figure below this text contains a filtered temperature profile. Now that's more like it.

Regarding the stored profiles, I've came up with a 6 point profile, out of which the starting and ending points are fixed (room temperature). This way, nearly every constant reference, delta-type or trapezoidal-type profile can be created and stored. Here's an example for the reflow profiles. I'm using the following structure to hold, load and save the profile data:

Every profile has a unique name. T_soak is the second temperature point from room temperature, dT_reflow is the temperature difference between the soaking and the reflow temperatures. t_soak_ramp is the first dynamic heating interval where the temperature changes from room temperature to the soaking temperature. dt_soak is the first constant reference interval, where we keep the soaking temperature. dt_reflow_ramp is the second dynamic heating interval connecting the soaking and reflow temperatures. dt_reflow is the second constant reference interval, here we keep the reflow temperature. Finally dt_cooling is the cooling temperature interval.
As you can see, I'm using temperature and time differences so that every variable can fit in a single byte value. On the EEPROM, the first two bytes contain the number of saved profiles. When the oven is started, we read this number, so when we are loading profiles, we know which memory zone to load. In case of saving a profile, if we chose a per-existing location, the device will overwrite it (after a warning), but the profile number won't change. Otherwise the number is incremented. I did not implement a functionality to delete a profile.
About the controller... I've implemented a simple PID algorithm with saturation and back calculation on the integral term.

On the left, we have the block diagram of the continuous-time PID controller with saturated output, filtered derivative and back calculation on the integral.
​
This is only a theoretical figure, there are no such things as continuous values and operations in a discrete-time system. I've used the trapezoidal approximation to discretize the controller:
Here Ts is the sampling time, which is set to be 1s.
The input of the controller is the error between the reference and the system output:
The proportional term is straight-forward:
This proportional term creates a control signal, which is "proportional" to the current error. If there is no error, there will be no corrective output signal, so we need additional actuation.
The transfer function of the filtered derivative term is given by the relation
​
which, after applying the trapezoidal discretisation, has the form:
or, in time domain (since that form can be programmed):
The derivative term creates a control signal proportional to the rate of change of the error. Some would call it anticipatory control, in reality it does not anticipate, only reacts stronger to fast changing signals (it increases robustness).
Similarly, the integral term is given by the transfer function:
in discrete form:
or in time domain:
The integral term takes into account the past error values. The integral part seeks to eliminate the residual error by increasing the control effort. When the error becomes zero, the integral term becomes constant.
​
The control output is formulated as the weighted sum of the previous terms:
This output could be any value depending on the error input and the coefficients, so we must set an upper and lower limit. This output is the duty cycle of the control PWM, so it's logical to keep the output between 0 (0%) and 1 (100%). I went one step further, and tested that the oven performs well with an upper bound of 0.85.
​
Now, remember, that the integral term will increase the output value if the error is not zero, but we just created a limiting factor, so the error will accumulate further, the system will overshoot, and the control time of the system will increase. This is called integral wind-up, and it can be tackled with the clamping of the integrator, or by back calculation.
​
Here I've opted for back calculation. We subtract the saturated output from the control output, and the weighted difference is subtracted from the control error:
How do you tackle such a controller? In reality the algorithm is simple, the tuning is somewhat time consuming, but we will get there. From the previous formooolas we see that we need a 2 value deep FIFO type structure for the error, derivative and integral terms, initialized to zero, next:
-
calculate the current error based on the reference and the filtered temperature
-
calculate the current back calculation value (beta)
-
calculate the current integral value based on the previous integral, the current and previous back calculation values
-
store the current back calculation value in the previous one
-
store the current integral value in the previous one
-
calculate the current derivative value based on the previous derivative, current and previous error values
-
store the current derivative value in the previous one
-
calculate the control by the given weighted sum
-
calculate the saturated control output
-
store the current error value in the previous one
-
set the duty cycle by multiplying the saturated control output with the Timer maximum compare value (50000 in my case).
See? Easy.
About those tuning methods... there are many, many PID tuning methods. If you have Matlab or Python, the easiest route is to give a step response to the system, save the temperature values and either apply the Oppelt tuning method, or use a system identification/control tuner toolbox. The next possible route is to apply a Bang-Bang control and tune the PID based on a modified Ziegler-Nichols table.
​
Since we do not want to write a scientific study based on these methods, I present my rule of thumb:
-
Set every parameter to zero, and the reference to 80% of it's maximum expected value.
-
Increase the proportional gain until the system stabilizes with quarter wave decay. Here you can add disturbance into the system by opening the door or using a fan. The temperature must settle, but there will be a steady state error.
-
Start increasing the integral term until the steady state error vanishes, without disturbing the system.
-
Start increasing the derivative term until you get the desired response speed and overshoot. Here you can induce disturbances again.
-
The derivative filter is optional, but I've set it to half of the derivative coefficient.
-
Again, the back calculation coefficient is set by observing the wind-up phenomenon. My go-to value for slow processes with dead-time is kp/(20ki) when I opt to use it.

The guts of the algorithm:
​
​
​
First I wanted to write the control loop using fixed point arithmetic, but then I got lazy, so soft float it was.
The only thing to remember when using floats (soft or hard) is that there is a huge overhead, and it is advised to only use it in one routine (main, don't even think about interrupts in an MCU without float accelerator).
​
Interrupts:
-
External GPIO interrupt: if it occurs, I used a soft debounce with TIM3. The timer is started.
-
Timer interrupts: TIM1 generates a 1ms interrupt, so I'm using a counter and two different flags for 1s control signal and 20ms display refresh. in TIM3 interrupt we check the button state, if set, we raise a button flag.
The main loop basically checks for two flags:
-
If control flag is raised: we read the temperature, filter it, calculate the reference signal, calculate the control signal, set the PWM duty cicle, ad reset the control flag.
-
If display flag is raised: we use a finite state automata to load profiles or store profiles. All of which are done in conjunction with the button flag and the rotation direction of the rotary encoders.
Just remember, if you ever optimize your code, be sure to set those variables which are set from multiple contexts volatile. You will thank me latter if the variable is not optimized out :).
​
The full code can be found on my GitLab.
So far, the oven behaves as expected. As soon as my times lets me, I'll edit this page with some temperature plots, and when I get an interesting project, worthy of using a reflow oven, I'll upload a video of it in use.