本节我们在迭代一的基础上增加采集土壤湿度数据,并根据湿度数据来决定是否自动进行浇水动作。
针对本节的目标,分析如下:
1. 需要获取土壤湿度数据,土壤湿度是模拟数据,每3分钟采集一次数据;
2. 判断湿度数据是否小于预定阀值,如果小于阀值就进行浇水;
3. 为避免数据采集问题导致误动作, 使用对最新5次采集数据进行综合判断;
本开发套件中的土壤湿度传感器是通过测量土壤的导电性来判断水分含量的,传感器读数是模拟值,导电性越大,表示越湿润,传感器读数越小;导电性越小,表示越干燥,传感器读数越大。
在Arduino-ESP32中使用analogRead来读取模拟数值,默认精度(分辨率)是12位,即数值范围是0到4095。
在日常生活中,湿度一般用0-100来表示,对于土壤湿度传感数值,可以使用map函数将该值映射到1到100的区间内。
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分钟
}
在本节的需求分析中,为避免单次的数据导致误动作,我们设计对最新5次采集数据进行综合判断来决定是否浇水。
当我们要存储连续采集的传感器数据用于后续处理时,可以使用数组来存储,但数组在动态调整大小和删除元素等操作时比较繁琐,另一个选择是使用列表(ArrayList)数据结构,通过使用ArrayList类,可以方便的只保存最新5条数据。
Arduino并未提供ArrayList类,这里我们使用第三方提供的ArrayList类库,安装步骤如下:
1.点击菜单”工具”->”管理库...”;
2.输入”ArrayList”进行筛选;
3.找到”ArrayList by Brayden Anderson”进行安装,这里安装的是1.0.2版本;
#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来实现。
FreeRTOS是一个用于嵌入式的微型实时操作系统内核,功能包括:任务管理、时间管理、信号量、消息队列等,可满足较小系统的多任务运行需求。要了解更多关于FreeRTOS的内容,可查阅FreeRTOS手册。
Arduino-ESP32开发包集成了FreeRTOS内核,通过使用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);
}
掌握了所需的知识点后,我们来实现迭代二的目标。
迭代二的程序流程图如下:
通过流程图可以看到按键任务和土壤湿度数据采集任务是各自独立的,适合通过多任务程序的方式来实现。
#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);
}
程序合并了迭代一实现和本节知识点的代码,没有特别的地方,就不详细解释了。
图灵测试的方法是:被测试人,和一个待测试的机器。测试时,测试人与被测试人是分开的,测试人只有以纯文本的方式向被测试人问一些问题,这些问题随便是什么问题都可以。问过一些问题后,如果测试人能够正确地分出谁是人谁是机器,那机器就没有通过图灵测试,如果测试人没有分出谁是机器谁是人,那这个机器就是有智能的。
本节我们实现一个基本能工作的手动浇水装置,即通过按下按键来闭合继发器让小水泵进行浇水。
本小节通过点亮LED和串口输出两个程序,来初步掌握ArduinoIDE、了解GPIO和串口使用、同时把开发环境与开发板的连接,上传程序的各环节跑通,
本程序是小鹏物联网智能浇花套件的单机版程序(不连接物联网),供同学们参考。
本文介绍ESP32中的中断机制,以及如何通过GPIO中断实现按钮控制。重点讲解了如何设置中断服务例程、处理中断抖动问题,并提供了消除中断抖动的示例代码。
本文主要介绍在未联网(AP热点)情况下实现WEB交互界面的CSS和javascript库。
本文介绍如何使用Arduino-ESP32库中的API函数获取ESP32的芯片、RAM信息等,并提供了一个示例程序代码。
ESP32系列(包括ESP32-S3)搭载Xtensa双核处理器,默认情况下Arduino框架仅使用单核运行用户代码,通过多核编程,可以充分利用硬件资源来提升系统响应和性能。
ESP32 芯片有34个物理GPIO管脚。每个GPIO管脚都可用作一个通用IO,或连接一个内部的外设信号。IO_MUX ¹、RTC IO MUX 和GPIO交换矩阵用于将信号从外设传输至GPIO管脚。
ESP32Encoder库是一个利用ESP32脉冲计数器硬件外设实现高效旋转编码器读取的软件库。
本文对比了几款适合物联网开发的盒子硬件参数,供大家参考。
专属教研服务系统,助您构建STEM课程体系,打造一站式教学环境。
为讲师、学员提供专属的物联网教学环境。