convert countDown from sec to ms

H friends. can anyone help me to convert the counteDown from second to millisecond in below sketch ?
thanks

#include <SPI.h>
#include "nRF24L01.h"
#include "RF24.h"
#include <avr/sleep.h>

int transmitterId = 0;

RF24 radio(9, 10);
const uint64_t pipes[3] = { 0xF0F0F0F0E1LL, 0xF0F0F0F0E2LL, 0xF0F0F0F0E3LL };

const byte pirPin = 2;
const byte relayPin = 7;
const byte calibrationTime = 2;
boolean alarmState = 0;
byte countDown = 0;

void setup() {
  Serial.begin(9600);
  pinMode(pirPin, INPUT);
  attachInterrupt(0, wakeUpNow, LOW);
  transmitterId = 64;
  radio.begin();
  radio.setPayloadSize(2);
  radio.setDataRate(RF24_250KBPS);
  radio.openWritingPipe(pipes[1]);
  digitalWrite(relayPin, 0);
  pinMode(relayPin, OUTPUT);
  Serial.print(F("calibrating sensor "));
  for (int i = 0; i < calibrationTime; i++)
  {
    Serial.print(F("."));
    delay(1000);
  }
  Serial.println(F(" done"));
  Serial.println(F("SENSOR ACTIVE"));
  Serial.println(F("-------------"));
  delay(5000);
  sleepNow();
}

void wakeUpNow()
{
}

void loop() {
  if (digitalRead(pirPin) && !alarmState)
  { // PIR active and no alarm
    radio.powerUp();
    digitalWrite(relayPin, 1); // relay on
    delay(250);
    digitalWrite(relayPin, 0);
    Serial.println("motion detected");
    Serial.println("Relay ON");
    countDown = 5; // seconds
    alarmState = HIGH; // remember it
  }

  if (countDown == 0 && alarmState)
  { // end of countdown and alarm
    digitalWrite(relayPin, 0); // relay off
    Serial.println(F("Lights off"));
    Serial.println(F("Relay OFF"));
    Serial.println(F("---------"));
    alarmState = LOW; // remember it
    sleepNow();
  }

  if (countDown > 0)
  {
    if (digitalRead(pirPin) == HIGH)
    { countDown = 5; // seconds
      Serial.println(F("motion detected"));
    }
    if (digitalRead(pirPin) == LOW)
    {
      Serial.println(F("motion ended"));
    }
    // if countdown is still running
    Serial.println(countDown);
    radio.write(&transmitterId, 1);
    if (countDown < 5)
    { // blink last 4sec
      for (int i = 0; i < 3; i ++)
      {
        delay(200);
      }
    }
    else delay(1000); // no blink
    countDown --;
  }
}

void sleepNow()
{
  set_sleep_mode(SLEEP_MODE_PWR_DOWN);
  sleep_enable();
  attachInterrupt(0, wakeUpNow, RISING);
  sleep_mode();
  sleep_disable();
  detachInterrupt(0);
}
else delay(1000);

This is where the program sits for each second. The problem is, you can't just change 1000 to 1, as your blinking is also going on... first write this code to blink without delay, and do everything without delay. Then you will be able to change the interval from seconds to milliseconds.

Perehama:

else delay(1000);

This is where the program sits for each second. The problem is, you can’t just change 1000 to 1, as your blinking is also going on… first write this code to blink without delay, and do everything without delay. Then you will be able to change the interval from seconds to milliseconds.

Hi thanks for reply. is another way to detect pirPin high faster than 1 sec ? my problem is the pirPin or any other external interrupt detected in 1 second. any way to change it to 50ms ? thanks.

if (countDown > 0)
 {
   if (digitalRead(pirPin) == HIGH)
   { countDown = 5; // seconds
     Serial.println(F("motion detected"));
   }
   if (digitalRead(pirPin) == LOW)
   {
     Serial.println(F("motion ended"));
   }
   // if countdown is still running
   Serial.println(countDown);
   radio.write(&transmitterId, 1);
   if (countDown < 5)
   { // blink last 4sec
     for (int i = 0; i < 3; i ++)
     {
       delay(200);
     }
   }
   else delay(1000); // no blink
   countDown --;
 }
}

