//KTR-40C3用 Arduino pro mini(5V/16MHz) 2025年6月4日
// VFO with Si5351A and OLED display for CW_Transceiver
// Version 2.4.14 (VFO制御とスクイーズキーヤー・手動電鍵統合版)
#include 
#include   //https://github.com/greiman/SSD1306Ascii
#include   // https://github.com/buxtronix/arduino/tree/master/libraries/Rotary
#include 
#include 
#include 

// --- Global setup ---
const unsigned long DEFAULT_FREQ = 19796940UL;
const int EEPROM_ADDR = 0;
unsigned long lastFreqWriteTime = 0;
bool freqChanged = false;
bool freqWritten = true;
unsigned long eeprom_cached_FREQ; // ★ 追加: EEPROMに書き込む予定の周波数を一時的に保持する変数

// TX/RX 状態遷移管理
unsigned long txDelayStartTime = 0; // 送信開始待ち時間計測用
bool txTransitionActive = false;    // 送信への移行中フラグ
bool txActive = false;              // VFO側がTX状態(RF出力ON)であることを示すフラグ

unsigned long rxDelayStartTime = 0; // 受信開始待ち時間計測用
bool rxTransitionActive = false;    // 受信への移行中フラグ

// Loop interval control for bar graph
unsigned long previousMillis = 0;
const long interval = 100;
int previousBarLength = -1;

// OLED and encoder
SSD1306AsciiAvrI2c oled;
Rotary r = Rotary(2, 3);

// Si5351A definitions
#define Si5351A_ADDR 0x60
#define MSNA_ADDR 26
#define MSNB_ADDR 34
#define MS0_ADDR 42
#define MS1_ADDR 50
#define MS2_ADDR 58
#define CLK0_CTRL 16
#define CLK1_CTRL 17
#define CLK2_CTRL 18
#define OUTPUT_CTRL 3
#define XTAL_LOAD_C 183
#define PLL_RESET 177

// I/O pins
#define SW_STEP 5
#define TX_SW 9 // 手動電鍵およびキーヤーからの送信制御入力
#define TX_ON 7
#define AF_mute 8 // AFミュート制御

// Frequency setup
const long LOW_FREQ = 19796940L;
const long HI_FREQ = 19996940L;
const long IF_FREQ = 12796940L;
unsigned long FREQ = DEFAULT_FREQ;
unsigned long FREQ_OLD = FREQ;
unsigned long txFreq_OLD = 0;
int dF = 120;
int RX_dF = 340;
int STEP = 100;

// --- キーヤーピンアサイン ---
#define DOT_PIN 11  // 短点パドル入力 (INPUT_PULLUP)
#define DASH_PIN 12 // 長点パドル入力 (INPUT_PULLUP)
#define SIDETONE_PIN 10     // サイドトーン出力 (D10)
#define SPEED_POT_PIN A6    // スピードコントロール用ポテンショメータ入力

// --- キーヤー定数 ---
const int MIN_WPM = 12;
const int MAX_WPM = 35;
const int SIDETONE_FREQ = 800; // サイドトーン周波数 (Hz)
const unsigned long DEBOUNCE_DELAY = 10; // パドル入力のデバウンス時間 (ms)
const unsigned long TX_ON_DELAY_MS = 3; // clk1 オンからTX_ON ONまでの遅延 (ms)
const unsigned long RX_ON_DELAY_MS = 5; // TX_ON OFFからAF_mute 解除までの遅延 (ms)


// --- キーヤー グローバル変数 ---
volatile int currentWpm = 15;
volatile unsigned long ditTimeMs = 0;
volatile unsigned long dahTimeMs = 0;
volatile unsigned long elementSpaceTimeMs = 0;

// キーヤーの内部動作状態 (割り込み内で更新されるので volatile)
enum KeyerState {
KEYER_IDLE,  // アイドル状態 (TX OFF)
KEYER_SENDING_ON,  // 符号のON期間送出中 (TX ON)
KEYER_SENDING_OFF  // 符号間のOFF期間送出中 (TX OFF)
};
volatile KeyerState currentKeyerState = KEYER_IDLE;

