Funpack5-4 - 基于FRDM-MCXN236和MLX90640的热成像相机
该项目使用了FRDM-MCXN236,MLX90640,实现了热成像相机的设计,它的主要功能为:热成像。
标签
嵌入式系统
Funpack活动
开发板
LGX
更新2026-10-16
23

项目介绍

本项目基于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、示例代码快速完成系统搭建,适用于物联网、工业控制、智能传感以及人机交互等嵌入式项目开发。

image.png

  • 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 像素的红外数据转换成可视化热成像图像。

方案框图:

image.png

调试软件

  • 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示意图

image.pngimage.pngimage.png

软件流程图关键代码介绍

鉴于本项目主要是基于上位机使用的,则事实上没有必要再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总线进行映射。

image.pngimage.png

所以直接在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
  • 串口数据包格式

然后简单设计一个串口数据包格式

image.png

  • 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种热成像常用的插值算法,多种不同的色卡提供给选择。

image.pngimage.png

  • 工程目录结构
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 # 网页上位机工具

功能展示图及说明

f8c9009cc4a20f5b65e8900b23fdcd0f.jpg

这里展示一下拍摄的效果

image.png

项目中遇到的难题及解决方法

本次项目其实比较简单,主要是对于Zephyr 熟悉程度不足,大部分时间基本都花在这了解如何使用Zephyr ,如何注册,调用,绑定设备上。

心得体会

本次也是接触到了很多新知识,热成像模组的使用,实际热图显示的插值算法,Zephyr的使用,收获颇丰

附件下载
zephyr_MCXN236_MLX90640.rar
mlx90640_tool.html
MLX90640-Datasheet-Melexis.pdf
MCXN236QSG (2).pdf
SCH_Schematic1_2026-09-12.pdf
ProPrj_MLX90640模块_2026-09-12.epro2
团队介绍
个人
团队成员
LGX
评论
0 / 100
查看更多
硬禾服务号
关注最新动态
0512-67862536
info@eetree.cn
江苏省苏州市苏州工业园区新平街388号腾飞创新园A2幢815室
苏州硬禾信息科技有限公司
Copyright © 2024 苏州硬禾信息科技有限公司 All Rights Reserved 苏ICP备19040198号