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

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

ただいまの
回答率

87.92%

3つある波形をプログラムで表したい。

受付中

回答 4

投稿 編集

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

score 6

![イメージ説明](87314f8c4bbb0c88d452a85d826a93c0.jpeg)イメージ説明イメージ説明

扇風機には三枚の羽根が付いています。羽根にはそれぞれ金属板が貼ってあり、1の羽根に識別のために異形形態にしています。
波形を見るセンサーは扇風機の真上0度の位置に取り付けています。回転スピードは一定していません。(リズムボタンがあり、早くなったり遅くなったりです)。LEDの点灯順番は1,3,2,1,3,2・・・・です。
例えは1の波形がオフになった瞬間2の羽根が280度の位置に来るのでこの場合点灯しません、2の羽根がオフになった瞬間3の羽根が280度の位置に来るのでLEDをON,300度の位置でOFFします。これをプログラムで書けないかと思っています。よろしくお願いします。

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

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

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

    クリップを取り消します

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

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

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

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

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

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

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

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

    質問の評価を下げる

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

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

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

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

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

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

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

    詳細な説明はこちら

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

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

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

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

  • thkana

    2020/09/05 11:21

    > 角度は特に限定していません。
    何の角度ですか? 主語を略されるとわかりません。直前の文は「原点」あるいは「センサー」が主題でしたが、原点は原点で動きませんし、センサーは既に原点に取り付けられています。

    > 任意に変更できるようにしたいのです。
    同様に、変更する要素は何ですか?

    > 消灯のタイミングはパルス幅の真ん中に来た時です。
    どのパルスですか?

    一周に一回、Z相だけセンスして、その周期から角度を求めて制御すればよいものを3枚の羽根それぞれをセンサーに反応させるようにしたが故に話が面倒になっている気がします。
    それぞれの羽根につけるにしても、切り欠きができるくらいなら、パルスの立ち下がりが羽根の真ん中の位置になるようにすることもできるはずですね? そうすれば、「羽根の真ん中」を調べる必要もなくパルスの立ち下がりで制御すればいいだけ。

    あちこちで「わざわざ話を面倒にしている」気がして仕方ありません。機構上出来ない、というのなら仕方ありませんが、どうなんでしょう。

    キャンセル

  • thkana

    2020/09/10 21:55

    3つ検出部がある理由はまぁわかりましたが。

    > LEDの点灯順番は1,3,2,1,3,2・・・・です。
    別に1の位置を識別しなくても、センサ1回おきにLEDを点灯すればいいだけなのではないですか? 1を知らなきゃいけない理由はなんでしょう?

    > 1の波形がオフになった瞬間2の羽根が280度の位置に来るのでこの場合点灯しません、
    > 2の羽根がオフになった瞬間3の羽根が280度の位置に来るのでLEDをON
    「ので」というのはLEDを点灯したりしなかったりする理由を表しているはずですよね? この2つの文で、LEDをONとOFFをわける条件が全く見えないのですが?

    点灯/消灯の角度は任意ではなかったのでしたっけ? 変動させたいパラメータはわかるように図に書き込んでいただけませんか。

    キャンセル

  • DMR

    2020/09/11 00:59

    1の位置を識別しているのはLEDの色を分けたいからです。1は赤、2は青、3は緑です。LEDを点灯する順番は、私がそうしたいからだけなんです。
    LEDをoffするタイミングの式と変数T1を図に入れておいたので見てくれますか。

    キャンセル

回答 4

+3

第二次大戦ぐらいの戦闘機が、プロペラの回転の隙間を縫って機関銃を撃った、なんていう話を思い出したり。もちろん当時はマイコンなんかないから全部機械仕掛けでしょうけど。

センサはちゃんと出来ているのなら、パルスの立ち上がり立ち下がりでタイマをキャプチャして、その値をみて判断すればよいのでは。

最初は回転数とか知りませんから、立ち上がり-立ち下がりの間隔を4回測定します。
そのパルス幅を見れば1の羽根を判別できるでしょう。残りの羽根のパルス幅もわかります。次の周で、短いパルス幅を検出したらLEDを点灯して、先の測定で残りの羽根のパルス幅の半分でタイマとのコンペアかなにかで割り込ませてLEDを消灯すれば。もちろん、この間も「次の周」に備えて各パルス幅は測り続けます。

