任务介绍
2025 Make Blocks第三期开始了,原先看到这个主题的时候初步猜想可能有温度这类比较大的任务,任务公开看了一下大部分倾向于运动控制,IMU传感器实际也是运动相关,结合最后的呈现效果,我们本次选择的任务是设计一款图像检测模块,可实现物体成像,如红外阵列、CMOS摄像头、热成像模块等,这个正好也和我们的最终目标不谋而合,在得捷上看了一下相关的传感器,说实话真的有点贵重,这样如果设计的一个板子上后面升级就有点麻烦了,那么这次我们就设计成一个单独的模块。
模块功能介绍
本次根据任务选择设计一个热成像模块(MLX90640),MLX90640是一种高分辨率、低功耗的热成像传感器,可以用于非接触式温度检测。该传感器采用MEMS技术制造,具有32x24像素的分辨率。传感器自带I2C接口,可以直接连接到主控单元上。可精确检测特定区域和温度范围内的目标物体,尺寸小巧,可方便集成到各种工业或智能控制应用中。
- 工作电压:3.3V/5V
- 通信接口:I2C (地址为0x33)
- 视场角(水平视角×垂直视角):55°×35° (角度小,适合远距离测量)
- 工作温度:-40℃~85℃
- 目标温度:-40℃~300℃
- 检测精度:±1℃
- 刷新速率:0.5Hz~64Hz (可编程设置)。
模块硬件介绍
MLX90640是Melexis公司推出的一款高性能32×24像素红外热传感器阵列,采用MEMS技术实现非接触式温度检测,适用于工业、安防、智能家居等领域。其核心参数包括32×24像素分辨率(768个测温点),测温范围-40°C至300°C,典型精度±1°C,提供D55(55°×35°)和D110(110°×75°)两种视场角型号,支持I²C接口(速率达1MHz),工作电压3.3V/5V,功耗低于23mA。该传感器具有高精度(NETD低至0.1K RMS@1Hz)、免校准、紧凑设计(4引脚TO39封装)等优势,噪声等效温差表现优异,无需频繁校准,集成光学元件简化系统设计。典型应用场景包括工业设备温度监测、智能家居人体检测、安防热成像监控等,特别适合需要低成本热成像解决方案的场景。开发时推荐使用STM32等ARM芯片(主频50MHz以上,SRAM≥20KB),支持数据插值算法提升分辨率(如扩展至512×384),并具备坏点校准功能(最多4个坏点)。
设计框图
设计框图主要是模块内的功能关系,如下:
原理图和PCB模块介绍
原理图
PCB
本次设计的模块采用独立的模式呈现,传感器的供电电压固定是3.3V,可以通过外部5V,板载有LDO进行压降,主要IO口控制电平为3.3V,相关IIC引脚已经上拉。
3D效果图
实物图
软件调试
本次的调试通过Adafruit ESP32-S3 TFT Feather开发板进行,主要是有屏幕能进行显示,也有对应的库进行快速开发,使用CircuitPython尽心公开发,对应的驱动代码如下:
import time
import board
import busio
import displayio
import terminalio
from adafruit_display_text.label import Label
from simpleio import map_range
import adafruit_mlx90640
number_of_colors = 64 # Number of color in the gradian
last_color = number_of_colors - 1 # Last color in palette
palette = displayio.Palette(number_of_colors) # Palette with all our colors
color_A = [
[0, 0, 0],
[0, 0, 255],
[0, 255, 255],
[0, 255, 0],
[255, 255, 0],
[255, 0, 0],
[255, 255, 255],
]
color_B = [[0, 0, 255], [0, 255, 255], [0, 255, 0], [255, 255, 0], [255, 0, 0]]
color_C = [[0, 0, 0], [255, 255, 255]]
color_D = [[0, 0, 255], [255, 0, 0]]
color = color_B
NUM_COLORS = len(color)
def MakeHeatMapColor():
for c in range(number_of_colors):
value = c * (NUM_COLORS - 1) / last_color
idx1 = int(value) # Our desired color will be after this index.
if idx1 == value: # This is the corner case
red = color[idx1][0]
green = color[idx1][1]
blue = color[idx1][2]
else:
idx2 = idx1 + 1 # ... and before this index (inclusive).
fractBetween = value - idx1
red = int(
round((color[idx2][0] - color[idx1][0]) * fractBetween + color[idx1][0])
)
green = int(
round((color[idx2][1] - color[idx1][1]) * fractBetween + color[idx1][1])
)
blue = int(
round((color[idx2][2] - color[idx1][2]) * fractBetween + color[idx1][2])
)
palette[c] = (0x010000 * red) + (0x000100 * green) + (0x000001 * blue)
MakeHeatMapColor()
# Bitmap for colour coded thermal value
image_bitmap = displayio.Bitmap(32, 24, number_of_colors)
# Create a TileGrid using the Bitmap and Palette
image_tile = displayio.TileGrid(image_bitmap, pixel_shader=palette)
# Create a Group that scale 32*24 to 128*96
image_group = displayio.Group(scale=4)
image_group.append(image_tile)
scale_bitmap = displayio.Bitmap(number_of_colors, 1, number_of_colors)
# Create a Group Scale must be 128 divided by number_of_colors
scale_group = displayio.Group(scale=2)
scale_tile = displayio.TileGrid(scale_bitmap, pixel_shader=palette, x=0, y=60)
scale_group.append(scale_tile)
for i in range(number_of_colors):
scale_bitmap[i, 0] = i # Fill the scale with the palette gradian
# Create the super Group
group = displayio.Group()
min_label = Label(terminalio.FONT, color=palette[0], x=0, y=110)
max_label = Label(terminalio.FONT, color=palette[last_color], x=80, y=110)
# Add all the sub-group to the SuperGroup
group.append(image_group)
group.append(scale_group)
group.append(min_label)
group.append(max_label)
# Add the SuperGroup to the Display
board.DISPLAY.root_group = group
min_t = 20 # Initial minimum temperature range, before auto scale
max_t = 37 # Initial maximum temperature range, before auto scale
i2c = busio.I2C(board.SCL, board.SDA, frequency=800000)
# i2c = board.STEMMA_I2C() # For using the built-in STEMMA QT connector on a microcontroller
mlx = adafruit_mlx90640.MLX90640(i2c)
print("MLX addr detected on I2C")
print([hex(i) for i in mlx.serial_number])
# mlx.refresh_rate = adafruit_mlx90640.RefreshRate.REFRESH_2_HZ
mlx.refresh_rate = adafruit_mlx90640.RefreshRate.REFRESH_4_HZ
frame = [0] * 768
while True:
stamp = time.monotonic()
try:
mlx.getFrame(frame)
except ValueError:
# these happen, no biggie - retry
continue
# print("Time for data aquisition: %0.2f s" % (time.monotonic()-stamp))
mini = frame[0] # Define a min temperature of current image
maxi = frame[0] # Define a max temperature of current image
for h in range(24):
for w in range(32):
t = frame[h * 32 + w]
if t > maxi:
maxi = t
if t < mini:
mini = t
image_bitmap[w, (23 - h)] = int(map_range(t, min_t, max_t, 0, last_color))
min_label.text = "%0.2f" % (min_t)
max_string = "%0.2f" % (max_t)
max_label.x = 120 - (5 * len(max_string)) # Tricky calculation to left align
max_label.text = max_string
min_t = mini # Automatically change the color scale
max_t = maxi
# print((mini, maxi)) # Use this line to display min and max graph in Mu
# print("Total time for aquisition and display %0.2f s" % (time.monotonic()-stamp))
效果展示
心得体会
这次实际上可以选择的内容非常多,毕竟传感器是咱们最常用到,不过最后还是选择了最相符的进行的设计,由于价值比较高,最好做成单独的模块可以重复使用,到时候在板子上做个接口就可以了。