加速度センサーを使った電子工作のソフト側でわからないことがあります。
↓回路図
https://gyazo.com/ca3fbe63ef1af3a120d00816b8cf4a0b
こんな感じで組んでいます。
↓ソース
https://ideone.com/6F7r9T
やりたいこと
①電源(モード切替)を1回押すと緑、2回押すと青のLEDが点灯。3回押すと消灯。以降繰り返す。
②緑または青が点灯しているときに揺れ(角度変更)を感知するとLEDが赤に点滅し、点滅後は元の色に戻す。
③揺れ(角度変更)の感知後、約3秒間角度が変わらなければ、その角度を基準(角度0度)とし、その後の揺れを感知する。
このような仕様にする場合、プログラムはどのようになりますか?
PICを使った電子工作でわからないことがあるので教えてください
Re: PICを使った電子工作でわからないことがあるので教えてください
コンパイル・検証(テスト)はしていませんが、こんな感じでしょうか?
#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秒以内に角度が変わった場合はどうするのですか? (上のプログラムでは、基準は更新するがLEDは光らせないとしました)あんどぅ さんが書きました:③揺れ(角度変更)の感知後、約3秒間角度が変わらなければ、その角度を基準(角度0度)とし、その後の揺れを感知する。
オフトピック
どうしてアナログ出力のはずのYoutが、デジタル入出力しかできないGP5に接続されているのだろう?
こうしないとどうしても基板に収まらなそうだった、とかだろうか?
こうしないとどうしても基板に収まらなそうだった、とかだろうか?
複雑な問題?マシンの性能を上げてOpenMPで殴ればいい!(死亡フラグ)
Re: PICを使った電子工作でわからないことがあるので教えてください
ありがとうございます。みけCAT さんが書きました:コンパイル・検証(テスト)はしていませんが、こんな感じでしょうか?約3秒以内に角度が変わった場合はどうするのですか? (上のプログラムでは、基準は更新するがLEDは光らせないとしました)あんどぅ さんが書きました:③揺れ(角度変更)の感知後、約3秒間角度が変わらなければ、その角度を基準(角度0度)とし、その後の揺れを感知する。
2日ほど考え込んでいろいろ試していたのですが、どうしてもわからなくて・・・
助かりました。
3秒以内に角度が変わった場合もLEDは点滅させます。
ここまで書いていただいたので、このあたりは自分で変更させていただきます。
どうしてもダメだったときに、改めて質問させていただきます。
私も今さっき気づき、I/Oを変更しました。みけCAT さんが書きました:オフトピックどうしてアナログ出力のはずのYoutが、デジタル入出力しかできないGP5に接続されているのだろう?
こうしないとどうしても基板に収まらなそうだった、とかだろうか?
私のソースだと、スイッチが押されたときの処理で
__delay_ms(10); //チャタリング除去
としていますが、どうしてもチャタリングが発生してしまいます。
本来、スイッチからの入力検知やカウントは
while(GP3 == 0);
__delay_ms(10);
i++;
while(GP3 == 1);
__delay_ms(10);
として、押されたときと離されたときの検出するために待機させるようですが、
今回のように割り込み(揺れの検知)が発生した場合に待機中だった場合にどうしようもなくなると思って
if(GP3 == 0)
としたのですが、なにかいい処理の仕方はないでしょうか?
Re: PICを使った電子工作でわからないことがあるので教えてください
本物の割り込みは使えないのでしょうか?あんどぅ さんが書きました:今回のように割り込み(揺れの検知)が発生した場合に待機中だった場合にどうしようもなくなると思って
もし使えるのであれば、以下の擬似コードのようにするといいかもしれません。(未検証です)
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を使った電子工作でわからないことがあるので教えてください
すみません。PIC初心者なので、割り込みの処理がいまいちわかっていません。みけ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を停止する
今後必要になるのはわかっているのですが。。。
Timer0オーバーフロー割り込み処理でだけで
角度変化を検出したら赤LED点滅
するだけじゃダメなのでしょうか?
状態変化割り込みやTimer1,2も使わないとダメですか?
Re: PICを使った電子工作でわからないことがあるので教えてください
#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軸方向の検出ができていません。
Re: PICを使った電子工作でわからないことがあるので教えてください
チャタリングによる誤動作防止用のウェイトは、No: 3のようにキー入力待機のループの外に出したほうがいい気がします。あんどぅ さんが書きました:どこか問題があるところなどあれば教えてください。
A/D変換のクロックがFOSC/32に設定されていますが、これは4MHzのクロックでは推奨される範囲の外になっています。
というコードはコメントに反してGP0(AN0)のみをAD変換対象にしている上、データシートによくないと書かれているADONとGOを同時に1にすることをやってしまっています。
一般論として割り込みハンドラの処理に無駄に長時間かけてはいけません。
割り込みハンドラ内(割り込みハンドラから呼び出される関数内を含む)で__delay_msは使わないべきでしょう。
Y軸方向の検出をするコードが書かれていないからでしょう。あんどぅ さんが書きました:それからY軸方向の検出ができていません。
角度を保存する変数をX軸用とY軸用の2個用意して、例えば10ms間隔で交互に読んで処理する、という実装が考えられます。
複雑な問題?マシンの性能を上げてOpenMPで殴ればいい!(死亡フラグ)
Re: PICを使った電子工作でわからないことがあるので教えてください
Fosc/8でAN0とAN1をアナログにするのでみけCAT さんが書きました:A/D変換のクロックがFOSC/32に設定されていますが、これは4MHzのクロックでは推奨される範囲の外になっています。
ANSEL = 0b000010011;
こういう書き方にしなければならないということですね?
変換の有効にするためにADONを1で対象ポートがAN0なので00ということは
ADCON0 = 0b00000001;
これでいいのですか?
ADCON0とGOの間にある__delay_msのことでしょうか?みけCAT さんが書きました: 一般論として割り込みハンドラの処理に無駄に長時間かけてはいけません。
割り込みハンドラ内(割り込みハンドラから呼び出される関数内を含む)で__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; //結果を格納する
Re: PICを使った電子工作でわからないことがあるので教えてください
いいえ。値はあっていますが、余計な0を付けて9桁にする必要はありません。あんどぅ さんが書きました:Fosc/8でAN0とAN1をアナログにするので
ANSEL = 0b000010011;
こういう書き方にしなければならないということですね?
もちろん、十六進数や十進数、演算子を使った式などで記述することもできるでしょう。
大丈夫だと思います。あんどぅ さんが書きました:変換の有効にするためにADONを1で対象ポートがAN0なので00ということは
ADCON0 = 0b00000001;
これでいいのですか?
それもだし、LED_CHANGE内の__delay_msもです。あんどぅ さんが書きました:ADCON0とGOの間にある__delay_msのことでしょうか?みけCAT さんが書きました: 一般論として割り込みハンドラの処理に無駄に長時間かけてはいけません。
割り込みハンドラ内(割り込みハンドラから呼び出される関数内を含む)で__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を使った電子工作でわからないことがあるので教えてください
すみません。みけCAT さんが書きました: いいえ。値はあっていますが、余計な0を付けて9桁にする必要はありません。
もちろん、十六進数や十進数、演算子を使った式などで記述することもできるでしょう。
入力ミスで9桁になってしまいました。
ADCON0の下のはいらないように思いますが、LED_CHANGE内のは点滅させるために使っています。みけCAT さんが書きました: それもだし、LED_CHANGE内の__delay_msもです。
__delay_msがいらないように、別の方法で対策(なんの?)しましょう。
なにか別の方法で点滅させる方法はありますか?
10ms間隔にする方法がいまいちわからないのですが、例えば割り込み毎に交互に検出する方法では問題がありますか?みけCAT さんが書きました: いいえ。
(約)10ms間隔になっていない時点で明らかに違いますね。
Re: PICを使った電子工作でわからないことがあるので教えてください
キャパシタに電荷を貯めて変換結果の精度を上げるため、10μs (msではない) くらいの遅延を入れた方がいいかもしれません。あんどぅ さんが書きました:ADCON0の下のはいらないように思いますが、
もちろんです。あんどぅ さんが書きました:LED_CHANGE内のは点滅させるために使っています。
なにか別の方法で点滅させる方法はありますか?
No: 4で書いたように、別のタイマーを使いましょう。
作りたい仕様と比較して問題があると考えられれば問題があるでしょう。みけCAT さんが書きました:10ms間隔にする方法がいまいちわからないのですが、例えば割り込み毎に交互に検出する方法では問題がありますか?
10ms間隔にするには、Timer0割り込みの処理をした後にTMR0の値を適切に設定し、約10ms後に次のTimer0割り込みが来るようにします。
ある割り込みでXの処理→約10ms後Yの処理→その約10ms後Xの処理→… というようにすれば、Xの処理、Yの処理それぞれがこれまで通り約20ms間隔になります。
複雑な問題?マシンの性能を上げてOpenMPで殴ればいい!(死亡フラグ)