PICを使った電子工作でわからないことがあるので教えてください

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

PICを使った電子工作でわからないことがあるので教えてください

#1

投稿記事 by あんどぅ » 9年前

加速度センサーを使った電子工作のソフト側でわからないことがあります。

↓回路図
https://gyazo.com/ca3fbe63ef1af3a120d00816b8cf4a0b
こんな感じで組んでいます。


↓ソース
https://ideone.com/6F7r9T

やりたいこと
①電源(モード切替)を1回押すと緑、2回押すと青のLEDが点灯。3回押すと消灯。以降繰り返す。
②緑または青が点灯しているときに揺れ(角度変更)を感知するとLEDが赤に点滅し、点滅後は元の色に戻す。
③揺れ(角度変更)の感知後、約3秒間角度が変わらなければ、その角度を基準(角度0度)とし、その後の揺れを感知する。

このような仕様にする場合、プログラムはどのようになりますか?

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

Re: PICを使った電子工作でわからないことがあるので教えてください

#2

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

コンパイル・検証(テスト)はしていませんが、こんな感じでしょうか?

コード:

#include <xc.h>
#define _XTAL_FREQ 4000000

__CONFIG(FOSC_INTOSCIO & WDTE_OFF & PWRTE_ON & MCLRE_OFF & CP_OFF & CPD_OFF & BOREN_ON & IESO_OFF & FCMEN_OFF);
__IDLOC(F683); //PIC12F683

void LED_ON_OFF(unsigned char);
void LED_CHANGE(unsigned char);

//差の絶対値を求める
unsigned int diff_abs(unsigned int a, unsigned int b)
{
    if(a >= b)
    {
        return a - b;
    }
    else
    {
        return b - a;
    }
}

//角度変更として検出する値の差の最小値
#define THRESHOLD 0x0100

void main(void)
{
    unsigned char i; //スイッチ用フラグに使用
    unsigned char change_flag; //角度変更があったことを示すフラグ
    unsigned int angle; //変更前の角度を保存する
    unsigned int current_angle; //読み取った角度を入れる
    unsigned char change_nores_time; //角度変更を検出しても反応しない時間

    OSCCON = 0b01100000; //3V電圧なのでクロック周波数を4MHzにする
    ANSEL = 0b00011000; //ADCのクロックをFOSC/8に設定、アナログ入力をGP4(AN3)は有効、他は無効にする
    TRISIO = 0b00111000; //入出力設定。GP3,GP4,GP5を入力、GP0,GP1,GP2は出力
    CMCON0 = 7;

    GPIO = 0b00000000;

    i = 1;
    change_flag = 0;
    angle = 0xFFFF;
    current_angle = 0;
    change_nores_time = 0;

    while(1) //無限ループ構造
    {
        ADCON0 = 0b00001101; //GP4(AN3)をAD変換対象にする。GP3とGP5はもともとデジタル?
        __delay_ms(20);
        GO = 1; //変換の開始
        while(GO); //変換が終わるまで待つ
        current_angle = ((unsigned int)ADRESH << 8) | ADRESL; //結果を格納する
        if(angle == 0xFFFF) //まだ変更前の角度のデータが無ければ、格納する
        {
            angle = current_angle;
        }

        if(GP3 == 0) //電源スイッチが押されたときの処理。
        {
            __delay_ms(10); //チャタリング除去

            if(i == 1) //スイッチが一回押されたとき(起動時)
            {
                LED_ON_OFF(i);
                i++;
            }
            else if(i == 2) //スイッチが二回押されたとき
            {
                LED_ON_OFF(i);
                i++;
            }
            else //スイッチが三回押されたとき
            {
                LED_ON_OFF(i);
                i = 1;
            }
        }

        //角度変更を検出しても反応しない時間が残っているなら、減らす
        if(change_nores_time > 0)
        {
            change_nores_time--;
        }

        if(diff_abs(current_angle, angle) >= THRESHOLD)
        {
            //角度変更を検出した
            if(change_nores_time == 0 && ((i == 2) || (i == 3)))
            {
                //反応しない時間は過ぎていて、LEDが点灯している
                LED_CHANGE(i-1);
                // LED_CHANGEに約1秒かかるので、3-1=2秒くらい
                change_nores_time = 100; //20ms * 100 = 2s (delay以外の処理によって誤差が大きくなるかも)
            }
            //変更前の角度を更新する
            angle = current_angle;
        }
    }
}