// 現在送出している符号のタイプ (ISRで更新、loopで参照)
enum CurrentlySendingSymbol {
NONE_SENDING,
DOT_SENDING,
DASH_SENDING
};
volatile CurrentlySendingSymbol currentlySendingSymbol = NONE_SENDING;

// 次に送出する符号のタイプ (メモリに記憶された符号) (volatile)
enum BufferedSymbol {
NONE_BUFFERED,
DOT_BUFFERED,
DASH_BUFFERED
};
volatile BufferedSymbol bufferedSymbol = NONE_BUFFERED; // 1つだけバッファする符号

// 割り込みハンドラ内でカウントするティック数
volatile unsigned long currentTickCount = 0;
volatile unsigned long requiredTicks = 0;

// パドル入力の状態 (デバウンス後) - volatileを追加
volatile bool dotPaddleState = false;
volatile bool dashPaddleState = false;

// デバウンス処理のための内部変数
static unsigned long lastDotReadTime = 0;
static unsigned long lastDashReadTime = 0;
static bool rawDotState = false;
static bool rawDashState = false;
static int lastPotValue = -1; // ポテンショメータの値を監視するための変数

// TX_SW (手動電鍵) の状態を保持する変数
volatile bool manualTxSwitchPressed = false; // ISRからも参照される可能性があるのでvolatileに
unsigned long lastManualTxReadTime = 0;
// Note: rawManualTxStateはloop内でローカル変数として処理する
const unsigned long MANUAL_TX_DEBOUNCE_DELAY = 10; // 手動電鍵のデバウンス

// --- 関数プロトタイプ (Si5351A / OLED / VFO) ---
void rotary_encoder();
void Fnc_Stp();
void drawBar(int currentBarLength);
void Set_CLK_Enable(uint8_t clk0, uint8_t clk1, uint8_t clk2);
void Freq_Disp(unsigned long Fre);
void Step_Disp(int Stp);
void Si5351_write(byte Reg, byte Data);
void Si5351_init();
void PLL_Set(char pll, uint32_t freq);
void MS_Set(uint8_t clk_no);
void Parameter_write(uint8_t REG_ADDR, uint32_t Pa1, uint32_t Pa2, uint32_t Pa3);

// --- 関数プロトタイプ (キーヤー) ---
void updateKeyerTiming();
void processPaddleInput();
void setTxOnState(bool on); // TX_ON制御を専用の関数に統一
void setupTimer1();

