Problems with this code

Hello,
I have the follwing setup:
Arduino Uno
PIR
Oled display
Led
RTC DS3231.

I would like the following to happen:
Between 21.00 and 09.00 hrs when there is movement the LED must be switched on.
The led stays on for about 10 seconds and then switches off. To check the time without connecting the Arduino to a serial monitor I would like to use the display

I have done a lot of research, made some code myself, and borrowed some code from here and there. This is what I have:

#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
#include <RTClib.h>

#define SCREEN_WIDTH 128
#define SCREEN_HEIGHT 64
#define SSD1306_I2C_ADDRESS 0x3C

#define PIR_PIN 5
#define LED_PIN 3

Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire);
RTC_DS3231 rtc;

unsigned long previousMillisTime = 0;
unsigned long previousMillisPIR = 0;
unsigned long ledStartTime = 0;
bool ledOn = false;
const long intervalTime = 1000;
const long intervalTimeLed = 1000;
const long ledDuration = 10 * 1000; // 10 seconds in milliseconds
const long pirActivationStartTime = 13L * 3600L + 30L * 60L; // 13:30 uur in seconds
const long pirDeactivationEndTime = 8L * 3600L + 55L * 60L; // 08:55 uur in seconds

const int LED_BRIGHTNESS = 64; // LED brightness (0-255)

void setup() {
  Serial.begin(9600);

  pinMode(PIR_PIN, INPUT);
  pinMode(LED_PIN, OUTPUT);

  if (!rtc.begin()) {
    Serial.println("Unable to find RTC");
    while (1);
  }

  if (rtc.lostPower()) {
    Serial.println("RTC lost power, setting the time!");
    rtc.adjust(DateTime(F(__DATE__), F(__TIME__)));
  }

  display.begin(SSD1306_I2C_ADDRESS, 0x3C);

  display.display();
  display.clearDisplay();
}

void loop() {
  unsigned long currentMillis = millis();

  DateTime now = rtc.now();

  // Display time every second
  if (currentMillis - previousMillisTime >= intervalTime) {
    previousMillisTime = currentMillis;

    display.clearDisplay();

    display.setTextSize(2, 5);
    display.setTextColor(SSD1306_WHITE);

    display.setCursor(10, 20);
    if (now.hour() < 10) {
      display.print("0");}
    display.print(now.hour(), DEC);

    display.print(':');

    if (now.minute() < 10) {
      display.print("0");}
    display.print(now.minute(), DEC);

    display.print(':');

    if (now.second() < 10) {
      display.print("0");}
    display.println(now.second(), DEC);

    display.display();
  }

  // Check PIR sensor and activate LED if conditions are met
  if ((now.unixtime() % (24 * 60 * 60) >= pirActivationStartTime) || (now.unixtime() % (24 * 60 * 60) <= pirDeactivationEndTime)) {
    if (digitalRead(PIR_PIN) == HIGH) {
      if (currentMillis - previousMillisPIR >= intervalTimeLed) {
        previousMillisPIR = currentMillis;

        // Turn on the LED
        digitalWrite(LED_PIN, HIGH);
        ledStartTime = currentMillis;
        ledOn = true;
        Serial.println("LED turned on");
      }
    }

    // Check if it's time to turn off the LED
    if (ledOn && currentMillis - ledStartTime >= ledDuration) {
      digitalWrite(LED_PIN, LOW);
      ledOn = false;
      Serial.println("LED turned off");
    }
  }
}


I have two issues I can't solve:

  1. I would like the led to fade on in two seconds, and then stay on for the 10 seconds as in one of the first lines, and then fade off in 1 second.

  2. In the code the led is programmed to stay on for 10 seconds but in reallity it stays on for 18 seconds. Why? I don't know

Could someone point me in the right direction?

Regards, Peter

And I have just found another issue: When the start time is set to 18.00 hrs, at 17.05 hrs the LED also switches on. So This is not working as it should.

What do you observe now?
[edit] The extra 8 seconds might be from the PIR timeout.

Hi,

as I described: The time on the Oled display says it is 17.19 hrs, which is correct.
The start time is 18.00 hrs. Only after the start time the led should turn on.
But it turns on just now.

Is see this in the monitor:

17:19:54.645 -> LED turned on
17:19:55.685 -> LED turned on
17:19:56.675 -> LED turned on
17:19:57.683 -> LED turned on
17:19:58.673 -> LED turned on
17:19:59.679 -> LED turned on
17:20:00.641 -> LED turned on
17:20:01.683 -> LED turned on
17:20:02.649 -> LED turned on
17:20:17.686 -> LED turned off

Is the PIR sensing you as you observe?

I hope I understand you correct:
I have connected the Pir to pin 5, and also connected a DMM. The DMM shows that the pir is active when there is movement and thus the led get's switched on.
I seems that the starttime is overruled somehow.

There is a large jump in the time here (15 seconds).

I simulated your sketch and the timeout (ON to OFF) was 15 seconds.

I know there is a gap, but I dont know where it is coming from.
15 seconds is correct, as it is stated in the setup:

