ARDUINO atmega328 shell ( управление МК по UART с компа или с телефона)

selevo

✩✩✩✩✩✩✩
25 Апр 2020
25
3
управление по UART 9600
команды:
? - вывести список комманд
?? вывести названия регистров ATMEGA328 и адреса
boot- быстро мигнуть 3 раза светодиодом и через 3 секунды перейти в бутлоадер
boot5 - быстро мигнуть 5 раз и через 5 секунд перейти в бутлоадер
rd адрес - считать ячейку памяти
rdb адрес, номер бита - считать бит по адресу
erd адрес - считать ячейку EEPROM
erdb адрес, номер бита - считать бит из EEPROM по адресу

wr адрес данные - записать в ячейку данные
ewr адрес данные - записать в eeprom
clre - очистить EEPROM
wrsb адрес,номер бита- установить бит по адресу
wrrb адрес,номер бита- сбросить бит по адресу
wrib адрес,номер бита- инвертировать бит по адресу
wr|b адрес, номер бита - записать по логическому "И"
wr&b адрес, номер бита - записать по логическому "ИЛИ"
wr& адрес, данные - записать по логическому "ИЛИ"
wr& адрес, данные - записать по логическому "И"
wr1 начальный адрес, длина блока - заполнить единицами блок адресов
wr0 начальный адрес, длина блока - заполнить нолями блок адресов
rda начальный адрес, длина - считать массив байтов
boud9600 переключится через 3 секунды на скорость 9600
boud19200 переключится через 3 секунды на скорость 9600, если в течении 5 секунд не придет команда ok переключиться обратно на 9600.

Проверил не всё, если кто заметит ошибки, пишите, может исправлю ))
Если кому не лень исправте справку на русский, я забыл.


C++:
#include <EEPROM.h>
#include <avr/boot.h>
#include <avr/pgmspace.h>

// -------------------------------------------------------------------
// 1. КОНСТАНТЫ ИСПОЛЬЗУЮЩИЕ PROGMEM (Flash Memory)
// -------------------------------------------------------------------

// Макросы для работы с памятью и регистрами
#define READ_RAM_BYTE(addr) (*((volatile uint8_t *)(addr)))
#define WRITE_RAM_BYTE(addr, data) (*((volatile uint8_t *)(addr)) = (data))

const word BOOTLOADER_START = 0x3C00;
// --- Тексты помощи и сообщений ---
const char HELP_TEXT[] PROGMEM =
    "--- Command List ---\n"
    "?: Show command list\n"
    "??: Show ATmega328 register names and addresses\n"
    "boot <blinks> <delay_s>: Enter bootloader using default address (e.g., boot 3 3)\n"
    "bootaddr <addr> <blinks> <delay_s>: Enter bootloader using custom address (e.g., bootaddr 3c00 5 5)\n"
    "rd <addr>: Read RAM byte at address (hex)\n"
    "rdb <addr>,<bit>: Read bit from RAM at address\n"
    "wr <addr> <data>: Write byte to RAM (hex)\n"
    "clre: Clear EEPROM (fill with 0xFF)\n"
    "erd <addr>: Read EEPROM byte at address (hex)\n"
    "erdb <addr>,<bit>: Read bit from EEPROM at address\n"
    "ewr <addr> <data>: Write byte to EEPROM (hex)\n"
    "wrsb <addr>,<bit>: Set bit (1) in RAM\n"
    "wrrb <addr>,<bit>: Clear bit (0) in RAM\n"
    "wrib <addr>,<bit>: Invert bit in RAM\n"
    "wr|b <addr>,<bit>: Bitwise OR with 1 (same as wrsb)\n"
    "wr&b <addr>,<bit>: Bitwise AND with 0 (same as wrrb)\n"
    "wr| <addr> <data>: Bitwise OR with data in RAM\n"
    "wr& <addr> <data>: Bitwise AND with data in RAM\n"
    "wr1 <start_addr> <len>: Fill RAM block with 0xFF\n"
    "wr0 <start_addr> <len>: Fill RAM block with 0x00\n"
    "rda <start_addr> <len>: Read array of bytes from RAM\n"
    "boud9600: Switch to 9600 baud in 3s\n"
    "boud19200: Switch to 19200 baud in 3s (with 5s rollback)\n"
    "--------------------";

const char REGISTER_HEADER[] PROGMEM = "--- ATmega328P Registers (partial list) ---";
const char REGISTER_FOOTER[] PROGMEM = "------------------------------------------";

// --- Сообщения об ошибках и статусе ---
const char ERR_RAM_BOUNDS[] PROGMEM = "Error: Address out of RAM bounds.";
const char ERR_BIT_INVALID[] PROGMEM = "Error: Invalid address or bit number (0-7).";
const char ERR_ADDR_LENGTH[] PROGMEM = "Error: Invalid start address or length.";
const char ERR_EEPROM_BOUNDS[] PROGMEM = "Error: Address out of EEPROM bounds.";
const char ERR_INTERNAL_OP[] PROGMEM = "Internal Error: Invalid operation.";
const char ERR_ADDR_FORMAT[] PROGMEM = "Error: Invalid bootloader address format or parameters.";


// --- Структура и список регистров (также в PROGMEM) ---
struct RegisterInfo {
    const char* name;
    uint16_t address;
};

// Частичный список регистров ATmega328P
const RegisterInfo registers[] PROGMEM = {
    {"SREG", 0x5F}, // Status Register
    {"SPH", 0x5E},  // Stack Pointer High
    {"SPL", 0x5D},  // Stack Pointer Low
    {"PORTB", 0x25}, // Port B Data Register
    {"DDRB", 0x24},  // Port B Data Direction Register
    {"PINB", 0x23},  // Port B Input Pins Address
    {"UBRR0H", 0xC5},// UART Baud Rate Register High
    {"UBRR0L", 0xC4},// UART Baud Rate Register Low
    {"UCSR0A", 0xC0},// UART Control and Status Register A
    // ... можно добавить остальные регистры
};
const int NUM_REGISTERS = sizeof(registers) / sizeof(registers[0]);

// -------------------------------------------------------------------
// 2. ВСПОМОГАТЕЛЬНЫЕ ФУНКЦИИ PROGMEM
// -------------------------------------------------------------------

/**
* @brief Вывод строки, хранящейся в PROGMEM, в Serial.
*/
void printPgmString(const char* str) {
    size_t len = strlen_P(str);
    for (size_t i = 0; i < len; i++) {
        Serial.write(pgm_read_byte(str + i));
    }
}

/**
* @brief Вывод данных регистра (имя из PROGMEM)
*/
void printPgmRegisterInfo(int index) {
    RegisterInfo info;
    // Читаем структуру из PROGMEM в RAM
    memcpy_P(&info, &registers[index], sizeof(RegisterInfo));

    Serial.print(info.name);
    Serial.print(F(" ("));
    Serial.print(index, DEC);
    Serial.print(F("): 0x"));
   
    if (info.address < 0x10) Serial.print('0');
    Serial.println(info.address, HEX);
}


