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

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

ただいまの
回答率

88.80%

Arduino シリアル通信時のdelay処理でプログラムを止めない方法

解決済

回答 2

投稿 編集

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

Li3

score 9

前提・実現したいこと

当方、プログラミング初心者です。

PythonとArduinoのシリアル通信を行っており、
Pythonが送信した値により、Arduinoの指定のピンを出力します。

Pythonから送信される値のスパンは概ね10ms程です。
この時、下記コードのCase1の場合のみ出力間隔を1000msにしたいです。

イメージとしては、

  1. PythonからCase1の信号が連続されて送信される場合は出力を1000ms間隔
  2. Case2,3は10ms間隔
  3. Case1の1000msの待ち時間中にCase2,3が送信された場合はCaseを中断してCase2,3の処理を行う

ただし、Python,Arduinoの処理を止めずにというのが前提です。
また、1000ms出力し続ける必要はなく1パルスで問題はありません。

また全ての信号を拾う必要はありません。
(その都度の信号を処理できれば良いので待ち時間中の信号は捨ててOKです。
ですので上記のイメージでなくでも実現できるかもしれません)

その際の問題については次に記載します。

具体的なコードを挙げていただけると嬉しく思います。

発生している問題・エラーメッセージ

Python,ArduinoともにCase1の場合、1000msプログラムがストップしますので
その期間は何も処理ができなくなる点が問題です。

該当のソースコード

void setup() {
  Serial.begin(9600);
  pinMode(1, OUTPUT);
  pinMode(2, OUTPUT);
  pinMode(3, OUTPUT);

}

