项目介绍
本项目基于NXP FRDM-MCXN236主控板,搭配使用了mikroBUS接口的MLX90640模组实现红外热成像相机,并设计对应的上位机网页,实现热成像相机的基本功能。
硬件介绍
- FRDM-MCXN236
FRDM-MCXN236 是 NXP 面向嵌入式开发与原型验证推出的一款 MCXN236 微控制器开发板,核心器件为 MCXN236VDFT,板载 MCU-Link 调试器,可方便地进行程序下载、在线调试和应用开发。开发板提供丰富的扩展资源,包括 Arduino、FRDM、mikroBUS、Pmod、SmartDMA/Camera 以及 FlexIO/LCD 等接口,便于连接传感器、显示屏、摄像头和各类扩展模块。
板上还集成了 CAN 收发器、加速度计、数字麦克风、RGB LED 和 W25Q64 外部 Flash,可满足通信、运动检测、音频采集和数据存储等应用需求。 该开发板支持通过 USB Type-C 与电脑连接,并预置 LED 闪烁示例程序,同时可结合 MCUXpresso 开发环境及官方 SDK、示例代码快速完成系统搭建,适用于物联网、工业控制、智能传感以及人机交互等嵌入式项目开发。

- MLX90640
MLX90640 是 Melexis 推出的一款 32×24 像素非接触式红外热成像传感器,内部集成 768 个红外感温像素,可获取目标区域的二维温度分布。器件出厂已完成校准,并集成环境温度传感器和电源监测功能,测量数据通过 I²C 接口输出,便于与单片机、嵌入式处理器连接。
该模块采用 3.3 V 供电,刷新率可在 0.5~64 Hz 范围内配置,目标温度测量范围约为 −40~300 ℃,工作环境温度为 −40~85 ℃,并提供 55°×35° 和 110°×75° 两种视场角版本。 由于无需机械扫描即可同时获得多个测温点,MLX90640 可用于人体存在与位置检测、非接触测温、智能空调、工业设备温度监测及低分辨率热成像等场景。
方案框图和项目设计思路
本项目整体由 MLX90640 mikroBUS 红外扩展板、FRDM-MCXN236 主控板、Zephyr 下位机程序以及网页上位机四部分组成。
其中,自主设计的 mikroBUS 扩展板完成红外信号采集;FRDM-MCXN236 作为主控制器,通过 I²C 与扩展板进行通信;下位机程序基于 Zephyr 实现 MLX90640 驱动适配、数据采集和 USB 数据传输;网页上位机负责接收数据,并将 32×24 像素的红外数据转换成可视化热成像图像。
方案框图:

调试软件
- vscode
Visual Studio Code(简称VS Code)是由微软开发的一个现代化、轻量级的代码编辑器,它支持几十种主流编程语言(如 JavaScript、Python、C++、Java、Go 等),并通过扩展提供更广泛的语言支持。
VSCode 可以在 Windows、macOS 和 Linux 操作系统上运行,为开发者提供了一个统一的开发环境。
VSCode 因其强大的功能和灵活性,已经成为全球开发者广泛使用的代码编辑器之一。
- MCUXpresso for VS Code
MCUXpresso for VS Code 是 NXP 面向其微控制器平台推出的 Visual Studio Code 开发扩展,可在 VS Code 中完成嵌入式工程的创建、代码编辑、编译、下载和在线调试等工作。它能够配合 MCUXpresso SDK、调试器及相关开发工具使用,为 NXP MCU 提供较完整的一体化开发环境。
在本项目中,MCUXpresso for VS Code 主要用于 FRDM-MCXN236 的 Zephyr 工程开发与调试,包括工程配置、代码编写、编译烧录以及程序运行调试。同时结合 Zephyr 的 Devicetree、I²C 和 USB 等组件,实现 MLX90640 红外扩展板的驱动移植、设备配置和数据采集,为整个红外热成像系统的软件开发提供支持。
mikroBUS 红外扩展板硬件设计
MLX90640只需要使用mikroBUS 上的iic通道即可,板子比较简单,以下是原理图,pcb以及3d示意图



