I'm using an esp32-wroom board. I'm using platformIO in VS Code to program this. The project I'm attempting is this...
I have push button switch wired up the the controller using a pullup resistor wired to +3.3V and the other end going to ground. My ultimate goal is to have multiple switches that play a given animation when the button is pressed and released. I want to animation to continue until I press the button again at which point I want the display to clear. With my current code (posted below) when I press t he button the animation plays 1 frame then stops. Another thing I'd like to have is the ability to set how fast the animation plays via code. I have been around in circles with this code 100 different ways from Sunday and I just can't figure out what I'm doing wrong. Would someone be kind enough to help me out here and maybe give me some clues where I'm going wrong? Also if I'm doing something totally dumb in my code I'd love some direction to get it right.
#include <MD_MAX72xx.h>
#include <SPI.h>
#include <Arduino.h>
#include "animations.h"
// Define the number of devices we have in the chain and the hardware interface
#define MAX_DEVICES 4
#define HARDWARE_TYPE MD_MAX72XX::FC16_HW
#define CLK_PIN 18 // or SCK
#define DATA_PIN 23 // or MOSI
#define CS_PIN 5 // or SS
// Create a new instance of the MD_MAX72XX library
MD_MAX72XX mx = MD_MAX72XX(HARDWARE_TYPE, DATA_PIN, CLK_PIN, CS_PIN, MAX_DEVICES);
unsigned long previousMillis = 0;
const long interval = 75;
const int button1Pin = 2;
const int button2Pin = 4;
int currentFrame = 0;
void playAnimation1(); // Forward declaration
void buttons(); // Forward declaration
void setup() {
mx.begin();
mx.control(MD_MAX72XX::INTENSITY, 3); // Set intensity level (0-15)
mx.clear();
pinMode(button1Pin, INPUT_PULLUP);
}
void loop() {
buttons();
}
void playAnimation1() {
// Ensure the number of columns does not exceed the array bounds
int numColumns = sizeof(animation_03[0]) / sizeof(animation_03[0][0]);
if (numColumns > 32) {
numColumns = 32; // Limit to 32 columns if the array has more columns
}
for (int i = 0; i < numColumns; i++) {
mx.setColumn(i, animation_03[currentFrame][i]);
}
currentFrame++;
if (currentFrame >= sizeof(animation_03) / sizeof(animation_03[0])) {
currentFrame = 0;
}
}
void buttons() {
#define button1Pressed LOW // Button wired with pullup resistor
uint32_t currentMillis = millis();
static uint32_t lastMillis;
const uint32_t debounceTime = 20;
bool currentButton1State = digitalRead(button1Pin);
static bool lastButton1State;
bool animation1Playing = false;
if (lastButton1State != currentButton1State) {
if (currentMillis - lastMillis >= debounceTime) {
lastButton1State = currentButton1State;
if (currentButton1State == button1Pressed) {
animation1Playing = true;
playAnimation1();
}
}
} else {
animation1Playing = false;
lastMillis = currentMillis;
}
}
Where did you get this code? It looks fairly sophisticated but you ask a simple question.
What currently is determining the frame rate that you want eventually to adjust?
Try this:
Move the bool animation1Playing so it is a global variable.
Remove the call to playAnimation1() in the button code.
In the loop, if animation1Playing is true, call playAnimation1() to show the next frame. Time that with a millis() based mechanism like you see in the Blink Without Delay example in the IDE.
I cannot test this, but it looks like playAnimation1() will do the next frame, and repeat when it gets to the end.
This code is the result of things I've been reading and testing I'm doing. I'm not 100% newb to C++ but rather like only 99.7% newb to it
Currently nothing is controlling my framerate. There's a global variable "interval" still in this code that I was trying to make work here. I forgot to delete that variable when my experiment failed.
I moved the animation1Playing bool to global and changed my loop like this...
It's still behaving in the same manor of playing 1 frame per button push. It seems as if my animation1Playing flag is getting set to true when I press the button then resetting itself to false before the function completes.
Lastly do you think I'm incorrectly using millis() in my buttons function? I'm just curious since you referred me to an example file.
Don't put a #define in a function like that. Put your constants at the top of the page. Or at least just before the function.
Your animation1Playing variable is local to the buttons function. That means at the end of the function it ceases to exist and when the function is called again a new one is created. You can make it global or static to avoid that.
Why would a function called "buttons" play an animation? A function called "buttons" should read buttons. Separating that logic will probably also help.
I'm sure there's a good reason, but I can't figure it out. Why shouldn't I have the #define button1Pressed LOW inside my buttons() function? If I move it out it just gives me a error. When I put in LOW at the end with it outside of the function the editor is recommending I use "LONG_WIDTH".
Also I moved the animation1Playing variable to global and I get the same behavior.
The buttons() function isn't playing the animation. It's reading the state of buttons and setting variables. The playAnimation1() function is doing the playing... well it should if I could figure out what's going wrong at least
Without seeing the code and error message all I can say is that you did it wrong.
If you've made modifications then please post the modified code. Again, maybe you did it wrong. You did say you're 99.7% new.
But it's called from buttons. Which isn't a show stopper, it just makes the code a little harder to manage. I think you'd stand a better chance of sorting your issues if that logic was separated. It would be easier to see what states were triggering what actions.
That was my bad about an error with the #define statement. I put it outside of the function and it's fine. Here is my updated code. Also I see your point about using the buttons function set the animation1Playing bool to true...or false. Is it really a big deal though? I just picked the name "buttons()" out of my backside. I could have well named the function frogs(). Since that function is looking to see if a button is pressed and an animation should happen when it is I just though it logical to call the animation function from there. Anywho... Here's the code. Still playing one frame every time I press the button
#include <MD_MAX72xx.h>
#include <SPI.h>
#include <Arduino.h>
#include "animations.h"
// Define the number of devices we have in the chain and the hardware interface
#define MAX_DEVICES 4
#define HARDWARE_TYPE MD_MAX72XX::FC16_HW
#define CLK_PIN 18 // or SCK
#define DATA_PIN 23 // or MOSI
#define CS_PIN 5 // or SS
// Create a new instance of the MD_MAX72XX library
MD_MAX72XX mx = MD_MAX72XX(HARDWARE_TYPE, DATA_PIN, CLK_PIN, CS_PIN, MAX_DEVICES);
const long interval = 75;
const int button1Pin = 2;
const int button2Pin = 4;
int currentFrame = 0;
bool animation1Playing = false;
void playAnimation1(); // Forward declaration
void buttons(); // Forward declaration
void setup() {
Serial.begin(115200);
mx.begin();
mx.control(MD_MAX72XX::INTENSITY, 3); // Set intensity level (0-15)
mx.clear();
pinMode(button1Pin, INPUT_PULLUP);
}
void loop() {
buttons();
if(animation1Playing == true) {
playAnimation1();
} else {
animation1Playing = false;
}
}
void playAnimation1() {
// Ensure the number of columns does not exceed the array bounds
int numColumns = sizeof(animation_03[0]) / sizeof(animation_03[0][0]);
if (numColumns > 32) {
numColumns = 32; // Limit to 32 columns if the array has more columns
}
for (int i = 0; i < numColumns; i++) {
mx.setColumn(i, animation_03[currentFrame][i]);
}
currentFrame++;
if (currentFrame >= sizeof(animation_03) / sizeof(animation_03[0])) {
currentFrame = 0;
}
}
#define button1Pressed LOW
void buttons() {
#define button1Pressed LOW // Button wired with pullup resistor
uint32_t currentMillis = millis();
static uint32_t lastMillis;
const uint32_t debounceTime = 20;
bool currentButton1State = digitalRead(button1Pin);
static bool lastButton1State;
if (lastButton1State != currentButton1State) {
if (currentMillis - lastMillis >= debounceTime) {
lastButton1State = currentButton1State;
if (currentButton1State == button1Pressed) {
animation1Playing = true;
}
}
} else {
animation1Playing = false;
lastMillis = currentMillis;
}
}
I forgot to remove the #define from buttons() before I copied. It's gone from inside the function now though
There are a lot of things that don't actually change what the code does but instead just make the code easier to write. Things like using variable names that are meaningful instead of x and i.
One of those things is to keep logical things logically separated. As an example (that doesn't necessarily apply to your case) imagine that you have several different things that are controlled by buttons and there is code in several places in your sketch reading buttons. It would be really easy to overlook something like the fact that two different functions are reading the buttons differently and trying to set the same variable. Or to overlook something simple like one button that ends up trying to trigger two different functions.
So the sort of ideal case is to separate and assign responsibilities. It's not unlike a team at work where you want each person to have their own lane and handle their own part. Right now it doesn't make much difference because the code is small. But as it grows you'll end up having to hunt through lines of code to find where something happens. You want to prevent that now. If you wait then the refactoring job becomes almost bigger than the job to write the code in the first place.
Now that brace issue sticks out. This doesn't set animationPlaying to false when the button isn't pressed. It sets it anytime the lastButton1State == currentButton1State and that first if statement is false. If you're still holding the button, then that will be the case on the next time this buttons function is called.
I'm defiantly soaking up your input here. I totally get your point about making the functions logical in what they do. I want to get this code working how it's arranged now then see about changing it up to include your suggestions in regards to the button function. I just feel if I were to try that now I'd never find what is breaking my current situation.
So I did a few changes and it's defiantly a step forward. Now when I press and hold the button the animation plays properly (well sorta, more on that in a second). When I release the button the animation stops and holds the current frame on the display.
So I tried putting in some code to allow me to set the framerate of the animation. I tried using the currentMillis - lastMillis and comparing it with the interval time, but that had no effect on the framerate. I can set interval to 1000000 and the animation just zooms through as fast as it can. Here's my last code if you wouldn't mind taking a new peek
#include <MD_MAX72xx.h>
#include <SPI.h>
#include <Arduino.h>
#include "animations.h"
// Define the number of devices we have in the chain and the hardware interface
#define MAX_DEVICES 4
#define HARDWARE_TYPE MD_MAX72XX::FC16_HW
#define CLK_PIN 18 // or SCK
#define DATA_PIN 23 // or MOSI
#define CS_PIN 5 // or SS
// Create a new instance of the MD_MAX72XX library
MD_MAX72XX mx = MD_MAX72XX(HARDWARE_TYPE, DATA_PIN, CLK_PIN, CS_PIN, MAX_DEVICES);
const int button1Pin = 2;
const int button2Pin = 4;
int currentFrame = 0;
bool animation1Playing = false;
int interval = 1000;
void playAnimation1(); // Forward declaration
void buttons(); // Forward declaration
void setup() {
mx.begin();
mx.control(MD_MAX72XX::INTENSITY, 3); // Set intensity level (0-15)
mx.clear();
pinMode(button1Pin, INPUT_PULLUP);
}
void loop() {
buttons();
if(animation1Playing == true) {
playAnimation1();
} else {
animation1Playing = false;
}
}
void playAnimation1() {
unsigned long currentMillis = millis();
unsigned long lastMillis = 0;
int numColumns = sizeof(animation_03[0]) / sizeof(animation_03[0][0]);
if (numColumns > 32) {
numColumns = 32; // Limit to 32 columns if the array has more columns
}
for (int i = 0; i < numColumns; i++) {
mx.setColumn(i, animation_03[currentFrame][i]);
}
if (currentMillis - lastMillis >= interval){
currentFrame++;
if (currentFrame >= sizeof(animation_03) / sizeof(animation_03[0])) {
currentFrame = 0;
}
}
}
#define button1Pressed LOW
void buttons() {
unsigned long currentMillis = millis();
unsigned long lastMillis = 0;
const uint32_t debounceTime = 20;
bool currentButton1State = digitalRead(button1Pin);
bool lastButton1State = HIGH;
if (lastButton1State != currentButton1State) {
if (currentMillis - lastMillis >= debounceTime) {
lastButton1State = currentButton1State;
if (currentButton1State == button1Pressed) {
animation1Playing = true;
}
}
} else {
animation1Playing = false;
lastMillis = currentMillis;
}
}
Other end of the switch. The resistor goes from 1 post of switch to +3.3v and to pin2. The other end of the switch goes to ground. That's a pullup configuration. When you press the button pin2 gets 0v when it's unpressed pin2 gets 3.3v
Would definitely be easier if you weren't defiant. (Sorry, I know it was probably spell check but I couldn't resist that one.)
lastMillis is always 0. For two reasons. FIrstly, it's a non-static local so it's being recreated each time the function is called. But secondly because there's no line in there to set it to currentMillis. (I do that one all the time)
Allright. I'm completely confused with the whole millis thing. What Delta said makes sense. I was just going back to 0 every time because I wasn't setting lastMillis to currentMillis.
Where I'm confused now though. So I'm trying to use the millis timer in 2 functions. Do I need to declare currentMillis = millis(); in both functions? When I tried doing that in a global manor it just didn't work and gave an error. Also I'm not sure how to properly use it in my functions or where to properly place it. Also when I include the line currentMillis = millis(); as the first line in my loop it's a big fat error saying not declared.
Here's what I got. I'm somewhat positive I'm misusing the millis throughout my program and that's the root of my whole problem. With this current code when I press the button the animation appears and seems to advance 1 frame then stops. Additional button presses have no effect. I'm really starting to feel overly stupid here. I'm sure it's something obvious staring me in the face and I'm just not seeing it.
p.s @Delta_G We all better hope that autocorrect never starts training AI models hahaha
#include <MD_MAX72xx.h>
#include <SPI.h>
#include <Arduino.h>
#include "animations.h"
// Define the number of devices we have in the chain and the hardware interface
#define MAX_DEVICES 4
#define HARDWARE_TYPE MD_MAX72XX::FC16_HW
#define CLK_PIN 18 // or SCK
#define DATA_PIN 23 // or MOSI
#define CS_PIN 5 // or SS
// Create a new instance of the MD_MAX72XX library
MD_MAX72XX mx = MD_MAX72XX(HARDWARE_TYPE, DATA_PIN, CLK_PIN, CS_PIN, MAX_DEVICES);
const int button1Pin = 2;
const int button2Pin = 4;
int currentFrame = 0;
bool animation1Playing = false;
unsigned long now = 0;
unsigned long lastMillis = 0;
int frameRate = 100;
void playAnimation1(); // Forward declaration
void buttons(); // Forward declaration
void setup() {
mx.begin();
mx.control(MD_MAX72XX::INTENSITY, 3); // Set intensity level (0-15)
mx.clear();
pinMode(button1Pin, INPUT_PULLUP);
}
void loop() {
now = millis();
buttons();
if(animation1Playing == true) {
playAnimation1();
} else {
animation1Playing = false;
}
}
void playAnimation1() {
//unsigned long now = millis();
// unsigned long lastMillis = 0;
int numColumns = sizeof(animation_03[0]) / sizeof(animation_03[0][0]);
if (numColumns > 32) {
numColumns = 32; // Limit to 32 columns if the array has more columns
}
for (int i = 0; i < numColumns; i++) {
mx.setColumn(i, animation_03[currentFrame][i]);
}
if (now - lastMillis >= frameRate){
lastMillis = now;
currentFrame++;
if (currentFrame >= sizeof(animation_03) / sizeof(animation_03[0])) {
currentFrame = 0;
}
}
}
#define button1Pressed LOW
void buttons() {
//unsigned long now = millis();
// unsigned long lastMillis = 0;
const int debounceTime = 20;
bool currentButton1State = digitalRead(button1Pin);
bool lastButton1State = HIGH;
if (lastButton1State != currentButton1State) {
if (now - lastMillis >= debounceTime) {
lastMillis = now;
lastButton1State = currentButton1State;
if (currentButton1State == button1Pressed) {
animation1Playing = true;
}
}
} else {
animation1Playing = false;
currentButton1State == lastButton1State;
}
}
So now buttons and playAnimation1 are both using the same lastMillis variable.
While there is only one current time and it's the same everywhere, there will be lots of different time-stamps saved for the last time different things happened.
So these lastMillis variables work best as static local that way buttons and playAnimation1 both get their own.
OK I included static unsigned long lastMillis = 0; in both my buttons() and playAnimation1() functions. Now I'm back to the animation playing while the button is pressed and held. On a positive note I'm now able to adjust my global int frameRate = 50; variable and adjust how fast the animation plays. Thank God for small victories
I'm about to change gears, but what you need to do instead is called state change detection, so that the relevant variable is toggled (turned true if false and vice versa) when the button is pressed (goes from up to down).
Nothing happens to the variable while the button is being held, and nothing happens when the button is released (goes from down to up).
There's an example of the pattern in the IDE sketch 02. Digital / StateChangeDetection.
Post the code that almost works except for this last detail, it is probably a realtively small change but with big difference.