void LED_ON_OFF(unsigned char n)
{
    if(n == 1)
    {
        GP0 = 0;
        GP1 = 0;
        GP2 = 1; //緑を点灯
    }
    else if(n == 2)
    {
        GP0 = 0;
        GP1 = 1; //青を点灯
        GP2 = 0;
    }
    else
    {
        GP0 = 0;
        GP1 = 0;
        GP2 = 0;
    }
}

void LED_CHANGE(unsigned char n)
{
    unsigned char x;

    for(x=0; x<10; x++) //赤を点滅
    {
        GP0 = 1;
        GP1 = 0;
        GP2 = 0;
        __delay_ms(50);
        GP0 = 0;
        GP1 = 0;
        GP2 = 0;
        __delay_ms(50);
    }

    LED_ON_OFF(n);
}
あんどぅ さんが書きました:③揺れ(角度変更)の感知後、約3秒間角度が変わらなければ、その角度を基準(角度0度)とし、その後の揺れを感知する。
約3秒以内に角度が変わった場合はどうするのですか? (上のプログラムでは、基準は更新するがLEDは光らせないとしました)
オフトピック
どうしてアナログ出力のはずのYoutが、デジタル入出力しかできないGP5に接続されているのだろう?
こうしないとどうしても基板に収まらなそうだった、とかだろうか?
複雑な問題?マシンの性能を上げてOpenMPで殴ればいい!(死亡フラグ)

あんどぅ

Re: PICを使った電子工作でわからないことがあるので教えてください

#3

投稿記事 by あんどぅ » 9年前

みけCAT さんが書きました:コンパイル・検証(テスト)はしていませんが、こんな感じでしょうか?
あんどぅ さんが書きました:③揺れ(角度変更)の感知後、約3秒間角度が変わらなければ、その角度を基準(角度0度)とし、その後の揺れを感知する。
約3秒以内に角度が変わった場合はどうするのですか? (上のプログラムでは、基準は更新するがLEDは光らせないとしました)
ありがとうございます。
2日ほど考え込んでいろいろ試していたのですが、どうしてもわからなくて・・・
助かりました。

3秒以内に角度が変わった場合もLEDは点滅させます。
ここまで書いていただいたので、このあたりは自分で変更させていただきます。
どうしてもダメだったときに、改めて質問させていただきます。
みけCAT さんが書きました:
オフトピック
どうしてアナログ出力のはずのYoutが、デジタル入出力しかできないGP5に接続されているのだろう?
こうしないとどうしても基板に収まらなそうだった、とかだろうか?
私も今さっき気づき、I/Oを変更しました。




私のソースだと、スイッチが押されたときの処理で
__delay_ms(10); //チャタリング除去
としていますが、どうしてもチャタリングが発生してしまいます。
本来、スイッチからの入力検知やカウントは
while(GP3 == 0);
__delay_ms(10);
i++;
while(GP3 == 1);
__delay_ms(10);
として、押されたときと離されたときの検出するために待機させるようですが、
今回のように割り込み(揺れの検知)が発生した場合に待機中だった場合にどうしようもなくなると思って
if(GP3 == 0)
としたのですが、なにかいい処理の仕方はないでしょうか?

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

Re: PICを使った電子工作でわからないことがあるので教えてください

#4

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

あんどぅ さんが書きました:今回のように割り込み(揺れの検知)が発生した場合に待機中だった場合にどうしようもなくなると思って
本物の割り込みは使えないのでしょうか?
もし使えるのであれば、以下の擬似コードのようにするといいかもしれません。(未検証です)