软件流程图关键代码介绍
鉴于本项目主要是基于上位机使用的,则事实上没有必要再mcu侧先将位图的各个温度全部计算出来后再通过cdc上传到上位机(虽然直接在mcu侧计算也完全是可以的)。以下是软件流程图
基于以上,对于mcu侧的代码就很简单了,主要有以下几点,对于MLX90640 官方驱动的移植,将移植好的驱动注册到zephyr中并为其绑定好引脚,配置好cdc驱动并设计串口数据包:
- MLX90640 官方驱动的移植以及注册
官方驱动仓库:mlx90640-library/headers at master · melexis/mlx90640-library
移植完(见附件)后只需要按照以下流程注册并使用即可:在设备树里添加设备 → 写设备的配置说明 → 驱动匹配这个设备 → Zephyr 创建设备实例 → 应用程序获取并使用设备
根据zephyr的文档可知,microBUS理论上应该被抽象成以下的结点,写app.overlay直接使用&mikrobus_i2c即可,但实际上,通过搜索板级文件frdm_mcxn236.dts我们可以发现,官方并没有对microBUS总线进行映射。


所以直接在microBUS上面的iic总线上挂载mlx90640即可
#app.overlay
&flexcomm2_lpi2c2 {
status = "okay";
clock-frequency = <I2C_BITRATE_FAST>;
mlx90640: mlx90640@33 {
compatible = "melexis,mlx90640-raw";
reg = <0x33>;
status = "okay";
};
};
接下来在创建以下路径dts/bindings/sensor创建文件melexis,mlx90640-raw.yaml,并在移植好的驱动代码调用刚刚定义的设备即可
#melexis,mlx90640-raw.yaml
description: Melexis MLX90640 thermal infrared array used by the custom MLXR raw driver
compatible: "melexis,mlx90640-raw"
include: i2c-device.yaml
- cdc驱动
基本与上一致,在设备树中进行注册,本次工程额外将初始化发送全部封装了一遍,使用时直接调用函数即可
#app.overlay
&zephyr_udc0 {
cdc_acm_uart0 {
compatible = "zephyr,cdc-acm-uart";
label = "Zephyr USB CDC-ACM";
};
};
#ifndef USB_CDC_H_
#define USB_CDC_H_
#include <stddef.h>
#include <stdint.h>
int usb_cdc_init(void);
int usb_cdc_send(const uint8_t *data, size_t len);
#endif
还需要打开cdc和iic的相关配置
#prj.conf
CONFIG_USBD_CDC_ACM_CLASS=y
CONFIG_CDC_ACM_SERIAL_INITIALIZE_AT_BOOT=n
CONFIG_SAMPLE_USBD_PID=0x0001
CONFIG_SAMPLE_USBD_PRODUCT="USBD CDC ACM sample"
CONFIG_I2C=y
- 串口数据包格式
然后简单设计一个串口数据包格式

