GameCounter/Source/src/buttons.c

225 lines
4.7 KiB
C

#include "buttons.h"
#include <avr/interrupt.h>
#include <util/atomic.h>
#include <util/delay.h>
ButtonState buttonUp;
ButtonState buttonDown;
ButtonState buttonOption;
// Since it's a pull-up, the value is low when the button is pressed.
// This macro just helps make the code more readable.
#define isPressed(state) (!state)
#define hasFlag(flags, value) ((flags & value) != 0)
#define setFlag(flags, value) flags |= (value)
#define clearFlag(flags, value) flags &= ~(value)
uint8_t inactivityTicks = 0;
void buttons_init()
{
// Input with internal pull-up resistor
DDRB &= ~BUTTON_ALL;
PORTB |= BUTTON_ALL;
buttons_reset();
// Mode 2: CTC
TCCR0A = _BV(WGM01);
// Set prescaler to divide by 1024
TCCR0B = _BV(CS02) | _BV(CS00);
// 10ms
OCR0A = XTAL / 1024.0 * 10e-3 - 1;
// Enable timer interrupt
TIMSK = _BV(OCIE0A);
sei();
}
void button_init(ButtonState* state, uint8_t readState)
{
(*state).flags = readState ? BSFLastReported : 0;
(*state).debounceTicks = 0;
(*state).repeatTicks = 0;
}
void buttons_reset()
{
uint8_t pinState = PINB;
button_init(&buttonUp, hasFlag(pinState, _BV(BUTTON_UP)));
button_init(&buttonDown, hasFlag(pinState, _BV(BUTTON_DOWN)));
button_init(&buttonOption, hasFlag(pinState, _BV(BUTTON_OPTION)));
}
uint8_t buttons_active()
{
return inactivityTicks <= DEBOUNCE_TIME;
}
uint8_t button_check(ButtonState* state, uint8_t readState)
{
#define stateRef (*state)
uint8_t lastState = hasFlag(stateRef.flags, BSFLastReported);
uint8_t newState = lastState;
// Debounce
if (readState != lastState)
{
stateRef.debounceTicks++;
if (stateRef.debounceTicks >= DEBOUNCE_TIME)
{
// Pressed / released
newState = readState;
if (newState)
setFlag(stateRef.flags, BSFLastReported);
else
clearFlag(stateRef.flags, BSFLastReported);
}
}
else
{
// State reverted, reset debounce timer
stateRef.debounceTicks = 0;
}
// Check new state
if (isPressed(newState))
{
if (!isPressed(lastState))
{
// Pressed
clearFlag(stateRef.flags, BSFRepeated | BSFReleased);
setFlag(stateRef.flags, BSFPressed | BSFRepeatEnabled);
stateRef.repeatTicks = REPEAT_START;
}
else if (hasFlag(stateRef.flags, BSFRepeatEnabled))
{
// Held pressed
stateRef.repeatTicks--;
if (stateRef.repeatTicks == 0)
{
stateRef.repeatTicks = REPEAT_NEXT;
setFlag(stateRef.flags, BSFRepeated);
}
}
}
else if (isPressed(lastState))
{
// Only trigger Released if Pressed is not yet removed by polling
if (hasFlag(stateRef.flags, BSFPressed))
setFlag(stateRef.flags, BSFReleased);
}
return isPressed(newState) || (stateRef.debounceTicks > 0);
#undef stateRef
}
// every 10ms
ISR(TIMER0_COMPA_vect)
{
uint8_t pinState = PINB;
uint8_t buttonActive;
buttonActive = button_check(&buttonUp, hasFlag(pinState, _BV(BUTTON_UP)));
buttonActive = button_check(&buttonDown, hasFlag(pinState, _BV(BUTTON_DOWN))) || buttonActive;
buttonActive = button_check(&buttonOption, hasFlag(pinState, _BV(BUTTON_OPTION))) || buttonActive;
if (buttonActive)
inactivityTicks = 0;
else if (inactivityTicks <= DEBOUNCE_TIME)
inactivityTicks++;
}
inline uint8_t readAndClearFlag(ButtonState* state, uint8_t flag, uint8_t clearAdditionalFlag)
{
uint8_t result;
ATOMIC_BLOCK(ATOMIC_FORCEON)
{
result = hasFlag((*state).flags, flag);
if (result)
(*state).flags &= ~(flag | clearAdditionalFlag);
}
return result;
}
uint8_t button_is_pressed(ButtonState* state)
{
return readAndClearFlag(state, BSFPressed, 0);
}
uint8_t button_is_released(ButtonState* state)
{
return readAndClearFlag(state, BSFReleased, BSFPressed);
}
uint8_t button_is_repeated(ButtonState* state)
{
return readAndClearFlag(state, BSFRepeated, 0);
}
uint8_t button_is_pressed_or_repeated(ButtonState* state)
{
return readAndClearFlag(state, BSFPressed | BSFRepeated, 0);
}
uint8_t button_is_pressed_short(ButtonState* state)
{
uint8_t result;
ATOMIC_BLOCK(ATOMIC_FORCEON)
{
result = hasFlag((*state).flags, BSFPressed) &&
hasFlag((*state).flags, BSFReleased) &&
(!hasFlag((*state).flags, BSFRepeated));
if (result)
(*state).flags &= ~(BSFPressed | BSFReleased);
}
return result;
}
uint8_t button_is_pressed_long(ButtonState* state)
{
uint8_t result;
ATOMIC_BLOCK(ATOMIC_FORCEON)
{
result = hasFlag((*state).flags, BSFPressed) &&
hasFlag((*state).flags, BSFRepeated);
// Remove RepeatEnabled as well to prevent further repeat events
if (result)
(*state).flags &= ~(BSFPressed | BSFRepeated | BSFRepeatEnabled);
}
return result;
}