// -------------------------------------------------------------------
// 3. ОСНОВНОЙ КОД и ОБРАБОТЧИКИ
// -------------------------------------------------------------------

long currentBaudRate = 9600;

void setup() {
    Serial.begin(currentBaudRate);
    pinMode(LED_BUILTIN, OUTPUT);
    Serial.println(F("UART Shell Ready. Type '?' for help."));
}

void loop() {
    if (Serial.available()) {
        String input = Serial.readStringUntil('\n');
        input.trim();
        if (input.length() > 0) {
            parseCommand(input);
        }
    }
}

void printHelp() {
    printPgmString(HELP_TEXT);
    Serial.println();
}

void printRegisters() {
    printPgmString(REGISTER_HEADER);
    Serial.println();
    for (int i = 0; i < NUM_REGISTERS; i++) {
        printPgmRegisterInfo(i);
    }
    printPgmString(REGISTER_FOOTER);
    Serial.println();
}

/**
* @brief Переход в бутлоадер
* @param boot_addr Адрес для перехода (0 - использовать BOOTLOADER_START)
* @param count Количество миганий
* @param delay_s Задержка в секундах
*/
void enterBootloader(uint16_t boot_addr, int count, int delay_s) {
    // Мигание
    for (int i = 0; i < count; i++) {
        digitalWrite(LED_BUILTIN, HIGH);
        delay(100);
        digitalWrite(LED_BUILTIN, LOW);
        delay(100);
    }

    Serial.print(F("Entering bootloader at address 0x"));
   
    if (boot_addr == 0) {
        boot_addr = BOOTLOADER_START;
        Serial.print(boot_addr, HEX);
        Serial.print(F(" (Default) in "));
    } else {
        Serial.print(boot_addr, HEX);
        Serial.print(F(" (Custom) in "));
    }
   
    Serial.print(delay_s);
    Serial.println(F(" seconds..."));

    delay(delay_s * 1000);

    void (*bootloader_ptr)(void) = (void (*)(void))boot_addr;
   
    cli();
    bootloader_ptr();
}

void readRam(unsigned int addr) {
    if (addr > RAMEND) {
        printPgmString(ERR_RAM_BOUNDS);
        Serial.println();
        return;
    }
    uint8_t data = READ_RAM_BYTE(addr);
    Serial.print(F("RAM[0x"));
    Serial.print(addr, HEX);
    Serial.print(F("] = 0x"));
    Serial.println(data, HEX);
}

void readRamBit(unsigned int addr, int bitNum) {
    if (addr > RAMEND || bitNum < 0 || bitNum > 7) {
        printPgmString(ERR_BIT_INVALID);
        Serial.println();
        return;
    }
    uint8_t data = READ_RAM_BYTE(addr);
    uint8_t bitValue = (data >> bitNum) & 0x01;
    Serial.print(F("RAM[0x"));
    Serial.print(addr, HEX);
    Serial.print(F("].Bit"));
    Serial.print(bitNum, DEC);
    Serial.print(F(" = "));
    Serial.println(bitValue, DEC);
}

void writeRam(unsigned int addr, uint8_t data) {
    if (addr > RAMEND) {
        printPgmString(ERR_RAM_BOUNDS);
        Serial.println();
        return;
    }
    WRITE_RAM_BYTE(addr, data);
    Serial.print(F("Wrote 0x"));
    Serial.print(data, HEX);
    Serial.print(F(" to RAM[0x"));
    Serial.print(addr, HEX);
    Serial.println(F("]."));
}

void readEeprom(unsigned int addr) {
    if (addr >= EEPROM.length()) {
        printPgmString(ERR_EEPROM_BOUNDS);
        Serial.println();
        return;
    }
    uint8_t data = EEPROM.read(addr);
    Serial.print(F("EEPROM[0x"));
    Serial.print(addr, HEX);
    Serial.print(F("] = 0x"));
    Serial.println(data, HEX);
}

void readEepromBit(unsigned int addr, int bitNum) {
    if (addr >= EEPROM.length() || bitNum < 0 || bitNum > 7) {
        printPgmString(ERR_BIT_INVALID);
        Serial.println();
        return;
    }
    uint8_t data = EEPROM.read(addr);
    uint8_t bitValue = (data >> bitNum) & 0x01;
    Serial.print(F("EEPROM[0x"));
    Serial.print(addr, HEX);
    Serial.print(F("].Bit"));
    Serial.print(bitNum, DEC);
    Serial.print(F(" = "));
    Serial.println(bitValue, DEC);
}

void writeEeprom(unsigned int addr, uint8_t data) {
    if (addr >= EEPROM.length()) {
        printPgmString(ERR_EEPROM_BOUNDS);
        Serial.println();
        return;
    }
    EEPROM.write(addr, data);
    Serial.print(F("Wrote 0x"));
    Serial.print(data, HEX);
    Serial.print(F(" to EEPROM[0x"));
    Serial.print(addr, HEX);
    Serial.println(F("]."));
}

void clearEeprom() {
    Serial.println(F("Clearing EEPROM... (May take a moment)"));
    for (int i = 0; i < EEPROM.length(); i++) {
        EEPROM.write(i, 0xFF);
    }
    Serial.println(F("EEPROM cleared to 0xFF."));
}

void manipulateRamBit(unsigned int addr, int bitNum, char operation) {
    if (addr > RAMEND || bitNum < 0 || bitNum > 7) {
        printPgmString(ERR_BIT_INVALID);
        Serial.println();
        return;
    }
   
    uint8_t data = READ_RAM_BYTE(addr);
    uint8_t mask = 1 << bitNum;

    const char* op_msg;

    switch (operation) {
        case 's': // wrsb
            data |= mask;
            op_msg = "Set bit (wrsb)";
            break;
        case 'r': // wrrb
            data &= ~mask;
            op_msg = "Clear bit (wrrb)";
            break;
        case 'i': // wrib
            data ^= mask;
            op_msg = "Invert bit (wrib)";
            break;
        case '|': // wr|b
            data |= mask;
            op_msg = "Bitwise OR with 1 (wr|b)";
            break;
        case '&': // wr&b
            data &= ~mask;
            op_msg = "Bitwise AND with 0 (wr&b)";
            break;
        default:
            printPgmString(ERR_INTERNAL_OP);
            Serial.println();
            return;
    }

    WRITE_RAM_BYTE(addr, data);
    Serial.print(op_msg);
    Serial.print(F(" at RAM[0x"));
    Serial.print(addr, HEX);
    Serial.print(F("]. Result: 0x"));
    Serial.println(data, HEX);
}