const long ledDuration = 15 * 1000; // 15 seconds in milliseconds

The time on the display keeps showing the correct time.
So, what could I do to solve this?

As I think @xfpd implied, I believe that the input signal from a PIR has a duration of 10-15 seconds from the time it was triggered, which might explain the behaviour you describe. On some sensors the duration can be altered with a built-in preset. Easily tested.

The delay on the pir is about 7 seconds. It's this one.

So, I have connected a DMM to the pir, and when activated I measure 3.3 volts.
The Pir is activated, stays on for 7 seconds, switches on the led, and the led stays on for 19 seconds, despite the programming of 15 seconds.

Rename your loop() function to xloop()... then add this test:

void loop() {
    Serial.print(digitalRead(PIR_PIN)); delay(100);
}

Watch the clock, and you will have the answer.

The programming is telling the LED to stay on for 19 seconds. You are trying to find the part of your program that is adding the 4 seconds.

How are you working around the PIR without triggering it?

Do you mean this?

Would you copy/paste the text of that image? (the OUTPUT part... sorry)

[edit] I see the "1" and "0" from the PIR activation. Each one was 100ms, so 10 "1s" = 1seconds, and using a measuring stick on the image, you have "7 seconds" of "1s"

I had to stare at this

  if ((now.unixtime() % (24 * 60 * 60) >= pirActivationStartTime) || (now.unixtime() % (24 * 60 * 60) <= pirDeactivationEndTime)) {
 

to see it should work, but only because the starting time comes later in the day than the stopping time.

And all it would take if that were not the case would be to test using logical and instead.

But this error you see is odd, I suggest using 24L to make the calculation a long integer, or better yet overall make a constant for yourself up top, viz:

const long secondsPerDay = 24L * 60 *60;

And use that everywhere. In either case, if this flaw is still there, it will be something interesting that doesn't jump up and make itself known.

@xfpd's idea of just watching the PIR is good, and it looks like you did it a harder way without using the Arduino.

I like to use a LED that directly reports what the sensor reading is as a first step in getting to know any sensor.

The timing can all be done in software, you can just key off the changes. Make the PIR reporting as fast as it can be, and just filter (ignore) additional changes for your own adjustable period.

It would use "state change detection" as the basis for seeing activity.

a7

The output you recieved showing "LED on" four or eight times is where the extra seconds originate... the PIR continued to trigger during its timeout.

Wrap the if (LedOn) with an if (LedOff) - so you only turn the LED ON one time... like this...

        // Turn on the LED
        if (ledOn == false) {
        digitalWrite(LED_PIN, HIGH);
        ledStartTime = currentMillis;
        ledOn = true;
        Serial.println("LED turned on");
        }
1 Like

Ah, sorry, I didn't excatly know what you meant.
But you are right, there are 87 1's, so about 8.7 seconds.
The manufacturere says: Delay time: 8 s (± 30%)

I borrowed this piece of software and hoped it worked.

I want to turn on a led(strip) after detection and between 9 pm and 9 am. Thus during the night.
That's why the start time is later than the stop time. But is "goes over the night". How do you say this (English is not my native language).

And when the project is finished, the led-strip should be on for 4 minutes or so, so the difference between 15 and 23 seconds is not important anymore.

Well my English is pretty good and I have no idea. :expressionless:

I don't know how you are testing this, so you may already use a line in setup() like

  rtc.adjust(DateTime(2014, 1, 21, 3, 0, 0));

to make it think it is any date and time. Easier than waiting or staying up all night or whatever.

Is the 5 PM undesirable behaviour still an issue?

a7

Hi @peterspie ,

feel free to have a look at this sketch

/*
  Forum: https://forum.arduino.cc/t/problems-with-this-code/1220732
  Wokwi: https://wokwi.com/projects/389010606799634433

  2024/02/06
  ec2021
*/

#define WOKWI


#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
#include <RTClib.h>


#define SCREEN_WIDTH 128
#define SCREEN_HEIGHT 64
#define SSD1306_I2C_ADDRESS 0x3C

#define PIR_PIN 5
#define LED_PIN 3

Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire);
#ifdef WOKWI
RTC_DS1307 rtc;
#else
RTC_DS3231 rtc;
#endif

unsigned long previousMillisTime = 0;
unsigned long ledStartTime = 0;
bool ledOn = false;
const long intervalTime = 1000;
const long ledDuration = 10 * 1000; // 10 seconds in milliseconds

struct timeType {
  private:
    int hour;
    int minute;
    unsigned long totalSeconds;
  public:
    void set(int Hour, int Minute) {
      hour = Hour;
      minute = Minute;
      totalSeconds = (hour * 60 + minute) * 60;
    }
    unsigned long inSeconds(){
      return totalSeconds;
    };
};

timeType pirActive;
timeType pirDeactive;

const int LED_BRIGHTNESS = 64; // LED brightness (0-255)

unsigned long currentMillis;
DateTime now;
boolean pirIsActive = false;

