Беспроводной телеграф на nrf24

По-другому. Например, битовые маски хранить.

Для меня- это что-то из фантастики))

А для меня это

что-то из психиатрии.

Вот массив, символ(1 байт) и его Морзе код, тоже 1 байт

const TMorze MorzeArray[] PROGMEM = {
	{ 'A',0b101 },
	{ 'B',0b11000 },
	{ 'C',0b11010 },
	{ 'D',0b1100 },
	{ 'E',0b10 },
и т.д.

Самая левая единица в Морзе-коде - флаг того, что дальше будут точки-тире, 1-тире, 0-точка.

Я пересмотрел кучу проектов. Это подходит для моих задач. В написании кода я -0. Пишу об этом сотый раз. Может Ваше сообщение меня и не касается и адресовано автору кода, но про быдлокод я слышу уже не первый раз. Но, код работает.

Тогда не трогай, пусь работает, тока сюда не выкладывай, код вызывает идиосинкразию и кроффь из глаз. КГ/АМ, как говорится.

надеюсь не ко мне.

Пока буду рисовать корпус устройства, а код, может кто-то и добьет по моим хотелкам. Если нет, то что получится, то и получится)

Дык, это ж “специалист в предметной области” посоветовал. И долго потом правильность такого кода отстаивал, объясняя, кто тут “экстремист”, а кто - “д’Артаньян” :slight_smile:

Аналогично, только ограничитель справа. И для алфавита и цифр позиционная таблица.

это не настоящая азбука морзе))) я вот че узнал…

#include <Wire.h> 
#include <LiquidCrystal_I2C.h>
#include <SPI.h>
#include "nRF24L01.h"
#include "RF24.h"

// ============== НАСТРОЙКИ ==============
const uint64_t pipe = 0xE8E8F0F0E1LL; 
const int LCD_ADDR = 0x27;
const int LCD_COLS = 16;
const int LCD_ROWS = 2;

// Пины
const int PIN_BUZZER = 2;
const int PIN_BUTTON_SYMBOL = 3;
const int PIN_BUTTON_MODE = 4;
const int PIN_LED_TRANSMIT = 5;
const int PIN_LED_RECEIVE = 6;
const int PIN_GND = 8;
const int PIN_CE = 9;
const int PIN_CSN = 10;

// Временные константы (в миллисекундах)
const unsigned long DASH_MIN_TIME = 150;
const unsigned long SYMBOL_TIMEOUT = 500;
const unsigned long SCREEN_TIMEOUT = 60000;
const unsigned long DEBOUNCE_DELAY = 20;
const unsigned long CYCLE_CHECK_INTERVAL = 10;

// Специальные команды
enum SpecialCommands {
  CMD_UNKNOWN = 40,
  CMD_SPACE = 41,
  CMD_COUNT = 42
};
// ========================================

// ============== СТРУКТУРА ДАННЫХ ==============
struct MorseSymbol {
  const char* morse;
  const char* symbol;
  const char* description;
  bool isSpecial;
};

const MorseSymbol morseTable[] = {
  // Буквы A-Z (0-25)
  {".-", "A", "A", false},
  {"-...", "B", "B", false},
  {"-.-.", "C", "C", false},
  {"-..", "D", "D", false},
  {".", "E", "E", false},
  {"..-.", "F", "F", false},
  {"--.", "G", "G", false},
  {"....", "H", "H", false},
  {"..", "I", "I", false},
  {".---", "J", "J", false},
  {"-.-", "K", "K", false},
  {".-..", "L", "L", false},
  {"--", "M", "M", false},
  {"-.", "N", "N", false},
  {"---", "O", "O", false},
  {".--.", "P", "P", false},
  {"--.-", "Q", "Q", false},
  {".-.", "R", "R", false},
  {"...", "S", "S", false},
  {"-", "T", "T", false},
  {"..-", "U", "U", false},
  {"...-", "V", "V", false},
  {".--", "W", "W", false},
  {"-..-", "X", "X", false},
  {"-.--", "Y", "Y", false},
  {"--..", "Z", "Z", false},
  
  // Цифры 0-9 (26-35)
  {".----", "1", "1", false},
  {"..---", "2", "2", false},
  {"...--", "3", "3", false},
  {"....-", "4", "4", false},
  {".....", "5", "5", false},
  {"-....", "6", "6", false},
  {"--...", "7", "7", false},
  {"---..", "8", "8", false},
  {"----.", "9", "9", false},
  {"-----", "0", "0", false},
  
  // Знаки препинания (36-39)
  {".-.-.-", ".", "точка", false},
  {"--..--", ",", "запятая", false},
  {"..--..", "?", "вопрос", false},
  {"-.-.--", "!", "восклицание", false},
  
  // Специальные символы (40-41)
  {"......", "�", "неизвестно", true},
  {".......", " ", "пробел", true}
};

