TektroHack/TecktroHack/ui_interface.ino
Razvalyaev d9934b51fe разделены DPO и MDO опции
подправлены описания
2025-07-25 11:54:52 +03:00

519 lines
15 KiB
C++
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
#include <U8g2lib.h>
#include <Fonts/FreeSans9pt7b.h>
#define FONT u8g2_font_8x13_tr
#define LITTLE_FONT u8g2_font_5x8_tr
#define LINE_SPACING 16
// OLED дисплей 128x64, I2C адрес по умолчанию — 0x3C
#define SCREEN_WIDTH 128
#define SCREEN_HEIGHT 64
#define OLED_RESET -1
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET);
// Инициализация дисплея SSD1306 128x64 по I2C
// Выбери нужный конструктор в зависимости от контроллера
// Этот - для SSD1306, I2C, с аппаратным reset (если нет reset - поставить U8G2_R0 и -1)
U8G2_SSD1306_128X64_NONAME_F_HW_I2C u8g2(U8G2_R0, /* reset=*/ U8X8_PIN_NONE);
// Кнопки
#define BTN_UP_PIN 3
#define BTN_DOWN_PIN 2
#define BTN_OK_PIN 1 // #
#define BTN_BACK_PIN 0 // *
extern uint16_t startAddr;
extern uint16_t versionAddr;
extern void writeStringToEEPROM(const char* str, uint16_t addr);
extern void readModuleFromEEPROM();
extern void readVersionFromEEPROM();
extern char versionStr[32];
extern char moduleStr[32];
enum MenuState {
MENU_MAIN,
MENU_SELECT_MODEL,
MENU_SELECT_OPTION,
MENU_SELECT_VERSION,
MENU_SHOW_EEPROM
};
const char* modelList[] = { "DPO4", "DPO3", "DPO2", "DPO1", "MDO4", "MDO3", "MDO2", "MDO1"};
const uint8_t modelCount = sizeof(modelList) / sizeof(modelList[0]);
// Фильтрация опций по модели
uint8_t filteredOptionIndexes[50];
uint8_t filteredOptionCount = 0;
const char* optionList[] = {
"BND", // Bundle (all-in-one)
"AERO", // Aerospace serial buses
"AFG", // Arbitrary Function Generator
"AUDIO", // Audio serial buses
"AUTO", // Automotive serial buses
"AUTOMAX",// Automotive serial buses
"COMP", // Computer serial buses
"EMBD", // Embedded systems
"ENET", // Ethernet
"FLEX", // FlexRay
"LMT", // Limit testing and mask testing
"MSO", // Mixed Signal Option
"PWR", // Power analysis
"SA", // Spectrum Analyzer
"SA3", // Spectrum Analyzer
"SA3T6", // Spectrum Analyzer
"SA6", // Spectrum Analyzer
"SEC", // Security extension
"TRIG", // RF Power Level Trigger
"USB", // USB analysis
"VID" // Video signal triggering
};
const uint8_t optionCount = sizeof(optionList) / sizeof(optionList[0]);
const char* optionDescriptions[] = {
"Include all available options in one",
"Aerospace buses: MIL-STD-1553",
"Waveform generator for custom signals",
"Audio buses: I2S, LJ, RJ, TDM",
"Cars buses: CAN, CAN FD, LIN",
"Cars buses: CAN, CAN FD, LIN, FlexRay",
"Computer buses: RS-232/422/485, UART",
"Embedded protocols: I2C, SPI",
"Ethernet: 10BASE-T, 100BASE-TX",
"Cars bus FlexRay",
"Limit and mask testing: Go/No-Go",
"Mixed Signal Oscill: analog + digital",
"Power: efficiency, ripple, dI/dt, dV/dt",
"Spectrum analysis: RF, high-frequency",
"Spectr analysis (3 MHz): RF, high-frequency",
"SA3 extention to 6 MHz for MDO4000C",
"Spectr analysis (6 MHz): RF, high-frequency",
"Security extension: access control",
"RF power level triggering",
"USB decoding: USB LS, FS, HS",
"Video triggering: HDTV"
};
bool isOptionForDPO[] = {
true, // BND → DPO4BND
true, // AERO → DPO4AERO
false, // AFG → (не используется в DPO)
true, // AUDIO → DPO4AUDIO
true, // AUTO → DPO4AUTO
true, // AUTOMAX → нет в DPO
true, // COMP → DPO4COMP
true, // EMBD → DPO4EMBD
true, // ENET → DPO4ENET
true, // FLEX → нет упоминания, но оставим
true, // LMT → DPO4LMT
false, // MSO → только для MDO (MDO4MSO)
true, // PWR → DPO4PWR
false, // SA → только MDO
false, // SA3 → только MDO
false, // SA3T6 → только MDO
false, // SA6 → только MDO
false, // SEC → только MDO (MDO4SEC)
false, // TRIG → только MDO (MDO4TRIG)
true, // USB → DPO4USB
true // VID → DPO4VID
};
bool isOptionForMDO[] = {
false, // BND → нет прямого MDO4BND
false, // AERO
true, // AFG → MDO4AFG
false, // AUDIO
false, // AUTO
false, // AUTOMAX → пусть будет для MDO
false, // COMP
false, // EMBD
false, // ENET
false, // FLEX
false, // LMT
true, // MSO → MDO4MSO
false, // PWR
true, // SA → MDO4SA3/MDO4SA6
true, // SA3 → MDO4SA3
true, // SA3 → MDO4SA3T6
true, // SA6 → MDO4SA6
true, // SEC → MDO4SEC
true, // TRIG → MDO4TRIG
false, // USB
false // VID
};
void filterOptionsForModel(const char* modelName) {
filteredOptionCount = 0;
bool isMDO = strstr(modelName, "MDO") != nullptr;
bool isDPO = strstr(modelName, "DPO") != nullptr;
for (uint8_t i = 0; i < optionCount; ++i) {
if ((isMDO && isOptionForMDO[i]) || (isDPO && isOptionForDPO[i])) {
filteredOptionIndexes[filteredOptionCount++] = i;
}
}
// Подстраховка
if (filteredOptionCount == 0) {
for (uint8_t i = 0; i < sizeof(optionList); ++i)
filteredOptionIndexes[filteredOptionCount++] = i;
}
}
uint8_t selectedModel = 0;
uint8_t selectedOption = 0;
MenuState currentMenu = MENU_MAIN;
uint8_t cursorPos = 0;
char editableVersion[16] = "v1.00";
uint8_t editIndex = 0;
bool editingVersion = false;
void setupUI() {
pinMode(BTN_UP_PIN, INPUT_PULLUP);
pinMode(BTN_DOWN_PIN, INPUT_PULLUP);
pinMode(BTN_OK_PIN, INPUT_PULLUP);
pinMode(BTN_BACK_PIN, INPUT_PULLUP);
if (!display.begin(SSD1306_SWITCHCAPVCC, 0x3C)) {
Serial.println(F("SSD1306 not found"));
while (true);
}
//display.clearDisplay();
//display.setFont(&FONT);
//display.setTextSize(1);
//display.setTextColor(SSD1306_WHITE);
//display.display();
u8g2.begin();
// Выбираем шрифт - очень маленький, около 6pt (пример: u8g2_font_6x10_tr)
u8g2.setFont(FONT);
showMainMenu();
}
void loopUI() {
static uint32_t lastPress = 0;
if (millis() - lastPress < 200) return;
if (currentMenu == MENU_SELECT_VERSION && editingVersion) {
if (!digitalRead(BTN_UP_PIN)) {
if (editableVersion[editIndex] >= '0' && editableVersion[editIndex] <= '9') {
if (editableVersion[editIndex] == '9') editableVersion[editIndex] = '0';
else editableVersion[editIndex]++;
}
updateMenuDisplay();
lastPress = millis();
return;
}
if (!digitalRead(BTN_DOWN_PIN)) {
if (editableVersion[editIndex] >= '0' && editableVersion[editIndex] <= '9') {
if (editableVersion[editIndex] == '0') editableVersion[editIndex] = '9';
else editableVersion[editIndex]--;
}
updateMenuDisplay();
lastPress = millis();
return;
}
if (!digitalRead(BTN_OK_PIN)) {
// Перейти к следующему редактируемому символу, пропуская точки и буквы
do {
editIndex++;
} while (editIndex < strlen(editableVersion) && (editableVersion[editIndex] < '0' || editableVersion[editIndex] > '9'));
if (editIndex >= strlen(editableVersion)) {
// Закончили редактировать
writeStringToEEPROM(editableVersion, versionAddr);
strncpy(versionStr, editableVersion, sizeof(versionStr));
editingVersion = false;
currentMenu = MENU_MAIN;
cursorPos = 0;
}
updateMenuDisplay();
lastPress = millis();
return;
}
if (!digitalRead(BTN_BACK_PIN)) {
// Назад по цифрам
do {
if (editIndex == 0) {
editingVersion = false;
currentMenu = MENU_MAIN;
cursorPos = 0;
break;
} else {
editIndex--;
}
} while (editableVersion[editIndex] < '0' || editableVersion[editIndex] > '9');
updateMenuDisplay();
lastPress = millis();
return;
}
return;
}
if (!digitalRead(BTN_UP_PIN)) {
if (cursorPos > 0) cursorPos--;
updateMenuDisplay();
lastPress = millis();
} else if (!digitalRead(BTN_DOWN_PIN)) {
cursorPos++;
updateMenuDisplay();
lastPress = millis();
} else if (!digitalRead(BTN_OK_PIN)) {
handleOk();
lastPress = millis();
} else if (!digitalRead(BTN_BACK_PIN)) {
handleBack();
lastPress = millis();
}
}
void showMainMenu() {
currentMenu = MENU_MAIN;
cursorPos = 0;
updateMenuDisplay();
}
void drawStringWrapped(int x, int y, int maxWidth, const char* text, int lineHeight) {
int lineStart = 0;
int textLen = strlen(text);
while (lineStart < textLen) {
int lineEnd = lineStart;
int lastSpace = -1;
int width = 0;
while (lineEnd < textLen) {
char c = text[lineEnd];
// Вычисляем ширину подстроки с lineStart до lineEnd + 1
char buffer[64]; // должен быть достаточного размера
int length = lineEnd - lineStart + 1;
if (length >= sizeof(buffer)) length = sizeof(buffer) - 1;
memcpy(buffer, text + lineStart, length);
buffer[length] = '\0';
int w = u8g2.getStrWidth(buffer);
if (w > maxWidth) {
break;
}
if (c == ' ' || c == '-' || c == '\t') {
lastSpace = lineEnd;
}
lineEnd++;
}
// Если мы вышли за предел maxWidth
if (lineEnd == lineStart) {
// Если один символ не помещается — выводим его все равно (чтобы не зацикливаться)
lineEnd = lineStart + 1;
} else if (lineEnd < textLen && lastSpace != -1) {
// Откатываемся до последнего пробела, чтобы не разрывать слово
lineEnd = lastSpace;
}
// Подстрока для вывода длиной (lineEnd - lineStart)
char buf[128];
int len = lineEnd - lineStart;
if (len >= sizeof(buf)) len = sizeof(buf) - 1;
strncpy(buf, text + lineStart, len);
buf[len] = 0;
u8g2.drawStr(x, y, buf);
y += lineHeight;
lineStart = (lineEnd == lastSpace) ? lineEnd + 1 : lineEnd;
}
}
void updateMenuDisplay() {
u8g2.clearBuffer();
//display.clearDisplay();
//display.setCursor(0, LINE_SPACING);
if (currentMenu == MENU_MAIN) {
// Выводим три строки меню с курсором >
if(cursorPos > 2)
{
cursorPos = 2;
}
u8g2.drawStr(0, LINE_SPACING * 1, cursorPos == 0 ? ">Set Options" : " Set Options");
u8g2.drawStr(0, LINE_SPACING * 2, cursorPos == 1 ? ">Set Version" : " Set Version");
u8g2.drawStr(0, LINE_SPACING * 3, cursorPos == 2 ? ">Read Module" : " Read Module");
/*display.print(cursorPos == 0 ? ">Set Options" : " Set Options");
display.setCursor(0, LINE_SPACING *2);
display.print(cursorPos == 1 ? ">Set Version" : " Set Version");
display.setCursor(0, LINE_SPACING *3);
display.print(cursorPos == 2 ? ">Read" : " Read");*/
} else if (currentMenu == MENU_SELECT_MODEL) {
u8g2.drawStr(0, LINE_SPACING * 1, "Model:");
// Формируем строку ">ModelName"
char line[20];
snprintf(line, sizeof(line), ">%s", modelList[cursorPos % modelCount]);
u8g2.drawStr(0, LINE_SPACING * 2, line);
/*display.print("Model:");
display.setCursor(0, LINE_SPACING *2);
display.print(">");
display.print(modelList[cursorPos % modelCount]);*/
} else if (currentMenu == MENU_SELECT_OPTION) {
u8g2.setFont(FONT); // основной шрифт
u8g2.drawStr(0, LINE_SPACING * 1, "Option:");
if (cursorPos >= filteredOptionCount) cursorPos = filteredOptionCount - 1;
char line[20];
uint8_t optIndex = filteredOptionIndexes[cursorPos];
snprintf(line, sizeof(line), ">%s", optionList[optIndex]);
u8g2.drawStr(0, LINE_SPACING * 2, line);
// Мелкий шрифт — описание
u8g2.setFont(LITTLE_FONT);
drawStringWrapped(0, LINE_SPACING * 3 + 4, SCREEN_WIDTH, optionDescriptions[optIndex], 10);
u8g2.setFont(FONT);
/*display.print("Option:");
display.setCursor(0, LINE_SPACING *2);
display.print(">");
display.print(optionList[cursorPos % optionCount]);*/
} else if (currentMenu == MENU_SELECT_VERSION) {
u8g2.drawStr(0, LINE_SPACING * 1, "UART set ver:");
// Для версии выводим символы с квадратными скобками вокруг редактируемого
int x = 0;
for (uint8_t i = 0; i < strlen(editableVersion); i++) {
char buf[4];
if (i == editIndex && editingVersion) {
snprintf(buf, sizeof(buf), "[%c]", editableVersion[i]);
} else {
snprintf(buf, sizeof(buf), " %c ", editableVersion[i]);
}
u8g2.drawStr(x, LINE_SPACING * 2, buf);
x += u8g2.getStrWidth(buf); // смещение курсора вправо на ширину предыдущей части
}
/*display.print("UART set ver:");
display.setCursor(0, LINE_SPACING *2);
for (uint8_t i = 0; i < strlen(editableVersion); i++) {
if (i == editIndex && editingVersion) {
display.print("[");
display.print(editableVersion[i]);
display.print("]");
} else {
display.print(" ");
display.print(editableVersion[i]);
display.print(" ");
}
}*/
} else if (currentMenu == MENU_SHOW_EEPROM) {
char buf[40];
snprintf(buf, sizeof(buf), "Model Option:");
u8g2.drawStr(0, LINE_SPACING * 1, buf);
snprintf(buf, sizeof(buf), " %s", moduleStr);
u8g2.drawStr(0, LINE_SPACING * 2, buf);
snprintf(buf, sizeof(buf), "Version:");
u8g2.drawStr(0, LINE_SPACING * 3, buf);
snprintf(buf, sizeof(buf), " %s", versionStr);
u8g2.drawStr(0, LINE_SPACING * 4, buf);
/*display.print("Mod:");
display.println(moduleStr);
display.setCursor(0, LINE_SPACING *3);
display.print("Ver:");
display.println(versionStr);*/
}
//display.display();
u8g2.sendBuffer();
}
void handleOk() {
if (currentMenu == MENU_MAIN) {
if (cursorPos == 0) {
currentMenu = MENU_SELECT_MODEL;
cursorPos = 0;
} else if (cursorPos == 1) {
currentMenu = MENU_SELECT_VERSION;
} else if (cursorPos == 2) {
readVersionFromEEPROM();
readModuleFromEEPROM();
currentMenu = MENU_SHOW_EEPROM;
}
} else if (currentMenu == MENU_SELECT_MODEL) {
selectedModel = cursorPos % modelCount;
filterOptionsForModel(modelList[selectedModel]); // ← ВАЖНО!
cursorPos = 0;
currentMenu = MENU_SELECT_OPTION;
} else if (currentMenu == MENU_SELECT_OPTION) {
selectedOption = filteredOptionIndexes[cursorPos];;
char fullModule[32];
snprintf(fullModule, sizeof(fullModule), "%s%s", modelList[selectedModel], optionList[selectedOption]);
writeStringToEEPROM(fullModule, startAddr);
readAndDumpEEPROM();
strncpy(moduleStr, fullModule, sizeof(moduleStr) - 1);
moduleStr[sizeof(moduleStr) - 1] = 0;
Serial.print("[UI] Options written: ");
Serial.println(fullModule);
currentMenu = MENU_MAIN;
cursorPos = 0;
} else if (currentMenu == MENU_SELECT_VERSION) {
editingVersion = true;
editIndex = 1;
}
updateMenuDisplay();
}
void handleBack() {
if (currentMenu == MENU_SELECT_OPTION) {
currentMenu = MENU_SELECT_MODEL;
} else {
currentMenu = MENU_MAIN;
}
cursorPos = 0;
updateMenuDisplay();
}