IoT爱好者,新手养鱼必备!基于ESP32手搓智能鱼缸:水体温度、大气压强、溶氧量,动态加热、降温或加氧一应俱全

admin 2026-01-07 02:45:59 网络安全文章 来源:ZONE.CI 全球网 0 阅读模式

文章总结: 本文介绍基于ESP32-C6的智能鱼缸监测仪制作过程,集成高精度温度与气压传感器及圆形LCD屏,实现水温、气压及溶氧量实时显示。项目采用ESP-IDF与FreeRTOS开发,通过I2C与SPI接口通信,利用均值滤波与插值算法计算溶氧量,并展示了PCB设计与核心代码实现,为IoT爱好者提供技术参考。 综合评分: 83 文章分类: 解决方案,其他


cover_image

IoT爱好者,新手养鱼必备!基于ESP32手搓智能鱼缸:水体温度、大气压强、溶氧量,动态加热、降温或加氧一应俱全

原创

.

IoT物联网技术

2026年1月6日 08:22 北京

文末联系小编,获取项目源码

趁着元旦大促,作为长期空军的钓鱼佬,小编终于入手了一套鱼缸、10条斑马鱼,成功转型新手养鱼人,日常生活的格调一下子就起来了。

同时,作为IoT物联网爱好者,小编利用三天假期,基于乐鑫ESP32手搓了一台温度-气压-水体溶氧量显示仪,可以实时掌握水体温度、大气压强、水体溶氧量,为养鱼提供参考,自动实现水体加热、降温或加氧。未来智能鱼缸项目还会实现自动换水、氛围灯和自动喂食等功能。

  • 主控SEEED XIAO ESP32-C6:一款基于两个 32 位 RISC-V 处理器构建的高性能主控芯片。该芯片具有 512KB SRAM 和 4 MB Flash,为物联网控制场景提供了丰富的编程空间和强大的处理能力。

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

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

  • 圆形LCD 屏幕 GC9A01:提供高分辨率240×240 IPS显示效果,是需要圆形显示器应用的理想选择。该模块专为高要求的工作环境设计,具有卓越的性能、持久的耐用性,并且可以轻松集成到各种系统中。

温度-气压-水体溶氧量显示仪基于ESP32-C6作为主控,采用ESP-IDF+Freertos进行开发,保证多任务的实时执行通过I2C接口连接两个传感器,其中I2C需要使用外部电阻进行上拉保证信号读取稳定性,主控还进行传感器数据读取与滤波、溶氧量计算,然后通过SPI接口连接LCD屏幕,控制LCD进行显示。

PCB设计上,根据圆形LCD屏幕的尺寸,将PCB外框设置为圆形。将两个传感器放置在边缘,尤其是温度传感器。其中温度传感器附近不进行铺铜,减少因其他元件导热影响温度测量。

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

  • NST461任务:周期读取本地和远端温度数据,由于跳变严重,对数据进行了均值滤波;随后通过LVGL显示数据,并通过消息队列发布数据;
  • NSPAD1N任务:周期读取气压数据,并通过LVGL显示;同样通过消息队列发布数据;
  • 溶氧量计算任务:通过消息队列等待温度和气压数据,通过插值计算得到溶氧量并显示;同时对于异常数据,进行警报。

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;}

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}

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;    }
&nbsp; &nbsp;&nbsp;/* 组装为 24 位并做符号扩展到 int32 */&nbsp; &nbsp; uint32_t u24 = ((uint32_t)raw[0] <<&nbsp;16) | ((uint32_t)raw[1] <<&nbsp;8) | (uint32_t)raw[2];&nbsp; &nbsp; int32_t &nbsp;s24 = (u24 &&nbsp;0x00800000U) ? (int32_t)(u24 |&nbsp;0xFF000000U) : (int32_t)u24;
&nbsp; &nbsp;&nbsp;/* 标定换算(按手册/实测调整 A、B) */&nbsp; &nbsp;&nbsp;const&nbsp;float&nbsp;A =&nbsp;231.250021f;&nbsp; &nbsp;&nbsp;const&nbsp;float&nbsp;B = &nbsp;-8.125010f;&nbsp; &nbsp;&nbsp;const&nbsp;float&nbsp;FS =&nbsp;8388607.0f; &nbsp;&nbsp;// 2^23 - 1&nbsp; &nbsp; *pressure = A * ((float)s24 / FS) + B;
&nbsp; &nbsp; ESP_LOGI(NSPAD1N_TAG,&nbsp;"P: %.1f kPa", *pressure);&nbsp; &nbsp;&nbsp;return&nbsp;ESP_OK;}

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