const int MORSE_COUNT = sizeof(morseTable) / sizeof(morseTable[0]);
// ===============================================

// ============== ГЛОБАЛЬНЫЕ ПЕРЕМЕННЫЕ ==============
LiquidCrystal_I2C lcd(LCD_ADDR, LCD_COLS, LCD_ROWS);
RF24 radio(PIN_CE, PIN_CSN);

// Состояние
bool transmitter = true;
bool screen = true;
bool buttonWasUp = false;
bool symbol_end = false;
bool wrong_symbol = false;

// Таймеры
unsigned long buttonPressTime = 0;
unsigned long lastActivityTime = 0;
unsigned long cycleCounter = 0;
unsigned long lastCycleCheck = 0;

// Данные
String currentSymbol = "";
byte cursorX = 0;
byte cursorY = 0;

// ===============================================

// ============== ФУНКЦИИ ПОИСКА ==============
int findMorseCode(String morse) {
  for (int i = 0; i < MORSE_COUNT; i++) {
    if (morse == morseTable[i].morse) {
      return i;
    }
  }
  return -1;
}

const char* getSymbolByIndex(int index) {
  if (index >= 0 && index < MORSE_COUNT) {
    return morseTable[index].symbol;
  }
  return "?";
}
// =============================================

// ============== УПРАВЛЕНИЕ ДИСПЛЕЕМ ==============
void displayOn() {
  lcd.display();
  lcd.backlight();
  screen = true;
  lastActivityTime = millis();
}

void displayOff() {
  lcd.noDisplay();
  lcd.noBacklight();
  screen = false;
}

void updateDisplay() {
  if (!screen) return;
  
  // Проверка на выход за границы
  if (cursorX >= LCD_COLS) {
    cursorX = 0;
    cursorY++;
    if (cursorY >= LCD_ROWS) {
      cursorY = 0;
      lcd.clear();
    }
  }
}
// =================================================

// ============== СВЕТОДИОДНАЯ ИНДИКАЦИЯ ==============
void updateModeIndicators() {
  if (transmitter) {
    digitalWrite(PIN_LED_TRANSMIT, HIGH);
    digitalWrite(PIN_LED_RECEIVE, LOW);
  } else {
    digitalWrite(PIN_LED_TRANSMIT, LOW);
    digitalWrite(PIN_LED_RECEIVE, HIGH);
  }
}

void blinkLED(int pin, int count = 1, int duration = 100) {
  for (int i = 0; i < count; i++) {
    digitalWrite(pin, HIGH);
    delay(duration);
    digitalWrite(pin, LOW);
    if (i < count - 1) delay(duration);
  }
}
// ====================================================

// ============== РЕЖИМ ПЕРЕДАТЧИКА ==============
void processTransmitter() {
  bool buttonState = digitalRead(PIN_BUTTON_SYMBOL);
  
  // Кнопка нажата
  if (!buttonState) {
    if (buttonWasUp) {
      buttonPressTime = millis();
      buttonWasUp = false;
    }
    
    // Воспроизводим звук
    tone(PIN_BUZZER, 550);
    lastActivityTime = millis();
  } 
  // Кнопка отпущена
  else {
    if (!buttonWasUp && buttonPressTime > 0) {
      noTone(PIN_BUZZER);
      
      unsigned long pressDuration = millis() - buttonPressTime;
      
      // Определяем символ (точка или тире)
      if (pressDuration >= DASH_MIN_TIME) {
        currentSymbol += "-";
      } else {
        currentSymbol += ".";
      }
      
      buttonWasUp = true;
      cycleCounter = 0;
      symbol_end = false;
    }
    
    // Проверка на завершение символа
    if (!symbol_end && currentSymbol.length() > 0) {
      cycleCounter += CYCLE_CHECK_INTERVAL;
      
      if (cycleCounter >= SYMBOL_TIMEOUT) {
        int index = findMorseCode(currentSymbol);
        
        if (index >= 0) {
          // Отправляем найденный символ
          radio.write(&index, sizeof(index));
          Serial.print("Отправлено: ");
          Serial.println(morseTable[index].symbol);
          
          // Визуальная обратная связь
          blinkLED(PIN_LED_TRANSMIT, 1, 50);
          symbol_end = true;
          wrong_symbol = false;
        } else {
          // Символ не найден
          wrong_symbol = true;
        }
        
        currentSymbol = "";
        cycleCounter = 0;
      }
    }
    
    // Обработка неизвестного символа
    if (wrong_symbol && cycleCounter >= SYMBOL_TIMEOUT * 2) {
      byte unknownCmd = CMD_UNKNOWN;
      radio.write(&unknownCmd, sizeof(unknownCmd));
      Serial.println("Отправлен неизвестный символ");
      
      blinkLED(PIN_LED_TRANSMIT, 2, 100);
      wrong_symbol = false;
      cycleCounter = 0;
    }
  }
}
// ================================================

