#include <Wire.h>
#include <Arduino.h>
// ****************************************
// Low-Level Serial driver.
#define DEBUG_SERIAL 1
#define SERIAL_BAUD 115200
void setupSerial() {
Serial.begin(SERIAL_BAUD);
#ifdef DEBUG_SERIAL
Serial.println("Serial ready!");
#endif
}
// ****************************************
// Low-Level Button driver.
// define this to turn on serial output.
#define DEBUG_BUTTONS 1
// number of shift registers you have chained together.
#define NUM_SHIFT_REGISTERS 1
// this code can only handle 8 buttons because it's set up to use only
// one shift register. You can add more shift registers by chaining them
// to each other, though (see shift register datasheet). This means,
// theoretically, with small modifications to the circuit and this code,
// there's no limit to the number of buttons you can support, while
// still using only 4 Arduino pins. In reality, though, you'll probably
// start having problems detecting button presses at some point because
// the loop will become too slow.
#define MAX_BUTTONS (8 * NUM_SHIFT_REGISTERS)
// this is the default number of millis between button checks.
// if you're having trouble detecting button presses,
// set this to a lower number or even just 0.
// If you need more CPU for other parts of your
// script, make this a higher number.
#define DEFAULT_DELAY_MILLIS 50L
enum ButtonAction { None, Up, Down };
// used to store context for each button.
typedef struct ButtonContext {
uint8_t button;
uint8_t state; // HIGH or LOW
unsigned long lastStateChangeMillis;
ButtonAction action; // last ButtonAction
} ButtonContext;
class Buttons {
public:
// numButtons: number of buttons to strobe. Each button is addressed from 0 ... numButtons.
// inputPin: button input pin.
// latchPin Shift Register overhead
// dataPin Shift Register overhead
// clockPin Shift Register overhead
// default delayMillis
Buttons(uint8_t numButtons, uint8_t inputPin, uint8_t latchPin, uint8_t dataPin, uint8_t clockPin, unsigned long delay = DEFAULT_DELAY_MILLIS);
// destructor.
// not sure if this is ever used in Arduino world.
~Buttons(void);
// this MUST BE CALLED EVERY LOOP in your script, or
// none of the features of this class will work properly!
void callEveryLoop();
// returns a ButtonAction.
// when you read the current button action, the action
// is reset to NONE for that button, until the user
// does something else with it.
// if whichButton is illegal, returns NONE.
ButtonAction getButtonAction(uint8_t whichButton);
private:
uint8_t numButtons;
uint8_t inputPin;
uint8_t latchPin;
uint8_t dataPin;
uint8_t clockPin;
uint8_t strobeCurButton;
unsigned long delayMillis;
unsigned long checkButtonTimeout;
// malloc'd dynamically in constructor.
ButtonContext *buttonContexts;
uint8_t *strobeNextButton;
};
// ---------------------------------------------------------------------------
// Buttons constructor
// ---------------------------------------------------------------------------
Buttons::Buttons(uint8_t nb, uint8_t ip, uint8_t lp, uint8_t dp, uint8_t cp, unsigned long delay) {
numButtons = nb;
if (numButtons > MAX_BUTTONS) {
#ifdef DEBUG_BUTTONS
Serial.print("Too many buttons: ");
Serial.println(numButtons);
#endif
while (true) ;
}
inputPin = ip;
latchPin = lp;
dataPin = dp;
clockPin = cp;
delayMillis = delay;
// can't know ahead of time how many buttons are needed,
// so have to use malloc() for the vars which depend on
// numButtons.
buttonContexts = (ButtonContext*)malloc(numButtons * sizeof(ButtonContext));
// next button strobe array (avoids an if stmt in time-critical function).
strobeNextButton = (uint8_t*)malloc(numButtons * sizeof(uint8_t));
// initialize the arrays.
for (uint8_t i=0; i<numButtons; i++) {
buttonContexts[i].button = i;
buttonContexts[i].state = LOW; // means "not pressed".
buttonContexts[i].lastStateChangeMillis = 0L;
buttonContexts[i].action = None;
// index of next button (avoids an if stmt).
strobeNextButton[i] = i+1;
#ifdef DEBUG_BUTTONS
Serial.print("Initialized Button ");
Serial.println(i);
#endif
}
// last one leads back to index 0.
strobeNextButton[numButtons-1] = 0;
strobeCurButton = numButtons-1;
// init the Arduino pins.
pinMode(inputPin, INPUT_PULLDOWN);
pinMode(latchPin, OUTPUT);
pinMode(dataPin, OUTPUT);
pinMode(clockPin, OUTPUT);
// prep next strobe action timeout.
checkButtonTimeout = 0;
#ifdef DEBUG_BUTTONS
Serial.println("Buttons ready.");
#endif
}
// not sure if this is ever called in Arduino world.
Buttons::~Buttons(void) {
#ifdef DEBUG_BUTTONS
Serial.print("Destructor of Buttons called.");
#endif
free(buttonContexts);
}
// API (public) methods.
ButtonAction Buttons::getButtonAction(uint8_t whichButton) {
// sanity check.
if ((whichButton < 0) || (whichButton >= numButtons)) {
return None;
}
ButtonAction action = buttonContexts[whichButton].action;
// once the action is read, we reset it to NONE.
buttonContexts[whichButton].action = None;
return action;
}
// loop overhead.
void Buttons::callEveryLoop() {
if (millis() > checkButtonTimeout) {
// check the next button
strobeCurButton = strobeNextButton[strobeCurButton];
// tell the shift-registers we're sending a new value to shift.
digitalWrite(latchPin, LOW);
// if you've chained more shift registers, this is where you'd be outputting
// all zeroes to all the SRs which aren't in play, and doing the following
// just for the one which controls strobeCurButton.
// shift out the pattern which activates only this button.
shiftOut(dataPin, clockPin, MSBFIRST, (uint8_t)0x01 << strobeCurButton);
// tell the shift register we're done shifting and it can now
// send the pattern out its output pins.
digitalWrite(latchPin, HIGH);
// at this point, all buttons are inactive EXCEPT for strobeCurButton,
// which mean it is the only one which can be sending a signal to the
// input pin. So, the the input pin reflects the state of just this one
// button: HIGH or LOW.
uint8_t buttonState = digitalRead(inputPin);
// interpret the button state.
if (buttonState != buttonContexts[strobeCurButton].state) {
// button state has changed since last call.
buttonContexts[strobeCurButton].action = (buttonState == LOW) ? Up : Down;
// update these.
buttonContexts[strobeCurButton].state = buttonState;
buttonContexts[strobeCurButton].lastStateChangeMillis = millis();
}
// set timeout for next button check.
checkButtonTimeout = millis() + delayMillis;
}
}
// Application level.
// max of 8!
#define NUM_BUTTONS 8
// Arduino pins. No matter how many buttons you have,
// you'll never need more Ard pins.
#define CLOCK_PIN 27 // 74HC595 SH_CP pin 11
#define LATCH_PIN 26 // 74HC595 ST_CP pin 12
#define DATA_PIN 25 // 74HC595 DS pin 14
#define INPUT_PIN 14 // A0
Buttons *buttons;
void setupButtons() {
buttons = new Buttons(NUM_BUTTONS, INPUT_PIN, LATCH_PIN, DATA_PIN, CLOCK_PIN);
}
// ****************************************
// API Level
//void readSensors() {
//}
void makeDecisions() {
for (uint8_t i=0; i<NUM_BUTTONS; i++) {
uint8_t action = buttons->getButtonAction(i);
if (action != None) {
Serial.print("Button ");
Serial.print(i);
switch (action) {
case Up:
Serial.println(" Up!");
break;
case Down:
Serial.println(" Down!");
break;
default:
Serial.print(": ");
Serial.print(action);
Serial.println(": Huh?");
break;
}
}
}
}
//void takeActions() {
//}
// Highest Level
void setup() {
Wire.begin();
// set up all the subsystems.
setupSerial();
setupButtons();
}
void loop() {
// readSensors();
makeDecisions();
// takeActions();
// housekeeping
buttons->callEveryLoop();
}