Some time ago I got a temperature measurement kit that works with a temperature sensor in the form of a thermocouple from the K-Type. That is, the sensor consists of a Chrome chroma nickel-Combination. The good thing is the measuring range. This element is on one Max6675 chip operated and in this combination temperatures of 0 ° C to 1023.75 ° C capture. The thermoelement would even be suitable for an area from -250 ° C to 1200 ° C -with a certain and with a different evaluation electronics.
In any case, the measuring range makes this combination interesting, as it is possible with the usual sensors such as DS18B20, AHT10, SHT21, BME280 etc. Only areas up to a maximum of 125 ° C. Today we will take a close look at the function of the thermalement, build a micropython module from the information from the data sheet for the Max6675, write a sample program as an application and thus show how the measurement accuracy of the structure can be determined for certain temperatures. A small OLED display will serve as a display element.
All of this awaits you in this new episode from the series
Micropython on the ESP32, ESP8266 and Raspberry Pi Pico
today
A thermometer up to 1023.75 ° C
Something theory-the Seebeck effect
A thermoelectric converter consists of two different metals or alloys, the ends of which are welded or soldered. One of the ladder is separated.
The thermoelectric or Seaside-Plower is that a hike of electrons takes place at the contact points of two different metals. If you heat or cool the contact points, this leads to an electrical voltage at the point where one of the two ladder is separated due to the different speed of the electrons at the contact points. Because the element itself acts as a voltage source, the technical power direction in the circle within the element is from the minus to the plus pole, because the voltmeter flows from plus to minus.
Figure 1: Seebeck effect
In our case, there is only the left connection, because the thermal couple only has two outputs, the ends of the two different ladder. The right connection, the cold contact, is replaced by an internal circuit in the Max6675 by a diode that has similar properties as the comparison point. This simulates the comparison temperature at the position of the max6675 and deducted from the value at the measuring point.
Thermo elements can never measure a temperature absolutely, but only only temperature differences between the contact points. It should also be borne in mind that the connection between CRNI and CU and between CR and CU also occur thermos tension, which are also taken into account by the Max6675. So we don't have to worry about it.
Figure 2: Seebeck effect with a simulated comparison point
In our case, the measuring point is housed in a stainless steel capsule, which in turn is in a cylinder with 6mm outer thread. The derivations are glass fiber insulated and covered with a stainless steel network. Unfortunately, the capsule is not waterproof with the cylinder. Print density assembly in a liquid or gas tank is therefore not possible without further sealing measures.
Figure 3: Thermo Couple
Figure 4: Max6675
Let's take a look at that right away Data sheet of the Max6675. On page 1 we read that the temperature is encoded with 12-bit resolution, but the highest value is 1024 ° C and the smallest level is 0.25 ° C. This info is important for programming. Further down in the data sheet, on pages 4 to 6, we find further information. The simulation of the voltage at the cold end by the comparison level (see Figure 2) works from -20 ° C to 85 ° C ambient temperature of the chip. The Max6675 manages the tracking of the correction voltage by a temperature -dependent diode. It is not a good idea to place a heat source next to the chip because it falsifies the recording of the temperature.
The actually highest temperature that can be measured is given on page 4 at 1023.75 ° C. This is understandable when all 12 bits are at 1. Ten bits represent the integer part, the two lowest quality bits the proportion of the measuring value.
The hardware
The hardware naturally differs in the controller used and the necessary Breadboards. ESP8266 and Raspberry Pi Pico can get along with a small Breadboard with 400 pins and four current rails. For the ESP32 you need either two such boards, or better two boards of the variant with 820 pins. Because of the wider board of the controllerboard, we have to put two Breadboards together via a power rail so that pins for the jumper cable still remain.
The circuits are not particularly demanding and therefore well suited for beginners. The same applies to programming.
ESP8266
Figure 5: Structure with ESP8266 and display
I used a D1 Mini V3 Nodemcu for my structure because it fits perfectly on a small Breadboard along with the rest of the hardware.
Figure 6: Max6675 ThermoSmoUple - circuit with ESP8266
ESP32
Figure 7: ESP32 with Max6675
For the ESP32, two Breadboards are needed because of the larger width of the board, which are put together via a power rail.
Figure 8: Max6675 thermo couple - circuit with ESP32
Raspberry Pi Pico (W)
Figure 9: Raspberry Pi Pico with Max6675
Figure 10: Max6675 Thermo couple - circuit with Raspberry Pi Pico (W)
Problems with data transmission via the SPI or I2C bus can be easily Logic Analyzer solve. The free program is used to display the pulse trains on the PC Logic2 from Saleae. Figure 11 shows the conditions on the CLK, Miso, CS and a control management that I used during development.
Figure 11: Sample with control peaks
The software
For flashes and the programming of the ESP32:
Thonny or
Signal tracking:
Used firmware for an ESP32:
Used firmware for an ESP8266:
Used firmware for the Raspberry Pi Pico W:
Rpi_pico_w-20240105-V1.23.1.f2
Used firmware for the Raspberry Pi Pico:
The micropython programs for the project:
timeout.py: Non-blocking software timer
max6675.py: Hardware driver
max6675_spi.py: Hardware driver with SPI
max6675_thermocouple.py: Sample program
max6675_thermoCouple_spi.py: Thermometer application
oled.py: OLED-API
SSD1306.PY: OLED hardware driver
Micropython - Language - Modules and Programs
To install Thonny you will find one here Detailed instructions (English version). There is also a description of how that Micropython firmware (As of 18.06.2022) on the ESP chip burned becomes. Like you that Raspberry Pi Pico get ready for use, you will find here.
Micropython is an interpreter language. The main difference to the Arduino IDE, where you always flash entire programs, is that you only have to flash the Micropython firmware once on the ESP32 so that the controller understands micropython instructions. You can use Thonny, µpycraft or ESPTOOL.PY. For Thonny I have the process here described.
As soon as the firmware has flashed, you can easily talk to your controller in a dialogue, test individual commands and see the answer immediately without having to compile and transmit an entire program beforehand. That is exactly what bothers me on the Arduino IDE. You simply save an enormous time if you can check simple tests of the syntax and hardware to trying out and refining functions and entire program parts via the command line before knitting a program from it. For this purpose, I always like to create small test programs. As a kind of macro, they summarize recurring commands. Whole applications then develop from such program fragments.
Autostart
If the program is to start autonomously by switching on the controller, copy the program text into a newly created blank tile. Save this file from Main.py in WorkSpace and upload it to the ESP chip. The program starts automatically the next time the reset or switching on.
Test programs
Programs from the current editor window in the Thonny-IDE are started manually via the F5 button. This can be done faster than the mouse click on the start button, or via the menu run. Only the modules used in the program must be in the flash of the ESP32.
In between, Arduino id again?
Should you later use the controller together with the Arduino IDE, just flash the program in the usual way. However, the ESP32/ESP8266 then forgot that it has ever spoken Micropython. Conversely, any espressif chip that contains a compiled program from the Arduino IDE or AT-Firmware or Lua or ... can be easily provided with the micropython firmware. The process is always like here described.
The Micropython module for the Max6675
According to the data sheet, the Max6675 is addressed via a emaciated version of the SPI bus. There are only the lines SCK, MISO and -CS. So you cannot send data to the Max6675. A measurement is started by setting -cs to 0 and immediately afterwards at 1.
Page 2 of the data sheet tells us that the provision of a measuring value then typically takes 170ms and a maximum of 220ms. With the next low-setting of the -cs line, the first of 16 bits is placed on the Miso line. The bits have the following meaning.
Figure 12: Bit description
Bit15 is always 0 (sign bit) and can be overlooked because only positive values are sent. The next 12 bits, with the MSB, contain the temperature value, as already described above. The BIT train is sampled with the falling flank of the clock signal. The rising clock flank causes the Max6675 to push the next bit onto the Miso line.
BIT2 signals with a 1 that no thermocouple is connected. Bit1 is always 0 and Bit0 is set highly oh.
After reading the bit sequence, CS is set to 1 again and a new measurement is started.
Compared to other measurement converters, these few conditions are quite easy to implement. We need a few imports.
From machine import Pin
From time import Sleep_us
From time-out import Timeoutms
Then define the Max6675 class and two class attributes.
XXXXXXXXXX
class Max6675:
duration = const(220)
error bit = const(0)
The method init() places the constructor Max6675() of the class. The PIN numbers of the data lines are handed over to him.
XXXXXXXXXX
def __init__(self,sck,miso,CS):
self.sck=Pin(sck,Pin.OUT,value=0)
self.miso=Pin(miso,Pin.OUT,value=1)
self.CS=Pin(CS,Pin.OUT,value=0)
self.error bit=0
self.tempo=0
self.complete=Timeoutms(0)
self.start measurement()
print("Max6675 object ready")
This creates the corresponding PIN objects and declare the instance attributes error bit and tempo.
Timeoutms() is a non -blocking software timer that we use to control the converter time. There is a so -called closure behind it. This is a function that in Timeoutms() is encapsulated and lives on through a trick, even if the surrounding function has already ended. You can find out more about closures in the PDF document Closures and decorators.pdf. Timeoutms() gives a reference to the closure compare() back, which we of the variables complete assign. complete can now be called up as a function. The call from complete() delivers False back if the duration set in milliseconds has not yet expired and Truewhen the time is over. Time never runs with 0 as an argument.
We start a measurement and announce instantiation.
We easily generate the clock by controlling the GPIO pins sck.
XXXXXXXXXX
def clock(self):
self.sck(1)
Sleep_us(2)
self.sck(0)
Sleep_us(2)
It looks similar with starting a converter process. Then we put the timer on the converter period of 220ms. The timer is now running in the background. We can query it anywhere and do other things in the meantime.
XXXXXXXXXX
def start measurement(self):
self.CS(0)
Sleep_us(1)
self.CS(1)
self.complete = Timeoutms(duration)
We come to the status of the error bit through the function error() approach. Of course we could simply do the instance attribute error bit Reference directly in the program. But that is not the fine type of object -oriented programming, simply overnight about innocent instance attributes.
XXXXXXXXXX
def error(self):
return self.error bit
With the function read() we pick up a temperature value. Here you can see the application of the timer complete(). We lie down on our lazy skin until the timer has expired and True returns. Then we leave the loop, set -Cs to 0, wait briefly and then empty the variable dataBy putting all bits to 0.
XXXXXXXXXX
def read(self):
while need self.complete():
passport
self.CS(0)
Sleep_us(10)
data=0
for I in range(15):
self.clock()
data=(data << 1) | self.miso()
self.CS(1)
self.tempo=(data >> 3) / 4
self.error bit=(data & 0x04) >> 2
self.complete = Timeoutms(duration)
return self.tempo
The for loop is run through 15 times. Bit15 has already been placed on the Miso line when -Cs went to 0. We ignore this bit because it brings no use for us.
With one clock we get the next bit to the Miso line. We read the line state and ornament The bit on the variable data, in which we previously pushed all bits around one place to the left. For the rest of the bits, the process is repeated, then we have everything in the box and can -Cs Place again on 1. This automatically starts the new change.
The temperature value is in Bit14: 3. When we the bits in data Push the LSB at BIT position 0 by 3 places.
We areolating the fault bit through Fierce With 0x04. This isolation BIT2. By pushing two positions to the right, the bit ends up in position 0 and delivers the value 0 or 1 error bit away.
After reading, we restore the timer and give back the temperature value.
With the function gettem temp() can be queried again.
XXXXXXXXXX
def gettem temp(self):
return self.tempo
That's it too. The class now provides everything we need in the sample program on the subject of Max6675.
A sample program
How about a program that runs on all three controller families? Finger to the diamond - we can do that! (Download)
Let's start with the weakest comrade, the ESP8266. It has very few GPIOs and is restricted by most restrictions. The next lines show what we can connect where.
XXXXXXXXXX
#Lua-Pins D0 D1 D3 D4 D5 D6 D7 D8 RX TX
#ESP8266 Pins 16 5 4 0 2 14 13 15 3 1
#Ache Hi Hi Lo Hi Hi
#Occupancy SC SD CL SO CS
Only GPIO14, 13 and 15 remain as a really available pins. We need GPIO4 and 5 for the display. The remaining pins must lead the specified levels when booting, otherwise the controller will not start properly. So the SPI lines are documented as specified in the ESP8266.
CLK - GPIO14
Miso - gpio12
CS - GPIO13
With the ESP32 and Raspberry Pi Pico (W) we have enough free cables and can decide what can be done as if you like or other requirements.
But we first import the necessary. We need that I2C-Bus, the class Max6675, the OLED-Api, from Sys The function exit() and the constant platform. The method Time-out() is a timer on a second base similar Timeoutms() and collect() is the broom that the data waste is used and disposed of.
XXXXXXXXXX
From machine import Pin, Softi2c
From Max6675 import Max6675
From OLED import OLED
From sys import exit, platform
From time-out import Time-out
From GC import collect
The constant platform is occupied for each controller family with a clear identifier. We use this to set I2C bus and SPI lines separately for each family.
XXXXXXXXXX
IF platform == "ESP8266":
print("ESP8266 family")
scl=Pin(5)
sda=Pin(4)
I2C=Softi2c(scl,sda,freq=100000)
TC=Max6675(14,12,13) # SCK, MISO, CS
elif platform == "ESP32":
print("ESP32-Family")
scl=Pin(22)
sda=Pin(21)
I2C=Softi2c(scl,sda,freq=100000)
TC=Max6675(13,14,15) # SCK, MISO, CS
elif platform == "RP2":
print("Raspi Pi Pico-Family")
scl=Pin(1)
sda=Pin(0)
I2C=Softi2c(scl,sda,freq=100000)
TC=Max6675(2,4,5) # SCK, MISO, CS
Else:
print("Unknown platform")
exit()
In any case, an I2C bus object and a Max6675 instance are defined.
An OLED object follows. We hand over the I2C object to the constructor and say in the fact that we use a display with a height of 32 pixels.
XXXXXXXXXX
D=OLED(I2C,Heightw=32)
D.writer("Max6675-Thermo",1,0)
Some variables still have to be defined. The subsequent time for measurements we put in 3 seconds in delay Fixed and also put the alarm clock with it.
XXXXXXXXXX
delay=3
refresh = Time-out(delay) # Refresh Every 3 Seconds
n=50
L=[0 for _ in range(n)]
round=0
mini=1023.75
maxi=0
We will be the sliding mean from the last n = Calculate 50 measurements. For this we use the list Lthat we advance with 50 zeros. The means of my choice was a List comprehension. Because there is no special variable necessary, I use the underlined for iteration.
We also need a round counter and memory for the smallest and greatest measurement. For several fixed temperatures, we can determine the deviation from the average as the most likely value and thereby obtain information about the accuracy of the measured values. To use the circuit as a dynamically working thermometer, the average value formation must be carried out differently. We will change the program later.
XXXXXXXXXX
while 1:
IF refresh():
collect()
TC.start measurement()
T=TC.read()
refresh = Time-out(delay)
print(T)
L.append(T)
L.pop(0)
In the main loop we react to the appearance of the timer event refresh(). It delivers Truewhen the timer has expired. Then we collect the data waste, start a measurement and pick up the result. The alarm clock is reorganized and the measured value is issued. We attach it to the list at the back and remove the first element. After 50 runs, the list is completely filled.
XXXXXXXXXX
round+=1
S=0
for I in range(n):
S+=L[I]
M = S / round IF round < n Else S / n
Increase round counters, sum value to 0 and add up the list elements. When calculating the average value, we use the round counter as a divisor up to the 50th round, then our n.
We let the number of the round, the mean and the list in Replica spend. The values of the current temperature and the average standardized on two decimal places also appear on the display,
XXXXXXXXXX
print(round,M,L)
D.writer("Temp: {: 3.2f} *c".format(T),1,1)
D.writer("Medium: {: 3.2f} *c".format(M),0,2)
mini=min(mini,T)
maxi=max.(maxi,T)
dmin=M - mini
dmax=maxi - M
print(dmin, M, dmax,"\ n")
We also calculate the minimal and maximum measured value, as well as the deviations from the mean. With increasing number of measurements, the deviations level on to certain limit values.
At a temperature of approx. 25 ° C, I received deviations from -1.28 ° C and +0.47 ° C of 25.53 ° C. This corresponds to -5.0% / +1.8%.
In the further course of the main loops, further features could now be built into the program via similar timers or other events, such as:
· Querying other sensors
· Switching when a limit is under/exceeded
· Sending values via WLAN
· Write to a file on SD-Card at fixed intervals
For testing the program and the module, each of the three buildings can now be taken.
The somewhat different program
In the data sheet of the Max6675 it also says that the bus would be Spi-compatible. That is a challenge. First we change the module a little. The locations in listing are formatted fat.
XXXXXXXXXX
From machine import Pin, Spi
From time import Sleep_us
From time-out import Timeoutms
class Max6675:
duration = const(220)
error bit = const(0)
def __init__(self,sck,Mosi,miso,CS):
self.spi=Spi(0,baud rate=1000000,
sck=Pin(sck),
Mosi=Pin(Mosi),
miso=Pin(miso))
self.CS=Pin(CS,Pin.OUT,value=0)
self.complete=Timeoutms(0)
self.start measurement()
self.error bit=0
self.tempo=0
print("Max6675 object ready")
def clock(self): # can be removed
self.sck(1)
Sleep_us(2)
self.sck(0)
Sleep_us(2)
def start measurement(self):
self.CS(0)
Sleep_us(1)
self.CS(1)
self.complete = Timeoutms(duration)
def error(self):
return self.error bit
def read(self):
while need self.complete():
passport
self.CS(0)
Sleep_us(10)
MSB,LSB=self.spi.read(2)
data=(MSB << 8) | LSB
self.CS(1)
self.tempo=(data >> 3) / 4
self.error bit=(data & 0x04) >> 2
self.complete = Timeoutms(duration)
return self.tempo
def gettem temp(self):
return self.tempo
The timing is now taking over the class Spi, also the shifting of the bits. The bytes-Object that us Spi.Read() delivers, we unzip in the same line in Msbyte and lsbyte. We push the MSbyte to the left and or or or with the Lsbyte - done. Everything else follows as usual.
We save the listing as max6675_spi.py away.
I promised to present a program for a thermometer. Please, here it is, together with the changed module - max6675_thermoCouple_spi.py.
XXXXXXXXXX
From machine import Pin, Softi2c
From max6675_spi import Max6675
From OLED import OLED
From sys import exit, platform
From GC import collect
IF platform == "ESP8266":
print("ESP8266 family")
scl=Pin(5)
sda=Pin(4)
I2C=Softi2c(scl,sda,freq=100000)
TC=Max6675(14,12,13)
elif platform == "ESP32":
print("ESP32-Family")
scl=Pin(22)
sda=Pin(21)
I2C=Softi2c(scl,sda,freq=100000)
TC=Max6675(13,14,15) # SCK, MISO, CS
elif platform == "RP2":
print("Raspi Pi Pico-Family")
scl=Pin(1)
sda=Pin(0)
I2C=Softi2c(scl,sda,freq=100000)
TC=Max6675(2,3,4,5) # SCK, MISO, CS
Else:
print("Unknown platform")
exit()
D=OLED(I2C,Heightw=32)
D.writer("Max6675-Thermo",1,0)
n=10
while 1:
collect()
S=0
for I in range(n):
T=TC.read()
S+=T
M = S / n
D.writer("Medium: {: 3.1f} *c".format(M),0,2)
# Querying other sensors
# e.g. Switch on a limits when the sub-/transition
# Send values
# Write to a file on SD-Card at fixed intervals
It is not for nothing that I deliberately chose the connections on the Raspberry Pi Pico so that the GPIOs fit the lines of the SPI0 interface. So a change for the test of the SPI module was not necessary. The class max6675.py This is an example of a software Spi-Read-only interface.
We summarize together
The Max6675, together with the thermalement, is not the fastest hardware for a thermometer and also not the most precise solution, but it is an approach with which temperatures up to 1000 ° C can be recorded. The system works relatively reliably at least up to 700 ° C. If you work at temperatures between 700 ° C and 1000 ° C, when cooling below 700 ° C in the CRNI alloy, the nuclear distribution is reorganized, which can lead to incorrect results.
Otherwise, I could imagine the use of the thermometer as a measuring point in EMAILE stoves or kilns. In fluid or gas tanks, use is only possible if it succeeds in closing the gap between the sensor and the surrounding cover with the 6mm thread of heating festival.