// ============== РЕЖИМ ПРИЁМНИКА ==============
void processReceiver() {
  if (radio.available()) {
    byte command;
    radio.read(&command, sizeof(command));
    
    displayOn();
    
    if (command >= 0 && command < MORSE_COUNT) {
      // Выводим символ на дисплей
      lcd.setCursor(cursorX, cursorY);
      lcd.print(morseTable[command].symbol);
      
      Serial.print("Получено: ");
      Serial.print(command);
      Serial.print(" -> ");
      Serial.println(morseTable[command].symbol);
      
      // Обновляем позицию курсора
      cursorX++;
      updateDisplay();
      
      // Специальная обработка для некоторых команд
      if (morseTable[command].isSpecial) {
        if (command == CMD_UNKNOWN) {
          blinkLED(PIN_LED_RECEIVE, 2, 150);
        } else if (command == CMD_SPACE) {
          // Пробел уже вывелся, но можно добавить мигание
          blinkLED(PIN_LED_RECEIVE, 1, 30);
        }
      } else {
        blinkLED(PIN_LED_RECEIVE, 1, 50);
      }
    }
  }
  
  // Автовыключение дисплея
  if (screen && (millis() - lastActivityTime > SCREEN_TIMEOUT)) {
    displayOff();
  }
}
// ==============================================

// ============== ОБРАБОТКА РЕЖИМА ==============
void handleModeSwitch() {
  static bool lastModeButtonState = HIGH;
  static unsigned long lastModeDebounceTime = 0;
  
  bool modeButtonState = digitalRead(PIN_BUTTON_MODE);
  
  if (modeButtonState == LOW && lastModeButtonState == HIGH && 
      (millis() - lastModeDebounceTime) > DEBOUNCE_DELAY) {
    
    lastModeDebounceTime = millis();
    
    // Переключаем режим
    transmitter = !transmitter;
    
    // Настраиваем радио
    if (transmitter) {
      radio.stopListening();
      displayOff();
    } else {
      radio.startListening();
      displayOn();
      cursorX = 0;
      cursorY = 0;
      lcd.clear();
    }
    
    updateModeIndicators();
    
    Serial.print("Режим: ");
    Serial.println(transmitter ? "ПЕРЕДАТЧИК" : "ПРИЁМНИК");
    
    // Мигаем светодиодами для подтверждения
    blinkLED(PIN_LED_TRANSMIT, 2, 100);
    blinkLED(PIN_LED_RECEIVE, 2, 100);
  }
  
  lastModeButtonState = modeButtonState;
}
// ==============================================

