//KTR-40C3用 Arduino pro mini(5V/16MHz) 2025年6月11日 // VFO with Si5351A and OLED display for CW_Transceiver // Version 2.4.16 (VFO制御とアイアンビックB・手動電鍵統合版) #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 = 20; //Sメーターの書換え周期(mS) 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 A1 // スピードコントロール用ポテンショメータ入力 // --- キーヤー定数 --- 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 = 3; // 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.setFont(font5x7); oled.setCursor(0, 4); //5行目 oled.write("1__3__5__7_9+20+40+60"); //Sメータ用 // oled.setCursor(0, 7); //8行目 // oled.write("Po___L____L____L____L"); //送信インジケータラベル 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がアクティブであることをマーク // 送信用ダミーのインジケータ※アナログポートの干渉対策を思いつくまでの仮の設定 oled.setFont(font5x7); oled.setCursor(0, 6); //意図的にSメーターとは違う行に表示 if (digitalRead(TX_ON) == HIGH) { oled.write("_Transmitting_"); //送信インジケータとして代替表示 } /* else { oled.setCursor(0, 6); oled.clearToEOL(); //強制的に消す。空白で消すより合理的 } */ } 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にしない (遅延させるため) } oled.setCursor(0, 6); oled.clearToEOL(); //強制的に消す。空白で消すより合理的 // 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; analogRead(A0); // ダミー int sensorValue = analogRead(A0); // 以下に5Vを1024段階で計測するとして逆算する const int MIN_ADC = 190; //0.93V +60dB フルスケール位置 const int MAX_ADC = 588; //2.87V メーター0位置 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を更新するロジックを持つ。 } //---バーグラフを1個飛びで表示 void drawBar(int currentBarLength) { oled.setFont(font5x7); int targetDisplayLength = currentBarLength; int previousDisplayLength = previousBarLength; if (targetDisplayLength > previousDisplayLength) { for (int i = previousDisplayLength; i < targetDisplayLength; i++) { oled.setCursor(i, 5); // カーソルはそのまま i if (i % 2 == 0) { // 偶数番目の位置には 'L' oled.write("L"); } else { // 奇数番目の位置には ' ' (スペース) oled.write(" "); } } } else if (targetDisplayLength < previousDisplayLength) { for (int i = targetDisplayLength; i < previousDisplayLength; i++) { oled.setCursor(i, 5); // カーソルはそのまま i oled.write(" "); // すべてスペースで消去 } } } 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; 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 割り込みを有効化 } //2025/6/10 アイアンビックBの動作を模倣してみた // Timer1 割り込みサービスルーチン (ISR) ISR(TIMER1_COMPA_vect) { currentTickCount++; // 現在のON/OFF期間のティック数をインクリメント // Iambic B のロジックを適用 BufferedSymbol nextSymbolFromKeyerInput = NONE_BUFFERED; // 現在、いずれかのパドルが押されているか bool anyPaddlePressed = dotPaddleState || dashPaddleState; if (currentKeyerState == KEYER_IDLE || currentKeyerState == KEYER_SENDING_OFF) { // IDLE または OFF期間中に新しい符号を決定 if (dotPaddleState && dashPaddleState) { // 両方のパドルが押されている場合 (Iambic B) if (currentlySendingSymbol == DASH_SENDING) { nextSymbolFromKeyerInput = DOT_BUFFERED; } else { nextSymbolFromKeyerInput = DASH_BUFFERED; } } else if (dotPaddleState) { nextSymbolFromKeyerInput = DOT_BUFFERED; } else if (dashPaddleState) { nextSymbolFromKeyerInput = DASH_BUFFERED; } } else if (currentKeyerState == KEYER_SENDING_ON) { // ON期間中もパドル入力をチェックし、先打ちバッファリング if (currentlySendingSymbol == DOT_SENDING && dashPaddleState && !dotPaddleState) { bufferedSymbol = DASH_BUFFERED; } else if (currentlySendingSymbol == DASH_SENDING && dotPaddleState && !dashPaddleState) { bufferedSymbol = DOT_BUFFERED; } } // バッファに符号があればそれを優先 if (bufferedSymbol != NONE_BUFFERED) { 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; // 符号間スペース } break; case KEYER_SENDING_OFF: // OFF期間(符号素間スペース)が終了したら if (currentTickCount >= requiredTicks) { // 次に送出する符号があれば、そのON期間を開始 if (nextSymbolFromKeyerInput != NONE_BUFFERED) { 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; } }