void binaryRamOp(unsigned int addr, uint8_t data, char operation) {
    if (addr > RAMEND) {
        printPgmString(ERR_RAM_BOUNDS);
        Serial.println();
        return;
    }

    uint8_t currentData = READ_RAM_BYTE(addr);
    const char* op_msg;
   
    switch (operation) {
        case '|': // wr|
            currentData |= data;
            op_msg = "Bitwise OR (wr|)";
            break;
        case '&': // wr&
            currentData &= data;
            op_msg = "Bitwise AND (wr&)";
            break;
        default:
            printPgmString(ERR_INTERNAL_OP);
            Serial.println();
            return;
    }

    WRITE_RAM_BYTE(addr, currentData);
    Serial.print(op_msg);
    Serial.print(F(" with 0x"));
    Serial.print(data, HEX);
    Serial.print(F(" at RAM[0x"));
    Serial.print(addr, HEX);
    Serial.print(F("]. New Value: 0x"));
    Serial.println(currentData, HEX);
}

void fillRamBlock(unsigned int startAddr, unsigned int len, uint8_t fillByte) {
    if (startAddr > RAMEND || startAddr + len > RAMEND || len == 0) {
        printPgmString(ERR_ADDR_LENGTH);
        Serial.println();
        return;
    }

    for (unsigned int i = 0; i < len; i++) {
        WRITE_RAM_BYTE(startAddr + i, fillByte);
    }
   
    Serial.print(F("Filled RAM block from 0x"));
    Serial.print(startAddr, HEX);
    Serial.print(F(" to 0x"));
    Serial.print(startAddr + len - 1, HEX);
    Serial.print(F(" with 0x"));
    Serial.println(fillByte, HEX);
}

void readRamArray(unsigned int startAddr, unsigned int len) {
    if (startAddr > RAMEND || startAddr + len > RAMEND || len == 0) {
        printPgmString(ERR_ADDR_LENGTH);
        Serial.println();
        return;
    }

    Serial.print(F("RAM[0x"));
    Serial.print(startAddr, HEX);
    Serial.print(F("..0x"));
    Serial.print(startAddr + len - 1, HEX);
    Serial.println(F("]:"));

    for (unsigned int i = 0; i < len; i++) {
        uint8_t data = READ_RAM_BYTE(startAddr + i);
       
        if (i > 0 && i % 16 == 0) {
            Serial.println();
            Serial.print(F("0x"));
            Serial.print(startAddr + i, HEX);
            Serial.print(F(": "));
        } else if (i % 16 == 0) {
            Serial.print(F("0x"));
            Serial.print(startAddr + i, HEX);
            Serial.print(F(": "));
        }

        if (data < 0x10) Serial.print('0');
        Serial.print(data, HEX);
        Serial.print(F(" "));
    }
    Serial.println();
}

void changeBaudRateWithRollback(long newBaudRate, bool rollback) {
    Serial.print(F("Switching to "));
    Serial.print(newBaudRate);
    Serial.println(F(" baud in 3 seconds. Send 'ok' to confirm."));
    delay(3000);

    Serial.end();
    Serial.begin(newBaudRate);

    currentBaudRate = newBaudRate;
    Serial.print(F("Switched to "));
    Serial.print(currentBaudRate);
    Serial.println(F(" baud. (If you don't see this, reconnect with the new speed)"));

    if (rollback) {
        long startTime = millis();
        bool confirmed = false;
       
        // Блокирующий цикл ожидания подтверждения
        while (millis() - startTime < 5000) {
            if (Serial.available()) {
                String confirm = Serial.readStringUntil('\n');
                confirm.trim();
                if (confirm.equalsIgnoreCase("ok")) {
                    confirmed = true;
                    Serial.println(F("Confirmed. Baud rate change permanent."));
                    break;
                }
            }
        }
       
        if (!confirmed) {
            Serial.println(F("Timeout. Rolling back to 9600 baud..."));
            delay(100);
            Serial.end();
            Serial.begin(9600);
            currentBaudRate = 9600;
            Serial.println(F("Rolled back to 9600 baud."));
        }
    }
}

void parseCommand(String input) {
    // Разделение строки на слова
    char command[input.length() + 1];
    input.toCharArray(command, input.length() + 1);
   
    char *p = command;
    char *str;
    char *words[5] = {NULL}; // Максимум 5 слов: <cmd> <p1> <p2> <p3> <p4>
    int wordCount = 0;

    while ((str = strtok_r(p, " ,", &p))) {
        if (wordCount < 5) {
            words[wordCount++] = str;
        } else {
            break;
        }
    }

    if (wordCount == 0) return;

    // --- Параметры для команды boot/bootaddr ---
    uint16_t boot_addr = 0; // 0 - использовать адрес по умолчанию
    int blinks = 0;
    int delay_s = 0;

    // --- Параметры для команд памяти ---
    unsigned int addr = (wordCount > 1) ? (unsigned int)strtol(words[1], NULL, 16) : 0;
    int bitNum = 0;
    uint8_t data = 0;
    unsigned int len = 0;

    if (wordCount > 2) {
        // Определяем второй аргумент в зависимости от команды
        if (strcmp(words[0], "rdb") == 0 || strcmp(words[0], "erdb") == 0 ||
            strcmp(words[0], "wrsb") == 0 || strcmp(words[0], "wrrb") == 0 ||
            strcmp(words[0], "wrib") == 0 || strcmp(words[0], "wr|b") == 0 || strcmp(words[0], "wr&b") == 0) {
           
            bitNum = (int)strtol(words[2], NULL, 10);
       
        } else if (strcmp(words[0], "wr") == 0 || strcmp(words[0], "ewr") == 0 ||
                   strcmp(words[0], "wr|") == 0 || strcmp(words[0], "wr&") == 0) {
           
            data = (uint8_t)strtol(words[2], NULL, 16);
           
        } else if (strcmp(words[0], "wr1") == 0 || strcmp(words[0], "wr0") == 0 ||
                   strcmp(words[0], "rda") == 0) {
           
            len = (unsigned int)strtol(words[2], NULL, 10);
        }
    }

    // --- Обработка команд ---
    if (strcmp(words[0], "?") == 0) {
        printHelp();
    } else if (strcmp(words[0], "??") == 0) {
        printRegisters();
    } else if (strcmp(words[0], "boot") == 0) {
        // boot <blinks> <delay_s>
        if (wordCount == 3) {
            blinks = (int)strtol(words[1], NULL, 10);
            delay_s = (int)strtol(words[2], NULL, 10);
            if (blinks > 0 && delay_s > 0) {
                enterBootloader(0, blinks, delay_s); // 0 = адрес по умолчанию
            } else {
                Serial.println(F("Error: Blinks and delay must be positive."));
            }
        } else {
            Serial.println(F("Error: Usage is 'boot <blinks> <delay_s>'."));
        }
    } else if (strcmp(words[0], "boot5") == 0) {
        // Старая команда boot5, теперь просто вызывается boot 5 5
        enterBootloader(0, 5, 5);
    } else if (strcmp(words[0], "bootaddr") == 0) {
        // bootaddr <addr> <blinks> <delay_s>
        if (wordCount == 4) {
            boot_addr = (uint16_t)strtol(words[1], NULL, 16);
            blinks = (int)strtol(words[2], NULL, 10);
            delay_s = (int)strtol(words[3], NULL, 10);
           
            if (boot_addr != 0 && blinks > 0 && delay_s > 0) {
                enterBootloader(boot_addr, blinks, delay_s);
            } else {
                printPgmString(ERR_ADDR_FORMAT);
                Serial.println();
            }
        } else {
            Serial.println(F("Error: Usage is 'bootaddr <addr> <blinks> <delay_s>'."));
        }
    } else if (strcmp(words[0], "rd") == 0 && wordCount == 2) {
        readRam(addr);
    } else if (strcmp(words[0], "rdb") == 0 && wordCount == 3) {
        readRamBit(addr, bitNum);
    } else if (strcmp(words[0], "erd") == 0 && wordCount == 2) {
        readEeprom(addr);
    } else if (strcmp(words[0], "erdb") == 0 && wordCount == 3) {
        readEepromBit(addr, bitNum);
    } else if (strcmp(words[0], "wr") == 0 && wordCount == 3) {
        writeRam(addr, data);
    } else if (strcmp(words[0], "ewr") == 0 && wordCount == 3) {
        writeEeprom(addr, data);
    } else if (strcmp(words[0], "clre") == 0) {
        clearEeprom();
    } else if (strcmp(words[0], "wrsb") == 0 && wordCount == 3) {
        manipulateRamBit(addr, bitNum, 's');
    } else if (strcmp(words[0], "wrrb") == 0 && wordCount == 3) {
        manipulateRamBit(addr, bitNum, 'r');
    } else if (strcmp(words[0], "wrib") == 0 && wordCount == 3) {
        manipulateRamBit(addr, bitNum, 'i');
    } else if (strcmp(words[0], "wr|b") == 0 && wordCount == 3) {
        manipulateRamBit(addr, bitNum, '|');
    } else if (strcmp(words[0], "wr&b") == 0 && wordCount == 3) {
        manipulateRamBit(addr, bitNum, '&');
    } else if (strcmp(words[0], "wr|") == 0 && wordCount == 3) {
        binaryRamOp(addr, data, '|');
    } else if (strcmp(words[0], "wr&") == 0 && wordCount == 3) {
        binaryRamOp(addr, data, '&');
    } else if (strcmp(words[0], "wr1") == 0 && wordCount == 3) {
        fillRamBlock(addr, len, 0xFF);
    } else if (strcmp(words[0], "wr0") == 0 && wordCount == 3) {
        fillRamBlock(addr, len, 0x00);
    } else if (strcmp(words[0], "rda") == 0 && wordCount == 3) {
        readRamArray(addr, len);
    } else if (strcmp(words[0], "boud9600") == 0) {
        changeBaudRateWithRollback(9600, false);
    } else if (strcmp(words[0], "boud19200") == 0) {
        changeBaudRateWithRollback(19200, true);
    } else {
        Serial.print(F("Unknown command: "));
        Serial.println(input);
        Serial.println(F("Type '?' for help."));
    }
}
 

