物联网项目开发实战-第3章-自动浇花项目迭代2

本节我们在迭代一的基础上增加采集土壤湿度数据,并根据湿度数据来决定是否自动进行浇水动作。

3.3迭代二:基于土壤湿度自动浇水

3.3.1迭代目标

本节我们在迭代一的基础上增加采集土壤湿度数据,并根据湿度数据来决定是否自动进行浇水动作。

3.3.2需求分析

针对本节的目标,分析如下:

1. 需要获取土壤湿度数据,土壤湿度是模拟数据,每3分钟采集一次数据;

2. 判断湿度数据是否小于预定阀值,如果小于阀值就进行浇水;

3. 为避免数据采集问题导致误动作, 使用对最新5次采集数据进行综合判断;

3.3.3知识点

知识点3-4:通过GPIO读取土壤湿度传感器数据

本开发套件中的土壤湿度传感器是通过测量土壤的导电性来判断水分含量的,传感器读数是模拟值,导电性越大,表示越湿润,传感器读数越小;导电性越小,表示越干燥,传感器读数越大。

物联网项目开发实战-第3章-自动浇花项目迭代2

在Arduino-ESP32中使用analogRead来读取模拟数值,默认精度(分辨率)是12位,即数值范围是0到4095。

在日常生活中,湿度一般用0-100来表示,对于土壤湿度传感数值,可以使用map函数将该值映射到1到100的区间内。

实例6:获取土壤湿度传感器数据

const int SOIL_MOISTURE_PIN = 35;

int collectData() {
  int originVal = analogRead(SOIL_MOISTURE_PIN);
  int sensorVal = 0;
  if (originVal > 0) {
    // 将值映射到1-100
    sensorVal = map(originVal, 1, 4095, 100, 1);
  }
  return sensorVal; 
}
 
void setup() {
  Serial.begin(115200);
  pinMode(SOIL_MOISTURE_PIN, INPUT);
}

void loop() {
  int val = collectData();
  Serial.printf("土壤湿度:%d\n", val);

  delay(180000); //3分钟
}

知识点3-5:使用高级数据结构:ArrayList

在本节的需求分析中,为避免单次的数据导致误动作,我们设计对最新5次采集数据进行综合判断来决定是否浇水。

当我们要存储连续采集的传感器数据用于后续处理时,可以使用数组来存储,但数组在动态调整大小和删除元素等操作时比较繁琐,另一个选择是使用列表(ArrayList)数据结构,通过使用ArrayList类,可以方便的只保存最新5条数据。

Arduino并未提供ArrayList类,这里我们使用第三方提供的ArrayList类库,安装步骤如下:

1.点击菜单”工具”->”管理库...”;

2.输入”ArrayList”进行筛选;

物联网项目开发实战-第3章-自动浇花项目迭代2

3.找到”ArrayList by Brayden Anderson”进行安装,这里安装的是1.0.2版本;

实例7:使用高级数据结构ArrayList

#include <ArrayList.h>

ArrayList<int> valList;

// 列表数值比较
int compareAll(int cmpValue) {
  for (int i=0; i<valList.size(); i++) {
    if (valList.get(i) > cmpValue) {
      // 只要有一个值高于阀值,即判定不满足。
      return 0;
    }
  }
  return 1;
}

void setup() {
  Serial.begin(115200);

  valList.add(35);
  valList.add(38);
  valList.add(41);
  valList.add(45);
  valList.add(48);

  if (compareAll(50)) {
    Serial.println("全部数值都小于阀值。");
  }

  if (compareAll(45)) {
    Serial.println("有数值大于阀值");
  }
}

void loop() {

  delay(100);
}

到目前为此,我们可以在迭代一代码上实现迭代二了,合并后的loop()代码将会如下:

void loop() {
  if (buttonClicked()) {
    // 浇水5秒
    watering(5);
  }

  int val = collectData();
  Serial.printf("土壤湿度:%d\n", val);
  // 此处是列表数值比较处理

  delay(180000); //3分钟
  //delay(10);
}

那么问题就来了,当delay(180000)执行时,此时按键状态是检测不到的!

导致此问题的原因是因为按键检测和数据采集在loop内是顺序执行(串行)的,要解决这个问题,需要把这两个操作做并行执行的处理。

顺序执行就好比两辆汽车在一个车道上只能一前一后跑,并行执行就是设计出多个车道,让车在车道上各跑各的,互不影响。

