这些小活动你都参加了吗?快来围观一下吧!>>
电子产品世界 » 论坛首页 » 活动中心 » 板卡试用 » 【GROVECREATORKIT测评】6、综合5-光控+人体感应自动灯

共1条 1/1 1 跳转至页

【GROVECREATORKIT测评】6、综合5-光控+人体感应自动灯

高工
2026-10-04 22:22:33     打赏

一、硬件介绍(Hardware)

1.1 主控板

Arduino Nano RP2040 Connect
  • MCU:Raspberry Pi RP2040(双核 ARM Cortex-M0+,133 MHz)

  • 封装:Arduino Nano 经典尺寸(18 × 45 mm),兼容 Grove 扩展板

  • 工作电压:3.3 V(逻辑电平 3.3 V,Grove 模块同电平域,无需电平转换)

  • 数字 IO:20 个,全部支持 INPUT_PULLUP

  • 模拟输入:8 个 12-bit ADC(本次用 A2 作为光强输入)

  • 开发框架:ArduinoCore-mbed(PlatformIO platform = raspberrypi)

1.2 Grove 模块清单

模块型号 / 规格丝印Arduino 引脚号GPIO接口类型
红外对管Grove - Line Finder v1.1(红外发射 + 光电晶体管)D22GPIO25数字输入
光敏电阻Grove - Light Sensor v1.2(LDR + LM358 运放)A216GPIO28模拟输入
继电器Grove - Relay(数字常开开关)D66GPIO18数字输出
模拟照明 LEDGrove - LED(LED + 板上电位器)D55GPIO17数字输出
模块细节① Grove - Line Finder v1.1(红外对管)
  • 板上集成了红外发射 LED + 红外敏感光电晶体管;

  • 本项目将其作为"人体存在检测"使用:红外光被人体/物体反射回晶体管时输出 HIGH;

  • 实测电平:无遮挡时输出 HIGH(ir_raw=1),被遮挡时输出 LOW(ir_raw=0)。

    • 因此判定逻辑 presence = (ir_raw == LOW)。

    • 不同批次模块的电平定义可能反转;首次使用时务必用串口确认。

② Grove - Light Sensor v1.2(光敏电阻)
  • 板上 LDR(光依赖电阻)+ LM358 双运放;

  • 光强 ↑ → LDR 阻值 ↓ → 运放输出电压 ↑ → analogRead 数值 ↑;

  • 12-bit ADC 范围 0..4095;本工程实测典型值见 §3.4;

  • 输出阻抗很低,可直接驱动 ADC,无需额外缓冲。

③ Grove - Relay(继电器)
  • 数字常开开关,控制端低电压(3.3 V / 5 V)即可控制高电压负载;

  • 模块上有 LED 指示灯,吸合时点亮;

  • 本项目用其控制外部照明负载,并把板载 LED 与 Grove LED 同步点亮以方便肉眼观察。

④ Grove - LED(模拟照明灯)
  • 板上 LED + 保护电阻 ;

  • 数字输数:HIGH 点亮,LOW 熄灭;

  • 作为"继电器已触发"的本地控制。

1.3 接线总览

Arduino Nano RP2040 Connect
                ┌────────────────────────────────┐
                │                          D2  ──┼──► Line Finder v1.1  (SIG)
                │                          A2  ──┼──► Light Sensor v1.2 (SIG, 模拟)
                │                                |
                │                          D6  ──┼──► Relay   (吸合控制)-LED (模拟照明)
                └────────────────────────────────┘
025697e1-6d1b-4ff9-97da-b88d1dd80277.png

二、软件设计(Software Design)

程序结构总览

整个固件只有一对 setup() / loop(),扁平结构:
main.cpp
├── 文件头注释(接线、行为、ADC 范围、实测电平)
├── 引脚 / 参数常量
├── 红外状态机结构 IRState + 光强结构 LightState
├── updatePresence()        ← 红外防抖 + 状态翻转
├── shouldLightOn()         ← 滞回决策
├── applyLight()            ← 输出同步(Relay + LED)
├── setup()                 ← 引脚 + 串口 + 启动信息
└── loop()
     ├── 1) updatePresence(now)            ← 每 30 ms 采样一次红外
     ├── 2) analogRead(A2)                 ← 一次性读光强
     ├── 3) shouldLightOn(L) / applyLight  ← 决策 + 应用
     └── 4) 节流日志

