前提・実現したいこと
初めて質問させて頂きます。
PICの初心者で、様々な所からご教示頂き勉強中です。
よろしくお願いいたします。
PIC16F1503を使用して下記動作のプログラミングを製作しています。
①PB1を押すとLED1が10秒間点滅しBZがON、LED1が点滅中にPB2を押すとキャンセルされ最初に戻る。
②10秒後LED1が消灯、BZがOFF、LED2が点滅
③PB2を押すとすべてキャンセルされ①に戻る
発生している問題・エラーメッセージ
動作はしているが
①PB1を押した後の、10秒カウントが17秒位ある。
②LED2の点滅が不安定で点滅周期が長かったり、短かったりする。
また、PB2を押さない限りLED2はずっと点滅するはずが、途中から点灯に代わってしまう。
③そもそもこのコードは最適なのか?
該当のソースコード
#define _XTAL_FREQ 2000000 void main(void) { //マイコン設定 OSCCON = 0b01100000; //内部クロック周波数を2MHzに設定 ANSELA = 0b00000000; //PortA全てのピンをデジタルモードに設定 ANSELC = 0b00000000; //PortC全てのピンをデジタルモードに設定 TRISA = 0b00000000; //PortA全てのピンを入力モードに設定 TRISC = 0b00000000; //PortC全てのピンを出力モードに設定 //初期処理:電源ON後、全ての出力を一度OFFにする。 LATC0=0; //LED1 LATC1=0; //LED2 LATC2=0; //BZ //RA5:PB1、RA4:PB2 while(1){ if(RA5==1){ int cnt=0; //PB1がONの時、最初にカウンタに0を代入 while(cnt<10000 && RA4==0){ //カウント値が30秒以下、PB2 OFFの時、下を実行 LATC2=1; //BZ ON if(cnt%500<250)LATC0=1; //LED1 ON else LATC0=0; //LED1 OFF __delay_ms(1); cnt++; } while(cnt==10000){ //カウント値が10秒に達したら下を実行 LATC0=0; //LED1 OFF LATC2=0; //BZ OFF while(RA4==0){ //PB2を押さない限り下を実行 if(cnt%1000<500)LATC1=1; //LED2 ON else LATC1=0; //LED2 OFF __delay_ms(1); cnt++; } } LATC0=0; //LED1 OFF LATC1=0; //LED2 OFF LATC2=0; //BZ OFF } } return; } ```### 試したこと ①の10秒カウントについて ・点滅コードが原因かと思い、LED1の点滅をやめて10間点灯に変更すると正常に10後LED2が点滅した。 ・点滅コードを ```ここに言語を入力 for(timer=0;teimer<10;timer++){ LATC0=1; __delay_ms(500); LATC1=0; __delay_ms(500); ```にすると、PB2を押してもキャンセルされない時がある。 ②の点滅が点灯に代わってしまう事について カウント上限?になってしまい、計算結果が真のままになってしまって点灯しっぱなしになっているのが原因かと思い、cnt++の下に if(cnt==2000){ cnt=0; } を追加したが関係なかった。 ### 補足情報(FW/ツールのバージョンなど) MPLABX IDE v5.30を使用
気になる質問をクリップする
クリップした質問は、後からいつでもMYページで確認できます。
またクリップした質問に回答があった際、通知やメールを受け取ることができます。
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。
2020/04/30 01:18
2020/04/30 01:56
2020/04/30 12:30
2020/05/12 11:26
2020/05/13 04:13
回答5件
0
__delay_ms(500);
にすると、PB2を押してもキャンセルされない時がある。
delay中に押して離すと、押していないと判定されるからでしょう。
cnt++の下に
if(cnt==2000){
cnt=0;
}
を追加したが関係なかった。
cnt
は10000
を超えているのでcnt==2000
が成立することはありません。
while(RA4==0)
の前でcnt
を0
にしておけばそのコードでも動くでしょう。
PB2を押さない限りLED2はずっと点滅するはずが、途中から点灯に代わってしまう
cnt
はint
(16bit)なので32767
を超えるとオーバーフローしてマイナス値になり、マイナス値の間はcnt%1000<500
が成立します。
(追記)
時間が合わない件について書き忘れていました。
y_waiwaiさんの回答にもある通り、delay以外の時間も加算されるため細かく刻むほどdelay以外で消費した時間が蓄積され表面化します。
LED点滅と時間カウントにはタイマの使用を検討されてみては?
時間の正確性にあまりこだわらないなら100ms間隔程度にすれば気にならない程度に収まると思いますが、その場合はdelayを伸ばした分ボタンはゆっくり操作する必要があります。
C
1__delay_ms(100); 2cnt+=100;
ループが回るたびにLATC0
,LATC1
,LATC2
を設定する必要もないですね…
250msや500msに一度しかLED設定していないので、以下のようにしてLED設定回数を減らし除算もなくすとどうなります?
C
1 while(1){ 2 if(RA5==1){ 3 int cnt=0; //PB1がONの時、最初にカウンタに0を代入 4 int led=0; 5 LATC2=1; //BZ ON 6 int ra4 = RA4; 7 while(cnt<10000 && ra4==0){ //カウント値が10秒以下、PB2 OFFの時、下を実行 8 LATC0=led=1-led; 9 for(int i = 0; i < 250 && ra4==0; ++i){ 10 __delay_ms(1); 11 cnt++; 12 ra4 = RA4; 13 } 14 } 15 if(cnt==10000){ //カウント値が10秒に達したら下を実行 16 LATC0=0; //LED1 OFF 17 LATC2=0; //BZ OFF 18 led=1; 19 ra4 = RA4; 20 while(ra4==0){ //PB2を押さない限り下を実行 21 LATC1=led=1-led; 22 for(int i = 0; i < 500 && ra4==0; ++i){ 23 __delay_ms(1); 24 ra4 = RA4; 25 } 26 } 27 } 28 LATC0=0; //LED1 OFF 29 LATC1=0; //LED2 OFF 30 LATC2=0; //BZ OFF 31 } 32 }
投稿2020/04/30 00:55
編集2020/05/03 09:48総合スコア4079
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。
2020/04/30 01:16
2020/04/30 06:28
退会済みユーザー
2020/04/30 06:47
2020/05/09 08:10
2020/05/12 00:21
2020/05/12 04:30
2020/05/13 02:47 編集
2020/05/13 02:33
2020/05/13 02:45
2020/05/13 04:16
0
PICには手を出していないので実験は出来ませんが...
各命令の実行に時間がかかるのは事実ですが、しかし
C
1 while (cnt < 10000 && RA4 == 0) { //カウント値が30秒以下、PB2 OFFの時、下を実行 2 LATC2 = 1; // BZ ON 3 if (cnt % 500 < 250) 4 LATC0 = 1; // LED1 ON 5 else LATC0 = 0; // LED1 OFF 6 __delay_ms(1); 7 cnt++; 8 }
で、__delay_ms()以外の部分の実行に0.7ms掛かっている、というのは単純に「ああそうですか」と受け入れていいレベルを超えていると思います。
点滅の周期も1.7秒くらいなのですか? 同じことですが、17秒の間に10回点滅しているのですか?
・点滅コードが原因かと思い、LED1の点滅をやめて10間点灯に変更すると正常に10後LED2が点滅した。
このときのコードはどのようなものだったのでしょう。
コンパイラの性能がすごく悪くて、割り算がものすごく遅い、とかだと可能性はあるかしら。
if (cnt % 500 < 250)
を
if( (cnt & 0x100)!=0 )
とかしたら(点滅周期は12msほどずれるけど)どうなるでしょうか。
なお、この部分だけでも元の質問ではLATC2 = 1;
のセミコロンがいわゆる全角だったり、cnt1
なんていう変数はなかったりします。コンパイルを通るわけがないので、これはあなたが実際にコンパイルして実行しているプログラムではあり得ません。あなたが実際にコンパイルして実行しているプログラム「そのもの」を提示して下さい。そうでないと、実は間違っているのを手で打ち直しているうちに「直して」しまっていて、いくら質問のプログラムを見ても原因が見つからない、なんていう可能性を否定出来なくなってしまいます。
もう一つ気になるのですが、スイッチの接続はどのような回路になっていますか? スイッチの一方をVDD(電源), もう一方をマイコンの入力ピンに繋いだだけ...なんていう回路になっていないでしょうか。(初心者と称する人が「スイッチONで1」としているとしばしばその回路になっているので...)
その場合、スイッチがOFFの場合マイコンの入力ピンは何も繋がっていない状態になりますがそれは「0(Lowレベル)」ではありません。たまたま0になるかも知れませんが、ノイズやちょっとした回路の都合で簡単に1になるかも知れません。不安定...問題になっている症状の1つじゃありませんでしたっけ。
マイコンの入力端子に何も繋がないという使い方は原則として「やってはいけない」と思って下さい。プルアップ/プルダウン回路を内蔵していてそれを使っている、とかいうのなら問題はないのですが、そうでなければマイコン外部にプルアップやプルダウン抵抗を接続して、マイコンの入力ピンの電位が確実に決まっているようにする必要があります。
コメントに関して追記 5/1
●スイッチの配線について
スイッチの配線は色々なサイトを見て勉強し、一応プルダウン?抵抗を接続しGNDに接続しています。
不安定という事象に対しては一番疑われる部分です。配線の間違い、部品の間違い(抵抗値等)、、接触不良、近隣とのショート、極端に長い配線等もありませんね? 「一応」ではなく「大丈夫」といい切れるようにして下さい。
●コンパイラの性能がすごく悪くて、割り算がものすごく遅い、とかだと可能性はあるかしら。
⇒そのような事があるんですか?想像がつきませんでした。
小学校で計算を習う順番を考えてみて下さい。足し算、引き算、掛け算、割り算の順で習うでしょう。その順で「難しい」のです。コンピュータの場合も、特別な計算回路を持っていないマイコンではその順番で計算が面倒になり、時間がかかることは考えられます。
volatile int i,j,result=0;//volatileはどこかで値が変更されているかも知れない、というコンパイラへの指示。これをつけないと計算を端折ることがあるので //ここでLED ON for(i=0;i<10000;i++){//ループの回数は違いが判断できて結果が出るまで我慢できるくらいの時間に適当に設定 for(j=0;j<100;j++) result=i+j; //ここを result=i-j;とかresult=i*jとかresult=i/jとかresult=i%jに替えてみる } } //ここでLED OFFしてLEDが点灯していた時間を計る while(1);//停止
みたいな実験をして調べてみるといいかも。
手元のArduino UNO(クロック16MHz)では+,-が約2秒 *が2.5秒に対し/と%は15.7秒かかりました。
PICで質問のプログラムの条件に置き換えると
・クロック2MHzで速度1/8(かかる時間は8倍)
・繰り返し回数10000で1/100
・CPUの作りも違ってAVRは1クロック1命令、PICは4クロック1命令が基本で4倍時間がかかる
とすると単純に換算して(相当いい加減な見積もりですが)5秒くらい。つまり10秒ループのはずが15秒とかの実行時間はあり得る、ということになりそうです。(意外と大きいなぁ)
だとすると、
●実験したコードは単純に
if(cnt%500<250)
LATC0=0;
else LATC0=1;
を抜いただけです。
これについては、そこを抜くのではなく
C
1if(cnt%500<250) 2LATC0=1; 3else LATC0=1;
による点きっぱなしも試してみた方がよさそうですね。「点滅」の影響なのか、割り算が遅いのか。
それはそれとして
●17秒の間に10回点滅しているのですか?
⇒10回以上点滅していました。
これは重要な事実です。点滅周期の情報がありませんが、点滅が10回を超えるということがあるなら、単純にループに時間がかかっているのとは別の現象ですから。
投稿2020/04/30 13:05
編集2020/05/01 02:05総合スコア7703
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。
2020/04/30 23:48
2020/05/01 00:47
2020/05/01 02:06
2020/05/01 02:25 編集
2020/05/01 05:06
2020/05/04 23:36
2020/05/09 08:07 編集
2020/05/09 09:23
2020/05/11 04:57
2020/05/11 05:04
2020/05/11 11:47
2020/05/11 23:55
0
ベストアンサー
誰も突っ込まないようなので
●スイッチの配線について
スイッチの配線は色々なサイトを見て勉強し、一応プルダウン?抵抗を接続しGNDに接続しています。
プルダウン(マイコンピンとGNDを抵抗で繋ぐ)すると、その電圧はLOWレベル(GND)側に引っ張りますから、LOWと判断されます。
スイッチの先は、押した時にそれとは識別できるようにしなければ意味がありませんから、VCC(5V)に繋ぎます。
※と言っても、ある程度動いているようですから、実際は正しく配線してあるのだと想像します。
①PB1を押した後の、10秒カウントが17秒位ある。
作業時間が蓄積されて誤差の7秒となっています。
8bitのPICはかなり非力なマイコンです。
現在流通されているマイコンの中でも最弱だと思った方が良いです。
(乗算器が無い&4クロックで1命令を行う)
2MHz駆動にすると、実質(他の多くのマイコンに換算すると)500kHzで駆動している事になります。
なので、内部クロックはもっと高くした方が良いです。例えば2MHz→16MHzに変更しただけで、誤差の7秒が
1秒程度になる事が期待されます。
そして、点滅のループもやはり、「このマイコンは処理能力が低い」という事を意識して書くべきです。
つまり、10000回もループさせたら誤差が大きくなります。
例えば10msを単位として、1000回のループにすれば、誤差は1/10程度になると期待されます。
上記の2つの事を考慮して書けば、7秒の誤差が1/80、つまり、0.1秒程度になるので、全く気になる事が無い範囲になります。
②LED2の点滅が不安定で点滅周期が長かったり、短かったりする。
また、PB2を押さない限りLED2はずっと点滅するはずが、途中から点灯に代わってしまう。
プログラムでは、if(cnt%1000<500)ですけど、変数cntはintです。
その場合、変数範囲の半分はマイナス値です。そうなると余り(%)もマイナス値になります。
よって、cnt++で、マイナス値に切り替わったあたりから暫く(約33秒)は(cnt%1000<500)は真になります。
cntをunsigned intで宣言すれば直ると思います。(とはいえ、数値が一周した瞬間だけ、ちょっと乱れます。)
③そもそもこのコードは最適なのか?
想定したように動いていない時点で・・・
細かい事は、他の方の指摘とダブりそうなので、控えます。
(一番最初なら一通り指摘したと思いますけど)
その代わり、一通り書き換えておいたものを置いておきます。
コンパイルまでは試しましたけど、レジスタを確認した訳でもないし、検証した訳でもありません。
c
1//接続を定義しておくと解り易い 2#define LED1 LATC0 3#define LED2 LATC1 4#define BZ LATC2 5#define PB1 RA5 6#define PB2 RA4 7 8#define _XTAL_FREQ 16000000 9#include <xc.h> 10 11void main(void) { 12 OSCCON = 0b01111000; //内部クロック周波数を16MHzに設定 13 ANSELA = 0b00000000; //PortA全てのピンをデジタルモードに設定 14 ANSELC = 0b00000000; //PortC全てのピンをデジタルモードに設定 15 TRISA = 0b11111111; //PortA全てのピンを入力モードに設定 16 TRISC = 0b00000000; //PortC全てのピンを出力モードに設定 17 18 //初期処理:電源ON後、全ての出力を一度OFFにする。 19 LED1 = 0; 20 LED2 = 0; 21 BZ = 0; 22 23 int cnt = 0; //カウンタ 24 int mode = 0;//modeは0:全停止 1:LED1点滅、BZオン 2:LED2点滅 25 26 while (1) { 27 //入力(sw)処理 28 if (PB1 && mode == 0) { mode = 1;cnt = 0;} 29 //modeが0の状態でPB1押されたら、modeを1にしてカウンタも初期化 30 if (PB2) {mode = 0;} 31 //PB2が押されたら、どんな状態からでもmodeを0 32 33 34 //出力処理 35 if (mode == 0) {//mode0は全オフ 36 LED1 = 0; 37 LED2 = 0; 38 BZ = 0; 39 } else if (mode == 1) {//mode1はLED1点滅、BZオン 40 LED1 = (cnt % 50 < 25); 41 LED2 = 0; 42 BZ = 1; 43 __delay_ms(10); 44 if (++cnt == 1000){mode = 2;cnt=0;}//10秒になったらmodeを2に 45 } else if (mode == 2) {//mode2はLED2のみ点滅 46 LED1 = 0; 47 LED2 = (cnt < 50); 48 BZ = 0; 49 __delay_ms(10); 50 if(++cnt==100)cnt=0; 51 } 52 } 53} 54
追記
よく見たら、②は、とっくにSHOMIさんが指摘していましたね。
それなら、後からでしゃばる必要も無かったなぁ・・・
投稿2020/05/03 02:30
編集2020/05/06 09:33総合スコア494
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。
2020/05/03 02:59
2020/05/03 05:51
2020/05/03 08:14
2020/05/24 01:57 編集
2020/05/04 14:22
2020/05/04 23:25
2020/05/05 03:14
2020/05/05 03:51
2020/05/05 22:20
2020/05/05 22:54
2020/05/06 04:14
2020/05/06 05:38
2020/05/06 06:49
2020/05/06 22:49
2020/05/07 00:01
2020/05/07 04:46 編集
2020/05/07 12:19
2020/05/07 13:19
2020/05/07 22:07
2020/05/07 22:46
2020/05/07 23:22
2020/05/11 02:05
2020/05/11 05:29
2020/05/11 09:09
2020/05/12 00:08
2020/05/12 03:24
2020/05/12 04:14
0
__delay_ms() ではなく 内蔵タイマを基準とします
PIC16F84 でテスト PIC16F1503 でも基本的な考え方は同じです (除算は使いません)
SW , LED , BZ の ON/OFF (o,1) は #define で設定しています
#include <xc.h> #pragma config WDTE=OFF,PWRTE=OFF,CP=OFF,FOSC=HS #define _XTAL_FREQ 10000000 // 10MHz #define FOSC4 ( _XTAL_FREQ / 4 ) // PIC CLOCK #define _500ms ( FOSC4 / 2 ) #define _10s ( FOSC4 * 10 ) #define LED1 RA2 #define LED2 RA3 #define LED_ON 1 #define LED_OFF 0 #define SW1 RB0 #define SW2 RB1 #define SW_ON 0 #define BZ RA1 #define BZ_ON 0 #define BZ_OFF 1 unsigned short long tmr0_cnt3 = 0 ; // 24bit unsigned long tmr0_cnt4 = 0 ; // 32bit unsigned char blink_flag = 0 ; void main(void){ TRISA = 0 ; // OUTPUT TRISB = 0xFF; // INPUT OPTION_REG = 0b01010111; // TMR0_ON Prescaler 1:256 , PORTB pullup LED1=LED_OFF ; LED2=LED_OFF ; BZ=BZ_OFF ; while(1){ if ( SW1 == SW_ON && blink_flag==0 ){ blink_flag=1 ; tmr0_cnt3=0 ; tmr0_cnt4=0 ; } if ( SW2 == SW_ON ){ LED1=LED_OFF ; LED2=LED_OFF ; BZ=BZ_OFF ; blink_flag=0 ; } if ( T0IF==1 && blink_flag ==1 ){ // tmr0 over flow T0IF=0; tmr0_cnt3 += 65536; if ( tmr0_cnt4 < _10s ){ tmr0_cnt4 += 65536; } if ( tmr0_cnt3 >= _500ms ){ tmr0_cnt3 -= _500ms; if ( tmr0_cnt4 < _10s ){ LED1 ^= 1 ; LED2=LED_OFF ; BZ=BZ_ON ; } else { LED2 ^= 1 ; LED1=LED_OFF ; BZ=BZ_OFF ; } } } } // while }
投稿2020/05/02 04:11
総合スコア401
0
そもそも、時間のカウントに__delay_msを使ってますが、たとえこの関数で1msきっちりのディレイが得られたとしても、その他の命令の実行時間がそれに加わりますんで、10000回まわすと10秒きっちり、というわけにはいかなくなります
そもそもディレイとかスリープ系の関数というのは、なにもしないでひたすらその時間がすぎるのを待ってるだけ、です。
ですんで、それを前提にコードの動作を追いかけてみてはどうでしょう
投稿2020/04/30 01:05
総合スコア88040
あなたの回答
tips
太字
斜体
打ち消し線
見出し
引用テキストの挿入
コードの挿入
リンクの挿入
リストの挿入
番号リストの挿入
表の挿入
水平線の挿入
プレビュー
質問の解決につながる回答をしましょう。 サンプルコードなど、より具体的な説明があると質問者の理解の助けになります。 また、読む側のことを考えた、分かりやすい文章を心がけましょう。