#include "buttons.h" #include #include 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; }