// ============== SETUP ==============
void setup() {
  // Инициализация пинов
  pinMode(PIN_BUZZER, OUTPUT);
  pinMode(PIN_BUTTON_SYMBOL, INPUT_PULLUP);
  pinMode(PIN_BUTTON_MODE, INPUT_PULLUP);
  pinMode(PIN_LED_TRANSMIT, OUTPUT);
  pinMode(PIN_LED_RECEIVE, OUTPUT);
  pinMode(PIN_GND, OUTPUT);
  digitalWrite(PIN_GND, LOW);
  
  // Инициализация последовательного порта
  Serial.begin(9600);
  Serial.println("Запуск системы...");
  
  // Инициализация дисплея
  lcd.init();
  lcd.backlight();
  lcd.print("Morse Radio");
  lcd.setCursor(0, 1);
  lcd.print("v2.0");
  
  // Инициализация радио
  radio.begin();
  radio.enableAckPayload();
  radio.openWritingPipe(pipe);
  radio.openReadingPipe(1, pipe);
  
  // Начальное состояние
  displayOn();
  updateModeIndicators();
  buttonWasUp = true;
  
  // Вывод таблицы Морзе в Serial для отладки
  Serial.println("--- Таблица Морзе загружена ---");
  Serial.print("Всего символов: ");
  Serial.println(MORSE_COUNT);
  
  delay(2000);
  lcd.clear();
}
// ===================================

// ============== LOOP ==============
void loop() {
  // Обновляем счётчик цикла для проверок таймаута
  if (millis() - lastCycleCheck > CYCLE_CHECK_INTERVAL) {
    lastCycleCheck = millis();
    if (transmitter && !buttonWasUp) {
      cycleCounter += CYCLE_CHECK_INTERVAL;
    }
  }
  
  // Обработка переключения режима
  handleModeSwitch();
  
  // Основная логика в зависимости от режима
  if (transmitter) {
    processTransmitter();
  } else {
    processReceiver();
  }
  
  // Небольшая задержка для стабильности
  delay(5);
}
// ===================================

// ============== ДОПОЛНИТЕЛЬНЫЕ ФУНКЦИИ ==============
// Функция для отладки - отправить тестовый символ
void sendTestSymbol(const char* morse) {
  int index = findMorseCode(morse);
  if (index >= 0) {
    Serial.print("Тест: ");
    Serial.print(morse);
    Serial.print(" -> ");
    Serial.println(morseTable[index].symbol);
  } else {
    Serial.println("Тест: символ не найден");
  }
}

// Функция для очистки дисплея
void clearDisplay() {
  lcd.clear();
  cursorX = 0;
  cursorY = 0;
}
// ====================================================

а вот настоящая! но это не точно…

#include <Wire.h> 
#include <LiquidCrystal_I2C.h>
#include <SPI.h>
#include "nRF24L01.h"
#include "RF24.h"

// ============== НАСТРОЙКИ ==============
const uint64_t pipe = 0xE8E8F0F0E1LL; 
const int LCD_ADDR = 0x27;
const int LCD_COLS = 16;
const int LCD_ROWS = 2;

// Пины
const int PIN_BUZZER = 2;
const int PIN_BUTTON_SYMBOL = 3;
const int PIN_BUTTON_MODE = 4;
const int PIN_LED_TRANSMIT = 5;
const int PIN_LED_RECEIVE = 6;
const int PIN_GND = 8;
const int PIN_CE = 9;
const int PIN_CSN = 10;

// Временные константы (в миллисекундах)
const unsigned long DASH_MIN_TIME = 150;        // Минимальное время для тире
const unsigned long SYMBOL_TIMEOUT = 500;       // Таймаут между символами
const unsigned long SCREEN_TIMEOUT = 60000;     // Таймаут подсветки
const unsigned long DEBOUNCE_DELAY = 20;        // Антидребезг
const unsigned long CYCLE_CHECK_INTERVAL = 10;  // Интервал проверки цикла
const unsigned long SPACE_PAUSE = 2000;         // Пауза для пробела (между словами)
const unsigned long LETTER_PAUSE = 500;         // Пауза между буквами

// Специальные команды
enum SpecialCommands {
  CMD_UNKNOWN = 52,  // Индекс для ошибки (последний элемент)
  CMD_COUNT = 53     // Общее количество команд
};
// ========================================

// ============== СТРУКТУРА ДАННЫХ ==============
struct MorseSymbol {
  const char* morse;      // код Морзе
  const char* symbol;     // отображаемый символ
  const char* description; // описание
};