要实现多任务并行,可以使用Arduino-ESP32内置的FreeRTOS来实现。

知识点3-6:使用FreeRTOS实现多任务并行

FreeRTOS是一个用于嵌入式的微型实时操作系统内核,功能包括:任务管理、时间管理、信号量、消息队列等,可满足较小系统的多任务运行需求。要了解更多关于FreeRTOS的内容,可查阅FreeRTOS手册。

Arduino-ESP32开发包集成了FreeRTOS内核,通过使用FreeRTOS的任务机制,可以方便的开发多任务(多线程)的应用。

实例8:使用FreeRTOS实现多任务运行

void buttonTask(void *pvParam) {
  while(1) {
    Serial.println("按键状态检测");
    vTaskDelay(10 / portTICK_PERIOD_MS);
  }
}

void collectTask(void *pvParam) {
  while(1) {
    Serial.println("按键状态检测");
    vTaskDelay(180000 / portTICK_PERIOD_MS); // 180秒
  }
}

void setup() {
  Serial.begin(115200);

  // 创建按键检测任务
  xTaskCreate(
    buttonTask,    // 任务函数
    "Button_Task",   // 任务名称
    10000,          // 堆栈大小(字节)
    NULL,           // 参数
    1,              // 优先级
    NULL            // 任务句柄
  );

  // 创建数据采集任务
  xTaskCreate( collectTask, "Collect_Task", 10000, NULL, 1, NULL );
}

void loop() {
  // ...
  vTaskDelay(10 / portTICK_PERIOD_MS);
} 

3.3.4实现

掌握了所需的知识点后,我们来实现迭代二的目标。

1.流程图

迭代二的程序流程图如下:
物联网项目开发实战-第3章-自动浇花项目迭代2

通过流程图可以看到按键任务和土壤湿度数据采集任务是各自独立的,适合通过多任务程序的方式来实现。

2.电路搭建

物联网项目开发实战-第3章-自动浇花项目迭代2

3.程序开发

#include <ArrayList.h>

const int RELAY_PIN = 12;
const int BUTTON_PIN = 13;
const int SOIL_MOISTURE_PIN = 35;
boolean button_pressed = 0;

ArrayList<int> valList;

// 浇水子程序
void watering(int seconds) {
  digitalWrite(RELAY_PIN, HIGH);
  delay(seconds * 1000);
  digitalWrite(RELAY_PIN, LOW);
  Serial.printf("浇水 %d秒。\n", seconds);
}

boolean buttonClicked() {
  // 检测按钮是否已按下
  if (digitalRead(BUTTON_PIN) == LOW) { // 已按下
    delay(10); // 延时10ms,用于防止机械抖动
    if (digitalRead(BUTTON_PIN)==LOW) { // 再次读取
      button_pressed = 1;
      Serial.print("按键已按下。");
    } 
  }

  // 检测按键是否已释放
  if (button_pressed==1 && digitalRead(BUTTON_PIN) == HIGH) { // 已释放
    delay(10); // 延时10ms,用于防止机械抖动
    if (button_pressed==1 && digitalRead(BUTTON_PIN) == HIGH) {
      button_pressed = 0;
      Serial.print("按键已释放。");
      Serial.print("按键单击。");
      return true;
    }
  }

  return false;
}

// 数据采集子程序
int collectData() {
  int originVal = analogRead(SOIL_MOISTURE_PIN);
  int sensorVal = 0;
  if (originVal > 0) {
    // 将值映射到1-100
    sensorVal = map(originVal, 1, 4095, 100, 1);
  }
  return sensorVal; 
}

// 列表数值比较
int compareAll(int cmpValue) {
  for (int i=0; i<valList.size(); i++) {
    if (valList.get(i) > cmpValue) {
      // 只要有一个值高于阀值,即判定不满足。
      return 0;
    }
  }
  return 1;
}

// 数据采集任务
void collectTask(void *pvParam) {
  while(1) {
    int val = collectData();
    Serial.printf("土壤湿度:%d\n", val);
    if (val>0) {
      valList.add(val);
      if (valList.size() > 5) {
        // 超过5条,移除最早的数据。
        valList.remove(0);
      }
      if (valList.size()==5 && compareAll(50)) {
        // 有5条数据,并且都小于阀值,则浇水
        watering(5);
      }
    }

    delay(180000); //3分钟
  }
}

