Hello Everyone,
I am quite new to Spark and time is running out before my Halloween prop has to be ready. I am using a Spark Relay Shield and would like to control 3 different props. Using Tinker, all three props are wired in and work fine, however, now is the time to automate it. I need help with the code to make this happen:
Event will be triggered by my motion sensor (drops to 0VDC when triggered). Sensor input needs to be ignored while the below tasks happen.
Turn on D0, D1, D2 (relays) 10 seconds after sensor trigger.
Keep relays on for 20 seconds
Reset and wait for another motion sensor trigger.
Any help would be greatly appreciated. Have to scare some people this year, all in good fun.
Use subtraction… the math with unsigned numbers always works out correctly even when the millis() count wraps: if((millis() - lastMotionTime) > propDelay)
Also there is a missing digitalWrite(RELAY3, LOW); and digitalWrite(RELAY3, HIGH);
@BDub
Do you have any documentation on it “always works out”? I’m just curious on why some math works out better than other math. Is there something special going on behind the scenes with the subtraction?
Also, the gist has been updated to include the missing digitalWrite(RELAY3, HIGH/LOW) lines
@harrisonhjones the issue is that millis() increments by 1 every millisecond, but it’s a statically typed 32-bit variable, so every 4294967296 milliseconds it will “wrap” and end up back at zero. This happens roughly every 50 days.
I guess it’s not exactly just because it’s subtraction, but also the way in which you are subtracting.
Since millis() returns an unsigned long (32bit) value, we want to specifically make sure all math is done with uint32_t aka unsigned long.
If you imagine millis() counting up from 0 in milliseconds and one day in the distant future wrapping around back to 0 because it’s always counting up and overflowing eventually…
If you save lastMotionTime = millis() 2 seconds before it wraps and then the following code executes: if((lastMotionTime + propDelay) < millis()) before millis() wraps, it will immediate execute because lastMotionTime + 20000 will wrap around to a small number which will be less than the current millis() value.
If you instead use subtraction, then 2 seconds before millis() wraps if we immediately execute this code: if((millis() - lastMotionTime) > propDelay) you’ll find that a large millis() value minus lastMotionTime (which we just saved 2 seconds ago) will basically be 2000, which is not greater than propDelay until it’s 20000. But what happens after millis() wraps? surely that will be bad… nope. This is where the subtraction helps. Let’s say we are 3 seconds from when we saved lastMotionTime, so millis() equals 1000 now. With unsigned integer math, 1000 minus (that really large value representing millis() 2 seconds before it wrapped… something like 4294965295 or so) is a negative number… but since we are using unsigned it’s always positive, so it wraps around and the answer is 3000. Thus you never have a wrap error case. Actually you’re probably thinking 1000 - 4294965295 should be some really large negative number, but if it’s always positive then it’s just some really large positive number, not 3000. Well…
The really low level explanation involves how computers do math. Subtraction of two integers is really subtraction of two binary numbers. Subtraction is done by adding the 2’s compliment of the number to be subtracted, to the number being subtracted from. In the case of unsigned math, the answer is always positive. Kind of a walkthrough with signed binary math https://www.youtube.com/watch?v=vfY7bN_3VKw
Subtraction “just always works” if you subtract the previous count from the current higher count (even if that higher count has a chance of wrapping). You always get the absolute difference between the two numbers. Notice this doesn’t work the same way if you subtract the currently incrementing value from the previous count, and it also won’t work if you try to subtract the previous count from the current higher count if it has wrapped around past the previous count
I hope that makes sense! Otherwise I defer to it “just always works” because it does
Thank you so much for the help! I will load the core this afternoon and test the setup.
Harrison, I will make BDub’s math changes if you don’t have time to change the code online.
BDub & Zach, thanks for the coding input. All of this reinforces the fact that I have some substantial learning to do. Do any of you know of a great place to start my coding voyage?
I will complete the project very soon thanks to all of your help.
@zach & @BDub: I appreciate the response but I’m still a little confused. I actually knew (and thought I understood) about the millis() wrapping and the 2s compliment subtraction which is why I’m so confused.
Let’s say we have the following situation:
lastMotionTime = 2 seconds before overflow
4 seconds goes by. Millis() is now 2,000
the “if((lastMotionTime + propDelay) < millis())” check occurs
lastMotionTime + 10,000 should equal 8,000 correct? 8,000 is > millis() so the statement would NOT be true and the IF would not fire. It sounds like you are saying that this is not the case. I just don’t understand
Note: see http://ideone.com/iafX6u. It appears both methods work so I continue to be a little confused
Yeah you’re thinking about it right… but the problem case with your code is when you are within 10 - 20 seconds of rollover (depending on propDelay vs propOnTime) and you save lastMotionTime and then the if statement is evaluated immediately, i.e., before millis() rolls over.
I’m going to steal your text
Let’s say we have the following situation:
lastMotionTime = 9 seconds before overflow
1 second goes by. millis() is now (4294967296 - 8000)
the if((lastMotionTime + propDelay) < millis()) check occurs
lastMotionTime + 10,000 should equal (4294967296 - 9000 + 10000) which rolls over to 1000.
1,000 is < millis() which is (4294967296 - 8000) so the statement WOULD BE true.
That’s the problem… super tiny problem albeit every 50 days. I only point it out so that when you are building rockets some day… you know!
Nicely done. You are correct! (obviously). I will make the change to the gist and my own libraries. Good catch.
Note, for those of you who want to “play along from home” the earlier ideone.com link I posted should go to the version in which millis() is 8 seconds before rollover and lastMotionTime is 9 seconds before which illustrates the error in my code
This years project was working this morning. I added to your code from last year and it worked! Now it is inconsistent. As soon as the core comes on line, it immediately starts toggling the relays. The motion sensor seems fine, 0vdc with no movement, strong 5vdc with motion. I hooked it up to D7 and the led comes on momentarily with motion. Could someone tell me what is going wrong?
what happens...
upon photon startup without any motion sensor input, relays D3 & D4 come on and toggle like they are supposed to. But that action is supposed to be triggered by motion. The toggling action occurs for the specified amount of time (10 secs), then immediately start toggling again. This goes on until I unplug the photon.
Expected behavior...
Once the photon is powered up, it should await motion sensor input, wait for a 1/4 second then toggle the relays on and off for 10 seconds. After that, it should return to a state awaiting motion sensor input again.
I had another post going,
but didn't hear much from anyone after that, so I thought I would charge ahead with your code from last year (modified), with some success.
Well, further troubleshooting reveals that any wire (open, no sensor or load) inserted into the pin designated for the motion input will trigger a never ending run cycle. I suspect that something has happened to either the v2 shield or the photon? I have a new photon arriving tomorrow. I love that they are only $20.
Has anyone reviewed my code to rule it out as the issue. I know it is probably not as efficient as it could be, but I am learning slowly.
dougal, I will research the pulldown resistor idea. Is there a magic value or is it dependent on the load? In this case the relay is switching 12vdc.