I wrote a code to have an auto light switch to turn on the lights at a vanity when my wife sits down. The hardware is an attiny45, a relay, and an untrasonic sensor. It works but frequently the lights turn off for a second.
I am revisiting the code. What I want to happen is the sensor reads <=25 the lights turn on for 60 seconds. I also want it to see that the sensor reads <=25 and resets the 60 second timer so that the lights will not have a chance to shut off for the second.
What I am seeing is the lights turn on for 60 seconds because of the delay and the millis does nothing.
int trigger_pin = 1;
int echo_pin =4;
int relay_pin = 0;
int time;
int distance;
unsigned long startMillis;
unsigned long currentMillis;
const unsigned long period = 60000;
const byte relay_Pin = 0;
void setup ( ) {
pinMode (trigger_pin, OUTPUT);
pinMode (echo_pin, INPUT);
pinMode (relay_pin, OUTPUT);
startMillis = millis();
}
void loop ( ) {
delayMicroseconds (50);
digitalWrite (trigger_pin, HIGH);
digitalWrite (trigger_pin, LOW);
time = pulseIn (echo_pin, HIGH);
distance = (time * 0.034) / 2;
currentMillis = millis();
if (currentMillis - startMillis >= period)
{
digitalWrite(relay_Pin, !digitalRead(relay_Pin));
startMillis = currentMillis;
}
if (distance <= 25)
{
digitalWrite (relay_pin, HIGH);
delay (60000);
}
else {
digitalWrite (relay_pin, LOW);
}
}
This construct is regarded as a "trick". It's used on the smallest versions of the AVR chip where memory is so limited that saving a single byte is significant. For an UNO or any other Arduino, there's enough memory that you should be able to use a variable to remember what you last wrote to the pin.
This construct is basically saying "I don't know what it was except I want to make it what it wasn't!"
If you're using millis() for timing, then you should not need delay(). Note: the delayMicroseconds() is a quick-and-easy way to operate the ultrasonic sensor and doesn't need to be replaced.
Think of it this way:
Ping the sensor
If there's something there, turn on the light and record this time
Else (nobody there), has the time expired?
Then turn off the light.
Go back to 1
This way you aren't turning off the light while pinging. You can check if there's still something there and then make a choice to turn the light on or off.
Explicitly switching the light off is only controlled by your distance test.
Your time testing will toggle the relay every 60 seconds irrespective of distance except immediately after that you distance test will then turn it off/on depending to >/< 25.
You need to re-think your logic here. You ha a teo state machine: Vanity light ON, Vanity light OFF. Primary focus is proximity so keep your distance test intact, but look at moving the millis test into the condition where distance <= 25. Keeping the light ON would only be a requirement when the proximity sensor detects your wife being at the vanity and should be OFF otherwise (do you have a cat?). Maybe even think about a delay in turning the light OFF just in case there are momentary glitches where distance is measured to be > 25.
The term state machine was already mentioned. In my view, the simplest implementation (for a beginner) is using a switch/case. I learn best from examples, hence below the example code that I would use; compiles for an Uno but not tested.
In the first state (I called it SM_START), you simply check if the distance is less than 25. The code will stay in that state till the the distance becomes less than 25. Once that is detected, the light is switched on, the time that this happened is remembered and the code switches to the next state.
In the next state (I called it SM_LIGHT_ON), you check again if the distance is less than 25. If so, you update the start time of the timer. Next you check if the required time has lapsed; if so, you switch the light off and the code switches to the next state (in this case back to the first state).
// the states for the state machine
enum STATES
{
SM_START,
SM_LIGHT_ON,
};
int trigger_pin = 1;
int echo_pin = 4;
int relay_pin = 0;
int time;
int distance;
// current time (from millis())
unsigned long currentTime;
// period that the light stay on after nobody is seated
const unsigned long period = 60000;
const byte relay_Pin = 0;
void setup ( ) {
pinMode (trigger_pin, OUTPUT);
pinMode (echo_pin, INPUT);
pinMode (relay_pin, OUTPUT);
}
void loop()
{
static unsigned long startTime;
STATES currentState = SM_START;
delayMicroseconds (50);
digitalWrite (trigger_pin, HIGH);
digitalWrite (trigger_pin, LOW);
time = pulseIn (echo_pin, HIGH);
distance = (time * 0.034) / 2;
currentTime = millis();
switch (currentState)
{
case SM_START:
// light off
digitalWrite(relay_pin, LOW);
// check distance
if (distance <= 25)
{
/*
if somebody seated
*/
// remember the start time for the timer
startTime = currentTime;
// light on
digitalWrite(relay_pin, HIGH);
// indicate next state
currentState = SM_LIGHT_ON;
}
break;
case SM_LIGHT_ON:
// check distance
if (distance <= 25)
{
/*
if somebody still seated
*/
// update the start time for the timer
startTime = currentTime;
}
// if period has lapsed
if (currentTime - startTime >= period)
{
/*
if nobody seated for period
*/
// light off
digitalWrite(relay_pin, LOW);
// indicate next state
currentState = SM_START;
}
break;
}
}
The first few lines define a new type STATES that names the two states for this application. There are a few changes in the declaration of the global variables and in setup(). The main change is in loop().
If you only want to give your wife 60 seconds, you can remove the step in the SM_LIGHT_ON state that updates the start time of the timer. I don't think that she'll appreciate that.
This compiles but is not tested (it's short, should debug relatively quick).
It has protection against thr relay switching too fast when the subject is near switching distance.
Long live cooperative tasking!
const byte trigger_pin = 1;
const byte echo_pin = 4;
const byte relay_pin = 0;
byte relayState;
// int time; <<=== time is a reserved word in Arduino
int pulseTime;
float distance;
unsigned long startLight, waitLight; // default value is zero
const unsigned long lightPeriod = 60000;
unsigned long startRelay, waitRelay; // relay needs to cool between switching
const unsigned long relayPeriod = 500;
void setup ( )
{
pinMode (trigger_pin, OUTPUT);
pinMode (echo_pin, INPUT);
pinMode (relay_pin, OUTPUT);
// startLight = millis(); // is still probably zero, 1ms = 16000 cpu cycles
}
void loop ( )
{
digitalWrite (trigger_pin, HIGH);
delayMicroseconds (50);
digitalWrite (trigger_pin, LOW);
pulseTime = pulseIn (echo_pin, HIGH);
distance = pulseTime * 0.017;
if ( waitLight > 0 ) // always runs
{
if (millis() - startLight >= waitLight)
{
waitLight = 0;
}
}
if (( waitRelay == 0 ) && ( waitLight == 0 ))
{
if ((distance <= 25.0) && (relayState == 0)) // in proximity and light is off
{
relayState = 1;
digitalWrite (relay_pin, relayState);
waitLight = lightPeriod;
waitRelay = relayPeriod;
}
else if ((distance >= 30.0) && (relayState == 1))
{
relayState = 0;
digitalWrite (relay_pin, relayState);
waitRelay = relayPeriod;
}
}
if ( waitRelay > 0 ) // always runs
{
if (millis() - startRelay >= waitRelay)
{
waitRelay = 0;
}
}
}
I'm pretty sure that the delayMicroseconds belongs between the sonic pin HIGH-LOW. You may have to change those lines.