コード:

Timer0オーバーフロー割り込み:
  Timer0が20ms後にオーバーフローするように設定する
  A/D変換の結果を受け取る
  もし、角度変更を検出したら
    LEDを点滅させる回数を設定する
    赤LEDを点灯する
    Timer2を起動する(50ms間隔)
  A/D変換を開始する

電源スイッチ状態変化割り込み:
  もし、変化を無視するフラグが立っていないなら
    電源スイッチが押されていて、前に記録した電源スイッチの状態が「押されていない」なら
      状態遷移処理を行う
      もし、LEDを点滅させる回数が0なら、LED出力を状態に応じて更新する
    変化を無視するフラグを立てる
    Timer1をリセットし、起動する(10ms後にオーバーフローするように)
    電源スイッチの状態を記録する

Timer1オーバーフロー割り込み:
  Timer1を停止する
  変化を無視するフラグを折る

Timer2マッチ割り込み:
  赤LEDをトグルする
  LEDを点滅させる回数をデクリメントする
  もし、LEDを点滅させる回数が0なら
    LED出力を状態に応じたものに更新する
    Timer2を停止する
複雑な問題?マシンの性能を上げてOpenMPで殴ればいい!(死亡フラグ)

あんどぅ

Re: PICを使った電子工作でわからないことがあるので教えてください

#5

投稿記事 by あんどぅ » 9年前

みけCAT さんが書きました:
あんどぅ さんが書きました:今回のように割り込み(揺れの検知)が発生した場合に待機中だった場合にどうしようもなくなると思って
本物の割り込みは使えないのでしょうか?
もし使えるのであれば、以下の擬似コードのようにするといいかもしれません。(未検証です)

コード:

Timer0オーバーフロー割り込み:
  Timer0が20ms後にオーバーフローするように設定する
  A/D変換の結果を受け取る
  もし、角度変更を検出したら
    LEDを点滅させる回数を設定する
    赤LEDを点灯する
    Timer2を起動する(50ms間隔)
  A/D変換を開始する

電源スイッチ状態変化割り込み:
  もし、変化を無視するフラグが立っていないなら
    電源スイッチが押されていて、前に記録した電源スイッチの状態が「押されていない」なら
      状態遷移処理を行う
      もし、LEDを点滅させる回数が0なら、LED出力を状態に応じて更新する
    変化を無視するフラグを立てる
    Timer1をリセットし、起動する(10ms後にオーバーフローするように)
    電源スイッチの状態を記録する

Timer1オーバーフロー割り込み:
  Timer1を停止する
  変化を無視するフラグを折る

Timer2マッチ割り込み:
  赤LEDをトグルする
  LEDを点滅させる回数をデクリメントする
  もし、LEDを点滅させる回数が0なら
    LED出力を状態に応じたものに更新する
    Timer2を停止する
すみません。PIC初心者なので、割り込みの処理がいまいちわかっていません。
今後必要になるのはわかっているのですが。。。
Timer0オーバーフロー割り込み処理でだけで
角度変化を検出したら赤LED点滅
するだけじゃダメなのでしょうか?
状態変化割り込みやTimer1,2も使わないとダメですか?

あんどぅ

Re: PICを使った電子工作でわからないことがあるので教えてください

#6

投稿記事 by あんどぅ » 9年前

コード:

#include <xc.h>
#include <pic.h>
#define _XTAL_FREQ 4000000

#define X GP0
#define Y GP1
#define SW GP3
#define RED GP2
#define BUL GP4
#define GRE GP5

#define SW_ON 0
#define SW_OFF 1

#define LED_ON 1
#define LED_OFF 0

#define THRESHOLD 0x0300

__CONFIG(FOSC_INTOSCIO & WDTE_OFF & PWRTE_ON & MCLRE_OFF & CP_OFF & CPD_OFF & BOREN_ON & IESO_OFF & FCMEN_OFF);
__IDLOC(F683); //PIC12F683