- mian.c
主程序采用双线程架构:主函数完成 USB CDC 与 MLX90640 初始化,并创建采集线程和发送线程;采集线程读取热成像原始数据并放入 FIFO,USB 线程从 FIFO 取数据并发送,实现采集与传输解耦
#include <stddef.h>
#include <stdint.h>
#include <errno.h>
#include <zephyr/kernel.h>
#include <zephyr/logging/log.h>
#include "mlx90640.h"
#include "usb_cdc.h"
#define MLX90640_REFRESH_RATE 7U // MLX90640最大64Hz Subpage刷新率
#define MLX90640_EEPROM_RESEND_COUNT 128U // 每128个FRAME包重发一次EEPROM
#define MLX90640_PACKET_BUFFER_COUNT 8U // MLXR高速发送缓冲区数量
#define MLX90640_CAPTURE_STACK_SIZE 8192U // MLX90640采集线程栈大小
#define MLX90640_USB_STACK_SIZE 4096U // USB发送线程栈大小
#define MLX90640_CAPTURE_PRIORITY 3 // MLX90640采集线程优先级
#define MLX90640_USB_PRIORITY 4 // USB发送线程优先级
#define MLX90640_READ_ERROR_DELAY_MS 1U // MLX90640读取失败重试延时
#define USB_SEND_RETRY_DELAY_MS 1U // USB发送缓冲区满重试延时
LOG_MODULE_REGISTER(main, LOG_LEVEL_INF);
struct thermal_packet_buffer {
void *fifo_reserved; // Zephyr FIFO内部链表保留字段
size_t packet_size; // 当前MLXR数据包有效长度
uint8_t data[MLX90640_PACKET_SIZE]; // MLXR完整二进制数据包
};
static struct thermal_packet_buffer packet_buffers[MLX90640_PACKET_BUFFER_COUNT]; // MLXR高速缓冲池
K_FIFO_DEFINE(free_packet_fifo); // 空闲MLXR缓冲区FIFO
K_FIFO_DEFINE(tx_packet_fifo); // 等待USB发送的MLXR缓冲区FIFO
K_THREAD_STACK_DEFINE(capture_stack, MLX90640_CAPTURE_STACK_SIZE); // MLX90640采集线程栈
K_THREAD_STACK_DEFINE(usb_stack, MLX90640_USB_STACK_SIZE); // USB发送线程栈
static struct k_thread capture_thread_data; // MLX90640采集线程控制块
static struct k_thread usb_thread_data; // USB发送线程控制块
static void packet_buffer_release(struct thermal_packet_buffer *buffer)
{
k_fifo_put(&free_packet_fifo, buffer); // 将MLXR缓冲区归还空闲池
}
static struct thermal_packet_buffer *packet_buffer_acquire(void)
{
return k_fifo_get(&free_packet_fifo, K_FOREVER); // 获取一个空闲MLXR缓冲区
}
static void packet_buffer_submit(struct thermal_packet_buffer *buffer)
{
k_fifo_put(&tx_packet_fifo, buffer); // 将完整MLXR包提交给USB线程
}
static void mlx90640_send_eeprom_to_queue(void)
{
struct thermal_packet_buffer *buffer; // EEPROM数据包缓冲区
int ret; // MLX90640函数返回值
buffer = packet_buffer_acquire(); // 获取空闲缓冲区
ret = mlx90640_read_eeprom_packet(buffer->data, sizeof(buffer->data), &buffer->packet_size); // 获取EEPROM原始包
if (ret != MLX90640_NO_ERROR) {
packet_buffer_release(buffer); // EEPROM读取失败后归还缓冲区
return;
}
packet_buffer_submit(buffer); // EEPROM包提交给USB发送线程
}
static void mlx90640_capture_thread(void *p1, void *p2, void *p3)
{
struct thermal_packet_buffer *buffer; // 当前MLXR采集缓冲区
uint32_t frame_count = 0U; // 已成功采集FRAME包数量
int ret; // MLX90640函数返回值
ARG_UNUSED(p1);
ARG_UNUSED(p2);
ARG_UNUSED(p3);
mlx90640_send_eeprom_to_queue(); // 启动后立即发送一次EEPROM校准包
while (1) {
buffer = packet_buffer_acquire(); // 获取空闲FRAME缓冲区
ret = mlx90640_read_raw_packet(buffer->data, sizeof(buffer->data), &buffer->packet_size); // 获取MLX90640 Raw Subpage包
if (ret != MLX90640_NO_ERROR) {
packet_buffer_release(buffer); // Raw读取失败后归还缓冲区
k_sleep(K_MSEC(MLX90640_READ_ERROR_DELAY_MS)); // 短暂等待后继续采集
continue;
}
packet_buffer_submit(buffer); // FRAME包提交给USB线程
frame_count++; // 更新成功FRAME数量
if ((frame_count % MLX90640_EEPROM_RESEND_COUNT) == 0U) {
mlx90640_send_eeprom_to_queue(); // 周期重发EEPROM保证网页可随时连接
}
}
}
static void usb_tx_thread(void *p1, void *p2, void *p3)
{
struct thermal_packet_buffer *buffer; // 当前待发送MLXR缓冲区
int ret; // USB CDC函数返回值
ARG_UNUSED(p1);
ARG_UNUSED(p2);
ARG_UNUSED(p3);
while (1) {
buffer = k_fifo_get(&tx_packet_fifo, K_FOREVER); // 等待完整MLXR包
while (1) {
ret = usb_cdc_send(buffer->data, buffer->packet_size); // 向USB CDC发送完整MLXR包
if (ret == 0) break;
if (ret == -ENOSPC) {
k_sleep(K_MSEC(USB_SEND_RETRY_DELAY_MS)); // CDC TX缓存满时等待后继续发送同一包
continue;
}
break;
}
packet_buffer_release(buffer); // USB处理完成后归还MLXR缓冲区
}
}
int main(void)
{
int ret; // 初始化函数返回值
ret = usb_cdc_init(); // 初始化USB CDC设备
if (ret != 0) {
LOG_ERR("usb_cdc_init failed: %d", ret);
return 0;
}
ret = mlx90640_init_raw(); // 初始化MLX90640 Raw采集模式
if (ret != MLX90640_NO_ERROR) {
LOG_ERR("mlx90640_init_raw failed: %d", ret);
return 0;
}
ret = mlx90640_set_refresh_rate(MLX90640_REFRESH_RATE); // 设置MLX90640最大64Hz Subpage刷新率
if (ret != MLX90640_NO_ERROR) {
LOG_ERR("mlx90640_set_refresh_rate failed: %d", ret);
return 0;
}
for (uint32_t i = 0U; i < MLX90640_PACKET_BUFFER_COUNT; i++) {
k_fifo_put(&free_packet_fifo, &packet_buffers[i]); // 将所有MLXR缓冲区加入空闲FIFO
}
k_thread_create(&usb_thread_data, usb_stack, K_THREAD_STACK_SIZEOF(usb_stack), usb_tx_thread, NULL, NULL, NULL, MLX90640_USB_PRIORITY, 0, K_NO_WAIT); // 创建USB发送线程
k_thread_name_set(&usb_thread_data, "mlx_usb_tx"); // 设置USB发送线程名称
k_thread_create(&capture_thread_data, capture_stack, K_THREAD_STACK_SIZEOF(capture_stack), mlx90640_capture_thread, NULL, NULL, NULL, MLX90640_CAPTURE_PRIORITY, 0, K_NO_WAIT); // 创建MLX90640采集线程
k_thread_name_set(&capture_thread_data, "mlx_capture"); // 设置MLX90640采集线程名称
LOG_INF("MLX90640 high speed stream started");
return 0;
}
- 上位机的实现
在串口助手的基础上,增加了热图显示,并内置了4种热成像常用的插值算法,多种不同的色卡提供给选择。