Bruzzer

★★★★✩✩✩
23 Май 2020
724
222
@selevo,
В вашей реализации, команда перехода в бутлоадер "boot" может работать не предсказуемо, в зависимости от версии бутлоадера.
Например в версии optiboot 4.4 (используемой в установочном пакете Arduino IDE 1.8.19) бутлоадер активируется только при перезагрузке по сигналу внешний Ресет. И в этом случае загрузчик просто не будет вызываться. Состояние регистров МК после этого будет не предсказуемым, т.е. не равным дефолтным значениям после RESET, и может приводить к глюкам.

Можно сделать "правильный" вариант перезагрузки с вызовом бутлоадера, но по моему в данном случае это не целесообразно.
 

selevo

✩✩✩✩✩✩✩
25 Апр 2020
25
3
@Bruzzer,
Да это дельная заметка....
Потестирую когда дойдет дело до бутлоадера... Да там вообще не нужен такой переход если подумать, там же есть третий проводок- аппаратного сброса.
Но я не расчитывал на полную совмстимость с ARDUINO платами на моих платах другой бутлоадер.
Поэтому я решил сделать выбор адреса для разных вариантов.
 

selevo

✩✩✩✩✩✩✩
25 Апр 2020
25
3
Добавил выбор кодировки

C++:
#include <EEPROM.h>
#include <avr/boot.h>
#include <avr/pgmspace.h>

// -------------------------------------------------------------------
// 1. КОНСТАНТЫ ИСПОЛЬЗУЮЩИЕ PROGMEM (Flash Memory)
// -------------------------------------------------------------------

// Макросы для работы с памятью и регистрами
#define READ_RAM_BYTE(addr) (*((volatile uint8_t *)(addr)))
#define WRITE_RAM_BYTE(addr, data) (*((volatile uint8_t *)(addr)) = (data))

const word BOOTLOADER_START = 0x3C00;
// --- Тексты помощи и сообщений ---
const char HELP_TEXT[] PROGMEM = 
    "--- Command List ---\n"
    "?: Show command list\n"
    "??: Show ATmega328 register names and addresses\n"
    "boot <blinks> <delay_s>: Enter bootloader using default address (e.g., boot 3 3)\n"
    "bootaddr <addr> <blinks> <delay_s>: Enter bootloader using custom address (e.g., bootaddr 3c00 5 5)\n"
    "rd <addr>: Read RAM byte at address (hex)\n"
    "rdb <addr>,<bit>: Read bit from RAM at address\n"
    "wr <addr> <data>: Write byte to RAM (hex)\n"
    "clre: Clear EEPROM (fill with 0xFF)\n"
    "erd <addr>: Read EEPROM byte at address (hex)\n"
    "erdb <addr>,<bit>: Read bit from EEPROM at address\n"
    "ewr <addr> <data>: Write byte to EEPROM (hex)\n"
    "wrsb <addr>,<bit>: Set bit (1) in RAM\n"
    "wrrb <addr>,<bit>: Clear bit (0) in RAM\n"
    "wrib <addr>,<bit>: Invert bit in RAM\n"
    "wr|b <addr>,<bit>: Bitwise OR with 1 (same as wrsb)\n"
    "wr&b <addr>,<bit>: Bitwise AND with 0 (same as wrrb)\n"
    "wr| <addr> <data>: Bitwise OR with data in RAM\n"
    "wr& <addr> <data>: Bitwise AND with data in RAM\n"
    "wr1 <start_addr> <len>: Fill RAM block with 0xFF\n"
    "wr0 <start_addr> <len>: Fill RAM block with 0x00\n"
    "rda <start_addr> <len>: Read array of bytes from RAM\n"
    "boud9600: Switch to 9600 baud in 3s\n"
    "boud19200: Switch to 19200 baud in 3s (with 5s rollback)\n"
    "cp0: Set encoding to UTF-8\n"           // <-- НОВАЯ КОМАНДА cp0
    "cp1: Set encoding to Windows-1251\n"    // <-- НОВАЯ КОМАНДА cp1
    "cp2: Set encoding to DOS/CP866\n"       // <-- НОВАЯ КОМАНДА cp2
    "--------------------";