/*関数プロトタイプ宣言*/
void LED_ON_OFF(unsigned char);
void LED_CHANGE(unsigned char);
unsigned int diff_abs(unsigned int,unsigned int);
void interrupt ISR(void);

/*グローバル変数*/
unsigned char SW_FLAG = 0;
unsigned char XY_FLAG = 0;
unsigned char LED_FLAG = 0; //LEDが現在点いている状態か?
unsigned char i = 0;


unsigned char change_flag = 0; //角度変更があったことを示すフラグ
unsigned int angle = 0XFFFF; //変更前の角度を保存する
unsigned int current_angle = 0; //読み取った角度を入れる
unsigned char change_nores_time = 0; //角度変更を検出しても反応しない時間

void main(void)
{
    OSCCON = 0b01100000; //3V電圧なのでクロック周波数を4MHzにする
    ANSEL = 0b000100011;
    TRISIO = 0b00001011; //入出力設定。GP2,GP4,GP5を出力、GP0,GP1,GP3は入力
    CMCON0 = 7;

    OPTION_REG = 0b00000111;
    INTCON = 0b10100000;

    GPIO = 0b00000000;

    while(1) //無限ループ構造
    {   
        while(SW == SW_ON)
        __delay_ms(10); //チャタリング除去
        i++;

        while(SW == SW_OFF)
        __delay_ms(10); //チャタリング除去

        if((i == 1) || (i == 2)) //スイッチが一回、または二回押されたとき
        {
            LED_ON_OFF(i);
            LED_FLAG = 1;
        }
        else //スイッチが三回押されたとき
        {
            LED_ON_OFF(i);
            i = 0;
            LED_FLAG = 0;
        }
    }
}

void LED_ON_OFF(unsigned char n)
{
    if(n == 1)
    {
        RED = LED_OFF;
        BUL = LED_OFF;
        GRE = LED_ON; //緑を点灯
    }
    else if(n == 2)
    {
        RED = LED_OFF;
        BUL = LED_ON; //青を点灯
        GRE = LED_OFF;
    }
    else
    {
        RED = LED_OFF;
        BUL = LED_OFF;
        GRE = LED_OFF;
    }
}

void LED_CHANGE(unsigned char n)
{
    unsigned char x;

    for(x=0; x<10; x++) //赤を点滅
    {
        RED = LED_ON;
        BUL = LED_OFF;
        GRE = LED_OFF;
        __delay_ms(50);
        RED = LED_OFF;
        BUL = LED_OFF;
        GRE = LED_OFF;
        __delay_ms(50);
    }

    LED_ON_OFF(n);
}

unsigned int diff_abs(unsigned int a,unsigned int b)
{
    if(a >= b)
    {
        return a - b;
    }
    else
    {
        return b - a;
    }
}

void interrupt ISR(void)
{
    GIE = 0;

    if(T0IF)
    {        
        ADCON0 = 0b00000011; //GP0(AN0)とGP1(AN1)をAD変換対象にする
        __delay_ms(20);
        GO = 1; //変換の開始
        while(GO); //変換が終わるまで待つ
        current_angle = ((unsigned int)ADRESH << 8) | ADRESL; //結果を格納する

        if(angle == 0xFFFF)
        {
            angle = current_angle;
        }

        if(diff_abs(current_angle,angle) >= THRESHOLD) //角度変更を検出した場合
        {
            if(LED_FLAG == 1) //LEDが点いているとき
            {
                LED_CHANGE(i-1); //赤を点滅
                change_nores_time = 100;
            }

                angle = current_angle;
        }
        else //角度変更がなかったとき
        {
            if(change_nores_time > 0) //3秒カウントダウン
            {
                change_nores_time--;
            }
            else //3秒間動きがなかったときに基準角度を更新
            {
                angle = current_angle;
            }
        }
    }

    T0IF = 0;
    GIE = 1;
}
これで大体落ち着いたのですが、どこか問題があるところなどあれば教えてください。
それからY軸方向の検出ができていません。

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