あるいはタイマ使わなくてフリーランのループでも測れるかなぁ? その切れ込みパルスは一周に対してどういう比率になるのでしょう?


(追記)質問と条件が変わってしまいますが、センサーは羽根1枚だけを、切込み無しで検出するという前提で簡易的(=タイマ使わなくてフリーランのループ)なプログラムを試してみました。それでこれだけ出来るのですが、これじゃダメなんですか?

おおよそ9ms毎に1msのパルスを作って、以下のプログラムに突っ込んでみました。Arduino UNOなので16MHzのAVR 328Pです。

void setup() {
  pinMode(2, INPUT);
  pinMode(13, OUTPUT);
  Serial.begin(115200);
}
const unsigned int LED_ON_PHASE = 0;
const unsigned int LED_OFF_PHASE = 180;
unsigned int count;
unsigned int totalCount;
unsigned int dummy1, dummy2;

int lastStat;

void loop() {
  int stat = digitalRead(2);
  if (!stat) {
    if (lastStat) {
      totalCount = count;
      count = 0;
      //      Serial.println(totalCount);
    } else {
      dummy1 = count;//プログラムの経路によって実行時間が変わるのを緩和するためのダミー
      dummy2 = 0;
    }
  } else {
    if (lastStat) {
      dummy1 = count;
      dummy2 = 0;
    } else {
      dummy1 = count;
      dummy2 = 0;
    }
  }
  int led = ((unsigned long)totalCount * LED_ON_PHASE / 360 < count) &&
            (count < (unsigned long)totalCount * LED_OFF_PHASE / 360);
  digitalWrite(13, led);
  count++;
  lastStat = stat;
}


これで、コメントしてあるSerial.printlnを活かすと183とか4とかの値になったので、タイマーを使わなくても2度分くらいのばらつきで動くことはできるようです。
(追記) 波形をみたところ、目視で100μs以下ぐらいのジッタはあるようです。要求される精度はどのくらいですか?

タイマーを使うにしても原理としては同じようにできるでしょう。精度や分解能は随分よくなる期待が持てるかと思います。

羽根3枚を検出しないといけない状況があるのでしょうか? それこそ羽根の隙間を狙わなきゃいけない、とかだと真面目に羽根を検出したほうがいいかも知れませんが。


質問情報の追記をうけての追記

例えば1の羽根が90度の位置に来た時にLEDを点灯させるという感じ
消灯のタイミングはパルス幅の真ん中に来た時です

とか、「どこにいっちゃったんだぁ?」と思いますけれど。

回転スピードは一定していません。

とかのびっくりネタはもう隠していませんか? 
10倍のダイナミックレンジってのは結構インパクトありますよ。最初に「どのくらいの時間をみているのか」と聞きましたよね? なぜかというと、それが設計の基本的なところで押さえるべきパラメータだからです。そこを、最初に言った9msから平気で120msまでしかも可変で引っ張る、なんていうのは、「バカにしている」と怒られても文句は言えないレベルです(怒らないけど思ってます)。

完成寸前まで作っちゃったんで載せときます。nac_tnkさんのタイマー版をベースにしてます。1度単位での制御はソフトループだと苦しいので。
制約として、①の前半と後半の立ち上がり間隔が、①の後半の立ち上がりと②の立ち上がりの間隔より短い必要があります(パルス全体の幅と切れ込みの入れ方によってはその関係は崩れます)。「一番短い立ち上がり間隔」を①の検出の要件としましたので。図が現物通りなら特に問題ないでしょう。
あまり明確にかかれていませんが、LEDの点灯開始は各パルスの立ち上がりと勝手に解釈しました。

LED制御ぐらいは自分でやって下さい。そもそもの質問の「①を知る」ことはこれでできているはずなので。

#include <limits.h>
const int LEDPORT[] = {11, 12, 13};
const int TM_STOP = 0;
const int TM_RUN = 0b11;

volatile bool roundComp;

