質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

ただいまの
回答率

88.91%

Arduino  LEDの点灯消灯をシリアル通信(文字列)でやりたい

解決済

回答 3

投稿 編集

  • 評価
  • クリップ 0
  • VIEW 342

koh_cafe

score 3

Arduino初心者です。
LEDの点灯消灯をシリアル通信(文字列)でやりたいのですがうまくいきません。

以下がスケッチなのですが、 Serial.println(dat); で目的のon01がdatに入力されているのは確認出来るのですが
if(dat == "on01"){ が動作していないようです。
データ型の問題の様な気がするのですが、あれこれ試しても上手くいきませんでした。
御教授のほどお願い致します。

追記
マルチポストを推奨をしていない旨を知らずに下記のリンクに同じ質問をしてしまいました。
https://detail.chiebukuro.yahoo.co.jp/qa/question_detail/q12228794132
まだ問題は解決はしていないのですが関連した質問などを参考にしてやってみます。

//IOのピン番号
const int DIN_PIN = 7;
const int LED_PIN = 6;

//入力された文字を入れる変数
char dat[32];   // 格納用文字列
int count = 0;  // 文字数のカウンタ

void setup(){
  pinMode( LED_PIN, OUTPUT);
  pinMode( DIN_PIN, INPUT_PULLUP );
  Serial.begin(9600);
} 
void loop(){
  //文字列を読み込む
    Serial.flush();
    if (Serial.available()) {
    dat[count] = Serial.read();
    if (count > 30 || dat[count] == '=') {  // 文字数が既定の個数を超えた場合、又は終了文字を受信した場合 
     dat[count] = '\0';                    // 末尾に終端文字を入れる
      count = 0;                            // 文字カウンタをリセット
    Serial.println(dat);
    } else {
      count++;                              // 文字カウンタに 1 加算
    }
  } 
    //出力
    //読み込んだ文字が「on01」のときシリアルモニタに「ON」をさせ、LEDを点灯させる
    if(dat == 'on01'){
     digitalWrite(LED_PIN, HIGH);
    //LEDを点灯させた後okを返信
     Serial.print("ok\n");
     Serial.flush();
     }

    //読み込んだ文字が「off01」のときシリアルモニタに「OFF」をさせ、LEDを消灯する
    if(dat == 'off01'){
      digitalWrite(LED_PIN, LOW);
    //LEDを消灯させた後okを返信
      Serial.print("ok\n");
      Serial.flush();
    }
}
  • 気になる質問をクリップする

    クリップした質問は、後からいつでもマイページで確認できます。

    またクリップした質問に回答があった際、通知やメールを受け取ることができます。

    クリップを取り消します

  • 良い質問の評価を上げる

    以下のような質問は評価を上げましょう

    • 質問内容が明確
    • 自分も答えを知りたい
    • 質問者以外のユーザにも役立つ

    評価が高い質問は、TOPページの「注目」タブのフィードに表示されやすくなります。

    質問の評価を上げたことを取り消します

  • 評価を下げられる数の上限に達しました

    評価を下げることができません

    • 1日5回まで評価を下げられます
    • 1日に1ユーザに対して2回まで評価を下げられます

    質問の評価を下げる

    teratailでは下記のような質問を「具体的に困っていることがない質問」、「サイトポリシーに違反する質問」と定義し、推奨していません。

    • プログラミングに関係のない質問
    • やってほしいことだけを記載した丸投げの質問
    • 問題・課題が含まれていない質問
    • 意図的に内容が抹消された質問
    • 過去に投稿した質問と同じ内容の質問
    • 広告と受け取られるような投稿

    評価が下がると、TOPページの「アクティブ」「注目」タブのフィードに表示されにくくなります。

    質問の評価を下げたことを取り消します

    この機能は開放されていません

    評価を下げる条件を満たしてません

    評価を下げる理由を選択してください

    詳細な説明はこちら

    上記に当てはまらず、質問内容が明確になっていない質問には「情報の追加・修正依頼」機能からコメントをしてください。

    質問の評価を下げる機能の利用条件

    この機能を利用するためには、以下の事項を行う必要があります。

質問への追記・修正、ベストアンサー選択の依頼

  • koh_cafe

    2020/07/22 22:48

    thkana 様
    ご指摘頂きありがとうございます。
    内容理解しました。
    無作法ですみません。

    キャンセル

  • thkana

    2020/07/24 08:05

    ルールに「質問内容にマルチポストをする理由を書き、他のサイトの投稿へのリンクを貼ってください。また、解決した際には必ずteratail及びすべての投稿に解決した旨と、どのように解決したかを記載してください。」と書いてあるのは理解したのですね。
    理解したのなら実行して下さい。あちらで解決したようですし。(知恵袋で解決したので知恵袋に追記する必要はないでしょう)

    キャンセル

  • koh_cafe

    2020/07/24 09:03

    thkana 様
    何度もすみません。

    キャンセル

回答 3

checkベストアンサー

+2

最初の間違いは「文字リテラル(定数)」と「文字列リテラル」の区別が出来ていないこと。文字リテラル'A'は単に数値65の意味(ASCIIコード環境下。なお、''の中に複数の文字を入れてもエラーにはならないことが多いですが、その使い方は通常しない、と思っておいていいです。それでも本気で知りたければその旨聞いて下さい)。一方、文字列リテラル"A"はどこかにデータ'A''\0'を置いたそのアドレス(ポインタ)の意味になります。今回は文字列として比較するのですから、比較対象は文字列リテラル。

で、文字列の中身の比較をするのならstrcmp()関数を使うのがまず第一案。
strcmp()関数は、第1/第2パラメータの文字列が等しければ0を返すので
if(strcmp(dat,"on01")==0){ //datの中身が"on01"に等しければ
とします。
なお、dat=="on01"は文字列の在り処の比較なので、意図した動きにはなりません。

でも、せっかく文字列型が用意されているのだから使わなきゃもったいないし、String型を使うのならもっと使い込まなきゃもったいない。

まず。
String型に対する==演算子はArduinoでは文字列の中身の比較をちゃんと(?)やってくれます。
https://www.arduino.cc/en/Tutorial/BuiltInExamples#strings を読めば、

The operator == and the method equals() perform identically.

と書いてあります。つまり
if(String(dat)=="on01"){で先のstrcmp版と同様の結果が得られます。String::equals()で書きたいのなら別に止めませんが...

で、Stringを使うと、文字列の格納スペースとか、最後に'\0'をつけることとかは基本的には気にする必要がなくなります。なので、datをそもそもString型にして
String dat="";
として宣言すれば、countの管理は不要。さらにcがchar型の値とすれば
dat+=c;
で文字列を成長させることが出来ます。また、やりたければ行頭や行末のホワイトスペース(スペース、改行、水平タブ)を除去するtrim()を使ってdat.trim()なんてことも出来ます。
先程の比較も、わざわざString型のオブジェクトを生成する必要もなく、
if(dat=="on01"){でOK。

速度やメモリ容量に厳しいプログラム(あるいはC言語の基礎修行)でないのなら、String型を使っちゃえばよいのでは。

つぎに。
全体の構成の話ですが、現状は

  1文字受信して
  文字列を成長させて
 コマンド検出/実行


という構成になっています。でも、コマンド検出/実行は、文字列が確定してからやるものではないでしょうか?

  1文字受信して
 コマンド文字列確定? ならば
   コマンド検出/実行
 でなければ
   文字列成長


成長中の文字列を覗くから、途中で実行しちゃうみたいな現象が起こるのです。

話は変わって、
Serial.flush();。多分、OS環境下のCでprintf()がデータをバッファに貯めることがあるという話を聞きかじってそれを真似たものだと思いますが、それはあくまでその処理系でのprintf()関数の話。ArduinoのSerial::flush()は全く別の意味、用途を持っています。その意味、用途を以てここに使っているのですか? まぁ、この場合使っても害はなさそうですが...

もう一点、Serial.print("文字列\n"):としているのはなぜでしょう? 特別な意図が無いのなら、Serail.println("文字列");とした方がよろしいかと思います。これも大きな害はないですけれど。

そんなこんなを盛り込むと、loop()関数の一例としてはこんなところでは。

//入力された文字を入れる変数
String dat = "";
void loop() {
  //文字列を読み込む
  if (Serial.available()) {
    char c = Serial.read();
    if ( c == '=') { // 終了文字を受信した場合
      //dat.toLowerCase();//必要に応じて。全部小文字に変換
      Serial.println(dat);
      //出力
      //読み込んだ文字が「on01」のときシリアルモニタに「ON」を送り、LEDを点灯させる
      if (dat == "on01") {
        digitalWrite(LED_PIN, HIGH);
        //LEDを点灯させた後okを返信
        Serial.println("ok");
      } else
      //読み込んだ文字が「off01」のときシリアルモニタに「OFF」を送り、LEDを消灯する
      if (dat == "off01") {
        digitalWrite(LED_PIN, LOW);
        //LEDを消灯させた後okを返信
        Serial.println("ok");
      } else
      //入力確認
      //読み込んだ文字が「in01」のとき
      if (dat == "in01") {
        //DIN_PINの状態をon/offで返信
        int value;
        value = digitalRead( DIN_PIN );
        if ( value == HIGH ) { //HIGHがOFF,LOWがON
          Serial.println("off");
        } else {
          Serial.println("on");
        }
      }else{
        Serial.println("Command ERROR.");
      }
      dat = "";
    } else {
      if (dat.length() < 5) {//これ以下でないとコマンドに絶対に一致しないのでデータを保存する意味がない。
        dat += c;
        //dat.trim();//必要に応じて。前後のホワイトスペース文字を除く
      }
    }
  }
}

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2020/07/25 01:04

    thkana 様

    ご丁寧な説明を頂きありがとうございます。
    只今、全て意図した動作をすることが確認できました。

    例えばSerial::flush()やSerial.print("文字列\n"):を使用した意図の質問がありましたが、
    そもそもウェブにあるサンプルコードで動きを確認し、
    問題が無ければそのまま、動作しなければ調べる、
    といった作業をしていて、ひとつひとつのコードの意味をきちんと理解していないため、
    AのサンプルコードにBのサンプルコードの一部を追記するなどといった作業に対応出来ませんでした。

    意図した動作にはなりましが、まずはそれを理解して次のモノに進みたいと考えています。
    ありがとうございました。

    因みにSerial::flush()はArduino1.0以降は「データ送信完了するまで待つ」という機能かと思いますが、
    受信バッファをクリアする命令は無いのでしょうか。

    キャンセル

  • 2020/07/25 06:43

    > 受信バッファクリア
    関数としてまとまったものがあるかは知りません(Arduinoのライブラリを全部把握しているわけでもないので...)。少なくとも地道に
    while(Serial.available()){ Serial.read(); }
    とすればバッファクリアされます。

    キャンセル

  • 2020/07/26 17:46

    thkana 様
    ありがとうございました。

    キャンセル

+1

文字列の比較はString.equalsを使います。

String(dat).equals("in01");

この辺りを読んでください
https://www.arduino.cc/en/Tutorial/BuiltInExamples#strings
https://www.arduino.cc/reference/en/language/variables/data-types/string/functions/equals/


追記:コメント欄はフォーマットが効かないので、ここに書きます。
2020/07/23 03:55の返信です。

原因はよく分からなかったのですが、とりあえず以下の問題があり、これらをクリアしたら動きました。

  • バッファオーバーフローが発生しています
  • 見えない文字(改行など)を想定していません
  • 読み込みし終わる(想定?)前に1.2.3.が走ります
バッファオーバーフローが発生しています
char dat[4]; // 格納用文字列

読み込む文字は of01 等、終端文字を入れて5バイトなので、4バイトでは足りません。なので、count 等が破壊される可能性があります。
(質問文では32バイトなのに…)

見えない文字(改行など)を想定していません

シリアルモニターの設定で回避出来ますが、改行文字'\n'等を読み取ってしまいます。
in01=\n という文字列が来たとき、次にin01=\n を受けても、=の解釈時にdatは\nin01=となっているので、どれにもequalsになりません。

  if (Serial.available()) {
    int chr = Serial.read();
    if (chr >= 31) {  // 可視文字または半角スペースを受け取ったとき
      dat[count] = chr;
読み込みし終わる(想定?)前に1.2.3.が走ります

多分、on01xxxxxx な入力をしても反応するのでは?
読み込みが完了したかどうかのフラグを入れると良いと思います

void loop(){
  bool fire = false;

  // 略

      dat[count] = Serial.read();
      if (count > 30 || dat[count] == '=') {
        fire = true;

  // 略

  if (fire == true) {

    //1.読み込んだ文字が「on01」のときLEDを点灯させる
    if(String(dat).equals("on01") == true){
      // ...
    }
}

投稿

編集

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2020/07/23 03:55

    maai 様 ozwk 様
    アドバイス頂きありがとうございます。
    dat[0] = '\0'; により一部は動作するようになりました。

    文字を読込んだ後の処理が下記のように1, 2, 3とあるのですが、
    3をスケッチから削除すると1と2は正常に動作し、2を削除すると1と3は正常に動作します。
    しかしながら、1~3全てが同時にスケッチにあると、Serial.println(dat); のみが動作し、
    1~3は何も動作しなくなります。
    if文に何らかの制限などがあるのでしょうか。
    御教示頂けると幸いです。

    //IOのピン番号
    const int DIN_PIN = 7;
    const int LED_PIN = 6;

    //入力された文字を入れる変数
    char dat[4]; // 格納用文字列
    int count = 0; // 文字数のカウンタ

    void setup(){
    pinMode( LED_PIN, OUTPUT);
    pinMode( DIN_PIN, INPUT_PULLUP );
    Serial.begin(9600);
    }
    void loop(){

    //文字列を読み込む
    if (Serial.available()) {
    dat[count] = Serial.read();
    if (count > 4 || dat[count] == '=') { // 文字数が既定の個数を超えた場合、又は終了文字を受信した場合
    dat[count] = '\0'; // 末尾に終端文字を入れる
    count = 0; // 文字カウンタをリセット
    Serial.println(dat);
    Serial.flush();
    } else {
    count++; // 文字カウンタに 1 加算
    }

    //1.読み込んだ文字が「on01」のときLEDを点灯させる
    if(String(dat).equals("on01") == true){
    digitalWrite(LED_PIN, HIGH);
    //LEDを点灯させた後okを返信
    Serial.print("ok\n");
    Serial.flush();
    dat[0] = '\0';
    }

    //2.読み込んだ文字が「of01」のときLEDを消灯する
    if(String(dat).equals("of01") == true){
    digitalWrite(LED_PIN, LOW);
    //LEDを消灯させた後okを返信
    Serial.print("ok\n");
    Serial.flush();
    dat[0] = '\0';
    }

    //3.読み込んだ文字が「in01」のとき
    if(String(dat).equals("in01") == true){
    //DIN_PINの状態をon/offで返信
    if ( DIN_PIN == HIGH ){ //HIGHがOFF,LOWがON
    Serial.print("off\n");
    dat[0] = '\0';
    }else{
    Serial.print("on\n");
    Serial.flush();
    dat[0] = '\0';
    }
    }
    }
    }

    キャンセル

  • 2020/07/23 17:56

    回答に追記しました。実際にarduinoで走らせないと挙動がよく分からなかった。。。
    「3をスケッチから削除すると1と2は正常に動作し、2を削除すると1と3は正常に動作します。」は確かにそうだったのですが、なぜ正常動作するのかは分かりませんでした

    キャンセル

  • 2020/07/24 07:24

    maai 様

    何度もご回答を頂きございます。
    結果として未だに意図した動作が出来ていないのですが、
    (スケッチをアップロード後の最初のon01のみ動作しその後は動作しない)

    「バッファオーバーフローが発生しています」と
    「読み込みし終わる(想定?)前に1.2.3.が走ります」のふたつについては
    その意味と対処の方法を理解したつもりです。
    32バイトを4バイトに変えてしまったのはあれこれ試した後に戻し忘れてしまいました。

    「見えない文字(改行など)を想定していません」の内容について、
    受信した文字がズレていく現象は確認出来るのですが、
    対処方法を正しく理解しないままご教示頂いたものを挿入しているのが原因と思われます。

    もう少しやってみます。

    キャンセル

0

  • Serial.flush();はこれまで受信した文字を捨ててしまうのでこれを実行してはダメです。この行はコメントアウトしときましょう
  • 文字列の比較はstrcmp関数を使います

でもひとつ、
文字列の比較をループ回るたびに行ってますが、これを、
1行受信完了したときだけ、にするように、
また、
文字列の処理が終わったら、再度処理しないように(例えば受信バッファをクリアするなど)
するようにしましょう。

投稿

編集

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2020/07/22 08:02

    > Serial.flush();はこれまで受信した文字を捨ててしまうのでこれを実行してはダメです。この行はコメントアウトしときましょう

    について、あれ?と思ったのですが、指摘事項は、Arduino1.0より前の過去の仕様のようです。(機能仕様バグ?)今は、データ送信完了するまで待つそうです。

    Serial.flush
    https://www.arduino.cc/reference/tr/language/functions/communication/serial/flush/
    Waits for the transmission of outgoing serial data to complete.
    (Prior to Arduino 1.0, this instead removed any buffered incoming serial data.)

    キャンセル

  • 2020/07/22 08:10

    ご指摘ありがとうございます
    たしかにそうなってますね。

    #ましかし、いずれにしろ不要なものなので削除しとくがいいかと

    キャンセル

  • 2020/07/22 08:12

    > #ましかし、いずれにしろ不要なものなので削除しとくがいいかと

    なるほど、loop関数冒頭のSerial.flush()の部分ですね。確かに不要(意図不明)に見えますね。

    キャンセル

  • 2020/07/23 03:24

    dodox86 様 y_waiwai 様
    アドバイス頂きありがとうございます。

    キャンセル

15分調べてもわからないことは、teratailで質問しよう!

  • ただいまの回答率 88.91%
  • 質問をまとめることで、思考を整理して素早く解決
  • テンプレート機能で、簡単に質問をまとめられる

関連した質問

同じタグがついた質問を見る