ErfanDL:
Hi thanks for reply. is another way to detect pirPin high faster than 1 sec ?

Yes, go through the blink without delay examples and tutorials, and remove all delay() calls from your code. These calls keep the processor from executing the other parts of your code for the delay interval.

Perehama:
Yes, go through the blink without delay examples and tutorials, and remove all delay() calls from your code. These calls keep the processor from executing the other parts of your code for the delay interval.

I'm noob in programming can you do it for me ?
thanks :cry: :cry: :cry:

ErfanDL:
I'm noob in programming can you do it for me ?
thanks :cry: :cry: :cry:

instead of

doFoo();
delay(1000);

try

unsigned long blinkWithoutDelay;
const unsigned long blinkInterval = 1000UL;
void setup() {
}
void loop() {
if (millis() >= blinkWithoutDelay + blinkInterval) {
doFoo();
blinkWithoutDelay = millis();
}
}

No, I wouldn't do it for you, noob or not.

Perehama:
instead of

doFoo();

delay(1000);



try


unsigned long blinkWithoutDelay;
const unsigned long blinkInterval = 1000UL;
void setup() {
}
void loop() {
if (millis() >= blinkWithoutDelay + blinkInterval) {
doFoo();
blinkWithoutDelay = millis();
}
}




No, I wouldn't do it for you, noob or not.

thanks I write the code and it’s worked but there is a problem, some times the external interrupt can’t reset the timer can you help me ? thanks

#include <SPI.h>
#include "nRF24L01.h"
#include "RF24.h"
#include <avr/sleep.h>
#define timeSeconds 15

RF24 radio(9, 10);

int transmitterId = 0;

const uint64_t pipes[3] = { 0xF0F0F0F0E1LL, 0xF0F0F0F0E2LL, 0xF0F0F0F0E3LL };
const byte pirPin = 2;
const byte relayPin = 7;

unsigned long now = millis();
unsigned long lastTrigger = 0;

boolean startTimer = false;
boolean alarmState = 0;

void detectsMovement() {
 //startTimer = true;
 lastTrigger = millis();
}

void setup() {
 Serial.begin(9600);
 pinMode(pirPin, INPUT_PULLUP);
 attachInterrupt(digitalPinToInterrupt(pirPin), detectsMovement, RISING);
 transmitterId = 64;
 radio.begin();
 radio.setPayloadSize(2);
 radio.setDataRate(RF24_250KBPS);
 radio.openWritingPipe(pipes[1]);
 digitalWrite(relayPin, 0);
 pinMode(relayPin, OUTPUT);
 delay(5000);
 sleepNow();
}

void loop() {
 // Current time
 if (digitalRead(pirPin) && !alarmState)
 {
   radio.powerUp();
   Serial.println("MOTION DETECTED!!!");
   digitalWrite(relayPin, HIGH);
   delay(250);
   digitalWrite(relayPin, LOW);
   startTimer = true;
   //lastTrigger = millis();
   radio.write(&transmitterId, 1);
   alarmState = HIGH;
 }
 now = millis();
 // 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(relayPin, LOW);
   alarmState = LOW;
   startTimer = false;
   delay(1000);
   sleepNow();
 }
}

void sleepNow()
{
 set_sleep_mode(SLEEP_MODE_PWR_DOWN);
 sleep_enable();
 attachInterrupt(digitalPinToInterrupt(pirPin), detectsMovement, RISING);
 sleep_mode();
 sleep_disable();
 detachInterrupt(pirPin);
}

delay() is Evil Incarnate and should never be used outside the most trivial blink sketches. Almost any real program will be done in by it.

