基于纳芯微NST461和NSPAD1N制作的温度-气压-水体溶氧量显示仪
该项目使用了NST461和NSPAD1N,实现了水体溶氧量显示仪的设计,它的主要功能为:检测水体温度、绝对气压,计算水体溶氧量并显示,为室内养鱼提供参考。
标签
温度检测
气压检测
溶氧量
小小洋洋
更新2025-12-02
同济大学
41
KiCad文件
全屏

一、任务活动介绍

本次参与的活动为“WeDesign第7期:纳芯微传感器 + MCU芯片/评估板”,WeDesign活动是硬禾学堂发起的“一起设计、一起体验”活动本项目选择了纳芯微温度传感器 NST461-DQNR绝压传感器 NSPAD1N200DR04,任务要求如下:

  1. 制作电路模块,读取NST461-DQNR本地温度和远程温度,读取NSPAD1N200DR04压力值,并使用串口软件完成温度曲线图绘制。
  2. 使用12指神探查看I2C的数据波形,与规格书的波形比较,并结合实际数据分析,完成报告书。


二、项目描述

eb333778fffe44fe345e61b1d794120b.jpg9f41ea8d71f931a9c4eeeb524db39648.jpg

image.png9e8cead09e63f86300fcece47e082e1f.jpg

本项目考虑在获取了温度和大气压强数据基础上,实现更加实用的应用。正好前段时间,家里购置了鱼缸和小金鱼,对于我这类养鱼新手,需要更精准的数据而不是经验。

因此,为了更好的监测养鱼环境数据,包括温度、水体温度、大气压强、参考水体溶氧量数据,以指导是否进行水体加热、水体降温、水体加氧操作

该项目在活动基本任务基础上,增加如下功能:

  1. 水体温度测量:利用三极管制作远端温度头,并进行防水封装,实现水体温度测量;
  2. 溶氧量标准计算:利用环境温度、水体温度、大气压强,参考《中华人民共和国国家环境保护标准》和《中华人民共和国国家标准—水质、溶解氧的测定》,理论计算水体溶氧量。
  3. 直观数据展示:通过屏幕直观展示上述数值、并通过图案标识。


三、芯片选型

  1. 温度传感器 NST461-DQNR:

NST461-DQNR 是一款高精度且低功耗的数字温度传感器,基于 CMOS 工艺晶体管 PN 结的温度效应,分辨率高达 0.0625°C。除了具有高精度的本地温度测量能力外,该传感器还支持通过外部晶体管进行远程温度测量。其远程测量功能主要通过外部低成本晶体管或二极管实现。

  1. 绝压传感器 NSPAD1N200DR04:

NSPAD1N200DR04 是一款经过精确校准的绝对压力传感器,采用汽车级专用集成电路(ASIC)对 MEMS 传感器元件进行校准和补偿,能够将 10千帕到40万帕 的压力信号转换为 SPI/I2C 输出信号。

  1. 主控SEEED XIAO ESP32-C6:

ESP32-C6 是一款基于两个 32 位 RISC-V 处理器 构建的高性能主控芯片。该芯片具有 512KB SRAM4 MB Flash,为物联网控制场景提供了丰富的编程空间和强大的处理能力。无线协议栈支持以下技术:2.4 GHz WiFi 6,Bluetooth® 5.3,Zigbee,Thread (802.15.4)。


四、方案框图与设计思路

根据芯片手册,使用I2C读取传感器数据,通过SPI将显示数据发送到LCD显示。

在主控ESP32-C6中,使用ESP-IDF+Freertos进行开发,保证多任务的实时执行。

额外的,主控还进行传感器数据读取与滤波、溶氧量计算和LVGL显示。

image.png


五、电路设计

  1. 原理图

项目使用ESP32-C6作为主控,通过I2C接口连接两个传感器,其中I2C需要使用外部电阻进行上拉保证信号读取稳定性。使用SPI接口连接LCD屏幕,控制LCD进行显示,额外的,通过PWM+NMOS控制LCD的背光亮度。

image.png

  1. PCB布局

PCB设计上,根据圆形LCD屏幕的尺寸,将PCB外框设置为圆形。

将两个传感器放置在边缘,尤其是温度传感器。

其中温度传感器附近不进行铺铜,减少因其他元件导热影响温度测量。

image.png

image.png

  1. PCB实物

由于两个传感器芯片很小,且引脚没有独立引出,因此最好通过热风枪或加热板进行焊接。

焊接完成贴片电阻之后,焊接排针排母,测量是否存在短路等情况。

最后插上ESP32C6模块即可。

image.png


六、软件设计与关键代码

  1. 软件设计流程

程序首先进行初始化,包括LCD、LVGL、I2C、NST461的初始化,并创建多个任务。

