前の状態を保持するプログラム2

フォーラム(掲示板)ルール
フォーラム(掲示板)ルールはこちら  ※コードを貼り付ける場合は [code][/code] で囲って下さい。詳しくはこちら
C素人

前の状態を保持するプログラム2

#1

投稿記事 by C素人 » 8年前

SWが全部で8個あります。
SWの状態に応じてLEDが点灯するプログラムとなっております。

SWは、フォトインタラプタの信号出力を使用しています。


[困っていること]
SWの状態に応じてLEDの点灯パターンが変化するのですが、
SW(フォトインタラプタ)のチャタリングによって、意図しない点灯パターンになってしまうことがあります。

swの状態が一定時間、case文で定義された処理を行うというようなことは可能でしょうか。


[sw(フォトインタラプタ)の配置]

〇□△と3つのセンサが同じ位置で横並びに配置しています。
しかし、3つのセンサの感度の違いによって、
例えば、3つとも黒を検知して欲しいのに、
センサの感度の違いにより、一瞬だけ、△が先に黒を検知してしまい、
一瞬だけ、違うモードに入ってしまいます。

これを解決したいと考えています。

コード:

while(1){
    char use_r = 0, use_g = 0, use_b = 0; /* 各関数を使うか */
    char is_valid = 1; /* 定義があるか */
 
    sw = PORTA & 0xFF;
 
    switch(sw){
        
            case 0x00:
                use_r = 1;
                use_b = 1;
                break;
 
            case 0x01:
                use_b = 1;
                break;
 
            case 0x30:
                use_g = 1;
                use_b = 1;
                break;
 
        default:
            is_valid = 0;   /* 定義が無かった */
            break;
    }
 
    /* 定義がある場合のみ処理する */
    if(is_valid) {
        if(prev_sw != sw) {
            Reset();        //初期化
            delay_ms(1);    //delay
        }
 
        if (use_r) LED_R();
        if (use_g) LED_G();
        if (use_b) LED_B();
    }
 
    prev_sw = sw;
}

huhuhu

Re: 前の状態を保持するプログラム2

#2

投稿記事 by huhuhu » 8年前

まず基本的なプログラム構造ですが
while(1)の無限ループを
を1ms周期で回るようにしています。(約1msの周期タスク)
設計の制約条件にあっていなかったらごめんなさい。

sw = PORTA & 0xFF;
の行で直接PORTAを参照するのではなくチャタリング除去後の値を参照するようにします。
チャタリング除去はポートを10ms間隔サンプリングして2回一致で値確定です。
=10ms前から変化がないビットを確定値としています。

コードはコンパイルしてないのでエラー出るかもです。ケアレスミスあるかもです。ごめんなさい。
あくまでも処理のイメージで捉えていただければと思います。

コード:

unsigned char Timer;
unsigned char PortNow;
unsigned char PortOld;
unsigned char PortDiff;
unsigned char PortChatterLess;

// 初期化処理
Timer = 10;
PortOld = 0xFF;
PortChatterLess = 0xFF;

while(1){
    char use_r = 0, use_g = 0, use_b = 0; /* 各関数を使うか */
    char is_valid = 1; /* 定義があるか */


    // 1ms周期ループとする。
    delay_ms(1);   

    // チャタリング除去
    Timer++;                                                    // タイマーカウントアップ
    if ( Timer >= 10 )                                          // 10ms経過した?
    {
        // 本箇所は10ms毎に実施します。
        PortNow = (unsigned char) PORTA & 0xFF;                 // 最新のポート値取得
        PortDiff = PortNow ^ PortOld;                           // 10ms間に変化があったビット(安定してないビット)を1にする → PortDiffに格納
        PortChatterLess = (PortChatterLess & PortDiff) |        // 変化があったビット(安定してないビット)は更新しない。
                          (PortNow & (~PortDiff));              // 変化のないビット(安定したビット)を最新のポート値で更新する。
        PortOld = PortNow;                                      // ポート値を保存
        Timer = 0;                                              // 10msタイマーリスタート
    }

    sw = PortChatterLess;           // sw = PORTA & 0xFF;
 
    switch(sw){
        
            case 0x00:
                use_r = 1;
                use_b = 1;
                break;
 
            case 0x01:
                use_b = 1;
                break;
 
            case 0x30:
                use_g = 1;
                use_b = 1;
                break;
 
        default:
            is_valid = 0;   /* 定義が無かった */
            break;
    }
 
    /* 定義がある場合のみ処理する */
    if(is_valid) {
        if(prev_sw != sw) {
            Reset();        //初期化
            delay_ms(1);    //delay
        }
 
        if (use_r) LED_R();
        if (use_g) LED_G();
        if (use_b) LED_B();
    }
 
    prev_sw = sw;
}