const char REGISTER_HEADER[] PROGMEM = "--- ATmega328P Registers (partial list) ---";
const char REGISTER_FOOTER[] PROGMEM = "------------------------------------------";

// --- Сообщения об ошибках и статусе ---
const char ERR_RAM_BOUNDS[] PROGMEM = "Error: Address out of RAM bounds.";
const char ERR_BIT_INVALID[] PROGMEM = "Error: Invalid address or bit number (0-7).";
const char ERR_ADDR_LENGTH[] PROGMEM = "Error: Invalid start address or length.";
const char ERR_EEPROM_BOUNDS[] PROGMEM = "Error: Address out of EEPROM bounds.";
const char ERR_INTERNAL_OP[] PROGMEM = "Internal Error: Invalid operation.";
const char ERR_ADDR_FORMAT[] PROGMEM = "Error: Invalid bootloader address format or parameters.";
const char ERR_BAUD_BLINKS[] PROGMEM = "Error: Blinks and delay must be positive.";
const char ERR_BAUD_USAGE[] PROGMEM = "Error: Usage is 'boot <blinks> <delay_s>'.";
const char ERR_BOOTADDR_USAGE[] PROGMEM = "Error: Usage is 'bootaddr <addr> <blinks> <delay_s>'.";
const char ERR_ENCODE[] PROGMEM = "Internal Error: Invalid encoding number.";


// --- Структура и список регистров (также в PROGMEM) ---
struct RegisterInfo {
    const char* name;
    uint16_t address;
};

// Частичный список регистров ATmega328P
const RegisterInfo registers[] PROGMEM = {
    {"SREG", 0x5F}, // Status Register
    {"SPH", 0x5E},  // Stack Pointer High
    {"SPL", 0x5D},  // Stack Pointer Low
    {"PORTB", 0x25}, // Port B Data Register
    {"DDRB", 0x24},  // Port B Data Direction Register
    {"PINB", 0x23},  // Port B Input Pins Address
    {"UBRR0H", 0xC5},// UART Baud Rate Register High
    {"UBRR0L", 0xC4},// UART Baud Rate Register Low
    {"UCSR0A", 0xC0},// UART Control and Status Register A
    // ... можно добавить остальные регистры
};
const int NUM_REGISTERS = sizeof(registers) / sizeof(registers[0]);

// --- Таблицы преобразования кодировок (из UTF-8 в однобайтные) ---
struct CyrillicCharMap {
    uint16_t utf8_word; // Два байта UTF-8 (начинается с 0xD0 или 0xD1)
    uint8_t cp1251_byte;
    uint8_t cp866_byte;
};

// Полный список кириллических символов, закодированных в UTF-8
const CyrillicCharMap cyrillic_map[] PROGMEM = {
    // UTF-8 (2 байта) | CP1251 | CP866 
    {0xD090, 0xC0, 0x80}, {0xD091, 0xC1, 0x81}, {0xD092, 0xC2, 0x82}, {0xD093, 0xC3, 0x83}, 
    {0xD094, 0xC4, 0x84}, {0xD095, 0xC5, 0x85}, {0xD096, 0xC6, 0x86}, {0xD097, 0xC7, 0x87}, 
    {0xD098, 0xC8, 0x88}, {0xD099, 0xC9, 0x89}, {0xD09A, 0xCA, 0x8A}, {0xD09B, 0xCB, 0x8B}, 
    {0xD09C, 0xCC, 0x8C}, {0xD09D, 0xCD, 0x8D}, {0xD09E, 0xCE, 0x8E}, {0xD09F, 0xCF, 0x8F}, 
    {0xD0A0, 0xD0, 0x90}, {0xD0A1, 0xD1, 0x91}, {0xD0A2, 0xD2, 0x92}, {0xD0A3, 0xD3, 0x93}, 
    {0xD0A4, 0xD4, 0x94}, {0xD0A5, 0xD5, 0x95}, {0xD0A6, 0xD6, 0x96}, {0xD0A7, 0xD7, 0x97}, 
    {0xD0A8, 0xD8, 0x98}, {0xD0A9, 0xD9, 0x99}, {0xD0AA, 0xDA, 0x9A}, {0xD0AB, 0xDB, 0x9B}, 
    {0xD0AC, 0xDC, 0x9C}, {0xD0AD, 0xDD, 0x9D}, {0xD0AE, 0xDE, 0x9E}, {0xD0AF, 0xDF, 0x9F}, 
    {0xD081, 0xA8, 0xF0}, // Ё
    // Строчные
    {0xD0B0, 0xE0, 0xA0}, {0xD0B1, 0xE1, 0xA1}, {0xD0B2, 0xE2, 0xA2}, {0xD0B3, 0xE3, 0xA3}, 
    {0xD0B4, 0xE4, 0xA4}, {0xD0B5, 0xE5, 0xA5}, {0xD0B6, 0xE6, 0xA6}, {0xD0B7, 0xE7, 0xA7}, 
    {0xD0B8, 0xE8, 0xA8}, {0xD0B9, 0xE9, 0xA9}, {0xD0BA, 0xEA, 0xAA}, {0xD0BB, 0xEB, 0xAB}, 
    {0xD0BC, 0xEC, 0xAC}, {0xD0BD, 0xED, 0xAD}, {0xD0BE, 0xEE, 0xAE}, {0xD0BF, 0xEF, 0xAF}, 
    {0xD180, 0xF0, 0xB0}, {0xD181, 0xF1, 0xB1}, {0xD182, 0xF2, 0xB2}, {0xD183, 0xF3, 0xB3}, 
    {0xD184, 0xF4, 0xB4}, {0xD185, 0xF5, 0xB5}, {0xD186, 0xF6, 0xB6}, {0xD187, 0xF7, 0xB7}, 
    {0xD188, 0xF8, 0xB8}, {0xD189, 0xF9, 0xB9}, {0xD18A, 0xFA, 0xBA}, {0xD18B, 0xFB, 0xBB}, 
    {0xD18C, 0xFC, 0xBC}, {0xD18D, 0xFD, 0xBD}, {0xD18E, 0xFE, 0xBE}, {0xD18F, 0xFF, 0xBF}, 
    {0xD191, 0xB8, 0xF1}  // ё
};
const int NUM_CYR_CHARS = sizeof(cyrillic_map) / sizeof(cyrillic_map[0]);


// -------------------------------------------------------------------
// 2. ВСПОМОГАТЕЛЬНЫЕ ФУНКЦИИ PROGMEM и КОДИРОВКИ
// -------------------------------------------------------------------

/**
 * @brief Вывод строки, хранящейся в PROGMEM, в Serial. (Используется для заголовков)
 */
void printPgmString(const char* str) {
    size_t len = strlen_P(str);
    for (size_t i = 0; i < len; i++) {
        Serial.write(pgm_read_byte(str + i));
    }
}

/**
 * @brief Вывод данных регистра (имя из PROGMEM)
 */
