As part of the build for my reflow oven I need some way to visualise the temperature in real-time to see how well it’s following the programmed profile. I’m basing my design on the EZ Make Oven over at Adafruit which uses their PyPortal to create the graphical interface but not having one of these myself I had to figure out something else. I’ve got a Pimoroni Breakout Garden with an MCP9600 thermocouple amplifier attached to a Raspberry Pi to measure the temperature of my oven so a solution using this would be ideal.

## What are my options?

Now I have no experience programming graphical interfaces. I’ve got a lot of experience using MATLAB and GNU Octave to plot graphs and what they produced, if it could be done in real-time, would be perfect. Fortunately there’s a Python plotting library called Matplotlib which provides a MATLAB-like interface that would also work nicely with the Python library I’m using to read the temperature from the MCP9600. Luckily for me there’s a tutorial on SparkFun that explains to you how to plot sensor data in real-time using Python and Matplotlib but the graph it generates goes from right to left when I’d need it to go the other way.

## Going from left to right

To get this to work I’ve combined the tutorial from SparkFun with another one here to produce the code below which generates a graph that updates in real-time from left to right and also includes a fixed plot with the target temperature.

import matplotlib.pyplot as plt
from matplotlib.animation import FuncAnimation
import datetime as dt
from mcp9600 import MCP9600

# Solder reflow profile
T = [25, 100, 150, 183, 235, 183]
t = [0, 30, 120, 150, 210, 240]
dTdt = [2.50, 0.56, 1.10, 0.87, -1.73]

# Maximum duration and temperature to display
duration = 300
maxtemperature = 275

# Set-up plot
fig, ax = plt.subplots(figsize=(8,6))
xs, ys, ts = [], [], []
line, = ax.plot([], [], 'b', zorder=2)

# Sampling interval in ms
interval = 300
# Total number of frames to display
total_frames = int(duration / (interval/1000))

# Initialize communication with MCP9600
mcp9600 = MCP9600(i2c_addr=0x66)
mcp9600.set_thermocouple_type('K')

def init():
# Set axis on figure
ax.set_xlim(0, duration)
ax.set_ylim(0, maxtemperature)

# Add labels to graph
plt.title('MCP9600 Temperature over Time')
plt.xlabel('Time (s)')
plt.ylabel('Temperature (deg C)')

# Get current oven temperature
T[0] = mcp9600.get_hot_junction_temperature()
print("Temperature now:", T[0])

# Calculate time adjustment constant for solder reflow profile
K = ((T[1] - T[0]) / dTdt[0]) - t[1]
print("Adjustment const.:", K)

# Adjust solder reflow profile
t_adjusted = [z + K for z in t]
t_adjusted[0] = 0
print("New timeline:", t_adjusted)

# Add grid to plot
plt.grid()

# Plot adjusted solder reflow profile
plt.plot(t_adjusted, T, 'r', zorder=1)

def update(i):
# Print the number of the current frame
print("i:", i)

# Read temperature (in Celsius) from the MCP9600
temp_c = mcp9600.get_hot_junction_temperature()
time_now = dt.datetime.now()
print("Temperature:", temp_c)

# Add time stamp to list
ts.append(time_now)

# Calculate time (in Seconds) since start
time_delta = (time_now-ts[0]).total_seconds()
print("Time:", time_delta)

# Add values to list for ploting
ys.insert(0, temp_c)
xs.insert(0, time_delta)

# Update line with new values
line.set_data(xs, ys)

# Create and show animation
ani = FuncAnimation(fig, update, repeat=False, frames=total_frames, interval=interval, init_func=init)
plt.show()

If you want to run this code yourself I’m using the Thonny IDE that comes pre-packaged in the Raspberry Pi OS and installed Matplotlib. When you run the script a new window will appear for your graph.

I’ve taken a video of the final graph and increased its speed as the script is written to run for 300 seconds or 5 minutes in total. I pinch my fingers around the thermocouple twice to change its temperature.

One thing I’ve noticed with the thermocouple I’m using is that it has a large response time. When I remove my fingers from it there is a long decay before the temperature readings to return to ambient. I’m pretty sure that this is due to the metal crimp that has been added to protect it. I may need to remove this but I’ll look into that later when I start testing my oven.

## Digging into the code

I’m going to go into a bit more detail about how my code works but I would highly recommend working your way through the SparkFun tutorial first as it takes you through multiple examples using Matplotlib which will get you familiar with everything.

