Hi all -- I've been studying Arduino for the better part of a year, and am becoming more comfortable using things like the Millis function. I've come across a post where the sketch writer used Millis to fade an LED up and down (see below). But I'm trying to fade the LED up, stop for 5 or so seconds, and then fade down. I've searched the topic all day but since everyone's code is different, its a bit harder to follow exactly what they're doing.
Im thinking along the lines of -- after the led fades up, you add something like
if (currentMillis >= previousFadeMillis+5000) {
// if currentMillis is greater than what it was before, by 5000 milliseconds
fadeValue = maxPWM - fadeIncrement
}
I think my train of logic is on the right path, but I guess I'm not sure exactly how to code it. If anyone can give me a boost I'd appreciate it!! Thanks!
const byte pwmLED = 8;
// define directions for LED fade
#define UP 0
#define DOWN 1
// constants for min and max PWM
const int minPWM = 0;
const int maxPWM = 255;
// State Variable for Fade Direction
byte fadeDirection = UP;
// Global Fade Value
// but be bigger than byte and signed, for rollover
int fadeValue = 0;
// How smooth to fade?
byte fadeIncrement = 5;
// millis() timing Variable, just for fading
unsigned long previousFadeMillis;
// How fast to increment?
int fadeInterval = 50;
void setup() {
// put pwmLED into known state (off)
analogWrite(pwmLED, fadeValue);
}
void doTheFade(unsigned long currentMillis) {
// is it time to update yet?
// if not, nothing happens
if (currentMillis - previousFadeMillis >= fadeInterval) {
// yup, it's time!
if (fadeDirection == UP) {
fadeValue = fadeValue + fadeIncrement;
if (fadeValue >= maxPWM) {
// At max, limit and change direction
fadeValue = maxPWM;
fadeDirection = DOWN;
}
} else {
//if we aren't going up, we're going down
fadeValue = fadeValue - fadeIncrement;
if (fadeValue <= minPWM) {
// At min, limit and change direction
fadeValue = minPWM;
fadeDirection = UP;
}
}
// Only need to update when it changes
analogWrite(pwmLED, fadeValue);
// reset millis for the next iteration (fade timer only)
previousFadeMillis = currentMillis;
}
}
void loop() {
// get the current time, for this time around loop
// all millis() timer checks will use this time stamp
unsigned long currentMillis = millis();
doTheFade(currentMillis);
}
I actually went through that earlier, but I'll take another stab at it and try to create an original code. The only reason I tried to add onto the existing code is because it does everything I need except the hold. I'll report back!
Time to stop copying other peoples code and start writing your own.
No matter how hard you look, you will not find all the features you need.
Looking at other code should be for learning techniques.
if (currentMillis >= previousFadeMillis+5000) {
The above line does not represent the correct way to use BWD style coding.
Declare a variable to hold the current state number. Use millis() to time the actions for the current state. When the actions for the current state are finished change the state variable and start timing the next one.
You need to (mentally) convert a span of time into a percentage of progress when working with millis(). If something is started when the arduino is powered on and has to run for 5 seconds, the code would be something like:
#define DURATION 5000.0f
#define PWM_PIN 8
unsigned long startMillis, currentMillis;
void setup()
{
//Other setup here
startMillis = millis();
pinMode(PWM_PIN, OUTPUT);
analogWrite(PWM_PIN, 0);
}
void loop()
{
currentMillis = millis();
float percentDone = (float)(currentMillis - startMillis) / DURATION;
if (percentDone < 0)
{
//This block is entered if the operation has not started yet
analogWrite(PWM_PIN, 0);
}
else if (percentDone > 1)
{
//This block is entered if the operation has completed
analogWrite(PWM_PIN, 255);
}
else
{
//This block is entered when the operation is partially done
//percentDone will be in the range 0.0 to 1.0 where 0
//is the starting point and 1.0 is the ending point. You can use the
//value to easaly map another, eg 0..255 for analogWrite, like this:
analogWrite(PWM_PIN, round(255.0f * percentDone));
}
}
Please note that floating point operations are heavy on the Arduino and if they are avoidable they should be. You could also "cheat" and use a library to accomplish your task with minimal code / effort:
#include <TDuino.h>
#define PWM_PIN 8
void timelineHandler(byte slot, float progress)
{
if (slot == 0) analogWrite(PWM_PIN, TL_MapToInt(progress, 0, 255);
else analogWrite(PWM_PIN, TL_MapToInt(progress, 255, 0);
/*if (tl.firstActive() < 0)
{
//This block will be entered when the timeline has completed
}*/
}
TTimeline tl(timelineHandler, 2);
void setup()
{
//Other setup here
pinMode(PWM_PIN, OUTPUT);
tl.set(0, 1000, 0); //Slot 0, duration 1 sec, start now
tl.set(1, 1000, 6000); //Slot 1, duration 1 sec, start after 6 secs (1 sec fade on + 5 sec pause)
}
void loop()
{
tl.loop();
}
TDuino docs for TTimeline. None of the code above has been tested..
So I sat down for a few hours and came up with this --
I also added in a PIR sensor to trigger the led turning on. The only issue I'm having now is that because the PIR has the 5sec "delay" built into it, my input (and LED) stays high for a minimum of 5seconds. I was thinking that for
if (ledStatus == HIGH) { // IF the LED status is HIGH
if (currentMillis - startMillis > ledOnTime) { // AND if a period longer than the "set LED ON" time has expired
digitalWrite (ledPin, LOW); // Turn the LED off
Could I write something like if (currentMillis - startMillis > ledOnTime - 5000)
where the -5000 would act as a way to work around the PIR delay keeping everything on? But I also feel like if I don't use the ledStatus as my "off trigger" then I may be able to work around it as well.
Any suggestions? Thanks for the motivation!
const int PirInputPin = 2; // PIR is connected to pin 2
const byte ledPin = 8; // LED is on Pin 8
int PirVal = 0; // Initial variable to store sensor status
int PirState = LOW; // Initial sensor condition
int ledStatus = LOW; // LED on or off
unsigned long startMillis; // Starting time for current loop
unsigned long currentMillis; // Current time for current loop
const unsigned long ledOnTime = 1; // How long you want the LED to stay on (+5000ms)
void setup() {
pinMode(ledPin, OUTPUT); // The LED is an output
pinMode(PirInputPin, INPUT); // The PIR is an input
startMillis = millis();
}
void loop() {
currentMillis = millis(); // Time variable
PirVal = digitalRead(PirInputPin); // Read the input value of the PIR
if (PirVal == HIGH) { // check if the PIR has detected motion
digitalWrite(ledPin, HIGH); // If it has, turn on the LED
ledStatus = HIGH; // Change the LED status to HIGH
startMillis = currentMillis; // Set the start time for the next loop to this current time
//if (PirState == LOW) { // AND if the PIR state is currently LOW, set it to currently HIGH
// PirState = HIGH;
if (ledStatus == HIGH) { // IF the LED status is HIGH
if (currentMillis - startMillis > ledOnTime) { // AND if a period longer than the "set LED ON" time has expired
digitalWrite (ledPin, LOW); // Turn the LED off
ledStatus = LOW; // Change the LED status to LOW
startMillis = currentMillis; // Set the start time for the next loop to this current time
}
}
}
else if (ledStatus == HIGH) { // If the above isnt happening AND the LED status is set to HIGH
if (currentMillis - startMillis > ledOnTime) { // And if a period longer than the "set LED ON" time has expired
digitalWrite (ledPin, LOW); // Turn the LED off
ledStatus = LOW; // Change the LED status to LOW
startMillis = currentMillis; // Set the start time for the next loop to this current time
// if (PirState == HIGH) { // If the PIR state is currently HIGH, set it to currently LOW
// PirState = LOW;
}
}
}
// }
//}
Negative time makes no sense. How can you know that the input is going to start 5 seconds in the future?
Instead of turning the LED on and starting the timer when the PIR is HIGH, detect the change in the PIR. That means you should record what it was on the previous loop and compare against that.
It makes sense as far as the negative time if it did the calculation during the loop and read if the time passed is greater than ~300ms~ (for example) but I also understand what you mean. is there a way to tell it directly that if its greater than 5000ms or 9000ms what to do? Like if I made time a variable? something like:
ledOnTime = 5000
if (ledStatus == HIGH) { // IF the LED status is HIGH
if (currentMillis - startMillis > ledOnTime) { // AND if a period longer than the "set LED ON" time has expired
digitalWrite (ledPin, LOW); // Turn the LED off
I will also try the idea of switching during the change. I think I was kind of onto that track when I had the PirState in my loop, but I commented those lines out.
That will turn it off ledOnTime milliseconds after it went on. If the PIR is still detecting movement OR still within its 5-second period, it will turn on again a millisecond later. Is this what you want?
Well, I'm closer. I got the led to come on via the PIR and I got it to only stay on for as long as I set my ledOnTime for -- but the only issue I have now is that its not recording the previousPIR state as the current state, because once the loop restarts, it read the new PIR state as the current...and since the PIR stays latched for 5seconds, it will start the whole process again.... basically making my LED come on twice in one motion sense (atleast I think thats what the issue is).
Is there a way to work around this?
const int PirInputPin = 2; // PIR is connected to pin 2
const byte ledPin = 8; // LED is on Pin 8
int PirVal = 0; // Initial variable to store sensor status
int PreviousPirVal = 0;
int PirState = LOW; // Initial sensor condition
boolean ledStatus = 0; // LED on or off
boolean PreviousLedStatus = 0;
int ledState = 0;
unsigned long startMillis; // Starting time for current loop
unsigned long currentMillis; // Current time for current loop
const unsigned long ledOnTime = 4000; // How long you want the LED to stay on
void setup() {
pinMode(ledPin, OUTPUT); // The LED is an output
pinMode(PirInputPin, INPUT); // The PIR is an input
startMillis = millis();
}
void loop() {
currentMillis = millis(); // Time variable
PirVal = digitalRead(PirInputPin); // Read the input value of the PIR
if (PirVal != PreviousPirVal) { // Check if the current PIR value is different from its last value
digitalWrite(ledPin, HIGH); // If it has changed, turn on the LED
ledStatus = 1; // Change the LED status to 1
startMillis = currentMillis; // Set the start time for the next loop to this current time
PreviousPirVal = PirVal; // Set the previous Pir Value equal to the current value, for the next loop
if (ledStatus == 1) { // IF the current LED status is 1
if (currentMillis - startMillis >= ledOnTime) { // AND if a period longer than the "set LED ON" time has expired
digitalWrite (ledPin, LOW); // Turn the LED off
ledStatus = 0; // Change the LED status to 0
startMillis = currentMillis; // Set the start time for the next loop to this current time
PreviousLedStatus = ledStatus; // Set the previous LED status equal to the current LED status
}
}
}
else if (PirVal == PreviousPirVal) { // If the current Pir value is the same as the previous PIR value
if (currentMillis - startMillis >= ledOnTime) { // And if a period longer than the "set LED ON" time has expired
digitalWrite (ledPin, LOW); // Turn the LED off
ledStatus = 0; // Change the LED status to LOW
startMillis = currentMillis; // Set the start time for the next loop to this current time
PreviousPirVal = PirVal;
}
}
}
I tried this, but it still didn't work. But it had me thinking -- could I run a command that had another variable, something like "timeTriggered" and make that equal to 5 seconds? Therefore if the timeTriggered <= last recorded time, the code won't execute?
Something like
if (PirVal != PreviousPirVal && timeTriggered >= currentMillis)
void loop()
{
currentTime = millis();
switch (currentState)
{
case FADING_UP:
if (currentTime - startTime >= fadePeriod)//time to change LED level
{
ledLevel++;
analogWrite(ledPin, ledLevel);
if (ledLevel == 255)
{
currentState = WAITING;
startTime = currentTime;
}
startTime = currentTime;
}
break;
case WAITING:
if (currentTime - startTime >= waitPeriod)
{
currentState = FADING_DOWN;
startTime = currentTime;
}
break;
case FADING_DOWN:
if (currentTime - startTime >= fadePeriod)//time to change LED level
{
ledLevel--;
analogWrite(ledPin, ledLevel);
if (ledLevel == 0)
{
currentState = DO_NOTHING;
}
startTime = currentTime;
}
break;
}
}
This makes total sense -- I understood the post 5 statement, but I think I'm too much of a rookie to conceptualize it from scratch haha.
But with this, since I'm using the PIR to start things up, is it still going to give me an issue with the 5sec delay? I won't be home for another 3hrs to try it out. I've been at work all day and have spent a good 4hours thinking about how to fix the sketch !!
As it stands the fade up, wait, fade down, do nothing sequence starts when the Arduino is powered up or reset but it should show you how to use a state machine.
To control it with a PIR you could introduce a state, perhaps AWAITING_TRIGGER, and only come out of that state when the PIR is triggered. If you want to exit one of the states because of PIR triggering then add that test within the code for the state and change to a different state when the PIR is triggered, or stops being triggered.
Once you have grasped the principle of using a state machine you will find it very flexible.