pythonwakaran
@pythonwakaran

Are you sure you want to delete the question?

Leaving a resolved question undeleted may help others!

arduino カウントダウンタイマーエラー

解決したいこと

arduinoで、第三者入力で入力してもらったタイマーをもとに、ゲーム開始時にミリ秒を加えてカウントダウンしたい。

発生している問題・エラー

指定したタイムではないタイマーになってしまう。また、00:00:00になったら71579:44などからリスタートされてしまう。

例)1m 0s が71579:44などから始まる。

NameError (uninitialized constant World)

または、問題・エラーが起きている画像をここにドラッグアンドドロップ

該当するソースコード

#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
#include <Keypad.h>
#include <Servo.h>

#define SCREEN_WIDTH 128
#define SCREEN_HEIGHT 64
#define OLED_RESET -1

Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET);

const byte ROWS = 4; 
const byte COLS = 3; 
char keys[ROWS][COLS] = {
  {'1','2','3'},
  {'4','5','6'},
  {'7','8','9'},
  {'*','0','#'}
};

byte rowPins[ROWS] = {2, 3, 4, 5}; 
byte colPins[COLS] = {6, 7, 8}; 

Keypad keypad = Keypad(makeKeymap(keys), rowPins, colPins, ROWS, COLS);

const int buzzerPin = A1;
const int servoPin = 10; // サーボのピン
char correctCode[5]; // ユーザーが設定するパスワード
int codeIndex = 0;
int attemptCounter = 0; // 試行回数のカウンター
const int maxAttempts = 3; // 最大試行回数
unsigned long startTime;
unsigned long timerMillis; // タイマーの時間
bool timerActive = false; // タイマーがアクティブかどうか
int timerMinutes = 0; // 設定された分
int timerSeconds = 0; // 設定された秒
bool passwordSet = false; // パスワードが設定されたかどうか

Servo myServo;

void setup() {
  pinMode(buzzerPin, OUTPUT);
  myServo.attach(servoPin);
  myServo.write(0); // サーボを初期位置に設定

  display.begin(SSD1306_SWITCHCAPVCC, 0x3C);
  display.clearDisplay();
  display.setTextSize(1);
  display.setTextColor(SSD1306_WHITE);
  display.setCursor(0, 0);
  display.println(F("Set Password:"));
  display.display();
}

void loop() {
  if (timerActive) {
    unsigned long currentMillis = millis();
    unsigned long elapsedTime = currentMillis - startTime;

    // 残り時間を計算
    unsigned long remainingTime = timerMillis - elapsedTime;

    // タイマーが切れた場合
    if (remainingTime <= 0) {
      handleTimeExpired();
      return;
    }

    // 残り時間を表示
    display.clearDisplay();
    display.setTextSize(2);
    display.setCursor(0, 0);
    display.print(F("Remaining: "));
    display.print(formatTime(remainingTime));
    display.display();
    
  } else {
    if (!passwordSet) {
      // パスワード設定の入力を受け付ける
      char key = keypad.getKey();
      if (key) {
        handleKeyPress(key);
      }

      // 入力後の表示更新
      display.clearDisplay();
      display.setCursor(0, 0);
      display.print(F("Set Password: "));
      display.setTextSize(2);
      for (int i = 0; i < codeIndex; i++) {
        display.print('*');  // 入力された分だけアスタリスクを表示
      }
      display.display();
    } else {
      // 時間設定の入力を受け付ける
      char key = keypad.getKey();
      if (key) {
        handleTimeInput(key);
      }

      // 入力後の表示更新
      display.clearDisplay();
      display.setCursor(0, 0);
      display.print(F("Set Time: "));
      display.print(timerMinutes);
      display.print(F(":"));
      display.print(timerSeconds);
      display.display();
    }
  }
}

void handleKeyPress(char key) {
  if (key == '#') {
    // エンター処理(パスワードの確定)
    if (codeIndex > 0) {
      correctCode[codeIndex] = '\0'; // 終端文字を追加
      if (codeIndex == 4) {
        passwordSet = true; // パスワード設定完了
        display.clearDisplay();
        display.setCursor(0, 0);
        display.println(F("Password Set!"));
        display.display();
        delay(2000); // 確認メッセージ表示
      }
    }
  } else if (key == '*') {
    // 文字削除処理
    if (codeIndex > 0) {
      codeIndex--;
      correctCode[codeIndex] = '\0'; // 最後の文字を削除
    }
  } else if (codeIndex < 4) {
    // 入力を保存
    correctCode[codeIndex] = key;
    codeIndex++;
  }
}

