FastBond3挑战部分-基于ESP32C6的智慧农业灌溉系统
该项目使用了Arduino IDE,实现了ESP32-C6-DEVKITC-1-N8的设计,它的主要功能为:测量土壤和空气数据,控制灌溉以及上传数据到服务器。
标签
嵌入式系统
QingSpace
更新2024-11-01
76

1.前言

在上一篇Fastbond3基础部分的文章中(FastBond3基础部分-基于ESP32C6的智慧农业灌溉系统 (eetree.cn)),我介绍了项目的创意方向,提供了简单的流程图,并对主要硬件进行了介绍。接下来我将对项目进行更深入的介绍与说明。

2.方案框图和原理图

2.1流程图(方案框图)

2.2原理图与PCB

原理图和PCB非常简单,本质上就是把洞洞板变成PCB,减少焊点和飞线,让作品更加美观,无需过多介绍。

3.程序功能说明

本程序使用了FreeRTOS,下面将介绍各个任务的代码以及功能

3.1 传感器读取任务

void SensorDataAcquisition(void *arg)
{
 // 光线传感器
 Wire.begin(LP_I2C_SDA, LP_I2C_SCL);
 lightMeter.begin();

 // 土壤湿度传感器
 pinMode(SOIL_SENSOR, INPUT);

 // 温湿度传感器
 dht11.setup(DHT11_PIN, DHTesp::DHT11);

 while(1)
{
   lux = lightMeter.readLightLevel();
   soil = 100 * (soil_max - analogRead(SOIL_SENSOR)) / (soil_max - soil_min);
   TH = dht11.getTempAndHumidity();

#if UART
   Serial.print("Light: ");
   Serial.print(lux);
   Serial.println(" lx");

   Serial.print("Soil: ");
   Serial.print(soil);
   Serial.println(" %");

   Serial.print("Temperature: ");
   Serial.print(TH.temperature);
   Serial.print(" °C\tHumidity: ");
   Serial.print(int(TH.humidity));
   Serial.println(" %");
#endif

   if (Serial.available() >= 9)
  {
   // 读取九位十六进制数据
   byte data[9];
   for (int i = 0; i < 9; i++)
  {
     data[i] = Serial.read();
  }

   // 提取第七位(B7)和第八位(B8)
   byte B7 = data[6];
   byte B8 = data[7];

   // 计算二氧化碳浓度
   co2 = B7 * 256 + B8;
#if UART
   // 输出结果
   Serial.print("CO2 Concentration: ");
   Serial.print(co2);
   Serial.println(" PPM");
#endif
  }

   sprintf(BT, "%4.1f %2.0f %4.0f %2.0f %4.0f\n", TH.temperature, TH.humidity, lux, soil, co2);

#if UART
   Serial.println();
   Serial.println(BT);
#endif

   delay(100);
}
}

任务1主要负责传感器数据的获取传感器的数据,其中光线传感器和温湿度传感器通过函数直接获取,土壤湿度传感器通过电容上的电压值计算出相对湿度,二氧化碳传感器则会通过串口把数据发送给ESP32,而ESP32C6恰好有两个串口,其中一个是LP串口(低功耗串口),它可以当成普通串口用。我将普通串口用作接收二氧化碳传感器的数据,LP串口专门与串口屏通信。

此外,我将所有的串口输出都加了“UART”宏,如果该宏的定义为1,则会在串口上显示各种数据,反之则不会,方便在调试时查看串口数据,调试好后就不需要串口输出了。

3.2 蓝牙任务

      /* 以下为BLE部分变量 */
BLEServer *pServer = NULL;
BLECharacteristic *pTxCharacteristic;
bool deviceConnected = false;
bool oldDeviceConnected = false;

void BLESend(const String& message);
void HMISend();

class MyServerCallbacks : public BLEServerCallbacks {
 void onConnect(BLEServer *pServer) {
   deviceConnected = true;
};

 void onDisconnect(BLEServer *pServer) {
   deviceConnected = false;
}
};

