I have no clue why this is happening to me but the buttons (in this case, buttonAbort and buttonStage) are not working whatsoever.
Edit: Basically the LEDs are placed right next to the button,(e.g. ledStage -> placed next to the buttonStage) they start to blink when the timer hits zero with a buzzer noise, and if the user presses one of the buttons, the buzzer noise stops and the LED placed next to the button they pressed turns on whereas the other turns off. I also connected the buttons without resistors. (directly to the pins)
/*
Jebediah's Launch Control System for Kerbal Space Program
Alpha Build 2.00
An Open-Source Project by John Seong
*/
#include <Wire.h>
#include <LiquidCrystal_I2C.h>
#include <Keypad.h>
const byte ROWS = 4;
const byte COLS = 3;
const int buzzer = A3;
const int ledAbort = 5;
const int ledStage = A5;
const int buttonAbort = 4;
const int buttonStage = A4;
char keys[ROWS][COLS] = {
{'1', '2', '3'},
{'4', '5', '6'},
{'7', '8', '9'},
{'*', '0', '#'}
};
byte rowPins[ROWS] = {12, 11, 10, 9};
byte colPins[COLS] = {8, 7, 6};
Keypad keypad = Keypad(makeKeymap(keys), rowPins, colPins, ROWS, COLS);
LiquidCrystal_I2C lcd(0x3F, 2, 1, 0, 4, 5, 6, 7, 3, POSITIVE);
// Show a two-line message on the LCD.
void lcdShow(const char *line0, const char *line1) {
lcd.clear();
lcd.print(line0);
lcd.setCursor(0, 1);
lcd.print(line1);
}
void lcdShowResult(float result) {
lcd.print(result);
}
void setup() {
pinMode(buzzer, OUTPUT);
pinMode(ledAbort, OUTPUT);
pinMode(ledStage, OUTPUT);
pinMode(buttonAbort, INPUT_PULLUP);
pinMode(buttonStage, INPUT_PULLUP);
Serial.begin(9600);
lcd.begin(16, 2);
while (!keypad.getKey()) ; // wait for key press
}
void loop() {
static enum {START, MENU, SET_COUNT, COUNT_DOWN, LAUNCH, SET_THRUST, SET_WEIGHT} state = START;
static uint32_t last_second; // millis() value on last full second
static int count;
static float thrust, weight, ratio;
int stageConfirm = digitalRead(buttonStage);
int abortConfirm = digitalRead(buttonAbort);
char key = keypad.getKey();
switch (state) {
case START: // transitional state
lcdShow("1. LAUNCH SEQ", "2. TWR CALC");
state = MENU;
/* fallthrough */
case MENU:
if (key == '1') { // Countdown
lcdShow("COUNTDOWN TIMER", "SECONDS: ");
count = 0;
state = SET_COUNT;
} else if (key == '2') { // TWR
lcdShow("TWR CALCULATOR", "THRUST: ");
thrust = 0, weight = 0, ratio = 0;
state = SET_THRUST;
}
break;
case SET_COUNT:
if (key >= '0' && key <= '9' && count <= 99) {
lcd.print(key);
count = 10 * count + (key - '0');
} else if (key == '#') {
lcdShow(" T-MINUS", " SECONDS");
// Force a refresh on entering COUNT_DOWN:
last_second = millis() - 1000;
count++;
state = COUNT_DOWN;
} else if (key == '*') {
state = START;
}
break;
case COUNT_DOWN:
if (millis() - last_second >= 1000) {
last_second += 1000;
count--;
if (count <= 10) {
tone(buzzer, 500);
delay(500);
noTone(buzzer);
}
if (count == 0) {
Serial.println("Lift off!");
} else if (count < 0) {
state = LAUNCH;
break;
}
lcd.setCursor(1, 1);
lcd.print(count < 10 ? " " : count < 100 ? " " : ""); // pad
lcd.print(count);
} else if (key == '*') {
state = START;
}
break;
case LAUNCH:
tone(buzzer, 3000);
digitalWrite(ledAbort, HIGH);
digitalWrite(ledStage, HIGH);
lcdShow(" T-ZERO WARNING", "CONFIRM IGNITION");
delay(1000);
lcd.clear();
digitalWrite(ledAbort, LOW);
digitalWrite(ledStage, LOW);
delay(500);
stageConfirm = digitalRead(buttonStage);
abortConfirm = digitalRead(buttonAbort);
if (stageConfirm == LOW) {
noTone(buzzer);
digitalWrite(ledStage, HIGH);
digitalWrite(ledAbort, LOW);
lcdShow(" LIFT OFF", "HAVE A SAFE FLIGHT");
break;
} else if (abortConfirm == LOW) {
break;
}
Serial.println(stageConfirm);
Serial.println(abortConfirm);
break;
case SET_THRUST:
if (key >= '0' && key <= '9' && thrust <= 9999) {
lcd.print(key);
thrust = 10 * thrust + (key - '0');
} else if (key == '#') {
lcdShow("TWR CALCULATOR", "WEIGHT: ");
state = SET_WEIGHT;
} else if (key == '*') {
state = START;
}
break;
case SET_WEIGHT:
if (key >= '0' && key <= '9' && weight <= 9999) {
lcd.print(key);
weight = 10 * weight + (key - '0');
} else if (key == '#') {
lcdShow("THRUST-TO-WEIGHT", "RATIO: ");
if (thrust != 0 || weight != 0) {
ratio = thrust / weight;
lcdShowResult(ratio);
}
} else if (key == '*') {
state = START;
}
break;
}
}
1 Answer 1
This code is reading the buttons at two places in loop()
, but the
first time the results are ignored. Only the readings done in the LAUNCH
state can have any effect. The problem is that the reading is done only
at one very specific time, 1.5 seconds after displaying "CONFIRM
IGNITION". If you press the button before or after this moment, it has
no effect.
Reading only once per loop iteration is fine, as long as the loop turns
fast enough, without getting stuck in a delay()
. As a general rule,
you try to avoid delay()
whenever you want to write a program that
should react to external events such as button presses. See the Arduino
example Blink without delay for a technique for achieving that. A
finite state machine is a good way to program without delays, and
fortunately you are already working with one. To detect a button press,
you typically use two distinct states:
- a "wait for button press" state that is sensitive to the button
- a "button has been pressed" state that is reached when the user presses the button.
There can be multiple "button pressed" states if there are multiple buttons to wait for. The transition from the "waiting" state to the others is then conditioned on the button presses:
case WAIT_FOR_BUTTON_PRESS:
if (digitalRead(button_1_pin) == LOW) {
do_the_action_relevant_to_button_1();
state = BUTTON_1_HAS_BEEN_PRESSED;
} else if (digitalRead(button_2_pin) == LOW) {
do_the_action_relevant_to_button_2();
state = BUTTON_2_HAS_BEEN_PRESSED;
}
break;
Obviously you would choose state names that are more relevant for your specific project.
/* ... */
music stuff is distracting. Why do you keep it? Would you mind creating a copy of your program and remove everything that is not related to the button problem, e.g. the LCD intro insetup()
. That would help as well.