ステッピングタコメータ机上テスト①

tachometer_TEST 電子工作
tachometer_TEST


加速減速調整を入れた普通に動くソースが完成した。
最初は脱調しまくって、もうお手上げだなんて思ってましたがこの長雨のせいで、ずっと机にかじりつくことが出来て意外と回り道しながら進んだ感はある。
Arduino uno R3で疑似信号を作ってみたがうまく反応しない。
なぜだ?arduino関係は時間を空けるとコロっと忘れてしまうのがダメなんだと思う。

秋月電子で買ったシグナルジェネレータはあるが、タコメータの机上テストには向いてないのでArduino uno R3でプログラミングしてみた。
外に遊びに行くお金もない、スロットに興じる軍資金もない、暇なので馬鹿みたいに意地になってソースを作る。あちこちから参考となるソースを頂いて作ったので割とまともに見える。
Arduinoの唯一の利点は利用者が多いってこと、なのでスケッチ(プログラミングソースコード)はいたるところに転がっている。
PICなんて過去の遺物にみえるくらい利用者がすくない。どうするんだろうMicrochip社。

今後のことも考えてnanoあたりでタコパルス発生装置として作っていた方が良いだろう。
アマゾンを今見て驚愕である。互換nanoが以前の3倍以上の値段で売っている。
昔買った3個500円の中華製の互換初代nanoがあるがまだ使えるかなぁということがちょっと心配。
ブートローダの導入ってどうすんだっけ? いかん。完全に忘れていたが、ネットの海をさまよっていたら思い出した。というか書いてあった。unoを書き込み機として使うんだけど、なんかもっと便利なものを買ったような記憶があるがそっちは残念ながら全く思い出せない。

テスト用に作ったタコパルス発生ソース(無保証)arduino uno R3用
おなじみのLCD液晶(1602)
50KΩBのボリューム1個
適当なタクトスイッチ4個
回路図は書くまでもなくソースに書いてあるので省略
組み立てるときにnanoと入れ替えようと思ったら、タクトスイッチがひとつもなかった。
しばらく保留だな。晴れてたら電車に乗ってちょっくら買いにとなるが外はあいにくの土砂降り
台風接近も聞いている(実は土曜日まで知らなかった)のでしばらく外出は控えよう。

#include <Wire.h>
#include <LiquidCrystal_I2C.h>
#include <EEPROM.h>

// --- ピン割り当て ---
const int pulsePin = 9;
const int potPin = A0;
const int sw1Pin = 2; // プリセット1
const int sw2Pin = 3; // プリセット2
const int sw3Pin = 4; // プリセット3
const int sw4Pin = 5; // スイープ開始・停止 / 設定モード移行

// --- LCD設定 ---
LiquidCrystal_I2C lcd(0x27, 16, 2); // アドレス0x27, 16文字2行

// --- 定数 ---
const float minFreq = 10.0;
const float maxFreq = 300.0; //300Hz :18,000rpm
const byte MAGIC_NUMBER = 0xA5; // EEPROM初期化確認用

// --- グローバル変数 ---
float pulsesPerRev = 1.0; // 1回転あたりのパルス数
int presetRPM[3] = {1500, 8000, 11500}; // プリセット初期値
byte currentMode = 0; // 0:手動, 1:P1, 2:P2, 3:P3, 4:スイープ

float currentFreq = 10.0;
int currentRPM = 0;

// スイープ用変数
float sweepFreq = 10.0;
bool sweepUp = true;
unsigned long lastSweepTime = 0;

// ボタン状態管理用
struct ButtonState {
  int pin;
  bool lastState;
  unsigned long pressTime;
  bool isPressed;
  bool longPressHandled;
};
ButtonState btn1 = {sw1Pin, HIGH, 0, false, false};
ButtonState btn2 = {sw2Pin, HIGH, 0, false, false};
ButtonState btn3 = {sw3Pin, HIGH, 0, false, false};
ButtonState btn4 = {sw4Pin, HIGH, 0, false, false};

// --- EEPROM アドレスマップ ---
// 0: マジックナンバー (1byte) 初期化済みデータ
// 1: pulsesPerRev (float, 4bytes)
// 5: presetRPM[0] (int, 2bytes)
// 7: presetRPM[1] (int, 2bytes)
// 9: presetRPM[2] (int, 2bytes)
// 11: currentMode (byte, 1byte)