void handleTimeInput(char key) {
  if (key == '#') {
    // タイマー開始処理
    startTimer(); // タイマーを開始する関数
  } else if (key >= '0' && key <= '9') {
    // 数字の入力処理
    if (timerMinutes < 10) {
      timerMinutes = timerMinutes * 10 + (key - '0'); // 分の設定
    } else if (timerSeconds < 60) {
      timerSeconds = timerSeconds * 10 + (key - '0'); // 秒の設定
    }
  }
}

void startTimer() {
  timerMillis = (timerMinutes * 60 + timerSeconds) * 1000; // ミリ秒に変換
  startTime = millis(); // タイマー開始時刻を記録
  timerActive = true; // タイマーをアクティブにする
}

void handleTimeExpired() {
  // タイマーの期限切れ処理
  timerActive = false;
  display.clearDisplay();
  display.setTextSize(3);
  display.setCursor(25, 20);
  display.println(F("Time's Up!"));
  display.display();
  myServo.write(180); // サーボを動かす
  delay(2000);
  myServo.write(0); // サーボを元の位置に戻す
}

String formatTime(unsigned long milliseconds) {
  unsigned long seconds = milliseconds / 1000;
  milliseconds %= 1000;
  unsigned long minutes = seconds / 60;
  seconds %= 60;

  String formattedTime = "";
  if (minutes < 10) formattedTime += "0";
  formattedTime += String(minutes) + ":";
  if (seconds < 10) formattedTime += "0";
  formattedTime += String(seconds) + ":" + String(milliseconds / 10); // ミリ秒を表示
  return formattedTime;
}

まだまだ初心者なのでわかりません助けてください。
お願いします。

0

2Answer

動作確認していませんが、

    // 残り時間を計算
    unsigned long remainingTime = timerMillis - elapsedTime;

    // タイマーが切れた場合
    if (remainingTime <= 0) {
      handleTimeExpired();
      return;
    }