Even though an interrupt can be taken during a delay(), if all it does is set a flag, returning from the interrupt just goes back to the delay() loop, so you don’t get to see the flag until the next entry.

The solutions proposed look perfectly reasonable.

Note that any variable shared between the ISR and the rest of the program must be declared ‘volatile’.

volatile bool sawMotion = false; // 'volatile' is critical!

void MotionSeen()
{
 sawMotion = true;
}

void loop()
   {
     if(sawMotion)
       dealWithMotion(); // sets sawMotion back to false
     if(mills() < start + delta)
       return;
     start = millis();
     ledon = !ledon;
     digitalWrite(ledpin, (ledon ? HIGH : LOW));
    }

There is a problem here, which volatile does not solve

ISR: sawMotion = true;
loop: if(sawMotion) handleMotion();
handleMotion: do stuff
      *
      sawMotion = false;
      return;

What happens if, right before sawMotion = false; (which I marked with a *) an interrupt came in again, and set sawMotion to true? This is called a “race condition” and can be a nightmare to discover, and a worse one to fix.

What is needed is an “atomic operation”, typically a way to do ++ and – as a single atomic action. I have not studied the AVR instruction set to know if this is possible. So what I would do is

volatile uint16_t motionCounter = 0;

void MotionDetected()
   {
     atomic_increment(motionCounter);
   }

void loop()
   {
    if(motionCounter > 0)
      handleMotion();
    // ... as before
   }

void handleMotion()
{
  ...
  atomic_decrement(motionCount);
 }

A bit of research (a 1-bit, actually, google “atomic increment avr”) reveals that there is a library at avr-libc: <util/atomic.h> Atomically and Non-Atomically Executed Code Blocks which handles this.

flounder:
delay() is Evil Incarnate and should never be used outside the most trivial blink sketches. Almost any real program will be done in by it.

Even though an interrupt can be taken during a delay(), if all it does is set a flag, returning from the interrupt just goes back to the delay() loop, so you don’t get to see the flag until the next entry.

The solutions proposed look perfectly reasonable.

Note that any variable shared between the ISR and the rest of the program must be declared ‘volatile’.

volatile bool sawMotion = false; // 'volatile' is critical!

void MotionSeen()
{
sawMotion = true;
}

void loop()
  {
    if(sawMotion)
      dealWithMotion(); // sets sawMotion back to false
    if(mills() < start + delta)
      return;
    start = millis();
    ledon = !ledon;
    digitalWrite(ledpin, (ledon ? HIGH : LOW));
    }




There is a problem here, which `volatile` does *not* solve


ISR: sawMotion = true;
loop: if(sawMotion) handleMotion();
handleMotion: do stuff
      *
      sawMotion = false;
      return;




What happens if, right before `sawMotion = false;` (which I marked with a *) an interrupt came in again, and set `sawMotion` to `true`? This is called a "race condition" and can be a nightmare to discover, and a worse one to fix.

What is needed is an "atomic operation", typically a way to do ++ and -- as a single atomic action. I have not studied the AVR instruction set to know if this is possible. So what I would do is


volatile uint16_t motionCounter = 0;

void MotionDetected()
  {
    atomic_increment(motionCounter);
  }

void loop()
  {
    if(motionCounter > 0)
      handleMotion();
    // … as before
  }

void handleMotion()
{
  …
  atomic_decrement(motionCount);
}




There is a library at https://www.nongnu.org/avr-libc/user-manual/group__util__atomic.html which handle this

thanks. I don’t good understand how to do it with my code ? do you mean use sawMotion = false; as boolean alarmState = 0; ?

Not sure what you are doing with alarmState = 0;. alarmState is a boolean and should be set to either true or false. You can set an integer variable to 0, but using a numeric value for a boolean is silly, even if the compiler accepts it and figures out how to do the right thing.

sawMotion was a name I chose because I couldn't see your code when I was typing, but yes, it is the same as your alarmState.

I have no idea what countdown is doing; comment-free code is hard to understand.