void printPgmRegisterInfo(int index) {
    RegisterInfo info;
    // Читаем структуру из PROGMEM в RAM
    memcpy_P(&info, &registers[index], sizeof(RegisterInfo));

    Serial.print(info.name); 
    Serial.print(F(" ("));
    Serial.print(index, DEC);
    Serial.print(F("): 0x"));
    
    if (info.address < 0x10) Serial.print('0');
    Serial.println(info.address, HEX);
}

// ------------------- ФУНКЦИИ КОДИРОВКИ -------------------

// Константы кодировок
#define ENCODE_UTF8 0
#define ENCODE_CP1251 1 // Windows-1251
#define ENCODE_CP866 2  // DOS (Code Page 866)

// Глобальная переменная для текущей кодировки (по умолчанию UTF-8)
int currentEncoding = ENCODE_UTF8;

/**
 * @brief Вывод строки в выбранной кодировке. Строки F(...) и String должны быть в UTF-8.
 */
void printEncodedString(const __FlashStringHelper* fsh) {
    PGM_P p = reinterpret_cast<PGM_P>(fsh);

    for (size_t i = 0; i < strlen_P(p); i++) {
        uint8_t byte1 = pgm_read_byte(p + i);

        // Если это первый байт кириллического символа в UTF-8 (начинается с D0 или D1)
        if ((byte1 == 0xD0 || byte1 == 0xD1) && (i + 1 < strlen_P(p))) {
            uint8_t byte2 = pgm_read_byte(p + i + 1);
            uint16_t utf8_word = (uint16_t)byte1 << 8 | byte2;

            bool found = false;
            CyrillicCharMap map;
            
            // Ищем символ в таблице
            for (int j = 0; j < NUM_CYR_CHARS; j++) {
                memcpy_P(&map, &cyrillic_map[j], sizeof(CyrillicCharMap));
                if (map.utf8_word == utf8_word) {
                    found = true;
                    // Вывод в нужной кодировке
                    if (currentEncoding == ENCODE_CP1251) {
                        Serial.write(map.cp1251_byte);
                    } else if (currentEncoding == ENCODE_CP866) {
                        Serial.write(map.cp866_byte);
                    } else {
                        // UTF-8 (по умолчанию)
                        Serial.write(byte1);
                        Serial.write(byte2);
                    }
                    i++; // Пропускаем следующий байт (он уже обработан)
                    break;
                }
            }
            if (!found) {
                 // Если не кириллица или не найдено, печатаем как есть (только первый байт)
                 Serial.write(byte1);
            }
        } else {
            // Однобайтные символы (ASCII 0x00-0x7F) и не кириллица
            Serial.write(byte1);
        }
    }
}


// -------------------------------------------------------------------
// 3. ОСНОВНОЙ КОД и ОБРАБОТЧИКИ
// -------------------------------------------------------------------

long currentBaudRate = 9600;

void setup() {
    Serial.begin(currentBaudRate);
    pinMode(LED_BUILTIN, OUTPUT);
    printEncodedString(F("UART Shell Ready. Type '?' for help.\n")); 
}

void loop() {
    if (Serial.available()) {
        String input = Serial.readStringUntil('\n');
        input.trim();
        if (input.length() > 0) {
            parseCommand(input);
        }
    }
}

void printHelp() {
    printPgmString(HELP_TEXT);
    Serial.println();
}

void printRegisters() {
    printPgmString(REGISTER_HEADER);
    Serial.println();
    for (int i = 0; i < NUM_REGISTERS; i++) {
        printPgmRegisterInfo(i);
    }
    printPgmString(REGISTER_FOOTER);
    Serial.println();
}

/**
 * @brief Переход в бутлоадер
 */
void enterBootloader(uint16_t boot_addr, int count, int delay_s) {
    // Мигание
    for (int i = 0; i < count; i++) {
        digitalWrite(LED_BUILTIN, HIGH);
        delay(100);
        digitalWrite(LED_BUILTIN, LOW);
        delay(100);
    }

    printEncodedString(F("Entering bootloader at address 0x"));
    
    if (boot_addr == 0) {
        boot_addr = BOOTLOADER_START;
        Serial.print(boot_addr, HEX);
        printEncodedString(F(" (Default) in "));
    } else {
        Serial.print(boot_addr, HEX);
        printEncodedString(F(" (Custom) in "));
    }
    
    Serial.print(delay_s);
    printEncodedString(F(" seconds...\n"));

    delay(delay_s * 1000);

    void (*bootloader_ptr)(void) = (void (*)(void))boot_addr;
    
    cli();
    bootloader_ptr();
}

void readRam(unsigned int addr) {
    if (addr > RAMEND) {
        printPgmString(ERR_RAM_BOUNDS);
        Serial.println();
        return;
    }
    uint8_t data = READ_RAM_BYTE(addr);
    Serial.print(F("RAM[0x"));
    Serial.print(addr, HEX);
    Serial.print(F("] = 0x"));
    Serial.println(data, HEX);
}

void readRamBit(unsigned int addr, int bitNum) {
    if (addr > RAMEND || bitNum < 0 || bitNum > 7) {
        printPgmString(ERR_BIT_INVALID);
        Serial.println();
        return;
    }
    uint8_t data = READ_RAM_BYTE(addr);
    uint8_t bitValue = (data >> bitNum) & 0x01;
    Serial.print(F("RAM[0x"));
    Serial.print(addr, HEX);
    Serial.print(F("].Bit"));
    Serial.print(bitNum, DEC);
    Serial.print(F(" = "));
    Serial.println(bitValue, DEC);
}

void writeRam(unsigned int addr, uint8_t data) {
    if (addr > RAMEND) {
        printPgmString(ERR_RAM_BOUNDS);
        Serial.println();
        return;
    }
    WRITE_RAM_BYTE(addr, data);
    printEncodedString(F("Wrote 0x"));
    Serial.print(data, HEX);
    printEncodedString(F(" to RAM[0x"));
    Serial.print(addr, HEX);
    printEncodedString(F("].\n"));
}

void readEeprom(unsigned int addr) {
    if (addr >= EEPROM.length()) {
        printPgmString(ERR_EEPROM_BOUNDS);
        Serial.println();
        return;
    }
    uint8_t data = EEPROM.read(addr);
    Serial.print(F("EEPROM[0x"));
    Serial.print(addr, HEX);
    Serial.print(F("] = 0x"));
    Serial.println(data, HEX);
}

void readEepromBit(unsigned int addr, int bitNum) {
    if (addr >= EEPROM.length() || bitNum < 0 || bitNum > 7) {
        printPgmString(ERR_BIT_INVALID);
        Serial.println();
        return;
    }
    uint8_t data = EEPROM.read(addr);
    uint8_t bitValue = (data >> bitNum) & 0x01;
    Serial.print(F("EEPROM[0x"));
    Serial.print(addr, HEX);
    Serial.print(F("].Bit"));
    Serial.print(bitNum, DEC);
    Serial.print(F(" = "));
    Serial.println(bitValue, DEC);
}