// --- Setup ---
void setup() {
    // Rotary Encoder setup
    r.begin();
    attachInterrupt(0, rotary_encoder, CHANGE);
    attachInterrupt(1, rotary_encoder, CHANGE);

    // Pin Modes
    pinMode(SW_STEP, INPUT_PULLUP);
    pinMode(TX_SW, INPUT_PULLUP); // TX_SW (手動電鍵 & キーヤーからの制御用)
    pinMode(TX_ON, OUTPUT);
    pinMode(AF_mute, OUTPUT);

    // Keyer related pin modes
    pinMode(DOT_PIN, INPUT_PULLUP);
    pinMode(DASH_PIN, INPUT_PULLUP);
    pinMode(SIDETONE_PIN, OUTPUT); // サイドトーン出力ピンを設定
    pinMode(SPEED_POT_PIN, INPUT);

    // Initial output states
    setTxOnState(false);    // TX_ONを初期オフ (専用関数経由)
    digitalWrite(AF_mute, HIGH);    // AF_muteを(受信状態)

    // EEPROM read for frequency
    EEPROM.get(EEPROM_ADDR, FREQ);
    if (FREQ < LOW_FREQ || FREQ > HI_FREQ) FREQ = DEFAULT_FREQ;
    FREQ_OLD = FREQ;
    eeprom_cached_FREQ = FREQ; // ★ 追加: 初期値を同期

    oled.begin(&Adafruit128x64, 0x3C); //0.96インチ128x64用、1.3インチは(&SH1106_128x64, 0x3C)に変更

    Si5351_init();

    // Initial PLL/MS settings for RX and TX
    PLL_Set('A', FREQ + RX_dF);
    MS_Set(0); // CLK0 (受信) のマルチシンセサイザ設定

    unsigned long initialTxFreq = FREQ + dF - IF_FREQ;
    PLL_Set('B', initialTxFreq);
    MS_Set(1); // CLK1 (送信) のマルチシンセサイザ設定
    txFreq_OLD = initialTxFreq; // 初期送信周波数を記録
    Si5351_write(CLK1_CTRL, 0x6D); //CLK1を4mAにおまじない

    // Display initial frequency and step
    Freq_Disp(FREQ);
    Step_Disp(STEP);

    // OLED bar graph label
    oled.setCursor(0, 4);
    oled.setFont(font5x7);
    oled.write("1__3__5__7_9+20+40+60");

    Set_CLK_Enable(1, 0, 0);    // 初期はCLK0 (受信) のみ有効
    
    // --- キーヤー初期化 ---
    updateKeyerTiming();    // 初期スピードの計算とタイミング変数の設定
    setupTimer1();          // Timer1割り込みを設定 (1ms周期)

    // グローバル割り込みを有効化 (setupTimer1でも行うが念のため)
    sei();
}

