这些小活动你都参加了吗?快来围观一下吧!>>
电子产品世界 » 论坛首页 » 活动中心 » 板卡试用 » Grove Creator Kit开发【小型环境监测站】

共1条 1/1 1 跳转至

Grove Creator Kit开发【小型环境监测站】

菜鸟
2026-08-24 22:50:58     打赏

一、硬件介绍

1、主控开发板

使用的是XIAO ESP32S3开发板,集成ESP32S3 32位双核处理器芯片,最高运行频率240MHz,板载8MB PSRAM和8MB FLASH

1787576271514383.png





功能引脚示意图

1787576305327340.png


2、扩展底板

一款支持XIAO系列的功能扩展板,具有丰富的外设:包括OLED、RTC、可扩展内存、无源蜂鸣器、RESET/用户按钮、5V舵机连接器等;

image.png


主要特性

image.png


功能引脚示意图

image.png


3、Grove Creator Kit 器件

在本次开发中,使用到的 `Grove Creator Kit 套件` 中的器件如下:


Grove - Temperature & Humidity Sensor (DHT11)

这款温湿度传感器提供预校准的数字输出,独特的电容式传感器元件测量相对湿度,温度通过负温度系数 (NTC) 热敏电阻测量;

image-20260822212659884.png



主要特性

image.png


Grove - Light Sensor

Grove - 光传感器集成了一个光敏电阻来检测光强度,当光强度增加时,光敏电阻的阻值会减小,板载的双运放芯片LM358产生与光强度相对应的电压;

输出信号是模拟值,光线越亮,数值越大;

image-20260822213401386.png


主要特性

image.png


Grove - Chainable RGB LED

基于P9813芯片,是一款全彩LED驱动器,提供了3个恒流驱动器以及256级灰度的调制输出;

通过2线传输(数据和时钟)与MCU通信;

image-20260821171844602.png


主要特性

工作电压:5V

工作电流:20mA

通信协议:串行


Grove - Sound Sensor

Grove - 声音传感器可以检测环境的声音强度,该模块的主要组件是一个简单的麦克风,基于LM358放大器和驻极体麦克风;

> 该模块的输出是模拟信号;

image-20260823173018577.png



主要特性

image.png


Grove - Buzzer

> Grove - 蜂鸣器模块的主要组件是一个压电蜂鸣器压;

> 压电蜂鸣器可以连接到数字输出,当输出为高电平时会发出音调;

image-20260820212950973.png


主要特性

image.png



二、功能实现

1、硬件搭建

将光传感器、温湿度传感器、RGB LED、声音传感器、蜂鸣器等模块,通过Grove接口分别连接到扩展板和开发板对应的引脚上;

image.png


实物搭建效果:

image-20260823180411782.png



2、功能效果

【小型环境监测站】:实现对室内环境温湿度、光照强度、环境响度等数据实时监测,并通过RGB LED的不同颜色变化反馈当前的环境状态;

以及当相关参数异常时触发蜂鸣器警报声,并且OLED屏幕 / WIFI网页端实时显示监测到的相关数据等信息;

温湿度检测:通过DHT11数字传感器,获取当前环境温度与相对湿度;

光照强度检测:通过光敏传感器输出模拟量,获取当前环境光强百分比(0~100%);

环境响度检测:通过声音传感器输出模拟量,获取当前环境响度大小(安静/有声音/声音很大);

RGB LED:通过不同RGB颜色的变化,反映当前环境状态等级(绿色/蓝色/黄色/红色);


警报触发条件

温度 > 33℃

湿度 > 80%

光照强度< 40%

声音振动幅度>150 (声音很大)


正常情况下:RGB LED为绿色;

当任意1项条件满足:RGB LED变为蓝色;

当任意2项条件满足:RGB LED变为黄色;

当3项及以上条件满足:RGB LED变为红色,同时触发蜂鸣器警报声音;