あんどーなつ

Re: 前の状態を保持するプログラム2

#3

投稿記事 by あんどーなつ » 8年前

マイコンの型番を言わずに議論してしまっているあたりすごい...

チャタリングを消すのであれば、オシロスコープをあてるのが一番いいのですが、
そうでなければ8bitマイコンを16bit, 32bitマイコンに変えて、
できるだけ速く動作するテストプログラムを動作させるなどするといいかもしれません。
8bitでもRAMのなかに256byte位はいれれるかな

コード:

char mem[256]; // このグローバル変数をICEで見る

int main() {
  int i;
  while (PORTA & 0x01) nop(); // PA0=Hを待つ
  for (i = 0; i < 256; i++) {
    delay_ms(1);
    mem[i] = PORTA & 0x01; // PA0をサンプリング
  }
  while (1) nop();
  //return 0;
}
PA0=Lでプログラム開始、PA0=Hに立ち上げ、
memの値が0x01か0x00になっていると思うので、バタバタしていれば
それがチャタリングだと思います。

あと、2個のLEDで、LL出力->LH->delay_ms(10000)->HH出力になるプログラムで
10秒カウントできるか確認してみてください。最適化オプションはOFFにしましょう。

あんどーなつ

Re: 前の状態を保持するプログラム2

#4

投稿記事 by あんどーなつ » 8年前

5行目の

while (PORTA & 0x01) nop();

は、

while (PORTA & 0x01 == 0x00) nop();

だと思います。すみませんでした。
他にも間違っているかもしれないです。よろしくお願いします

アバター
みけCAT
記事: 6734
登録日時: 14年前
住所: 千葉県
連絡を取る:

Re: 前の状態を保持するプログラム2

#5

投稿記事 by みけCAT » 8年前

あんどーなつ さんが書きました:5行目の

while (PORTA & 0x01) nop();

は、

while (PORTA & 0x01 == 0x00) nop();

だと思います。
この処理系(の関係する部分)がC言語の規格に沿っているとかていすると、
==演算子は&演算子より優先順位が高いので、
PORTA & 0x01 == 0x00

PORTA & (0x01 == 0x00)
すなわち
PORTA & 0
と等価になります。
0とのAND演算の結果は常に0になるので、この条件式は常に偽になります。

コード:

while ((PORTA & 0x01) == 0x00) nop();
ではないですか?
複雑な問題?マシンの性能を上げてOpenMPで殴ればいい!(死亡フラグ)

あんどーなつ

Re: 前の状態を保持するプログラム2

#6

投稿記事 by あんどーなつ » 8年前

みけCAT さんが書きました:

コード:

while ((PORTA & 0x01) == 0x00) nop();
ではないですか?
みけCATさんのご指摘の通りです。ありがとうございます。
マイコン用のプログラムは私のほうですぐに動かすことができないので、確認用プログラムを書いてみました。

コード:

#include <stdio.h>

int main() {
  if (0x01 & 0x01 == 0x00) printf("A\n");
  if (0x00 & 0x01 == 0x00) printf("B\n");
  if ((0x01 & 0x01) == 0x00) printf("C\n");
  if ((0x00 & 0x01) == 0x00) printf("D\n");
  return 0;
}
実行結果
$ ./a
D

私のプログラムはA,Bの形式でしたが、それだとbit0が0でも1でも反応しない、
つまりwhile (PORTA & 0x01 == 0x00)はすぐに抜けてしまうみたいです。

みけCATさんの修正を加えるとC,Dの形式になりますが、PORTAのbit0が0のときに
真になるので正しく動作するものと思います。

C素人

Re: 前の状態を保持するプログラム2

#7

投稿記事 by C素人 » 8年前

huhuhu さま
あんどーなつ さま
みけCAT さま