// --- Loop関数 ---
void loop() {
    unsigned long now = millis();

    if (digitalRead(SW_STEP) == LOW) Fnc_Stp();

    // 手動電鍵のデバウンス処理
    bool currentRawManualTx = (digitalRead(TX_SW) == LOW);
    // rawManualTxStateはここでローカル変数として定義
    static bool rawManualTxState = false; // staticにして値を保持

    if (currentRawManualTx != rawManualTxState) {
        lastManualTxReadTime = now;
        rawManualTxState = currentRawManualTx;
    }
    if (now - lastManualTxReadTime > MANUAL_TX_DEBOUNCE_DELAY) {
        manualTxSwitchPressed = rawManualTxState; // volatile変数に最終状態を書き込み
    }

    // --- TX/RX 状態遷移処理 (VFO側とキーヤー側の両方から制御を統合) ---
    // 手動電鍵が押されていれば、キーヤーの状態にかかわらず送信要求を優先する
    bool requestTx = manualTxSwitchPressed || (currentKeyerState != KEYER_IDLE);

    if (requestTx) {
        if (!txTransitionActive) {
            // 受信から送信への移行開始
            txDelayStartTime = now;
            txTransitionActive = true;
            rxTransitionActive = false; // 受信への移行をリセット

            digitalWrite(AF_mute, LOW); // AFミュート解除

            // 送信周波数に切り替え
            Set_CLK_Enable(0, 1, 0); // CLK1 (送信) を有効
        }

        // TX_ONはキーヤーISRと手動電鍵の状態に基づいて、setTxOnState()で制御される
        // ここでは直接digitalWrite(TX_ON)はしない
        txActive = true; // TXがアクティブであることをマーク
    } else { // 送信要求がない場合 (キーヤーも手動電鍵もTXを要求していない)
        if (!rxTransitionActive) {
            rxDelayStartTime = now;
            rxTransitionActive = true;
            txTransitionActive = false; // 送信への移行をリセット

            setTxOnState(false); // 送信要求がなくなった時点でTX_ONをLOWにする
            txActive = false; // TX非アクティブをマーク

            // TX_ONがオフになったら直ちにRX周波数に切り替え
            Set_CLK_Enable(1, 0, 0); // CLK0 (受信) を有効
            // AF_muteはまだONにしない (遅延させるため)
        }

        // RX_ON_DELAY_MS の遅延後にAF_muteをONにする
        if (rxTransitionActive && (now - rxDelayStartTime >= RX_ON_DELAY_MS)) {
            digitalWrite(AF_mute, HIGH); // AFミュートオン
            rxTransitionActive = false; // 受信への移行完了
        }
    }

    // --- 周波数変更検出 ---
    if (FREQ != FREQ_OLD) {
        PLL_Set('A', FREQ + RX_dF);
        MS_Set(0); // CLK0 (受信) に紐づくマルチシンセサイザ

        unsigned long txFreq_new = FREQ + dF - IF_FREQ;
        PLL_Set('B', txFreq_new);
        MS_Set(1); // CLK1 (送信) に紐づくマルチシンセサイザ
        txFreq_OLD = txFreq_new; // 送信周波数を更新

        Freq_Disp(FREQ);
        FREQ_OLD = FREQ;
        freqChanged = true;
        lastFreqWriteTime = now;
        freqWritten = false;
        eeprom_cached_FREQ = FREQ; // ★ 修正: EEPROMに書き込む予定の値を更新
    }

    // --- バーグラフ ---
    if (now - previousMillis >= interval) {
        previousMillis = now;
        int sensorValue = analogRead(A0);
        const int MIN_ADC = 190;
        const int MAX_ADC = 588;
        sensorValue = constrain(sensorValue, MIN_ADC, MAX_ADC);
        int currentBarLength = map(sensorValue, MAX_ADC, MIN_ADC, 0, 128);

        if (currentBarLength != previousBarLength) {
            drawBar(currentBarLength);
            previousBarLength = currentBarLength;
        }
    }

    // --- EEPROM書き込み処理 ---
    if (freqChanged && !freqWritten && (now - lastFreqWriteTime >= 2000)) {
        unsigned long storedFreq;
        EEPROM.get(EEPROM_ADDR, storedFreq);
        if (storedFreq != eeprom_cached_FREQ) { // ★ 修正: 保持している値と比較
            EEPROM.put(EEPROM_ADDR, eeprom_cached_FREQ); // ★ 修正: 保持している値を書き込み
        }
        freqWritten = true;
        freqChanged = false;
    }

    // --- キーヤー関連処理 ---
    updateKeyerTiming();    // WPMを常に更新し、それに伴うタイミング変数を再計算
    processPaddleInput();   // パドル入力を処理 (デバウンスと状態更新)

    // サイドトーン制御: TX_ONピンの状態に連動させる
    if (digitalRead(TX_ON) == HIGH) { // TX_ONがHIGHの時
        tone(SIDETONE_PIN, SIDETONE_FREQ);
    } else { // TX_ONがLOWの時
        noTone(SIDETONE_PIN);
    }

    // ここではbufferedSymbolを直接設定せず、ISRに任せる。
    // ISRがdotPaddleState/dashPaddleStateとcurrentKeyerState/currentlySendingSymbolを見て
    // 適切にbufferedSymbolを更新するロジックを持つ。
}

void drawBar(int currentBarLength) {
    oled.setFont(font5x7);

    if (currentBarLength > previousBarLength) {
        for (int i = previousBarLength; i < currentBarLength; i++) {
            oled.setCursor(i, 5);
            oled.print("L");
        }
    } else if (currentBarLength < previousBarLength) {
        for (int i = currentBarLength; i < previousBarLength; i++) {
            oled.setCursor(i, 5);
            oled.print(" ");
        }
    }
}

void Set_CLK_Enable(uint8_t clk0, uint8_t clk1, uint8_t clk2) {
    uint8_t val = 0;
    if (!clk0) val |= (1 << 0);
    if (!clk1) val |= (1 << 1);
    if (!clk2) val |= (1 << 2);
    Si5351_write(OUTPUT_CTRL, val);
}