The secret to creating a live graph in Matplotlib is by setting up an animation using FuncAnimation. To this function you pass the graph being animated – fig; details of what is changed each time a frame of the animation is updated – update; whether the animation is to be looped – repeat; how many frames are being generated in total – frames; the interval between each update – interval; and an initialising function to set everything up at the start – init_func.

ani = FuncAnimation(fig, update, repeat=False, frames=total_frames, interval=interval, init_func=init)

The figure is set-up with the following lines of code. The comma at the end of line, is intentional and not a typo – the SparkFun guide gives a good explanation of its purpose.

fig, ax = plt.subplots(figsize=(8,6))
xs, ys, ts = [], [], []
line, = ax.plot([], [], 'b', zorder=2)

The width and height of the graph in inches are set by figsize=(8,6). xs and ys are lists for the data points on the x- and y-axes respectively. An intermediate variable, ts, is created to store the current time when the data is sampled. The colour of the line is set by 'b' and as I want the line to appear on top of the target temperature plot I set zorder=2 which means it will be drawn second.

I’ve created two variables that set the limits on the x- and y-axes to be displayed.

duration = 300
maxtemperature = 275

Along with the interval duration in milliseconds, interval = 300, the total number of frames my animation will have can be calculated. FuncAnimation only accepts an integer for this value so I convert it using int().

frames = int(duration / (interval/1000))

I need to configure the MCP9600 before I can get any temperature readings so there are a couple of lines for this.

mcp9600 = MCP9600(i2c_addr=0x66)
mcp9600.set_thermocouple_type('K')

I plan on using the Chip Quik TS391AX solder paste in my oven. The datasheet has a suggested temperature profile for it. I’ve taken the key temperatures and times from this graph, t & T, and created arrays for them which can be used to plot a simplified version of this graph in Matplotlib – the red line I’ve drawn below.

I’ve also worked out the rate of change, dTdt, for the different zones which is used to reposition the graph depending on the starting temperature. You can find more information on the different zones in the Wikipedia entry for reflow soldering.

# Solder reflow profile
T = [25, 100, 150, 183, 235, 183]
t = [0, 30, 120, 150, 210, 240]
dTdt = [2.50, 0.56, 1.10, 0.87, -1.73]

When FuncAnimation runs it will call the init function first and run it only once. The function I’ve created does two things – firstly it repositions the reflow profile based on the current ambient temperature which might not be the 25°C set as the starting point in the datasheet and the rate of change in the first zone of the profile.

def init():
# Get current oven temperature
T[0] = mcp9600.get_hot_junction_temperature()
print("Temperature now:", T[0])

# Calculate time adjustment constant for solder reflow profile
K = ((T[1] - T[0]) / dTdt[0]) - t[1]
print("Adjustment const.:", K)

# Adjust solder reflow profile
t_adjusted = [z + K for z in t]
t_adjusted[0] = 0
print("New timeline:", t_adjusted)


The second thing it does is to set-up the figure by limiting the x- and y- axes, adding labels, showing a grid and then plotting the solder reflow profile.

def init():
# Set axis on figure
ax.set_xlim(0, duration)
ax.set_ylim(0, maxtemperature)

# Add labels to graph
plt.title('MCP9600 Temperature over Time')
plt.xlabel('Time (s)')
plt.ylabel('Temperature (deg C)')

# Add grid to plot
plt.grid()

# Plot adjusted solder reflow profile
plt.plot(t_adjusted, T, 'r', zorder=1)

FuncAnimation will then repeatedly call the update function until the maximum frame count that is set by total_frames is reached.

def update(i):
# Print the number of the current frame
print("i:", i)

# Read temperature (in Celsius) from the MCP9600
temp_c = mcp9600.get_hot_junction_temperature()
time_now = dt.datetime.now()
print("Temperature:", temp_c)

# Add time stamp to list
ts.append(time_now)

# Calculate time (in Seconds) since start
time_delta = (time_now-ts[0]).total_seconds()
print("Time:", time_delta)

In update I find out the current time each time a temperature measurement is taken, time_now, and build up an array of timestamps, ts, each time it is called. The x-axis values are then calculated from this so that the graph starts at t = 0.

time_delta = (time_now-ts[0]).total_seconds()

Finally to ensure the graph is plotted from left to right I add the new values to the beginning of each array and then update the line.

def update(i):
# Add values to list for ploting
ys.insert(0, temp_c)
xs.insert(0, time_delta)

# Update line with new values
line.set_data(xs, ys)

Putting this all together gives you a clean plot that will show your oven temperature in real-time as well as a target temperature. Happy plotting.