225 lines
4.7 KiB
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;
|
|
} |