void writeEeprom(unsigned int addr, uint8_t data) {
    if (addr >= EEPROM.length()) {
        printPgmString(ERR_EEPROM_BOUNDS);
        Serial.println();
        return;
    }
    EEPROM.write(addr, data);
    printEncodedString(F("Wrote 0x"));
    Serial.print(data, HEX);
    printEncodedString(F(" to EEPROM[0x"));
    Serial.print(addr, HEX);
    printEncodedString(F("].\n"));
}

void clearEeprom() {
    printEncodedString(F("Clearing EEPROM... (May take a moment)\n"));
    for (int i = 0; i < EEPROM.length(); i++) {
        EEPROM.write(i, 0xFF); 
    }
    printEncodedString(F("EEPROM cleared to 0xFF.\n"));
}

void manipulateRamBit(unsigned int addr, int bitNum, char operation) {
    if (addr > RAMEND || bitNum < 0 || bitNum > 7) {
        printPgmString(ERR_BIT_INVALID);
        Serial.println();
        return;
    }
    
    uint8_t data = READ_RAM_BYTE(addr);
    uint8_t mask = 1 << bitNum;

    const __FlashStringHelper* op_msg;

    switch (operation) {
        case 's': // wrsb
            data |= mask;
            op_msg = F("Set bit (wrsb)");
            break;
        case 'r': // wrrb
            data &= ~mask;
            op_msg = F("Clear bit (wrrb)");
            break;
        case 'i': // wrib
            data ^= mask;
            op_msg = F("Invert bit (wrib)");
            break;
        case '|': // wr|b
            data |= mask;
            op_msg = F("Bitwise OR with 1 (wr|b)");
            break;
        case '&': // wr&b
            data &= ~mask;
            op_msg = F("Bitwise AND with 0 (wr&b)");
            break;
        default:
            printPgmString(ERR_INTERNAL_OP);
            Serial.println();
            return;
    }

    WRITE_RAM_BYTE(addr, data);
    printEncodedString(op_msg); 
    printEncodedString(F(" at RAM[0x"));
    Serial.print(addr, HEX);
    printEncodedString(F("]. Result: 0x"));
    Serial.println(data, HEX);
}

void binaryRamOp(unsigned int addr, uint8_t data, char operation) {
    if (addr > RAMEND) {
        printPgmString(ERR_RAM_BOUNDS);
        Serial.println();
        return;
    }

    uint8_t currentData = READ_RAM_BYTE(addr);
    const __FlashStringHelper* op_msg;
    
    switch (operation) {
        case '|': // wr|
            currentData |= data;
            op_msg = F("Bitwise OR (wr|)");
            break;
        case '&': // wr&
            currentData &= data;
            op_msg = F("Bitwise AND (wr&)");
            break;
        default:
            printPgmString(ERR_INTERNAL_OP);
            Serial.println();
            return;
    }

    WRITE_RAM_BYTE(addr, currentData);
    printEncodedString(op_msg);
    printEncodedString(F(" with 0x"));
    Serial.print(data, HEX);
    printEncodedString(F(" at RAM[0x"));
    Serial.print(addr, HEX);
    printEncodedString(F("]. New Value: 0x"));
    Serial.println(currentData, HEX);
}

void fillRamBlock(unsigned int startAddr, unsigned int len, uint8_t fillByte) {
    if (startAddr > RAMEND || startAddr + len > RAMEND || len == 0) {
        printPgmString(ERR_ADDR_LENGTH);
        Serial.println();
        return;
    }

    for (unsigned int i = 0; i < len; i++) {
        WRITE_RAM_BYTE(startAddr + i, fillByte);
    }
    
    printEncodedString(F("Filled RAM block from 0x"));
    Serial.print(startAddr, HEX);
    printEncodedString(F(" to 0x"));
    Serial.print(startAddr + len - 1, HEX);
    printEncodedString(F(" with 0x"));
    Serial.println(fillByte, HEX);
}

void readRamArray(unsigned int startAddr, unsigned int len) {
    if (startAddr > RAMEND || startAddr + len > RAMEND || len == 0) {
        printPgmString(ERR_ADDR_LENGTH);
        Serial.println();
        return;
    }

    Serial.print(F("RAM[0x"));
    Serial.print(startAddr, HEX);
    Serial.print(F("..0x"));
    Serial.print(startAddr + len - 1, HEX);
    printEncodedString(F("]:\n"));

    for (unsigned int i = 0; i < len; i++) {
        uint8_t data = READ_RAM_BYTE(startAddr + i);
        
        if (i > 0 && i % 16 == 0) {
            Serial.println();
            Serial.print(F("0x"));
            Serial.print(startAddr + i, HEX);
            Serial.print(F(": "));
        } else if (i % 16 == 0) {
            Serial.print(F("0x"));
            Serial.print(startAddr + i, HEX);
            Serial.print(F(": "));
        }

        if (data < 0x10) Serial.print('0');
        Serial.print(data, HEX);
        Serial.print(F(" "));
    }
    Serial.println();
}

void changeBaudRateWithRollback(long newBaudRate, bool rollback) {
    printEncodedString(F("Switching to "));
    Serial.print(newBaudRate);
    printEncodedString(F(" baud in 3 seconds. Send 'ok' to confirm.\n"));
    delay(3000);

    Serial.end();
    Serial.begin(newBaudRate);

    currentBaudRate = newBaudRate;
    printEncodedString(F("Switched to "));
    Serial.print(currentBaudRate);
    printEncodedString(F(" baud. (If you don't see this, reconnect with the new speed)\n"));

    if (rollback) {
        long startTime = millis();
        bool confirmed = false;
        
        // Блокирующий цикл ожидания подтверждения
        while (millis() - startTime < 5000) {
            if (Serial.available()) {
                String confirm = Serial.readStringUntil('\n');
                confirm.trim();
                if (confirm.equalsIgnoreCase("ok")) {
                    confirmed = true;
                    printEncodedString(F("Confirmed. Baud rate change permanent.\n"));
                    break;
                }
            }
        }
        
        if (!confirmed) {
            printEncodedString(F("Timeout. Rolling back to 9600 baud...\n"));
            delay(100);
            Serial.end();
            Serial.begin(9600);
            currentBaudRate = 9600;
            printEncodedString(F("Rolled back to 9600 baud.\n"));
        }
    }
}

/**
 * @brief Устанавливает кодировку для вывода.
 * @param encodeNum 0=UTF-8, 1=CP1251, 2=CP866
 */
void setEncoding(int encodeNum) {
    // Внутренняя проверка. Внешняя логика уже передала корректное число.
    if (encodeNum < 0 || encodeNum > 2) { 
        printPgmString(ERR_ENCODE);
        Serial.println();
        return;
    }
    
    currentEncoding = encodeNum;
    
    printEncodedString(F("Encoding set to: "));
    if (currentEncoding == ENCODE_UTF8) {
        printEncodedString(F("UTF-8 (cp0)"));
    } else if (currentEncoding == ENCODE_CP1251) {
        printEncodedString(F("Windows-1251 (cp1)"));
    } else {
        printEncodedString(F("DOS/CP866 (cp2)"));
    }
    Serial.println(); // Перевод строки
}

