GameCounter/Source/src/buttons.c

197 lines
3.8 KiB
C

#include "buttons.h"
#include <avr/interrupt.h>
#include <util/atomic.h>
ButtonState buttonUp;
ButtonState buttonDown;
ButtonState buttonOption;
void button_init(ButtonState* state)
{
(*state).flags = BSFLastReported;
(*state).debounceTicks = 0;
(*state).repeatTicks = 0;
}
void buttons_init()
{
button_init(&buttonUp);
button_init(&buttonDown);
button_init(&buttonOption);
// Input with internal pull-up resistor
DDRB &= ~BUTTON_ALL;
PORTB |= BUTTON_ALL;
// 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();
}
// 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 flagSet(flags, value) ((flags & value) != 0)
#define setFlag(flags, value) flags |= (value)
#define clearFlag(flags, value) flags &= ~(value)
void button_check(ButtonState* state, uint8_t readState)
{
#define stateRef (*state)
uint8_t lastState = flagSet(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 changed, 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 (flagSet(stateRef.flags, BSFRepeatEnabled))
{
// Held pressed
stateRef.repeatTicks--;
if (stateRef.repeatTicks == 0)
{
stateRef.repeatTicks = REPEAT_NEXT;
setFlag(stateRef.flags, BSFRepeated);
}
}
}
else if (isPressed(lastState))
{
// Released
setFlag(stateRef.flags, BSFReleased);
}
#undef stateRef
}
// every 10ms
ISR(TIMER0_COMPA_vect)
{
uint8_t pinState = PINB;
button_check(&buttonUp, flagSet(pinState, _BV(BUTTON_UP)));
button_check(&buttonDown, flagSet(pinState, _BV(BUTTON_DOWN)));
button_check(&buttonOption, flagSet(pinState, _BV(BUTTON_OPTION)));
}
inline uint8_t readAndClearFlag(ButtonState* state, uint8_t flag)
{
uint8_t result;
ATOMIC_BLOCK(ATOMIC_FORCEON)
{
result = flagSet((*state).flags, flag);
(*state).flags &= ~(flag);
}
return result;
}
uint8_t button_is_pressed(ButtonState* state)
{
return readAndClearFlag(state, BSFPressed);
}
uint8_t button_is_released(ButtonState* state)
{
return readAndClearFlag(state, BSFReleased);
}
uint8_t button_is_repeated(ButtonState* state)
{
return readAndClearFlag(state, BSFRepeated);
}
uint8_t button_is_pressed_or_repeated(ButtonState* state)
{
return readAndClearFlag(state, BSFPressed | BSFRepeated);
}
uint8_t button_is_released_short(ButtonState* state)
{
uint8_t result;
ATOMIC_BLOCK(ATOMIC_FORCEON)
{
result = flagSet((*state).flags, BSFReleased) &&
(!flagSet((*state).flags, BSFRepeated));
if (result)
(*state).flags &= ~BSFReleased;
}
return result;
}
uint8_t button_is_pressed_long(ButtonState* state)
{
uint8_t result;
ATOMIC_BLOCK(ATOMIC_FORCEON)
{
result = flagSet((*state).flags, BSFPressed) &&
flagSet((*state).flags, BSFRepeated);
// Remove RepeatEnabled as well to prevent further repeat events
if (result)
(*state).flags &= ~(BSFPressed | BSFRepeated | BSFRepeatEnabled);
}
return result;
}