void setup() {
  Serial.begin(115200);
  pirActive.set(13, 30);
  pirDeactive.set( 8, 55);
  pinMode(PIR_PIN, INPUT);
  pinMode(LED_PIN, OUTPUT);

  if (!rtc.begin()) {
    Serial.println("Unable to find RTC");
    while (1);
  }

#ifndef WOKWI
  if (rtc.lostPower()) {
    Serial.println("RTC lost power, setting the time!");
    rtc.adjust(DateTime(F(__DATE__), F(__TIME__)));
  }
#endif
  display.begin(SSD1306_I2C_ADDRESS, SSD1306_I2C_ADDRESS);
  display.display();
  display.clearDisplay();
}

void loop() {
  now = rtc.now();
  handleTime(); // Display time every second
  checkPIR(); // Check PIR sensor and activate LED if conditions are met
}

void handleTime() {
  if (millis() - previousMillisTime >= intervalTime) {
    checkPirTime();
    previousMillisTime = millis();
    display.clearDisplay();
    display.setTextSize(2, 5);
    display.setTextColor(SSD1306_WHITE);
    display.setCursor(10, 20);
    printWithLeadingZero(now.hour(), true);
    printWithLeadingZero(now.minute(), true);
    printWithLeadingZero(now.second(), false);
    display.display();
  }
}

void printWithLeadingZero(int number, boolean displayColon) {
  if (number < 10) {
    display.print("0");
  }
  display.print(number, DEC);
  if (displayColon) {
    display.print(":");
  }
}

void checkPirTime() {
  unsigned long timeInSeconds = (now.hour() * 60 + now.minute()) * 60;
  byte mode;
  if (pirDeactive.inSeconds() < pirActive.inSeconds()) {
    mode = 0;  // Deactivation is earlier than activation
  } else {
    mode = 1;  // Deactivation is later than activation
  }
  if (pirDeactive.inSeconds() == pirActive.inSeconds()) {
    mode = 2; // Deactivation is at the same time as activation
  }
  switch (mode) {
    case 0:  // Deactivation earlier
      pirIsActive = !(timeInSeconds >= pirDeactive.inSeconds() &&
                      timeInSeconds < pirActive.inSeconds());
      break;
    case 1:  // Deactivation later
      pirIsActive =  (timeInSeconds >= pirActive.inSeconds() &&
                      timeInSeconds <  pirDeactive.inSeconds());
      break;
    case 2:  // At the same time ... ?!! Set PIR always active
      pirIsActive = true;
      break;
  }
  if (!pirIsActive && ledOn) {
    digitalWrite(LED_PIN, LOW);
    ledOn = false;
  }
}

void checkPIR() {
  if (pirIsActive && digitalRead(PIR_PIN) == HIGH && !ledOn) {
    digitalWrite(LED_PIN, HIGH);
    ledStartTime = millis();
    ledOn = true;
    Serial.println("LED turned on");
  }
  if (ledOn && millis() - ledStartTime >= ledDuration) {
    digitalWrite(LED_PIN, LOW);
    ledOn = false;
    Serial.println("LED turned off");
  }
}

You can check it out on Wokwi:

https://wokwi.com/projects/389010606799634433

I use the following method to find out whether the PIR shall be active or not:

void checkPirTime() {
  unsigned long timeInSeconds = (now.hour() * 60 + now.minute()) * 60;
  byte mode;
  if (pirDeactive.inSeconds() < pirActive.inSeconds()) {
    mode = 0;  // Deactivation is earlier than activation
  } else {
    mode = 1;  // Deactivation is later than activation
  }
  if (pirDeactive.inSeconds() == pirActive.inSeconds()) {
    mode = 2; // Deactivation is at the same time as activation
  }
  switch (mode) {
    case 0:  // Deactivation earlier
      pirIsActive = !(timeInSeconds >= pirDeactive.inSeconds() &&
                      timeInSeconds < pirActive.inSeconds());
      break;
    case 1:  // Deactivation later
      pirIsActive =  (timeInSeconds >= pirActive.inSeconds() &&
                      timeInSeconds <  pirDeactive.inSeconds());
      break;
    case 2:  // At the same time ... ?!! Set PIR always active
      pirIsActive = true;
      break;
  }
  if (!pirIsActive && ledOn) {
    digitalWrite(LED_PIN, LOW);
    ledOn = false;
  }
}

There are three possibilities for activation and deactivation per day:

  • case 0 : Deactivation is earlier than Activation
    PIR is inactive between deactivation time and activation time
  • case 1 : Deactivation is later than Activation
    PIR is active after activation time and before deactivation time
  • case 2: Activation and Deactivation have been assigned the same time
    PIR is always active (per my personal definition ... :wink: Maybe changed.)

timeInSeconds is just the day time in seconds to be compared with activation and deactivation time in seconds. It is automatically calculated when the function timeType::set() is used.

I made only a few tests but it seems to work alright.

Good luck!

(Sorry, there was a flaw in the first version, I had to change an || by an && in case 1 ...)

I test in the following way:
The time is now 21.38
I set the start time at 21.55
I upload the code
I make some movement to trigger the pir
I see the DMM says then 3.3 volt
And I observe the led.
While the led shouldn't turn on before start time, it just does.