ここ
符号無し変数 remainingTime が アンダーフローしているのではないでしょうか?
msec単位でちょうど0になる可能性は低いし、0以下にはならないので

    if (remainingTime <= 0) {

での判定は ほとんど失敗すると思います。

-    if (remainingTime <= 0) {
+    if (timerMillis <= elapsedTime) {

にすれば良いかと・・・

1Like

Comments

  1. @pythonwakaran

    Questioner

    勝手にリスタートされる現象がなくなりました。
    バグが減り、ホッとしました。
    本当にありがとうございました。
    指定したタイムではないタイマーになってしまうところもわかりません。
    もしよければ教えてください。

  2. void handleTimeInput(char key) {
      if (key == '#') {
        // タイマー開始処理
        startTimer(); // タイマーを開始する関数
      } else if (key >= '0' && key <= '9') {
        // 数字の入力処理
        if (timerMinutes < 10) {
          timerMinutes = timerMinutes * 10 + (key - '0'); // 分の設定
        } else if (timerSeconds < 60) {
          timerSeconds = timerSeconds * 10 + (key - '0'); // 秒の設定
        }
      }
    }
    

    プログラムでは、見たところ数字キーで分、秒を入力して #キーで 動作開始に見えます。 仕様がわからないのですが、数字の入力処理がおかしく見えます。
    仕様を教えてください。

    1.キー入力はどのようなやり方を想定しているのか?
    例) 0912# -> 9分12秒
    2.入力値の制限は?
    例) 分は10分未満 秒は60秒未満 範囲外は無視する

    指定したタイムではないタイマーになってしまう

    が意図している事を具体的な入力と出力で表現してください。

    例)0123# と入力したら Set Time:: と表示される。
    例) 0100# と入力したら Set Time は正しく表示されるが、
      動作開始すると Remaining:::** と表示される。

    風にお願いします。

  3. @pythonwakaran

    Questioner

    設定は、01(分)00(秒)#(エンター)->01(分):00(秒):00(ミリ秒)
    のように表示したいと思っています。

    タイマーを設定する画面の時はSet Time:画面上に表示して、動作を開始すると、画面上にremaining:と表示して、タイマーを表示して、パスワード入力の待ち受けをするようにしたいです。
    また、パスワードが入力されたら*を表示し、4桁入力したら#キーをおして、判定に移るようにしたいです。
    間違えて入力してしまった時用に1文字削除する*キーを用意したいです。
    タイムアップ又は3回間違えたら、サーボを回転させるようにしたいです。

    時間は、何分でも設定できるようにしたいと思っています。
    しかし、実行すると、
    0100#と入力したら、71579(分):44(秒)と出力されてしまいます。
    わかりにくくてすみません。

  4. やりたい事は理解できました。
    少しプログラム内容とは離れているように見受けられます。

    入力の問題は 0100# の場合は意図した動作になるので
    現状で表示の異常についてプログラム見ました。

    実機がないので確認のお願いです

    0100入力中(#推していない)では 01:00が表示されてて、#を押すと表示がおかしくなるのではないでしょうか?

    私は 普段 String型を使わないので推測になりますが

    +String formattedTime;
    String formatTime(unsigned long milliseconds) {
      unsigned long seconds = milliseconds / 1000;
      milliseconds %= 1000;
      unsigned long minutes = seconds / 60;
      seconds %= 60;
    
    -  String formattedTime = "";
    +  formattedTime = "";
      if (minutes < 10) formattedTime += "0";
      formattedTime += String(minutes) + ":";
      if (seconds < 10) formattedTime += "0";
      formattedTime += String(seconds) + ":" + String(milliseconds / 10); // ミリ秒を表示
      return formattedTime;
    }
    

    この修正で 0100#で Remaining: が正しく表示されないでしょうか?

  5. @pythonwakaran

    Questioner

    修正したのですが、
    IMG_2155.jpg

    IMG_2156.jpg

    このようになってしまうんですよね。

  6. @pythonwakaran

    Questioner

    実は、
    #include
    #include
    #include
    #include
    #include

    #define SCREEN_WIDTH 128
    #define SCREEN_HEIGHT 64
    #define OLED_RESET -1

    Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET);

    const byte ROWS = 4;
    const byte COLS = 3;
    char keys[ROWS][COLS] = {
    {'1','2','3'},
    {'4','5','6'},
    {'7','8','9'},
    {'*','0','#'}
    };

    byte rowPins[ROWS] = {2, 3, 4, 5};
    byte colPins[COLS] = {6, 7, 8};

    Keypad keypad = Keypad(makeKeymap(keys), rowPins, colPins, ROWS, COLS);

    const int buzzerPin = A1;
    const int servoPin = 10; // サーボのピン
    const int codeLength = 4; // パスワードの長さ
    const char correctCode[codeLength] = {'0', '3', '3', '4'}; // 正しいパスワード
    char enteredCode[codeLength + 1]; // 終端文字を含めるため +1
    int codeIndex = 0;
    int attemptCounter = 0; // 試行回数のカウンター
    const int maxAttempts = 3; // 最大試行回数
    unsigned long startTime;
    const unsigned long timeLimit = 120000; // 2分に設定
    unsigned long lastBuzzerTime = 0; // 最後にブザーが鳴った時間
    bool timerActive = true; // タイマーがアクティブかどうか

    Servo myServo;

    void setup() {
    pinMode(buzzerPin, OUTPUT);
    myServo.attach(servoPin);
    myServo.write(0); // サーボを初期位置に設定

    display.begin(SSD1306_SWITCHCAPVCC, 0x3C);
    display.clearDisplay();
    display.setTextSize(1);
    display.setTextColor(SSD1306_WHITE);
    display.setCursor(0, 0);
    display.println(F("Enter password:"));
    display.display();

    Serial.begin(9600); // シリアル通信を開始
    startTime = millis();
    }

    void loop() {
    if (!timerActive) {
    return; // タイマーが無効な場合は何もしない
    }

    unsigned long currentMillis = millis();
    unsigned long elapsedTime = currentMillis - startTime;
    unsigned long remainingTime = timeLimit - elapsedTime;

    // タイマーが切れた場合
    if (elapsedTime >= timeLimit) {
    Serial.println("Timer expired!"); // デバッグ用メッセージ
    handleTimeExpired();
    return;
    }

    // 残り時間が30秒以下のとき、1秒ごとにブザーを鳴らす
    if (remainingTime <= 30000) {
    if (currentMillis - lastBuzzerTime >= 1000) { // 1秒ごとに
    tone(buzzerPin, 1000, 100); // ブザーを鳴らす
    lastBuzzerTime = currentMillis; // 最後の鳴らした時間を更新
    }
    }

    // キー入力をチェック
    char key = keypad.getKey();
    if (key) {
    handleKeyPress(key);
    tone(buzzerPin, 1000, 100); // ボタンが押されたらブザーを鳴らす
    }

    // シリアル入力をチェック
    if (Serial.available() > 0) {
    String input = Serial.readStringUntil('\n'); // 改行まで読み込む
    input.trim(); // 前後の空白を削除
    if (input.length() == codeLength) {
    input.toCharArray(enteredCode, codeLength + 1); // 文字列を配列に変換
    handlePasswordInput();
    }
    }

    // 残り時間を表示
    display.clearDisplay();
    display.setTextSize(1);
    display.setCursor(0, 0);
    display.println(F("password:"));

    // 常に中央に '' を表示(最大4桁)
    display.setTextSize(2);
    int centerX = (SCREEN_WIDTH - 48) / 2; // 4つのアスタリスクの幅(12 * 4)
    display.setCursor(centerX, 25); // 中央に配置
    for (int i = 0; i < codeLength; i++) {
    display.print(i < codeIndex ? '
    ' : ' '); // 入力されている分だけアスタリスク、残りはスペース
    }

    // タイマーの表示を下の中央に移動
    display.setTextSize(2);
    display.setCursor((SCREEN_WIDTH - 60) / 2 - 20, SCREEN_HEIGHT - 20);
    display.print(formatTime(remainingTime)); // ここで remainingTime を使用
    display.display(); // 表示を更新
    }

    void handleKeyPress(char key) {
    if (key == '#') {
    // エンター処理
    if (codeIndex == codeLength) { // 4文字入力されている場合のみ判定
    enteredCode[codeIndex] = '\0'; // 終端文字を追加
    handlePasswordInput();
    }
    } else if (key == '*') {
    // 文字削除処理
    if (codeIndex > 0) {
    codeIndex--;
    enteredCode[codeIndex] = '\0'; // 最後の文字を削除
    }
    } else if (codeIndex < codeLength) {
    // 入力を保存
    enteredCode[codeIndex] = key;
    codeIndex++;
    }

    // パスワード表示の更新
    display.clearDisplay(); // 表示をクリア
    display.setCursor(0, 0);
    display.print(F("Enter password:"));

    // 常に中央に '' を表示(最大4桁)
    display.setTextSize(2);
    int centerX = (SCREEN_WIDTH - 48) / 2; // 4つのアスタリスクの幅(12 * 4)
    display.setCursor(centerX, 25); // 中央に配置
    for (int i = 0; i < codeLength; i++) {
    display.print(i < codeIndex ? '
    ' : ' '); // 入力されている分だけアスタリスク、残りはスペース
    }
    display.display(); // 表示を更新
    }

    void handlePasswordInput() {
    if (checkCode(enteredCode)) {
    handleSuccess();
    } else {
    handleFailure();
    }
    codeIndex = 0; // 入力をリセット
    }

    bool checkCode(char *code) {
    for (int i = 0; i < codeLength; i++) {
    if (code[i] != correctCode[i]) {
    return false;
    }
    }
    return true;
    }

    void handleSuccess() {
    tone(buzzerPin, 1000, 500); // 成功時のブザー
    display.clearDisplay();

    // 成功メッセージを中央に大きく表示
    display.setTextSize(3);
    display.setCursor(0, 20);
    display.println(F("Success!"));
    display.display();

    myServo.write(0); // サーボは動かさない
    delay(2000); // 2秒待機
    while (true); // リセットボタンが押されるまで停止
    }

    void handleFailure() {
    attemptCounter++;
    // 失敗音を3回鳴らす
    for (int i = 0; i < 3; i++) {
    tone(buzzerPin, 1000, 100); // 短いビープ音
    delay(200); // 短い間隔
    }

    display.clearDisplay();

    // 失敗メッセージを中央に大きく表示
    display.setTextSize(3);
    display.setCursor(25, 20); // 上に移動
    display.println(F("Fail!"));

    // 残りの試行回数を表示
    display.setTextSize(1);
    display.setCursor(30, 50);
    display.print(F("Attempts: "));
    display.println(maxAttempts - attemptCounter);
    display.display();

    delay(2000); // 2秒間待機して表示を見えるようにする

    if (attemptCounter >= maxAttempts) {
    display.clearDisplay();
    display.setTextSize(3);
    display.setCursor(35, 20);
    display.println(F("Max"));
    display.setCursor(0, 40);
    display.println(F("Attempts"));

    // 最大試行回数に達した場合、長いビープを1回鳴らす
    tone(buzzerPin, 1000, 1000);
    delay(1000); // 長めのビープ音
    noTone(buzzerPin);
    
    // サーボの動作
    myServo.write(180); // サーボを動かす
    delay(2000); // サーボの動作時間
    myServo.write(0); // サーボを元の位置に戻す
    
    delay(2000); // 2秒間待機
    while (true); // ループを止める
    

    }
    noTone(buzzerPin);
    }

    void handleTimeExpired() {
    timerActive = false; // タイマーを無効にする
    display.clearDisplay(); // 表示をクリア

    display.setTextSize(3);
    display.setCursor(25, 20);
    display.println(F("Fail!")); // タイマーが切れた場合に表示

    // 画面を更新
    display.display();

    // サーボの動作
    myServo.write(180); // サーボを動かす
    delay(2000); // サーボの動作時間
    myServo.write(0); // サーボを元の位置に戻す

    // ブザーを鳴らす
    tone(buzzerPin, 1000, 1000); // 長いビープ音
    delay(1000);
    noTone(buzzerPin);
    }

    String formatTime(unsigned long milliseconds) {
    unsigned long seconds = milliseconds / 1000;
    milliseconds %= 1000;
    unsigned long minutes = seconds / 60;
    seconds %= 60;

    String formattedTime = "";
    if (minutes < 10) formattedTime += "0";
    formattedTime += String(minutes) + ":";
    if (seconds < 10) formattedTime += "0";
    formattedTime += String(seconds) + ":";
    if (milliseconds / 10 < 10) formattedTime += "0"; // ミリ秒のフォーマット
    formattedTime += String(milliseconds / 10, DEC); // ミリ秒も表示
    return formattedTime;
    }
    このコードをベースにして、パスワードと、タイマーをキーパッドから入力してもらってゲームをするような感じなんです。

  7. int のサイズがの影響かもしれません

    -int timerMinutes = 0; // 設定された分
    +unsigned long timerMinutes = 0; // 設定された分
    -int timerSeconds = 0; // 設定された秒
    +unsigned long timerSeconds = 0; // 設定された秒
    

    で確認していただけないでしょうか?

  8. @pythonwakaran

    Questioner

    わかりました。
    夕方ごろ確認します。

  9. @pythonwakaran

    Questioner

    タイマー直りました!!
    タイマー設定では、分と秒がそれぞれ設定できるようにしてほしいです。
    このゲームで、パスワードを設定した人以外にゲームをやってほしいんですが、ゲーム開始後に、
    パスワードが入力されないことと、それぞれに音を付けてほしいです。

    0:0となっている部分をまずは、左がわの0を点滅表示させて、分を編集できるようにして、#キーが押されたら、右がわの0を点滅表示させて、秒を編集できるようにして、#が押されたらゲームを開始できるようにしてほしいです。

    パスワードが打たれたら、タイマーの下の真ん中に*を打った文字数分だけ表示して、#キーが押されたら、判定に移るようにしてほしいです。

    音は、キー入力(#キー*を含むと)されたとき、タイマーが30秒以下になったとき1秒おきに、失敗したとき、成功したときにつけてほしいです。
    本当に申し訳ないのですが、手伝っていただけないでしょうか。

  10. 推測が当たっていたようですね。よかったです。
    なぜこのようになったか残しておきますね。
    たぶん 質問者どのの環境では、intが 16bit longが 32bit なのでしょうね。

    timerMillisは, unsigned long
    timerMinutes timerSecondsは, intです。

    0100#の場合
    timerMinutes = 1
    timerSeconds = 0
    です。

      timerMillis = (timerMinutes * 60 + timerSeconds) * 1000; // ミリ秒に変換
    

    (timerMinutes * 60 + timerSeconds) * 1000 を演算すると 6000 = EA60
    (オーバーフローして符号反転している)
    型が符号付き int なので、-5536 となります。
    これを符号なしLongにすると FFFFEA60 つまり 4294961760 となります。

    この値を元の分秒に戻すと

      unsigned long seconds = milliseconds / 1000;
      milliseconds %= 1000;
      unsigned long minutes = seconds / 60;
      seconds %= 60;
    

    minutes = 4294961760 / 1000 = 4294961
    4294961 % 60 = 71582分
    だから質問者殿の タイマー値が 71579:44 みたいな数値が出てきたのです。

    timerMillis 演算時にキャストしても良かったのですが、確実に確認する為に
    timerMinutes、timerSeconds を unsigned long に変更した結果が知りたかったのです。

    私も 71582分 が何か最初わからなかったのですが、ふとした事がきっかけで
    解けて、スッキリしました。 ありがとうございます。

    クローズのほどお願いします。

  11. @pythonwakaran

    Questioner

    本当にありがとうございました。
    クローズさせていただきます。

NameError (uninitialized constant World)
または、問題・エラーが起きている画像をここにドラッグアンドドロップ
ここの部分は消し忘れで、関係ありません。

0Like

Your answer might help someone💌