Last active
June 18, 2022 11:37
Revisions
-
julznc revised this gist
Jun 18, 2022 . No changes.There are no files selected for viewing
-
julznc revised this gist
Jun 18, 2022 . No changes.There are no files selected for viewing
-
julznc created this gist
Jun 18, 2022 .There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -0,0 +1,584 @@ #include <Wire.h> #include <Adafruit_GFX.h> #include <Adafruit_SSD1306.h> #define MAIN_BTN_PIN (2) // PA14 #define P1_BTN_PIN (3) // PA9 #define P2_BTN_PIN (5) // PA15 #define LED_PIN LED_BUILTIN // (13) // 100ms timer interrupt #define K_TIMER_COMP (3277) // 32768 = 1 sec #define K_TIMER_CORR ((0<<7) | 0) // bit7 -sign; bit[6:0] magnitude #define SCREEN_WIDTH (128) // OLED display width, in pixels #define SCREEN_HEIGHT (32) // OLED display height, in pixels #define SCREEN_ADDRESS (0x3C) Adafruit_SSD1306 oled(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, -1); enum state_et { STATE_INIT, STATE_RUN, STATE_PAUSE, STATE_CONFIG } e_state; enum mode_et { MODE_3min_2sec, // 3+2 MODE_5min, // 5 min MODE_10min, // 10 min MODE_90min, // 1.5 hr } e_mode; enum config_state_et { CONFIG_RETURN, CONFIG_RESTART, CONFIG_3min_2sec, // 3+2 CONFIG_5min, // 5 min CONFIG_10min, // 10 min CONFIG_90min, // 1.5 hr } e_config_state; typedef enum { BUTTON_IDLE = 0, BUTTON_SHORT_PRESSED = 1, BUTTON_LONG_PRESSED = 2, BUTTON_PRESSED = BUTTON_SHORT_PRESSED | BUTTON_LONG_PRESSED, } e_button_status; volatile uint32_t u32_100ms_counter; volatile uint32_t u32_last_active; volatile uint32_t u32_main_btn_released; volatile uint32_t u32_main_btn_pressed; volatile uint32_t u32_p1_btn_pressed; volatile uint32_t u32_p2_btn_pressed; e_button_status btn_main; e_button_status btn_p1; e_button_status btn_p2; struct stats_st { volatile uint32_t u32_time; // x 0.1s volatile bool b_running; // true = continue countdown uint16_t u16_moves; } s_p1_stats, s_p2_stats; static void mainBtnToggled(void) { if (LOW == digitalRead(MAIN_BTN_PIN)) { u32_main_btn_pressed = u32_100ms_counter; } else { u32_main_btn_released = u32_100ms_counter; } u32_last_active = u32_100ms_counter; } static void p1BtnPressed(void) { u32_p1_btn_pressed = u32_100ms_counter; u32_last_active = u32_100ms_counter; } static void p2BtnPressed(void) { u32_p2_btn_pressed = u32_100ms_counter; u32_last_active = u32_100ms_counter; } void RTC_Handler(void) { if (RTC->MODE0.INTFLAG.bit.CMP0) { //RTC->MODE0.INTFLAG.reg = 0xFF; // Clear all interrupts RTC->MODE0.INTFLAG.reg |= 0x81; // Clear CMP0 and OVF interrupts only u32_100ms_counter++; if (STATE_RUN == e_state) { if (s_p1_stats.b_running && s_p1_stats.u32_time) { s_p1_stats.u32_time--; } if (s_p2_stats.b_running && s_p2_stats.u32_time) { s_p2_stats.u32_time--; } } } } void getButtonStatus(void) { static bool b_long_pressed = false; // default status btn_main = BUTTON_IDLE; btn_p1 = BUTTON_IDLE; btn_p2 = BUTTON_IDLE; if (0 != u32_main_btn_released) { delay(10); // debounce? if (LOW == digitalRead(MAIN_BTN_PIN)) { // false trigger? } else if ((0 == u32_main_btn_pressed) || (u32_main_btn_pressed > u32_main_btn_released)) { // invalid ? } else if (true == b_long_pressed) { u32_main_btn_released = 0; } else if (u32_main_btn_released - u32_main_btn_pressed < 15) { btn_main = BUTTON_SHORT_PRESSED; u32_main_btn_pressed = 0; u32_main_btn_released = 0; } b_long_pressed = false; } else if (0 != u32_main_btn_pressed) { delay(10); // debounce? if (HIGH == digitalRead(MAIN_BTN_PIN)) { // false trigger? } else if (u32_100ms_counter - u32_main_btn_pressed > 25) { btn_main = BUTTON_LONG_PRESSED; b_long_pressed = true; u32_main_btn_pressed = 0; u32_main_btn_released = 0; } } if ((0 != u32_p1_btn_pressed) || (0 != u32_p2_btn_pressed)) { delay(10); // debounce? if ((0 != u32_p1_btn_pressed) && (LOW == digitalRead(P1_BTN_PIN))) { btn_p1 = BUTTON_PRESSED; } if ((0 != u32_p2_btn_pressed) && (LOW == digitalRead(P2_BTN_PIN))) { btn_p2 = BUTTON_PRESSED; } u32_p1_btn_pressed = 0; u32_p2_btn_pressed = 0; } } static inline void displayTime(struct stats_st *ps_stats, int xpos) { char str_time_buf[16]; uint32_t u32_100ms; oled.setTextSize(2); oled.setCursor(xpos, 12); u32_100ms = ps_stats->u32_time; if (u32_100ms < 200) { #if 0 snprintf(str_time_buf, sizeof(str_time_buf) - 1, "%3ld.%ld", u32_100ms / 10, u32_100ms % 10); oled.print(str_time_buf); #else snprintf(str_time_buf, sizeof(str_time_buf) - 1, "0:%02ld ", u32_100ms / 10); oled.print(str_time_buf); oled.setTextSize(1); oled.setCursor(oled.getCursorX() - 14, 10 + 7); snprintf(str_time_buf, sizeof(str_time_buf) - 1, ".%ld", u32_100ms % 10); oled.print(str_time_buf); #endif } else { u32_100ms /= 10; snprintf(str_time_buf, sizeof(str_time_buf) - 1, "%2ld:%02ld", u32_100ms / 60, u32_100ms % 60); oled.print(str_time_buf); } //oled.fillRect(xpos + 24, 30, 16, 1, ps_stats->b_running ? SSD1306_INVERSE : SSD1306_BLACK); oled.fillRect(xpos + 24, 30, 16, 2, ps_stats->b_running ? ((ps_stats->u32_time>>1) & SSD1306_INVERSE) : SSD1306_BLACK); } static inline void toggleConfigDisplay(config_state_et cfg) { switch(cfg) { case CONFIG_RETURN: oled.fillRect(0, 3, 44, 12, SSD1306_INVERSE); break; case CONFIG_RESTART: oled.fillRect(0, 18, 44, 12, SSD1306_INVERSE); break; case CONFIG_3min_2sec: oled.fillRect(62, 3, 22, 12, SSD1306_INVERSE); break; case CONFIG_5min: oled.fillRect(94, 3, 22, 12, SSD1306_INVERSE); break; case CONFIG_10min: oled.fillRect(62, 18, 22, 12, SSD1306_INVERSE); break; case CONFIG_90min: oled.fillRect(94, 18, 22, 12, SSD1306_INVERSE); break; } } static inline void applyMode(mode_et mode) { s_p1_stats.b_running = false; s_p2_stats.b_running = false; s_p1_stats.u16_moves = 0; s_p2_stats.u16_moves = 0; switch(mode) { case MODE_3min_2sec: s_p1_stats.u32_time = (3 * 60 * 10) + 20; s_p2_stats.u32_time = (3 * 60 * 10) + 20; break; case MODE_5min: s_p1_stats.u32_time = 5 * 60 * 10; s_p2_stats.u32_time = 5 * 60 * 10; break; case MODE_10min: s_p1_stats.u32_time = 10 * 60 * 10; s_p2_stats.u32_time = 10 * 60 * 10; break; case MODE_90min: s_p1_stats.u32_time = 90 * 60 * 10; s_p2_stats.u32_time = 90 * 60 * 10; break; } e_mode = mode; e_state = STATE_INIT; } void applyConfig(void) { switch(e_config_state) { case CONFIG_RESTART: applyMode(e_mode); break; case CONFIG_3min_2sec: applyMode(MODE_3min_2sec); break; case CONFIG_5min: applyMode(MODE_5min); break; case CONFIG_10min: applyMode(MODE_10min); break; case CONFIG_90min: applyMode(MODE_90min); break; } } void oledDisplay(void) { static state_et prev_state = STATE_INIT; static config_state_et prev_config = CONFIG_RETURN; if (STATE_CONFIG == e_state) { if (STATE_CONFIG != prev_state) { e_config_state = CONFIG_RETURN; prev_config = e_config_state; oled.clearDisplay(); oled.setTextSize(1); oled.setCursor(4, 5); oled.print("RETURN"); oled.setCursor(1, 20); oled.print("RESTART"); oled.setCursor(64, 5); oled.print("3+2"); oled.setCursor(96, 5); oled.print("5+0"); oled.setCursor(64, 20); oled.print("10m"); oled.setCursor(96, 20); oled.print("90m"); toggleConfigDisplay(e_config_state); } else if (prev_config != e_config_state) { toggleConfigDisplay(prev_config); toggleConfigDisplay(e_config_state); } } else { if (STATE_CONFIG == prev_state) { oled.clearDisplay(); } oled.drawLine(SCREEN_WIDTH/2, 10, SCREEN_WIDTH/2, SCREEN_HEIGHT - 1, SSD1306_WHITE); displayTime(&s_p1_stats, 0); displayTime(&s_p2_stats, SCREEN_WIDTH/2 + 4); oled.setTextSize(1); if (STATE_PAUSE == e_state) { oled.setCursor(SCREEN_WIDTH/2-17, 0); oled.print("PAUSED"); } else { if (STATE_PAUSE == prev_state) { oled.fillRect(0, 0, SCREEN_WIDTH - 1, 10, SSD1306_BLACK); } uint16_t u16_moves = max(s_p1_stats.u16_moves, s_p2_stats.u16_moves); oled.setCursor(SCREEN_WIDTH / 2 - (u16_moves > 9 ? 4 : 2), 0); oled.print(u16_moves); } } oled.display(); prev_state = e_state; prev_config = e_config_state; } void setup() { pinMode(LED_PIN, OUTPUT); pinMode(MAIN_BTN_PIN, INPUT_PULLUP); pinMode(P1_BTN_PIN, INPUT_PULLUP); pinMode(P2_BTN_PIN, INPUT_PULLUP); if (!oled.begin(SSD1306_SWITCHCAPVCC, SCREEN_ADDRESS)) { while (1) { __asm("nop"); } // halt! } oled.setTextColor(SSD1306_WHITE, SSD1306_BLACK); oled.cp437(true); oled.clearDisplay(); oled.display(); setupRTC(K_TIMER_COMP, K_TIMER_CORR); attachInterrupt(digitalPinToInterrupt(MAIN_BTN_PIN), mainBtnToggled, CHANGE); attachInterrupt(digitalPinToInterrupt(P1_BTN_PIN), p1BtnPressed, FALLING); attachInterrupt(digitalPinToInterrupt(P2_BTN_PIN), p2BtnPressed, FALLING); e_state = STATE_INIT; e_config_state = CONFIG_RETURN; u32_100ms_counter = 0; u32_last_active = 0; u32_main_btn_released = 0; u32_main_btn_pressed = 0; u32_p1_btn_pressed = 0; u32_p2_btn_pressed = 0; applyMode(MODE_5min); } void loop() { static state_et prev_state_before_config; static uint32_t u32_prev_display = 0; getButtonStatus(); #define PLAYER_MOVED(prev, next) do { \ if (0 != s_##next##_stats.u32_time) { \ if (!s_##prev##_stats.b_running) { \ if ((MODE_3min_2sec == e_mode) && \ (s_##next##_stats.u16_moves)) \ s_##next##_stats.u32_time += 20; \ s_##next##_stats.u16_moves++; \ } \ s_##next##_stats.b_running = false; \ s_##prev##_stats.b_running = true; \ } \ } while (0) switch (e_state) { case STATE_INIT: if ((BUTTON_SHORT_PRESSED == btn_main) || (BUTTON_LONG_PRESSED == btn_main)) { prev_state_before_config = e_state; e_state = STATE_CONFIG; digitalWrite(LED_PIN, HIGH); } if (BUTTON_PRESSED == btn_p1) { PLAYER_MOVED(p2, p1); e_state = STATE_RUN; } if (BUTTON_PRESSED == btn_p2) { PLAYER_MOVED(p1, p2); e_state = STATE_RUN; } break; case STATE_RUN: if (BUTTON_SHORT_PRESSED == btn_main) { e_state = STATE_PAUSE; } else { if (BUTTON_PRESSED == btn_p1) { PLAYER_MOVED(p2, p1); } if (BUTTON_PRESSED == btn_p2) { PLAYER_MOVED(p1, p2); } } break; case STATE_PAUSE: if (BUTTON_SHORT_PRESSED == btn_main) { e_state = STATE_RUN; } if (BUTTON_LONG_PRESSED == btn_main) { prev_state_before_config = e_state; e_state = STATE_CONFIG; digitalWrite(LED_PIN, HIGH); } break; case STATE_CONFIG: if (BUTTON_SHORT_PRESSED == btn_main) { e_state = prev_state_before_config; applyConfig(); digitalWrite(LED_PIN, LOW); } if (BUTTON_PRESSED == btn_p1) { if (e_config_state > CONFIG_RETURN) { e_config_state = (config_state_et)(e_config_state - 1); } } if (BUTTON_PRESSED == btn_p2) { if (e_config_state < CONFIG_90min) { e_config_state = (config_state_et)(e_config_state + 1); } } break; default: e_state = STATE_INIT; break; } if (u32_prev_display != u32_100ms_counter) { oledDisplay(); u32_prev_display = u32_100ms_counter; } // sleep for 3 or 10 minutes if (u32_100ms_counter - u32_last_active > (STATE_RUN == e_state ? (10*60*10) : (3*60*10))) { digitalWrite(LED_PIN, LOW); oled.ssd1306_command(SSD1306_DISPLAYOFF); #if 1 // samd21g standby mode NVIC_DisableIRQ(RTC_IRQn); SysTick->CTRL &= ~SysTick_CTRL_TICKINT_Msk; // disable systick interrupt SCB->SCR |= SCB_SCR_SLEEPDEEP_Msk; __DSB(); __WFI(); // wait for any interrupt (any button press) SysTick->CTRL |= SysTick_CTRL_TICKINT_Msk; // re-enable systick interrupt NVIC_EnableIRQ(RTC_IRQn); #endif oled.ssd1306_command(SSD1306_DISPLAYON); e_state = STATE_INIT; } } // https://forum.arduino.cc/t/samd21-rtc-millisecond-timing/655161/8 void setupRTC(uint32_t u32_compare, uint8_t u8_correction) { // configure the 32768 Hz oscillator SYSCTRL->XOSC32K.reg = SYSCTRL_XOSC32K_ONDEMAND | SYSCTRL_XOSC32K_RUNSTDBY | SYSCTRL_XOSC32K_EN32K | SYSCTRL_XOSC32K_XTALEN | SYSCTRL_XOSC32K_STARTUP(6) | SYSCTRL_XOSC32K_ENABLE; while (GCLK->STATUS.bit.SYNCBUSY); // Wait for synchronization // attach peripheral clock to 32768 Hz oscillator (1 tick = 1/32768 second) GCLK->GENDIV.reg = GCLK_GENDIV_ID(2) | GCLK_GENDIV_DIV(0); while (GCLK->STATUS.bit.SYNCBUSY); // Wait for synchronization GCLK->GENCTRL.reg = (GCLK_GENCTRL_GENEN | GCLK_GENCTRL_SRC_XOSC32K | GCLK_GENCTRL_ID(2)); while (GCLK->STATUS.bit.SYNCBUSY); // Wait for synchronization GCLK->CLKCTRL.reg = (uint32_t)((GCLK_CLKCTRL_CLKEN | GCLK_CLKCTRL_GEN_GCLK2 | (RTC_GCLK_ID << GCLK_CLKCTRL_ID_Pos))); while (GCLK->STATUS.bit.SYNCBUSY); // Wait for synchronization // disable RTC if it is enabled, to allow reconfiguration RTC->MODE0.CTRL.reg &= ~RTC_MODE0_CTRL_ENABLE; while (RTC->MODE0.STATUS.bit.SYNCBUSY); // Wait for synchronization // trigger RTC software reset RTC->MODE0.CTRL.reg |= RTC_MODE0_CTRL_SWRST; while (RTC->MODE0.STATUS.bit.SYNCBUSY); // Wait for synchronization // configure RTC in mode 0 (32-bit) RTC->MODE0.CTRL.reg |= RTC_MODE0_CTRL_PRESCALER_DIV1 | RTC_MODE0_CTRL_MODE_COUNT32; while (RTC->MODE0.STATUS.bit.SYNCBUSY); // Wait for synchronization // set match_clear bit(7) to clear counter for periodic interrupts // this will probably screw up anything else using the RTC // See Table 18-1. MODE0 - Mode Register Summary RTC->MODE0.CTRL.reg |= RTC_MODE0_CTRL_MATCHCLR; while (RTC->MODE0.STATUS.bit.SYNCBUSY); // Wait for synchronization // enter freq correction here as sign (bit7) and magnitude (bits 6-0) RTC_FREQCORR_VALUE(u8_correction); // adjust if necessary // initialize counter & compare values RTC->MODE0.COUNT.reg = 0; while (RTC->MODE0.STATUS.bit.SYNCBUSY); // Wait for synchronization RTC->MODE0.COMP[0].reg = u32_compare; while (RTC->MODE0.STATUS.bit.SYNCBUSY); // Wait for synchronization // enable the CMP0 interrupt in the RTC RTC->MODE0.INTENSET.reg |= RTC_MODE0_INTENSET_CMP0; while (RTC->MODE0.STATUS.bit.SYNCBUSY); // Wait for synchronization // re-enable RTC after reconfiguration and initial scheduling RTC->MODE0.CTRL.reg |= RTC_MODE0_CTRL_ENABLE; while (RTC->MODE0.STATUS.bit.SYNCBUSY); // Wait for synchronization // enable continuous synchronization while (RTC->MODE0.STATUS.bit.SYNCBUSY); RTC->MODE0.READREQ.reg = RTC_READREQ_RREQ | RTC_READREQ_RCONT | 0x0010; while (RTC->MODE0.STATUS.bit.SYNCBUSY); // enable RTC interrupt in controller NVIC_SetPriority(RTC_IRQn, 0x00); NVIC_EnableIRQ(RTC_IRQn); }