A noncontact thermometer does not read one infinitely small point. Move an infrared sensor farther from a small target and more of its surroundings can contribute to the result, even when the digital data pass every checksum. This experiment connects one exact three-volt MLX90614 to a Pico, verifies its packet and error flag, then separates apparent object temperature, sensor temperature and target geometry.
Choose the complete suffix before connecting power
Use a Melexis MLX90614ESF-BAA-000-TU, the three-volt, single-zone standard-field version, in the four-lead Transistor Outline TO-39 metal package. This is a bare sensor with explicit support parts, not an unspecified GY-series module. The B supply option is essential: the similarly named AAA five-volt version has different electrical conditions. A similar can or a shared bus address does not establish interchangeability.
The current manufacturer download is datasheet revision 021 dated June 2, 2026. For the selected B supply family, normal VDD is 2.6โ3.6 V, with electrical tables referenced to a nominal 3.0 V. Pico’s 3.3 V supply is inside the operating range. That does not mean the specified supply dependence has vanished; record actual supply, and do not apply a typical compensation coefficient as if it were an individual calibration. Absolute maximum ratings are not the target for a bench supply.
The selected E temperature grade is intended for sensor temperatures from โ40 to 85 ยฐC. Its internal sensor-temperature channel and its remotely inferred object-temperature channel describe different things. The sensor temperature is the package’s thermal state, not a direct room-air reference. This tabletop experiment uses ordinary dry surfaces near room temperature. It does not establish a clinical thermometer, fever measurement or a calibration standard.
Actual chip pins and passive support parts

| Exact MLX90614 pin | Connection | Function |
|---|---|---|
| 1 SCL/Vz | Pico GP1, physical 2; 10 kฮฉ to 3.3 V | Serial clock; use the selected B variant’s electrical specification |
| 2 SDA/PWM | Pico GP0, physical 1; separate 10 kฮฉ to 3.3 V | Serial data in factory-default bus mode |
| 3 VDD | Pico 3V3 OUT, physical 36 | Sensor supply |
| 4 VSS | Pico ground, physical 38 | Common return |
Add one 100 nF ceramic capacitor rated at least 10 V directly between pins 3 and 4. It is a local supply bypass, not a filter placed on the sensing window. Use two 10 kฮฉ pull-ups, one on each bus line, with short leads around 10 cm. At 3.3 V the nominal low-state current through one pull-up is about 3.3/10000 = 0.33 mA before the output’s small low voltage. The chosen resistance is a bench starting point, not proof of a measured rise time.
Identify leads from the exact manufacturer’s package drawing and index mark. The functional schematic numbers nets; it is not a photograph or a license to assume a left-to-right lead order. A bottom view and a top view mirror each other. Verify pin numbering before insertion. Disconnect power when changing the circuit, and use the Pico’s Universal Serial Bus (USB) connector as the only power input.
The metal package and optical opening must remain unobstructed. Do not press the window against a surface or warm the can with your fingers during acquisition. Keep the Pico regulator and other warm electronics away from it. Thermal gradients across the sensor package can affect interpretation even if the target itself is steady. The electronics lessons provide the ground and pull-up foundations; this experiment adds optical and thermal context.
A System Management Bus packet with an error check
The sensor’s factory-default address is seven-bit 0x5A. Its System Management Bus (SMBus) interface supports 10โ100 kHz; the code selects 100 kHz. MicroPython’s Inter-Integrated Circuit (IยฒC) peripheral performs the required read-word transaction: send command with no stop, issue a repeated start for the read, and receive low byte, high byte and packet error check (PEC). A repeated start keeps the command and returned word in the intended transaction.
The PEC is an eight-bit cyclic redundancy check (CRC) using polynomial 0x07 and initial value zero. Its input includes write-address byte B4, the command, read-address byte B5, then the low and high data bytes. Checking only the two data bytes would be the wrong packet calculation. The manufacturer’s example command 07 with data D2 3A has PEC 30; the tests reproduce that exact vector.
Command 0x06 reads sensor temperature from random-access memory (RAM), and 0x07 reads the single-zone object channel. The code allows only those two commands. It does not write electrically erasable programmable read-only memory (EEPROM), change emissivity, program an address or switch output modes. Inspect an unexpected device or mode instead of sending a permanent write to make a generic driver work.
The two data bytes form an unsigned word, with the most significant bit used as a temperature error flag for the object result. This conservative decoder rejects a set high bit on either temperature read. After validation, temperature in Celsius is raw ร 0.02 โ 273.15. Thus raw 0x3AD2 equals 15058, or 28.01 ยฐC. A 0.02-degree step is resolution, not a claim of 0.02-degree accuracy.
A short frame, failed command, transfer exception, invalid PEC, error flag or value outside the selected part’s temperature domain latches a session fault. A partially read pair is not emitted. The program reads sensor and object temperatures sequentially, so it does not claim simultaneous physical samples. No exposed sequence counter proves that every poll represents a different internal conversion. Each output keeps physical_sample_time_known false and true_surface_temperature_C unknown.
The field of view has weighted edges