主要运行流程图


11.png



三、代码编写

主要相关代码

> OLED屏幕:显示温湿度、光照强度信息;

> 网页UI端:显示温湿度、光照强度、响度、警报项数等信息;

#include <DHT11.h>
#include <U8x8lib.h>
#include <Wire.h>
#include <ChainableLED.h>
#include <WiFi.h>
#include <WebServer.h>

// WiFi配置
#define WIFI_SSID     "WIFI"
#define WIFI_PASSWORD "WIFI_PASS"
WebServer server(80);

// 引脚配置
#define LIGHT_PIN   A3
#define DHT11_PIN   D7
#define RGB_CIN     D9
#define RGB_DIN     D10
#define Sound_PIN   A2
#define BUZZER_PIN  D0

// OLED/DHT11/RGB LED配置
U8X8_SSD1306_128X64_NONAME_HW_I2C u8x8(/* reset=*/ U8X8_PIN_NONE, /* clock=*/ SCL, /* data=*/ SDA );
DHT11 dht11(DHT11_PIN);
ChainableLED leds(RGB_CIN, RGB_DIN, 1);

// 相关传感器变量
int lightValue = 0;
int lightPercent = 0;
int temperature = 0;
int humidity = 0;
int dhtResult = -1;

int lastValidTemp = 0;
int lastValidHumi = 0;
bool hasValidData = false;

int adcAvg;
int amplitude;
const int baseLevel = 2020; 
int alarmCount = 0;
String soundLevel;

unsigned long timerLT = 0;
const unsigned long REFRESH_RATE = 500;     // 光强刷新时间ms
unsigned long timerHum = 0;
const unsigned long intervalHum = 2000; //DHT11 刷新时间

unsigned long buzzerTimer = 0;
bool buzzerState = false;
const unsigned long buzzerOnMs = 150;   // 蜂鸣器持续时间
const unsigned long buzzerOffMs = 150;



void handleJsonData(){
  String json = "{";
  if(hasValidData){
    json += "\"temp\":" + String(lastValidTemp);
    json += ",\"humi\":" + String(lastValidHumi);
  }else{
    json += "\"temp\":null";
    json += ",\"humi\":null";
  }
  json += ",\"light\":" + String(lightPercent);
  json += ",\"sound\":\"" + soundLevel + "\"";
  json += ",\"alarm\":" + String(alarmCount);
  json += "}";
  server.send(200, "application/json", json);
}