决策模型

2.3.1 真值表
当前灯状态presencelightRaw 关系决策灯
关0(无人)—关关
关1(有人)lightRaw >= DARK_LAMP_OFF(不够暗)关关
关1(有人)lightRaw < DARK_LAMP_OFF(够暗)开开
开0(无人)—关关
开1(有人)lightRaw > DARK_LAMP_ON(环境变亮)关关
开1(有人)DARK_LAMP_OFF ≤ lightRaw ≤ DARK_LAMP_ON保持开开
滞回(hysteresis)
  • 滞回窗口 [DARK_LAMP_OFF, DARK_LAMP_ON];

  • 灯状态在窗口内不翻转,避免临界值反复跳变(chatter);

  • 当前实测 DARK_LAMP_OFF = 500、DARK_LAMP_ON = 1000,开灯稳态 lightRaw ≈ 600 落在窗口正中,距两条边界各 400,充分抗自激。

关键算法

2红外防抖(连续 N 次一致)
const uint8_t  IR_DEBOUNCE_N = 3;
const uint16_t IR_SAMPLE_MS   = 30;   // 30 ms × 3 = 90 ms 才允许翻状态
  • 每 30 ms 采样一次 digitalRead(PIN_IR);

  • 连续 IR_DEBOUNCE_N 次一致才允许 presence 翻转;

  • 翻转瞬间重置两侧计数器,重新累计;

  • 物理遮挡通常 ≥ 200 ms,90 ms 防抖不会漏检,但能完全吃掉电磁毛刺与抖动。

滞回决策
static bool shouldLightOn(uint16_t lightRaw) {
  if (light.lightOn) {
    if (!ir.presence)               return false;  // 无人 -> 关
    if (lightRaw > DARK_LAMP_ON)    return false;  // 环境变亮 -> 关
    return true;                                   // 滞回区 -> 保持开
  } else {
    if (!ir.presence)               return false;
    if (lightRaw >= DARK_LAMP_OFF)  return false;  // 还没暗到能开
    return true;                                   // 暗 + 有人 -> 开
  }
}
输出同步
static void applyLight(bool want) {
  if (light.lightOn == want) return;              // 状态未变 -> 不写引脚
  light.lightOn = want;
  digitalWrite(PIN_RELAY, want ? RELAY_ON : (uint8_t)!RELAY_ON);
}
  • 每次只写一次 Relay + Lamp;

  • 状态未变时不写,避免抖动时引脚反复翻转;

  • RELAY_ON常量集中顶部,便于适配不同极性模块。

时间参数

参数值含义
DARK_LAMP_OFF500当前关灯 → lightRaw < 此值 视为"够暗、允许开"
DARK_LAMP_ON1000当前开灯 → lightRaw > 此值 视为"够亮、必须关"
IR_SAMPLE_MS30红外采样周期
IR_DEBOUNCE_N3红外防抖:连续 N 次一致才翻状态
LOG_INTERVAL_MS200串口日志节流
主循环 delay20 ms采样与刷新周期

三、功能展示

eepw-5.gif


附:全部代码
#include <Arduino.h>

const uint8_t PIN_IR    = 2;    // 丝印 D2,  Line Finder SIG
const uint8_t PIN_LIGHT = A2;   // 丝印 A2,  Light Sensor SIG (模拟)
const uint8_t PIN_RELAY = 6;    // 丝印 D6,  Relay SIG
const uint8_t PIN_LAMP  = 5;    // 丝印 D5,  Lamp LED SIG (模拟照明)

const uint16_t DARK_LAMP_OFF = 500;
const uint16_t DARK_LAMP_ON  = 1000;
const uint8_t  IR_DEBOUNCE_N = 3;        // 连续 N 次一致才翻状态
const uint16_t IR_SAMPLE_MS   = 30;       // 每 30 ms 采样一次

const uint32_t LOG_INTERVAL_MS = 200;

const uint8_t RELAY_ON = HIGH;
const uint8_t LED_ON   = HIGH;

struct IRState {
    bool       presence      = false;  // 当前是否判定"有人"
    bool       lastRawBlocked = false;// 上一次的红外原始值 (true=被遮挡)
    uint8_t    runBlocked    = 0;     // 连续被遮挡计数
    uint8_t    runClear      = 0;     // 连续通畅计数
    uint32_t   lastSampleMs  = 0;
} ir;