void setup() {
  pinMode(pulsePin, OUTPUT);
  pinMode(sw1Pin, INPUT_PULLUP);
  pinMode(sw2Pin, INPUT_PULLUP);
  pinMode(sw3Pin, INPUT_PULLUP);
  pinMode(sw4Pin, INPUT_PULLUP);
  
  Serial.begin(115200);
  lcd.init();
  lcd.backlight();

  // PWM出力の設定
  TCCR1A = _BV(COM1A1); 
  TCCR1B = _BV(WGM13) | _BV(WGM12) | _BV(CS11) | _BV(CS10);

  // SW4を押しながら起動した場合は設定モードへ (Power ON + Sw4)
  if (digitalRead(sw4Pin) == LOW) {
    settingsMode();
  }

  // EEPROMの読み込みと初期化チェック
  byte magic = EEPROM.read(0);
  if (magic != MAGIC_NUMBER) {
    // 初回起動時(EEPROM初期化)
    EEPROM.put(1, pulsesPerRev);
    EEPROM.put(5, presetRPM[0]);
    EEPROM.put(7, presetRPM[1]);
    EEPROM.put(9, presetRPM[2]);
    EEPROM.put(11, currentMode);
    EEPROM.write(0, MAGIC_NUMBER);
  } else {
    // 既存設定の読み込み
    EEPROM.get(1, pulsesPerRev);
    EEPROM.get(5, presetRPM[0]);
    EEPROM.get(7, presetRPM[1]);
    EEPROM.get(9, presetRPM[2]);
    EEPROM.get(11, currentMode);
  }
  
  updateDisplay();
}

void loop() {
  handleButtons();

  switch (currentMode) {
    case 0: // 手動モード
      {
        int potValue = analogRead(potPin);
        currentFreq = minFreq + (float)potValue * (maxFreq - minFreq) / 1023.0;
        currentRPM = freqToRPM(currentFreq);
        setPulseFrequency(currentFreq);
      }
      break;
    case 1: // プリセット1
    case 2: // プリセット2
    case 3: // プリセット3
      {
        currentRPM = presetRPM[currentMode - 1];
        currentFreq = rpmToFreq(currentRPM);
        setPulseFrequency(currentFreq);
        
        // 手動モード
        // ボタンで戻る
      }
      break;
    case 4: // スイープモード
      {
        if (millis() - lastSweepTime > 20) {
          lastSweepTime = millis();
          if (sweepUp) {
            sweepFreq += 1.0;
            if (sweepFreq >= maxFreq) sweepUp = false;
          } else {
            sweepFreq -= 1.0;
            if (sweepFreq <= minFreq) sweepUp = true;
          }
          currentFreq = sweepFreq;
          currentRPM = freqToRPM(currentFreq);
          setPulseFrequency(currentFreq);
        }
      }
      break;
  }

  // LCDの表示更新(チラつかない用100msに1回程度に制限)
  static unsigned long lastDisplayUpdate = 0;
  if (millis() - lastDisplayUpdate > 100) {
    lastDisplayUpdate = millis();
    updateDisplay();
  }
}

// ボタン処理関数
void handleButtons() {
  checkButton(&btn1, 1);
  checkButton(&btn2, 2);
  checkButton(&btn3, 3);
  checkButton(&btn4, 4);
}

void checkButton(ButtonState* btn, int btnNum) {
  bool currentState = digitalRead(btn->pin);
  
  if (currentState == LOW && btn->lastState == HIGH) {
    btn->pressTime = millis();
    btn->isPressed = true;
    btn->longPressHandled = false;
  }
  
  if (currentState == LOW && btn->isPressed) {
    if (millis() - btn->pressTime > 1500 && !btn->longPressHandled) { // 1.5秒長押し
      btn->longPressHandled = true;
      handleLongPress(btnNum);
    }
  }
  
  if (currentState == HIGH && btn->lastState == LOW) {
    if (millis() - btn->pressTime > 50 && !btn->longPressHandled) { // 短押し(チャタリング防止)
      handleShortPress(btnNum);
    }
    btn->isPressed = false;
  }
  btn->lastState = currentState;
}

void handleShortPress(int btnNum) {
  if (btnNum >= 1 && btnNum <= 3) {
    currentMode = btnNum; // プリセット呼び出し
    EEPROM.put(11, currentMode);
  } else if (btnNum == 4) {
    if (currentMode == 4) {
      currentMode = 0; // スイープから手動へ戻る
    } else {
      currentMode = 4; // スイープ開始
      sweepFreq = currentFreq; // 現在の周波数からスタート
    }
    EEPROM.put(11, currentMode);
  }
}

void handleLongPress(int btnNum) {
  if (btnNum >= 1 && btnNum <= 3) {
    if (currentMode == 0) { // 手動モードの時、上書き可能
      presetRPM[btnNum - 1] = currentRPM;
      int addr = 5 + (btnNum - 1) * 2;
      EEPROM.put(addr, presetRPM[btnNum - 1]);
      
      lcd.setCursor(0, 0);
      lcd.print("Preset ");
      lcd.print(btnNum);
      lcd.print(" Saved! ");
      delay(1000); // メッセージ確認用ディレイ
    }
  }
}