void Freq_Disp(unsigned long Fre) {
    unsigned long displayFreq = Fre - IF_FREQ;
    /* string方式は止めることにした
    String freqt = String(displayFreq);
    oled.setFont(fixednums8x16);
    oled.setCursor(8, 1);
    oled.print(freqt.substring(0, 1) + "." + freqt.substring(1, 4) + "." + freqt.substring(4));
    */
    char buf[12];
    sprintf(buf, "%lu", displayFreq);
    oled.setFont(fixednums8x16);
    oled.setCursor(8, 1);
    oled.print(buf[0]); oled.print(".");
    oled.print(buf[1]); oled.print(buf[2]); oled.print(buf[3]); oled.print(".");
    oled.print(buf + 4);
}

void Step_Disp(int Stp) {
    oled.setFont(font5x7);
    oled.setCursor(100, 2);
    oled.print((Stp == 1000) ? " 1K" : (Stp == 100) ? "100" : " 10");
}

void Si5351_write(byte Reg, byte Data) {
    Wire.beginTransmission(Si5351A_ADDR);
    Wire.write(Reg);
    Wire.write(Data);
    Wire.endTransmission();
//    delayMicroseconds(10);
}

//Registerの設定は要注意
void Si5351_init() {
    Si5351_write(OUTPUT_CTRL, 0xFF);   // All outputs disabled
    Si5351_write(CLK0_CTRL, 0x80);     // Power down CLK0
    Si5351_write(CLK1_CTRL, 0x80);     // Power down CLK1
    Si5351_write(CLK2_CTRL, 0x80);     // Power down CLK2
    Si5351_write(XTAL_LOAD_C, 0x92);   // Set crystal load capacitance
    Si5351_write(PLL_RESET, 0xA0);     // Reset all PLLs
    Si5351_write(CLK0_CTRL, 0x4D);     // CLK0: PLLA, 4mA, (RX)
    Si5351_write(CLK1_CTRL, 0x6D);     // CLK1: PLLB, 4mA, (TX)
    Si5351_write(CLK2_CTRL, 0x6C);     // CLK2: PLLB, 2mA, (使わないから何でもいい)
    Si5351_write(OUTPUT_CTRL, 0xFC);   // Enable CLK0 CLK1 at init
}

void PLL_Set(char pll, uint32_t freq) {
    uint8_t addr = (pll == 'A') ? MSNA_ADDR : MSNB_ADDR;
    uint64_t pll_freq = (uint64_t)freq * 32;
    uint32_t mult = pll_freq / 25000000;
    uint32_t l = pll_freq % 25000000;
    uint32_t num = (uint64_t)l * 1048575 / 25000000;
    uint32_t denom = 1048575;
    uint32_t P1 = 128 * mult + ((128 * num) / denom) - 512;
    uint32_t P2 = (128 * num) % denom;
    uint32_t P3 = denom;
    Parameter_write(addr, P1, P2, P3);
}

void MS_Set(uint8_t clk_no) {
    uint8_t addr;
    if (clk_no == 0) addr = MS0_ADDR;
    else if (clk_no == 1) addr = MS1_ADDR;
    else addr = MS2_ADDR;

    uint32_t P1 = 128 * 32 - 512; // Divide by 32
    uint32_t P2 = 0;
    uint32_t P3 = 1;
    Parameter_write(addr, P1, P2, P3);
}

void Parameter_write(uint8_t REG_ADDR, uint32_t Pa1, uint32_t Pa2, uint32_t Pa3) {
    Si5351_write(REG_ADDR,      (Pa3 >> 8) & 0xFF);
    Si5351_write(REG_ADDR + 1, Pa3 & 0xFF);
    Si5351_write(REG_ADDR + 2, (Pa1 >> 16) & 0x03);
    Si5351_write(REG_ADDR + 3, (Pa1 >> 8) & 0xFF);
    Si5351_write(REG_ADDR + 4, Pa1 & 0xFF);
    Si5351_write(REG_ADDR + 5, ((Pa3 >> 12) & 0xF0) | ((Pa2 >> 16) & 0x0F));
    Si5351_write(REG_ADDR + 6, (Pa2 >> 8) & 0xFF);
    Si5351_write(REG_ADDR + 7, Pa2 & 0xFF);
}

