ESP32 with PIR Motion Sensor using Interrupts and Timers#

From: ESP32 with PIR Motion Sensor using Interrupts and Timers —— Random Nerd Tutorials

电路与功能#

本教程展示了如何使用 PIR 运动传感器通过 ESP32 检测运动。在本例中,当检测到运动(触发中断)时,ESP32 会启动一个计时器,并在预定义的秒数内打开 LED。当计时器倒计时结束时,LED 会自动关闭。

  1. 检测运动: 用到中断

  2. 倒计时: 定时器(但不用 delay)

  • LED 连接 GPIO26

  • 运动检测连接 GPI27

中断#

可以用作中断的引脚都用红框框起来了,例如设置 GPIO27 引脚用为输入中断

digitalPinToInterrupt(27)
attachInterrupt(digitalPinToInterrupt(motionSensor), detectsMovement, RISING);

中断引脚#

注意 GPIO27 连接 Motion Sensor

PIR Motion Sensor#

Complete Code#

#define timeSeconds 10

// Set GPIOs for LED and PIR Motion Sensor
const int led = 26;
const int motionSensor = 27;

// Timer: Auxiliary variables
unsigned long now = millis();
unsigned long lastTrigger = 0;
boolean startTimer = false;
boolean motion = false;

// Checks if motion was detected, sets LED HIGH and starts a timer
void IRAM_ATTR detectsMovement() {                      // 1
  digitalWrite(led, HIGH);
  startTimer = true;      // 可看作计时开关                           
  lastTrigger = millis();
}

void setup() {
  // Serial port for debugging purposes
  Serial.begin(115200);
  
  // PIR Motion Sensor mode INPUT_PULLUP
  pinMode(motionSensor, INPUT_PULLUP);
  // Set motionSensor pin as interrupt, assign interrupt function and set RISING mode

  attachInterrupt(digitalPinToInterrupt(motionSensor), detectsMovement, RISING);      
                                                        // 2

  // Set LED to LOW
  pinMode(led, OUTPUT);
  digitalWrite(led, LOW);
}

void loop() {
  // Current time
  now = millis();
  if((digitalRead(led) == HIGH) && (motion == false)) {
    Serial.println("MOTION DETECTED!!!");
    motion = true;                                      // 3
  }
  
  // Turn off the LED after the number of seconds defined in the timeSeconds variable
  if(startTimer && (now - lastTrigger > (timeSeconds*1000))) { 
    Serial.println("Motion stopped...");
    digitalWrite(led, LOW);                             
    startTimer = false;     
    motion = false;                                     // 4
  }
}

分析#

startTimer 看作是计时开关。

间接利用了 LED 的状态。如果不用 LED,则可以额外使用一个状态变量。

  1. detectsMovement() 定义中断函数,即当 GPIO27 有中断时执行,不是被调用。立即点亮 LED,记下当前时间,以及定时器状态。

  2. 完成中断的绑定,将中断函数绑定到 GPIO27 的上升沿

  3. motion 的判断方法有点复杂。必须同时满足两个条件,才算检测到了运动,即 LED 要亮起,且 motion 的旧记录为 false。这保证了一次 Motion 事件,只输出一条消息到串口。 (是否包含了防抖能力?)

  4. 完成 LED 熄灭倒计时。 条件: 在已开始记时的条件下,时间经过了给定的时间。倒计时到后, startTimer = false,关闭计时开关。

注意#

本程序的功能看似简单,但状态检测与推理并不容易,上面的分析也仅仅是验证,并未严格证明。有什么套路让分析更容易一点?代数的方法?状态图的方法?否则逻辑复杂一点之后,就很不好严谨地分析了。况且,分析是一回事,设计出来是另一回事(更难)。

中断函数要尽量简短,复杂的逻辑并不出现在中断函数中,而是在 loop() 中。这是因为中断函数必须得简短,于是只改写了几个变量。

同时,位于 (1) 处的 IRAM_ATTR is used to run the interrupt code in RAM, otherwise code is stored in flash and it’s slower.