#include "FastLED.h"
#include "firebeetle_timer.h"
uint32_t global_now = 0;
/**************************************************************/
/* Button handling: A pin is defined for the button,
the debounce_delay gives the number of ms we use for debouncing,
the button_interrupt signals from the ISR to the normal loop that
something is happening on the input button
the "virtual_button_on" is the global state of the button so that
it behaves like a on/off switch.
initialization_complete is a security switch so that the interrupt
can not be triggered until the setup function completed it's task.*/
/**************************************************************/
#define BUTTON_PIN 2 // Target System Button Pin
#define DEBOUNCE_DELAY 40 // Debounce time in milliseconds
volatile bool button_interrupt = true;
bool virtual_button_on = true;
bool virtual_button_state_has_changed = false;
bool button_physical_state = true;
bool debounce_active = true;
uint32_t end_of_debounce_time = 0; // End time of debounce quarantine
uint32_t next_button_check = millis();
bool button_debounce();
void handle_button();
void button_pin_change();
/****************************************************************/
/* FastLED set up*/
/****************************************************************/
#define PIXELPIN 7 // Target System Pixel Pin
//LED matrix size
#define WIDTH 8
#define HEIGHT 1
#define NUM_LEDS 8
#define LAST_VISIBLE_LED 7
CRGB leds[NUM_LEDS];
#define BRIGHTNESS 220
#define SIGMA 30 //width of curve
#define PARSING 17 // parsing each X position on the curve
//#define LIGHTMIN 30
#define LIGHTMIN 0
#define LIGHTMAX_BLUE 220
#define LIGHTMAX_RED 115
#define LIGHTMAX_WHITE 110
#define SPEED 16 // how many positions we move through the gaussian each update_interval
#define UPDATE_INTERVAL 30
uint32_t light_bar_next_update = 0;
uint8_t mu = 255; // Startposition der Gaußkurve
//uint8_t gaussCurve[255];
#define NO_CLOCK_CORRECTION 1 // asked by Neopixel library
float normalization = 0;
/****************************************************************/
/* forward declarations */
/****************************************************************/
// Button
bool button_debounce();
void handle_button();
void button_interrupt_enable();
// LED effect
float gaussian(int x, int mu, float sigma);
void updateGaussPosition();
void setLEDColors();
void led_effect_off();
void led_effect_step();
void setup() {
Serial.begin(115200);
Serial.println("Woke up");
// Button and Interrupt
pinMode(BUTTON_PIN, INPUT_PULLUP);
attachInterrupt(digitalPinToInterrupt(BUTTON_PIN), button_pin_change, CHANGE);
// Led effect
FastLED.addLeds<NEOPIXEL, PIXELPIN>(leds, NUM_LEDS); // Pin für Neopixel
FastLED.clear();
FastLED.show();
FastLED.setBrightness(BRIGHTNESS);
FastLED.setDither(DISABLE_DITHER);
normalization = 255.0 / gaussian(127, 127, SIGMA); // Skaliert die Kurve, sodass das Maximum 255 ist
// Sleep timer and wake up
set_button_wake_up(BUTTON_PIN);
}
/* The loop is organized so that codes are only executed if their time
has come. Every piece of codes also gives the next time it needs
to be executed. Like that we can go to sleep everytime if there is
nothing to do for the next couple of millis.
*/
void loop() {
global_now = millis();
// check on button
if (button_interrupt) handle_button();
// if debouncing is still active but the time is over, handle the button
if (debounce_active && (global_now > end_of_debounce_time)) handle_button();
if (virtual_button_state_has_changed){
Serial.print("button change detected, is now ");
Serial.println(virtual_button_on);
virtual_button_state_has_changed = false;
light_bar_next_update = global_now;
}
// check on light effect
if (global_now >= light_bar_next_update) {
if (virtual_button_on) led_effect_step();
else led_effect_off();
}
// go to sleep if possible. We stay awake during debounce.
if (not debounce_active){
// check min update time
// normally next line should be the min of all action times but
// in this program we just have one single item, so it's easy
uint32_t next_action = (next_button_check < light_bar_next_update)?next_button_check:light_bar_next_update;
uint32_t time_to_sleep = next_action - millis();
if (time_to_sleep > 1){
if (virtual_button_on){
go_to_sleep(time_to_sleep);
return;
}
else if (button_physical_state == true){
Serial.println("Going to deep sleep");
// wait until the button is released
//while(digitalRead(BUTTON_PIN)==LOW);
//delay(40);// and a little longer
detachInterrupt(digitalPinToInterrupt(BUTTON_PIN));
go_to_deep_sleep();
}
}
}
}
/************************************************************************/
/* LED effect handling */
/************************************************************************/
void led_effect_step(){
updateGaussPosition();
setLEDColors();
FastLED.show();
light_bar_next_update = global_now + UPDATE_INTERVAL;
}
void led_effect_off(){
FastLED.clear();
FastLED.show();
light_bar_next_update = global_now + UPDATE_INTERVAL * 1000;
}
const float expTable[] = {
1.00000, 0.96079, 0.92312, 0.88704, 0.85254, 0.81968, 0.78846, 0.75888,
0.73096, 0.70468, 0.68003, 0.65699, 0.63552, 0.61560, 0.59716, 0.58016
};
float gaussian(int x, int mu, float sigma) {
int dx = x - mu;
int index = abs(dx) * 10 / (int)sigma; // Skalieren und runden Sie den Index
if (index >= sizeof(expTable) / sizeof(float)) {
return 0.0; // Index außerhalb des gültigen Bereichs
}
return expTable[index];
}
void updateGaussPosition() {
mu-= SPEED;
if (mu <= 1) mu =250; // Zurücksetzen auf die Startposition
}
void setLEDColors() {
for (int i = 0; i < NUM_LEDS; i++) {
uint8_t brightness;
int pos = mu - (i*PARSING);
if (pos < 0) pos +=255;
if (i < 3) {
brightness = scale8((uint8_t)(gaussian(pos, 127, SIGMA) * normalization), LIGHTMAX_BLUE);
if (brightness < LIGHTMIN) brightness = LIGHTMIN;
leds[i] = CHSV(160, 255, brightness); // Blau
} else if (i >= 3 && i <= 4) {
brightness = scale8((uint8_t)(gaussian(pos, 127, SIGMA) * normalization), LIGHTMAX_WHITE);
if (brightness < LIGHTMIN) brightness = LIGHTMIN;
leds[i] = CHSV(0, 1, brightness); // Weiß
} else {
brightness = scale8((uint8_t)(gaussian(pos, 127, SIGMA) * normalization), LIGHTMAX_RED);
if (brightness < LIGHTMIN) brightness = LIGHTMIN;
leds[i] = CHSV(0, 255, brightness); // Rot
}
}
}
/************************************************************************/
/* Button interrupt handling */
/************************************************************************/
// Interrupt-Service-Routine (ISR) for Pin Change Interrupt
void IRAM_ATTR button_pin_change() {
noInterrupts(); // deactivate interrupts to not get too many pin change interrupts from bouncing
button_interrupt = true; //tell the central loop to call handle_button()
}
/* sets the state of the virtual button. */
void handle_button(){
button_interrupt = false; //Tell everyone the interrupt has been handled now.
// check if the button was debounced already
if(button_debounce()){
button_physical_state = digitalRead(BUTTON_PIN);
if ( button_physical_state == LOW) {
virtual_button_on = !virtual_button_on; //Toggle state
virtual_button_state_has_changed = true;
}
interrupts(); // Activate interrupts. TODO: Necessary here and below?
}
interrupts();
}
/* A function that checks if DEBOUNCE_DELAY ms have passed without
state change. If less time has passed, returns false and resets timer.
If more time has passed, returns true.
This function is used together with the button interrupt: Instead of polling hundreds
of times, it is called only on state change interrupts or after the last debounce
time is over.
Uses two globals, debounce_active and end_of_debounce_time to communicate with the rest of the
program:
If there are no more interrupts coming, after the time stored in end_of_debounce_time and if
debounce_active is still set to "true", this function is called a last time
to confirm the debouncing is over. */
bool button_debounce() {
static bool lastButtonState = HIGH; // The previous state of the button
int buttonState = digitalRead(BUTTON_PIN);
// Check if the button state has changed
if (buttonState != lastButtonState) {
// No valid button state change, reset the timer
lastButtonState = buttonState;
end_of_debounce_time = millis() + DEBOUNCE_DELAY; // Update the time of the last button state change
next_button_check = global_now;
debounce_active = true;
return false;
}
// Check if enough time has passed since the last button state change
if (millis() > end_of_debounce_time) {
// Check if it's a valid button state change
if (lastButtonState == buttonState) {
// The button state is stable and valid
lastButtonState = buttonState;
debounce_active = false; // end debounce phase
return true; // tell everyone this time it was good
}
}
debounce_active = true;
return false; //Waiting time wasn't long enough, this time it wasn't okay.
}