class MyCallbacks : public BLECharacteristicCallbacks {
 void onWrite(BLECharacteristic *pCharacteristic) {
   String rxValue = pCharacteristic->getValue();
#if UART
   if (rxValue.length() > 0) {
     Serial.println("*********");
     Serial.print("Received Value: ");
     for (int i = 0; i < rxValue.length(); i++) {
       Serial.print(rxValue[i]);
    }

     Serial.println();
     Serial.println("*********");
  }
#endif
}
};

void BLETask(void *arg)
{
  // Create the BLE Device
 BLEDevice::init("Smart Agriculture");

 // Create the BLE Server
 pServer = BLEDevice::createServer();
 pServer->setCallbacks(new MyServerCallbacks());

 // Create the BLE Service
 BLEService *pService = pServer->createService(SERVICE_UUID);

 // Create a BLE Characteristic
 pTxCharacteristic = pService->createCharacteristic(CHARACTERISTIC_UUID_TX, BLECharacteristic::PROPERTY_NOTIFY);

 pTxCharacteristic->addDescriptor(new BLE2902());

 BLECharacteristic *pRxCharacteristic = pService->createCharacteristic(CHARACTERISTIC_UUID_RX, BLECharacteristic::PROPERTY_WRITE);

 pRxCharacteristic->setCallbacks(new MyCallbacks());

 // Start the service
 pService->start();

 // Start advertising
 pServer->getAdvertising()->start();
 #if UART
 Serial.println("Waiting a client connection to notify...");
 #endif

 while(1)
{
   BLESend(BT);

   // disconnecting
   if (!deviceConnected && oldDeviceConnected) {
     delay(500);                   // give the bluetooth stack the chance to get things ready
     pServer->startAdvertising();  // restart advertising
#if UART
     Serial.println("start advertising");
#endif
     oldDeviceConnected = deviceConnected;
  }
   // connecting
   if (deviceConnected && !oldDeviceConnected) {
     // do stuff here on connecting
     oldDeviceConnected = deviceConnected;
  }
}
}

此处的代码来自Arduino IDE中的ESP32的示例程序,不做过多讲解。总体上就是负责创建一个BLE设备、服务器、服务和特征,并处理设备连接和断开连接的逻辑等。在连接成功后会发送数据到客户端(即蓝牙APP)。

3.3 串口屏任务

HardwareSerial LP_Serial(1); //这一行在最开始

LP_Serial.begin(115200, SERIAL_8N1, LP_UART_RXD, LP_UART_TXD); //这一行在setup函数中

void HMITask(void *arg)
{
 while(1)
{
   LP_Serial.printf("data.temp.txt=\"%.1f\"", TH.temperature);
   HMISend();

   LP_Serial.printf("data.huma.txt=\"%.0f\"", TH.humidity);
   HMISend();

   LP_Serial.printf("data.lit.txt=\"%.0f\"", lux);
   HMISend();

   LP_Serial.printf("data.hums.txt=\"%.0f %\"", soil);
   HMISend();

   LP_Serial.printf("data.co.txt=\"%.0f\"", co2);
   HMISend();

   delay(1000);

   if (LP_Serial.available())
  {
     String LP_data = LP_Serial.readStringUntil(';');

     LP_data.trim();

     if (LP_data == "1:0") {
         led_state = 0;
    } else if (LP_data == "1:1") {
         led_state = 1;
    } else if (LP_data == "1:2") {
         led_state = 2;
    } else if (LP_data == "2:0") {
         fan_state = 0;
    } else if (LP_data == "2:1") {
         fan_state = 1;
    } else if (LP_data == "2:2") {
         fan_state = 2;
    } else if (LP_data == "3:0") {
         pump_state = 0;
    } else if (LP_data == "3:1") {
         pump_state = 1;
    } else if (LP_data == "3:2") {
         pump_state = 2;
    }
  }
}
}

此任务首先需要定义一下LP串口,然后初始化LP串口,并向串口屏不断发送传感器的数据,同时如果接收到串口屏发来的控制指令做出相应处理。外设的状态有:0-关,1-开,2-自动,而“1:2”中前一个数字是外设编号,第二个数字是外设更改后的状态,外设1是LED,外设2是风扇,外设3是水泵。