// --- 補助関数 (キーヤー関連) ---

// Rotary encoder ISR (ISR内でmillis()は使用しない)
void rotary_encoder() {
    unsigned char result = r.process();
    if (result) {
        FREQ += (result == DIR_CW) ? STEP : -STEP;
        FREQ = constrain(FREQ, LOW_FREQ, HI_FREQ);
        freqChanged = true; // millis()の呼び出しを避ける
    }
}

void Fnc_Stp() {
    STEP = (STEP == 10) ? 1000 : ((STEP == 1000) ? 100 : 10); // 10 -> 1000 -> 100 -> 10 の順に切り替え
    delay(10);
    Step_Disp(STEP);
    while (digitalRead(SW_STEP) == LOW) delay(10); // ボタンが離れるまで待機
}

// WPMに基づいてキーヤーのタイミング変数を更新する関数
void updateKeyerTiming() {
    int sensorValue = analogRead(SPEED_POT_PIN);
    int newWpm = map(sensorValue, 0, 1023, MIN_WPM, MAX_WPM);

    if (newWpm != currentWpm || lastPotValue == -1) {
        currentWpm = newWpm;
        if (currentWpm == 0) currentWpm = MIN_WPM;

        ditTimeMs = 1200L / currentWpm;
        dahTimeMs = ditTimeMs * 3;
        elementSpaceTimeMs = ditTimeMs * 1;
    }
    lastPotValue = sensorValue;
}

// パドル入力のデバウンス処理と状態更新
void processPaddleInput() {
    unsigned long now = millis();

    bool currentRawDot = !digitalRead(DOT_PIN);
    if (currentRawDot != rawDotState) {
        lastDotReadTime = now;
        rawDotState = currentRawDot;
    }
    if (now - lastDotReadTime > DEBOUNCE_DELAY) {
        dotPaddleState = rawDotState;
    }

    bool currentRawDash = !digitalRead(DASH_PIN);
    if (currentRawDash != rawDashState) {
        lastDashReadTime = now;
        rawDashState = currentRawDash;
    }
    if (now - lastDashReadTime > DEBOUNCE_DELAY) {
        dashPaddleState = rawDashState;
    }
}

// TX_ON制御を行うヘルパー関数 - ISRからも呼ばれる
void setTxOnState(bool on) {
    if (on) {
        digitalWrite(TX_ON, HIGH);
    } else {
        digitalWrite(TX_ON, LOW);
    }
}

// Timer1をセットアップする関数 (1ミリ秒ごとに割り込み)
void setupTimer1() {
    TCCR1A = 0;
    TCCR1B = 0;
    TCCR1B |= (1 << WGM12); // CTCモード
    TCCR1B |= (1 << CS11);  // プリスケーラを8に設定 (16MHz / 8 = 2MHz)
    OCR1A = 1999;           // 1ms周期 (0から1999までカウント)
    TIMSK1 |= (1 << OCIE1A); // Timer1 Compare Match A 割り込みを有効化
}