String getHtmlPage(){
  String html = R"HTML(
    <!DOCTYPE html>
    <html>
    <head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>小型环境监测站</title>
    <style>
    *{box-sizing:border-box;margin:0;padding:0;font-family:"Microsoft Yahei",sans-serif;}
    body{
        background:#121422;
        color:#e8ecff;
        padding:40px 20px;
    }
    .wrap{
        max-width:520px;
        margin:0 auto;
    }
    h1{
        text-align:center;
        font-size:42px;
        color:#62d0ff;
        margin-bottom:40px;
        font-weight:bold;
        letter-spacing:3px;
    }
    .main-card{
        background:#1e223b;
        padding:36px 32px;
        border-radius:20px;
        box-shadow: 0 8px 30px #0b0d18;
        border:1px solid #303558;
    }
    .row{
        display:flex;
        justify-content:space-between;
        padding:18px 4px;
        border-bottom:1px solid #34395c;
        font-size:22px;
    }
    .row:last-child{
        border-bottom:none;
    }
    .label-text{
        color:#b1b8e0;
    }
    .val-text{
        color:#ffffff;
        font-weight:bold;
    }
    .warn{
        color:#ff4444 !important;
    }
    </style>
    </head>
    <body>
    <div>
    <h1>小型环境监测站</h1>
    <div>
        <div>
            <span>温度</span>
            <span id="val_temp">-- ℃</span>
        </div>
        <div>
            <span>湿度</span>
            <span id="val_humi">-- %RH</span>
        </div>
        <div>
            <span>光强</span>
            <span id="val_light">-- %</span>
        </div>
        <div>
            <span>响度</span>
            <span id="val_sound">--</span>
        </div>
        <div>
            <span>警报项数</span>
            <span id="val_alarm">--</span>
        </div>
    </div>
    </div>

    <script>
    async function fetchData(){
    try{
        const res = await fetch("/data");
        const d = await res.json();

        //温度
        const tempDom = document.getElementById("val_temp");
        if(d.temp!==null){
            tempDom.innerText = d.temp+" ℃";
            if(d.temp > 33) tempDom.classList.add("warn");
            else tempDom.classList.remove("warn");
        }else{
            tempDom.innerText = "-- ℃";
            tempDom.classList.remove("warn");
        }

        //湿度
        const humiDom = document.getElementById("val_humi");
        if(d.humi!==null){
            humiDom.innerText = d.humi+" %RH";
            if(d.humi > 80) humiDom.classList.add("warn");
            else humiDom.classList.remove("warn");
        }else{
            humiDom.innerText = "-- %RH";
            humiDom.classList.remove("warn");
        }

        //光强
        const lightDom = document.getElementById("val_light");
        lightDom.innerText = d.light + " %";
        if(d.light < 40) lightDom.classList.add("warn");
        else lightDom.classList.remove("warn");

        //响度
        const soundDom = document.getElementById("val_sound");
        soundDom.innerText = d.sound;
        if(d.sound === "声音很大") soundDom.classList.add("warn");
        else soundDom.classList.remove("warn");

        //警报项数
        document.getElementById("val_alarm").innerText = d.alarm;

    }catch(e){
    }
    }
    setInterval(fetchData,500);
    fetchData();
    </script>
    </body>
    </html>
    )HTML";
    return html;
}

void handleRoot() {
  server.send(200, "text/html", getHtmlPage());
}

void wifiInit(){
  WiFi.begin(WIFI_SSID, WIFI_PASSWORD);
  int cnt=0;
  while(WiFi.status() != WL_CONNECTED && cnt<20){
    delay(500);
    Serial.print(".");
    cnt++;
  }
  if(WiFi.status() == WL_CONNECTED){
    Serial.println("\nWiFi连接成功!");
    Serial.print("网页访问地址:http://");
    Serial.println(WiFi.localIP());
  }else{
    Serial.println("\nWiFi连接失败,请检查账号密码");
  }
  server.on("/", handleRoot);
  server.on("/data",handleJsonData);
  server.begin();
  Serial.println("Web服务已启动");
}

//OLED 数值更新
void refreshOled(){
    u8x8.setCursor(3,1);
    u8x8.print("Light:");
    u8x8.print(lightPercent);
    u8x8.print("%   ");

    u8x8.setCursor(3,3);
    u8x8.print("Temp:");
    if(hasValidData)
    {
        u8x8.print(lastValidTemp);
        u8x8.print("C   ");
    }else{
        u8x8.print("--   ");
    }

    u8x8.setCursor(3,5);
    u8x8.print("Humi:");
    if(hasValidData)
    {
        u8x8.print(lastValidHumi);
        u8x8.print("%   ");
    }
    else
    {
        u8x8.print("--   ");
    }
}

void setup()
{
    Serial.begin(115200);
    u8x8.begin();
    u8x8.setFlipMode(0);
    u8x8.setFont(u8x8_font_7x14_1x2_r);

    pinMode(DHT11_PIN, INPUT);
    pinMode(LIGHT_PIN, INPUT);
    pinMode(Sound_PIN, INPUT);
    pinMode(BUZZER_PIN, OUTPUT);
    digitalWrite(BUZZER_PIN, LOW);

    u8x8.clear();
    leds.setColorRGB(0,0,255,0);
    delay(800);
    wifiInit();
}