And I don't know what you mean by the interrupt code can't reset the timer. Your interrupt code does exactly nothing, and I suspect that doing nothing won't change any state. I don't even know what you mean by 'timer'. You will either have to comment the code well or explain it better.

flounder:
Not sure what you are doing with alarmState = 0;. alarmState is a boolean and should be set to either true or false. You can set an integer variable to 0, but using a numeric value for a boolean is silly, even if the compiler accepts it and figures out how to do the right thing.

sawMotion was a name I chose because I couldn't see your code when I was typing, but yes, it is the same as your alarmState.

I have no idea what countdown is doing; comment-free code is hard to understand.

And I don't know what you mean by the interrupt code can't reset the timer. Your interrupt code does exactly nothing, and I suspect that doing nothing won't change any state. I don't even know what you mean by 'timer'. You will either have to comment the code well or explain it better.

the alarmstate saved the last time pin HIGH or LOW. I mean from time is when there is no interrupt for 15 second reset the timer for detect the next interrupt if appear.
in this sketch from the external interrupt the led turn on for 250ms then wait for 15 second if there is no interrupt reset the timer again.

I was able to make a little more sense of your code. What you have is a very bad computational model of what is going on. You are executing your code sequentially, with delay() statements added to sequence it temporally.

What you want is a state-machine model, where each time you enter the loop you take an action based on the current state, and either leave the state alone or set a new state

static const uint32_t RADIO_WARMUP_TIME = 250;
static const uint32_t RELAY_ON_TIME = 5000;

static uint32_t next;  // the next event time for the current state

typedef enum {IDLE, ALARMED, WARMING_UP, RELAY_ON } States;

States state = IDLE;
/******************************************************************
* WakeUpNow
*
* Notes:
*    This ISR does nothing; it just returns, and if the machine was asleep, it wakes up
*    and resumes running
******************************************************************/
void WakeUpNow() {}