// Timer1 割り込みサービスルーチン (ISR)
ISR(TIMER1_COMPA_vect) {
    currentTickCount++; // 現在のON/OFF期間のティック数をインクリメント

    // キーヤーの現在の状態とパドル入力に基づいて次に送る符号を決定
    // manualTxSwitchPressed が優先されるため、ここではキーヤーの符号決定に集中
    BufferedSymbol nextSymbolFromKeyerInput = NONE_BUFFERED;

    // バッファが空の場合、リアルタイム入力を確認
    if (bufferedSymbol == NONE_BUFFERED) {
        if (dotPaddleState) {
            nextSymbolFromKeyerInput = DOT_BUFFERED;
        } else if (dashPaddleState) {
            nextSymbolFromKeyerInput = DASH_BUFFERED;
        }
    } else {
        // バッファに符号がある場合はそれを優先
        nextSymbolFromKeyerInput = bufferedSymbol;
    }

    switch (currentKeyerState) {
        case KEYER_IDLE:
            currentlySendingSymbol = NONE_SENDING; // IDLEなので送出符号なし
            // アイドル時は manualTxSwitchPressed またはキーヤーの符号入力があればTX_ONをHIGHにする
            if (manualTxSwitchPressed || nextSymbolFromKeyerInput != NONE_BUFFERED) {
                setTxOnState(true); // TX_ONをHIGHにする
                // キーヤーが符号を送出する場合のみ、状態を遷移
                if (nextSymbolFromKeyerInput != NONE_BUFFERED) {
                    currentTickCount = 0;   // ティックカウントをリセット
                    currentKeyerState = KEYER_SENDING_ON;

                    // 次に送出すべきON期間のティック数を設定
                    if (nextSymbolFromKeyerInput == DOT_BUFFERED) {
                        requiredTicks = ditTimeMs;
                        currentlySendingSymbol = DOT_SENDING;
                    } else if (nextSymbolFromKeyerInput == DASH_BUFFERED) {
                        requiredTicks = dahTimeMs;
                        currentlySendingSymbol = DASH_SENDING;
                    }
                    bufferedSymbol = NONE_BUFFERED; // 送出を開始したらバッファをクリア
                }
            } else {
                setTxOnState(false); // 送信要求がなければTX_ONをLOWにする
            }
            break;

        case KEYER_SENDING_ON:
            // ON期間が終了したら、OFF期間へ移行
            if (currentTickCount >= requiredTicks) {
                setTxOnState(false);    // TX_ONをLOWにする
                currentTickCount = 0;   // ティックカウントをリセット
                currentKeyerState = KEYER_SENDING_OFF;
                requiredTicks = elementSpaceTimeMs; // 符号間スペース

                // 先打ちロジック: 現在送出中の符号と異なるパドルが押されたらバッファ
                if (currentlySendingSymbol == DOT_SENDING && dashPaddleState && !dotPaddleState) {
                    bufferedSymbol = DASH_BUFFERED;
                } else if (currentlySendingSymbol == DASH_SENDING && dotPaddleState && !dashPaddleState) {
                    bufferedSymbol = DOT_BUFFERED;
                }
            }
            break;

        case KEYER_SENDING_OFF:
            // OFF期間(符号素間スペース)が終了したら
            if (currentTickCount >= requiredTicks) {
                // 次に送出する符号があれば、そのON期間を開始
                if (nextSymbolFromKeyerInput != NONE_BUFFERED) { // bufferedSymbolに加えてリアルタイム入力も考慮
                    setTxOnState(true); // TX_ONをHIGHにする
                    currentTickCount = 0; // ティックカウントをリセット
                    currentKeyerState = KEYER_SENDING_ON;

                    // 次に送出すべきON期間のティック数を設定
                    if (nextSymbolFromKeyerInput == DOT_BUFFERED) {
                        requiredTicks = ditTimeMs;
                        currentlySendingSymbol = DOT_SENDING;
                    } else if (nextSymbolFromKeyerInput == DASH_BUFFERED) {
                        requiredTicks = dahTimeMs;
                        currentlySendingSymbol = DASH_SENDING;
                    }
                    bufferedSymbol = NONE_BUFFERED; // 送出を開始したらバッファをクリア
                } else if (manualTxSwitchPressed) {
                    // キーヤーからの符号入力がなくても、手動電鍵が押されていればTXを維持
                    setTxOnState(true);
                    currentlySendingSymbol = NONE_SENDING; // キーヤーは符号を出していない
                    currentKeyerState = KEYER_IDLE; // キーヤー自体はアイドル状態に戻す
                }
                else {
                    // バッファもリアルタイム入力もなければ、IDLE状態に戻る
                    currentlySendingSymbol = NONE_SENDING;
                    currentKeyerState = KEYER_IDLE;
                    bufferedSymbol = NONE_BUFFERED; // IDLEに戻る際もバッファをクリア
                }
            }
            break;
    }
}