void loop()
{
    unsigned long now = millis();
    server.handleClient();

    //DHT11温湿度 2s读取一次
    if(now - timerHum >= intervalHum)
    {
        timerHum = now;
        dhtResult = dht11.readTemperatureHumidity(temperature, humidity);
        if(dhtResult == 0){
            lastValidTemp = temperature;
            lastValidHumi = humidity;
            hasValidData = true;
        }
    }
    bool dhtValid = (dhtResult == 0);

    //声音采样
    long sum = 0;
    const int sampleCnt = 32;
    for(int i = 0; i < sampleCnt; i++)
    {
        sum += analogRead(Sound_PIN);
    }
    adcAvg = sum / sampleCnt;
    amplitude = abs(adcAvg - baseLevel);

    //响度等级
    if(amplitude < 30){
        soundLevel = "安静";
    }else if(amplitude < 150){
        soundLevel = "有声音";
    }else{
        soundLevel = "声音很大";
    }

    //警报条件统计
    alarmCount = 0;
    bool cond1 = hasValidData && (lastValidTemp > 33);
    bool cond2 = hasValidData && (lastValidHumi > 80);
    bool cond3 = (lightPercent < 40);
    bool cond4 = (amplitude > 150);

    if(cond1) alarmCount++;
    if(cond2) alarmCount++;
    if(cond3) alarmCount++;
    if(cond4) alarmCount++;

    //RGB LED颜色反馈
    if(alarmCount == 0)
    {
        leds.setColorRGB(0, 0, 255, 0);     // 绿
    }
    else if(alarmCount == 1)
    {
        leds.setColorRGB(0, 0, 0, 255);
    }
    else if(alarmCount == 2)
    {
        leds.setColorRGB(0, 255, 255, 0);
    }
    else if(alarmCount >=3)
    {
        leds.setColorRGB(0, 255, 0, 0);
    }

    //蜂鸣器 警报
    if(alarmCount >=3)
    {
        if(buzzerState == false && (now - buzzerTimer >= buzzerOffMs))
        {
            digitalWrite(BUZZER_PIN, HIGH);
            buzzerState = true;
            buzzerTimer = now;
        }
        if(buzzerState == true && (now - buzzerTimer >= buzzerOnMs))
        {
            digitalWrite(BUZZER_PIN, LOW);
            buzzerState = false;
            buzzerTimer = now;
        }
    }
    else
    {
        digitalWrite(BUZZER_PIN, LOW);
        buzzerState = false;
    }


    // 光强 500ms更新
    if(now - timerLT >= REFRESH_RATE){
        timerLT = now;
        lightValue = analogRead(LIGHT_PIN);
        lightPercent = map(lightValue, 0, 4095, 0, 100);
        lightPercent = constrain(lightPercent,0,100);   //  0~100%
        refreshOled();
    }
}



四、程序烧录

1、连接USB数据线至开发板;

2、选择端口号对应的开发板;

3、点击 `上传` 烧录程序到开发板上;

image-20260818173949849.png



五、效果演示

OLED屏幕上实时显示当前环境的部分相关数据,同时通过串口打印当前网页端的访问地址,可查看更多当前环境的相关信息;

image-20260823224537287.png



网页端:当相关参数超过设定范围时,将会变为红色字体显示;

image-20260823232614360.png



效果演示

11.gif




六、总结

在Grove Creator Kit - α套件中,大多数模块都是采用的5V供电,以及部分是3V3供电,因此在实际使用过程中,需要单独引出5V供电引脚用来给这些模块供电,由于这些模块都是采用的Grove接口,因此在搭建过程中相比于杜邦线连接也比较方便;特别是套件中的摇杆模块、温湿度传感器、LED等模块,非常适合在不同的项目中进行快速原型验证和进行开发使用!






共1条 1/1 1 跳转至

回复

匿名不能发帖!请先 [ 登陆 注册 ]