void loop() {
  byte var = Serial.read();
  var = var - 0x30;

  switch (var) {
    case 1:
      Serial.write(1);
      digitalWrite(1, HIGH);
      delay(1000);
      digitalWrite(1, LOW);
      break;

    case 2:
      Serial.write(2);
      digitalWrite(2, HIGH);
      delay(10);
      digitalWrite(2, LOW);
      break;

    case 3:
      Serial.write(3);
      digitalWrite(3, HIGH);
      delay(10);
      digitalWrite(3, LOW);
      break;
}

試したこと

■delay関数を使用した場合
当然ですが、処理が1000ms間ストップすることになります。
Python側のプログラムも1000ms間ストップする為、delay関数は使えませんでした

■外部ライブラリCopyThreadsを使用した場合
これも同様でした

補足情報(FW/ツールのバージョンなど)

  • 気になる質問をクリップする

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

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

    クリップを取り消します

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

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

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

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

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

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

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

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

    質問の評価を下げる

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

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

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

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

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

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

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

    詳細な説明はこちら

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

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

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

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

  • Kenji.Noguchi

    2020/05/16 13:35

    この類のアプリはイベント駆動型で書くのが通例でしょう。Arduino event driven libraryとかframeworkなどで検索するとすぐにヒットします。

    キャンセル

  • Li3

    2020/05/16 14:37

    回答ありがとうございます。
    申し訳ですが調べたところパッとは理解できなかったので
    時間をかけて学んでいこうと思います。

    キャンセル

  • thkana

    2020/05/16 17:20

    内容に関してpythonは全然関係ないと思います。タグは外してはいかがですか?

    キャンセル

  • Li3

    2020/05/16 20:10

    すみません、削除しました

    キャンセル

回答 2

checkベストアンサー

+1

質問のArduinoのコードは本当に動かしたものですか? イメージだけならそう明記して頂いたほうが悩まなくていいかな、と思うのですけど。最も一般的なArduino UNOであれば、SerialのTXとpin 1は共用(切り替え)ですから、pin1にLEDを繋ぎつつSerial.write()は成り立ちません。

delay処理でプログラムを止めない方法

delayはプログラムの動作を止める命令(関数)ですので、この時点で言っていることが滅茶苦茶です。

でも、例えば、1000msを待つのに1000ms一回ではなく10ms待ちを200回、という手段もあるわけです。1000ms連続で眠り込んでしまえばその間なにもできませんが、10ms毎に目を覚ませば、その間になにかしら作業を行うことはできます。

それを用いて、「起動後1秒間だけLEDが点灯する」というプログラムを考えるなら

const int DLY=10;
const int PERIOD=1000;
const int LEDPIN=13;

int count;
int ledStat=1;

void setup(){
  pinMode(LEDPIN,OUTPUT);
  count=PERIOD/DLY;
}

void loop(){
  if(count > 0){
//この処理がcountが0になるまで、PERIOD/DLY回行われる。結果、1秒間LEDが点灯
    ledStat = 1;
    count--;
    delay(DLY);
  }else{
    ledStat = 0;
  }
  digitalWrite(LEDPIN,ledStat);
}


では、シリアルで'1'を受信してから1秒間点灯してみましょうか。

const int DLY = 10;
const int PERIOD = 1000;
const int LEDPIN = 13;

int count;
int ledStat = 1;

void setup() {
  Serial.begin(9600);
  pinMode(LEDPIN, OUTPUT);
  count = PERIOD / DLY;
}

void loop() {
  byte var = Serial.read();
  switch (var) {
    case '1':
      count = PERIOD / DLY; //countに1秒間動作を設定
      break;
    default:
      break;
  }
  if (count > 0) {
    ledStat = 1;
    count--;
    delay(DLY);
  } else {
    ledStat = 0;
  }
  digitalWrite(LEDPIN, ledStat);
}


これだと、1を連続受信するとそこから1秒間点灯します。
もし最初に1を受信してから1秒間で消灯したいなら、カウントダウン中はカウンタに新しい設定をしなければいいわけで、

//loop()関数のみ変更
void loop() {
  byte var = Serial.read();
  switch (var) {
    case '1':
      if (count <= 0) { //カウントダウン中でないときだけ
        count = PERIOD / DLY; //1秒間動作開始
      }
      break;
    default:
      break;
  }
  if (count > 0) {
    ledStat = 1;
    count--;
    delay(DLY);
  } else {
    ledStat = 0;
  }
  digitalWrite(LEDPIN, ledStat);
}


あるいは、'1'を受信したときだけ10msLEDを点灯、その後1秒間'1'に反応させないととしたら

//loop()関数のみ変更
void loop() {
  byte var = Serial.read();
  ledStat=0; //基本LEDは消灯
  switch (var) {
    case '1':
      if (count <= 0) {
        count = PERIOD / DLY;
        ledStat=1; //1DLY周期だけLED点灯
      }
      break;
    default:
      break;
  }
  if (count > 0) {
    count--;
    delay(DLY);
  }
  digitalWrite(LEDPIN, ledStat);
}


これに'2'とか'3'への対応を盛り込んでみれば、こんなところでしょうか。

const int DLY = 10;
const int PERIOD = 1000;
const int LEDPIN[3] = {2, 3, 4};

int count;
int ledStat;

void setup() {
  Serial.begin(9600);
  for( int pin : LEDPIN){ //LEDPINの各要素について
    pinMode(pin, OUTPUT);
  }
  count = PERIOD / DLY;
}

void loop() {
  byte var = Serial.read();
  ledStat = 0; //基本はLED消灯
  switch (var) {
    case '1':
      if (count <= 0) {
        count = PERIOD / DLY;
        ledStat = 1; //LED1を点灯
      }
      break;
    case '2':
      //カウントダウン中でもおかまいなし
      count = 1; //カウントダウン回数1
      ledStat = 2; //LED2を点灯
      break;
    case '3':
      count = 1;
      ledStat = 3; //LED3を点灯
      break;
    default:
      break;
  }
  if (count > 0) {
    count--;
    delay(DLY);
  }
  digitalWrite(LEDPIN[0], ledStat==1);
  digitalWrite(LEDPIN[1], ledStat==2);
  digitalWrite(LEDPIN[2], ledStat==3);
}

投稿

  • 回答の評価を上げる

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

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

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

  • 回答の評価を下げる

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

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

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

  • 2020/05/16 21:11

    回答ありがとうございます。順序立てて説明頂き、プログラム初心者でも理解できました。
    経過時間を測って処理をするという事に囚われていたのですがこんな方法もあるのですね!
    大変恐縮ではございますがフォローさせて頂きました。今後とも宜しくお願い致します。

    キャンセル

  • 2020/05/17 03:19

    もし12121212と言う文字が送られたきたときはLED1は1秒以下の間隔で出力されることになりますが良いのでしょうかね?「Caseを中断してCase2,3の処理を行う」とのことなので、それで良いのかもしれませんが。

    キャンセル

  • 2020/05/17 07:03

    Kenji.Noguchiさん
    そう解釈しています。'1'が「連続されて送信される場合」ともありますので、他の解釈は私には出来ませんでした。

    キャンセル

  • 2020/05/17 07:36

    > 経過時間を測って処理をするという事に囚われていた
    回答のプログラムだって、「短時間のdelayの回数を数える」という方法で経過時間を測っているわけで、millis()を使ったとしてもそんなに大きくプログラムが変わるわけではありません。

    キャンセル

+1

delayを使うと止まるので使ってはダメです
待つ時間分の時刻を算出して、その時刻に到達したかどうかを判定するようにし、到達してない場合はそのままリターンするなり終了するなりしましょう


unsigned long timo_set(int ms)
{
    return mills()+ms;
}

bool timo_chk(unsigned long tim)
{
    long a=tim - mills();
    return a<0;
}

unsigned long timer;
int flag;

void loop() {
    if(flag){
      if(timo_chk(timer)){
        digitalWrite(1, LOW);
        digitalWrite(2, LOW);
        digitalWrite(3, LOW);
      }
      flag=false;
     }
  int var = Serial.read();
  if(var<0) return;

  switch (var) {
    case '1':
      Serial.write(1);
      digitalWrite(1, HIGH);
      timer=timo_set(1000);
        flag=true;
      break;

    case '2':
      Serial.write(2);
      digitalWrite(2, HIGH);
      timer=timo_set(10);
        flag=true;
      break;

    case '3':
      Serial.write(3);
      digitalWrite(3, HIGH);
      timer=timo_set(10);
        flag=true;
      break;
}


#素で書いてるので動作の保証はしない

投稿

編集

  • 回答の評価を上げる

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

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

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

  • 回答の評価を下げる

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

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

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

  • 2020/05/16 13:42

    いつも回答ありがとうございます。
    経過時間の測定としてmillis() を使用しようと思ったのですが、
    breakで処理を終了させている為、累積時間がリセットされます。
    また、breakを使用しない場合、処理が重くなってしまい
    (Python側の信号スパンのせいだと思います)走らせることができませんでした。
    お忙しいところ恐縮ですが、解決のための具体的なコードはご教示願えませんでしょうか。。。

    キャンセル

  • 2020/05/16 13:47

    累積時間がリセットされるってのがちと意味不明ですが、判定時刻はグローバル変数にしないとダメですよ
    python にも、ミリ秒単位の時間読み出し関数があるんでそれを使いましょう

    キャンセル

  • 2020/05/16 14:26

    時間に関する関数を調べましたがmillis以外に使えそうなものが
    見つける事ができなかったので申し訳ございませんが
    可能であればでかまいませんので質問に記載したソースコードを
    踏まえた具体的なコードで教えていただけませんか。

    キャンセル

  • 2020/05/16 21:06

    お忙しい中、コードの記載ありがとうございます。しばらくいろいろと変更して
    試していたのですがbool型のif(timo_chk(timer))の部分で出力をLOWにすることができませんでした。
    理解力がなくて申し訳ございません。。。

    キャンセル

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

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

関連した質問

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