// ПРАВИЛЬНАЯ таблица символов Морзе (без дубликатов)
const MorseSymbol morseTable[] = {
  // Буквы A-Z (0-25)
  {".-", "A", "буква А"},
  {"-...", "B", "буква B"},
  {"-.-.", "C", "буква C"},
  {"-..", "D", "буква D"},
  {".", "E", "буква E"},
  {"..-.", "F", "буква F"},
  {"--.", "G", "буква G"},
  {"....", "H", "буква H"},
  {"..", "I", "буква I"},
  {".---", "J", "буква J"},
  {"-.-", "K", "буква K"},
  {".-..", "L", "буква L"},
  {"--", "M", "буква M"},
  {"-.", "N", "буква N"},
  {"---", "O", "буква O"},
  {".--.", "P", "буква P"},
  {"--.-", "Q", "буква Q"},
  {".-.", "R", "буква R"},
  {"...", "S", "буква S"},
  {"-", "T", "буква T"},
  {"..-", "U", "буква U"},
  {"...-", "V", "буква V"},
  {".--", "W", "буква W"},
  {"-..-", "X", "буква X"},
  {"-.--", "Y", "буква Y"},
  {"--..", "Z", "буква Z"},
  
  // Цифры (26-35)
  {".----", "1", "цифра 1"},
  {"..---", "2", "цифра 2"},
  {"...--", "3", "цифра 3"},
  {"....-", "4", "цифра 4"},
  {".....", "5", "цифра 5"},
  {"-....", "6", "цифра 6"},
  {"--...", "7", "цифра 7"},
  {"---..", "8", "цифра 8"},
  {"----.", "9", "цифра 9"},
  {"-----", "0", "цифра 0"},
  
  // Знаки препинания (36-51)
  {".-.-.-", ".", "точка"},
  {"--..--", ",", "запятая"},
  {"..--..", "?", "вопросительный знак"},
  {"-.-.--", "!", "восклицательный знак"},
  {"---...", ":", "двоеточие"},
  {"-.-.-.", ";", "точка с запятой"},
  {"-...-", "=", "равно"},
  {".-...", "&", "амперсанд"},
  {"..--.-", "_", "подчеркивание"},
  {".-..-.", "\"", "кавычки"},
  {"-.--.", "(", "открывающая скобка"},
  {"-.--.-", ")", "закрывающая скобка"},
  {".-.-.", "+", "плюс"},
  {"-....-", "-", "дефис"},
  {"/", "/", "косая черта"},
  {"..--.", "@", "собака"},
  
  // Специальные (52)
  {"........", "#", "ошибка/неизвестный символ"}
};

// Пробел обрабатывается отдельно через паузу, а не через код Морзе!

const int MORSE_COUNT = sizeof(morseTable) / sizeof(morseTable[0]);
// ===============================================

// ============== ГЛОБАЛЬНЫЕ ПЕРЕМЕННЫЕ ==============
LiquidCrystal_I2C lcd(LCD_ADDR, LCD_COLS, LCD_ROWS);
RF24 radio(PIN_CE, PIN_CSN);

// Состояние
bool transmitter = true;
bool screen = true;
bool buttonWasUp = true;
bool symbol_end = false;
bool wrong_symbol = false;
bool waitingForSpace = false;
unsigned long spacePauseStart = 0;

// Таймеры
unsigned long buttonPressTime = 0;
unsigned long lastActivityTime = 0;
unsigned long cycleCounter = 0;
unsigned long lastCycleCheck = 0;
unsigned long lastSymbolTime = 0;

// Данные
String currentSymbol = "";
byte cursorX = 0;
byte cursorY = 0;

// Статистика
int symbolsSent = 0;
int symbolsReceived = 0;
// ===============================================

// ============== ФУНКЦИИ ПОИСКА ==============
int findMorseCode(String morse) {
  for (int i = 0; i < MORSE_COUNT; i++) {
    if (morse == morseTable[i].morse) {
      return i;
    }
  }
  return -1;
}

const char* getSymbolByIndex(int index) {
  if (index >= 0 && index < MORSE_COUNT) {
    return morseTable[index].symbol;
  }
  return "?";
}

void printMorseTable() {
  Serial.println("=== ТАБЛИЦА МОРЗЕ ===");
  for (int i = 0; i < MORSE_COUNT; i++) {
    Serial.print(i);
    Serial.print(": ");
    Serial.print(morseTable[i].morse);
    Serial.print(" -> ");
    Serial.print(morseTable[i].symbol);
    Serial.print(" (");
    Serial.print(morseTable[i].description);
    Serial.println(")");
  }
  Serial.print("Всего символов: ");
  Serial.println(MORSE_COUNT);
}
// =============================================