The xAA optical option has a nominal 90-degree field width at half response to the manufacturer’s point-source test. Half response is a sensitivity definition, not a hard circular boundary that abruptly excludes everything outside it. Contributions beyond the illustrated cone and the target’s shape still matter. A small hot area on a larger cool background produces a combined radiometric observation, not necessarily the small area’s true temperature.
For a simple nominal cone illustration, diameter D = 2d tan(ฮธ/2). With ฮธ = 90 degrees, D = 2d. At distance d = 50 mm, the nominal half-response span is about 100 mm; at 100 mm, about 200 mm. The supplied helper calculates this geometry. It does not calculate the exact weighted footprint, certify target fill or correct a mixed scene. Make a target much larger than the illustrated span when investigating the target itself, and document the surroundings.
Emissivity describes how a surface emits thermal radiation relative to an ideal reference at the same temperature. Reflected thermal radiation also matters. A shiny surface can return a different apparent reading from a matte surface at a similar physical temperature because its emitted and reflected contributions differ. This is not fixed by checking the digital packet again. Factory emissivity settings are not automatically correct for every object.
Ordinary visible transparency is not a guarantee of transparency in the sensor’s infrared band. A window between the sensor and an object can contribute its own emission and change what reaches the detector. Do not describe this sensor as seeing through household glass. If comparing with an independent contact thermometer, record its placement, contact quality, settling and uncertainty rather than declaring one instrument right from a single difference.
Full read-only program
The original RP2040 Pico runs MicroPython 1.26.0. Save the following as main.py or use the readโevaluateโprint loop (REPL), its interactive console. The program waits 500 milliseconds before acquisition, beyond the specified 250-millisecond power-on data-valid interval. Thermal equilibrium of the package is a separate and potentially longer requirement. Polling then pauses 500 milliseconds between diagnostic pairs.
"""Read-only MLX90614ESF-BAA lab; original Pico/MicroPython1.26.0.
No EEPROM writes, emissivity adjustment or medical interpretation.
"""
import math
def pec(data):
crc=0
for byte in data:
crc^=byte
for _ in range(8):crc=((crc<<1)^7)&255 if crc&128 else (crc<<1)&255
return crc
def decode(command,frame):
if command not in (0x06,0x07):raise ValueError('Only temperature RAM reads allowed')
if not isinstance(frame,(bytes,bytearray))or len(frame)!=3:raise ValueError('Need low byte, high byte, PEC')
if pec(bytes([0xb4,command,0xb5])+frame[:2])!=frame[2]:raise ValueError('Packet error check failed')
raw=frame[0]|frame[1]<<8
if raw&0x8000:raise ValueError('Temperature error flag set')
value=raw*.02-273.15
low,high=(-40,85)if command==6 else(-70,380)
if not low<=value<=high:raise ValueError('Outside selected part temperature domain')
return value
def cone_width(distance_mm):
if type(distance_mm)not in(int,float)or not math.isfinite(distance_mm)or not 0<distance_mm<=1000:
raise ValueError('Distance must be finite, positive and at most1000mm')
# Nominal90-degree full width at half response, not a hard-edged sensed area.
return 2*distance_mm*math.tan(math.radians(45))
class MLX90614:
def __init__(self,bus):self.bus=bus;self.failed=False
def read(self,command):
if self.failed:raise ValueError('Session faulted; inspect before a new run')
if command not in(6,7):raise ValueError('Read-only temperature commands only')
try:
if self.bus.writeto(0x5a,bytes([command]),False)!=1:raise OSError('Command incomplete')
return decode(command,self.bus.readfrom(0x5a,3))
except BaseException:
self.failed=True;raise
def pair(self):
ambient=self.read(6)
apparent=self.read(7)
return {'state':'APPARENT_TEMPERATURE_DIAGNOSTIC','sensor_temperature_C':ambient,
'apparent_object_temperature_C':apparent,'physical_sample_time_known':False,
'true_surface_temperature_C':None}
def acquire(sensor,sleep_ms,emit=print,count=60):
if type(count)is not int or not 1<=count<=1000:raise ValueError('Invalid bounded run length')
sleep_ms(500)
for i in range(count):
row=sensor.pair();row['poll_index']=i;emit(row);sleep_ms(500)
def main():
from machine import Pin,I2C
from time import sleep_ms
sensor=MLX90614(I2C(0,sda=Pin(0),scl=Pin(1),freq=100000))
try:acquire(sensor,sleep_ms)
except (OSError,ValueError)as error:
print({'state':'SESSION_FAULT','apparent_object_temperature_C':None,'error':str(error)})
if __name__=='__main__':main()
Change target geometry, not several variables at once
- Record the exact BAA marking, firmware, supply measured at the can, support parts, target material and room setup. Inspect the unpowered wiring and package orientation before connecting USB.
- Fix the sensor on a nonconductive support and allow it to stop warming after handling. Begin with a large matte room-temperature target. Record sensor and apparent-object readings together, without filling the unknown true-surface field.
- At a fixed orientation, compare distances such as 25, 50 and 100 mm. Calculate the nominal half-response span for each and record whether the target remains much larger. Avoid physically touching the sensor or target during a run.
- Use a smaller target with a visibly different background at a similar safe temperature range. Repeat distances in separate labelled trials. A changing apparent reading can expose target-fill sensitivity, but does not by itself identify exact surface temperatures.
- Return to the first large target and placement. Check whether readings and sensor temperature return near their earlier range. Document any change instead of silently applying a numerical offset.
There is no need for boiling water, open heating elements or body measurements. The blank observation sheet separates distance, nominal geometry, apparent temperature, sensor temperature, target material, background and independent reference. Keep a failed transaction as a failed trial. A new Python object starts another bus session after inspection; it does not prove a fault was repaired or restore a lost physical observation.
Challenge: a 40 mm target is 100 mm away from the xAA sensor. Is a valid PEC enough to call the number that target’s temperature? Answer: no. The nominal half-response span is approximately 200 mm, much larger than the target, and the weighting has no hard edge. The surroundings and surface properties remain part of the problem. Packet integrity and optical selectivity answer different questions.
Twenty actual-code host checks cover the published PEC example, temperature conversion, corruption of each frame byte, command-dependent PEC, error flags, domain guards, repeated-start call sequence, partial-pair failure, sticky session faults, bounded acquisition and geometry. They run with injected bus objects on desktop Python. They do not verify an SMBus waveform, optical response, emissivity, target temperature or firmware execution on a Pico. The physical bench log is empty.
Current primary sources
- Melexis current datasheet page and MLX90614 revision 021, June 2, 2026: exact supply options, package pins, read-word PEC, error flag, optics and thermal constraints.
- MicroPython 1.26 IยฒC transactions and original Pico pinout.
No separate applicable erratum was located in the manufacturer’s current documentation examined; absence is not guaranteed. Recheck the complete ordering suffix and current primary document before a physical build. A module with another supply option or optic is a different fixture.
Download the reader files (ZIP): complete source code, the blank observation worksheet and original editable SVG/PNG diagrams for pico-mlx90614-spot-geometry are inside tricky-circuit-batch008-campaigns75-80/. The inline program is complete; the download is supplementary.





