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

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

ただいまの
回答率

88.32%

C言語 Arduino サーボモータの停止指令プログラム

解決済

回答 3

投稿

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

himuka55

score 1

前提・実現したいこと

Arduinoでサーボモータ制御の学習をしています。
リモコンスイッチで動作指令しています。連続作動中にリモコンスイッチ
例えば4番を押すことで途中停止させようとプログラム作りに挑戦していますが
うまくいきません。
今のプログラムは以下の通りです。回転速度を調整できるプログラムです。
リモコン作動のプログラムはネット上から拝借したものです。
プログラム後半の他のスイッチを押すとfor(;;)で連続動作するようにしてます。
いまのプログラムでリモコンスイッチを押すことによるサーボモータ動作は
問題ありません。
for(;;)で連続回転している途中で例えば4番スイッチを押すと
途中停止させるプログラムが分かりません。
いろいろbreak とか digitalWrite(...LOW)とか試しましたが
うまくいきません。 なお
サーボモータはTower Pro SG92Rです。
教えてください。よろしくお願い致します。

include <VarSpeedServo.h> //2servo speed control rimokon    

//1番SWONで緑点灯、2個順次サーボ動作連続
//2SWで赤LED点灯消え、3SWで緑LED点灯消え、その他swで赤LED点灯2番サーボ連続動作 OK 2020.10.18
VarSpeedServo myservo1;    // インスタンス作成
VarSpeedServo myservo2;

define INFRARED        11  // 赤外線センサー

define RECEIVE_OK      3  // 緑LED

define RECEIVE_ERROR   4  // 赤LED