Re: PICを使った電子工作でわからないことがあるので教えてください

#7

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

あんどぅ さんが書きました:どこか問題があるところなどあれば教えてください。
チャタリングによる誤動作防止用のウェイトは、No: 3のようにキー入力待機のループの外に出したほうがいい気がします。

A/D変換のクロックがFOSC/32に設定されていますが、これは4MHzのクロックでは推奨される範囲の外になっています。

コード:

ADCON0 = 0b00000011; //GP0(AN0)とGP1(AN1)をAD変換対象にする
というコードはコメントに反してGP0(AN0)のみをAD変換対象にしている上、データシートによくないと書かれているADONとGOを同時に1にすることをやってしまっています。

一般論として割り込みハンドラの処理に無駄に長時間かけてはいけません。
割り込みハンドラ内(割り込みハンドラから呼び出される関数内を含む)で__delay_msは使わないべきでしょう。
あんどぅ さんが書きました:それからY軸方向の検出ができていません。
Y軸方向の検出をするコードが書かれていないからでしょう。
角度を保存する変数をX軸用とY軸用の2個用意して、例えば10ms間隔で交互に読んで処理する、という実装が考えられます。
複雑な問題?マシンの性能を上げてOpenMPで殴ればいい!(死亡フラグ)

あんどぅ

Re: PICを使った電子工作でわからないことがあるので教えてください

#8

投稿記事 by あんどぅ » 9年前

みけCAT さんが書きました:A/D変換のクロックがFOSC/32に設定されていますが、これは4MHzのクロックでは推奨される範囲の外になっています。
Fosc/8でAN0とAN1をアナログにするので
ANSEL = 0b000010011;
こういう書き方にしなければならないということですね?
みけCAT さんが書きました:

コード:

ADCON0 = 0b00000011; //GP0(AN0)とGP1(AN1)をAD変換対象にする
というコードはコメントに反してGP0(AN0)のみをAD変換対象にしている上、データシートによくないと書かれているADONとGOを同時に1にすることをやってしまっています。
変換の有効にするためにADONを1で対象ポートがAN0なので00ということは
ADCON0 = 0b00000001;
これでいいのですか?
みけCAT さんが書きました: 一般論として割り込みハンドラの処理に無駄に長時間かけてはいけません。
割り込みハンドラ内(割り込みハンドラから呼び出される関数内を含む)で__delay_msは使わないべきでしょう。
ADCON0とGOの間にある__delay_msのことでしょうか?
使わないべきというのは、いらないということでしょうか?もしくは、別の方法で対策するべきということでしょうか?
みけCAT さんが書きました: Y軸方向の検出をするコードが書かれていないからでしょう。
角度を保存する変数をX軸用とY軸用の2個用意して、例えば10ms間隔で交互に読んで処理する、という実装が考えられます。

コード:

 ADCON0 = 0b00000001; //GP0(AN0)をAD変換対象にする
 __delay_ms(20);
 GO = 1; //変換の開始
 while(GO); //変換が終わるまで待つ
 current_angle_x = ((unsigned int)ADRESH << 8) | ADRESL; //結果を格納する

 ADCON0 = 0b00000101; //GP1(AN1)をAD変換対象にする
 __delay_ms(20);
 GO = 1; //変換の開始
 while(GO); //変換が終わるまで待つ
 current_angle_y = ((unsigned int)ADRESH << 8) | ADRESL; //結果を格納する
こういうことですか?

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

Re: PICを使った電子工作でわからないことがあるので教えてください

#9

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