void pulse_read() {
  //立ち上がり位置の記録。
  unsigned int edge = TCNT1;//即座にタイマー値を取得することでばらつきを防ぐ

  static unsigned int lastEdge;//前回の立ち上がり位置
  static unsigned int width[4];//各パルスの幅を保存
  static int cnt = 0;//パルスのインデックス

  if (cnt == 3 ) {
    //タイマー操作を優先するため同条件のif文が分かれている
    TCCR1B = TM_STOP; //数μs狂うかも知れないけど、安全なタイマーカウンタ操作のため止める
    if (OCR1A > TCNT1) { //未実行のコンペアがあったら移行する
      OCR1A -= TCNT1;
    }
    TCNT1 = 0; //タイマーカウンタをリセット
    TCCR1B = TM_RUN;
  }

  //バルス立ち上がりの間隔を求める
  width[(cnt + 3) % 4] = edge - lastEdge;
  if ( cnt == 3 ) {
    roundComp = true;//一周データ取り終わったことを記録
    edge = 0;
  }
  lastEdge = edge;

  // いちばん幅が狭いところつまり1のパルスの検出。
  unsigned int wMin = width[0];
  int firstEdge = 0;
  for (int i = 1; i < 4; i++) {
    if (width[i] < wMin) {
      wMin = width[i];
      firstEdge = i;
    }
  }

  //一周分のデータが揃っているならLED処理
  if (roundComp ) {
    // 直前での120度分の回転時間
    int w_3 = width[(cnt + 3) % 4];
    //1のパルスが割れている分の補正
    if (cnt == (firstEdge + 2) % 4) {
      w_3 += width[firstEdge];
    }

    if (cnt == firstEdge) {//1のセンサが発報
      OCR1A = edge + w_3 * 40l / 120; //例として40度回る時間後タイマーで消灯
      //計算途中でのオーバーフローを防ぐため一部をlong型にしている
      digitalWrite(LEDPORT[0], HIGH);//センサが発報したら点灯
    }
    if (cnt == (firstEdge + 2) % 4 ) { //2のセンサが発報
      OCR1A = edge + w_3 * 20l / 120; //20度
      digitalWrite(LEDPORT[1], HIGH);//センサが発報したら点灯
    }
    if (cnt == (firstEdge + 3) % 4 ) { //3のセンサが発報
      OCR1A = edge + w_3 * 60l / 120; //60度
      digitalWrite(LEDPORT[2], HIGH);//センサが発報したら点灯
    }
  }

  cnt=(cnt+1)%4;//立ち上がり取得カウンタを進めておく
}

ISR (TIMER1_OVF_vect) {//低速、停止時に0にする為
  roundComp = 0;
}

ISR (TIMER1_COMPA_vect) {//OCRA一致割り込みでLED OFF
  //面倒なので全部OFF
  for ( auto l : LEDPORT) {
    digitalWrite(l, 0);
  }
}

void setup() {
  Serial.begin(115200) ;
  for ( auto l : LEDPORT) {
    pinMode(l, OUTPUT);
  }
  TCCR1A = 0;
  TCCR1B = TM_RUN; //1/64=4us/countでタイマー1を回す
  OCR1A = 0;
  OCR1B = 0;
  TIMSK1 = 0b11; //必要な割り込みON
  attachInterrupt(0, pulse_read, RISING);//センサーD2ピン
}

void loop() {
}


実行結果

投稿

編集

  • 回答の評価を上げる

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

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

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

  • 回答の評価を下げる

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

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

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

  • 2020/09/10 14:08

    すみません。thkaneさんの疑問に応えよう思いながらプログラムを見ていたらそっちに頭がいっぱいになり、応えられない状態になってました。
    最初から整理したので一番最初の追記、修正のところ見てもらえますか。

    キャンセル

  • 2020/09/12 22:36

    thkanaさん怒らないでくださいよ。7月ごろからArduinoを始めたんですがプログラムの意味が全然わからなくで頭の中がへんになってるんです。他の言語も知らないので余計です。このプラグラムを理解できるように頑張ってみます。また分からないところがたくさんあると思うのでその時はよろしくお願いします。

    キャンセル

  • 2020/09/13 10:18

    > プログラムの意味が全然わからなくで
    プログラムは、コンピュータになにかをやらせるための指示です。
    コンピュータは、プログラムに書いてある通りに動くだけの機械に過ぎません。

    今回問題になったのは、プログラム以前の話。「なにをやらせるのか」が明確になっていなかったこと、それを明確にしようと問いかけたら、無視したりコロコロと言う事が変わったり、大事なことが最後の最後で出てきたりしたこと。
    なにをやらせるかわからないのに指示(プログラム)なんて出来るわけありませんし、やることが変わってしまったらプログラムは違ったものになり、程度によっては今まで考えたことは全く無駄になるかも知れません。

    プログラムを考えるためには、「なにをやらせるのか」が決まっている必要があります。もちろん、ある程度作ってみて実験的に決めなければならない要素もあるでしょう。それでも、わかっている部分からプログラムの多くが決定され、未定なものが未定であることは明示できて、あり得る変化の範囲を想定して対応することも出来るでしょう。

    もう一つ、これはプログラムとは全く関係ないことですが...(つまりプログラミングの初心者であることは関係ない)
    回答者は、あなたの知っている/当然であると思っていることを、あなたがここに書かない限り知らない、ということを意識してください。
    学校で勉強を教わっていてわからない問題があったのなら、先生もクラスメートも同じ問題を共有していますから「昨日の授業でやった例題3-2」で全て話は通じるでしょう。塾に行っても、生徒の学年や勉強範囲を知っているわけでその前提のもとで教えてくれるでしょう。
    しかし、ここでの回答者は、あなたのことを「何も」知りません。あなたのスキルも、やろうとしていることも。
    あなたが扇風機のそれぞれの羽根の位置でLEDを点灯しようとしている、などということは知らないわけで、そこに「3つのパルスの一つを識別したい」というだけの質問を持ち込んだら「最初からパルスを一つにしとけばいい」なんていう(あなたにとっては)的はずれな回答になるのもやむなしでは。
    そういう意味で、ここでの質問は学校で先生や友人に聞くのとは異なります。わからないところ「だけ」書いても理解されません。背景や必要な要素、求めるものはちゃんと質問に書いてください。回答側が記述が足りないと思えば追記・修正依頼に書くでしょうから、それに対応してください。

    キャンセル

+1

<入力の問題だったようなので、全面的に書き換えます。>

基本的に、「簡単」とは言わないけど、1つずつ必要な処理を書いていけば出来る事だと思います。
ただし、目的がLED1個を角度○~△の間だけ点灯なら、何も考えずに書いていけばいい事ですけど、
目的によってはそうはいきません。バーサライタなんかだとほぼ裏方です。
つまり、loop関数は表示(LED操作)の為に残して、外部割り込みやタイマーを使って、現在の角度を取得できるように構成させる必要?というか、そうした方がやり易かったりします。
単にint degree_get()のような関数を作って、現在の角度を取得するように構成させます。

※thkanaさんのスケッチへのレスを見る限り、そのスケッチに付け足して何かを作るのはまだ無理のように感じました。

出来るだけ簡単に角度を取得できる工夫も必要です。角度の取得に時間がかかっていれば表示はその影響を受けます。
この程度の事は数us程度の誤差(遅れ/作業時間)で済ませたい所です。

簡単に言えば、まずやりたい事全体で考える事です。そして、どのようにするのが良いシステムになるのか
(どのように無駄のないものにするか)、を考えてください。

羽根(パルス)の数も、4(3)つあれば、それだけ識別に手間取りますし、間違いも起こります。
これは1つにすべきです。

↓は、やはり羽根1つでのサンプルです。degree_get()をすればその時の角度(degree)*100で取得します。
後はセンサーの設置位置に合わせて適当にオフセットを付ければ良いです。
ループ関数は何もしていない(↓はテスト用の表示が入っている)ので、自由に使えます。

volatile word period;

void pulse_read() {
  period=TCNT1;
  TCNT1=0;
}

ISR (TIMER1_OVF_vect) {//低速、停止時に0にする為
  period=0;
}

word degree_get() {//角度*100を返す
  if(period==0)return 0;
  return 36000UL*TCNT1/period;
}

void setup() {
  Serial.begin(9600) ;
  TCCR1A=0;
  TCCR1B=2; //1/8=0.5us/countでタイマー1を回す
  TIMSK1=1;//無回転等の判別用。まぁ無くてもOK
  attachInterrupt(0, pulse_read, RISING);//センサーD2ピン
  tone(4,125);//8ms ※テスト信号。これをD2に入れる
}

void loop() {
  Serial.print(period);
  Serial.print("  ");
  Serial.println(degree_get());

  delay(300);
}

プログラム的には超簡単です。(レジスタ操作は別として)
解説すれば、外部割り込みで、パルスの上昇で割り込み(pulse_read関数)をかけています。(原点)
また、タイマー1(TCNT1)をストップウォッチのように使っています。
割り込み時にタイマ値を読み取りリセットします。そのタイマー値は回転周期(period)です。
degree_get関数は回転周期と現在のタイマ値から割合を出し、36000をかけて戻しています。

※実際にはtoneやSerialは消してください。

投稿

編集

  • 回答の評価を上げる

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

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

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

  • 回答の評価を下げる

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

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

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

  • 2020/09/07 01:34

    すいません。もう少しこのあたり詳しい解説が欲しいです。
    volatile word period;

    void pulse_read() {
    period=TCNT1;
    TCNT1=0;
    }

    ISR (TIMER1_OVF_vect) {//低速、停止時に0にする為
    period=0;
    }

    word degree_get() {//角度*100を返す
    if(period==0)return 0;
    return 36000UL*TCNT1/period;
    }
    よろしくお願いいたします。

    キャンセル

  • 2020/09/07 02:52

    > volatile word period;
    周期です。割り込みで使う外部変数はvolatileを付けます。単位は0.5usです。

    > void pulse_read() {
    > period=TCNT1;
    > TCNT1=0;
    > }
    これは、ピンがLOW→HIGHになった瞬間に実行される(外部割り込み)関数です。
    羽根が1枚なら、ストップウォッチで毎回計測すれば周期が判ります。
    なので、新たな周期を記録して、リセット0しています。

    > ISR (TIMER1_OVF_vect) {//低速、停止時に0にする為
    > period=0;
    > }
    タイマー1溢れ割り込みです。16bitタイマーなので、計測できるのは0.5*65536=32768usまでです。
    回転速度で言えば1830RPS以下の速度で回すなら分周を変える必要があります。
    で、この割り込みに来たという事は低速すぎて35536カウントした事になりますから、本来は分周を上げる
    方が適切かもしれません。今回は「低速エラー」として、periodを0にしています。
    ※停止した場合は周期の取得は出来ません。中途半端な値で止まります。それが嫌なので識別させときます。

    > word degree_get() {//角度*100を返す
    > if(period==0)return 0;
    > return 36000UL*TCNT1/period;
    > }
    利用しやすい形で関数を作ります。エラーをして、0を返していますけど、適切では無かったです。
    ※0度の時も0を返すので区別がつかない。0xFFFFあたりの方が良いですね。
    で、勿論360UL*TCNT1/periodとして、0~359(或いは360や361等も返す可能性はあります)でも良いですけど、
    扇風機という事なら面積も広いのでそれに100を掛けました。
    原点調整の為にここで
    return 36000UL*TCNT1/period+offset;
    としても良いです。

    キャンセル

  • 2020/09/12 18:59

    タイマー周り、お借りしました。

    キャンセル

0

タイマ割り込みでタイマカウンタを操作していけば図のような波形も生成可能です
一番幅が狭いところで、Arduino程度のCPUでは、数百μs程度が限界ですが

投稿

  • 回答の評価を上げる

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

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

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

  • 回答の評価を下げる

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

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

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

0

私ならセンサーを2つつけて羽1だけ両方のセンサーが反応するように金属板を取り付けます。

投稿

編集

  • 回答の評価を上げる

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

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

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

  • 回答の評価を下げる

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

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

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

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

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

関連した質問

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