void setup() {
Serial.begin(9600);   
// 赤外線
pinMode(INFRARED, INPUT);
pinMode(RECEIVE_OK,OUTPUT);
pinMode(RECEIVE_ERROR,OUTPUT);  
myservo1.attach(9);    //servo1 D9ピンをサーボ1の信号線
myservo2.attach(10);  //servo2 D10ピンをサーボ2の信号線
}
void loop() {
unsigned long dword = 0, pls;
// リーダーコードの開始(8T = 9ms)
while(digitalRead(INFRARED) == HIGH){
// none
}
// リーダーコードの終了(4T = 4.5ms)
while(digitalRead(INFRARED) == LOW){
// none    

// NECフォーマットの読み込み(32bit)
// (16bitのカスタマーコード(メーカー識別コード) + 8bitのデータコード + 8bitの反転データコード)
for(int i = 0; i < 32; i++) {       
// パルスの検出(パルスの長さ)    
// ※戻り値はマイクロ秒(μs)
pls = pulseIn(INFRARED, HIGH);          
// パルス検出のタイムアウト(1秒)
if(pls >= 1000000) {
Serial.println("timeout");   
return;                
}
// ビットが1の場合にデータを加算する
// ※ビット0= 1125μs ビット1 = 2250μs
if(pls >=  1126){
// Serial.println(pls);   
dword |= 1UL << i;  
}
}
// 待機状態のエラーを回避
// ※このスケッチ固有のエラーです。
if(dword == 0){return;}
// リピートコード(9ms + 2.25ms)
pulseIn(INFRARED, HIGH);  
// ストップビット(0.56ms)
while(digitalRead(INFRARED) == HIGH){
// none

// データコードが正しく受信できているかを確認
byte bit8_data1 = (dword >> 16) & 0xFF;         // データコード
byte bit8_data2 = 255 - ((dword >> 24) & 0xFF); // データコードの反転
if(bit8_data1 == bit8_data2){
// 送信波形を反転する
dword = (dword  >> 24)  & 0xFF        |
((dword >> 16)  & 0xFF) << 8  |
((dword >>  8)  & 0xFF) << 16 |            
(dword & 0xFF)         << 24;   
// デバッグ用         
//リモコンキー1番
Serial.println(dword ,HEX);      
if(dword == 0xFF0CF3){
digitalWrite(RECEIVE_OK,HIGH);//sw1押すと緑LED点灯してサーボ1動いて次に2が動いて停止 緑led off
delay(300);
myservo1.write(180,100,true); 
myservo1.write(0,100,true);
delay(2000);
myservo2.write(180,100,true); 
myservo2.write(0,100,true);
delay(2000);      
digitalWrite(RECEIVE_OK,LOW);
delay(2000); //緑LED消える
}
else if(dword == 0xFF18E7) //2のスイッチ押すと赤1LED点灯してすぐ消える
{
digitalWrite(RECEIVE_ERROR,HIGH);
delay(1000);     
digitalWrite(RECEIVE_ERROR,LOW);
}
else if(dword == 0xFF5EA1)//3のスイッチ押すと緑LED点灯してすぐ消える
{
digitalWrite(RECEIVE_OK,HIGH);
delay(1000);     
digitalWrite(RECEIVE_OK,LOW);
}
else {           //他のsw押すと赤LED点灯2サーボが連続動作
digitalWrite(RECEIVE_ERROR,HIGH);
delay(300);
for(;;)
{
myservo2.write(180,100,true); 
delay(1000);
myservo2.write(0,100,true);
delay(1000);
//if (dword == 0xFF0CF3) 連続作動中に例えばsw1で停止させる方法が分からない
// digitalWrite(9,LOW);
}
}
}
}  

ここに質問の内容を詳しく書いてください。
(例)PHP(CakePHP)で●●なシステムを作っています。
■■な機能を実装中に以下のエラーメッセージが発生しました。

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

エラーメッセージ

該当のソースコード

ソースコード

試したこと

ここに問題に対して試したことを記載してください。

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

ここにより詳細な情報を記載してください。

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

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

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

    クリップを取り消します

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

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

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

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

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

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

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

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

    質問の評価を下げる

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

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

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

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

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

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

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

    詳細な説明はこちら

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

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

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

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

  • y_waiwai

    2020/10/21 08:20

    このままではコードが読みづらいので、質問を編集し、<code>ボタンを押し、出てくる’’’の枠の中にコードを貼り付けてください

    キャンセル

回答 3

+2

ヘルプ等を参考にソースコードを掲載して下さい。特に#記号は特別な意味を持っているので悲惨なことになっています。

で。今のプログラムは

void loop(){
  //リモコンコードの受信
    //受信したコードをdword変数の設定
  //受信したコードによる動作
    for(;;){
      //サーボの操作
    }
}


という構造になっていますから、一度for(;;)のループに入ってしまうとリモコンの受信も、コードの更新も行われないのでリモコンの操作で脱出は不可能です。
しかも、リモコンコードの受信部分は

  // リーダーコードの開始(8T = 9ms)
  while (digitalRead(INFRARED) == HIGH) {
    // none
  }
  // リーダーコードの終了(4T = 4.5ms)
  while (digitalRead(INFRARED) == LOW) {
    // none
  }


となっているため、リモコン信号を受信するまでは先に進まない構造です。
つまり、小手先で

いろいろbreak とか digitalWrite(...LOW)とか試し

てもダメです。根本的に構造を考え直さないと。例えば

void loop(){
  if( digitalRead(INFRARED) == HIGH){//何か受信している?
    //リモコンコードの受信
  }else{//受信していない
    switch(直前に受信したリモコンコード){
      case キー1:
        //キー1を受信したときの処理。ただしここに滞留してはいけない。
        //そのためdelayを使用しないで、loop()が繰り返し実行される
        //ことを前提にmillis()等を用いて時間を測って動作を切り替える
      break;
      case キー2:
        //キー1と同様
      break;
      case キー3:
        //同様
      break;
      default: //他のキー
        //同様
   }
}


のようなことにすることでなんとかならないでしょうか。

loop()の繰り返しを前提に時間を測る、というのは例えばLチカで

unsigned long refTime; //時間を測る原点を格納
bool ledStat=true;
void setup(){
  //他の処理
  refTime=millis(); //時間を測る原点を設定
}
void loop(){
  unsigned long curretTime;
  currentTime=millis();
  if(currentTime-refTime>1000){//前回から1秒以上経った
    ledStat=!ledStat; //LED状態を反転
    refTime=currentTime; //時間を測る原点を再設定
  }
  digitalWrite(RECEIVE_OK,ledStat);//LED状態を反映
}


のようにして、プログラム中にループやdelay()で留まる場所を作らずに時間の管理をする、ということです。

投稿

  • 回答の評価を上げる

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

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

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

  • 回答の評価を下げる

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

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

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

  • 2020/10/21 20:35

    返信ありがとうございます。
    void loop(){
    if( digitalRead(INFRARED) == HIGH){//何か受信している?
    //リモコンコードの受信
    }else{//受信していない
    switch(直前に受信したリモコンコード){
    case キー1:
    //キー1を受信したときの処理。ただしここに滞留してはいけない。
    //そのためdelayを使用しないで、loop()が繰り返し実行される
    //ことを前提にmillis()等を用いて時間を測って動作を切り替える
    break;
    case キー2:
    //キー1と同様
    break;
    case キー3:
    //同様
    break;
    default: //他のキー

    delayを使わないでmillisを使い時間を測って...
    私は初心者ですのでよく意味がわかりませんがなんとか挑戦したいと思います。
    switch case break など初めてですがトライしてみます。
    ネット上で参考にあるものがあれば紹介お願い致します。
    なお、参考書として
    「arduinoでロボット工作をたのしもう第2版」秀和システム 鈴木美朗志著
    「やさしいC 第4版」高橋麻奈著 などがあります。
    よろしくお願い致します。

    キャンセル

checkベストアンサー

+1

これだと、連続動作中もそうなのですけど、通常の動作中もリモコン操作は受け付けないですよね?
必ず一連の操作が終わらないとリモコン受信にならないし、逆にリモコン受信の所に行けばそれ以外の動作は出来ません。
簡単に言えば、この手法だと詰んでいる=スケッチを大きく変えないとならない、という事です。

パッと思い付く方法は3つですね。
・リモコン受信に外部割り込みを使う。※外部割り込みでは直ぐcliにして、サーボライブラリが動くようにする。
・タイマー割り込みで、サーボ処理を行う。=リモコン部はそのまま
・0.1~0.3ms程度のポーリング(タイマ割り込みでもloop関数でも良い)で、リモコン、サーボ操作の両方とも書き換え

今後の事を考えれば、ポーリングでリモコン受信させた方が良さそうには思いますけど…
いずれにしても、現在のサーボ処理のスケッチは割り込み処理の形(カウントして計測する)に変える事になると思います。

※VarSpeedServoライブラリが裏でタイマ1を使い、タイマ割り込みで動作している、という事も頭に入れておいてください。

<追記1>
外部割り込みでリモコン取得も、開始信号から信号最期、或いはエラーまで専有するやり方と、毎回時間を計測して、直ぐに終了するやり方があると思うけど、
前者なら、現在のスケッチからの変更は少ないです。けれども、他の動作に支障が出やすいです。
後者なら、書き直しになりますけど、こちらの方がベーシックなやり方だと思いますし、他のスケッチと共存し易いです。

で、後者の方を教えます。

まず、リモコン通信のフォーマットを確認して下さい。
ChaNさんの所が参考になります。http://elm-chan.org/docs/ir_format.html

そこに書いてあるようにNECフォーマットの場合は562μsが基準となり、信号の長さで0と1が決まります。
受信側からけば、HIGHの長さが基本ですから、HIGHの長さだけ測っておけばいいです。
※送信信号が有る時は、受信側はLOWです。何もないときはHIGHです。その図からは反転します。
(といっても、受信側の回路にもよりますけど)

考え方は、HIGHになった時に、測定開始時刻を記録、LOWになった時に、HIGHの時間を算出。
その時間が、ちゃんとフォーマットの通りに来ていれば記録を続けるけれども、一度でも範囲外なら、そこまでのデータは捨てます。
で、32bit分送られて来たら、どのSWかの判定です。

実際のRAWデータで、どの位の時間なのかを確認した方が良いです。案外いい加減なものですから、範囲を外している場合があります。

↓が外部割り込み関数のサンプルです。
※つらつらと書いただけで、検証はしていません。内容を読み取ってください。

volatile byte ir_num=0;//割り込みとloop関数両方で参照する変数は外部変数で、volatileを付ける。

void recv() {//外部割り込み関数
    static byte state=0;//0:測定前、1~:受信中
    static uint32_t up=0,length=0,result=0;

    if(digitalRead(pin))up=micros();
    else {
        length=micros()-up;
        if(length>4000 &&length>5000)state=1;//開始条件8T=4496
        else if(state) {
            if(length>460 && length<760)state++;//T=562
            else if(length>1400 && length<1900) {//3T=1686
                result|=(0x80000000UL>>(state-1));
                state++;
            } else {//範囲外ならやり直し
                state=0;
                result=0;
            }
            if(state==33) {//データが揃ったら
                switch (result) {
                case 0xFF0CF30D:
                    ir_num=1;
                    break;
                case 0xFF18E718:
                    ir_num=2;
                    break;
                case 0xFF5EA15E:
                    ir_num=3;
                    break;
                default:
                    ir_num=4;

                }
                state=0;
                result=0;
            }
        }
    }
}

で、この結果(ir_num)を受けて、サーボを操作するわけですけど、リモコン信号が来た場合に、
・即時に停止、或いは次の動作へ移行。(停止の場合はその場に停止か、HOME位置へ戻すか?)
・サイクルを実行してから次の動作平行
があると思います。
(現在はサイクルな訳ですけど。その場合、ちゃんと受信しているのか判りにくくなります。
今回のトグル動作とかも、長いサイクルの場合は判り難く、2回押す事も考えられます)

もう一点、現在、myservo○.write(○,○,true);(終わるまで待機)を使っていますけど、
1台のサーボなら兎も角、複数だと、1台ずつしか動かせません。
本当にそれで良いのでしょうか?

(つまり、現在のスケッチが、狙い通りの動きなのか、書き方が判らないからそうなっているのかが判断付きません)

処理的には複雑にはなりますけど、2台を別のタイミングでそれぞれ同時に操作する事は可能です。

そのあたりをちょっと聞いてから追記します。
※「sw1で停止」のみの対応なら簡単です。

投稿

編集

  • 回答の評価を上げる

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

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

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

  • 回答の評価を下げる

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

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

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

  • 2020/10/21 11:59

    早速の返信ありがとうございます。全くの初心者ですのでよろしくお願い致します。
    1番目のリモコン受信に外部割込みを使う方法にまずトライしたいと思います。
    割り込みは具体的にはどういうコマンドを使うのですか? 当方は現在、参考書として
    「arduinoでロボット工作をたのしもう第2版」秀和システム 鈴木美朗志著
    「やさしいC 第4版」高橋麻奈著 などがあります。
    ネット上で参考になるものがあればご紹介お願い致します。

    キャンセル

  • 2020/10/22 11:03

    ご指導ありがとうございます。
    ①リモコン通信のフォーマット確認ですがオシロスコープなどで計測しないと具体的な数値は分からないのではないでしょうか? 現状のプログラムでリモコンスイッチ番号とシリアルモニタの表示がプログラム通りになっているのでNEC方式と考えていいのではないでしょうか。
    ②割り込みプログラム
      初心者の私には相当レベルが高いので理解に苦しんでいます。
      できる限りトライしたいと思います。
    ③myservo○.write(○,○,true);
      サーボモータの速度制御をするためにVarSpeedServo.hを使っています。
      このコマンド myservo1.write(180,100,true)は1個づつしか動かないのですか。
      同時に2個以上動かすためには速度指定ができない servo1.write(180); delay(100);
    servo2.write(90); delay(100); ...にするということですか?
      私は実際は4足歩行ロボットを作ろと思っています。全部で8個のサーボモーターを
      制御しようと思っています。ハードつくりも並行して進めています。
     よろしくお願い致します。

    キャンセル

  • 2020/10/22 12:57 編集

    ①いやいや、普通にスケッチを書けば良いだけですよ。
     ↑でもHIGH長さを測っていますから、その後に
     Serial.println(length);
     を付けて、シリアルモニタで実際の長さ、バラつきを確認します。
     でそれを元に、多少if(○○<length && △△>length)という部分の値を調整します。
     勿論、想定通りなら修正する必要は無いです。


    ②↑の外部割り込みのスケッチの事を言っているのですよね?
     明確に書いていないですけど、ピン変化(CHANGE)=↑↓の両方で割り込み関数に飛ぶようにします。
     attachInterrupt (0, recv, CHANGE);//int0はD2ピン(UNOなら)
    とかをsetup関数に記述します。
     
     で、まずは
    > if(digitalRead(pin))up=micros();//↑(LOWからHIGHへの変化)の時は開始時刻upを記録
    > else {
    > length=micros()-up;//↓(HIGHからLOWへの変化)の時はHIGHのパルス長(length)を算出

    です。後はこのパルス長lengthが規格通りかどうかを判断します。
    stateは、0は何も情報が無い、無通信状態です。スタートのシグナル(8T)が来たら1です。後は1bitずつ情報が入れば1増えます。
    ※開始時のresultが0なので、そのピットが0の時は、stateをインクリメントするだけ、1の時だけ、resultのそのビットを1にします。
    途中で規格外が来れば最初からやり直しでstateは0に戻します。(resultもやり直しなので0にする)
    stateが33になったら、32ビット情報を取得した事を意味します。


    ③勿論、ライブラリ的にはサポートしています。同時に動かす事も可能です。ただし、
     myservo1.write(180,100,true)の最後の「true」は、「移動終了までここに留まる(監視する)」という意味です。
     その間は他には何も出来ないので、同時に動かす事は出来ません。。
     この書き方をしている内は、8台制御するにも、「1台ずつの切り替え」になってしまいます。
     
     例えば、サーボ1,2共に0の位置にいるとして、
     myservo1.write(180,50);
     delay(500);
     myservo2.write(180,50);
     とすれば、サーボ2が0.5秒遅れで同じように(同時に)動く、という事は判ると思います。
     ※ただしこれでも、delay(500)があるので、その間は他のサーボは動かせない事になります。
     
     別の書き方をすれば
     myservo1.write(180,50);
     while(myservo1.read()<60);//サーボ1が60度になるまで待機
     myservo2.write(180,50);
     で、凡そ60度ずれた状態で回ります。
     ※ただし、この方法もまた、60度になるまでそこで足止めをくらいます
     
     要は、どう動かしたいのか?です。一連の動作サイクルをする必要があるのか?それとも、リモコンカーのように、
     瞬時に命令を実行して欲しいのか?で、組み方が変わります。
     
     ※trueを使う手法が駄目と言っている訳ではありません。「現在の内容=やりたい動作」なら、それが簡単に書けてBESTとも言えます。

    キャンセル

0

for(;;)

で無限ループさせてしまうと、そこで処理が止まってしまい、他の動作ができなくなりますねー
ここらへん、考え方からまるっきり変える必要があります

投稿

編集

  • 回答の評価を上げる

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

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

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

  • 回答の評価を下げる

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

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

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

  • 2020/10/21 20:00

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

    キャンセル

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

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

関連した質問

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