void parseCommand(String input) {
    // Разделение строки на слова
    char command[input.length() + 1];
    input.toCharArray(command, input.length() + 1);
    
    char *p = command;
    char *str;
    char *words[5] = {NULL}; // Максимум 5 слов: <cmd> <p1> <p2> <p3> <p4>
    int wordCount = 0;

    while ((str = strtok_r(p, " ,", &p))) {
        if (wordCount < 5) {
            words[wordCount++] = str;
        } else {
            break;
        }
    }

    if (wordCount == 0) return;

    // --- Параметры для команды boot/bootaddr ---
    uint16_t boot_addr = 0; // 0 - использовать адрес по умолчанию
    int blinks = 0;
    int delay_s = 0;

    // --- Параметры для команд памяти ---
    // Адрес (всегда первое числовое слово, кроме bootaddr)
    unsigned int addr = (wordCount > 1) ? (unsigned int)strtol(words[1], NULL, 16) : 0;
    int bitNum = 0; 
    uint8_t data = 0;
    unsigned int len = 0;


    // --- Обработка команд ---
    if (strcmp(words[0], "?") == 0) {
        printHelp();
    } else if (strcmp(words[0], "??") == 0) {
        printRegisters();
    } else if (strcmp(words[0], "boot") == 0) {
        // boot <blinks> <delay_s>
        if (wordCount == 3) {
            blinks = (int)strtol(words[1], NULL, 10);
            delay_s = (int)strtol(words[2], NULL, 10);
            if (blinks > 0 && delay_s > 0) {
                enterBootloader(0, blinks, delay_s); // 0 = адрес по умолчанию
            } else {
                printPgmString(ERR_BAUD_BLINKS);
                Serial.println();
            }
        } else {
            printPgmString(ERR_BAUD_USAGE);
            Serial.println();
        }
    } else if (strcmp(words[0], "boot5") == 0) {
        enterBootloader(0, 5, 5);
    } else if (strcmp(words[0], "bootaddr") == 0) {
        // bootaddr <addr> <blinks> <delay_s>
        if (wordCount == 4) {
            boot_addr = (uint16_t)strtol(words[1], NULL, 16);
            blinks = (int)strtol(words[2], NULL, 10);
            delay_s = (int)strtol(words[3], NULL, 10);
            
            if (boot_addr != 0 && blinks > 0 && delay_s > 0) {
                enterBootloader(boot_addr, blinks, delay_s);
            } else {
                printPgmString(ERR_ADDR_FORMAT);
                Serial.println();
            }
        } else {
            printPgmString(ERR_BOOTADDR_USAGE);
            Serial.println();
        }
    } else if (strcmp(words[0], "rd") == 0 && wordCount == 2) {
        readRam(addr);
    } else if (strcmp(words[0], "rdb") == 0 && wordCount == 3) {
        // Перечитываем bitNum, т.к. это третее слово в строке
        bitNum = (int)strtol(words[2], NULL, 10);
        readRamBit(addr, bitNum);
    } else if (strcmp(words[0], "erd") == 0 && wordCount == 2) {
        readEeprom(addr);
    } else if (strcmp(words[0], "erdb") == 0 && wordCount == 3) {
        // Перечитываем bitNum
        bitNum = (int)strtol(words[2], NULL, 10);
        readEepromBit(addr, bitNum);
    } else if (strcmp(words[0], "wr") == 0 && wordCount == 3) {
        // Перечитываем data
        data = (uint8_t)strtol(words[2], NULL, 16);
        writeRam(addr, data);
    } else if (strcmp(words[0], "ewr") == 0 && wordCount == 3) {
        // Перечитываем data
        data = (uint8_t)strtol(words[2], NULL, 16);
        writeEeprom(addr, data);
    } else if (strcmp(words[0], "clre") == 0) {
        clearEeprom();
    } else if (strcmp(words[0], "wrsb") == 0 && wordCount == 3) {
        // Перечитываем bitNum
        bitNum = (int)strtol(words[2], NULL, 10);
        manipulateRamBit(addr, bitNum, 's');
    } else if (strcmp(words[0], "wrrb") == 0 && wordCount == 3) {
        // Перечитываем bitNum
        bitNum = (int)strtol(words[2], NULL, 10);
        manipulateRamBit(addr, bitNum, 'r');
    } else if (strcmp(words[0], "wrib") == 0 && wordCount == 3) {
        // Перечитываем bitNum
        bitNum = (int)strtol(words[2], NULL, 10);
        manipulateRamBit(addr, bitNum, 'i');
    } else if (strcmp(words[0], "wr|b") == 0 && wordCount == 3) {
        // Перечитываем bitNum
        bitNum = (int)strtol(words[2], NULL, 10);
        manipulateRamBit(addr, bitNum, '|');
    } else if (strcmp(words[0], "wr&b") == 0 && wordCount == 3) {
        // Перечитываем bitNum
        bitNum = (int)strtol(words[2], NULL, 10);
        manipulateRamBit(addr, bitNum, '&');
    } else if (strcmp(words[0], "wr|") == 0 && wordCount == 3) {
        // Перечитываем data
        data = (uint8_t)strtol(words[2], NULL, 16);
        binaryRamOp(addr, data, '|');
    } else if (strcmp(words[0], "wr&") == 0 && wordCount == 3) {
        // Перечитываем data
        data = (uint8_t)strtol(words[2], NULL, 16);
        binaryRamOp(addr, data, '&');
    } else if (strcmp(words[0], "wr1") == 0 && wordCount == 3) {
        // Перечитываем len
        len = (unsigned int)strtol(words[2], NULL, 10);
        fillRamBlock(addr, len, 0xFF);
    } else if (strcmp(words[0], "wr0") == 0 && wordCount == 3) {
        // Перечитываем len
        len = (unsigned int)strtol(words[2], NULL, 10);
        fillRamBlock(addr, len, 0x00);
    } else if (strcmp(words[0], "rda") == 0 && wordCount == 3) {
        // Перечитываем len
        len = (unsigned int)strtol(words[2], NULL, 10);
        readRamArray(addr, len);
    } else if (strcmp(words[0], "boud9600") == 0) {
        changeBaudRateWithRollback(9600, false);
    } else if (strcmp(words[0], "boud19200") == 0) {
        changeBaudRateWithRollback(19200, true);
    } else if (strcmp(words[0], "cp0") == 0) { // <-- НОВАЯ КОМАНДА cp0
        setEncoding(ENCODE_UTF8);
    } else if (strcmp(words[0], "cp1") == 0) { // <-- НОВАЯ КОМАНДА cp1
        setEncoding(ENCODE_CP1251);
    } else if (strcmp(words[0], "cp2") == 0) { // <-- НОВАЯ КОМАНДА cp2
        setEncoding(ENCODE_CP866);
    } else {
        Serial.print(F("Unknown command: "));
        Serial.println(input);
        printEncodedString(F("Type '?' for help.\n"));
    }
}
 
Изменено: