diff --git a/Core/Clock/melody.c b/Core/Clock/melody.c index e386821..58e6508 100644 --- a/Core/Clock/melody.c +++ b/Core/Clock/melody.c @@ -1,5 +1,6 @@ #include "melody.h" +MelodyHandle melody; #define FIXED_ARR 1000 // фиксированный период таймера #define FIXED_VOLUME 2 // фиксированный период таймера diff --git a/Core/Clock/melody.h b/Core/Clock/melody.h index 35e2887..c567b1a 100644 --- a/Core/Clock/melody.h +++ b/Core/Clock/melody.h @@ -3,7 +3,7 @@ #include "stm32f1xx_hal.h" #include "songs.h" -//#include "sounds.h" +#include "sounds.h" typedef struct { TIM_HandleTypeDef* htim; @@ -15,6 +15,7 @@ typedef struct { uint8_t is_playing; uint16_t bpm; } MelodyHandle; +extern MelodyHandle melody; void Melody_Init(MelodyHandle* mh, TIM_HandleTypeDef* htim, uint32_t channel, uint32_t timer_clock_hz); void Melody_SetBPM(MelodyHandle* mh, uint16_t bpm); diff --git a/Core/Clock/menu.c b/Core/Clock/menu.c index ca4ae5c..65dd4db 100644 --- a/Core/Clock/menu.c +++ b/Core/Clock/menu.c @@ -1,426 +1,195 @@ #include "menu.h" #include "segment.h" -#include "clock_manager.h" +#include +#include -// Время удержания кнопки (мс), после которого нажатие считается "длинным" -// Используется для входа в меню из режима часов -#define LONG_PRESS_MS 500 +#define LONG_PRESS_MS 500 -// Задержка перед началом автоповтора (мс) -// При удержании кнопки первые 500 мс ничего не происходит -#define REPEAT_DELAY_MS 500 +MenuContext g_ctx; +uint8_t g_sound_enabled = 1; -// Интервал между автоповторами (мс) -// После начала повтора каждые 100 мс кнопка срабатывает заново -#define REPEAT_INTERVAL_MS 100 +extern bool ReadButton(Button_Type btn); +extern MenuNode* Menu_GetRootMenu(void); -// Интервал мигания редактируемого разряда (мс) -// Моргает каждые 500 мс (0.5 сек вкл / 0.5 сек выкл) -#define BLINK_INTERVAL_MS 500 - -// Задержка антидребезга (мс) -// Изменение состояния кнопки фиксируется только если оно стабильно в течение 30 мс -#define DEBOUNCE_MS 30 - -typedef enum { - MAIN_MENU_SET_TIME, - MAIN_MENU_SET_DUTY, - MAIN_MENU_RESET, - MAIN_MENU_COUNT -} MainMenuItem; - -typedef struct { - // Состояние - SystemState state; - MainMenuItem selectedMenuItem; +void Menu_Init(MenuNode* startNode) { + g_ctx.currentNode = startNode; + g_ctx.needsRedraw = true; + g_ctx.lastTick = 0; - // Для SET_TIME - time_t originalTime; - time_t editTime; - uint8_t editStep; - bool blinkState; - uint32_t lastBlinkTime; - - // Для SET_DUTY - uint8_t originalDuty; - uint8_t editDuty; - - // Для кнопок - bool buttonPrevState[BUTTON_COUNT]; - uint32_t buttonPressTime[BUTTON_COUNT]; - bool longPressSent[BUTTON_COUNT]; - bool repeatActive[BUTTON_COUNT]; - uint32_t lastRepeatTime[BUTTON_COUNT]; - uint32_t lastDebounceTime[BUTTON_COUNT]; - bool buttonStableState[BUTTON_COUNT]; - -} MenuContext; - -MenuContext menu; - -// ==================== Отображение ==================== -static void FormatTime(char* buf, const time_t* t) { - buf[0] = '0' + t->hour / 10; - buf[1] = '0' + t->hour % 10; - buf[2] = '0' + t->min / 10; - buf[3] = '0' + t->min % 10; - buf[4] = '0' + t->sec / 10; - buf[5] = '0' + t->sec % 10; -} - -static void UpdateDisplay(void) { - switch (menu.state) { - case STATE_CLOCK: { - time_t now = ClockManager_GetTime(1); - char buf[7]; - FormatTime(buf, &now); - Segment_SetString(buf); - break; - } - - case STATE_MAIN_MENU: { - switch (menu.selectedMenuItem) { - case MAIN_MENU_SET_TIME: - Segment_SetString("SET T "); - break; - case MAIN_MENU_SET_DUTY: - Segment_SetString("SET D "); - break; - case MAIN_MENU_RESET: - Segment_SetString("RESET "); - break; - default: - break; - } - break; - } - - case STATE_SET_TIME: { - char buf[7]; - FormatTime(buf, &menu.editTime); - if (menu.blinkState && menu.editStep < 6) { - buf[menu.editStep] = ' '; - } - Segment_SetString(buf); - break; - } - - case STATE_SET_DUTY: { - char buf[6] = {'D', 'U', 'T', 'Y', ' ' , ' '}; - if (menu.editDuty == 10) { - buf[4] = '1'; - buf[5] = '0'; - } else { - buf[5] = '0' + menu.editDuty; - } - Segment_SetString(buf); - break; - } - - case STATE_RESET_CONFIRM: - Segment_SetString("RESET "); - break; + if (startNode && startNode->onEnter) { + startNode->onEnter(); } } -// ==================== Логика SET_TIME ==================== -static void IncreaseTimeDigit(void) { - uint8_t tens, units; - switch (menu.editStep) { - case 0: // десятки часов (0-2) - tens = menu.editTime.hour / 10; - units = menu.editTime.hour % 10; - tens++; - if (tens > 2) tens = 0; - menu.editTime.hour = tens * 10 + units; - break; - - case 1: // единицы часов (0-9, но с учетом десятков) - tens = menu.editTime.hour / 10; - units = menu.editTime.hour % 10; - units++; - if (tens == 2 && units > 3) units = 0; // 23 → 20 - if (units > 9) units = 0; - menu.editTime.hour = tens * 10 + units; - break; - - case 2: // десятки минут (0-5) - tens = menu.editTime.min / 10; - units = menu.editTime.min % 10; - tens = (tens + 1) % 6; - menu.editTime.min = tens * 10 + units; - break; - - case 3: // единицы минут (0-9) - units = (menu.editTime.min % 10 + 1) % 10; - menu.editTime.min = (menu.editTime.min / 10) * 10 + units; - break; - - case 4: // десятки секунд (0-5) - tens = menu.editTime.sec / 10; - units = menu.editTime.sec % 10; - tens = (tens + 1) % 6; - menu.editTime.sec = tens * 10 + units; - break; - - case 5: // единицы секунд (0-9) - units = (menu.editTime.sec % 10 + 1) % 10; - menu.editTime.sec = (menu.editTime.sec / 10) * 10 + units; - break; +// НОВАЯ ФУНКЦИЯ - открыть меню +void Menu_OpenMenu(MenuNode* menu) { + if (!menu) return; + g_ctx.currentNode = menu; + g_ctx.needsRedraw = true; + if (menu->onEnter) { + menu->onEnter(); } - UpdateDisplay(); } -static void DecreaseTimeDigit(void) { - uint8_t tens, units; - switch (menu.editStep) { - case 0: // десятки часов (0-2) - tens = menu.editTime.hour / 10; - units = menu.editTime.hour % 10; - if (tens == 0) tens = 2; - else tens--; - menu.editTime.hour = tens * 10 + units; - break; - - case 1: // единицы часов - tens = menu.editTime.hour / 10; - units = menu.editTime.hour % 10; - if (units == 0) { - units = (tens == 2) ? 3 : 9; - } else { - units--; - } - menu.editTime.hour = tens * 10 + units; - break; - - case 2: // десятки минут (0-5) - tens = menu.editTime.min / 10; - units = menu.editTime.min % 10; - tens = (tens == 0) ? 5 : tens - 1; - menu.editTime.min = tens * 10 + units; - break; - - case 3: // единицы минут - units = (menu.editTime.min % 10 == 0) ? 9 : (menu.editTime.min % 10) - 1; - menu.editTime.min = (menu.editTime.min / 10) * 10 + units; - break; - - case 4: // десятки секунд (0-5) - tens = menu.editTime.sec / 10; - units = menu.editTime.sec % 10; - tens = (tens == 0) ? 5 : tens - 1; - menu.editTime.sec = tens * 10 + units; - break; - - case 5: // единицы секунд - units = (menu.editTime.sec % 10 == 0) ? 9 : (menu.editTime.sec % 10) - 1; - menu.editTime.sec = (menu.editTime.sec / 10) * 10 + units; - break; - } - UpdateDisplay(); -} - -// ==================== Обработка кнопок ==================== -static void ProcessButton(Button_Type btn, bool longPress) { - // Длинное нажатие SELECT в режиме часов - вход в меню - if (menu.state == STATE_CLOCK && longPress && btn == BUTTON_SELECT) { - menu.state = STATE_MAIN_MENU; - menu.selectedMenuItem = MAIN_MENU_SET_TIME; - UpdateDisplay(); +void Menu_HandleButton(Button_Type btn, bool longPress) { + if (!g_ctx.currentNode) return; + + // Длинное нажатие SELECT на корневом уровне (часы) - вход в меню + if (longPress && btn == BUTTON_SELECT && g_ctx.currentNode->parent == NULL) { + SOUND_DOUBLE; + MenuNode* rootMenu = Menu_GetRootMenu(); + if (rootMenu) { + Menu_OpenMenu(rootMenu); + } return; } - switch (menu.state) { - case STATE_MAIN_MENU: - if (btn == BUTTON_UP) { - menu.selectedMenuItem++; - if (menu.selectedMenuItem >= MAIN_MENU_COUNT) { - menu.selectedMenuItem = 0; - } - UpdateDisplay(); - } - else if (btn == BUTTON_DOWN) { - if (menu.selectedMenuItem == 0) { - menu.selectedMenuItem = MAIN_MENU_COUNT - 1; - } else { - menu.selectedMenuItem--; - } - UpdateDisplay(); - } - else if (btn == BUTTON_SELECT && !longPress) { - switch (menu.selectedMenuItem) { - case MAIN_MENU_SET_TIME: - menu.state = STATE_SET_TIME; - menu.originalTime = ClockManager_GetTime(0); - menu.editTime = menu.originalTime; - menu.editStep = 0; - menu.blinkState = true; - menu.lastBlinkTime = HAL_GetTick(); - break; - case MAIN_MENU_SET_DUTY: - menu.state = STATE_SET_DUTY; - menu.originalDuty = ClockManager_GetDuty(); - menu.editDuty = menu.originalDuty; - break; - case MAIN_MENU_RESET: - menu.state = STATE_RESET_CONFIRM; - break; - default: break; - } - UpdateDisplay(); - } - else if (btn == BUTTON_BACK) { - menu.state = STATE_CLOCK; - UpdateDisplay(); - } - break; - - case STATE_SET_TIME: - if (btn == BUTTON_UP) { - IncreaseTimeDigit(); - } - else if (btn == BUTTON_DOWN) { - DecreaseTimeDigit(); - } - else if (btn == BUTTON_SELECT) { - menu.editStep++; - if (menu.editStep >= 6) { - ClockManager_SetTime(menu.editTime.hour, menu.editTime.min, menu.editTime.sec); - menu.state = STATE_CLOCK; - } - UpdateDisplay(); - } - else if (btn == BUTTON_BACK) { - menu.state = STATE_CLOCK; - UpdateDisplay(); - } - break; - - case STATE_SET_DUTY: - if (btn == BUTTON_UP && menu.editDuty < 10) { - menu.editDuty++; - Segment_SetBrightness(menu.editDuty * 10); - UpdateDisplay(); - } - else if (btn == BUTTON_DOWN && menu.editDuty > 0) { - menu.editDuty--; - Segment_SetBrightness(menu.editDuty * 10); - UpdateDisplay(); - } - else if (btn == BUTTON_SELECT) { - ClockManager_SetDuty(menu.editDuty); - menu.state = STATE_CLOCK; - UpdateDisplay(); - } - else if (btn == BUTTON_BACK) { - menu.state = STATE_CLOCK; - Segment_SetBrightness(menu.originalDuty * 10); - UpdateDisplay(); - } - break; - - case STATE_RESET_CONFIRM: - if (btn == BUTTON_SELECT) { - ClockManager_ResetTime(); - ClockManager_SetDuty(8); - menu.state = STATE_CLOCK; - UpdateDisplay(); - } - else if (btn == BUTTON_BACK) { - menu.state = STATE_CLOCK; - UpdateDisplay(); - } - break; - - default: - break; + if (g_ctx.currentNode->onButton) { + if(btn == BUTTON_SELECT) + { + SOUND_DOUBLE; + } + else + { + SOUND_CLICK; + } + + g_ctx.currentNode->onButton(btn, longPress); + return; } -} - -// ==================== Публичные функции ==================== -void Menu_Init(void) { - menu.state = STATE_CLOCK; - menu.selectedMenuItem = MAIN_MENU_SET_TIME; - menu.editStep = 0; - menu.blinkState = false; - menu.lastBlinkTime = 0; - for (int i = 0; i < BUTTON_COUNT; i++) { - menu.buttonPrevState[i] = false; - menu.buttonPressTime[i] = 0; - menu.longPressSent[i] = false; - menu.repeatActive[i] = false; - menu.lastDebounceTime[i] = 0; - menu.buttonStableState[i] = false; + if (g_ctx.currentNode->parent != NULL) { + switch (btn) { + case BUTTON_UP: + SOUND_CLICK; + if (g_ctx.currentNode->selectedChild > 0) { + g_ctx.currentNode->selectedChild--; + g_ctx.needsRedraw = true; + } + break; + + case BUTTON_DOWN: + SOUND_CLICK; + if (g_ctx.currentNode->selectedChild < g_ctx.currentNode->childCount - 1) { + g_ctx.currentNode->selectedChild++; + g_ctx.needsRedraw = true; + } + break; + + case BUTTON_SELECT: { + SOUND_DOUBLE; + if (g_ctx.currentNode->children) { + MenuNode* selected = g_ctx.currentNode->children[g_ctx.currentNode->selectedChild]; + if (selected) { + g_ctx.currentNode = selected; + g_ctx.needsRedraw = true; + if (selected->onEnter) { + selected->onEnter(); + } + } + } + break; + } + + case BUTTON_BACK: + SOUND_CLICK; + Menu_GoBack(); + break; + + default: + break; + } } } void Menu_Process(void) { uint32_t tick = HAL_GetTick(); - if (menu.state == STATE_SET_TIME) { - if (tick - menu.lastBlinkTime >= BLINK_INTERVAL_MS) { - menu.lastBlinkTime = tick; - menu.blinkState = !menu.blinkState; - UpdateDisplay(); - } - } + // ОБРАБОТКА КНОПОК С ДЕБАУНСОМ for (int i = 0; i < BUTTON_COUNT; i++) { - bool rawPressed = ReadButton(i); - bool pressed; - - // Антидребезг - if (rawPressed != menu.buttonStableState[i]) { - menu.lastDebounceTime[i] = tick; - menu.buttonStableState[i] = rawPressed; - } - - if ((tick - menu.lastDebounceTime[i]) >= DEBOUNCE_MS) { - pressed = menu.buttonStableState[i]; - } else { - pressed = menu.buttonPrevState[i]; // старое значение пока не стабилизировалось - } - bool wasPressed = menu.buttonPrevState[i]; + bool reading = ReadButton((Button_Type)i); - if (pressed && !wasPressed) { - menu.buttonPressTime[i] = tick; - menu.longPressSent[i] = false; - menu.repeatActive[i] = false; - } - else if (pressed && wasPressed) { - if (!menu.longPressSent[i] && (tick - menu.buttonPressTime[i] >= LONG_PRESS_MS)) { - menu.longPressSent[i] = true; - ProcessButton((Button_Type)i, true); - } - else if (menu.longPressSent[i] && !menu.repeatActive[i] && - (tick - menu.buttonPressTime[i] >= REPEAT_DELAY_MS)) { - menu.repeatActive[i] = true; - menu.lastRepeatTime[i] = tick; - ProcessButton((Button_Type)i, true); - } - else if (menu.repeatActive[i] && (tick - menu.lastRepeatTime[i] >= REPEAT_INTERVAL_MS)) { - menu.lastRepeatTime[i] = tick; - ProcessButton((Button_Type)i, true); - } - } - else if (!pressed && wasPressed) { - if (!menu.longPressSent[i]) { - ProcessButton((Button_Type)i, false); - } - menu.repeatActive[i] = false; + if (reading != g_ctx.lastButtonState[i]) { + g_ctx.lastDebounceTime[i] = tick; } - menu.buttonPrevState[i] = pressed; + if ((tick - g_ctx.lastDebounceTime[i]) > 30) { // DEBOUNCE_MS = 30 + if (reading != g_ctx.buttonState[i]) { + g_ctx.buttonState[i] = reading; + + if (g_ctx.buttonState[i]) { + g_ctx.pressStartTime[i] = tick; + g_ctx.longPressTriggered[i] = false; + } else { + if (!g_ctx.longPressTriggered[i]) { + Menu_HandleButton((Button_Type)i, false); + } + } + } + } + + if (g_ctx.buttonState[i] && !g_ctx.longPressTriggered[i]) { + if ((tick - g_ctx.pressStartTime[i]) >= LONG_PRESS_MS) { + g_ctx.longPressTriggered[i] = true; + Menu_HandleButton((Button_Type)i, true); + } + } + + g_ctx.lastButtonState[i] = reading; } - static uint32_t lastClockUpdate = 0; - if (menu.state == STATE_CLOCK && (tick - lastClockUpdate >= 200)) { - lastClockUpdate = tick; - UpdateDisplay(); + if (g_ctx.currentNode && g_ctx.currentNode->onUpdate) { + g_ctx.currentNode->onUpdate(); + } + + if (tick - g_ctx.lastTick >= 200) { + g_ctx.lastTick = tick; + g_ctx.needsRedraw = true; + } + + if ((g_ctx.needsRedraw && g_ctx.currentNode && g_ctx.currentNode->display) || + (g_ctx.currentNode && g_ctx.currentNode->display && g_ctx.currentNode->needsRedraw)) { + g_ctx.currentNode->display(); + g_ctx.needsRedraw = false; } } -SystemState Menu_GetState(void) { - return menu.state; +void Menu_GoBack(void) { + if (g_ctx.currentNode && g_ctx.currentNode->parent) { + g_ctx.currentNode = g_ctx.currentNode->parent; + g_ctx.needsRedraw = true; + if (g_ctx.currentNode->onEnter) { + g_ctx.currentNode->onEnter(); + } + } +} + +void Menu_Refresh(void) { + g_ctx.needsRedraw = true; +} + +MenuNode* Menu_GetCurrentNode(void) { + return g_ctx.currentNode; +} + +void Menu_Sound_On(void) { + g_sound_enabled = 1; + SOUND_SUCCESS; +} + +void Menu_Sound_Off(void) { + g_sound_enabled = 0; +} + +void Menu_Sound_Toggle(void) { + if (g_sound_enabled) { + Menu_Sound_Off(); + } else { + Menu_Sound_On(); + } +} + +uint8_t Menu_Sound_IsEnabled(void) { + return g_sound_enabled; } \ No newline at end of file diff --git a/Core/Clock/menu.h b/Core/Clock/menu.h index c5fbb5c..b6d0aff 100644 --- a/Core/Clock/menu.h +++ b/Core/Clock/menu.h @@ -4,7 +4,15 @@ #include #include #include "main.h" +#include "melody.h" +// Макросы звуковых эффектов +#define SFX_BPM 480 +#define SOUND_CLICK if(g_sound_enabled) Melody_Play(&melody, &SFX_Click, SFX_BPM) +#define SOUND_DOUBLE if(g_sound_enabled) Melody_Play(&melody, &SFX_DoubleBeep, SFX_BPM) +#define SOUND_ERROR if(g_sound_enabled) Melody_Play(&melody, &SFX_Error, SFX_BPM) +#define SOUND_SUCCESS if(g_sound_enabled) Melody_Play(&melody, &SFX_Success, SFX_BPM) + typedef enum { BUTTON_UP, BUTTON_DOWN, @@ -13,18 +21,50 @@ typedef enum { BUTTON_COUNT } Button_Type; -typedef enum { - STATE_CLOCK, // режим часов - STATE_MAIN_MENU, // главное меню (выбор пункта) - STATE_SET_TIME, // настройка времени - STATE_SET_DUTY, // настройка яркости - STATE_RESET_CONFIRM // подтверждение сброса -} SystemState; +typedef struct MenuNode { + const char* name; + struct MenuNode* parent; + struct MenuNode** children; + uint8_t childCount; + uint8_t selectedChild; + uint8_t needsRedraw; + + void (*display)(void); + void (*onEnter)(void); + void (*onUpdate)(void); + void (*onButton)(Button_Type btn, bool longPress); + + void* data; +} MenuNode; -// Чтение кнопки (реализуется в main.c или hal) -int ReadButton(int btn); +typedef struct { + MenuNode* currentNode; + uint32_t lastTick; + bool needsRedraw; + + + uint32_t lastDebounceTime[BUTTON_COUNT]; + uint8_t lastButtonState[BUTTON_COUNT]; + uint8_t buttonState[BUTTON_COUNT]; + uint32_t pressStartTime[BUTTON_COUNT]; + uint8_t longPressTriggered[BUTTON_COUNT]; +} MenuContext; -void Menu_Init(void); +// Глобальные переменные +extern uint8_t g_sound_enabled; +extern MenuContext g_ctx; + +// Функции +void Menu_Init(MenuNode* startNode); void Menu_Process(void); +void Menu_HandleButton(Button_Type btn, bool longPress); +void Menu_GoBack(void); +void Menu_Refresh(void); +MenuNode* Menu_GetCurrentNode(void); +void Menu_OpenMenu(MenuNode* menu); +void Menu_Sound_On(void); +void Menu_Sound_Off(void); +void Menu_Sound_Toggle(void); +uint8_t Menu_Sound_IsEnabled(void); #endif \ No newline at end of file diff --git a/Core/Clock/menu_items.c b/Core/Clock/menu_items.c new file mode 100644 index 0000000..a595286 --- /dev/null +++ b/Core/Clock/menu_items.c @@ -0,0 +1,695 @@ +#include "menu_items.h" +#include "segment.h" +#include "clock_manager.h" +#include +#include +#include +static MenuNode g_rootMenu; +// Сначала объявляем узлы активностей +MenuNode g_clockNode; +MenuNode g_timerNode; +MenuNode g_stopwatchNode; +MenuNode g_ZeroMillisNode; +MenuNode g_ReactionTimeNode; +MenuNode g_ClickerTimeNode; + +// Потом объявляем узлы меню +MenuNode g_gamesNode; +MenuNode g_settingsNode; +MenuNode g_timeEditNode; +MenuNode g_dutyEditNode; +MenuNode g_LEDEditNode; +MenuNode g_MenuSoundNode; +MenuNode g_PowerOnSongNode; +MenuNode g_SongNode; +MenuNode g_resetNode; + + +// ==================== Данные ==================== +typedef struct { + time_t editTime; + uint8_t editStep; + bool blinkState; + uint32_t lastBlink; +} TimeEditData; + +typedef struct { + uint32_t startTime; + uint32_t pressTime; + bool celebrating; + bool result; + int32_t diff_ms; + uint8_t state; +} Game1SecData; + +typedef struct { + uint32_t waitStart; + uint32_t ledOnTime; + uint32_t reactionTime; + uint8_t state; +} GameReactionData; + +typedef struct { + uint32_t endTime; + uint16_t clicks; + bool active; + bool finished; +} GameClickerData; + +static TimeEditData g_timeData; +static uint8_t g_originalDuty; +static uint8_t g_editDuty; +static Game1SecData g_game1sec; +static GameReactionData g_gameReaction; +static GameClickerData g_gameClicker; + +// Текущая активность в корне +static MenuNode* g_currentActivity = NULL; + +// ==================== Функции отображения активностей ==================== +static void Display_Clock(void) { + time_t now = ClockManager_GetTime(1); + char buf[7]; + sprintf(buf, "%02d%02d%02d", now.hour, now.min, now.sec); + Segment_SetString(buf); +} + +static void Display_Timer(void) { + Segment_SetString("TIMER "); +} + +static void Display_Stopwatch(void) { + Segment_SetString("SECOND "); +} + +static void Display_ZeroMillis(void) { + char buf[7]; + switch (g_game1sec.state) { + case 0: + g_game1sec.celebrating = 0; + sprintf(buf, "START"); break; + case 1: + g_game1sec.diff_ms = (HAL_GetTick() - g_game1sec.startTime); + case 2: + sprintf(buf, " %4d", g_game1sec.diff_ms/10); + break; + default: sprintf(buf, "ERROR"); break; + } + if(g_game1sec.state == 2) + { + if((g_game1sec.diff_ms/10)%100 == 0) + { + if(!g_game1sec.celebrating) + { + g_game1sec.celebrating = 1; + Melody_Play(&melody, &Polyphia_PlayingGod, 134); + } + } + } + Segment_SetString(buf); +} + +static void Display_Reaction(void) { + char buf[7]; + switch (g_gameReaction.state) { + case 0: sprintf(buf, "START"); break; + case 1: sprintf(buf, " "); break; + case 2: sprintf(buf, "888888"); break; + case 3: sprintf(buf, "%6d", g_gameReaction.reactionTime); break; + default: sprintf(buf, "ERROR"); break; + } + Segment_SetString(buf); +} + +static void Display_Clicker(void) { + char buf[7]; + if (!g_gameClicker.active && !g_gameClicker.finished) { + sprintf(buf, "START"); + } else if (g_gameClicker.active) { + uint32_t remaining = (g_gameClicker.endTime - HAL_GetTick()) / 1000; + sprintf(buf, "%2d %3d", remaining, g_gameClicker.clicks); + } else { + sprintf(buf, " %3d", g_gameClicker.clicks); + } + Segment_SetString(buf); +} + +static void Display_MenuItem(void) { + MenuNode* current = Menu_GetCurrentNode(); + if (current && current->selectedChild < current->childCount) { + const char* name = current->children[current->selectedChild]->name; + char buf[7]; + sprintf(buf, "%-6.6s", name); + Segment_SetString(buf); + } +} + +static void Display_TimeEdit(void) { + char buf[7]; + sprintf(buf, "%02d%02d%02d", + g_timeData.editTime.hour, + g_timeData.editTime.min, + g_timeData.editTime.sec); + if (g_timeData.blinkState && g_timeData.editStep < 6) { + buf[g_timeData.editStep] = ' '; + } + Segment_SetString(buf); +} + +static void Display_DutyEdit(void) { + char buf[7] = "DUTY "; + if (g_editDuty == 10) { + buf[4] = '1'; + buf[5] = '0'; + } else { + buf[5] = '0' + g_editDuty; + } + Segment_SetString(buf); +} + +static void Display_Reset(void) { + Segment_SetString("RESET "); +} + +// ==================== Вход в активности ==================== +static void OnEnter_Clock(void) { + g_currentActivity = &g_clockNode; + g_rootMenu.parent = &g_clockNode; +} + +static void OnEnter_Timer(void) { + g_currentActivity = &g_timerNode; + g_rootMenu.parent = &g_timerNode; +} + +static void OnEnter_Stopwatch(void) { + g_currentActivity = &g_stopwatchNode; + g_rootMenu.parent = &g_stopwatchNode; +} + +static void OnEnter_ZeroMillis(void) { + g_game1sec.state = 0; + Menu_Refresh(); +} + +static void OnEnter_Reaction(void) { + g_gameReaction.state = 0; + Menu_Refresh(); +} + +static void OnEnter_Clicker(void) { + g_gameClicker.active = false; + g_gameClicker.finished = false; + g_gameClicker.clicks = 0; + Menu_Refresh(); +} + +static void OnEnter_TimeEdit(void) { + g_timeData.editTime = ClockManager_GetTime(0); + g_timeData.editStep = 0; + g_timeData.blinkState = true; + g_timeData.lastBlink = HAL_GetTick(); +} + +static void OnEnter_DutyEdit(void) { + g_originalDuty = ClockManager_GetDuty(); + g_editDuty = g_originalDuty; + Segment_SetBrightness(g_editDuty * 10); +} + +static void OnEnter_Reset(void) {} + +// ==================== Обновления ==================== +static void OnUpdate_TimeEdit(void) { + uint32_t tick = HAL_GetTick(); + if (tick - g_timeData.lastBlink >= 500) { + g_timeData.lastBlink = tick; + g_timeData.blinkState = !g_timeData.blinkState; + Menu_Refresh(); + } +} + +static void OnUpdate_Reaction(void) { + uint32_t tick = HAL_GetTick(); + if (g_gameReaction.state == 1) { + if (tick - g_gameReaction.waitStart >= g_gameReaction.ledOnTime) { + g_gameReaction.state = 2; + g_gameReaction.waitStart = tick; + Menu_Refresh(); + } + } + else if (g_gameReaction.state == 2) { + if (tick - g_gameReaction.waitStart >= 2000) { + g_gameReaction.state = 3; + g_gameReaction.reactionTime = 999; + Menu_Refresh(); + } + } +} + +static void OnUpdate_Clicker(void) { + if (g_gameClicker.active && HAL_GetTick() >= g_gameClicker.endTime) { + g_gameClicker.active = false; + g_gameClicker.finished = true; + Menu_Refresh(); + } +} + +// ==================== Обработчики кнопок активностей ==================== +static void Clock_OnButton(Button_Type btn, bool longPress) { + if (longPress && btn == BUTTON_SELECT) { + Menu_OpenMenu(&g_rootMenu); + } +} + +static void Timer_OnButton(Button_Type btn, bool longPress) { + if (longPress && btn == BUTTON_SELECT) { + Menu_OpenMenu(&g_rootMenu); + } +} + +static void Stopwatch_OnButton(Button_Type btn, bool longPress) { + if (longPress && btn == BUTTON_SELECT) { + Menu_OpenMenu(&g_rootMenu); + } +} + +static void ZeroMillis_OnButton(Button_Type btn, bool longPress) { + if (longPress && btn == BUTTON_SELECT) { + Menu_OpenMenu(&g_rootMenu); + return; + } + if (btn == BUTTON_BACK) { + Menu_GoBack(); + } + + uint32_t tick = HAL_GetTick(); + if (g_game1sec.state == 0 && btn == BUTTON_SELECT) { + g_game1sec.state = 1; + g_game1sec.startTime = tick; + Menu_Refresh(); + } + else if ((btn == BUTTON_SELECT) && (g_game1sec.state == 1)) { + g_game1sec.state = 2; + g_game1sec.pressTime = HAL_GetTick(); + g_game1sec.diff_ms = (g_game1sec.pressTime - g_game1sec.startTime); + if (g_game1sec.diff_ms < 0) g_game1sec.diff_ms = -g_game1sec.diff_ms; + Menu_Refresh(); + } + else if ((g_game1sec.state == 2) && (btn == BUTTON_SELECT)) + { + g_game1sec.state = 0; + } +} + +static void Reaction_OnButton(Button_Type btn, bool longPress) { + if (longPress && btn == BUTTON_SELECT) { + Menu_OpenMenu(&g_rootMenu); + return; + } + + if (btn == BUTTON_BACK) { + Menu_GoBack(); + } + + uint32_t tick = HAL_GetTick(); + if (g_gameReaction.state == 0 && btn == BUTTON_SELECT) { + g_gameReaction.state = 1; + g_gameReaction.waitStart = tick; + g_gameReaction.ledOnTime = 1000 + (rand() % 4000); + Menu_Refresh(); + } + else if (g_gameReaction.state == 2 && btn == BUTTON_SELECT) { + g_gameReaction.reactionTime = tick - g_gameReaction.waitStart; + g_gameReaction.state = 3; + Menu_Refresh(); + } + else if ((g_gameReaction.state == 3) && (btn == BUTTON_SELECT)) + { + g_gameReaction.state = 0; + } +} + +static void Clicker_OnButton(Button_Type btn, bool longPress) { + if (longPress && btn == BUTTON_SELECT) { + Menu_OpenMenu(&g_rootMenu); + return; + } + + if (btn == BUTTON_BACK) { + Menu_GoBack(); + } + + if (!g_gameClicker.active && !g_gameClicker.finished && btn == BUTTON_SELECT) { + g_gameClicker.active = true; + g_gameClicker.endTime = HAL_GetTick() + 10000; + g_gameClicker.clicks = 0; + Menu_Refresh(); + } + else if (g_gameClicker.active && btn == BUTTON_UP) { + g_gameClicker.clicks++; + Menu_Refresh(); + } + else if (g_gameClicker.finished && (btn == BUTTON_SELECT)) + { + g_gameClicker.finished = 0; + g_gameClicker.active = 0; + } +} + +static void TimeEdit_OnButton(Button_Type btn, bool longPress) { + (void)longPress; + + switch (btn) { + case BUTTON_UP: { + uint8_t tens, units; + switch (g_timeData.editStep) { + case 0: + tens = g_timeData.editTime.hour / 10; + units = g_timeData.editTime.hour % 10; + tens = (tens + 1) % 3; + g_timeData.editTime.hour = tens * 10 + units; + break; + case 1: + tens = g_timeData.editTime.hour / 10; + units = (g_timeData.editTime.hour % 10 + 1) % 10; + if (tens == 2 && units > 3) units = 0; + g_timeData.editTime.hour = tens * 10 + units; + break; + case 2: + tens = (g_timeData.editTime.min / 10 + 1) % 6; + g_timeData.editTime.min = tens * 10 + (g_timeData.editTime.min % 10); + break; + case 3: + units = (g_timeData.editTime.min % 10 + 1) % 10; + g_timeData.editTime.min = (g_timeData.editTime.min / 10) * 10 + units; + break; + case 4: + tens = (g_timeData.editTime.sec / 10 + 1) % 6; + g_timeData.editTime.sec = tens * 10 + (g_timeData.editTime.sec % 10); + break; + case 5: + units = (g_timeData.editTime.sec % 10 + 1) % 10; + g_timeData.editTime.sec = (g_timeData.editTime.sec / 10) * 10 + units; + break; + } + Menu_Refresh(); + break; + } + case BUTTON_DOWN: { + uint8_t tens, units; + switch (g_timeData.editStep) { + case 0: + tens = g_timeData.editTime.hour / 10; + units = g_timeData.editTime.hour % 10; + tens = (tens == 0) ? 2 : tens - 1; + g_timeData.editTime.hour = tens * 10 + units; + break; + case 1: + tens = g_timeData.editTime.hour / 10; + units = g_timeData.editTime.hour % 10; + if (units == 0) units = (tens == 2) ? 3 : 9; + else units--; + g_timeData.editTime.hour = tens * 10 + units; + break; + case 2: + tens = g_timeData.editTime.min / 10; + tens = (tens == 0) ? 5 : tens - 1; + g_timeData.editTime.min = tens * 10 + (g_timeData.editTime.min % 10); + break; + case 3: + units = g_timeData.editTime.min % 10; + units = (units == 0) ? 9 : units - 1; + g_timeData.editTime.min = (g_timeData.editTime.min / 10) * 10 + units; + break; + case 4: + tens = g_timeData.editTime.sec / 10; + tens = (tens == 0) ? 5 : tens - 1; + g_timeData.editTime.sec = tens * 10 + (g_timeData.editTime.sec % 10); + break; + case 5: + units = g_timeData.editTime.sec % 10; + units = (units == 0) ? 9 : units - 1; + g_timeData.editTime.sec = (g_timeData.editTime.sec / 10) * 10 + units; + break; + } + Menu_Refresh(); + break; + } + case BUTTON_SELECT: + g_timeData.editStep++; + if (g_timeData.editStep >= 6) { + ClockManager_SetTime(g_timeData.editTime.hour, + g_timeData.editTime.min, + g_timeData.editTime.sec); + Menu_GoBack(); + } + Menu_Refresh(); + break; + case BUTTON_BACK: + Menu_GoBack(); + default: + break; + } +} + +static void DutyEdit_OnButton(Button_Type btn, bool longPress) { + (void)longPress; + switch (btn) { + case BUTTON_UP: + if (g_editDuty < 10) { + g_editDuty++; + Segment_SetBrightness(g_editDuty * 10); + Menu_Refresh(); + } + break; + case BUTTON_DOWN: + if (g_editDuty > 0) { + g_editDuty--; + Segment_SetBrightness(g_editDuty * 10); + Menu_Refresh(); + } + break; + case BUTTON_SELECT: + ClockManager_SetDuty(g_editDuty); + Menu_GoBack(); + break; + case BUTTON_BACK: + ClockManager_SetDuty(g_originalDuty); + Menu_GoBack(); + default: + break; + } +} + +static void Reset_OnButton(Button_Type btn, bool longPress) { + + if ((btn == BUTTON_SELECT) && longPress) { + ClockManager_ResetTime(); + ClockManager_SetDuty(5); + Menu_GoBack(); + } +} + +// ==================== Узлы активностей (корень) ==================== +MenuNode g_clockNode = { + .name = "CLOC", + .parent = NULL, + .children = NULL, + .childCount = 0, + .selectedChild = 0, + .display = Display_Clock, + .onEnter = OnEnter_Clock, + .onUpdate = NULL, + .onButton = Clock_OnButton, + .data = NULL +}; + +MenuNode g_timerNode = { + .name = "TIMER", + .parent = NULL, + .children = NULL, + .childCount = 0, + .selectedChild = 0, + .display = Display_Timer, + .onEnter = OnEnter_Timer, + .onUpdate = NULL, + .onButton = Timer_OnButton, + .data = NULL +}; + +MenuNode g_stopwatchNode = { + .name = "SECOND", + .parent = NULL, + .children = NULL, + .childCount = 0, + .selectedChild = 0, + .display = Display_Stopwatch, + .onEnter = OnEnter_Stopwatch, + .onUpdate = NULL, + .onButton = Stopwatch_OnButton, + .data = NULL +}; + +MenuNode g_ZeroMillisNode = { + .name = "00 SEC", + .parent = &g_gamesNode, + .children = NULL, + .childCount = 0, + .selectedChild = 0, + .needsRedraw = 1, + .display = Display_ZeroMillis, + .onEnter = OnEnter_ZeroMillis, + .onUpdate = NULL, + .onButton = ZeroMillis_OnButton, + .data = &g_game1sec +}; + +MenuNode g_ReactionTimeNode = { + .name = "CSTEST", + .parent = &g_gamesNode, + .children = NULL, + .childCount = 0, + .selectedChild = 0, + .display = Display_Reaction, + .onEnter = OnEnter_Reaction, + .onUpdate = OnUpdate_Reaction, + .onButton = Reaction_OnButton, + .data = &g_gameReaction +}; + +MenuNode g_ClickerTimeNode = { + .name = "CLICER", + .parent = &g_gamesNode, + .children = NULL, + .childCount = 0, + .selectedChild = 0, + .display = Display_Clicker, + .onEnter = OnEnter_Clicker, + .onUpdate = OnUpdate_Clicker, + .onButton = Clicker_OnButton, + .data = &g_gameClicker +}; + +// ==================== Узлы меню ==================== +MenuNode g_gamesNode; +MenuNode g_settingsNode; +MenuNode g_timeEditNode; +MenuNode g_dutyEditNode; +MenuNode g_LEDEditNode; +MenuNode g_MenuSoundNode; +MenuNode g_PowerOnSongNode; +MenuNode g_SongNode; +MenuNode g_resetNode; + +static MenuNode* g_gamesChildren[] = { + &g_ZeroMillisNode, + &g_ReactionTimeNode, + &g_ClickerTimeNode +}; + +static MenuNode* g_settingsChildren[] = { + &g_timeEditNode, + &g_dutyEditNode, + &g_LEDEditNode, + &g_MenuSoundNode, + &g_PowerOnSongNode, + &g_SongNode, + &g_resetNode +}; + +static MenuNode* g_mainMenuChildren[] = { + &g_clockNode, + &g_timerNode, + &g_stopwatchNode, + &g_gamesNode, + &g_settingsNode +}; + +static MenuNode g_rootMenu = { + .name = "MAIN", + .parent = NULL, + .children = g_mainMenuChildren, + .childCount = 5, + .selectedChild = 0, + .display = Display_MenuItem, + .onEnter = NULL, + .onUpdate = NULL, + .onButton = NULL, + .data = NULL +}; + +void MenuItems_Init(void) { + g_gamesNode = (MenuNode){ + .name = "PLAY", + .parent = &g_rootMenu, + .children = g_gamesChildren, + .childCount = 3, + .selectedChild = 0, + .display = Display_MenuItem, + .onEnter = NULL, + .onUpdate = NULL, + .onButton = NULL, + .data = NULL + }; + + g_settingsNode = (MenuNode){ + .name = "SETUP", + .parent = &g_rootMenu, + .children = g_settingsChildren, + .childCount = 7, + .selectedChild = 0, + .display = Display_MenuItem, + .onEnter = NULL, + .onUpdate = NULL, + .onButton = NULL, + .data = NULL + }; + + g_timeEditNode = (MenuNode){ + .name = "SET T", + .parent = &g_settingsNode, + .children = NULL, + .childCount = 0, + .selectedChild = 0, + .display = Display_TimeEdit, + .onEnter = OnEnter_TimeEdit, + .onUpdate = OnUpdate_TimeEdit, + .onButton = TimeEdit_OnButton, + .data = &g_timeData + }; + + g_dutyEditNode = (MenuNode){ + .name = "SET D", + .parent = &g_settingsNode, + .children = NULL, + .childCount = 0, + .selectedChild = 0, + .display = Display_DutyEdit, + .onEnter = OnEnter_DutyEdit, + .onUpdate = NULL, + .onButton = DutyEdit_OnButton, + .data = &g_editDuty + }; + + g_resetNode = (MenuNode){ + .name = "RESET", + .parent = &g_settingsNode, + .children = NULL, + .childCount = 0, + .selectedChild = 0, + .display = Display_Reset, + .onEnter = OnEnter_Reset, + .onUpdate = NULL, + .onButton = Reset_OnButton, + .data = NULL + }; +} + +MenuNode* Menu_GetRootMenu(void) { + return &g_rootMenu; +} + +MenuNode* Menu_GetCurrentActivity(void) { + return g_currentActivity; +} \ No newline at end of file diff --git a/Core/Clock/menu_items.h b/Core/Clock/menu_items.h new file mode 100644 index 0000000..21752c0 --- /dev/null +++ b/Core/Clock/menu_items.h @@ -0,0 +1,19 @@ +#ifndef MENU_ITEMS_H +#define MENU_ITEMS_H + +#include "menu.h" + +// Глобальные узлы меню (для доступа из main.c) +extern MenuNode g_clockNode; +extern MenuNode g_timerNode; +extern MenuNode g_stopwatchNode; +extern MenuNode g_gamesNode; +extern MenuNode g_settingsNode; +extern MenuNode g_timeEditNode; +extern MenuNode g_dutyEditNode; +extern MenuNode g_resetNode; + +// Инициализация всех пунктов меню +void MenuItems_Init(void); + +#endif \ No newline at end of file diff --git a/Core/Src/main.c b/Core/Src/main.c index a98dcf9..3ea5898 100644 --- a/Core/Src/main.c +++ b/Core/Src/main.c @@ -25,7 +25,7 @@ /* Private includes ----------------------------------------------------------*/ /* USER CODE BEGIN Includes */ #include "clock_manager.h" -#include "menu.h" +#include "menu_items.h" #include "segment.h" #include "melody.h" #include "songs.h" @@ -81,7 +81,6 @@ void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim) { } } -MelodyHandle melody; #define curr_song Polyphia_OD Melody_t *mySong = &curr_song; @@ -127,12 +126,13 @@ int main(void) /* USER CODE BEGIN WHILE */ Segment_Init(); ClockManager_Init(); - Menu_Init(); + Menu_Init(&g_clockNode); HAL_TIM_PWM_Start(&htim1, TIM_CHANNEL_1); HAL_TIM_Base_Start_IT(&htim2); + MenuItems_Init(); Melody_Init(&melody, &htim1, TIM_CHANNEL_1, 72000000); - Melody_Play(&melody, mySong, 134); +// Melody_Play(&melody, mySong, 134); while (1) { Menu_Process(); diff --git a/MDK-ARM/lamp.uvoptx b/MDK-ARM/lamp.uvoptx index 0709e2d..bad51b0 100644 --- a/MDK-ARM/lamp.uvoptx +++ b/MDK-ARM/lamp.uvoptx @@ -170,6 +170,16 @@ 1 htim1.Instance,0x0A + + 4 + 1 + g_ctx + + + 5 + 1 + g_game1sec + 0 @@ -598,6 +608,30 @@ 0 0 0 + ..\Core\Clock\menu_items.c + menu_items.c + 0 + 0 + + + 5 + 28 + 5 + 0 + 0 + 0 + ..\Core\Clock\menu_items.h + menu_items.h + 0 + 0 + + + 5 + 29 + 1 + 0 + 0 + 0 ..\Core\Clock\menu.c menu.c 0 @@ -605,7 +639,7 @@ 5 - 28 + 30 5 0 0 @@ -617,7 +651,7 @@ 5 - 29 + 31 1 0 0 @@ -629,7 +663,7 @@ 5 - 30 + 32 5 0 0 @@ -641,7 +675,7 @@ 5 - 31 + 33 1 0 0 @@ -653,7 +687,7 @@ 5 - 32 + 34 5 0 0 @@ -665,7 +699,7 @@ 5 - 33 + 35 5 0 0 @@ -677,7 +711,19 @@ 5 - 34 + 36 + 5 + 0 + 0 + 0 + ..\Core\Clock\sounds.h + sounds.h + 0 + 0 + + + 5 + 37 5 0 0 diff --git a/MDK-ARM/lamp.uvprojx b/MDK-ARM/lamp.uvprojx index dc92526..424817f 100644 --- a/MDK-ARM/lamp.uvprojx +++ b/MDK-ARM/lamp.uvprojx @@ -777,6 +777,16 @@ 5 ..\Core\Clock\clock_manager.h + + menu_items.c + 1 + ..\Core\Clock\menu_items.c + + + menu_items.h + 5 + ..\Core\Clock\menu_items.h + menu.c 1 @@ -812,6 +822,11 @@ 5 ..\Core\Clock\songs.h + + sounds.h + 5 + ..\Core\Clock\sounds.h + notes.h 5