struct LightState {
    bool lightOn = false;             // 当前灯状态 (true=开)
} light;

uint32_t lastLogMs = 0;

static inline bool irBlockedNow() {
    return digitalRead(PIN_IR) == LOW;   // LOW -> 被遮挡 -> presence
}


static void updatePresence(uint32_t now) {
    if ((uint16_t)(now - ir.lastSampleMs) < IR_SAMPLE_MS) return;
    ir.lastSampleMs = now;

    const bool blocked = irBlockedNow();

    if (blocked == ir.lastRawBlocked) {
        // 跟上次一样: 清零对侧计数, 同侧 +1
        if (blocked) { ir.runBlocked++; ir.runClear = 0; }
        else         { ir.runClear++;   ir.runBlocked = 0; }
    } else {
        // 翻转: 重置两个计数, 重头开始累计
        ir.lastRawBlocked = blocked;
        if (blocked) { ir.runBlocked = 1; ir.runClear = 0; }
        else         { ir.runClear   = 1; ir.runBlocked = 0; }
    }

    if ( ir.presence && ir.runClear   >= IR_DEBOUNCE_N) ir.presence = false;
    if (!ir.presence && ir.runBlocked >= IR_DEBOUNCE_N) ir.presence = true;
}

static bool shouldLightOn(uint16_t lightRaw) {
    if (light.lightOn) {
        if (!ir.presence)                 return false;  // 没人 -> 关
        if (lightRaw > DARK_LAMP_ON)      return false;  // 环境变亮 -> 关
        return true;                                     // 滞回区 -> 保持开
    } else {
        if (!ir.presence)                 return false;
        if (lightRaw >= DARK_LAMP_OFF)    return false;  // 还没暗到能开
        return true;                                     // 暗 + 有人 -> 开
    }
}

static void applyLight(bool want) {
    if (light.lightOn == want) return;
    light.lightOn = want;
    digitalWrite(PIN_RELAY, want ? RELAY_ON : (uint8_t)!RELAY_ON);
    digitalWrite(PIN_LAMP,  want ? LED_ON   : (uint8_t)!LED_ON);
}

void setup() {
    pinMode(PIN_IR,    INPUT);
    pinMode(PIN_LIGHT, INPUT);
    pinMode(PIN_RELAY, OUTPUT);
    pinMode(PIN_LAMP,  OUTPUT);

    // 起始: 继电器断开 + 模拟照明熄灭
    digitalWrite(PIN_RELAY, (uint8_t)!RELAY_ON);
    digitalWrite(PIN_LAMP,  (uint8_t)!LED_ON);

    Serial.begin(115200);
    uint32_t t0 = millis();
    while (!Serial && (millis() - t0) < 2000) { /* 等待 USB CDC */ }

    Serial.println();
    Serial.println("=== Light + PIR Auto Lamp ===");
    Serial.println("    IR=D2, Light=A2, Relay=D6, Lamp=D5");
    Serial.print("    DARK_LAMP_OFF="); Serial.print(DARK_LAMP_OFF);
    Serial.print("  DARK_LAMP_ON=");  Serial.print(DARK_LAMP_ON);
    Serial.println("  (12-bit ADC, lightRaw < 500 = dark, > 1000 = bright)");
    Serial.println("    ON when blocked && dark (with hysteresis)");
    Serial.println();
}

// ---------------------------------------------------------------------
// loop
// ---------------------------------------------------------------------
void loop() {
    const uint32_t now = millis();
    updatePresence(now);
    const uint16_t lightRaw = analogRead(PIN_LIGHT);
    const bool want = shouldLightOn(lightRaw);
    applyLight(want);

    if (now - lastLogMs >= LOG_INTERVAL_MS) {
        lastLogMs = now;
        Serial.print("ir_raw=");      Serial.print(digitalRead(PIN_IR));
        Serial.print("  L=");         Serial.print(lightRaw);
        Serial.print("  presence=");  Serial.print((int)ir.presence);
        Serial.print("  lamp=");      Serial.print((int)light.lightOn);
        Serial.println();
    }

    delay(20);
}





关键词: GROVE     光控     感应    

共1条 1/1 1 跳转至页

回复

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