/* ================================================================================ 多機能インターフェース実験キット for Arduino Nano / Uno © 2025 JH4VAJ ================================================================================ ■概要 スライドスイッチ(D12~D9, MSB→LSB, 4bit値)でLED点滅やエンコーダLED表示、サウンダ発音など各種モードを切替え。 ■ピンアサイン D2 : ロータリエンコーダA D3 : ロータリエンコーダB D8~D4 : LED5本(D8=MSB, D4=LSB) D12~D9 : スライドスイッチ4bit(D12=MSB, D9=LSB) D13 : ビルトインLED(ハートビート - 1 2 3 4の点滅を繰返し) A0 : プッシュスイッチ A1 : 圧電サウンダ A2 : 可変抵抗 ■モード一覧(スライドスイッチ4bit値) 0b0000 : プッシュスイッチを押している間のみ全LED単純点滅 0b0001 : PATTERN配列に従ったパターンで常時点滅 0b0010 : 可変抵抗で調整した間隔で全LED点滅 0b0100 : エンコーダ値を4bit+符号でLED表示(D8=符号、負で点灯)、 このモードに切替えたタイミングでカウンタを0クリア 0b1000 : 圧電サウンダでThe Entertainerジングルを発音、LED消灯 その他 : LED全消灯・サウンダ停止 ■補足 - スライドスイッチまたはエンコーダ値が変化したとき、シリアルに 「SW: 0001 Enc: -5」のような形式で1行表示する(起動時含む) - ロータリエンコーダ値は常時シリアルモニタに出力 - 動作中はArduino内蔵LEDを所定のパターンで点滅(生存確認) ================================================================================ */ // ==== ピン・配列定義 ==== // ロータリエンコーダ const int ENC_A = 2; const int ENC_B = 3; // LED(MSB→LSB: D8~D4) const int LED_PINS[] = {8, 7, 6, 5, 4}; const int NUM_LEDS = sizeof(LED_PINS)/sizeof(LED_PINS[0]); // スライドスイッチ4bit(D12=MSB, D9=LSB) const int SWITCH_PINS[] = {12, 11, 10, 9}; const int NUM_SWITCHES = sizeof(SWITCH_PINS)/sizeof(SWITCH_PINS[0]); const int HEARTBEAT_LED = 13; // ビルトインLED const int PUSH_SW = A0; // プッシュスイッチ const int BUZZER_PIN = A1; // 圧電サウンダ const int POT_PIN = A2; // 可変抵抗 // ==== The Entertainer メロディ定義 ==== // 2/4拍子、テンポ=80BPM(4分=750ms) const unsigned int entertainer_melody[] = { 0, 0, 1175, 1244, // R R D6 D#6 1318,2093,1318,2093,1318,2093, // E6 C7 E6 C7 E6 C7 2093,2093,2093,2349,2489, // C7 C7 C7 D7 D#7 2637,2093,2349,2637,2637,1976,2349, // E7 C7 D7 E7 E7 B6 D7 1046 // C6 }; const unsigned int entertainer_durations[] = { 0,0,188,188, // (4) (8) x x 188,375,188,375,188,188, // x 8 x 8 x x 750,188,188,188,188, // 4 x x x x 188,188,188,188,188,375, // x x x x x 8 1125 // 4+ }; const unsigned int entertainer_length = sizeof(entertainer_melody) / sizeof(entertainer_melody[0]); // メロディ再生用状態変数 unsigned int entertainerMelodyIdx = 0; unsigned long entertainerMelodyMillis = 0; bool entertainerMelodyPlaying = false; uint8_t entertainerMelodyPrevSwBits = 0; // ==== 制御パラメータ ==== // デバウンス時間 const unsigned long ENCODER_DEBOUNCE_MS = 5; const unsigned long SWITCH_DEBOUNCE_MS = 10; // 点滅パターン・周期 const unsigned long BLINK_PERIOD = 400; const unsigned long CYCLE_PERIOD = BLINK_PERIOD * 2; const int PATTERN[] = {1,0,1,0,0,0,0,0}; const int PATTERN_LEN = sizeof(PATTERN)/sizeof(PATTERN[0]); const unsigned long PATTERN_STEP_MS = CYCLE_PERIOD / PATTERN_LEN; // 点滅周期(可変抵抗利用時の範囲) const int MIN_INTERVAL = 50; const int MAX_INTERVAL = 1000; const float LOG_BASE = 20.0; // ハートビートLED周期パターン const uint16_t HEARTBEAT_PATTERNS[] = { 0b1000000000000000, 0b1010000000000000, 0b1010100000000000, 0b1010101000000000 }; const int NUM_HEARTBEAT_PATTERNS = sizeof(HEARTBEAT_PATTERNS)/sizeof(HEARTBEAT_PATTERNS[0]); const int HEARTBEAT_PATTERN_BITS = 16; const unsigned long HEARTBEAT_PATTERN_STEP_MS = 70; // 16*70ms=1120msで一周期 // ==== 状態変数 ==== // ロータリエンコーダ関連 volatile int encoderValue = 0; // 現在のエンコーダカウント uint8_t prevEncA = HIGH; // A相前回状態 unsigned long lastEncChange = 0; // A相変化時刻 int prevEncoderDisplay = 0; // 前回値 // スイッチ関連 unsigned long lastSwChange[NUM_SWITCHES] = {}; // 各ビットのデバウンス管理用時刻 uint8_t stableSw[NUM_SWITCHES] = {1, 1, 1, 1}; // デバウンス済値 unsigned long lastPushChange = 0; // プッシュスイッチデバウンス管理用 uint8_t stablePush = 1; // プッシュスイッチ安定値 uint8_t prevSwDisplay = 0xFF; // 前回値 // LED点滅・制御 unsigned long prevBlink = 0; // 単純点滅制御用 unsigned long patternTimer = 0; // パターン点滅管理用 uint8_t patternStep = 0; // パターン現在インデックス bool stateBlink = false; // ハートビート uint8_t heartbeatPatternIndex = 0; uint8_t heartbeatBitIndex = 0; unsigned long heartbeatPatternTimer = 0; // =========================================================================== // 初期化 // - 各ピンのI/O初期化 // - シリアル開始、スイッチ・エンコーダ初期表示(改行追加済み) // =========================================================================== void setup() { pinMode(ENC_A, INPUT_PULLUP); pinMode(ENC_B, INPUT_PULLUP); for (int i = 0; i < NUM_LEDS; i++) { pinMode(LED_PINS[i], OUTPUT); digitalWrite(LED_PINS[i], LOW); } for (int i = 0; i < NUM_SWITCHES; i++) { pinMode(SWITCH_PINS[i], INPUT_PULLUP); } pinMode(PUSH_SW, INPUT_PULLUP); pinMode(BUZZER_PIN, OUTPUT); digitalWrite(BUZZER_PIN, LOW); pinMode(POT_PIN, INPUT); pinMode(HEARTBEAT_LED, OUTPUT); digitalWrite(HEARTBEAT_LED, LOW); Serial.begin(9600); delay(200); Serial.println(); // 起動時にシリアルへ改行出力 encoderValue = 0; // 起動時にスイッチ・エンコーダ値の初期状態を表示 uint8_t swBits = getSwitchBits(); prevSwDisplay = swBits; prevEncoderDisplay = encoderValue; printSwEncLine(swBits, encoderValue); } /** * The Entertainerメロディ再生状態を初期化する。 * インデックス・タイマ・再生中フラグ・前回スイッチ値をリセットし音も止める。 */ void resetEntertainerMelody() { entertainerMelodyIdx = 0; entertainerMelodyMillis = 0; entertainerMelodyPlaying = false; entertainerMelodyPrevSwBits = 0; noTone(BUZZER_PIN); } /** * スライドスイッチと状態変数に基づきThe Entertainerのメロディを演奏・ループする。 * スイッチがOFFのときは再生を停止し、音も消す。 */ void playEntertainerMelody() { uint8_t swBits = getSwitchBits(); // スイッチOFFのとき即座に再生終了 if (swBits != 0b1000) { entertainerMelodyPlaying = false; return; } // 再生未開始 または OFF→ONになった直後は曲頭から再生 if (!entertainerMelodyPlaying || entertainerMelodyPrevSwBits != 0b1000) { entertainerMelodyIdx = 0; entertainerMelodyMillis = millis(); entertainerMelodyPlaying = true; if (entertainer_melody[entertainerMelodyIdx] > 0) { tone(BUZZER_PIN, entertainer_melody[entertainerMelodyIdx]); } else { noTone(BUZZER_PIN); } entertainerMelodyPrevSwBits = swBits; return; } // 再生中:duration経過で次の音へ if (entertainerMelodyPlaying && swBits == 0b1000) { if (millis() - entertainerMelodyMillis >= entertainer_durations[entertainerMelodyIdx]) { entertainerMelodyIdx++; entertainerMelodyMillis = millis(); if (entertainerMelodyIdx >= entertainer_length) { entertainerMelodyIdx = 0; // ループ再生 } if (entertainer_melody[entertainerMelodyIdx] > 0) { tone(BUZZER_PIN, entertainer_melody[entertainerMelodyIdx]); } else { noTone(BUZZER_PIN); } } } entertainerMelodyPrevSwBits = swBits; } // =========================================================================== // メインループ // - ハートビートLED、ロータリエンコーダ監視 // - スライドスイッチとエンコーダ値変化時に状態表示 // - スライドスイッチで動作モード切替 // =========================================================================== void loop() { beatHeartbeat(); // ハートビートLED点滅管理 processEncoder(); // エンコーダ入力処理 static uint8_t prevSwBitsLoop = 0xFF; uint8_t swBits = getSwitchBits(); // 0b0100へ遷移時はカウンタクリア・LED表示更新 if (prevSwBitsLoop != 0b0100 && swBits == 0b0100) { encoderValue = 0; showEncoderLeds(); } prevSwBitsLoop = swBits; // スイッチまたはエンコーダ値変化時に一行表示 if (swBits != prevSwDisplay || encoderValue != prevEncoderDisplay) { printSwEncLine(swBits, encoderValue); prevSwDisplay = swBits; prevEncoderDisplay = encoderValue; } switch (swBits) { case 0b0000: resetEntertainerMelody(); simpleBlinkPushMode(); break; case 0b0001: resetEntertainerMelody(); patternBlinkMode(); break; case 0b0010: resetEntertainerMelody(); potBlinkMode(); break; case 0b0100: resetEntertainerMelody(); showEncoderLeds(); break; case 0b1000: playEntertainerMelody(); allLeds(false); break; default: resetEntertainerMelody(); allOffMode(); break; } } /** * ロータリエンコーダA/Bの状態変化を監視してカウント増減。 * 変化時にはシリアルモニタへ現在値も出力 */ void processEncoder() { int currA = digitalRead(ENC_A); unsigned long now = millis(); // A相の状態遷移を検出 if (currA != prevEncA) { // デバウンス制御 if (now - lastEncChange >= ENCODER_DEBOUNCE_MS) { lastEncChange = now; // A相立下がり時にB相で方向判定 if (currA == LOW) { if (digitalRead(ENC_B) == HIGH) { encoderValue++; } else { encoderValue--; } } } prevEncA = currA; } } /** * スライドスイッチ(D12=MSB~D9=LSB)の4bit値を返す。 */ uint8_t getSwitchBits() { uint8_t bits = 0; unsigned long now = millis(); // 4bit分のスイッチ読み取り for (int i = 0; i < NUM_SWITCHES; i++) { uint8_t raw = digitalRead(SWITCH_PINS[i]); // デバウンス処理 if (raw != stableSw[i]) { if (now - lastSwChange[i] > SWITCH_DEBOUNCE_MS) { lastSwChange[i] = now; stableSw[i] = raw; } } else { lastSwChange[i] = now; } bits = (bits << 1) | (stableSw[i] ? 1 : 0); } return bits; } /** * プッシュスイッチの状態(押下時true)を返す。 */ bool getPushState() { uint8_t raw = digitalRead(PUSH_SW); unsigned long now = millis(); // デバウンス if (raw != stablePush) { if (now - lastPushChange > SWITCH_DEBOUNCE_MS) { lastPushChange = now; stablePush = raw; } } else { lastPushChange = now; } return stablePush == LOW; } /** * ビルトインLED(D13)点滅で生存確認 */ void beatHeartbeat() { unsigned long now = millis(); if (now - heartbeatPatternTimer >= HEARTBEAT_PATTERN_STEP_MS) { heartbeatPatternTimer = now; // パターン配列とビットインデックスで点灯/消灯決定 uint16_t pattern = HEARTBEAT_PATTERNS[heartbeatPatternIndex]; int bit = (pattern >> (HEARTBEAT_PATTERN_BITS - 1 - heartbeatBitIndex)) & 0x01; digitalWrite(HEARTBEAT_LED, bit ? HIGH : LOW); // パターン内bit進行&パターン番号更新 heartbeatBitIndex++; if (heartbeatBitIndex >= HEARTBEAT_PATTERN_BITS) { heartbeatBitIndex = 0; heartbeatPatternIndex++; if (heartbeatPatternIndex >= NUM_HEARTBEAT_PATTERNS) { heartbeatPatternIndex = 0; } } } } /** * エンコーダ値をLEDで2進4bit+符号(MSB、負値のとき点灯)で表示 */ void showEncoderLeds() { int val = encoderValue; int absVal = abs(val); // MSB(LED8)は負なら点灯 digitalWrite(LED_PINS[0], val < 0 ? HIGH : LOW); // 下位4ビットを順次LEDに表示 for (int i = 1; i < NUM_LEDS; i++) { digitalWrite(LED_PINS[i], (absVal >> (NUM_LEDS-1-i)) & 0x01 ? HIGH : LOW); } } /** * 圧電サウンダで1kHz発音、LED全消灯(※The Entertainerで置換済) */ void beepBuzzer() { tone(BUZZER_PIN, 1000); allLeds(false); } /** * プッシュスイッチを押している間だけ全LEDが単純点滅するモード(スイッチ0000用) */ void simpleBlinkPushMode() { static bool prevPress = false; bool currPress = getPushState(); if (currPress) { unsigned long now = millis(); // 押した直後は必ず即点灯 if (!prevPress) { stateBlink = true; allLeds(true); prevBlink = now; } else if (now - prevBlink >= BLINK_PERIOD) { stateBlink = !stateBlink; prevBlink = now; allLeds(stateBlink); } patternTimer = now; patternStep = 0; } else { allLeds(false); patternTimer = millis(); patternStep = 0; prevBlink = millis(); stateBlink = false; } prevPress = currPress; } /** * PATTERN配列に従ったパターン点滅を常時実行するモード(スイッチ0001用) */ void patternBlinkMode() { unsigned long now = millis(); // パターン点滅周期ごとにstepインクリメント if (now - patternTimer >= PATTERN_STEP_MS) { patternTimer = now; patternStep = (patternStep + 1) % PATTERN_LEN; } allLeds(PATTERN[patternStep]); } /** * 可変抵抗値でLED点滅周期を可変制御 */ void potBlinkMode() { int pot = analogRead(POT_PIN); float norm = (float)pot / 1023.0; // 対数スケーリングで周期設定 float logVal = log10((LOG_BASE - 1) * norm + 1) / log10(LOG_BASE); unsigned long interval = (unsigned long)((MAX_INTERVAL - MIN_INTERVAL) * (1.0 - logVal) + MIN_INTERVAL); unsigned long now = millis(); if (now - prevBlink >= interval) { stateBlink = !stateBlink; prevBlink = now; allLeds(stateBlink); } patternTimer = now; patternStep = 0; } /** * 全LED/ブザーOFF・点滅パターン変数初期化 */ void allOffMode() { allLeds(false); stateBlink = false; prevBlink = millis(); patternTimer = millis(); patternStep = 0; } /** * LED配列すべてを一括ON/OFF */ void allLeds(bool on) { for (int i = 0; i < NUM_LEDS; i++) { digitalWrite(LED_PINS[i], on ? HIGH : LOW); } } /** * スライドスイッチ値・エンコーダ値を指定形式で1行表示 * 例: SW: 0001 Enc: -5 */ void printSwEncLine(uint8_t swBits, int encVal) { Serial.print("SW: "); for (int i = NUM_SWITCHES - 1; i >= 0; i--) { Serial.print((swBits >> i) & 1); } Serial.print(" Enc: "); Serial.println(encVal); }