I am trying to detect when continuous motion has been triggered from a PIR sensor for more than 8 seconds. Here is what I have. When sensor is LOW 'no motion...' is displayed, then short motion fires the first '< 8 sec' IF statement. When sensor returns to LOW - no motion is displayed as it should but then when motion is detected a second time, the code seems to freeze and nothing happens.
unsigned long startMillis;
boolean timingFlag = false;
const int buttonPin = 2;
int buttonState = 0;
void setup() {
pinMode(buttonPin, INPUT);
Serial.begin(19200);
delay(500);
}
void loop() {
buttonState = digitalRead(buttonPin);
if (buttonState == HIGH && millis() - startMillis <= 8000UL)
{
Serial.println("Motion Detected but less than 8");
delay(1000);
//the PIR timed out with in the three seconds so cancel timing
timingFlag = false; //disable timing
}
if (buttonState == LOW)
{
Serial.println("No Motion...");
delay(1000);
timingFlag = true; //enable timing
}
//when nine seconds have gone by with consistant detection do something
if (timingFlag == false && millis() - startMillis >= 9000UL)
{
//There has now been nine seconds of constant PIR detection
Serial.println("Motion Detected and greater than 9 sec");
delay(1000);
//Do Something
}
}
There is one very obvious problem with your current code, as Scheff already mentioned in the comments: You never actually set your startMillis to anything, so they are probably (but not necessarily) always 0.
This means, that the statement if (buttonState == HIGH && millis() - startMillis <= 8000UL) will always be false after 8000 ms (until millis() flows over, after around 50 days* ), so timingFlag will never be reset to false after that. This ultimately leads to your "freezing" situation.
I tried to find a good place to set the startMillis in your code, but I honestly find it a little confusing, so I allowed myself to rewrite your logic, hope you don't mind. (Please note that I also changed the variable names from button to detector, since it seemed more fitting to me):
(This version triggers at the transitions from HIGH to LOW)
// define the threshold, after which an action shall be triggered
const int detectionThreshold = 8000;
const int detectorPin = 2;
unsigned long startTime = 0;
int lastDetectorState = LOW;
void setup() {
pinMode(detectorPin, INPUT);
Serial.begin(19200);
delay(500);
}
void triggerDetectionAction(){
// do whatever needs to be done after 8 seconds of motion in here
}
void loop() {
int currentDetectorState = digitalRead(detectorPin);
// if detector is low, no motion is detected
if( currentDetectorState == LOW ){
// when the detector is LOW, we want to check if the last state was HIGH
// because then we just arrived at the transition from HIGH to LOW =>
// "something was detected" to "there is no longer something detected"
if( lastDetectorState == HIGH ){
// then, we can get the total duration, the detection lasted
unsigned long detectionDuration = millis() - startTime;
// and print it for easier debugging
Serial.print("Detection ended after ");
Serial.print(detectionDuration);
Serial.println(" milliseconds");
// finally, we check if the durations was more than
// or equal to our threshold
if( detectionDuration >= detectionThreshold ){
// and trigger stuff if necessary
triggerDetectionAction();
}
}
// if last detector state was LOW too,
// we don't want to do anything
}else{
// here we wan't to check for the transition of LOW to HIGH,
// so we check our last detector state
if( lastDetectorState == LOW ){
// if we caught the transition,
// set the start time to the current millis
startTime = millis();
// we could also set an indicator LED
// or Serial.print something here
Serial.println("Detection started");
}
// otherwise, we don't wan't to do anything
}
// finally, we save our current state into the last state,
// so we have it available in the next loop
lastDetectorState = currentDetectorState;
// do your other loop stuff here
}
Please note that I couldn't test the code at the time writing, so there may be (syntax) errors
*More about millis and overflow here: https://www.arduino.cc/reference/en/language/functions/time/millis/
Update: This version will trigger immediately when the threshold is reached. It also includes an example how to trigger an action once and every loop after the threshold was reached.
// define the threshold, after which an action shall be triggered
const int detectionThreshold = 8000;
const int detectorPin = 2;
unsigned long startTime = 0;
int lastDetectorState = LOW;
bool actionTriggered = false;
void setup() {
pinMode(detectorPin, INPUT);
Serial.begin(19200);
delay(500);
}
void triggerOnce(){
// this will be called once, when the threshold is reached
}
void triggerEveryLoop(){
// this will be called every loop, after the threshold was reached
// for as long as the detector stays high
}
void loop() {
int currentDetectorState = digitalRead(detectorPin);
if( currentDetectorState == LOW ){
if( lastDetectorState == HIGH ){
// since we want to trigger immediately when the threshold is reached,
// we actually don't need this transition any longer.
// We can still keep it for debugging reasons thought.
// If you don't need this, you can simply remove the entire block
unsigned long detectionDuration = millis() - startTime;
Serial.print("Detection ended after ");
Serial.print(detectionDuration);
Serial.println(" milliseconds");
}
}else{
// Check for LOW => HIGH transition change
if( lastDetectorState == LOW ){
// if we caught the transition,
// set the start time to the current millis
startTime = millis();
// and reset the flag
actionTriggered = false;
// we could also set an indicator LED
// or Serial.print something here
Serial.println("Detection started");
}else{
// otherwise we want to check the duration
unsigned long detectionDuration = millis() - startTime;
// and trigger immediatley when the threshold is reached
if( detectionDuration >= detectionThreshold ){
// as long as it wasn't already triggered before
if( !actionTriggered ){
Serial.println("Threshold reached, triggering");
// now we also need to set a flag, so we know we already triggerd this action once
actionTriggered = true;
triggerOnce();
}
// we can also do something every loop
// this can be handy for e.g. blinking a light or playing a sound or something
triggerEveryLoop();
}
}
}
// finally, we save our current state into the last state,
// so we have it available in the next loop
lastDetectorState = currentDetectorState;
// do your other loop stuff here
}
Related
I want to count milliseconds on my Pico to make nonblocking code in C++.
Arduino has the millis() function, but after looking though all the example programs and scouring the internet I couldn't find anything like that for the Pico using C++.
This is the BlinkWithoutDelay sketch from the Arduino examples that shows what I want to do:
const int ledPin = LED_BUILTIN;// the number of the LED pin
// Variables will change:
int ledState = LOW; // ledState used to set the LED
// Generally, you should use "unsigned long" for variables that hold time
// The value will quickly become too large for an int to store
unsigned long previousMillis = 0; // will store last time LED was updated
// constants won't change:
const long interval = 1000; // interval at which to blink (milliseconds)
void setup() {
// set the digital pin as output:
pinMode(ledPin, OUTPUT);
}
void loop() {
// here is where you'd put code that needs to be running all the time.
// check to see if it's time to blink the LED; that is, if the difference
// between the current time and last time you blinked the LED is bigger than
// the interval at which you want to blink the LED.
unsigned long currentMillis = millis();
if (currentMillis - previousMillis >= interval) {
// save the last time you blinked the LED
previousMillis = currentMillis;
// if the LED is off turn it on and vice-versa:
if (ledState == LOW) {
ledState = HIGH;
} else {
ledState = LOW;
}
// set the LED with the ledState of the variable:
digitalWrite(ledPin, ledState);
}
}
I decided to go with a repeating timer as suggested by some comments and the examples that used it really difficult to understand for me. So I'll try to explain it the best I can.
*Note that I don't really understand some of the values being passed into functions but I know enough to make this work. Please feel free to correct me
#include "pico/stdlib.h"
bool led_state;
//Function Called by the timer
bool timer_callback( repeating_timer_t *rt )
{
//Blinks the LED
led_state = !led_state;
gpio_put(25, led_state);
return (true);
}
int main(){
//From what I can tell this creates an identifier for the timer
static repeating_timer_t timer;
stdio_init_all();
//GPIO setting (GPIO_25 is the LED pin on the pico)
gpio_init(25);
gpio_set_dir(25, GPIO_OUT);
//Sets a repeating timer to call a function every 1000 milliseconds
//add_repeating_timer_ms( int interval_in_milliseconds, ?&function to call?, ???, ?timer identifier? );
add_repeating_timer_ms( 1000, &timer_callback, NULL, &timer );
while(true){
//Blinks LED briefly at a different interval than the repeating timer
//This is to show that my code isn't blocked
gpio_put(25, !led_state);
sleep_ms(50);
gpio_put(25, led_state);
sleep_ms(450);
}
return 0;
}
What is my Issue with the button, it dont work correctly and I am wondering why. I dont get it. The Button is connected over the 5V Then a 10k resistor and connected with a wire to the ground. I think the isr is correctly implemented. And the function is also correct. But I do not really get whats the issue with the Button is. I tried a capacitor but that didnt also work well... I connected it directly before the the button.
#include <LiquidCrystal.h>
LiquidCrystal lcd(5, 6, 9, 10, 11, 12);
unsigned long volatile startTime = 0;
unsigned long volatile endTime = 0;
unsigned long volatile sec;
volatile boolean buttonState;
const int button = 2;
long ms;
long timeT;
long z;
void setup() {
Serial.begin(9600);
// put your setup code here, to run once:
pinMode(button, INPUT_PULLUP);
lcd.setCursor(0, 0);
lcd.print("Stopwatch");
attachInterrupt(digitalPinToInterrupt(button), isr, FALLING);
}
void loop() {
if (buttonState == LOW) {
startTime = millis();
}
while (buttonState == HIGH) {
z = (millis() - startTime);
sec = (z / 1000);
ms = z % 1000;
lcd.setCursor(0, 1);
lcd.print(sec);
lcd.print(".");
lcd.print(ms);
lcd.setCursor(9, 1);
lcd.print("Secs");
}
}
//Interrupt Service Routines
void isr() {
buttonState = !buttonState;
}
Buttons are quirky things. It can happen that they send multiple signals (rather, that the contact isn't immediately stable). A condenser might help, but that too can output a wavy signal and trigger multiple "events" and, anyway, it introduces a delay.
So, delay for delay, you could do something like this which should be more controllable:
if (buttonState !== previousState) {
startTime = millis();
previousState = buttonState;
triggered = 0;
} else {
if (0 == triggered) {
if ((millis() - startTime) > threshold) {
triggered = 1;
// Okay, buttonState is now reliably LOW or HIGH
...
}
} else {
// Continuous event (pressed or not-pressed)
}
}
In practice, this should only react when the button has been uninterruptedly pressed or released for "threshold" millis. Assuming BTN is the actual signal from the button without any other attending hardware,
1 2 3 4 5 6
BTN: LLLLLLLLLLLLLLHLHHLHHHLHHHHHHHHHHHHHHHHHHHHHHHHHHHLHHHLLHHLLLLLLLLL
FLT: ___________________________-----------------------------------_____
Time --------------------------------------------------------------->
1 button is physically pressed
2 button is now truly pressed
3 stable button press is detected
4 button begins to be released
5 button is now fully released
6 stable button release is detected
Hoping someone can see what I'm missing as it's gotta be right there staring at me in the face..
I've got this code (below) set up on an ESP32 to spawn a thread that simply monitors the state of a pin connected to a switch. Essentially this code is supposed to wait for the button to be held for 3 seconds and then do something important. The actual input seems to read fine, but for some reason once I've pressed the button, the button state is stuck for like 15 seconds after un-pressing the switch.
For example,
Press the switch, the actualBtnState reads 1, buttonState reads 1 (after 50us),and btnPressTime increments as expected.
Release switch, actualBtnState reads 0, btnState reads 1, and btnPressTime stops incrementing.
After 50us, expecting to see btnState read 0 and then trigger the else or elseif blocks (depending on how long the button was held). Actual results continue to read btnState = 1 and btnPressTime = [whatever the last held time was] for a solid 15 seconds or more. actuyalBtnState reads correctly at 0 this entire time and for some reason lastDebounceTime keeps incrementing?
I should note that this is part of a much larger project, hence the threading. I also can't seem to print anything within the resetBtnCB function as I immediately get a "guru mediation error kernel panic whatever-the-error-is" error and the esp reboots.
Code:
#include <Arduino.h>
#define BUTTON_PIN 27
// Variables will change:
int buttonState; // the current reading from the input pin
int lastButtonState = LOW; // the previous reading from the input pin
unsigned long lastDebounceTime = 0; // the last time the output pin was toggled
unsigned long debounceDelay = 50; // the debounce time; increase if the output flickers
unsigned long buttonPressTime = 0; //Amount of time the button has been held down
unsigned long actualBtnState = 0; //The actual reading from the pin without noise filtering
void resetBtnCB(void *pvParameters)
{
pinMode(BUTTON_PIN, INPUT);
while (true)
{
// read the state of the switch into a local variable:
int reading = digitalRead(BUTTON_PIN);
actualBtnState = reading;
// If the switch changed, due to noise or pressing:
if (reading != lastButtonState)
{
// reset the debouncing timer
lastDebounceTime = millis();
}
unsigned long timeSinceDebounce = millis() - lastDebounceTime;
if (timeSinceDebounce > debounceDelay)
{
// whatever the reading is at, it's been there for longer than the debounce
// delay, so take it as the actual current state:
buttonState = reading;
if (buttonState == HIGH)
{
buttonPressTime += timeSinceDebounce;
}
else if (buttonPressTime > 300)
{
buttonPressTime = 0;
// SUCCESS! Do something important here as we've held the button for x seconds
}
else
{
buttonPressTime = 0;
}
}
// save the reading. Next time through the loop, it'll be the lastButtonState:
lastButtonState = reading;
vTaskDelay(10);
}
}
void setup()
{
Serial.begin(115200);
xTaskCreate(resetBtnCB, "reset_button", 1024, NULL, 10, NULL);
}
void loop()
{
char debug[512];
sprintf(debug, "button state %u, lastD %u, buttonPressTime %u, actualBtnState %u, lastBtnState %u", buttonState, lastDebounceTime, buttonPressTime, actualBtnState, lastButtonState);
Serial.println(debug);
delay(50);
yield();
}
I'm just learning Arduino and i've got a DC Motor & IR Receiver connected. It's working fine if i press the button once but i can't figure out how to keep the motor spinning if i hold the button down as the REPEAT command is the same numbers.
I figured i would store the last code sent and check if the repeat command and last code match but it doesn't seem to be working and can't figure out why.
#include <IRremote.h>
int IRpin = 11; // pin for the IR sensor
IRrecv irrecv(IRpin);
decode_results results;
int lastCode;
void setup() {
// put your setup code here, to run once:
pinMode(7, OUTPUT);
pinMode(8, OUTPUT);
pinMode(9, OUTPUT);
pinMode(13, OUTPUT);
Serial.begin(9600);
irrecv.enableIRIn(); // Enable IR Receiver.
}
void loop() {
// put your main code here, to run repeatedly:
if
(irrecv.decode(&results)) {
Serial.println(results.value);
irrecv.resume();
Serial.println("Last Code is set to: ");
Serial.write(lastCode);
if(results.value== 16748655 || (results.value== 4294967295 && lastCode== 16748655)) // Your ON button value
{
digitalWrite(8, HIGH);
digitalWrite(7, LOW);
analogWrite(9, 255);
delay(1000);
analogWrite(9, 0);
lastCode= 16748655;
}
else if(results.value == 16769055 || (results.value== 4294967295 && lastCode== 16769055)) // Your OFF button value
{
digitalWrite(8, LOW);
digitalWrite(7, HIGH);
analogWrite(9, 255);
delay(1000);
analogWrite(9, 0);
lastCode= 16769055;
}
}
}
A more reliable approach to running the motor until the button is released is to use a "no-code" timeout. That is, if the "no code" state persists for a period longer than the auto-repeat period, then it has been released.
It is not clear in your code what the 1 second analogue pulse is for, but placing long delays in your loop() function makes your system far less responsive. Better to poll the system tick and "do stuff" when it is time to do so. Also magic numbers should be avoided if you want anyone to understand your code and avoid errors in maintenance.
The following uses system tick polling to implement the "no-code" timeout. I have omitted the motor on/off code because it is not clear what you are doing there with the 1 second delays.
#define NO_CODE 0xFFFFFFFFul
#define MOTOR_ON_CODE 0xFF906Ful
#define MOTOR_OFF_CODE 0xFFE01Ful
#define STOP_TIME_MS 250ul // stop after button release for 250ms
void loop( )
{
static unsigned long last_on_time = 0 ;
if( irrecv.decode( &results ) )
{
irrecv.resume() ;
unsigned long code = results.value ;
// If motor off code or no code timeout...
if( code == MOTOR_OFF_CODE ||
(code == NO_CODE && millis() - last_on_time > STOP_TIME_MS) )
{
// Motor off
...
}
else if( code == MOTOR_ON_CODE )
{
// Continuously update last on time while button is held
last_on_time = millis() ;
// Motor on
...
}
}
}
I have included response to the motor-off code, but that may not be necessary, since the motor will be switched off 250ms (or whatever time you choose) after the ON button is released in any case. You might instead have a forward/reverse button and release either to stop:
#define NO_CODE 0xFFFFFFFFul
#define MOTOR_FWD_CODE 0xFF906Ful
#define MOTOR_REV_CODE 0xFFE01Ful
#define STOP_TIME_MS 250ul // stop after button release for 250ms
void loop( )
{
static unsigned long last_on_time = 0 ;
if( irrecv.decode( &results ) )
{
irrecv.resume() ;
unsigned long code = results.value ;
switch( code )
{
case NO_CODE :
{
if( millis() - last_on_time > STOP_TIME_MS )
{
// Motor off
...
}
}
break ;
case MOTOR_FWD_CODE :
{
// Continuously update last on time while button is held
last_on_time = millis() ;
// Motor forward
...
}
break ;
case MOTOR_FWD_CODE :
{
// Continuously update last on time while button is held
last_on_time = millis() ;
// Motor reverse
...
}
break ;
}
}
}
I have an arduino that does mostly data collection and sends it to an ESP8266 over serial. Serial communication to the ESP is not quick as you may know and it depends on a lot of waiting. I have a button and I want to immediately stop any data collection or sending and have it open a door. The door opening takes about 30 seconds. What's the best way to do this?
Not the full code, but it goes something like the below.
Of course this doesn't work because you can't use WHILE or DELAY in an ISR, but I don't know how to restructure it.
attachInterrupt(4 , openadoor, FALLING);
void loop(){
gathersomedata();
senddatatoESP();
if(wait_for_esp_response(2000,"OK")) lightGreenLED();
else lightRedLED();
}
byte wait_for_esp_response(int timeout, const char* term) {
unsigned long t = millis();
bool found = false;
int i = 0;
int len = strlen(term);
while (millis() < t + timeout) {
if (Serial2.available()) {
buffer[i++] = Serial2.read();
if (i >= len) {
if (strncmp(buffer + i - len, term, len) == 0) {
found = true;
break;
}
}
}
}
buffer[i] = 0;
}
void openadoor(){
while (doortimer + dooropentime >= millis() && digitalRead(openbutton) == HIGH && digitalRead(closebutton) == HIGH) {
digitalWrite(DoorOpenRelay, LOW);
}
digitalWrite(DoorOpenRelay, HIGH);
}
TL;DR - see Nick's Answer. :-)
Without the complete code, I can only guess at a few things:
1) You shouldn't wait in an ISR. Even calling millis() is discouraged, as it depends on the Timer0 ISR getting called, which will be prevented as long as you're in your openadoor ISR.
2) In general, the ISR should only do things that are very quick... think microseconds. That's tens to hundreds of instructions, which can be just a few lines of code. Even digitalWrite is almost too slow. If there's more to do, you should just set a volatile flag that is watched in loop. Then loop can do the time-consuming work.
3) Calculating elapsed time must be in this form:
if (millis() - startTime >= DESIRED_TIME)
where startTime is the same type as millis(), a uint32_t:
uint32_t startTime;
You set startTime whereever it's appropriate:
startTime = millis();
This avoids the rollover problem, when millis() rolls over from 232-1 to 0.
4) It looks like you know how to "block" until a certain amount of time has elapsed: the while loop will keep your sketch at that point. If you just change it to an if statement, the Arduino can continue on its way to handle other things.
Because loop happens so quickly, the if statement will check the time very frequently... unless you delay or block somewhere else, like wait_for_esp_response. :-( That while loop should change to an if statement as well. The routine is more like check_for_esp_response.
5) You have to track the state of the door opening and closing process. This is a Finite-State machine problem. Nick has a good description here, too. You can use the enum type to define the states that the door can be in: CLOSED, OPENING, OPENED and CLOSING.
When the OPEN button is pressed, you can look at the state and see if you should start opening it. Then start a timer, turn on the relay and, most importantly, set the state to OPENING. Next time through loop, you can test the state (a switch statement), and for the OPENING case, look at the time to see if it has been long enough. If it has set the state to OPENED. And so on.
If I incorporate all these things into your sketch, it should start to look like this:
volatile bool doorOpenPressed = false;
volatile bool doorClosePressed = false;
static const uint32_t DOOR_OPEN_TIME = 30000UL; // ms
static const uint32_t DOOR_CLOSE_TIME = 30000UL; // ms
static const uint32_t DATA_SAMPLE_TIME = 60000UL; // ms
static uint32_t lastDataTime, sentTime, relayChanged;
static bool waitingForResponse = false;
static uint8_t responseLen = 0;
enum doorState_t { DOOR_CLOSED, DOOR_OPENING, DOOR_OPENED, DOOR_CLOSING };
doorState_t doorState = DOOR_CLOSED;
void setup()
{
attachInterrupt(4 , openadoor, FALLING);
}
void loop()
{
// Is it time to take another sample?
if (millis() - lastDataTime > DATA_SAMPLE_TIME) {
lastDataTime = millis();
gathersomedata();
// You may want to read all Serial2 input first, to make
// sure old data doesn't get mixed in with the new response.
senddatatoESP();
sentTime = millis();
waitingForResponse = true;
responseLen = 0; // ready for new response
}
// If we're expecting a response, did we get it?
if (waitingForResponse) {
if (check_for_esp_response("OK")) {
// Got it!
lightGreenLED();
waitingForResponse = false;
} else if (millis() - sentTime > 2000UL) {
// Too long!
lightRedLED();
waitingForResponse = false;
} // else, still waiting
}
// Check and handle the door OPEN and CLOSE buttons,
// based on the current door state and time
switch (doorState) {
case DOOR_CLOSED:
if (doorOpenPressed) {
digitalWrite(DoorOpenRelay, LOW);
relayChanged = millis();
doorState = DOOR_OPENING;
}
break;
case DOOR_OPENING:
// Has the door been opening long enough?
if (millis() - relayChanged > DOOR_OPEN_TIME) {
digitalWrite(DoorOpenRelay, HIGH);
doorState = DOOR_OPENED;
} else if (!doorOpenPressed && doorClosePressed) {
// Oops, changed their mind and pressed the CLOSE button.
// You may want to calculate a relayChanged time that
// is set back from millis() based on how long the
// door has been opening. If it just started opening,
// you probably don't want to drive the relay for the
// full 30 seconds.
...
}
break;
case DOOR_OPENED:
if (doorClosePressed) {
...
}
break;
case DOOR_CLOSING:
if (millis() - relayChanged > DOOR_CLOSE_TIME) {
...
}
break;
}
}
void openadoor()
{
doorOpenPressed = true;
}
bool check_for_esp_response(const char* term)
{
bool found = false;
if (Serial2.available()) {
// You should make sure you're not running off the end
// of "buffer" here!
buffer[responseLen++] = Serial2.read();
int len = strlen(term);
if (responseLen >= len) {
if (strncmp(buffer + responseLen - len, term, len) == 0) {
found = true;
}
}
}
return found;
}
The key is that you don't block or delay anywhere. loop gets called over and over, and it just checks a few variables. Most of the time, there's nothing to do. But sometimes, based on the state or the current time, it gathers some data, sends it, reads the response, and opens or closes the door. These actions do not interfere with each other, because there are no blocking while loops, only quick checks with if statements.
Open the door in the ISR and set a flag. Also store the time when you opened it. Both of those variables should be declared volatile.
Then in your main loop see if:
The flag is set; and
Time is up
If so, close the door (and clear the flag).
May I assume that setting the variables as "volatile" will prevent the compiler optimizing them? If so, then would you mind explaining why you thought this necessary.
Variables modified inside an ISR may change when the compiler does not expect them to. Using volatile tells the compiler to reload such variables from RAM (and not cache them into a register) so it always gets the most up-to-date copy.
Just as an example, say you had a flag set inside an ISR. And in your main (non-ISR) code you had this:
flag = false;
while (!flag)
{ } // wait for flag to be set
The compiler looks at that and thinks "well, flag will never change" and optimizes away the test for it changing. With volatile though, the compiler keeps the test, because it has to keep reloading flag from RAM.