3.4 WiFi/MQTT任务

      /* 以下为WiFi和MQTT部分变量 */
const char *ssid = "Mate 40 Pro"; // Wifi 账号
const char *password = "15239570078gzc";  // wifi 密码

//客户端变量
WiFiClient espClient;
PubSubClient client(espClient);

// 配置消息
char config_temperature[] = "{\"unique_id\":\"Smart-Agriculture-Temperature\",\"name\":\"温度传感器\",\"icon\":\"mdi:thermometer\",\"state_topic\":\"Smart-Agriculture/temperature/state\",\"json_attributes_topic\":\"Smart-Agriculture/temperature/attributes\",\"unit_of_measurement\":\"℃\",\"device\":{\"identifiers\":\"ESP32\",\"manufacturer\":\"QingSpace\",\"model\":\"HA\",\"name\":\"ESP32\",\"sw_version\":\"1.0\"}}";
char config_humidity[] = "{\"unique_id\":\"Smart-Agriculture-Humidity\",\"name\":\"空气湿度传感器\",\"icon\":\"mdi:water-percent\",\"state_topic\":\"Smart-Agriculture/humidity/state\",\"json_attributes_topic\":\"Smart-Agriculture/humidity/attributes\",\"unit_of_measurement\":\"%\",\"device\":{\"identifiers\":\"ESP32\",\"manufacturer\":\"QingSpace\",\"model\":\"HA\",\"name\":\"ESP32\",\"sw_version\":\"1.0\"}}";
char config_light[] = "{\"unique_id\":\"Smart-Agriculture-Light\",\"name\":\"光照传感器\",\"icon\":\"mdi:brightness-5\",\"state_topic\":\"Smart-Agriculture/light/state\",\"json_attributes_topic\":\"Smart-Agriculture/light/attributes\",\"unit_of_measurement\":\"lux\",\"device\":{\"identifiers\":\"ESP32\",\"manufacturer\":\"QingSpace\",\"model\":\"HA\",\"name\":\"ESP32\",\"sw_version\":\"1.0\"}}";
char config_soil_moisture[] = "{\"unique_id\":\"Smart-Agriculture-Soil-Moisture\",\"name\":\"土壤湿度传感器\",\"icon\":\"mdi:water-outline\",\"state_topic\":\"Smart-Agriculture/soil-moisture/state\",\"json_attributes_topic\":\"Smart-Agriculture/soil-moisture/attributes\",\"unit_of_measurement\":\"%\",\"device\":{\"identifiers\":\"ESP32\",\"manufacturer\":\"QingSpace\",\"model\":\"HA\",\"name\":\"ESP32\",\"sw_version\":\"1.0\"}}";
char config_co2[] = "{\"unique_id\":\"Smart-Agriculture-CO2\",\"name\":\"二氧化碳传感器\",\"icon\":\"mdi:molecule-co2\",\"state_topic\":\"Smart-Agriculture/co2/state\",\"json_attributes_topic\":\"Smart-Agriculture/co2/attributes\",\"unit_of_measurement\":\"ppm\",\"device\":{\"identifiers\":\"ESP32\",\"manufacturer\":\"QingSpace\",\"model\":\"HA\",\"name\":\"ESP32\",\"sw_version\":\"1.0\"}}";


// MQTT Broker 服务端连接
const char *mqtt_broker = "192.168.43.179";//mqtt服务器地址
const char *mqtt_username = "QSG";
const char *mqtt_password = "gzc20050414";
const int mqtt_port = 1883;//端口