// 任务:计算溶氧量void&nbsp;oxygen_task(void&nbsp;*arg){&nbsp; &nbsp;&nbsp;static&nbsp;char&nbsp;sdat[50];&nbsp; &nbsp;&nbsp;// 标准大气压101.325 kPa下的氧溶解度,温度0-40°,&nbsp; &nbsp;&nbsp;const&nbsp;float&nbsp;Rou_O2[41] = {&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;14.62,&nbsp;&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;14.22,&nbsp;13.83,&nbsp;13.46,&nbsp;13.11,&nbsp;12.77,&nbsp;12.45,&nbsp;12.14,&nbsp;11.84,&nbsp;11.56,&nbsp;11.29,&nbsp;&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;11.03,&nbsp;10.78,&nbsp;10.54,&nbsp;10.31,&nbsp;10.08,&nbsp;9.87,&nbsp;9.66,&nbsp;9.47,&nbsp;9.28,&nbsp;9.09,&nbsp;&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;8.91,&nbsp;8.73,&nbsp;8.56,&nbsp;8.42,&nbsp;8.26,&nbsp;8.11,&nbsp;7.97,&nbsp;7.83,&nbsp;7.69,&nbsp;7.56,&nbsp;&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;7.43,&nbsp;7.30,&nbsp;7.18,&nbsp;7.07,&nbsp;6.95,&nbsp;6.84,&nbsp;6.73,&nbsp;6.63,&nbsp;6.53,&nbsp;6.43&nbsp; &nbsp; };&nbsp; &nbsp;&nbsp;// 饱和水蒸汽压力,温度0-40°&nbsp; &nbsp;&nbsp;const&nbsp;float&nbsp;p_w[41] = {&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;0.61,&nbsp;&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;0.66,&nbsp;0.71,&nbsp;0.76,&nbsp;0.81,&nbsp;0.87,&nbsp;0.93,&nbsp;1.00,&nbsp;1.07,&nbsp;1.15,&nbsp;1.23,&nbsp;&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;1.31,&nbsp;1.40,&nbsp;1.49,&nbsp;1.60,&nbsp;1.71,&nbsp;1.81,&nbsp;1.93,&nbsp;2.07,&nbsp;2.20,&nbsp;2.81,&nbsp;&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;2.99,&nbsp;3.17,&nbsp;3.36,&nbsp;3.56,&nbsp;3.77,&nbsp;4.00,&nbsp;4.24,&nbsp;4.49,&nbsp;4.76,&nbsp;5.02,&nbsp;&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;5.32,&nbsp;5.62,&nbsp;5.94,&nbsp;6.28,&nbsp;6.62,&nbsp;6.98,&nbsp;2.81,&nbsp;2.99,&nbsp;3.17,&nbsp;7.37&nbsp; &nbsp; };&nbsp; &nbsp;&nbsp;// 计算公式,不同温度和气压下的氧溶解度&nbsp; &nbsp;&nbsp;// Rou = Rou_O2[t] * (p - p_w[t]) / (101.325 - p_w[t])&nbsp; &nbsp;&nbsp;// 其中t是温度,p是当前压强&nbsp; &nbsp;&nbsp;float&nbsp;t =&nbsp;20; &nbsp;// 设置目标温度&nbsp; &nbsp;&nbsp;float&nbsp;p =&nbsp;101.325; &nbsp;// 设置当前压力&nbsp; &nbsp;&nbsp;while(1)&nbsp;&nbsp; &nbsp; {&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;xQueueReceive(pressure_queue, &p, portMAX_DELAY);&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;xQueueReceive(temp_queue, &t, portMAX_DELAY);
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;// 确保温度在0到40度之间&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;if(t <&nbsp;0) t =&nbsp;0;&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;if(t >&nbsp;39) t =&nbsp;39;&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;// 获取温度下的低点和高点&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;int&nbsp;t_floor = (int)t; &nbsp;// 向下取整的温度值&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;int&nbsp;t_ceil = t_floor +&nbsp;1; &nbsp;// 向上取整的温度值
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;// 进行线性插值计算氧溶解度&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;float&nbsp;Rou_O2_interp = Rou_O2[t_floor] + (t - t_floor) * (Rou_O2[t_ceil] - Rou_O2[t_floor]);&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;// 进行线性插值计算水蒸气压力&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;float&nbsp;p_w_interp = p_w[t_floor] + (t - t_floor) * (p_w[t_ceil] - p_w[t_floor]);&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;// 计算最终的氧溶解度(氧气浓度)&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;float&nbsp;Rou = Rou_O2_interp * (p - p_w_interp) / (101.325&nbsp;- p_w_interp);&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;// 显示在屏幕上&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;snprintf(sdat,&nbsp;50,&nbsp;"%.1f mg/L", Rou);&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;lv_label_set_text(ui_Lo2, sdat);&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;lv_slider_set_value(ui_So2, (int16_t)(Rou*10), LV_ANIM_ON);&nbsp; &nbsp; }}

3D打印的外壳组装实物效果

原文地址:

https://www.eetree.cn/project/detail/4691


如有IoT 源码采购和项目交付需求,请扫码联系小编,微信号: beacon0418

往期推荐

☞开箱即用!国产开源30+AI视觉算法IoT智能物联网云平台

☞国产开源Web 工业IoT组态软件,支持Modbus、OPC,支持拖拉拽

☞源码交付,7天完成国产信创部署智慧工地方案

☞4万元,国产信创私有化部署,破解县域无人机AI巡检平台落地难题

☞上班摸鱼, 智能AI 监控老板行踪

免责声明:本公众号所发布的内容来源于互联网,我们会尊重并维护原作者的权益。由于信息来源众多,若文章内容出现版权问题,或文中使用的图片、资料、下载链接等,如涉及侵权,请告知我们,我们将尽快处理。


免责声明:

本文所载程序、技术方法仅面向合法合规的安全研究与教学场景,旨在提升网络安全防护能力,具有明确的技术研究属性。

任何单位或个人未经授权,将本文内容用于攻击、破坏等非法用途的,由此引发的全部法律责任、民事赔偿及连带责任,均由行为人独立承担,本站不承担任何连带责任。

本站内容均为技术交流与知识分享目的发布,若存在版权侵权或其他异议,请通过邮件联系处理,具体联系方式可点击页面上方的联系我

本文转载自:IoT物联网技术 .《IoT爱好者,新手养鱼必备!基于ESP32手搓智能鱼缸:水体温度、大气压强、溶氧量,动态加热、降温或加氧一应俱全》

评论:0   参与:  0