From 18268280dc6c0bd44035b53655ae43e9c88245bd Mon Sep 17 00:00:00 2001 From: Akshay Nair Date: Sat, 28 Feb 2026 17:37:19 +0530 Subject: Add set time mode + test for button --- justfile | 8 ++---- src/button.c | 53 ++++++++++++++++------------------ src/button.h | 17 ++++++----- src/display.c | 34 ++++++++++++++++++---- src/display.h | 14 --------- src/main.c | 2 +- src/mode.c | 74 ++++++++++++++++++++++++++++++++++++++++++++++++ src/mode.h | 22 ++++++++++++++ src/rtc.c | 23 +++++++++------ src/rtc.h | 4 +++ test/button_test.c | 67 +++++++++++++++++++++++++++++++++++++++++++ test/justfile | 13 +++++++++ test/stubbed/avr/io.h | 46 +++++++++++++++--------------- test/stubbed/testindex.c | 26 +++++++++++++++++ 14 files changed, 310 insertions(+), 93 deletions(-) create mode 100644 src/mode.c create mode 100644 src/mode.h create mode 100644 test/button_test.c create mode 100644 test/justfile create mode 100644 test/stubbed/testindex.c diff --git a/justfile b/justfile index e08c5fb..67ac3f0 100644 --- a/justfile +++ b/justfile @@ -1,6 +1,8 @@ set export set unstable +mod test + default: just --choose || true @@ -8,7 +10,7 @@ AVR_LIBC := env("AVR_LIBC", "/usr/lib/avr") F_CPU := "32768" MCU := "attiny84a" MCU_PART := "attiny84" -SRCS := "src/main.c src/display.c src/rtc.c src/button.c" +SRCS := "src/main.c src/display.c src/rtc.c src/button.c src/mode.c" OUTDIR := "./out" PROGRAMMER := "arduino" UPLOAD_SPEED := "19200" @@ -40,10 +42,6 @@ write-fuses: check-hexfile-exists read-fuses: {{AVRDUDE}} -U lfuse:r:-:h -U hfuse:r:-:h -U efuse:r:-:h -test: - gcc -g -std=gnu99 -DSTUBBED=1 -I./src -I./test/stubbed/ -Wall -Wno-unused-variable {{SRCS}} -o {{OUTDIR}}/test - {{OUTDIR}}/test - clean: rm -rf {{OUTDIR}} diff --git a/src/button.c b/src/button.c index dc52265..f7c2a07 100644 --- a/src/button.c +++ b/src/button.c @@ -2,42 +2,27 @@ #include #include "button.h" -#include "rtc.h" - -enum ButtonState check_button_state(); - -void button_tick() { - enum ButtonState button_state = check_button_state(); - switch (button_state) { - case BUTTON_PRESSED: - if (seconds <= 10) - seconds = 0; - else - seconds -= 10; - break; - case BUTTON_LONG_PRESSED: - seconds += 10; - break; - default: - break; - } -} +#include "mode.h" + +void button_tick() { apply_button_state(check_button_state()); } bool is_button_active = false; uint16_t button_tick_count = 0; +uint16_t button_inactive_for_ticks = 0; enum ButtonState check_button_state() { - if (is_button_active && button_tick_count >= LONG_PRESS_TICK_COUNT) { - button_tick_count = 0; - is_button_active = false; - return BUTTON_LONG_PRESSED; + // Disable button for some time + if (button_inactive_for_ticks > 0) { + button_inactive_for_ticks--; + return ButtonIdle; } - bool in_debounce_period = is_button_active && button_tick_count > 0 && - button_tick_count < DEBOUNCE_TICK_COUNT; + bool was_active = is_button_active; + bool in_debounce_period = + button_tick_count > 0 && button_tick_count < DEBOUNCE_TICK_COUNT; // Ignore readings from debounce period - if (!in_debounce_period) { + if (!is_button_active || !in_debounce_period) { DDRA &= ~(1 << PA0); PORTA &= ~(1 << PA0); is_button_active = (PINA & (1 << PA0)) == 0; @@ -45,12 +30,22 @@ enum ButtonState check_button_state() { if (is_button_active) { button_tick_count++; + if (button_tick_count >= LONG_PRESS_TICK_COUNT) { + button_tick_count = 0; + is_button_active = false; + button_inactive_for_ticks = BUTTON_INACTIVE_TICK_COUNT; + return ButtonLongPressed; + } + return ButtonActive; } else { if (button_tick_count >= DEBOUNCE_TICK_COUNT) { button_tick_count = 0; - return BUTTON_PRESSED; + button_inactive_for_ticks = BUTTON_INACTIVE_TICK_COUNT; + return ButtonPressed; } + if (!was_active) + button_tick_count = 0; } - return BUTTON_IDLE; + return ButtonIdle; } diff --git a/src/button.h b/src/button.h index 8aa7442..5152336 100644 --- a/src/button.h +++ b/src/button.h @@ -1,19 +1,22 @@ #ifndef _LOADED_BUTTON #define _LOADED_BUTTON #include -#include + +#include "mode.h" #define DEBOUNCE_TICK_COUNT 10 #define LONG_PRESS_TICK_COUNT 60 #define MAX_TICK_COUNT 80 +#define BUTTON_INACTIVE_TICK_COUNT 10 + +bool extern is_button_active; -enum ButtonState { - BUTTON_IDLE = 0, - BUTTON_PRESSED = 1, - BUTTON_LONG_PRESSED = 2, -}; +void button_tick(); +enum ButtonState check_button_state(); + +// Exposed for test uint16_t extern button_tick_count; +uint16_t extern button_inactive_for_ticks; -void button_tick(); #endif diff --git a/src/display.c b/src/display.c index 8380b81..3d83337 100644 --- a/src/display.c +++ b/src/display.c @@ -4,8 +4,17 @@ #include #include "display.h" +#include "mode.h" #include "rtc.h" +// "_gfedcba" +// Reversed because a-g is mapped to pa0-6. (pa7 is decimal) +// Complemented because display is common anode +static const uint32_t digit_masks[] = { + 0b1000000, 0b1111001, 0b0100100, 0b0110000, 0b0011001, + 0b0010010, 0b0000010, 0b1111000, 0b0000000, 0b0010000, +}; + void display_setup() { // Set prescaler CS01 + CS00 (F_CPU / 64) + CTC // For 32768Hz -> 32768/64 = 512Hz @@ -26,11 +35,27 @@ void display_setup() { PORTB = (1 << PB2); } +void write_time(uint8_t digit_index, const uint8_t digits[4]); + +// Current digit position volatile uint8_t current_digit = 0; // Render current digit void display_render(void) { - write_time(current_digit); + switch (current_mode) { + case ModeDisplayTime: { + uint8_t digits[] = {minutes_digit1, minutes_digit2, seconds_digit1, + seconds_digit2}; + write_time(current_digit, digits); + break; + } + case ModeSetTime: { + uint8_t digits[] = {new_hours / 10, new_hours % 10, new_minutes / 10, + new_minutes % 10}; + write_time(current_digit, digits); + break; + } + } // Cycle through the 4 digits current_digit = (current_digit + 1) % 4; @@ -53,14 +78,11 @@ static uint8_t special_segment_pins[] = {5, 10, 6, 7}; // Digit position -> special segment anti-pin (pair of pin) static uint8_t special_segment_antipins[] = {10, 5, 7, 6}; -void write_time(uint8_t digit_index) { +void write_time(uint8_t digit_index, const uint8_t digits[4]) { // reset pins uint16_t ddr = ~SPECIAL_SEGMENT_PINS; uint16_t port = 0; - uint8_t digits[] = {seconds_digit1, seconds_digit2, minutes_digit1, - minutes_digit2}; - uint8_t digit = digits[digit_index]; uint16_t segments = digit_masks[digit]; uint8_t pin = special_segment_pins[digit_index]; @@ -92,7 +114,7 @@ void write_time(uint8_t digit_index) { } DDRA = ddr; - DDRB = ddr >> 8; PORTA = port; + DDRB = ddr >> 8; PORTB = port >> 8; } diff --git a/src/display.h b/src/display.h index 44c0c0e..0353705 100644 --- a/src/display.h +++ b/src/display.h @@ -1,22 +1,8 @@ #ifndef _LOADED_DISPLAY #define _LOADED_DISPLAY -#include - -// "_gfedcba" -// Reversed because a-g is mapped to pa0-6. (pa7 is decimal) -// Complemented because display is common anode -static const uint32_t digit_masks[] = { - 0b1000000, 0b1111001, 0b0100100, 0b0110000, 0b0011001, - 0b0010010, 0b0000010, 0b1111000, 0b0000000, 0b0010000, -}; - -// Current digit position -volatile extern uint8_t current_digit; void display_setup(void); -void write_time(uint8_t digit_index); - void display_render(void); #endif diff --git a/src/main.c b/src/main.c index 98b63a2..2e20b7b 100644 --- a/src/main.c +++ b/src/main.c @@ -28,7 +28,7 @@ ISR(TIM0_COMPA_vect, ISR_FLATTEN) { ISR(TIM1_COMPA_vect, ISR_FLATTEN) { rtc_increment(); } // Button interrupt -ISR(PCINT0_vect, ISR_FLATTEN) { button_tick(); } +// ISR(PCINT0_vect, ISR_FLATTEN) { button_tick(); } int main(void) { PRR = 0b0011; // Power reduction register. Shut down USI and ADC diff --git a/src/mode.c b/src/mode.c new file mode 100644 index 0000000..b6fad97 --- /dev/null +++ b/src/mode.c @@ -0,0 +1,74 @@ +#include + +#include "mode.h" +#include "rtc.h" + +enum Mode current_mode = ModeDisplayTime; +volatile uint8_t new_minutes = 0; +volatile uint8_t new_hours = 0; +volatile uint8_t current_digit_index = 0; +volatile uint16_t time_set_timeout_ticks = 0; + +void increment_digit() { + switch (current_digit_index) { + case 0: + new_hours = (new_hours + 10) % 24; + break; + case 1: + new_hours = (new_hours + 1) % 24; + break; + case 2: + new_minutes = (new_minutes + 10) % 60; + break; + case 3: + new_minutes = (new_minutes + 1) % 60; + break; + } +} + +void apply_button_state(enum ButtonState button_state) { + switch (current_mode) { + case ModeDisplayTime: + switch (button_state) { + case ButtonLongPressed: + new_minutes = minutes; + new_hours = hours; + current_digit_index = 0; + current_mode = ModeSetTime; + break; + default: + break; + } + break; + case ModeSetTime: + if (button_state == ButtonIdle) { + time_set_timeout_ticks = + time_set_timeout_ticks == 0 ? 0 : time_set_timeout_ticks - 1; + if (time_set_timeout_ticks == 0) { + current_mode = ModeDisplayTime; + return; + } + } else { + time_set_timeout_ticks = 10 * 1024; + } + + switch (button_state) { + case ButtonPressed: + increment_digit(); + break; + case ButtonLongPressed: + if (current_digit_index == 3) { + rtc_set_minutes(new_minutes); + rtc_set_hours(new_hours); + current_mode = ModeDisplayTime; + current_digit_index = 0; + } else { + current_digit_index++; + } + break; + default: + break; + } + break; + } +} diff --git a/src/mode.h b/src/mode.h new file mode 100644 index 0000000..db65fc8 --- /dev/null +++ b/src/mode.h @@ -0,0 +1,22 @@ +#ifndef _LOADED_MODE +#define _LOADED_MODE +#include +enum Mode { + ModeDisplayTime, + ModeSetTime, +}; + +enum ButtonState { + ButtonIdle = 0, + ButtonActive = 1, + ButtonPressed = 2, + ButtonLongPressed = 3, +}; + +extern enum Mode current_mode; +extern volatile uint8_t new_minutes; +extern volatile uint8_t new_hours; + +void apply_button_state(enum ButtonState button_state); + +#endif diff --git a/src/rtc.c b/src/rtc.c index 53a5983..bbf79e1 100644 --- a/src/rtc.c +++ b/src/rtc.c @@ -38,13 +38,20 @@ void rtc_increment(void) { seconds_digit2 = (seconds / 10) % 10; if (s >= 60) { uint8_t m = minutes + 1; - minutes = m % 60; - minutes_digit1 = minutes % 10; - minutes_digit2 = (minutes / 10) % 10; - if (m >= 60) { - hours = (hours + 1) % 24; - hours_digit1 = hours % 10; - hours_digit2 = (hours / 10) % 10; - } + rtc_set_minutes(m); + if (m >= 60) + rtc_set_hours(hours + 1); } } + +void rtc_set_minutes(uint8_t m) { + minutes = m % 60; + minutes_digit1 = minutes % 10; + minutes_digit2 = (minutes / 10) % 10; +} + +void rtc_set_hours(uint8_t h) { + hours = h % 24; + hours_digit1 = hours % 10; + hours_digit2 = (hours / 10) % 10; +} diff --git a/src/rtc.h b/src/rtc.h index c1c1730..991c188 100644 --- a/src/rtc.h +++ b/src/rtc.h @@ -17,4 +17,8 @@ void rtc_setup(void); void rtc_increment(void); +void rtc_set_minutes(uint8_t m); + +void rtc_set_hours(uint8_t h); + #endif diff --git a/test/button_test.c b/test/button_test.c new file mode 100644 index 0000000..3c48e43 --- /dev/null +++ b/test/button_test.c @@ -0,0 +1,67 @@ +#include "button.h" +#include "mode.h" +#include +#include + +void test_button_press() { + assert(check_button_state() == ButtonIdle); + + PINA &= ~(1 << PA0); // Button is pressed + assert(check_button_state() == ButtonActive); + for (int i = 0; i < DEBOUNCE_TICK_COUNT; i++) + assert(check_button_state() == ButtonActive); + + PINA |= (1 << PA0); // Button is released + + assert(check_button_state() == ButtonPressed); // Detects button press + assert(check_button_state() == ButtonIdle); // Resets to idle +} + +void test_button_long_press() { + assert(check_button_state() == ButtonIdle); + + // Button is pressed for a while + PINA &= ~(1 << PA0); + for (int i = 0; i < LONG_PRESS_TICK_COUNT - 1; i++) + assert(check_button_state() == ButtonActive); + + assert(check_button_state() == ButtonLongPressed); // Detects button press + + assert(check_button_state() == ButtonIdle); // Resets to idle +} + +void test_button_inactive_after_press() { + assert(check_button_state() == ButtonIdle); + + // Button is pressed + PINA &= ~(1 << PA0); + for (int i = 0; i < DEBOUNCE_TICK_COUNT; i++) + assert(check_button_state() == ButtonActive); + PINA |= (1 << PA0); // Button is released + assert(check_button_state() == ButtonPressed); + assert(check_button_state() == ButtonIdle); // Resets to idle + + // Button is pressed again + PINA &= ~(1 << PA0); + + // Button stays inactive for some time + for (int i = 0; i < BUTTON_INACTIVE_TICK_COUNT - 1; i++) + assert(check_button_state() == ButtonIdle); + // Button becomes active again after delay + assert(check_button_state() == ButtonActive); +} + +void before_each() { + PINA = (1 << PA0); // Active on low + button_tick_count = 0; + button_inactive_for_ticks = 0; +} +#define TEST before_each(); + +int main() { + TEST test_button_press(); + TEST test_button_long_press(); + TEST test_button_inactive_after_press(); + return 0; +} + diff --git a/test/justfile b/test/justfile new file mode 100644 index 0000000..626e094 --- /dev/null +++ b/test/justfile @@ -0,0 +1,13 @@ +OUTDIR := "../out" +TSRCS := "../src/display.c ../src/rtc.c ../src/button.c ../src/mode.c ./stubbed/testindex.c" + +TCFLAGS := f"-g -std=gnu99 -DSTUBBED=1 -I../src -I./stubbed/ -Wall -Wno-unused-variable {{TSRCS}}" + +main: + gcc {{TCFLAGS}} ../src/main.c -o {{OUTDIR}}/test_main + {{OUTDIR}}/test_main + +button: + gcc {{TCFLAGS}} ./button_test.c -o {{OUTDIR}}/test_button + {{OUTDIR}}/test_button + diff --git a/test/stubbed/avr/io.h b/test/stubbed/avr/io.h index 40cc460..c056ef1 100644 --- a/test/stubbed/avr/io.h +++ b/test/stubbed/avr/io.h @@ -2,13 +2,13 @@ #define STUB_AVR_IO #include -static int DDRA = 0; -static int DDRB = 0; +extern int DDRA; +extern int DDRB; -static int PORTA = 0; -static int PORTB = 0; -static int PINA = 0; -static int PINB = 0; +extern int PORTA; +extern int PORTB; +extern int PINA; +extern int PINB; #define PA0 0 #define PA1 1 @@ -24,23 +24,23 @@ static int PINB = 0; #define PB3 3 #define PB4 4 -static int PRR = 0; -static int TCCR0B = 0; -static int TCCR0A = 0; -static int TIMSK0 = 0; -static int TIFR0 = 0; -static int OCR0A = 0; -static int OCR0B = 0; -static int TCNT0 = 0; -static int TCCR1B = 0; -static int TCCR1A = 0; -static int TIMSK1 = 0; -static int TIFR1 = 0; -static int OCR1A = 0; -static int OCR1B = 0; -static int TCNT1 = 0; -static int TCNT1H = 0; -static int TCNT1L = 0; +extern int PRR; +extern int TCCR0B; +extern int TCCR0A; +extern int TIMSK0; +extern int TIFR0; +extern int OCR0A; +extern int OCR0B; +extern int TCNT0; +extern int TCCR1B; +extern int TCCR1A; +extern int TIMSK1; +extern int TIFR1; +extern int OCR1A; +extern int OCR1B; +extern int TCNT1; +extern int TCNT1H; +extern int TCNT1L; #define CS00 1 #define CS01 1 diff --git a/test/stubbed/testindex.c b/test/stubbed/testindex.c new file mode 100644 index 0000000..e3c8866 --- /dev/null +++ b/test/stubbed/testindex.c @@ -0,0 +1,26 @@ + +int DDRA = 0; +int DDRB = 0; + +int PORTA = 0; +int PORTB = 0; +int PINA = 0; +int PINB = 0; + +int PRR = 0; +int TCCR0B = 0; +int TCCR0A = 0; +int TIMSK0 = 0; +int TIFR0 = 0; +int OCR0A = 0; +int OCR0B = 0; +int TCNT0 = 0; +int TCCR1B = 0; +int TCCR1A = 0; +int TIMSK1 = 0; +int TIFR1 = 0; +int OCR1A = 0; +int OCR1B = 0; +int TCNT1 = 0; +int TCNT1H = 0; +int TCNT1L = 0; -- cgit v1.3.1