init работает
This commit is contained in:
61
Core/Clock/clock_manager.c
Normal file
61
Core/Clock/clock_manager.c
Normal file
@@ -0,0 +1,61 @@
|
||||
#include "clock_manager.h"
|
||||
#include "segment.h"
|
||||
#include "rtc.h"
|
||||
|
||||
static uint8_t dutyValue = 5;
|
||||
static time_t currentTime;
|
||||
RTC_TimeTypeDef rtc_time;
|
||||
|
||||
// Яркость в RTC Backup Register (используем BKP_DR1)
|
||||
static void SaveDuty(void) {
|
||||
HAL_RTCEx_BKUPWrite(&hrtc, RTC_BKP_DR1, dutyValue);
|
||||
}
|
||||
|
||||
static void LoadDuty(void) {
|
||||
uint32_t val = HAL_RTCEx_BKUPRead(&hrtc, RTC_BKP_DR1);
|
||||
dutyValue = (val <= 10 && val > 0) ? (uint8_t)val : 5;
|
||||
}
|
||||
|
||||
void ClockManager_Init(void) {
|
||||
LoadDuty();
|
||||
Segment_SetBrightness(dutyValue * 10);
|
||||
|
||||
// Если RTC не инициализирован - сброс времени
|
||||
if (HAL_RTC_GetTime(&hrtc, &rtc_time, RTC_FORMAT_BIN) != HAL_OK) {
|
||||
ClockManager_ResetTime();
|
||||
}
|
||||
}
|
||||
|
||||
time_t ClockManager_GetTime(void) {
|
||||
HAL_RTC_GetTime(&hrtc, &rtc_time, RTC_FORMAT_BIN);
|
||||
currentTime.hour = rtc_time.Hours;
|
||||
currentTime.min = rtc_time.Minutes;
|
||||
currentTime.sec = rtc_time.Seconds;
|
||||
return currentTime;
|
||||
}
|
||||
|
||||
void ClockManager_SetTime(uint8_t hour, uint8_t min, uint8_t sec) {
|
||||
RTC_TimeTypeDef rtc_time;
|
||||
rtc_time.Hours = hour;
|
||||
rtc_time.Minutes = min;
|
||||
rtc_time.Seconds = sec;
|
||||
// rtc_time.TimeFormat = RTC_HOURFORMAT_24;
|
||||
// rtc_time.DayLightSaving = RTC_DAYLIGHTSAVING_NONE;
|
||||
// rtc_time.StoreOperation = RTC_STOREOPERATION_RESET;
|
||||
HAL_RTC_SetTime(&hrtc, &rtc_time, RTC_FORMAT_BIN);
|
||||
}
|
||||
|
||||
uint8_t ClockManager_GetDuty(void) {
|
||||
return dutyValue;
|
||||
}
|
||||
|
||||
void ClockManager_SetDuty(uint8_t value) {
|
||||
if (value > 10) value = 10;
|
||||
dutyValue = value;
|
||||
Segment_SetBrightness(dutyValue * 10);
|
||||
SaveDuty();
|
||||
}
|
||||
|
||||
void ClockManager_ResetTime(void) {
|
||||
ClockManager_SetTime(0, 0, 0);
|
||||
}
|
||||
26
Core/Clock/clock_manager.h
Normal file
26
Core/Clock/clock_manager.h
Normal file
@@ -0,0 +1,26 @@
|
||||
#ifndef CLOCK_MANAGER_H
|
||||
#define CLOCK_MANAGER_H
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
#include "main.h" // где определен time_t
|
||||
|
||||
// Инициализация
|
||||
void ClockManager_Init(void);
|
||||
|
||||
// Получить текущее время из RTC
|
||||
time_t ClockManager_GetTime(void);
|
||||
|
||||
// Установить время в RTC
|
||||
void ClockManager_SetTime(uint8_t hour, uint8_t min, uint8_t sec);
|
||||
|
||||
// Получить яркость (0-10)
|
||||
uint8_t ClockManager_GetDuty(void);
|
||||
|
||||
// Установить яркость (0-10) и сохранить
|
||||
void ClockManager_SetDuty(uint8_t value);
|
||||
|
||||
// Сброс времени на 00:00:00
|
||||
void ClockManager_ResetTime(void);
|
||||
|
||||
#endif
|
||||
426
Core/Clock/menu.c
Normal file
426
Core/Clock/menu.c
Normal file
@@ -0,0 +1,426 @@
|
||||
#include "menu.h"
|
||||
#include "segment.h"
|
||||
#include "clock_manager.h"
|
||||
|
||||
// Время удержания кнопки (мс), после которого нажатие считается "длинным"
|
||||
// Используется для входа в меню из режима часов
|
||||
#define LONG_PRESS_MS 500
|
||||
|
||||
// Задержка перед началом автоповтора (мс)
|
||||
// При удержании кнопки первые 500 мс ничего не происходит
|
||||
#define REPEAT_DELAY_MS 500
|
||||
|
||||
// Интервал между автоповторами (мс)
|
||||
// После начала повтора каждые 100 мс кнопка срабатывает заново
|
||||
#define REPEAT_INTERVAL_MS 100
|
||||
|
||||
// Интервал мигания редактируемого разряда (мс)
|
||||
// Моргает каждые 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;
|
||||
|
||||
// Для 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();
|
||||
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) {
|
||||
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;
|
||||
}
|
||||
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();
|
||||
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();
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
// ==================== Публичные функции ====================
|
||||
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) {
|
||||
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];
|
||||
|
||||
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) {
|
||||
return menu.state;
|
||||
}
|
||||
30
Core/Clock/menu.h
Normal file
30
Core/Clock/menu.h
Normal file
@@ -0,0 +1,30 @@
|
||||
#ifndef MENU_H
|
||||
#define MENU_H
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
#include "main.h"
|
||||
|
||||
typedef enum {
|
||||
BUTTON_UP,
|
||||
BUTTON_DOWN,
|
||||
BUTTON_SELECT,
|
||||
BUTTON_BACK,
|
||||
BUTTON_COUNT
|
||||
} Button_Type;
|
||||
|
||||
typedef enum {
|
||||
STATE_CLOCK, // режим часов
|
||||
STATE_MAIN_MENU, // главное меню (выбор пункта)
|
||||
STATE_SET_TIME, // настройка времени
|
||||
STATE_SET_DUTY, // настройка яркости
|
||||
STATE_RESET_CONFIRM // подтверждение сброса
|
||||
} SystemState;
|
||||
|
||||
// Чтение кнопки (реализуется в main.c или hal)
|
||||
int ReadButton(int btn);
|
||||
|
||||
void Menu_Init(void);
|
||||
void Menu_Process(void);
|
||||
|
||||
#endif
|
||||
222
Core/Clock/segment.c
Normal file
222
Core/Clock/segment.c
Normal file
@@ -0,0 +1,222 @@
|
||||
#include "segment.h"
|
||||
#include "main.h"
|
||||
|
||||
// ==================== Конфигурация ====================
|
||||
#define DIGITS_COUNT 6 // Количество разрядов
|
||||
#define PROCESS_INTERVAL_US 10 // Интервал вызова Segment_Process в микросекундах
|
||||
|
||||
volatile uint32_t REFRESH_RATE = 80; // Частота обновления всех цифр в Гц (можно менять)
|
||||
volatile uint8_t GLOBAL_BRIGHTNESS = 100; // Глобальная яркость в процентах (0-100)
|
||||
|
||||
// Маска переназначения: новый_бит = старый_бит
|
||||
// Так как перепутаны бит5 (F) и бит6 (G), меняем их местами
|
||||
#define SWAP_BIT5_BIT6(x) (((x) & 0x9F) | (((x) & 0x20) << 1) | (((x) & 0x40) >> 1))
|
||||
|
||||
// Макросы для быстрой работы с пинами (вместо HAL_GPIO_WritePin)
|
||||
#define SET_SEGMENT_A(val) SEGMENT_A_GPIO_Port->BSRR = (SEGMENT_A_Pin << 16) | ((val) ? 0 : SEGMENT_A_Pin)
|
||||
#define SET_SEGMENT_B(val) SEGMENT_B_GPIO_Port->BSRR = (SEGMENT_B_Pin << 16) | ((val) ? 0 : SEGMENT_B_Pin)
|
||||
#define SET_SEGMENT_C(val) SEGMENT_C_GPIO_Port->BSRR = (SEGMENT_C_Pin << 16) | ((val) ? 0 : SEGMENT_C_Pin)
|
||||
#define SET_SEGMENT_D(val) SEGMENT_D_GPIO_Port->BSRR = (SEGMENT_D_Pin << 16) | ((val) ? 0 : SEGMENT_D_Pin)
|
||||
#define SET_SEGMENT_E(val) SEGMENT_E_GPIO_Port->BSRR = (SEGMENT_E_Pin << 16) | ((val) ? 0 : SEGMENT_E_Pin)
|
||||
#define SET_SEGMENT_F(val) SEGMENT_F_GPIO_Port->BSRR = (SEGMENT_F_Pin << 16) | ((val) ? 0 : SEGMENT_F_Pin)
|
||||
#define SET_SEGMENT_G(val) SEGMENT_G_GPIO_Port->BSRR = (SEGMENT_G_Pin << 16) | ((val) ? 0 : SEGMENT_G_Pin)
|
||||
|
||||
// ==================== Таблица символов ====================
|
||||
// Для общего анода: 0 - сегмент горит, 1 - сегмент не горит
|
||||
// Биты: A B C D E F G
|
||||
typedef struct {
|
||||
char c;
|
||||
uint8_t mask;
|
||||
} CharMap;
|
||||
|
||||
static const CharMap charTable[] = {
|
||||
// ==================== Цифры ====================
|
||||
{'0', 0xC0},
|
||||
{'1', 0xF9},
|
||||
{'2', 0xA4},
|
||||
{'3', 0xB0},
|
||||
{'4', 0x99},
|
||||
{'5', 0x92},
|
||||
{'6', 0x82},
|
||||
{'7', 0xF8},
|
||||
{'8', 0x80},
|
||||
{'9', 0x90},
|
||||
|
||||
// ==================== Буквы ====================
|
||||
{'A', 0x88}, {'a', 0x88},
|
||||
{'B', 0x83}, {'b', 0x83},
|
||||
{'C', 0xC6}, {'c', 0xC6},
|
||||
{'D', 0xA1}, {'d', 0xA1},
|
||||
{'E', 0x86}, {'e', 0x86},
|
||||
{'F', 0x8E}, {'f', 0x8E},
|
||||
{'G', 0xC2}, {'g', 0xC2},
|
||||
{'H', 0x89}, {'h', 0x89},
|
||||
{'I', 0xF9}, {'i', 0xF9},
|
||||
{'J', 0xF1}, {'j', 0xF1},
|
||||
{'L', 0xC7}, {'l', 0xC7},
|
||||
{'N', 0xAB}, {'n', 0xAB},
|
||||
{'O', 0xC0}, {'o', 0xC0},
|
||||
{'P', 0x8C}, {'p', 0x8C},
|
||||
{'Q', 0x98}, {'q', 0x98},
|
||||
{'R', 0xAF}, {'r', 0xAF},
|
||||
{'S', 0x92}, {'s', 0x92},
|
||||
{'T', 0x87}, {'t', 0x87},
|
||||
{'U', 0xC1}, {'u', 0xC1},
|
||||
{'Y', 0x91}, {'y', 0x91},
|
||||
|
||||
// ==================== Символы ====================
|
||||
{'-', 0xBF},
|
||||
{'_', 0xF7},
|
||||
{' ', 0xFF}
|
||||
};
|
||||
|
||||
#define CHAR_TABLE_SIZE (sizeof(charTable)/sizeof(CharMap))
|
||||
|
||||
// ==================== Статические переменные ====================
|
||||
static uint8_t displayBuffer[DIGITS_COUNT]; // Буфер СРАЗУ МАСОК сегментов
|
||||
static uint8_t currentPos;
|
||||
|
||||
static uint32_t pwmCounter;
|
||||
static uint32_t currentDigitTime;
|
||||
static uint32_t currentPwmThreshold;
|
||||
|
||||
// ==================== Низкий уровень ====================
|
||||
static void SetSegments(uint8_t mask) {
|
||||
SET_SEGMENT_A((mask >> 0) & 1);
|
||||
SET_SEGMENT_B((mask >> 1) & 1);
|
||||
SET_SEGMENT_C((mask >> 2) & 1);
|
||||
SET_SEGMENT_D((mask >> 3) & 1);
|
||||
SET_SEGMENT_E((mask >> 4) & 1);
|
||||
SET_SEGMENT_F((mask >> 5) & 1);
|
||||
SET_SEGMENT_G((mask >> 6) & 1);
|
||||
}
|
||||
|
||||
// Получить маску по символу
|
||||
static uint8_t GetCharMask(char c) {
|
||||
for (int i = 0; i < CHAR_TABLE_SIZE; i++) {
|
||||
if (charTable[i].c == c)
|
||||
return charTable[i].mask;
|
||||
}
|
||||
return 0xFF; // если символ неизвестен — пусто
|
||||
}
|
||||
|
||||
// ==================== Управление разрядами ====================
|
||||
void DisableAllDigits(void) {
|
||||
SET_SEGMENT_A(1); SET_SEGMENT_B(1); SET_SEGMENT_C(1);
|
||||
SET_SEGMENT_D(1); SET_SEGMENT_E(1); SET_SEGMENT_F(1);
|
||||
SET_SEGMENT_G(1);
|
||||
|
||||
DIGIT_HOUR_H_GPIO_Port->BSRR = DIGIT_HOUR_H_Pin << 16;
|
||||
DIGIT_HOUR_L_GPIO_Port->BSRR = DIGIT_HOUR_L_Pin << 16;
|
||||
DIGIT_MIN_H_GPIO_Port->BSRR = DIGIT_MIN_H_Pin << 16;
|
||||
DIGIT_MIN_L_GPIO_Port->BSRR = DIGIT_MIN_L_Pin << 16;
|
||||
DIGIT_SEC_H_GPIO_Port->BSRR = DIGIT_SEC_H_Pin << 16;
|
||||
DIGIT_SEC_L_GPIO_Port->BSRR = DIGIT_SEC_L_Pin << 16;
|
||||
}
|
||||
|
||||
void EnableDigit(uint8_t pos) {
|
||||
switch(pos) {
|
||||
case 0: DIGIT_HOUR_H_GPIO_Port->BSRR = DIGIT_HOUR_H_Pin; break;
|
||||
case 1: DIGIT_HOUR_L_GPIO_Port->BSRR = DIGIT_HOUR_L_Pin; break;
|
||||
case 2: DIGIT_MIN_H_GPIO_Port->BSRR = DIGIT_MIN_H_Pin; break;
|
||||
case 3: DIGIT_MIN_L_GPIO_Port->BSRR = DIGIT_MIN_L_Pin; break;
|
||||
case 4: DIGIT_SEC_H_GPIO_Port->BSRR = DIGIT_SEC_H_Pin; break;
|
||||
case 5: DIGIT_SEC_L_GPIO_Port->BSRR = DIGIT_SEC_L_Pin; break;
|
||||
}
|
||||
}
|
||||
|
||||
// ==================== Логика ====================
|
||||
static void NextDigit(void) {
|
||||
currentPos++;
|
||||
if (currentPos >= DIGITS_COUNT) currentPos = 0;
|
||||
|
||||
if (REFRESH_RATE == 0) REFRESH_RATE = 1;
|
||||
|
||||
uint32_t totalCalls = (1000000UL / REFRESH_RATE) / PROCESS_INTERVAL_US;
|
||||
if (totalCalls < DIGITS_COUNT) totalCalls = DIGITS_COUNT;
|
||||
|
||||
currentDigitTime = totalCalls / DIGITS_COUNT;
|
||||
if (currentDigitTime < 1) currentDigitTime = 1;
|
||||
|
||||
currentPwmThreshold = (currentDigitTime * GLOBAL_BRIGHTNESS) / 100;
|
||||
|
||||
pwmCounter = 0;
|
||||
}
|
||||
|
||||
static void UpdateOneDigit(void) {
|
||||
if (pwmCounter == 0) {
|
||||
DisableAllDigits();
|
||||
|
||||
__NOP(); __NOP(); // анти-гостинг
|
||||
|
||||
uint8_t mask = displayBuffer[currentPos];
|
||||
|
||||
// SWAP только для позиции 5
|
||||
if (currentPos == 5)
|
||||
mask = SWAP_BIT5_BIT6(mask);
|
||||
|
||||
SetSegments(mask);
|
||||
EnableDigit(currentPos);
|
||||
}
|
||||
|
||||
if (pwmCounter >= currentPwmThreshold) {
|
||||
DisableAllDigits();
|
||||
SetSegments(0xFF);
|
||||
}
|
||||
|
||||
pwmCounter++;
|
||||
|
||||
if (pwmCounter >= currentDigitTime) {
|
||||
NextDigit();
|
||||
}
|
||||
}
|
||||
|
||||
// ==================== Публичные функции ====================
|
||||
|
||||
// Инициализация модуля
|
||||
void Segment_Init(void) {
|
||||
currentPos = 0;
|
||||
pwmCounter = 0;
|
||||
currentDigitTime = 1;
|
||||
currentPwmThreshold = 1;
|
||||
|
||||
for (int i = 0; i < DIGITS_COUNT; i++)
|
||||
displayBuffer[i] = 0xFF; // пусто
|
||||
|
||||
NextDigit();
|
||||
}
|
||||
|
||||
// Установить символ в разряд
|
||||
void Segment_SetChar(uint8_t pos, char c) {
|
||||
if (pos >= DIGITS_COUNT) return;
|
||||
displayBuffer[pos] = GetCharMask(c);
|
||||
}
|
||||
|
||||
// Установить напрямую маску сегментов
|
||||
void Segment_SetRaw(uint8_t pos, uint8_t mask) {
|
||||
if (pos >= DIGITS_COUNT) return;
|
||||
displayBuffer[pos] = mask;
|
||||
}
|
||||
|
||||
// Установить строку (например "HELLO ")
|
||||
void Segment_SetString(const char *str) {
|
||||
for (int i = 0; i < DIGITS_COUNT; i++) {
|
||||
if (str[i] == 0)
|
||||
displayBuffer[i] = 0xFF;
|
||||
else
|
||||
displayBuffer[i] = GetCharMask(str[i]);
|
||||
}
|
||||
}
|
||||
|
||||
// Установка глобальной яркости (0-100%)
|
||||
void Segment_SetBrightness(uint8_t percent) {
|
||||
if (percent > 100) percent = 100;
|
||||
if (percent < 1) percent = 1;
|
||||
GLOBAL_BRIGHTNESS = percent;
|
||||
}
|
||||
|
||||
// Основная функция обновления дисплея
|
||||
// Вызывается каждые 10 микросекунд из таймера
|
||||
void Segment_Process(void) {
|
||||
UpdateOneDigit();
|
||||
}
|
||||
34
Core/Clock/segment.h
Normal file
34
Core/Clock/segment.h
Normal file
@@ -0,0 +1,34 @@
|
||||
#ifndef SEGMENT_H
|
||||
#define SEGMENT_H
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#define SEG_DIGITS_COUNT 6 // Количество разрядов на дисплее
|
||||
|
||||
// ==================== Инициализация ====================
|
||||
// Инициализация модуля (обнуляет буферы и переменные)
|
||||
void Segment_Init(void);
|
||||
|
||||
// ==================== Отображение ====================
|
||||
// Установить символ в конкретный разряд (0..SEG_DIGITS_COUNT-1)
|
||||
void Segment_SetChar(uint8_t pos, char c);
|
||||
|
||||
// Установить сразу строку (например "HELLO ")
|
||||
void Segment_SetString(const char *str);
|
||||
|
||||
// Установить напрямую маску сегментов для разряда
|
||||
void Segment_SetRaw(uint8_t pos, uint8_t mask);
|
||||
|
||||
// Установка времени для отображения
|
||||
void Segment_SetTime(uint8_t hours, uint8_t minutes, uint8_t seconds);
|
||||
|
||||
// ==================== Яркость ====================
|
||||
// Установка глобальной яркости (0-100%)
|
||||
void Segment_SetBrightness(uint8_t percent);
|
||||
|
||||
// ==================== Обновление дисплея ====================
|
||||
// Основная функция обновления дисплея
|
||||
// Вызывается каждые PROCESS_INTERVAL_US микросекунд или из таймера
|
||||
void Segment_Process(void);
|
||||
|
||||
#endif
|
||||
Reference in New Issue
Block a user