// ============== УПРАВЛЕНИЕ ДИСПЛЕЕМ ==============
void displayOn() {
  lcd.display();
  lcd.backlight();
  screen = true;
  lastActivityTime = millis();
}

void displayOff() {
  lcd.noDisplay();
  lcd.noBacklight();
  screen = false;
}

void updateCursor() {
  if (cursorX >= LCD_COLS) {
    cursorX = 0;
    cursorY++;
    if (cursorY >= LCD_ROWS) {
      cursorY = 0;
      lcd.clear();
    }
  }
}

void addCharToDisplay(char c) {
  lcd.setCursor(cursorX, cursorY);
  lcd.print(c);
  cursorX++;
  updateCursor();
}

void addStringToDisplay(const char* str) {
  lcd.setCursor(cursorX, cursorY);
  lcd.print(str);
  cursorX += strlen(str);
  updateCursor();
}
// =================================================

// ============== СВЕТОДИОДНАЯ ИНДИКАЦИЯ ==============
void updateModeIndicators() {
  if (transmitter) {
    digitalWrite(PIN_LED_TRANSMIT, HIGH);
    digitalWrite(PIN_LED_RECEIVE, LOW);
  } else {
    digitalWrite(PIN_LED_TRANSMIT, LOW);
    digitalWrite(PIN_LED_RECEIVE, HIGH);
  }
}

void blinkLED(int pin, int count = 1, int duration = 100) {
  for (int i = 0; i < count; i++) {
    digitalWrite(pin, HIGH);
    delay(duration);
    digitalWrite(pin, LOW);
    if (i < count - 1) delay(duration);
  }
}

void indicateSuccess() {
  blinkLED(PIN_LED_TRANSMIT, 1, 50);
}

void indicateError() {
  blinkLED(PIN_LED_TRANSMIT, 3, 100);
}

void indicateReceive() {
  blinkLED(PIN_LED_RECEIVE, 1, 50);
}
// ====================================================

// ============== РАДИО ФУНКЦИИ ==============
bool sendCommand(byte cmd) {
  bool result = radio.write(&cmd, sizeof(cmd));
  if (result) {
    symbolsSent++;
    Serial.print("Отправлено [");
    Serial.print(symbolsSent);
    Serial.print("]: ");
    Serial.print(cmd);
    Serial.print(" -> ");
    Serial.println(getSymbolByIndex(cmd));
  }
  return result;
}

bool receiveCommand(byte &cmd) {
  if (radio.available()) {
    radio.read(&cmd, sizeof(cmd));
    symbolsReceived++;
    return true;
  }
  return false;
}
// ============================================

// ============== РЕЖИМ ПЕРЕДАТЧИКА ==============
void processTransmitter() {
  bool buttonState = digitalRead(PIN_BUTTON_SYMBOL);
  
  // Обработка пробела (длинное нажатие)
  if (waitingForSpace) {
    if (millis() - spacePauseStart > SPACE_PAUSE) {
      // Отправляем пробел как специальную команду
      // В реальной азбуке Морзе пробел - это пауза, но для простоты
      // мы можем отправить специальный код
      byte spaceCmd = CMD_UNKNOWN - 1; // Используем предпоследний индекс для пробела
      sendCommand(spaceCmd);
      waitingForSpace = false;
      indicateSuccess();
    }
    return;
  }
  
  // Кнопка нажата
  if (!buttonState) {
    if (buttonWasUp) {
      buttonPressTime = millis();
      buttonWasUp = false;
      noTone(PIN_BUZZER); // Сначала выключаем, потом включим
    }
    
    // Воспроизводим звук
    tone(PIN_BUZZER, 550);
    lastActivityTime = millis();
    lastSymbolTime = millis();
  } 
  // Кнопка отпущена
  else {
    if (!buttonWasUp && buttonPressTime > 0) {
      noTone(PIN_BUZZER);
      
      unsigned long pressDuration = millis() - buttonPressTime;
      
      // Определяем символ (точка или тире)
      if (pressDuration >= DASH_MIN_TIME) {
        currentSymbol += "-";
        Serial.println("Добавлено тире");
      } else {
        currentSymbol += ".";
        Serial.println("Добавлена точка");
      }
      
      buttonWasUp = true;
      cycleCounter = 0;
      symbol_end = false;
    }
    
    // Проверка на завершение символа
    if (!symbol_end && currentSymbol.length() > 0) {
      if (millis() - lastSymbolTime > SYMBOL_TIMEOUT) {
        int index = findMorseCode(currentSymbol);
        
        if (index >= 0) {
          // Отправляем найденный символ
          if (sendCommand(index)) {
            // Добавляем символ на локальный дисплей
            addStringToDisplay(morseTable[index].symbol);
            symbol_end = true;
            wrong_symbol = false;
            indicateSuccess();
          }
        } else {
          // Символ не найден
          Serial.print("Неизвестный код: ");
          Serial.println(currentSymbol);
          wrong_symbol = true;
        }
        
        currentSymbol = "";
        lastSymbolTime = millis();
      }
    }
    
    // Обработка неизвестного символа
    if (wrong_symbol && millis() - lastSymbolTime > SYMBOL_TIMEOUT * 2) {
      byte unknownCmd = CMD_UNKNOWN;
      if (sendCommand(unknownCmd)) {
        addStringToDisplay("?");
        Serial.println("Отправлен неизвестный символ");
        indicateError();
      }
      
      wrong_symbol = false;
      lastSymbolTime = millis();
    }
  }
  
  // Проверка на пробел (длинная пауза)
  if (buttonWasUp && !waitingForSpace && currentSymbol.length() == 0) {
    if (millis() - lastSymbolTime > SPACE_PAUSE && lastSymbolTime > 0) {
      waitingForSpace = true;
      spacePauseStart = millis();
      Serial.println("Пауза для пробела...");
    }
  }
}
// ================================================

