Compare commits
3 Commits
e5285479f9
...
71fc59d34d
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
71fc59d34d | ||
|
|
7b7ab6f11d | ||
|
|
ad9b951bdb |
@@ -1,6 +1,8 @@
|
|||||||
#include "melody.h"
|
#include "melody.h"
|
||||||
|
|
||||||
#define FIXED_ARR 10 // фиксированный период таймера
|
MelodyHandle melody;
|
||||||
|
#define FIXED_ARR 1000 // фиксированный период таймера
|
||||||
|
#define FIXED_VOLUME 2 // фиксированный период таймера
|
||||||
|
|
||||||
// Конвертирует длительность ноты в миллисекунды
|
// Конвертирует длительность ноты в миллисекунды
|
||||||
static uint32_t _duration_to_ms(MelodyHandle* mh, float duration) {
|
static uint32_t _duration_to_ms(MelodyHandle* mh, float duration) {
|
||||||
@@ -31,15 +33,14 @@ static void _set_freq(MelodyHandle* mh, uint32_t freq) {
|
|||||||
// Фиксированный период
|
// Фиксированный период
|
||||||
__HAL_TIM_SET_AUTORELOAD(mh->htim, FIXED_ARR);
|
__HAL_TIM_SET_AUTORELOAD(mh->htim, FIXED_ARR);
|
||||||
// Скважность 50% для чистого тона
|
// Скважность 50% для чистого тона
|
||||||
__HAL_TIM_SET_COMPARE(mh->htim, mh->channel, FIXED_ARR / 2);
|
__HAL_TIM_SET_COMPARE(mh->htim, mh->channel, FIXED_VOLUME);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Melody_Init(MelodyHandle* mh, TIM_HandleTypeDef* htim, uint32_t channel, uint32_t timer_clock_hz) {
|
void Melody_Init(MelodyHandle* mh, TIM_HandleTypeDef* htim, uint32_t channel, uint32_t timer_clock_hz) {
|
||||||
mh->htim = htim;
|
mh->htim = htim;
|
||||||
mh->channel = channel;
|
mh->channel = channel;
|
||||||
mh->timer_clock_hz = timer_clock_hz;
|
mh->timer_clock_hz = timer_clock_hz;
|
||||||
mh->sequence = NULL;
|
mh->melody = NULL;
|
||||||
mh->length = 0;
|
|
||||||
mh->current_index = 0;
|
mh->current_index = 0;
|
||||||
mh->note_start_time = 0;
|
mh->note_start_time = 0;
|
||||||
mh->is_playing = 0;
|
mh->is_playing = 0;
|
||||||
@@ -56,20 +57,19 @@ void Melody_SetBPM(MelodyHandle* mh, uint16_t bpm) {
|
|||||||
mh->bpm = bpm;
|
mh->bpm = bpm;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Melody_Play(MelodyHandle* mh, const Note* sequence, uint16_t length, uint16_t bpm) {
|
void Melody_Play(MelodyHandle* mh, Melody_t *melody, uint16_t bpm) {
|
||||||
if (mh->is_playing) {
|
if (mh->is_playing) {
|
||||||
_set_freq(mh, 0);
|
_set_freq(mh, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
mh->sequence = sequence;
|
mh->melody = melody;
|
||||||
mh->length = length;
|
|
||||||
mh->current_index = 0;
|
mh->current_index = 0;
|
||||||
mh->is_playing = 1;
|
mh->is_playing = 1;
|
||||||
mh->note_start_time = HAL_GetTick();
|
mh->note_start_time = HAL_GetTick();
|
||||||
mh->bpm = bpm;
|
mh->bpm = bpm;
|
||||||
|
|
||||||
if (length > 0 && sequence[0].freq != 0) {
|
if (melody->length > 0 && melody->sequence[0].freq != 0) {
|
||||||
_set_freq(mh, sequence[0].freq);
|
_set_freq(mh, melody->sequence[0].freq);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -82,22 +82,22 @@ void Melody_Update(MelodyHandle* mh) {
|
|||||||
if (!mh->is_playing) return;
|
if (!mh->is_playing) return;
|
||||||
|
|
||||||
uint32_t now = HAL_GetTick();
|
uint32_t now = HAL_GetTick();
|
||||||
uint32_t dur_ms = _duration_to_ms(mh, mh->sequence[mh->current_index].duration);
|
uint32_t dur_ms = _duration_to_ms(mh, mh->melody->sequence[mh->current_index].duration);
|
||||||
|
|
||||||
if (now - mh->note_start_time >= dur_ms) {
|
if (now - mh->note_start_time >= dur_ms) {
|
||||||
mh->current_index++;
|
mh->current_index++;
|
||||||
|
|
||||||
if (mh->current_index >= mh->length) {
|
if (mh->current_index >= mh->melody->length) {
|
||||||
Melody_Stop(mh);
|
Melody_Stop(mh);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
mh->note_start_time = now;
|
mh->note_start_time = now;
|
||||||
|
|
||||||
if (mh->sequence[mh->current_index].freq == NOTE_REST) {
|
if (mh->melody->sequence[mh->current_index].freq == NOTE_REST) {
|
||||||
__HAL_TIM_SET_COMPARE(mh->htim, mh->channel, 0);
|
__HAL_TIM_SET_COMPARE(mh->htim, mh->channel, 0);
|
||||||
} else {
|
} else {
|
||||||
_set_freq(mh, mh->sequence[mh->current_index].freq);
|
_set_freq(mh, mh->melody->sequence[mh->current_index].freq);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,174 +2,26 @@
|
|||||||
#define MELODY_H
|
#define MELODY_H
|
||||||
|
|
||||||
#include "stm32f1xx_hal.h"
|
#include "stm32f1xx_hal.h"
|
||||||
|
#include "songs.h"
|
||||||
typedef struct {
|
#include "sounds.h"
|
||||||
uint16_t freq; // частота в Гц или NOTE_REST
|
|
||||||
float duration; // длительность в долях (0.25 = четверть)
|
|
||||||
} Note;
|
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
TIM_HandleTypeDef* htim;
|
TIM_HandleTypeDef* htim;
|
||||||
uint32_t channel;
|
uint32_t channel;
|
||||||
uint32_t timer_clock_hz; // частота тактирования таймера в Гц
|
uint32_t timer_clock_hz; // частота тактирования таймера в Гц
|
||||||
const Note* sequence;
|
Melody_t *melody;
|
||||||
uint16_t length;
|
|
||||||
uint16_t current_index;
|
uint16_t current_index;
|
||||||
uint32_t note_start_time;
|
uint32_t note_start_time;
|
||||||
uint8_t is_playing;
|
uint8_t is_playing;
|
||||||
uint16_t bpm;
|
uint16_t bpm;
|
||||||
} MelodyHandle;
|
} MelodyHandle;
|
||||||
|
extern MelodyHandle melody;
|
||||||
|
|
||||||
void Melody_Init(MelodyHandle* mh, TIM_HandleTypeDef* htim, uint32_t channel, uint32_t timer_clock_hz);
|
void Melody_Init(MelodyHandle* mh, TIM_HandleTypeDef* htim, uint32_t channel, uint32_t timer_clock_hz);
|
||||||
void Melody_SetBPM(MelodyHandle* mh, uint16_t bpm);
|
void Melody_SetBPM(MelodyHandle* mh, uint16_t bpm);
|
||||||
void Melody_Play(MelodyHandle* mh, const Note* sequence, uint16_t length, uint16_t bpm);
|
void Melody_Play(MelodyHandle* mh, Melody_t* melody, uint16_t bpm);
|
||||||
void Melody_Stop(MelodyHandle* mh);
|
void Melody_Stop(MelodyHandle* mh);
|
||||||
void Melody_Update(MelodyHandle* mh);
|
void Melody_Update(MelodyHandle* mh);
|
||||||
uint8_t Melody_IsPlaying(MelodyHandle* mh);
|
uint8_t Melody_IsPlaying(MelodyHandle* mh);
|
||||||
|
|
||||||
// Длительности (в долях от целой ноты)
|
|
||||||
#define NOTE_WHOLE 1.0
|
|
||||||
#define NOTE_HALF 0.5
|
|
||||||
#define NOTE_QUARTER 0.25
|
|
||||||
#define NOTE_EIGHTH 0.125
|
|
||||||
#define NOTE_SIXTEENTH 0.0625
|
|
||||||
#define NOTE_THIRTYSECOND 0.03125
|
|
||||||
|
|
||||||
#define NOTE_WHOLE_DOT 1.5
|
|
||||||
#define NOTE_HALF_DOT 0.75
|
|
||||||
#define NOTE_QUARTER_DOT 0.375
|
|
||||||
#define NOTE_EIGHTH_DOT 0.1875
|
|
||||||
#define NOTE_SIXTEENTH_DOT 0.09375
|
|
||||||
|
|
||||||
// Ноты (частота, Гц) - диапазон 700-8000 Гц для SCS-17-S
|
|
||||||
// Октавы смещены: старая 4-я = новая 0-я, старая 5-я = новая 1-я и т.д.
|
|
||||||
|
|
||||||
// 0-я октава (бывшая 4-я)
|
|
||||||
#define NOTE_B0 494
|
|
||||||
#define NOTE_C1 523
|
|
||||||
#define NOTE_CS1 554
|
|
||||||
#define NOTE_D1 587
|
|
||||||
#define NOTE_DS1 622
|
|
||||||
#define NOTE_E1 659
|
|
||||||
#define NOTE_F1 698
|
|
||||||
#define NOTE_FS1 740
|
|
||||||
#define NOTE_G1 784
|
|
||||||
#define NOTE_GS1 831
|
|
||||||
#define NOTE_A1 880
|
|
||||||
#define NOTE_AS1 932
|
|
||||||
#define NOTE_B1 988
|
|
||||||
|
|
||||||
// 2-я октава (бывшая 6-я)
|
|
||||||
#define NOTE_C2 1047
|
|
||||||
#define NOTE_CS2 1109
|
|
||||||
#define NOTE_D2 1175
|
|
||||||
#define NOTE_DS2 1245
|
|
||||||
#define NOTE_E2 1319
|
|
||||||
#define NOTE_F2 1397
|
|
||||||
#define NOTE_FS2 1480
|
|
||||||
#define NOTE_G2 1568
|
|
||||||
#define NOTE_GS2 1661
|
|
||||||
#define NOTE_A2 1760
|
|
||||||
#define NOTE_AS2 1865
|
|
||||||
#define NOTE_B2 1976
|
|
||||||
|
|
||||||
// 3-я октава (бывшая 7-я)
|
|
||||||
#define NOTE_C3 2093
|
|
||||||
#define NOTE_CS3 2217
|
|
||||||
#define NOTE_D3 2349
|
|
||||||
#define NOTE_DS3 2489
|
|
||||||
#define NOTE_E3 2637
|
|
||||||
#define NOTE_F3 2794
|
|
||||||
#define NOTE_FS3 2960
|
|
||||||
#define NOTE_G3 3136
|
|
||||||
#define NOTE_GS3 3322
|
|
||||||
#define NOTE_A3 3520
|
|
||||||
#define NOTE_AS3 3729
|
|
||||||
#define NOTE_B3 3951
|
|
||||||
|
|
||||||
// 4-я октава (бывшая 8-я)
|
|
||||||
#define NOTE_C4 4186
|
|
||||||
#define NOTE_CS4 4435
|
|
||||||
#define NOTE_D4 4699
|
|
||||||
#define NOTE_DS4 4978
|
|
||||||
#define NOTE_E4 5274
|
|
||||||
#define NOTE_F4 5588
|
|
||||||
#define NOTE_FS4 5920
|
|
||||||
#define NOTE_G4 6272
|
|
||||||
#define NOTE_GS4 6645
|
|
||||||
#define NOTE_A4 7040
|
|
||||||
#define NOTE_AS4 7459
|
|
||||||
#define NOTE_B4 7902
|
|
||||||
|
|
||||||
// Сольфеджио с новыми октавами
|
|
||||||
// 0-я октава
|
|
||||||
#define SI0 NOTE_B0 // 494 Hz
|
|
||||||
|
|
||||||
// 1-я октава
|
|
||||||
#define DO1 NOTE_C1 // 523 Hz
|
|
||||||
#define RE1 NOTE_D1 // 587 Hz
|
|
||||||
#define MI1 NOTE_E1 // 659 Hz
|
|
||||||
#define FA1 NOTE_F1 // 698 Hz
|
|
||||||
#define SOL1 NOTE_G1 // 784 Hz
|
|
||||||
#define LA1 NOTE_A1 // 880 Hz
|
|
||||||
#define SI1 NOTE_B1 // 988 Hz
|
|
||||||
|
|
||||||
// 2-я октава
|
|
||||||
#define DO2 NOTE_C2 // 1047 Hz
|
|
||||||
#define RE2 NOTE_D2 // 1175 Hz
|
|
||||||
#define MI2 NOTE_E2 // 1319 Hz
|
|
||||||
#define FA2 NOTE_F2 // 1397 Hz
|
|
||||||
#define SOL2 NOTE_G2 // 1568 Hz
|
|
||||||
#define LA2 NOTE_A2 // 1760 Hz
|
|
||||||
#define SI2 NOTE_B2 // 1976 Hz
|
|
||||||
|
|
||||||
// 3-я октава
|
|
||||||
#define DO3 NOTE_C3 // 2093 Hz
|
|
||||||
#define RE3 NOTE_D3 // 2349 Hz
|
|
||||||
#define MI3 NOTE_E3 // 2637 Hz
|
|
||||||
#define FA3 NOTE_F3 // 2794 Hz
|
|
||||||
#define SOL3 NOTE_G3 // 3136 Hz
|
|
||||||
#define LA3 NOTE_A3 // 3520 Hz
|
|
||||||
#define SI3 NOTE_B3 // 3951 Hz
|
|
||||||
|
|
||||||
// 4-я октава
|
|
||||||
#define DO4 NOTE_C4 // 4186 Hz
|
|
||||||
#define RE4 NOTE_D4 // 4699 Hz
|
|
||||||
#define MI4 NOTE_E4 // 5274 Hz
|
|
||||||
#define FA4 NOTE_F4 // 5588 Hz
|
|
||||||
#define SOL4 NOTE_G4 // 6272 Hz
|
|
||||||
#define LA4 NOTE_A4 // 7040 Hz
|
|
||||||
#define SI4 NOTE_B4 // 7902 Hz
|
|
||||||
|
|
||||||
// Диезы для 1-й октавы
|
|
||||||
#define DO1s NOTE_CS1 // 554 Hz
|
|
||||||
#define RE1s NOTE_DS1 // 622 Hz
|
|
||||||
#define FA1s NOTE_FS1 // 740 Hz
|
|
||||||
#define SOL1s NOTE_GS1 // 831 Hz
|
|
||||||
#define LA1s NOTE_AS1 // 932 Hz
|
|
||||||
|
|
||||||
// Диезы для 2-й октавы
|
|
||||||
#define DO2s NOTE_CS2 // 1109 Hz
|
|
||||||
#define RE2s NOTE_DS2 // 1245 Hz
|
|
||||||
#define FA2s NOTE_FS2 // 1480 Hz
|
|
||||||
#define SOL2s NOTE_GS2 // 1661 Hz
|
|
||||||
#define LA2s NOTE_AS2 // 1865 Hz
|
|
||||||
|
|
||||||
// Диезы для 3-й октавы
|
|
||||||
#define DO3s NOTE_CS3 // 2217 Hz
|
|
||||||
#define RE3s NOTE_DS3 // 2489 Hz
|
|
||||||
#define FA3s NOTE_FS3 // 2960 Hz
|
|
||||||
#define SOL3s NOTE_GS3 // 3322 Hz
|
|
||||||
#define LA3s NOTE_AS3 // 3729 Hz
|
|
||||||
|
|
||||||
// Диезы для 4-й октавы
|
|
||||||
#define DO4s NOTE_CS4 // 4435 Hz
|
|
||||||
#define RE4s NOTE_DS4 // 4978 Hz
|
|
||||||
#define FA4s NOTE_FS4 // 5920 Hz
|
|
||||||
#define SOL4s NOTE_GS4 // 6645 Hz
|
|
||||||
#define LA4s NOTE_AS4 // 7459 Hz
|
|
||||||
|
|
||||||
#define NOTE_REST 0
|
|
||||||
|
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
@@ -1,426 +1,195 @@
|
|||||||
#include "menu.h"
|
#include "menu.h"
|
||||||
#include "segment.h"
|
#include "segment.h"
|
||||||
#include "clock_manager.h"
|
#include <string.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
|
||||||
// Время удержания кнопки (мс), после которого нажатие считается "длинным"
|
|
||||||
// Используется для входа в меню из режима часов
|
|
||||||
#define LONG_PRESS_MS 500
|
#define LONG_PRESS_MS 500
|
||||||
|
|
||||||
// Задержка перед началом автоповтора (мс)
|
MenuContext g_ctx;
|
||||||
// При удержании кнопки первые 500 мс ничего не происходит
|
uint8_t g_sound_enabled = 1;
|
||||||
#define REPEAT_DELAY_MS 500
|
|
||||||
|
|
||||||
// Интервал между автоповторами (мс)
|
extern bool ReadButton(Button_Type btn);
|
||||||
// После начала повтора каждые 100 мс кнопка срабатывает заново
|
extern MenuNode* Menu_GetRootMenu(void);
|
||||||
#define REPEAT_INTERVAL_MS 100
|
|
||||||
|
|
||||||
// Интервал мигания редактируемого разряда (мс)
|
void Menu_Init(MenuNode* startNode) {
|
||||||
// Моргает каждые 500 мс (0.5 сек вкл / 0.5 сек выкл)
|
g_ctx.currentNode = startNode;
|
||||||
#define BLINK_INTERVAL_MS 500
|
g_ctx.needsRedraw = true;
|
||||||
|
g_ctx.lastTick = 0;
|
||||||
|
|
||||||
// Задержка антидребезга (мс)
|
if (startNode && startNode->onEnter) {
|
||||||
// Изменение состояния кнопки фиксируется только если оно стабильно в течение 30 мс
|
startNode->onEnter();
|
||||||
#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;
|
|
||||||
|
|
||||||
// Для 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;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ==================== Логика SET_TIME ====================
|
// НОВАЯ ФУНКЦИЯ - открыть меню
|
||||||
static void IncreaseTimeDigit(void) {
|
void Menu_OpenMenu(MenuNode* menu) {
|
||||||
uint8_t tens, units;
|
if (!menu) return;
|
||||||
switch (menu.editStep) {
|
g_ctx.currentNode = menu;
|
||||||
case 0: // десятки часов (0-2)
|
g_ctx.needsRedraw = true;
|
||||||
tens = menu.editTime.hour / 10;
|
if (menu->onEnter) {
|
||||||
units = menu.editTime.hour % 10;
|
menu->onEnter();
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
UpdateDisplay();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static void DecreaseTimeDigit(void) {
|
void Menu_HandleButton(Button_Type btn, bool longPress) {
|
||||||
uint8_t tens, units;
|
if (!g_ctx.currentNode) return;
|
||||||
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: // единицы часов
|
// Длинное нажатие SELECT на корневом уровне (часы) - вход в меню
|
||||||
tens = menu.editTime.hour / 10;
|
if (longPress && btn == BUTTON_SELECT && g_ctx.currentNode->parent == NULL) {
|
||||||
units = menu.editTime.hour % 10;
|
SOUND_DOUBLE;
|
||||||
if (units == 0) {
|
MenuNode* rootMenu = Menu_GetRootMenu();
|
||||||
units = (tens == 2) ? 3 : 9;
|
if (rootMenu) {
|
||||||
} else {
|
Menu_OpenMenu(rootMenu);
|
||||||
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();
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
switch (menu.state) {
|
if (g_ctx.currentNode->onButton) {
|
||||||
case STATE_MAIN_MENU:
|
if(btn == BUTTON_SELECT)
|
||||||
if (btn == BUTTON_UP) {
|
{
|
||||||
menu.selectedMenuItem++;
|
SOUND_DOUBLE;
|
||||||
if (menu.selectedMenuItem >= MAIN_MENU_COUNT) {
|
|
||||||
menu.selectedMenuItem = 0;
|
|
||||||
}
|
}
|
||||||
UpdateDisplay();
|
else
|
||||||
|
{
|
||||||
|
SOUND_CLICK;
|
||||||
}
|
}
|
||||||
else if (btn == BUTTON_DOWN) {
|
|
||||||
if (menu.selectedMenuItem == 0) {
|
g_ctx.currentNode->onButton(btn, longPress);
|
||||||
menu.selectedMenuItem = MAIN_MENU_COUNT - 1;
|
return;
|
||||||
} else {
|
|
||||||
menu.selectedMenuItem--;
|
|
||||||
}
|
}
|
||||||
UpdateDisplay();
|
|
||||||
}
|
if (g_ctx.currentNode->parent != NULL) {
|
||||||
else if (btn == BUTTON_SELECT && !longPress) {
|
switch (btn) {
|
||||||
switch (menu.selectedMenuItem) {
|
case BUTTON_UP:
|
||||||
case MAIN_MENU_SET_TIME:
|
SOUND_CLICK;
|
||||||
menu.state = STATE_SET_TIME;
|
if (g_ctx.currentNode->selectedChild > 0) {
|
||||||
menu.originalTime = ClockManager_GetTime(0);
|
g_ctx.currentNode->selectedChild--;
|
||||||
menu.editTime = menu.originalTime;
|
g_ctx.needsRedraw = true;
|
||||||
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;
|
break;
|
||||||
|
|
||||||
case STATE_SET_TIME:
|
case BUTTON_DOWN:
|
||||||
if (btn == BUTTON_UP) {
|
SOUND_CLICK;
|
||||||
IncreaseTimeDigit();
|
if (g_ctx.currentNode->selectedChild < g_ctx.currentNode->childCount - 1) {
|
||||||
}
|
g_ctx.currentNode->selectedChild++;
|
||||||
else if (btn == BUTTON_DOWN) {
|
g_ctx.needsRedraw = true;
|
||||||
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;
|
break;
|
||||||
|
|
||||||
case STATE_SET_DUTY:
|
case BUTTON_SELECT: {
|
||||||
if (btn == BUTTON_UP && menu.editDuty < 10) {
|
SOUND_DOUBLE;
|
||||||
menu.editDuty++;
|
if (g_ctx.currentNode->children) {
|
||||||
Segment_SetBrightness(menu.editDuty * 10);
|
MenuNode* selected = g_ctx.currentNode->children[g_ctx.currentNode->selectedChild];
|
||||||
UpdateDisplay();
|
if (selected) {
|
||||||
|
g_ctx.currentNode = selected;
|
||||||
|
g_ctx.needsRedraw = true;
|
||||||
|
if (selected->onEnter) {
|
||||||
|
selected->onEnter();
|
||||||
}
|
}
|
||||||
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;
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
case STATE_RESET_CONFIRM:
|
case BUTTON_BACK:
|
||||||
if (btn == BUTTON_SELECT) {
|
SOUND_CLICK;
|
||||||
ClockManager_ResetTime();
|
Menu_GoBack();
|
||||||
ClockManager_SetDuty(8);
|
|
||||||
menu.state = STATE_CLOCK;
|
|
||||||
UpdateDisplay();
|
|
||||||
}
|
|
||||||
else if (btn == BUTTON_BACK) {
|
|
||||||
menu.state = STATE_CLOCK;
|
|
||||||
UpdateDisplay();
|
|
||||||
}
|
|
||||||
break;
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ==================== Публичные функции ====================
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void Menu_Process(void) {
|
void Menu_Process(void) {
|
||||||
uint32_t tick = HAL_GetTick();
|
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++) {
|
for (int i = 0; i < BUTTON_COUNT; i++) {
|
||||||
bool rawPressed = ReadButton(i);
|
bool reading = ReadButton((Button_Type)i);
|
||||||
bool pressed;
|
|
||||||
|
|
||||||
// Антидребезг
|
if (reading != g_ctx.lastButtonState[i]) {
|
||||||
if (rawPressed != menu.buttonStableState[i]) {
|
g_ctx.lastDebounceTime[i] = tick;
|
||||||
menu.lastDebounceTime[i] = tick;
|
|
||||||
menu.buttonStableState[i] = rawPressed;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if ((tick - menu.lastDebounceTime[i]) >= DEBOUNCE_MS) {
|
if ((tick - g_ctx.lastDebounceTime[i]) > 30) { // DEBOUNCE_MS = 30
|
||||||
pressed = menu.buttonStableState[i];
|
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 {
|
} else {
|
||||||
pressed = menu.buttonPrevState[i]; // старое значение пока не стабилизировалось
|
if (!g_ctx.longPressTriggered[i]) {
|
||||||
}
|
Menu_HandleButton((Button_Type)i, false);
|
||||||
bool wasPressed = menu.buttonPrevState[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;
|
|
||||||
}
|
|
||||||
|
|
||||||
menu.buttonPrevState[i] = pressed;
|
|
||||||
}
|
|
||||||
|
|
||||||
static uint32_t lastClockUpdate = 0;
|
|
||||||
if (menu.state == STATE_CLOCK && (tick - lastClockUpdate >= 200)) {
|
|
||||||
lastClockUpdate = tick;
|
|
||||||
UpdateDisplay();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
SystemState Menu_GetState(void) {
|
if (g_ctx.buttonState[i] && !g_ctx.longPressTriggered[i]) {
|
||||||
return menu.state;
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
}
|
}
|
||||||
@@ -4,6 +4,14 @@
|
|||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
#include <stdbool.h>
|
#include <stdbool.h>
|
||||||
#include "main.h"
|
#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 {
|
typedef enum {
|
||||||
BUTTON_UP,
|
BUTTON_UP,
|
||||||
@@ -13,18 +21,50 @@ typedef enum {
|
|||||||
BUTTON_COUNT
|
BUTTON_COUNT
|
||||||
} Button_Type;
|
} Button_Type;
|
||||||
|
|
||||||
typedef enum {
|
typedef struct MenuNode {
|
||||||
STATE_CLOCK, // режим часов
|
const char* name;
|
||||||
STATE_MAIN_MENU, // главное меню (выбор пункта)
|
struct MenuNode* parent;
|
||||||
STATE_SET_TIME, // настройка времени
|
struct MenuNode** children;
|
||||||
STATE_SET_DUTY, // настройка яркости
|
uint8_t childCount;
|
||||||
STATE_RESET_CONFIRM // подтверждение сброса
|
uint8_t selectedChild;
|
||||||
} SystemState;
|
uint8_t needsRedraw;
|
||||||
|
|
||||||
// Чтение кнопки (реализуется в main.c или hal)
|
void (*display)(void);
|
||||||
int ReadButton(int btn);
|
void (*onEnter)(void);
|
||||||
|
void (*onUpdate)(void);
|
||||||
|
void (*onButton)(Button_Type btn, bool longPress);
|
||||||
|
|
||||||
void Menu_Init(void);
|
void* data;
|
||||||
|
} MenuNode;
|
||||||
|
|
||||||
|
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;
|
||||||
|
|
||||||
|
// Глобальные переменные
|
||||||
|
extern uint8_t g_sound_enabled;
|
||||||
|
extern MenuContext g_ctx;
|
||||||
|
|
||||||
|
// Функции
|
||||||
|
void Menu_Init(MenuNode* startNode);
|
||||||
void Menu_Process(void);
|
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
|
#endif
|
||||||
695
Core/Clock/menu_items.c
Normal file
695
Core/Clock/menu_items.c
Normal file
@@ -0,0 +1,695 @@
|
|||||||
|
#include "menu_items.h"
|
||||||
|
#include "segment.h"
|
||||||
|
#include "clock_manager.h"
|
||||||
|
#include <string.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
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;
|
||||||
|
}
|
||||||
19
Core/Clock/menu_items.h
Normal file
19
Core/Clock/menu_items.h
Normal file
@@ -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
|
||||||
161
Core/Clock/notes.h
Normal file
161
Core/Clock/notes.h
Normal file
@@ -0,0 +1,161 @@
|
|||||||
|
#ifndef NOTES_H
|
||||||
|
#define NOTES_H
|
||||||
|
#include "stm32f1xx_hal.h"
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
uint16_t freq; // частота в Гц или NOTE_REST
|
||||||
|
float duration; // длительность в долях (0.25 = четверть)
|
||||||
|
} Note_t;
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
float length; // длительность мелодии в количестве нот
|
||||||
|
const Note_t *sequence; // последовательность нот
|
||||||
|
} Melody_t;
|
||||||
|
|
||||||
|
|
||||||
|
// Длительности (в долях от целой ноты)
|
||||||
|
#define NOTE_WHOLE 1.0
|
||||||
|
#define NOTE_HALF 0.5
|
||||||
|
#define NOTE_QUARTER 0.25
|
||||||
|
#define NOTE_EIGHTH 0.125
|
||||||
|
#define NOTE_SIXTEENTH 0.0625
|
||||||
|
#define NOTE_THIRTYSECOND 0.03125
|
||||||
|
|
||||||
|
#define NOTE_WHOLE_DOT 1.5
|
||||||
|
#define NOTE_HALF_DOT 0.75
|
||||||
|
#define NOTE_QUARTER_DOT 0.375
|
||||||
|
#define NOTE_EIGHTH_DOT 0.1875
|
||||||
|
#define NOTE_SIXTEENTH_DOT 0.09375
|
||||||
|
|
||||||
|
// Ноты (частота, Гц) - диапазон 700-8000 Гц для SCS-17-S
|
||||||
|
// Октавы смещены: старая 4-я = новая 0-я, старая 5-я = новая 1-я и т.д.
|
||||||
|
|
||||||
|
// 0-я октава (бывшая 4-я)
|
||||||
|
#define NOTE_B0 494
|
||||||
|
#define NOTE_C1 523
|
||||||
|
#define NOTE_CS1 554
|
||||||
|
#define NOTE_D1 587
|
||||||
|
#define NOTE_DS1 622
|
||||||
|
#define NOTE_E1 659
|
||||||
|
#define NOTE_F1 698
|
||||||
|
#define NOTE_FS1 740
|
||||||
|
#define NOTE_G1 784
|
||||||
|
#define NOTE_GS1 831
|
||||||
|
#define NOTE_A1 880
|
||||||
|
#define NOTE_AS1 932
|
||||||
|
#define NOTE_B1 988
|
||||||
|
|
||||||
|
// 2-я октава (бывшая 6-я)
|
||||||
|
#define NOTE_C2 1047
|
||||||
|
#define NOTE_CS2 1109
|
||||||
|
#define NOTE_D2 1175
|
||||||
|
#define NOTE_DS2 1245
|
||||||
|
#define NOTE_E2 1319
|
||||||
|
#define NOTE_F2 1397
|
||||||
|
#define NOTE_FS2 1480
|
||||||
|
#define NOTE_G2 1568
|
||||||
|
#define NOTE_GS2 1661
|
||||||
|
#define NOTE_A2 1760
|
||||||
|
#define NOTE_AS2 1865
|
||||||
|
#define NOTE_B2 1976
|
||||||
|
|
||||||
|
// 3-я октава (бывшая 7-я)
|
||||||
|
#define NOTE_C3 2093
|
||||||
|
#define NOTE_CS3 2217
|
||||||
|
#define NOTE_D3 2349
|
||||||
|
#define NOTE_DS3 2489
|
||||||
|
#define NOTE_E3 2637
|
||||||
|
#define NOTE_F3 2794
|
||||||
|
#define NOTE_FS3 2960
|
||||||
|
#define NOTE_G3 3136
|
||||||
|
#define NOTE_GS3 3322
|
||||||
|
#define NOTE_A3 3520
|
||||||
|
#define NOTE_AS3 3729
|
||||||
|
#define NOTE_B3 3951
|
||||||
|
|
||||||
|
// 4-я октава (бывшая 8-я)
|
||||||
|
#define NOTE_C4 4186
|
||||||
|
#define NOTE_CS4 4435
|
||||||
|
#define NOTE_D4 4699
|
||||||
|
#define NOTE_DS4 4978
|
||||||
|
#define NOTE_E4 5274
|
||||||
|
#define NOTE_F4 5588
|
||||||
|
#define NOTE_FS4 5920
|
||||||
|
#define NOTE_G4 6272
|
||||||
|
#define NOTE_GS4 6645
|
||||||
|
#define NOTE_A4 7040
|
||||||
|
#define NOTE_AS4 7459
|
||||||
|
#define NOTE_B4 7902
|
||||||
|
|
||||||
|
// Сольфеджио с новыми октавами
|
||||||
|
// 0-я октава
|
||||||
|
#define SI0 NOTE_B0 // 494 Hz
|
||||||
|
|
||||||
|
// 1-я октава
|
||||||
|
#define DO1 NOTE_C1 // 523 Hz
|
||||||
|
#define RE1 NOTE_D1 // 587 Hz
|
||||||
|
#define MI1 NOTE_E1 // 659 Hz
|
||||||
|
#define FA1 NOTE_F1 // 698 Hz
|
||||||
|
#define SOL1 NOTE_G1 // 784 Hz
|
||||||
|
#define LA1 NOTE_A1 // 880 Hz
|
||||||
|
#define SI1 NOTE_B1 // 988 Hz
|
||||||
|
|
||||||
|
// 2-я октава
|
||||||
|
#define DO2 NOTE_C2 // 1047 Hz
|
||||||
|
#define RE2 NOTE_D2 // 1175 Hz
|
||||||
|
#define MI2 NOTE_E2 // 1319 Hz
|
||||||
|
#define FA2 NOTE_F2 // 1397 Hz
|
||||||
|
#define SOL2 NOTE_G2 // 1568 Hz
|
||||||
|
#define LA2 NOTE_A2 // 1760 Hz
|
||||||
|
#define SI2 NOTE_B2 // 1976 Hz
|
||||||
|
|
||||||
|
// 3-я октава
|
||||||
|
#define DO3 NOTE_C3 // 2093 Hz
|
||||||
|
#define RE3 NOTE_D3 // 2349 Hz
|
||||||
|
#define MI3 NOTE_E3 // 2637 Hz
|
||||||
|
#define FA3 NOTE_F3 // 2794 Hz
|
||||||
|
#define SOL3 NOTE_G3 // 3136 Hz
|
||||||
|
#define LA3 NOTE_A3 // 3520 Hz
|
||||||
|
#define SI3 NOTE_B3 // 3951 Hz
|
||||||
|
|
||||||
|
// 4-я октава
|
||||||
|
#define DO4 NOTE_C4 // 4186 Hz
|
||||||
|
#define RE4 NOTE_D4 // 4699 Hz
|
||||||
|
#define MI4 NOTE_E4 // 5274 Hz
|
||||||
|
#define FA4 NOTE_F4 // 5588 Hz
|
||||||
|
#define SOL4 NOTE_G4 // 6272 Hz
|
||||||
|
#define LA4 NOTE_A4 // 7040 Hz
|
||||||
|
#define SI4 NOTE_B4 // 7902 Hz
|
||||||
|
|
||||||
|
// Диезы для 1-й октавы
|
||||||
|
#define DO1s NOTE_CS1 // 554 Hz
|
||||||
|
#define RE1s NOTE_DS1 // 622 Hz
|
||||||
|
#define FA1s NOTE_FS1 // 740 Hz
|
||||||
|
#define SOL1s NOTE_GS1 // 831 Hz
|
||||||
|
#define LA1s NOTE_AS1 // 932 Hz
|
||||||
|
|
||||||
|
// Диезы для 2-й октавы
|
||||||
|
#define DO2s NOTE_CS2 // 1109 Hz
|
||||||
|
#define RE2s NOTE_DS2 // 1245 Hz
|
||||||
|
#define FA2s NOTE_FS2 // 1480 Hz
|
||||||
|
#define SOL2s NOTE_GS2 // 1661 Hz
|
||||||
|
#define LA2s NOTE_AS2 // 1865 Hz
|
||||||
|
|
||||||
|
// Диезы для 3-й октавы
|
||||||
|
#define DO3s NOTE_CS3 // 2217 Hz
|
||||||
|
#define RE3s NOTE_DS3 // 2489 Hz
|
||||||
|
#define FA3s NOTE_FS3 // 2960 Hz
|
||||||
|
#define SOL3s NOTE_GS3 // 3322 Hz
|
||||||
|
#define LA3s NOTE_AS3 // 3729 Hz
|
||||||
|
|
||||||
|
// Диезы для 4-й октавы
|
||||||
|
#define DO4s NOTE_CS4 // 4435 Hz
|
||||||
|
#define RE4s NOTE_DS4 // 4978 Hz
|
||||||
|
#define FA4s NOTE_FS4 // 5920 Hz
|
||||||
|
#define SOL4s NOTE_GS4 // 6645 Hz
|
||||||
|
#define LA4s NOTE_AS4 // 7459 Hz
|
||||||
|
|
||||||
|
#define NOTE_REST 0
|
||||||
|
|
||||||
|
|
||||||
|
#endif
|
||||||
126
Core/Clock/songs.h
Normal file
126
Core/Clock/songs.h
Normal file
@@ -0,0 +1,126 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "notes.h"
|
||||||
|
|
||||||
|
#define song_length(song_arr) sizeof(song_arr)/sizeof(song_arr[0])
|
||||||
|
|
||||||
|
static const Note_t Polyphia_OD_Notes[] = {
|
||||||
|
//1 ТАКТ
|
||||||
|
{FA1s, NOTE_SIXTEENTH},
|
||||||
|
{LA1, NOTE_SIXTEENTH},
|
||||||
|
{DO2s, NOTE_SIXTEENTH},
|
||||||
|
{FA2s, NOTE_SIXTEENTH},
|
||||||
|
{NOTE_REST, NOTE_QUARTER},
|
||||||
|
|
||||||
|
{FA1s, NOTE_SIXTEENTH},
|
||||||
|
{LA1, NOTE_SIXTEENTH},
|
||||||
|
{DO2s, NOTE_SIXTEENTH},
|
||||||
|
{RE2, NOTE_SIXTEENTH},
|
||||||
|
{FA1, NOTE_SIXTEENTH},
|
||||||
|
{SOL1s, NOTE_SIXTEENTH},
|
||||||
|
{SI1, NOTE_SIXTEENTH},
|
||||||
|
{FA2s, NOTE_EIGHTH},
|
||||||
|
|
||||||
|
//2 ТАКТ
|
||||||
|
{SOL2s, NOTE_SIXTEENTH},
|
||||||
|
{RE2, NOTE_SIXTEENTH},
|
||||||
|
{SI1, NOTE_SIXTEENTH},
|
||||||
|
{DO1s, NOTE_SIXTEENTH},
|
||||||
|
{FA1, NOTE_SIXTEENTH},
|
||||||
|
{SOL1s, NOTE_SIXTEENTH},
|
||||||
|
{DO2s, NOTE_SIXTEENTH},
|
||||||
|
{FA2, NOTE_THIRTYSECOND},
|
||||||
|
{FA2s, NOTE_THIRTYSECOND},
|
||||||
|
{FA2, NOTE_SIXTEENTH},
|
||||||
|
{DO2s, NOTE_SIXTEENTH},
|
||||||
|
{SI1, NOTE_SIXTEENTH},
|
||||||
|
{SOL1s, NOTE_SIXTEENTH},
|
||||||
|
{FA1, NOTE_SIXTEENTH},
|
||||||
|
{DO3s, NOTE_SIXTEENTH},
|
||||||
|
|
||||||
|
//3 ТАКТ
|
||||||
|
{FA1s, NOTE_SIXTEENTH},
|
||||||
|
{LA1, NOTE_SIXTEENTH},
|
||||||
|
{DO2s, NOTE_SIXTEENTH},
|
||||||
|
{FA2s, NOTE_SIXTEENTH},
|
||||||
|
{NOTE_REST, NOTE_QUARTER},
|
||||||
|
|
||||||
|
{FA1s, NOTE_SIXTEENTH},
|
||||||
|
{LA1, NOTE_SIXTEENTH},
|
||||||
|
{DO2s, NOTE_SIXTEENTH},
|
||||||
|
{RE2, NOTE_SIXTEENTH},
|
||||||
|
{FA1, NOTE_SIXTEENTH},
|
||||||
|
{SOL1s, NOTE_SIXTEENTH},
|
||||||
|
{SI1, NOTE_SIXTEENTH},
|
||||||
|
{FA2s, NOTE_EIGHTH},
|
||||||
|
|
||||||
|
//4 ТАКТ
|
||||||
|
{SOL2s, NOTE_SIXTEENTH},
|
||||||
|
{RE2, NOTE_SIXTEENTH},
|
||||||
|
{SI1, NOTE_SIXTEENTH},
|
||||||
|
{LA1, NOTE_SIXTEENTH},
|
||||||
|
{DO2s, NOTE_SIXTEENTH},
|
||||||
|
{FA2s, NOTE_SIXTEENTH},
|
||||||
|
{LA2, NOTE_SIXTEENTH},
|
||||||
|
|
||||||
|
{SI1, NOTE_SIXTEENTH},
|
||||||
|
{SI0, NOTE_SIXTEENTH},
|
||||||
|
{RE2, NOTE_SIXTEENTH},
|
||||||
|
{SOL2s, NOTE_SIXTEENTH},
|
||||||
|
{LA1, NOTE_SIXTEENTH},
|
||||||
|
{LA2, NOTE_SIXTEENTH},
|
||||||
|
{FA2s, NOTE_SIXTEENTH},
|
||||||
|
|
||||||
|
//5 ТАКТ
|
||||||
|
{SI1, NOTE_SIXTEENTH},
|
||||||
|
{RE2, NOTE_SIXTEENTH},
|
||||||
|
{FA2s, NOTE_SIXTEENTH},
|
||||||
|
{SI2, NOTE_SIXTEENTH}
|
||||||
|
};
|
||||||
|
static Melody_t Polyphia_OD = {song_length(Polyphia_OD_Notes), Polyphia_OD_Notes};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
static const Note_t Polyphia_PlayingGod_Notes[] = {
|
||||||
|
//1 ТАКТ
|
||||||
|
{SOL2, NOTE_QUARTER},
|
||||||
|
{MI3, NOTE_EIGHTH},
|
||||||
|
{SI3, NOTE_EIGHTH},
|
||||||
|
{MI4, NOTE_EIGHTH},
|
||||||
|
{SI3, NOTE_EIGHTH},
|
||||||
|
{SOL3, NOTE_EIGHTH},
|
||||||
|
{SI3, NOTE_EIGHTH},
|
||||||
|
{DO4, NOTE_EIGHTH_DOT},
|
||||||
|
{LA3, NOTE_SIXTEENTH},
|
||||||
|
{SI3, NOTE_EIGHTH},
|
||||||
|
{SI2, NOTE_EIGHTH_DOT},
|
||||||
|
{DO2, NOTE_SIXTEENTH},
|
||||||
|
{SI3, NOTE_EIGHTH},
|
||||||
|
{MI3, NOTE_QUARTER},
|
||||||
|
|
||||||
|
//2 ТАКТ
|
||||||
|
{RE3s, NOTE_QUARTER},
|
||||||
|
{SI2, NOTE_EIGHTH},
|
||||||
|
{LA2, NOTE_EIGHTH},
|
||||||
|
{LA3, NOTE_EIGHTH},
|
||||||
|
{SI3, NOTE_SIXTEENTH},
|
||||||
|
{LA3, NOTE_EIGHTH},
|
||||||
|
{RE3, NOTE_SIXTEENTH},
|
||||||
|
{FA4s, NOTE_SIXTEENTH},
|
||||||
|
{LA4, NOTE_SIXTEENTH},
|
||||||
|
{RE3s, NOTE_EIGHTH},
|
||||||
|
{LA3, NOTE_SIXTEENTH},
|
||||||
|
{RE3s, NOTE_EIGHTH},
|
||||||
|
|
||||||
|
{SI4, NOTE_SIXTEENTH},
|
||||||
|
{FA4s, NOTE_SIXTEENTH},
|
||||||
|
{RE3s, NOTE_THIRTYSECOND},
|
||||||
|
{NOTE_REST, NOTE_THIRTYSECOND},
|
||||||
|
{RE3s, NOTE_EIGHTH},
|
||||||
|
{MI3, NOTE_EIGHTH},
|
||||||
|
|
||||||
|
{DO3, NOTE_SIXTEENTH},
|
||||||
|
{SI2, NOTE_SIXTEENTH},
|
||||||
|
{MI3, NOTE_SIXTEENTH},
|
||||||
|
};
|
||||||
|
static Melody_t Polyphia_PlayingGod = {song_length(Polyphia_PlayingGod_Notes), Polyphia_PlayingGod_Notes};
|
||||||
54
Core/Clock/sounds.h
Normal file
54
Core/Clock/sounds.h
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "notes.h"
|
||||||
|
|
||||||
|
#define song_length(song_arr) sizeof(song_arr)/sizeof(song_arr[0])
|
||||||
|
#define SFX_BPM 480 // 480 BPM для звуковых эффектов
|
||||||
|
|
||||||
|
// ==================== ЗВУКОВЫЕ ЭФФЕКТЫ ====================
|
||||||
|
|
||||||
|
// Короткий писк
|
||||||
|
static const Note_t SFX_Beep_Notes[] = {
|
||||||
|
{DO4, NOTE_SIXTEENTH}
|
||||||
|
};
|
||||||
|
static Melody_t SFX_Beep = {song_length(SFX_Beep_Notes), SFX_Beep_Notes};
|
||||||
|
|
||||||
|
// Двойной писк
|
||||||
|
static const Note_t SFX_DoubleBeep_Notes[] = {
|
||||||
|
{DO4, NOTE_SIXTEENTH},
|
||||||
|
{NOTE_REST, NOTE_SIXTEENTH},
|
||||||
|
{DO4, NOTE_SIXTEENTH}
|
||||||
|
};
|
||||||
|
static Melody_t SFX_DoubleBeep = {song_length(SFX_DoubleBeep_Notes), SFX_DoubleBeep_Notes};
|
||||||
|
|
||||||
|
// Ошибка
|
||||||
|
static const Note_t SFX_Error_Notes[] = {
|
||||||
|
{DO4, NOTE_EIGHTH},
|
||||||
|
{NOTE_REST, NOTE_EIGHTH},
|
||||||
|
{DO4, NOTE_EIGHTH}
|
||||||
|
};
|
||||||
|
static Melody_t SFX_Error = {song_length(SFX_Error_Notes), SFX_Error_Notes};
|
||||||
|
|
||||||
|
// Успех
|
||||||
|
static const Note_t SFX_Success_Notes[] = {
|
||||||
|
{DO4, NOTE_SIXTEENTH},
|
||||||
|
{MI4, NOTE_SIXTEENTH},
|
||||||
|
{SOL4, NOTE_QUARTER}
|
||||||
|
};
|
||||||
|
static Melody_t SFX_Success = {song_length(SFX_Success_Notes), SFX_Success_Notes};
|
||||||
|
|
||||||
|
// Нажатие кнопки
|
||||||
|
static const Note_t SFX_Click_Notes[] = {
|
||||||
|
{DO4, NOTE_THIRTYSECOND}
|
||||||
|
};
|
||||||
|
static Melody_t SFX_Click = {song_length(SFX_Click_Notes), SFX_Click_Notes};
|
||||||
|
|
||||||
|
// Тревога
|
||||||
|
static const Note_t SFX_Alarm_Notes[] = {
|
||||||
|
{LA4, NOTE_EIGHTH},
|
||||||
|
{NOTE_REST, NOTE_EIGHTH},
|
||||||
|
{LA4, NOTE_EIGHTH},
|
||||||
|
{NOTE_REST, NOTE_EIGHTH},
|
||||||
|
{LA4, NOTE_EIGHTH}
|
||||||
|
};
|
||||||
|
static Melody_t SFX_Alarm = {song_length(SFX_Alarm_Notes), SFX_Alarm_Notes};
|
||||||
@@ -25,9 +25,10 @@
|
|||||||
/* Private includes ----------------------------------------------------------*/
|
/* Private includes ----------------------------------------------------------*/
|
||||||
/* USER CODE BEGIN Includes */
|
/* USER CODE BEGIN Includes */
|
||||||
#include "clock_manager.h"
|
#include "clock_manager.h"
|
||||||
#include "menu.h"
|
#include "menu_items.h"
|
||||||
#include "segment.h"
|
#include "segment.h"
|
||||||
#include "melody.h"
|
#include "melody.h"
|
||||||
|
#include "songs.h"
|
||||||
/* USER CODE END Includes */
|
/* USER CODE END Includes */
|
||||||
|
|
||||||
/* Private typedef -----------------------------------------------------------*/
|
/* Private typedef -----------------------------------------------------------*/
|
||||||
@@ -79,49 +80,9 @@ void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim) {
|
|||||||
Segment_Process();
|
Segment_Process();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
MelodyHandle melody;
|
|
||||||
const Note mySong[] = {
|
|
||||||
// //1 ÒÀÊÒ
|
|
||||||
// {SOL2, NOTE_QUARTER},
|
|
||||||
// {MI3, NOTE_EIGHTH},
|
|
||||||
// {SI3, NOTE_EIGHTH},
|
|
||||||
// {MI4, NOTE_EIGHTH},
|
|
||||||
// {SI3, NOTE_EIGHTH},
|
|
||||||
// {SOL3, NOTE_EIGHTH},
|
|
||||||
// {SI3, NOTE_EIGHTH},
|
|
||||||
// {DO4, NOTE_EIGHTH_DOT},
|
|
||||||
// {LA3, NOTE_SIXTEENTH},
|
|
||||||
// {SI3, NOTE_EIGHTH},
|
|
||||||
// {SI2, NOTE_EIGHTH_DOT},
|
|
||||||
// {DO2, NOTE_SIXTEENTH},
|
|
||||||
// {SI3, NOTE_EIGHTH},
|
|
||||||
// {MI3, NOTE_QUARTER},
|
|
||||||
|
|
||||||
//2 ÒÀÊÒ
|
#define curr_song Polyphia_OD
|
||||||
{RE3s, NOTE_QUARTER},
|
Melody_t *mySong = &curr_song;
|
||||||
{SI2, NOTE_EIGHTH},
|
|
||||||
{LA2, NOTE_EIGHTH},
|
|
||||||
{LA3, NOTE_EIGHTH},
|
|
||||||
{SI3, NOTE_SIXTEENTH},
|
|
||||||
{LA3, NOTE_EIGHTH},
|
|
||||||
{RE3, NOTE_SIXTEENTH},
|
|
||||||
{FA4s, NOTE_SIXTEENTH},
|
|
||||||
{LA4, NOTE_SIXTEENTH},
|
|
||||||
{RE3s, NOTE_EIGHTH},
|
|
||||||
{LA3, NOTE_SIXTEENTH},
|
|
||||||
{RE3s, NOTE_EIGHTH},
|
|
||||||
|
|
||||||
{SI4, NOTE_SIXTEENTH},
|
|
||||||
{FA4s, NOTE_SIXTEENTH},
|
|
||||||
{RE3s, NOTE_THIRTYSECOND},
|
|
||||||
{NOTE_REST, NOTE_THIRTYSECOND},
|
|
||||||
{RE3s, NOTE_EIGHTH},
|
|
||||||
{MI3, NOTE_EIGHTH},
|
|
||||||
|
|
||||||
{DO3, NOTE_SIXTEENTH},
|
|
||||||
{SI2, NOTE_SIXTEENTH},
|
|
||||||
{MI3, NOTE_SIXTEENTH},
|
|
||||||
};
|
|
||||||
|
|
||||||
/* USER CODE END 0 */
|
/* USER CODE END 0 */
|
||||||
|
|
||||||
@@ -165,13 +126,13 @@ int main(void)
|
|||||||
/* USER CODE BEGIN WHILE */
|
/* USER CODE BEGIN WHILE */
|
||||||
Segment_Init();
|
Segment_Init();
|
||||||
ClockManager_Init();
|
ClockManager_Init();
|
||||||
Menu_Init();
|
Menu_Init(&g_clockNode);
|
||||||
HAL_TIM_PWM_Start(&htim1, TIM_CHANNEL_1);
|
HAL_TIM_PWM_Start(&htim1, TIM_CHANNEL_1);
|
||||||
HAL_TIM_Base_Start_IT(&htim2);
|
HAL_TIM_Base_Start_IT(&htim2);
|
||||||
// Èíèöèàëèçàöèÿ ñ BPM = 120
|
MenuItems_Init();
|
||||||
Melody_Init(&melody, &htim1, TIM_CHANNEL_1, 72000000);
|
Melody_Init(&melody, &htim1, TIM_CHANNEL_1, 72000000);
|
||||||
|
|
||||||
Melody_Play(&melody, mySong, sizeof(mySong)/sizeof(mySong[0]), 80);
|
// Melody_Play(&melody, mySong, 134);
|
||||||
while (1)
|
while (1)
|
||||||
{
|
{
|
||||||
Menu_Process();
|
Menu_Process();
|
||||||
|
|||||||
@@ -170,6 +170,16 @@
|
|||||||
<WinNumber>1</WinNumber>
|
<WinNumber>1</WinNumber>
|
||||||
<ItemText>htim1.Instance,0x0A</ItemText>
|
<ItemText>htim1.Instance,0x0A</ItemText>
|
||||||
</Ww>
|
</Ww>
|
||||||
|
<Ww>
|
||||||
|
<count>4</count>
|
||||||
|
<WinNumber>1</WinNumber>
|
||||||
|
<ItemText>g_ctx</ItemText>
|
||||||
|
</Ww>
|
||||||
|
<Ww>
|
||||||
|
<count>5</count>
|
||||||
|
<WinNumber>1</WinNumber>
|
||||||
|
<ItemText>g_game1sec</ItemText>
|
||||||
|
</Ww>
|
||||||
</WatchWindow1>
|
</WatchWindow1>
|
||||||
<Tracepoint>
|
<Tracepoint>
|
||||||
<THDelay>0</THDelay>
|
<THDelay>0</THDelay>
|
||||||
@@ -598,6 +608,30 @@
|
|||||||
<tvExp>0</tvExp>
|
<tvExp>0</tvExp>
|
||||||
<tvExpOptDlg>0</tvExpOptDlg>
|
<tvExpOptDlg>0</tvExpOptDlg>
|
||||||
<bDave2>0</bDave2>
|
<bDave2>0</bDave2>
|
||||||
|
<PathWithFileName>..\Core\Clock\menu_items.c</PathWithFileName>
|
||||||
|
<FilenameWithoutPath>menu_items.c</FilenameWithoutPath>
|
||||||
|
<RteFlg>0</RteFlg>
|
||||||
|
<bShared>0</bShared>
|
||||||
|
</File>
|
||||||
|
<File>
|
||||||
|
<GroupNumber>5</GroupNumber>
|
||||||
|
<FileNumber>28</FileNumber>
|
||||||
|
<FileType>5</FileType>
|
||||||
|
<tvExp>0</tvExp>
|
||||||
|
<tvExpOptDlg>0</tvExpOptDlg>
|
||||||
|
<bDave2>0</bDave2>
|
||||||
|
<PathWithFileName>..\Core\Clock\menu_items.h</PathWithFileName>
|
||||||
|
<FilenameWithoutPath>menu_items.h</FilenameWithoutPath>
|
||||||
|
<RteFlg>0</RteFlg>
|
||||||
|
<bShared>0</bShared>
|
||||||
|
</File>
|
||||||
|
<File>
|
||||||
|
<GroupNumber>5</GroupNumber>
|
||||||
|
<FileNumber>29</FileNumber>
|
||||||
|
<FileType>1</FileType>
|
||||||
|
<tvExp>0</tvExp>
|
||||||
|
<tvExpOptDlg>0</tvExpOptDlg>
|
||||||
|
<bDave2>0</bDave2>
|
||||||
<PathWithFileName>..\Core\Clock\menu.c</PathWithFileName>
|
<PathWithFileName>..\Core\Clock\menu.c</PathWithFileName>
|
||||||
<FilenameWithoutPath>menu.c</FilenameWithoutPath>
|
<FilenameWithoutPath>menu.c</FilenameWithoutPath>
|
||||||
<RteFlg>0</RteFlg>
|
<RteFlg>0</RteFlg>
|
||||||
@@ -605,7 +639,7 @@
|
|||||||
</File>
|
</File>
|
||||||
<File>
|
<File>
|
||||||
<GroupNumber>5</GroupNumber>
|
<GroupNumber>5</GroupNumber>
|
||||||
<FileNumber>28</FileNumber>
|
<FileNumber>30</FileNumber>
|
||||||
<FileType>5</FileType>
|
<FileType>5</FileType>
|
||||||
<tvExp>0</tvExp>
|
<tvExp>0</tvExp>
|
||||||
<tvExpOptDlg>0</tvExpOptDlg>
|
<tvExpOptDlg>0</tvExpOptDlg>
|
||||||
@@ -617,7 +651,7 @@
|
|||||||
</File>
|
</File>
|
||||||
<File>
|
<File>
|
||||||
<GroupNumber>5</GroupNumber>
|
<GroupNumber>5</GroupNumber>
|
||||||
<FileNumber>29</FileNumber>
|
<FileNumber>31</FileNumber>
|
||||||
<FileType>1</FileType>
|
<FileType>1</FileType>
|
||||||
<tvExp>0</tvExp>
|
<tvExp>0</tvExp>
|
||||||
<tvExpOptDlg>0</tvExpOptDlg>
|
<tvExpOptDlg>0</tvExpOptDlg>
|
||||||
@@ -629,7 +663,7 @@
|
|||||||
</File>
|
</File>
|
||||||
<File>
|
<File>
|
||||||
<GroupNumber>5</GroupNumber>
|
<GroupNumber>5</GroupNumber>
|
||||||
<FileNumber>30</FileNumber>
|
<FileNumber>32</FileNumber>
|
||||||
<FileType>5</FileType>
|
<FileType>5</FileType>
|
||||||
<tvExp>0</tvExp>
|
<tvExp>0</tvExp>
|
||||||
<tvExpOptDlg>0</tvExpOptDlg>
|
<tvExpOptDlg>0</tvExpOptDlg>
|
||||||
@@ -641,7 +675,7 @@
|
|||||||
</File>
|
</File>
|
||||||
<File>
|
<File>
|
||||||
<GroupNumber>5</GroupNumber>
|
<GroupNumber>5</GroupNumber>
|
||||||
<FileNumber>31</FileNumber>
|
<FileNumber>33</FileNumber>
|
||||||
<FileType>1</FileType>
|
<FileType>1</FileType>
|
||||||
<tvExp>0</tvExp>
|
<tvExp>0</tvExp>
|
||||||
<tvExpOptDlg>0</tvExpOptDlg>
|
<tvExpOptDlg>0</tvExpOptDlg>
|
||||||
@@ -653,7 +687,7 @@
|
|||||||
</File>
|
</File>
|
||||||
<File>
|
<File>
|
||||||
<GroupNumber>5</GroupNumber>
|
<GroupNumber>5</GroupNumber>
|
||||||
<FileNumber>32</FileNumber>
|
<FileNumber>34</FileNumber>
|
||||||
<FileType>5</FileType>
|
<FileType>5</FileType>
|
||||||
<tvExp>0</tvExp>
|
<tvExp>0</tvExp>
|
||||||
<tvExpOptDlg>0</tvExpOptDlg>
|
<tvExpOptDlg>0</tvExpOptDlg>
|
||||||
@@ -663,6 +697,42 @@
|
|||||||
<RteFlg>0</RteFlg>
|
<RteFlg>0</RteFlg>
|
||||||
<bShared>0</bShared>
|
<bShared>0</bShared>
|
||||||
</File>
|
</File>
|
||||||
|
<File>
|
||||||
|
<GroupNumber>5</GroupNumber>
|
||||||
|
<FileNumber>35</FileNumber>
|
||||||
|
<FileType>5</FileType>
|
||||||
|
<tvExp>0</tvExp>
|
||||||
|
<tvExpOptDlg>0</tvExpOptDlg>
|
||||||
|
<bDave2>0</bDave2>
|
||||||
|
<PathWithFileName>..\Core\Clock\songs.h</PathWithFileName>
|
||||||
|
<FilenameWithoutPath>songs.h</FilenameWithoutPath>
|
||||||
|
<RteFlg>0</RteFlg>
|
||||||
|
<bShared>0</bShared>
|
||||||
|
</File>
|
||||||
|
<File>
|
||||||
|
<GroupNumber>5</GroupNumber>
|
||||||
|
<FileNumber>36</FileNumber>
|
||||||
|
<FileType>5</FileType>
|
||||||
|
<tvExp>0</tvExp>
|
||||||
|
<tvExpOptDlg>0</tvExpOptDlg>
|
||||||
|
<bDave2>0</bDave2>
|
||||||
|
<PathWithFileName>..\Core\Clock\sounds.h</PathWithFileName>
|
||||||
|
<FilenameWithoutPath>sounds.h</FilenameWithoutPath>
|
||||||
|
<RteFlg>0</RteFlg>
|
||||||
|
<bShared>0</bShared>
|
||||||
|
</File>
|
||||||
|
<File>
|
||||||
|
<GroupNumber>5</GroupNumber>
|
||||||
|
<FileNumber>37</FileNumber>
|
||||||
|
<FileType>5</FileType>
|
||||||
|
<tvExp>0</tvExp>
|
||||||
|
<tvExpOptDlg>0</tvExpOptDlg>
|
||||||
|
<bDave2>0</bDave2>
|
||||||
|
<PathWithFileName>..\Core\Clock\notes.h</PathWithFileName>
|
||||||
|
<FilenameWithoutPath>notes.h</FilenameWithoutPath>
|
||||||
|
<RteFlg>0</RteFlg>
|
||||||
|
<bShared>0</bShared>
|
||||||
|
</File>
|
||||||
</Group>
|
</Group>
|
||||||
|
|
||||||
<Group>
|
<Group>
|
||||||
|
|||||||
@@ -777,6 +777,16 @@
|
|||||||
<FileType>5</FileType>
|
<FileType>5</FileType>
|
||||||
<FilePath>..\Core\Clock\clock_manager.h</FilePath>
|
<FilePath>..\Core\Clock\clock_manager.h</FilePath>
|
||||||
</File>
|
</File>
|
||||||
|
<File>
|
||||||
|
<FileName>menu_items.c</FileName>
|
||||||
|
<FileType>1</FileType>
|
||||||
|
<FilePath>..\Core\Clock\menu_items.c</FilePath>
|
||||||
|
</File>
|
||||||
|
<File>
|
||||||
|
<FileName>menu_items.h</FileName>
|
||||||
|
<FileType>5</FileType>
|
||||||
|
<FilePath>..\Core\Clock\menu_items.h</FilePath>
|
||||||
|
</File>
|
||||||
<File>
|
<File>
|
||||||
<FileName>menu.c</FileName>
|
<FileName>menu.c</FileName>
|
||||||
<FileType>1</FileType>
|
<FileType>1</FileType>
|
||||||
@@ -807,6 +817,21 @@
|
|||||||
<FileType>5</FileType>
|
<FileType>5</FileType>
|
||||||
<FilePath>..\Core\Clock\melody.h</FilePath>
|
<FilePath>..\Core\Clock\melody.h</FilePath>
|
||||||
</File>
|
</File>
|
||||||
|
<File>
|
||||||
|
<FileName>songs.h</FileName>
|
||||||
|
<FileType>5</FileType>
|
||||||
|
<FilePath>..\Core\Clock\songs.h</FilePath>
|
||||||
|
</File>
|
||||||
|
<File>
|
||||||
|
<FileName>sounds.h</FileName>
|
||||||
|
<FileType>5</FileType>
|
||||||
|
<FilePath>..\Core\Clock\sounds.h</FilePath>
|
||||||
|
</File>
|
||||||
|
<File>
|
||||||
|
<FileName>notes.h</FileName>
|
||||||
|
<FileType>5</FileType>
|
||||||
|
<FilePath>..\Core\Clock\notes.h</FilePath>
|
||||||
|
</File>
|
||||||
</Files>
|
</Files>
|
||||||
</Group>
|
</Group>
|
||||||
<Group>
|
<Group>
|
||||||
|
|||||||
Reference in New Issue
Block a user