// パルス出力設定関数
void setPulseFrequency(float freq) {
  if (freq < 1.0) freq = 1.0;
  unsigned int icrValue = (unsigned int)(125000.0 / freq);
  if (icrValue < 208) icrValue = 208;
  if (icrValue > 65535) icrValue = 65535;

  ICR1 = icrValue;
  OCR1A = icrValue / 2;
}

// 変換関数
int freqToRPM(float freq) {
  return (int)((freq * 60.0) / pulsesPerRev);
}
float rpmToFreq(int rpm) {
  return ((float)rpm * pulsesPerRev) / 60.0;
}

// LCD表示更新関数
void updateDisplay() {
  lcd.setCursor(0, 0);
  switch (currentMode) {
    case 0: lcd.print("Mode: Manual    "); break;
    case 1: lcd.print("Mode: Preset 1  "); break;
    case 2: lcd.print("Mode: Preset 2  "); break;
    case 3: lcd.print("Mode: Preset 3  "); break;
    case 4: lcd.print("Mode: AutoSweep "); break;
  }

  lcd.setCursor(0, 1);
  char buf[17];
  // 例: " 11500rpm 300Hz "
  sprintf(buf, "%5drpm %3d.%1dHz", currentRPM, (int)currentFreq, (int)(currentFreq * 10) % 10);
  lcd.print(buf);
}

// 設定モード(起動時にSW4押しっぱ)
void settingsMode() {
  lcd.init();
  lcd.backlight();
  lcd.setCursor(0, 0);
  lcd.print("Setup Mode");
  EEPROM.get(1, pulsesPerRev);

  // 利用可能なパルス係数の選択肢
  float pprOptions[] = {0.5, 1.0, 2.0, 4.0};
  int optionIndex = 1; // デフォルト 1.0
  for (int i = 0; i < 4; i++) {
    if (abs(pulsesPerRev - pprOptions[i]) < 0.1) optionIndex = i;
  }

  while (true) {
    int potValue = analogRead(potPin);
    int newIndex = map(potValue, 0, 1023, 0, 3);
    newIndex = constrain(newIndex, 0, 3);
    
    if (newIndex != optionIndex) {
      optionIndex = newIndex;
      lcd.setCursor(0, 1);
      lcd.print("PPR(Pulse): ");
      lcd.print(pprOptions[optionIndex], 1);
      lcd.print("   ");
    }

    // SW4が長押しされたら保存して再起動待ち
    if (digitalRead(sw4Pin) == LOW) {
      delay(1500);
      if (digitalRead(sw4Pin) == LOW) {
        pulsesPerRev = pprOptions[optionIndex];
        EEPROM.put(1, pulsesPerRev);
        lcd.clear();
        lcd.print("Settings Saved.");
        lcd.setCursor(0, 1);
        lcd.print("Please Restart.");
        while (true); // 電源リセット待ちで停止
      }
    }
  }
}

タコ信号発生装置を作る前からタコメータのソースはシグナルジェネレータのテストですでに判明している問題がある。
シフトアップインジケータだ。動画では一度書き込みテストをしているので9000RPMが初期値になっている。閾値を適当に設定したロジックでは閾値前後で点滅するので再度ロジックを考えなおす必要があるって、前も問題なってなかったか?同じところで躓いているような気がする。思考が同じってことは全然治らない性格のようだ。LEDの点灯に関して要修正。結局ここもアイドリング付近のゆらぎと同じで9000RPM直前の回転数のゆらぎを正確に読み取るため、直前の回転数では点灯ではなく点滅してしまうということだ。どう処理しようかな。

操作確認動画 セットアップ編。前回のソースより数歩進んで書き直している

0点出しのオープニングセレモニー後、ボタンを長押しでセットアップに入る。
設定フェーズ1に突入合図、LED1回点灯。ボタンを短く押すごとに、
52Y 1000rpm→29L 5000rpm→4UO 9500rpmの位置で移動し、移動した先でボタンを長押しで盤面のスケールを確定、LEDが2回点灯で設定完了。スイッチを離すとフェーズ2のシフトインジケータの設定に入る。(デフォルトは10500rpmだが、テストのため 9000rpmに事前設定変更)
ボタンを短く押すごとに+500rpmで移動、12000rpmを超えると7000RPMに戻る。指定回転数で長押しするとLEDが3回点滅し設定が確定し待機動作に戻る。

疑似タコ信号で机上テスト②を実施したら、実車テストに入るがアイドリング付近の移動量も極力更新スピードを抑えているので針の動きは緩慢になってるはず。ピコピコと動かないようにしたのですごく動きに興味があるが基板はまだ悩んでいるので今後、少し間が空きます。

今回は3Dプリンタもあるので耐久性に難があるがテスト仕様はピッタリフィットでお届け出るかと思ってます。

コメント