I am creating a light box which changes state when on a different side. The idea being when the box is on its face it is off and when it is on its side a light sequence starts.
The sequence is 4 seconds long and fades from off to a sort of white in those 4 seconds by increasing the R,G,B values gradually. Eventually I want the sequence to run for 3 minutes but thought I should get this major part working before going further.
It sounds so simple in my head but if for whatever reason the user puts the box back on its face to switch off during the 4 second sequence, I want the NeoPixel to switch off. For a reason unknown I dont know how to get it to stop if its mid fade, it has to wait until the end of the sequence to then switch off. I will submit some of my code below in hopes it helps but I think my issue is much simpler than looking at it.
I have tried rewritting this code so many times using while loops, for loops, breaks, if statements. Really unsure why this is proving so difficult.
All help is truly appreciated, I have been stuck on this for an embarrassing amount of time.
Thank you,
A stressed uni student.
#include <Adafruit_NeoPixel.h> // Library
#define PIN 6 // On Trinket or Gemma, suggest changing this to 1
#define NUMPIXELS 7 // Popular NeoPixel ring size
Adafruit_NeoPixel pixels(NUMPIXELS, PIN, NEO_GRB + NEO_KHZ800);
#define DELAYVAL 500 // Time (in milliseconds) to pause between pixels
int lightState = 0;
int tiltCount = 0;
int lastTilt = 0;
int j = 0;
int a = 0;
int b = 0;
void setup(){
pixels.begin();
pinMode(7, INPUT);
Serial.begin(9600);
}
void loop() {
pixels.clear();
int tiltState = digitalRead(7);
Serial.print("Tilt state: ");
Serial.println(tiltState);
if (tiltState != lastTilt) {
// if the state has changed, increment the counter
if (tiltState == HIGH) {
// if the current state is HIGH then the button went from off to on:
tiltCount++;
Serial.println(tiltCount);
if (tiltCount == 1){
a = 100;
b = 100;
for (j = 0; j < 255; j++ && a++ && b++){
for (int i = 0; i < NUMPIXELS; i++){
pixels.setPixelColor(i,pixels.Color(j, a, b));
pixels.show();
}
delay(30);
}
}
}
if (tiltState == LOW){
tiltCount = 0;
for (int i = 0; i < NUMPIXELS; i++){
pixels.setPixelColor(i,pixels.Color(0,0,0));
pixels.show();
}
}
lastTilt = tiltState;
}
}
You need to restructure your code and get rid of that for() loop to do the fading. Instead of the for() loop, just let loop() do what it was designed to do. Each time through loop, check your tilt. If necessary turn it off, or begin fading or do nothing if the tilt hasn't changed.
Then, if you are fading, increment your color and display
The best solution is to eliminate all your delays and unpack your loops into non-blocking code that quickly finishes loop() and remembers it's own state between calls.
Here's an example of Neopixel animations in Wokwi that pays attention to button presses in the middle of animations:
Take apart this for(j...) loop into a persistent j and redo the delay(30) using an if(millis() - last > 30){... scheme.
And maybe take apart the for(int i... loop as well, depending on the effect you want.
This should get you close... How is your tilt sensor wired up? You have it as an INPUT which means it will need to have a pull-up or pull-down resistor. Is one present?
#include <Adafruit_NeoPixel.h> // Library
#define PIN 6 // On Trinket or Gemma, suggest changing this to 1
#define NUMPIXELS 7 // Popular NeoPixel ring size
Adafruit_NeoPixel pixels(NUMPIXELS, PIN, NEO_GRB + NEO_KHZ800);
#define DELAYVAL 500 // Time (in milliseconds) to pause between pixels
int lightState = 0;
int tiltCount = 0;
int lastTilt = 0;
int j = 0;
int a = 0;
int b = 0;
bool isRunning = false;
unsigned long lastChangeTime;
void setup() {
pixels.begin();
pinMode(7, INPUT);
Serial.begin(9600);
}
void loop() {
int tiltState = digitalRead(7);
Serial.print("Tilt state: ");
Serial.println(tiltState);
if (tiltState != lastTilt) {
// if the state has changed, increment the counter
if (tiltState == HIGH) {
// if the current state is HIGH then the button went from off to on:
isRunning = true;
a = 100;
b = 100;
j = 0;
}
else {
isRunning = false;
pixels.clear();
pixels.show();
}
}
if ( isRunning ) {
if ( millis() - lastChangeTime >= DELAYVAL ) {
// time to update
lastChangeTime = millis();
for (int i = 0; i < NUMPIXELS; i++) {
pixels.setPixelColor(i, pixels.Color(j, a, b));
}
pixels.show();
delay(30);
a++;
b++;
j++;
if ( a > 255 ) a = 255;
if ( b > 255 ) b = 255;
if ( j > 255 ) j = 255;
}
}
lastTilt = tiltState;
}
Thank you for your reply. It is an INPUT and has a 2.2kOhm resistor attached. Is this correct? The code you supplied definitely helps towards my problem. Building from this can I just add more for loops and if statements under the one you gave me.
The idea for my box is for breathing. The first 4 seconds is for breathing in, then it is a slight colour change for 2 seconds for holding breath and then it is 6 seconds fading from the (breathing in) first colour out to nothing. I have a few things to play with but you have helped me greatly.
I will do that with the millis instead of delay. The 30 was just a placeholder, needs to be changed to be closer to 4 seconds.
As for the "for int i", is there an easier way to do this for all 7 LEDs? I want them to come on all at once and change colour at the same time, unsure the best way? Just replace i with "NUMPIXELS"?
What I was unclear about is if you want a "wipe" effect with a distinct "show" in between changing each LED's color, or more like
where the colors are all updated before the show.
In the second case, this for loop isn't much of a blocking problem, and you don't need to eliminate the for loop to make your code non-blocking.
In the first case, if you want to spend significant time wiping across a number of LEDs, then the for loop would be blocking other code from functioning, and you might disassemble the loop into its state variables and rely on loop() for the looping.
But disassembling the outer for (j = 0; j < 255; j++ && a++ && b++){ that is wrapped around a delay() is the main issue.
With the three modes, breathingIn, holdingBreath, and breathingOut, I'd write them all up as functions and call each as different cases in a switch statement.
State variable-wise, if you know tiltCount, tiltState, j, and whether you are breathing in, holding, or breathing out, that pretty much defines everything you need to know to compute what colors the LEDs should be showing. The BlinkWithoutDelay, the Several Things at the Same Time separate the code into maintaining state variables, and acting on state variables. Done right, it can make it simple to make fast, flexible, responsive code.
Who knows? "attached" is not a very specific description. A schematic would be better as well as what your sensor is.
If you want multiple things to happen in sequence, implement a state machine with your states being INHALE, HOLD, EXHALE. Then, every time through loop(), you see what state you are in and react. If it is time for the next state, you update your state variable and note the time of the transition... Google has lots of examples.
Here's a partially tested function for doing the maintaining the LEDs for the first for loop in your code:
void ciaranhBreatheLEDS(int wait){
// per https://forum.arduino.cc/t/stopping-a-for-loop-part-way-through/967148
//depends on `j` as a global state variable.
static int a,b;
static unsigned long last = 0;
if(millis() - last >= wait){
last = millis(); // ***** edit *****
if(j <= 0 || j > 255 ){ // initialize state vars
j = 0;
a = 100;
b = 100;
}
for (int i = 0; i < NUMPIXELS; i++){
pixels.setPixelColor(i,pixels.Color(j, a, b));
}
pixels.show();
j++; a++ ; b++; // update internal and external state vars
}
}
With the transition arrows on the left showing the tiltState == LOW shortcut back to OFF, and the arrows on the right showing the normal operation and conditions, and some little loops on each state showing that you should try to write non-blocking code that will return to the same state the next time loop(){switch(currentMode){case...} comes around.