// 按键任务
void buttonTask(void *pvParam) {
  while(1) {
    if (buttonClicked()) {
      // 浇水5秒
      watering(5);
    }

    vTaskDelay(10 / portTICK_PERIOD_MS);
  }
}

void setup() {
  Serial.begin(115200);
  pinMode(RELAY_PIN, OUTPUT);
  pinMode(BUTTON_PIN, INPUT_PULLUP); // 使用引脚内置上拉电阻
  pinMode(SOIL_MOISTURE_PIN, INPUT);

  // 创建按键检测任务
  xTaskCreate(
    buttonTask,    // 任务函数
    "Button_Task",   // 任务名称
    10000,          // 堆栈大小(字节)
    NULL,           // 参数
    1,              // 优先级
    NULL           // 任务句柄
  );

  // 创建数据采集任务
  xTaskCreate( collectTask, "Collect_Task", 10000, NULL, 1, NULL );
}

void loop() {
  // ...
  vTaskDelay(10 / portTICK_PERIOD_MS);
}

程序合并了迭代一实现和本节知识点的代码,没有特别的地方,就不详细解释了。

- 本文由用户 老张 发布,文中观点仅代表作者本人,不代表本站立场。
- 如需转载,请联系作者;如有侵权,请联系本站处理。

2025-09   阅读(215)   评论(0)
 标签: 种植 ESP32 FreeRTOS 物联网

涨知识
导轨

导轨又称滑轨、线性导轨、线性滑轨,用于直线往复运动场合,拥有比直线轴承更高的额定负载, 同时可以承担一定的扭矩,可在高负载的情况下实现高精度的直线运动。

评论:
相关文章
MimiClaw应用与开发教程1:部署和测试

MimiClaw‌ 是一款基于 ‌ESP32-S3‌ 芯片的超轻量级AI助手,适合嵌入式AI与物联网开发者快速部署本地化AI代理。本系列教程基于MimiClaw的Arduino移植版本进行讲解,小节主要讲解部署和测试。


ESP32扫描wifi 热点列表

就像我们用手机打开WiFi功能后可以浏览附近的可用WiFi。要将手机连接到热点,通常需要打开Wi-Fi设置应用程序,列出可用的网络,然后选择所需的热点。然后输入密码(或不输入密码),可以使用ESP32进行相同的操作。


MimiClaw 配置飞书机器人和添加硬件控制技能

本文本介绍配置飞书机器人为MimiClaw的一个输入/输出端,和添加一个控制WS2812与LED的控制技能。


ESP32-S3 部署 MimicLaw 完整教程:从零到成功调用 DeepSeek

一块 30 块钱的开发板 + 一个大模型 API,就能做出可以听懂人话的智能硬件。 本文记录完整安装过程和踩坑经验,确保你跟着做就能跑通。


MimiClaw 架构全解析,把 “智能龙虾” 跑在 ESP32 上

本文将从手绘架构图入手,逐层拆解 MimiClaw 的分层设计、核心模块、数据流转与底层实现,带你解剖这只“智能虾”的技术骨架,看懂在 C 语言加持下,AI 智能体如何以可穿戴设备的形态,在你身边稳稳运行、离线服务、主动响应。


如何用 platform.local.txt 深度定制 ESP32 编译流程?

本文介绍如何在不脱离 ArduinoIDE 可视化开发的前提下,通过一个名为 platform.local.txt 的小文件,实现对 ESP32 编译流程的精准控制。


优化Arduino-ESP32程序体积

本文将系统分析程序体积增长的五大根源,并提供经过验证的优化方案,帮助减小固件大小。


开发ESP32大模型AI语音助手-从软件到硬件

本文所DIY的语音助手设备端使用的是MicroPython、服务端是Python,对于很多开发者来说MicroPython入门没难度。


【ESP32 C++教程】Unit10-2:音频录制

本小节使用音频开发框架实现一个音频录制到文件的示例。


ESP32 I2S 接口深度解析:从时序、格式到 ESP-IDF 驱动实战

I2S协议通过BCLK、LRCLK和DATA三线精准传输音频数据,但时序边沿、帧格式、时钟源等细节常引发噪声或断连。本文详解ESP32的I2S实现,从协议原理到ESP-IDF v5.x代码配置,助你避开常见陷阱,确保音频稳定传输。