/*****************************************************************
* loop
*****************************************************************/
void loop()
{
  switch(state)
    { /* state */
      case IDLE:
          if(digitalRead(pirPin))
            { /* motion seen *;
             state = ALARMED;
             break;
            } /* motion seen */
          SleepNow();
          break;
      case ALARMED:
             radio.powerUp();
             digitalWrite(relayPin, 1); // relay on
             next = millis() + RADIO_WARMUP_TIME;
             state = WARMING_UP;
             break;
      case WARMING_UP:
             if(millis() < next)
                 break;
             digitalWrite(relayPin, 0);
             Serial.println("motion detected");
             Serial.println("Relay ON");
             state = RELAY_ON;
             next = millis() + RELAY_ON_TIME;
             break;
      case RELAY_ON:
             if(millis() < next)
                break;
              // relay has been on long enough
             digitalWrite(relayPin, 0); // relay off
             Serial.println(F("Lights off"));
             Serial.println(F("Relay OFF"));
             Serial.println(F("---------"));
             state = IDLE;
             break;
    } /* state */

}

This code is simple, easy to understand, easy to maintain, easy to extend. Adding the blinking light is left as An Exercise For The Reader

There is also a basic problem with your code. You are using an edge-triggered interrupt (RISING) which means that if the sensor detects motion just before SleepNow, you won't see the event until the PIR sensor resets and can produce another rising edge. If the motion continues, there is no rising edge, and you get no interrupts. So you could have a large group of square dancers who enter just before the SleepNow() call, and they could party for hours. I suggest a level-triggered interrupt. You can detach it when you go from IDLE to ALARMED, and it doesn't matter how the motion is handled; if there is still motion just as you enter SleepNow(), the level-triggered interrupt ensures that it will be seen.

flounder:

next = millis() + RADIO_WARMUP_TIME;

@flounder, my first thought was that this could be solved with a Finite State Machine. However, you should investigate the use of millis(). Don't calculate a millis value in the future, don't compare millis() with a value. That causes a problem during the rollover.
I have examples for millis() and also the combination of millis() and a Finite State Machine.

@ErfanDL, we see such questions often in the forums. Someone has a working sketch and want to change a small thing. That small thing means the whole sketch has to be re-written.
When you continue to use Arduino, then someday you have to learn how to use millis() and avoid delay(). The start is the Blink Without Delay example sketch that has already been mentioned by Perehama.
Using millis() with a Finite State Machine is something for more advanced Arduino users. Maybe this problem is a good moment to start with it.

@ErfanDL, I sense that you are too much focussed on details. Details can be filled in later. The most important thing is to lean back and think about the global structure of the code. What should be done and how does it interact with other parts.

thanks for all. I working on the code and report if I successes.

@flounder @Koepel finally problem solved with below sketch:

#include <SPI.h>
#include "nRF24L01.h"
#include "RF24.h"
#include <avr/sleep.h>

RF24 radio(9, 10);

int transmitterId = 0;

const uint64_t pipes[3] = { 0xF0F0F0F0E1LL, 0xF0F0F0F0E2LL, 0xF0F0F0F0E3LL };
const byte pirPin = 2;
const byte relayPin = 7;

int calibrationTime = 30;
long unsigned int lowIn;
long unsigned int pause = 10000;
boolean lockLow = true;
boolean takeLowTime;
int PIRValue = 0;

void detectsMovement() {

}

void setup() {
 Serial.begin(9600);
 pinMode(pirPin, INPUT);
 attachInterrupt(digitalPinToInterrupt(pirPin), detectsMovement, RISING);
 transmitterId = 64;
 radio.begin();
 radio.setPayloadSize(2);
 radio.setDataRate(RF24_250KBPS);
 radio.openWritingPipe(pipes[1]);
 digitalWrite(relayPin, 0);
 pinMode(relayPin, OUTPUT);
 delay(5000);
 sleepNow();
}

void loop() {
 PIRSensor();
}

void PIRSensor() {
 if (digitalRead(pirPin) == HIGH) {
   if (lockLow) {
     PIRValue = 1;
     lockLow = false;
     digitalWrite(relayPin, HIGH);
     radio.powerUp();
     delay(250);
     digitalWrite(relayPin, LOW);
     radio.write(&transmitterId, 1);
     Serial.println("Motion detected.");
     delay(50);
   }
   takeLowTime = true;
 }
 if (digitalRead(pirPin) == LOW) {
   if (takeLowTime) {
     lowIn = millis(); takeLowTime = false;
   }
   if (!lockLow && millis() - lowIn > pause) {
     PIRValue = 0;
     lockLow = true;
     digitalWrite(relayPin, LOW);
     Serial.println("Motion ended.");
     delay(50);
     sleepNow();
   }
 }
}

void sleepNow()
{
 set_sleep_mode(SLEEP_MODE_PWR_DOWN);
 sleep_enable();
 attachInterrupt(digitalPinToInterrupt(pirPin), detectsMovement, RISING);
 sleep_mode();
 sleep_disable();
 detachInterrupt(pirPin);
}

Yes, I waas ignoring the 50-day rollover problem. In my own work, I use a 64-bit timestamp, uint64_t, and deal with the rollover by seeing if the current millis() is less than the last millis(), and if so, adjusting it appropriately.

typedef uint64_t millis64_t;

// Note: this is intended to be a singleton class
// TODO: think about how to remove the singleton limitation
class millis64 {
protected;
     millis64_t now64;
     uint32_t lastnow;

public;
     millis64() { lastnow = millis(); now64 = lastnow; }

     millis64_t millis()
       { 
        uint32_t now = millis():
        if(now < lastnow)
           { /* rolled over */
             now64 += (0xFFFFFFFFL) - lastnow + 1;  // add what got us to rollover
           } /* rolled over */
        now64 += now;
        return now64;
       } // millis64
};

I typed this in from memory, since I don’t have access to my source file as I’m typing this.