NST461任务:周期读取本地和远端温度数据,由于跳变严重,对数据进行了均值滤波;随后通过LVGL显示数据,并通过消息队列发布数据;

NSPAD1N任务:周期读取气压数据,并通过LVGL显示;同样通过消息队列发布数据;

溶氧量计算任务:通过消息队列等待温度和气压数据,通过插值计算得到溶氧量并显示;同时对于异常数据,进行警报。

显示仪.png

  1. 关键代码
    1. NST461设置远端温度偏移值:由于远端温度使用三极管,不同的三极管具有不同的特性,需要根据实际情况设置偏移值。计算得到后,写入对应地址即可,如下:
esp_err_t nst461_set_offset_factor()
{
    // 根据测量结果,选取的三极管温差为+13.0℃,需要减去这么多
    // 因为是减去,需要使用~进行取反
    // 比例系统选取1不变
    // -13.0 = -13 + (0)*0.0625
    data_wr[0] = ~0x0D;
    data_wr[1] = ~0x00;
    i2c_bus_write_bytes(handler, NST461_OFFSET_H_ADDR, 1, data_wr);
    i2c_bus_write_bytes(handler, NST461_OFFSET_L_ADDR, 1, data_wr + 1);

    return ESP_OK;
}

b. NST461温度读取并滤波:读取到本地和远端数据后,进行滤波防止温度跳变严重。

esp_err_t nst461_get_senser_dat(float *lt, float *rt)
{
    // 读取本地温度
    uint8_t ltemp[2];
    if (i2c_bus_read_bytes(handler, 0x00, 1, ltemp) != ESP_OK)
        return ESP_FAIL;
    if (i2c_bus_read_bytes(handler, 0x15, 1, ltemp + 1) != ESP_OK)
        return ESP_FAIL;
    float local_temp = (float)ltemp[0] + (float)(ltemp[1] >> 4) * 0.0625;

    // 读取远程温度
    uint8_t rtemp[2];
    if (i2c_bus_read_bytes(handler, 0x01, 1, rtemp) != ESP_OK)
        return ESP_FAIL;
    if (i2c_bus_read_bytes(handler, 0x10, 1, rtemp + 1) != ESP_OK)
        return ESP_FAIL;
    float remote_temp = (float)rtemp[0] + (float)(rtemp[1] >> 4) * 0.0625;

    // 应用移动平均滤波器
    local_temp = moving_average_filter(local_temp, &local_temp_filter);
    remote_temp = moving_average_filter(remote_temp, &remote_temp_filter);

    // 记录结果
    ESP_LOGI(NST461_TAG, "Local Temp: %.2f C, Remote Temp: %.2f C", local_temp, remote_temp);

    // 通过指针返回结果
    *lt = local_temp;
    *rt = remote_temp;

    return ESP_OK; // 成功时返回ESP_OK
}

c. NSPAD1N读取气压数据:读取到原始数据后,需要通过计算得到准确的气压值。

/*——— 读取压力(kPa)———*/
esp_err_t nspad1n_get_senser_dat(float *pressure)
{
    if (pressure == NULL) {
        ESP_LOGE(NSPAD1N_TAG, "pressure out ptr is null");
        return ESP_ERR_INVALID_ARG;
    }
    if (handler == NULL) {
        ESP_LOGE(NSPAD1N_TAG, "I2C handler not initialized");
        return ESP_FAIL;
    }

    /* 触发一次测量(如果芯片需要) */
    data_wr[0] = 0x0A;  // 具体启动命令以手册为准
    esp_err_t err = i2c_bus_write_bytes(handler, NSPAD1N_REG_CMD, 1, data_wr);
    if (err != ESP_OK) {
        ESP_LOGE(NSPAD1N_TAG, "write CMD failed: %d", err);
        return err;
    }

    /* 轮询等待就绪 */
    uint8_t retries = NSPAD1N_POLL_RETRIES;
    while (retries--) {
        err = i2c_bus_read_bytes(handler, NSPAD1N_REG_CMD, 1, data_wr);
        if (err != ESP_OK) {
            ESP_LOGE(NSPAD1N_TAG, "read STATUS failed: %d", err);
            return err;
        }
        if ((data_wr[0] & NSPAD1N_STATUS_READY) == NSPAD1N_STATUS_READY) {
            break;
        }
        vTaskDelay(pdMS_TO_TICKS(NSPAD1N_POLL_DELAY_MS));
    }
    if (retries == 0) {
        ESP_LOGE(NSPAD1N_TAG, "read timeout");
        return ESP_ERR_TIMEOUT;
    }

    /* 读取 24bit 原始压力数据 */
    uint8_t raw[3];
    err = i2c_bus_read_bytes(handler, NSPAD1N_REG_PRESS_MSB, 3, raw);
    if (err != ESP_OK) {
        ESP_LOGE(NSPAD1N_TAG, "read pressure failed: %d", err);
        return err;
    }

    /* 组装为 24 位并做符号扩展到 int32 */
    uint32_t u24 = ((uint32_t)raw[0] << 16) | ((uint32_t)raw[1] << 8) | (uint32_t)raw[2];
    int32_t  s24 = (u24 & 0x00800000U) ? (int32_t)(u24 | 0xFF000000U) : (int32_t)u24;

    /* 标定换算(按手册/实测调整 A、B) */
    const float A = 231.250021f;
    const float B =  -8.125010f;
    const float FS = 8388607.0f;   // 2^23 - 1
    *pressure = A * ((float)s24 / FS) + B;

    ESP_LOGI(NSPAD1N_TAG, "P: %.1f kPa", *pressure);
    return ESP_OK;
}