みなさん、回答ありがとうございます。
どの回答も私の知らないことばかりで勉強になります。

今回の現象を把握するため、オシロスコープでSW信号の変化を確認しました。
チャタリングに関しては、ハードで対策を取りました。
それでも、センサの感度の違いにより、横並びセンサが変化するタイミングが一致(※1)しませんでした。

※1: マイコンの周波数を落とせば、タイミングが一致します。
マイコンの周波数を上げると、当たり前ですが、わずかな差でも検知するので、タイミングが一致しません。
   別の理由によりマイコンの周波数を下げられないので、switch~case文の中で再確認を行うようにしてみました。

次のように記述したところ、意図しない点灯パターンに入ってしまうのを防げるようになりました。
一応これで解決できたと思っているのですが、なにか問題りますでしょうか。

コード:

	case 0x00:
		sw = PORTA & 0xFF;
		delay_ms(5);
		if (sw == 0x01) {
		//処理
		}
	case 0x01:


huhuhu

Re: 前の状態を保持するプログラム2

#8

投稿記事 by huhuhu » 8年前

現在の処理途中でdelayする方法だと、他の処理との兼ね合いを考えると、なかなか実現が難しいと思います。

これはリアルタイムOS等を使用しない場合のよく使われる定石の一つですが、メインループを一定時間周期で回すことを検討してみませんか?
この方法は特に、4bit、8bitCPUや、ROM/RAM容量が小さな16bit、32bitCPUでは良く使われています。


ループを何週したかを変数で管理すれば時間の経過を知ることが出来ます。
たとえばメインループ(while(1))を1ms毎に一周するようにした場合、ループを5周すれば5ms経過したことになります。
このループ回数を管理する変数をいくつか持てばそれぞれ異なった時間間隔でそれぞれ処理を行うことが出来ます。(並列処理を実現しやすくなります)
このような構成にすれば、CPUの処理速度を変えずともソフトウェアでチャタリング除去を実現できます。
ハードでのチャタリング除去による回路コストの上昇を嫌うならなおさら良い方法になると思いますよ。
チャタリング除去時間(チャタリングの安定待ち時間)ですが、私の経験上メカニカルスイッチでは10ms~20msくらいが多いです。
今回のフォトインタラプタ回路の場合はメカニカルスイッチとは特性が違うでしょうから、みなさんがおっしゃているとおりオシロで実動作を観測され
チャタリング除去時間を決定したほうが良いと思います。
チャタリング除去時間の決定については、フォトインタラプタ信号が安定するまでの時間ぎりぎりよりも、ユーザーの操作感に影響を与えない程度に
十分長くしたほうが良いと思います。


またNo.2で私が提示したチャタリング除去方法も良く使われる定石の一つですので、余裕がありましたらご検討を。。。

コード:

int Count5ms = 0;
int Count10ms = 0;
int Count100ms = 0;

while(1)
{
    //delay_ms(1);                            // 当座の実験にはdelay_ms(1)でもよいが、ハードウェアタイマーを使用した処理にすると良い。
    if (1ms経過を判定する関数() == 1ms経過?)          // ハードウェアタイマーを使用した(間接的/直接的に使用)1ms経過を判定する関数
    {
        // 1msごとの処理をここに記述 (ステートマシン、シーケンス処理で並列処理)

    Count5ms++;
        if (Count5ms >= 5)
        {
            Count5ms = 0;
            // 5msごとの処理をここに記述 (ステートマシン、シーケンス処理で並列処理)
        }

    Count10ms++;
        if (Count10ms >= 10)
        {
            Count10ms = 0;
            // 10msごとの処理をここに記述 (ステートマシン、シーケンス処理で並列処理)
        }

    Count100ms++;
        if (Count100ms >= 100)
        {
            Count100ms = 0;
            // 100msごとの処理をここに記述 (ステートマシン、シーケンス処理で並列処理)
        }
    }
}

C素人

Re: 前の状態を保持するプログラム2

#9

投稿記事 by C素人 » 8年前

huhuhu様

ありがとうございます。
CPU処理速度を変えなくても、ソフトウェアでチャタリングできる・・・
思いつきもしませんでした。大変勉強になります。
早速試してみます。
みなさんありがとうございました。

閉鎖

“C言語何でも質問掲示板” へ戻る