// ============== РЕЖИМ ПРИЁМНИКА ==============
void processReceiver() {
  byte command;
  
  if (receiveCommand(command)) {
    displayOn();
    
    if (command >= 0 && command < MORSE_COUNT) {
      // Обычный символ
      addStringToDisplay(morseTable[command].symbol);
      
      Serial.print("Получено [");
      Serial.print(symbolsReceived);
      Serial.print("]: ");
      Serial.print(command);
      Serial.print(" -> ");
      Serial.print(morseTable[command].symbol);
      Serial.print(" (");
      Serial.print(morseTable[command].description);
      Serial.println(")");
      
      indicateReceive();
    } else if (command == CMD_UNKNOWN) {
      // Неизвестный символ
      addStringToDisplay("?");
      Serial.println("Получен неизвестный символ");
      blinkLED(PIN_LED_RECEIVE, 2, 150);
    } else if (command == CMD_UNKNOWN - 1) {
      // Пробел
      addStringToDisplay(" ");
      Serial.println("Получен пробел");
      blinkLED(PIN_LED_RECEIVE, 1, 30);
    }
    
    lastActivityTime = millis();
  }
  
  // Автовыключение дисплея
  if (screen && (millis() - lastActivityTime > SCREEN_TIMEOUT)) {
    displayOff();
    Serial.println("Дисплей выключен");
  }
}
// ==============================================

// ============== ОБРАБОТКА РЕЖИМА ==============
void handleModeSwitch() {
  static bool lastModeButtonState = HIGH;
  static unsigned long lastModeDebounceTime = 0;
  
  bool modeButtonState = digitalRead(PIN_BUTTON_MODE);
  
  if (modeButtonState == LOW && lastModeButtonState == HIGH && 
      (millis() - lastModeDebounceTime) > DEBOUNCE_DELAY) {
    
    lastModeDebounceTime = millis();
    
    // Переключаем режим
    transmitter = !transmitter;
    
    // Сбрасываем состояние
    currentSymbol = "";
    buttonWasUp = true;
    waitingForSpace = false;
    lastSymbolTime = millis();
    
    // Настраиваем радио
    if (transmitter) {
      radio.stopListening();
      displayOff();
      Serial.println("РЕЖИМ: ПЕРЕДАТЧИК");
    } else {
      radio.startListening();
      displayOn();
      cursorX = 0;
      cursorY = 0;
      lcd.clear();
      lcd.print("RECEIVING...");
      Serial.println("РЕЖИМ: ПРИЁМНИК");
    }
    
    updateModeIndicators();
    
    // Мигаем светодиодами для подтверждения
    blinkLED(PIN_LED_TRANSMIT, 2, 100);
    blinkLED(PIN_LED_RECEIVE, 2, 100);
  }
  
  lastModeButtonState = modeButtonState;
}
// ==============================================