void MQTTTask(void *arg)
{
 // connecting to a WiFi network
 WiFi.begin(ssid, password);
 while (WiFi.status() != WL_CONNECTED) {
     delay(2000);
#if UART
     Serial.println("Connecting to WiFi...");
#endif
}
#if UART
 Serial.println("Connected to WiFi");
#endif

 //connecting to a mqtt broker 连接服务端
 client.setBufferSize(512); // 增加消息缓冲区大小
 client.setServer(mqtt_broker, mqtt_port);
 while (!client.connected()) {
     String client_id = "esp32-client-";
     client_id += String(WiFi.macAddress());
     Serial.printf("The client %s connects to the public mqtt broker\n", client_id.c_str());
     if (client.connect(client_id.c_str(), mqtt_username, mqtt_password)) {
#if UART
         Serial.println("Public emqx mqtt broker connected");
#endif
    } else {
#if UART
         Serial.print("failed with state ");
         Serial.print(client.state());//返回连接状态
#endif
         delay(2000);
    }
}

 // 发送初始化配置消息
 client.publish("homeassistant/sensor/HA/Smart-Agriculture-Temperature/config", config_temperature);
 client.publish("homeassistant/sensor/HA/Smart-Agriculture-Humidity/config", config_humidity);
 client.publish("homeassistant/sensor/HA/Smart-Agriculture-Light/config", config_light);
 client.publish("homeassistant/sensor/HA/Smart-Agriculture-Soil-Moisture/config", config_soil_moisture);
 client.publish("homeassistant/sensor/HA/Smart-Agriculture-CO2/config", config_co2);
 while(1)
{
   client.publish("Smart-Agriculture/temperature/state", String(TH.temperature).c_str());
   client.publish("Smart-Agriculture/humidity/state", String(TH.humidity).c_str());
   client.publish("Smart-Agriculture/light/state", String(lux).c_str());
   client.publish("Smart-Agriculture/soil-moisture/state", String(soil).c_str());
   client.publish("Smart-Agriculture/co2/state", String(co2).c_str());

   delay(1000);
}
}

此任务的配置部分也是节选自Arduino IDE中ESP32的示例程序,主要说一下发送的信息内容。在每次连接成功后,我们需要先向搭建在树莓派上的MQTT服务器发送传感器的初始化配置信息,如果Home Assistant上没有相应的设备则会添加上去,如果已经有了就不会有变化。然后不断向MQTT服务器发送传感器数据,不断更新网页上的传感器数据。这样可以在相对较远的距离下无线查看传感器数据。不过目前只能在局域网内查看,即ESP32,树莓派和查看数据的设备要再同一个局域网内。

3.5 外设控制任务

void ControlTask(void *arg)
{
 pinMode(LED_PIN, OUTPUT);
 pinMode(FAN_PIN1, OUTPUT);
 pinMode(FAN_PIN2, OUTPUT);

 while(1)
{
   if(led_state == 2)
  {
     digitalWrite(LED_PIN, lux < lux_limit ? HIGH : LOW);
  }
   else
  {
     digitalWrite(LED_PIN, led_state ? HIGH : LOW);
  }

   if(fan_state == 2)
  {
     analogWrite(FAN_PIN1, (TH.temperature > temp_limit ? 1 : 0) * 75);
  }
   else
  {
     analogWrite(FAN_PIN1, (fan_state ? 1 : 0) * 75);
  }

   if(pump_state == 2)
  {
     analogWrite(PUMP_PIN1, (soil < soil_limit ? 1 : 0) * 255);
  }
   else
  {
     analogWrite(PUMP_PIN1, (pump_state ? 1 : 0) * 255);
  }
}
}

这里就比较简单了,只是根据外设的状态去自动或手动控制外设的运行与否。

4.活动心得

在该项目中,我第一次使用到了树莓派,遇到了很多问题,但是都被我一一克服。同时,我也发现了ESP32在无线通信方面的强大优势,也是第一次将ESP32投入实际应用之中,我相信在之后的创意之路上ESP32一定能成为我的好帮手。

软硬件
附件下载
ESP32C6_Smart_Agriculture.zip
团队介绍
一位热爱嵌入式开发的大学生
团队成员
QingSpace
评论
0 / 100
查看更多
硬禾服务号
关注最新动态
0512-67862536
info@eetree.cn
江苏省苏州市苏州工业园区新平街388号腾飞创新园A2幢815室
苏州硬禾信息科技有限公司
Copyright © 2024 苏州硬禾信息科技有限公司 All Rights Reserved 苏ICP备19040198号