- 工程目录结构
zephyr_MCXN236_MLX90640/
├── CMakeLists.txt
├── prj.conf # Kconfig 片段(USB 设备栈 / I2C / 日志)
├── app.overlay # 设备树覆盖:CDC-ACM 节点 + LPI2C2 上的 MLX90640
├── dts/bindings/sensor/
│ └── melexis,mlx90640-raw.yaml # 自定义 compatible 绑定(仅继承 i2c-device.yaml)
├── src/
│ ├── main.c # 线程编排、缓冲区池、FIFO 调度
│ ├── usb_cdc.c / usb_cdc.h # USB CDC-ACM 初始化与环形缓冲非阻塞发送
│ └── mlx90640/
│ ├── MLX90640_API.c/.h # Melexis 官方 API(参数提取、温度计算、坏点修复)
│ ├── MLX90640_I2C_Driver.c/.h# 官方驱动的 Zephyr I2C 移植层(本项目核心改动)
│ ├── mlx90640.c # 二次封装:原始模式 / 温度模式
│ ├── mlx90640_packet.c/.h # MLXR 二进制包封装协议
│ └── include/mlx90640_config.h # 集中式参数表
└── tool/mlx90640_tool.html # 网页上位机工具
功能展示图及说明

这里展示一下拍摄的效果

项目中遇到的难题及解决方法
本次项目其实比较简单,主要是对于Zephyr 熟悉程度不足,大部分时间基本都花在这了解如何使用Zephyr ,如何注册,调用,绑定设备上。
心得体会
本次也是接触到了很多新知识,热成像模组的使用,实际热图显示的插值算法,Zephyr的使用,收获颇丰