d. 溶氧量计算:等待温度和气压数据后,通过插值的方式计算得到理论的氧溶解度。

// 任务:计算溶氧量
void oxygen_task(void *arg)
{
    static char sdat[50];
    // 标准大气压101.325 kPa下的氧溶解度,温度0-40°,
    const float Rou_O2[41] = {
        14.62,
        14.22, 13.83, 13.46, 13.11, 12.77, 12.45, 12.14, 11.84, 11.56, 11.29,
        11.03, 10.78, 10.54, 10.31, 10.08, 9.87, 9.66, 9.47, 9.28, 9.09,
        8.91, 8.73, 8.56, 8.42, 8.26, 8.11, 7.97, 7.83, 7.69, 7.56,
        7.43, 7.30, 7.18, 7.07, 6.95, 6.84, 6.73, 6.63, 6.53, 6.43
    };
    // 饱和水蒸汽压力,温度0-40°
    const float p_w[41] = {
        0.61,
        0.66, 0.71, 0.76, 0.81, 0.87, 0.93, 1.00, 1.07, 1.15, 1.23,
        1.31, 1.40, 1.49, 1.60, 1.71, 1.81, 1.93, 2.07, 2.20, 2.81,
        2.99, 3.17, 3.36, 3.56, 3.77, 4.00, 4.24, 4.49, 4.76, 5.02,
        5.32, 5.62, 5.94, 6.28, 6.62, 6.98, 2.81, 2.99, 3.17, 7.37
    };
    // 计算公式,不同温度和气压下的氧溶解度
    // Rou = Rou_O2[t] * (p - p_w[t]) / (101.325 - p_w[t])
    // 其中t是温度,p是当前压强
    float t = 20;  // 设置目标温度
    float p = 101.325;  // 设置当前压力
    while(1)
    {
        xQueueReceive(pressure_queue, &p, portMAX_DELAY);
        xQueueReceive(temp_queue, &t, portMAX_DELAY);

        // 确保温度在0到40度之间
        if(t < 0) t = 0;
        if(t > 39) t = 39;
        // 获取温度下的低点和高点
        int t_floor = (int)t;  // 向下取整的温度值
        int t_ceil = t_floor + 1;  // 向上取整的温度值

        // 进行线性插值计算氧溶解度
        float Rou_O2_interp = Rou_O2[t_floor] + (t - t_floor) * (Rou_O2[t_ceil] - Rou_O2[t_floor]);
        // 进行线性插值计算水蒸气压力
        float p_w_interp = p_w[t_floor] + (t - t_floor) * (p_w[t_ceil] - p_w[t_floor]);
        // 计算最终的氧溶解度(氧气浓度)
        float Rou = Rou_O2_interp * (p - p_w_interp) / (101.325 - p_w_interp);
        // 显示在屏幕上
        snprintf(sdat, 50, "%.1f mg/L", Rou);
        lv_label_set_text(ui_Lo2, sdat);
        lv_slider_set_value(ui_So2, (int16_t)(Rou*10), LV_ANIM_ON);
    }
}


七、波形展示——使用带屏12指神探

  1. 12指神探接线示意图

PCB中引出了电源引脚、I2C的时钟线和数据线引脚。如图,连接带屏12指神探到对应的引脚上,连接关系如下:

12指神探 <---> PCB

GND <---> GND

5V0 <---> 5V

D2-CH0 <---> SDA

D3-CH1 <---> SCL

image.png

  1. I2C通信波形

通过硬禾带屏12指神探,连接I2C引脚,使用PulseView软件,测量读取两款传感器过程中的引脚数据,对比波形与数据。

    1. NST461的从机地址为0x4C,以读取0x00地址数据为例:发送从机地址(0x4C<<1 + 0)-> 等待ACK成功 -> 并写入0x00寄存器地址;发送从机地址(0x4C<<1 + 1) -> 等待ACK成功 -> 读取到从机返回的数据。具体如下:

f5b0751ea82151bec89d678c3a6dc054.png

图:读取从机地址0x4C,寄存器地址0x00的1Byte数据

b. 以同样的方式,分别读取寄存器地址0x150x010x10的数据,波形如下:

617cc920495515388cfb742de82240ed.png

图:读取从机地址0x4C,寄存器地址0x15的1Byte数据

21fc89d90404531bb416093c27d2a840.png

图:读取从机地址0x4C,寄存器地址0x01的1Byte数据

27fefca19a7718a649c10b5d187c6fcb.png

图:读取从机地址0x4C,寄存器地址0x10的1Byte数据

c. 对于NSPAD1N,从机地址为0x7F。为了读取大气压数据,首先需要触发测量。先写入地址NSPAD1N_REG_CMD 0x30数据0x0A,触发一次测量;然后轮询等待就绪,期间读取同个地址,并检查就绪位NSPAD1N_STATUS_READY 0x02是否为1。波形数据如下:

b023166ab4f80accc556b6210ead730f.png

图:写入从机地址0x7F,寄存器地址0x30的1Byte数据0x0A

586716bf0fc4f97687742f1d2d432da9.png

图:读取从机地址0x7F,寄存器地址0x30的1Byte数据

d. 待上述有数据后,连续读取地址NSPAD1N_REG_PRESS_MSB  0x06的3个数据,后通过换算即可得到正确的数据。波形如下:

a93c1cb2f2d0c574ac06d581427ff0e8.png

图:读取从机地址0x7F,寄存器地址0x06的连续3Bytes数据

  1. 传感器数据波形显示

通过串口工具,将本地温度、远端温度、大气压强和理论氧溶解量进行显示。现象如下:

a. 测量过程中,将远端三极管放在了热水中,可以看到远端温度t迅速上升到60;拿出来后温度下降。

b. 气压保持在101.7kPa;

c. 本地温度保持在29℃左右;

d. 温度上升将导致水体溶氧量下降,温度下降将导致水体溶氧量上升。

image.png


八、实物演示

  1. 零件组成:① 远端测温头(连接一个三极管SS8050,并通过热缩管密封防水);② 主控SEEED XIAO ESP32-C6和制作的PCB底板(上面有两个传感器和一个圆形LCD屏幕);③ 3D打印的外壳。

9e8cead09e63f86300fcece47e082e1f.jpg

  1. 组装完成后,将远端测温头放置在空气中进行测量:本地温度30.5℃(因LCD热量传导导致温度较高),远端温度28.8℃,大气压强101.7kPa,计算的溶氧量7.8mg/L。

9f41ea8d71f931a9c4eeeb524db39648.jpg

  1. 将远端测温头放置在热水中测量:此时远端温度升高至52.8℃,并且溶氧量降低至6.6mg/L。

eb333778fffe44fe345e61b1d794120b.jpg


九、难点与解决

  1. 难点:利用温度传感器 NST461-DQNR测量远端温度时,虽然有显示但是温度差异巨大。

解决:查看手册,其中有offset地址和factor地址,用于调整PN结特性的偏移量和变化比例,通过测量所选三极管的输出,得到准确的数值后写入,此时温度正常显示。

  1. 难点:使用LVGL,显示颜色总是无法调整到与预期的一致。

解决:对于LCD显示,需要确定两个参数:颜色顺序(RGB/BGR等)和字节顺序(是否需要高低字节交换),分别在LCD驱动和LVGL配置文件中进行设置,即可正常显示。


十、总结

  1. 通过本次活动,了解到了温度测量方法,即利用PN结随温度的特性进行测量,是一个从理论到实际应用的典型;
  2. 本次尝试了ESP-IDF和LVGL,边学边开发,很有收获;
  3. 此次项目不仅是简单的读取温湿度,更是针对实际需求如室内淡水养鱼,输出显示溶氧量,具备一定的实际用途。
  4. 最后感谢主办方和芯片提供商!


附件下载
Temperature-pressure-display.kicad_sch
原理图
Temperature-pressure-display.kicad_pcb
PCB
ESP32C6-GC9A01-Displayer.zip
完整代码—使用VSCode+ESP-IDF
3D外壳-温度气压显示仪.zip
3D外壳文件
团队介绍
评论
0 / 100
查看更多
硬禾服务号
关注最新动态
0512-67862536
info@eetree.cn
江苏省苏州市苏州工业园区新平街388号腾飞创新园A2幢815室
苏州硬禾信息科技有限公司
Copyright © 2024 苏州硬禾信息科技有限公司 All Rights Reserved 苏ICP备19040198号