// ============== СТАТИСТИКА ==============
void printStats() {
  Serial.println("=== СТАТИСТИКА ===");
  Serial.print("Отправлено символов: ");
  Serial.println(symbolsSent);
  Serial.print("Получено символов: ");
  Serial.println(symbolsReceived);
  Serial.print("Режим: ");
  Serial.println(transmitter ? "Передатчик" : "Приёмник");
  Serial.println("=================");
}
// =======================================

// ============== SETUP ==============
void setup() {
  // Инициализация пинов
  pinMode(PIN_BUZZER, OUTPUT);
  pinMode(PIN_BUTTON_SYMBOL, INPUT_PULLUP);
  pinMode(PIN_BUTTON_MODE, INPUT_PULLUP);
  pinMode(PIN_LED_TRANSMIT, OUTPUT);
  pinMode(PIN_LED_RECEIVE, OUTPUT);
  pinMode(PIN_GND, OUTPUT);
  digitalWrite(PIN_GND, LOW);
  
  // Инициализация последовательного порта
  Serial.begin(9600);
  Serial.println("=================================");
  Serial.println("    МОРЗЕ РАДИО v2.0");
  Serial.println("=================================");
  
  // Инициализация дисплея
  lcd.init();
  lcd.backlight();
  lcd.print("Morse Radio");
  lcd.setCursor(0, 1);
  lcd.print("v2.0");
  
  // Инициализация радио
  radio.begin();
  radio.enableAckPayload();
  radio.openWritingPipe(pipe);
  radio.openReadingPipe(1, pipe);
  
  // Начальное состояние
  displayOn();
  updateModeIndicators();
  
  // Вывод таблицы Морзе
  printMorseTable();
  
  Serial.println("Система готова к работе!");
  Serial.println("Кнопка 3 - ввод Морзе");
  Serial.println("Кнопка 4 - смена режима");
  Serial.println("=================================");
  
  delay(2000);
  lcd.clear();
}
// ===================================

// ============== LOOP ==============
void loop() {
  // Обработка переключения режима
  handleModeSwitch();
  
  // Основная логика в зависимости от режима
  if (transmitter) {
    processTransmitter();
  } else {
    processReceiver();
  }
  
  // Вывод статистики каждые 10 секунд (для отладки)
  static unsigned long lastStatsTime = 0;
  if (millis() - lastStatsTime > 10000) {
    lastStatsTime = millis();
    printStats();
  }
  
  // Небольшая задержка для стабильности
  delay(5);
}
// ===================================

Скетч использует 12926 байт (42%) памяти устройства. Всего доступно 30720 байт.
Глобальные переменные используют 2663 байт (130%) динамической памяти, оставляя -615 байт для локальных переменных. Максимум: 2048 байт.
Недостаточно памяти; прочитайте https://support.arduino.cc/hc/en-us/articles/360013825179
раздел данных превышает доступное пространство на плате

Compilation error: раздел данных превышает доступное пространство на плате

А Вы, простите, чего ожидали от кодов “специалистов в предметной области”, типа таких?

Я Вам тут уже цитировал:

«Тролли же с удовольствием дают исходники, радостно наблюдая как стремительно фалломорфируют просящие (да и кто обещал рабочие исходники?)»

ой все, значит не судьба))) надо собирать вникать, и делать))) а я быстро, может кто за основу возьмет…

Нет тот случай! Никто за основу ничего не взьмёт! Будьте любезны – готовый код в подарочной упаковке! А то, ишь, взяли моду – “за основу”. Это что ж, белому человеку после таких “помощников” ещё и самому допиливать? Совсем, блин, берега попутали :frowning:

Удивительно, правда?

ну обленился я немного, не проверил,))) и все делаю как правило на esp32, и вот там можно развернуться…
а за основу можно взять все символы!))) зато как понятно теперь?! можно сказать из 0 и 1 сделано…
вот и надо было им так на памяти экономить… эээх

Пусть ИИ теперь под УНО адаптирует вариант скетча.

В PROGMEM “закатайте” и делов - то)))

а если ии все поломает ? особенно с PROGMEM…
вот если автор сам обратится к нему, и будет понимать что он делает…

тогда можно наверное, а то добавишь и неизвестно где искать проблему потом…)))

Как сказал бы мой покойный сосед - А если нет?