あんどぅ さんが書きました:Fosc/8でAN0とAN1をアナログにするので
ANSEL = 0b000010011;
こういう書き方にしなければならないということですね?
いいえ。値はあっていますが、余計な0を付けて9桁にする必要はありません。
もちろん、十六進数や十進数、演算子を使った式などで記述することもできるでしょう。
あんどぅ さんが書きました:変換の有効にするためにADONを1で対象ポートがAN0なので00ということは
ADCON0 = 0b00000001;
これでいいのですか?
大丈夫だと思います。
あんどぅ さんが書きました:
みけCAT さんが書きました: 一般論として割り込みハンドラの処理に無駄に長時間かけてはいけません。
割り込みハンドラ内(割り込みハンドラから呼び出される関数内を含む)で__delay_msは使わないべきでしょう。
ADCON0とGOの間にある__delay_msのことでしょうか?
それもだし、LED_CHANGE内の__delay_msもです。
あんどぅ さんが書きました:使わないべきというのは、いらないということでしょうか?もしくは、別の方法で対策するべきということでしょうか?
両方です。
__delay_msがいらないように、別の方法で対策(なんの?)しましょう。
あんどぅ さんが書きました:
みけCAT さんが書きました: Y軸方向の検出をするコードが書かれていないからでしょう。
角度を保存する変数をX軸用とY軸用の2個用意して、例えば10ms間隔で交互に読んで処理する、という実装が考えられます。

コード:

 ADCON0 = 0b00000001; //GP0(AN0)をAD変換対象にする
 __delay_ms(20);
 GO = 1; //変換の開始
 while(GO); //変換が終わるまで待つ
 current_angle_x = ((unsigned int)ADRESH << 8) | ADRESL; //結果を格納する

 ADCON0 = 0b00000101; //GP1(AN1)をAD変換対象にする
 __delay_ms(20);
 GO = 1; //変換の開始
 while(GO); //変換が終わるまで待つ
 current_angle_y = ((unsigned int)ADRESH << 8) | ADRESL; //結果を格納する
こういうことですか?
いいえ。
(約)10ms間隔になっていない時点で明らかに違いますね。
複雑な問題?マシンの性能を上げてOpenMPで殴ればいい!(死亡フラグ)

あんどぅ

Re: PICを使った電子工作でわからないことがあるので教えてください

#10

投稿記事 by あんどぅ » 9年前

みけCAT さんが書きました: いいえ。値はあっていますが、余計な0を付けて9桁にする必要はありません。
もちろん、十六進数や十進数、演算子を使った式などで記述することもできるでしょう。
すみません。
入力ミスで9桁になってしまいました。

みけCAT さんが書きました: それもだし、LED_CHANGE内の__delay_msもです。
__delay_msがいらないように、別の方法で対策(なんの?)しましょう。
ADCON0の下のはいらないように思いますが、LED_CHANGE内のは点滅させるために使っています。
なにか別の方法で点滅させる方法はありますか?
みけCAT さんが書きました: いいえ。
(約)10ms間隔になっていない時点で明らかに違いますね。
10ms間隔にする方法がいまいちわからないのですが、例えば割り込み毎に交互に検出する方法では問題がありますか?

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

Re: PICを使った電子工作でわからないことがあるので教えてください

#11

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

あんどぅ さんが書きました:ADCON0の下のはいらないように思いますが、
キャパシタに電荷を貯めて変換結果の精度を上げるため、10μs (msではない) くらいの遅延を入れた方がいいかもしれません。
あんどぅ さんが書きました:LED_CHANGE内のは点滅させるために使っています。
なにか別の方法で点滅させる方法はありますか?
もちろんです。
No: 4で書いたように、別のタイマーを使いましょう。
みけCAT さんが書きました:10ms間隔にする方法がいまいちわからないのですが、例えば割り込み毎に交互に検出する方法では問題がありますか?
作りたい仕様と比較して問題があると考えられれば問題があるでしょう。
10ms間隔にするには、Timer0割り込みの処理をした後にTMR0の値を適切に設定し、約10ms後に次のTimer0割り込みが来るようにします。
ある割り込みでXの処理→約10ms後Yの処理→その約10ms後Xの処理→… というようにすれば、Xの処理、Yの処理それぞれがこれまで通り約20ms間隔になります。
複雑な問題?マシンの性能を上げてOpenMPで殴ればいい!(死亡フラグ)

閉鎖

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