第26回 ブザーを制御するプログラムを追加する

ベース回路にブザーを追加してタイマーにする続きです。今回はプログラムを作ります。今回でいよいよ入門回路完成です!

本シリーズ記事の内容を改訂して、基礎編、応用編、実践編として以下のリンクに公開しています。以下のシリーズはさらにいろいろなPICマイコンの機能をご紹介しています!
PICマイコン電子工作入門 〜基礎編〜
PICマイコン電子工作入門 〜応用編〜
PICマイコン電子工作入門 〜実践編〜

目次

今回の説明

発光ダイオード点滅回路を完成させるために以下の順序で説明しています。このエントリの説明は(6)「ベース回路にブザーを追加」の部分になります。

  1. 発光ダイオード(LED)を電池と抵抗のみで光らせる回路を組む
    PICマイコンの回路を組む前に、まずは電池、抵抗、発光ダイオードのみを使って、ブレッドポード上に回路を組んで発光ダイオードを光らせてみます。ここでは電池、抵抗、発光ダイオードの回路記号、回路図と、回路図からブレッドボードに組む方法を説明します。
  2. PICマイコンのベース回路を組む
    はじめの一歩の回路は、発光ダイオードを1秒に1回光らせるだけの回路です。この回路を組みます。
  3. プログラムを作る
    発光ダイオードを1秒に1回光らせるプログラムを作成します。
  4. PICマイコンに書き込んで動作させる
    作成したプログラムをPICマイコンに書き込んで動作させてみます。
  5. ベース回路にスイッチを追加
    発光ダイオードの点滅をスイッチで開始させるために、ベース回路にスイッチを追加します。これまでは発光ダイオードを光らせる、という出力制御をしましたが、PICマイコンで外部から信号を入力する方法を確認します。
  6. ベース回路にブザーを追加
    スイッチ付きの1秒に1回光らせる回路を作りましたので、ブザーを追加してタイマーを作ってみます。

動作処理の方針

今までに作成したプログラムは以下のような動作処理をしています。

  1. 初期状態の設定として、GP5ピンをONにして、発光ダイオードを点灯させる。(電源が入ってPICマイコンが動いたことを示すため)
  2. スイッチピン(GP4)を監視し続けて、スイッチがONになるまで待つ
  3. スイッチが押されたら、発光ダイオードを1秒に1回、点滅させる。これを永遠繰り返す

今度は、一定時間経過したらブザーを鳴らすようにします。これをどのように制御すればよいか、上の処理をベースに差分を考えてみます。差分を赤文字にしてあります。

  1. 初期状態の設定として、GP5ピンをONにして発光ダイオードの点灯させる。またGP2ピンをOFFにしてブザーをOFFにする
  2. スイッチピン(GP4)を監視し続けて、スイッチがONになるまで待つ
  3. スイッチが押されたら、発光ダイオードを1秒に1回、点滅させる。この点滅は指定秒数の回数繰り返す
  4. 指定秒数点滅させたら、GP2ピンをONにしてブザーを鳴らす。またGP5ピンをONにして発光ダイオードを点灯させる。
  5. (4)の状態を続ける

今まで作成したプログラムに、赤文字の部分を追加、あるいは変更すればプログラムが作れそうです。それではひとつひとつ、どのようにすればよいか検討しましょう。

まず(1)のブザーをOFFにする制御ですが、GP2ピンをOFFにすればよいので、

GP2 = 0;

でOKそうですよね。(2)は変更がありませんのでそのままです。(3)の指定秒数繰り返すのはどうしたらよいでしょうか。予め決めた回数を繰り返すのは、for文を使えばよさそうですよね。ということで、現在、点滅の繰り返しはwhile文を使用していますので、これをfor文に変えて一定回数繰り返すことにします。

また(4)ですが、(3)で一定時間点滅を繰り返してfor文を抜けたら、ブザーを鳴らします。また、発光ダイオードも同時に点灯させようと思います。ブザーを鳴らすのは、

GP2 = 1;

でOKですよね。また発光ダイオードを点灯させるのは

GP5 = 1;

で問題なしです。あとは(5)でこの状態をずっと保持するので、

while(1){
}

とすることにします。

プログラム作成

動作処理部分は上のように変更すればよさそうですが、他の部分についても検討します。何度もしつこくてすみませんが、プログラムの構成を再度確認しましょう。(中身のプログラムは初版(点滅を永遠繰り返すのみ)のものです)

Prog structure

今回もコメント部分は動作処理の内容をメモしておくことにします。次のヘッダファイルインクルード部分ですが、ブザーの制御はピンの制御だけで済みますので、今までどおりのヘッダファイルで問題ありません。PICマイコンコンフィグレーション設定部分とその他設定部分も、特に変更はありません。

それではメイン関数内を確認します。

Main block

まず内部クロック設定は特に変更ありません。ピンの設定ですが、現在はすべてのピンがデジタル設定ですのでANSELについては変更なしです。入出力設定(TRISIO)については、GP4ピンとGP3ピンが入力ピンで、他は出力ピンの設定です。今回は新規にGP2ピンを出力ピンとしてブザーを制御しますが、すでに出力ピンになっていますので、この部分も変更なしです。

あとはピンの初期設定でブザー制御の追加となります。

ということで、ほぼピンの初期設定部分と動作処理部分の追加・変更で済みそうですね。

特に難しいところはないと思いますので、これ以上の解説はせずに、プログラムを載せておきます。なお、タイマーの時間は10秒にしています。コメントを入れましたので、プログラム上での処理内容を確認してみてください。わからないことがありましたらCONTACTページからご連絡いただくかコメントいただければと思います。

/*
 * File:   main.c
 * Author: Tool Labs
 * プログラム内容:
 *   電源投入後は発光ダイオード(GP5)を点灯、ブザーはOFFにしておく。
 *   スイッチ(GP4)が押されたら、発光ダイオードを点滅させる。
 *   一定時間経過したらブザー(GP2)を鳴らし、発光ダイオード(GP5)を点灯させる。
 */


// インクルードファイル
#include <stdio.h>
#include <stdlib.h>
#include <xc.h>

// PIC12F683コンフィグレーションビット設定
#pragma config FOSC = INTOSCIO  // Oscillator Selection bits (INTOSCIO oscillator: I/O function on RA4/OSC2/CLKOUT pin, I/O function on RA5/OSC1/CLKIN)
#pragma config WDTE = OFF       // Watchdog Timer Enable bit (WDT disabled)
#pragma config PWRTE = ON       // Power-up Timer Enable bit (PWRT enabled)
#pragma config MCLRE = OFF      // MCLR Pin Function Select bit (MCLR pin function is digital input, MCLR internally tied to VDD)
#pragma config CP = OFF         // Code Protection bit (Program memory code protection is disabled)
#pragma config CPD = OFF        // Data Code Protection bit (Data memory code protection is disabled)
#pragma config BOREN = ON       // Brown Out Detect (BOR enabled)
#pragma config IESO = OFF       // Internal External Switchover bit (Internal External Switchover mode is disabled)
#pragma config FCMEN = OFF      // Fail-Safe Clock Monitor Enabled bit (Fail-Safe Clock Monitor is disabled)

// クロック周波数指定
// (__delay_ms()関数が必要としているため)
#define _XTAL_FREQ  1000000

/*
 * main()関数
 */
int main(int argc, char** argv) {

    // PICマイコン初期化
    OSCCON = 0x40;  //クロック周波数を1MHzに設定
    ANSEL  = 0x00;  //すべてのピンをデジタルモードに設定
    TRISIO = 0x10;  //GP4を入力ピン、それ以外を出力ピンに設定。ただしGP3ピンはもともと入力固定

    // タイマー用の変数
    unsigned char timer;

    // ブザーのピン(GP2)を0にして、ブザーをOFFにする
    GP2 = 0;

    // LEDのピン(GP5)を1にして、LEDを点灯させる
    GP5 = 1;

    // スイッチ(GP4)がOFFの間、何もしないで待ち続ける
    while(GP4 == 0){
    }

    // スイッチが押されたら、LEDの点滅を開始する
    // まずはじめにLEDのピン(GP5)を0にして、LEDを消灯する
    GP5 = 0;

    // 1秒点滅を10回繰り返す。つまり10秒間点滅させる
    for(timer=0; timer<10; timer++) {
        // まず、消灯状態のまま950ミリ秒待つ
        __delay_ms(950);
        // 950ミリ秒経過したら、LEDのピンを1にして、LEDを点灯する
        GP5 = 1;
        // LED点灯状態で50ミリ秒待つ
        __delay_ms(50);
        // 50ミリ秒経ったら、LEDのピンを0にして、LEDを消灯する
        GP5 = 0;
    }

    // ブザーのピン(GP2)を1にして、ブザーをならす
    GP2 = 1;

    // LEDのピン(GP5)を1にして、LEDを点灯させる
    GP5 = 1;
    
    // そのままの状態にする
    while(1){
    }

    // ここには到達しない
    return (EXIT_SUCCESS);
}

動作確認

プログラムが理解できたら、ビルドしてPICマイコンに書き込み、動作確認してみてください。10秒間点滅を繰り返したら、ブザーが鳴って発光ダイオードが点灯した状態になるはずです。動きましたか? これでついに入門回路の完成です!

ただ、残念ながら時間が経つとブザーは鳴りっぱなしですので、ブザーを止めるには電池ボックスのスイッチをOFFにするしかありません。この改善については、チャレンジ課題にしたいと思います。

動作確認についても、うまく動かなかったり、わからないところがありましたらご連絡いただければサポートできる範囲でお手伝いします!

タイマー時間の変更

さて、これでようやく完成したわけですが、上のプログラムではあとあと問題が出てしまうような、よくありがちな実装をしています。もしかしたら、プログラムを読んでいる時に気づかれた方もいらっしゃるかもしれませんね。

動作確認するときの時間を短くするために、タイマーとしては短い10秒、という時間で動作確認しました。でも実用的なタイマーでしたらもうちょっと時間をのばしてみたいですよね。ということで早速プログラムを変更して5分タイマーを作ってみたいと思います。

今の10秒タイマーを5分タイマーにするには、for文のところが現在は10回ループするようになっているので、ここを5分=300秒、つまり300回ループするように変更すれば、簡単じゃん、っていう感じですよね。

それでは実際にfor文のところを

for(timer=0; timer<300; timer++) {

と変更して動作させてみてください。点滅を300回数えるのはしんどいので、時計で時間を計測してみましょう。電源をつないで、スイッチを押してタイマーを開始して、、5分待って、、、そろそろ6分すぎるんだけど、、、、あれ? 鳴らない、、、、、なかなかブザーが鳴りませんよね。このプログラムではいつまで待ってもブザーは鳴りません。

結論を言ってしまうと、変数のtimerに問題があります。このtimerは

unsigned char timer;

で型宣言しています。この “unsigned char” は符号なし8ビット、ということで10進数でいうと、0〜255までしか数字を保持できません。for文では “timer++” としてtimerをカウントアップしていますが、timerが255の状態で timer++ をすると0に戻ります。つまり、いつまで待っても300(正確には299)までカウントできないわけです。

timerを型宣言するときは実用的な時間が計測できるようにします。型としては、

  • unsigned char (8ビット)
  • unsigned short (16ビット)
  • unsigned short long (24ビット)
  • unsigned long (32ビット)

があります。8ビットは255までのカウント、つまり4分強のタイマーしか作れません。16ビットあれば65535までのカウント、時間に直すと18時間強までのタイマーとなります。今回の回路はそこまでの時間を正確に測れなさそうですので、timerの型は unsigned shortで十分でしょう。ということで、このあたりにも注意してプログラムの最終版とします。タイマー時間は5分にしています。

/*
 * File:   main.c
 * Author: Tool Labs
 * プログラム内容:
 *   電源投入後は発光ダイオード(GP5)を点灯、ブザーはOFFにしておく。
 *   スイッチ(GP4)が押されたら、発光ダイオードを点滅させる。
 *   一定時間経過したらブザー(GP2)を鳴らし、発光ダイオード(GP5)を点灯させる。
 *   for文のtimerで秒数をカウント。timerの最大値は65535。
 */


// インクルードファイル
#include <stdio.h>
#include <stdlib.h>
#include <xc.h>

// PIC12F683コンフィグレーションビット設定
#pragma config FOSC = INTOSCIO  // Oscillator Selection bits (INTOSCIO oscillator: I/O function on RA4/OSC2/CLKOUT pin, I/O function on RA5/OSC1/CLKIN)
#pragma config WDTE = OFF       // Watchdog Timer Enable bit (WDT disabled)
#pragma config PWRTE = ON       // Power-up Timer Enable bit (PWRT enabled)
#pragma config MCLRE = OFF      // MCLR Pin Function Select bit (MCLR pin function is digital input, MCLR internally tied to VDD)
#pragma config CP = OFF         // Code Protection bit (Program memory code protection is disabled)
#pragma config CPD = OFF        // Data Code Protection bit (Data memory code protection is disabled)
#pragma config BOREN = ON       // Brown Out Detect (BOR enabled)
#pragma config IESO = OFF       // Internal External Switchover bit (Internal External Switchover mode is disabled)
#pragma config FCMEN = OFF      // Fail-Safe Clock Monitor Enabled bit (Fail-Safe Clock Monitor is disabled)

// クロック周波数指定
// (__delay_ms()関数が必要としているため)
#define _XTAL_FREQ  1000000

/*
 * main()関数
 */
int main(int argc, char** argv) {

    // PICマイコン初期化
    OSCCON = 0x40;  //クロック周波数を1MHzに設定
    ANSEL  = 0x00;  //すべてのピンをデジタルモードに設定
    TRISIO = 0x10;  //GP4を入力ピン、それ以外を出力ピンに設定。ただしGP3ピンはもともと入力固定

    // タイマー用の変数
    unsigned short timer;

    // ブザーのピン(GP2)を0にして、ブザーをOFFにする
    GP2 = 0;

    // LEDのピン(GP5)を1にして、LEDを点灯させる
    GP5 = 1;

    // スイッチ(GP4)がOFFの間、何もしないで待ち続ける
    while(GP4 == 0){
    }

    // スイッチが押されたら、LEDの点滅を開始する
    // まずはじめにLEDのピン(GP5)を0にして、LEDを消灯する
    GP5 = 0;

    // 1秒点滅を300回繰り返す。つまり5分間点滅させる
    for(timer=0; timer<300; timer++) {
        // まず、消灯状態のまま950ミリ秒待つ
        __delay_ms(950);
        // 950ミリ秒経過したら、LEDのピンを1にして、LEDを点灯する
        GP5 = 1;
        // LED点灯状態で50ミリ秒待つ
        __delay_ms(50);
        // 50ミリ秒経ったら、LEDのピンを0にして、LEDを消灯する
        GP5 = 0;
    }

    // ブザーのピン(GP2)を1にして、ブザーをならす
    GP2 = 1;

    // LEDのピン(GP5)を1にして、LEDを点灯させる
    GP5 = 1;
    
    // そのままの状態にする
    while(1){
    }

    // ここには到達しない
    return (EXIT_SUCCESS);
}

プログラムの仕様を決めるときは、最小値や最大値に注意するようにしたほうがよいですね。今回はタイマーでしたのでどのくらいの時間まで計れればよいかを先に検討して、それに応じてプログラム内の変数を定義するように注意します。

他の例では、例えば温度計を作る場合、何度〜何度まで計れるようにするか、その値が扱える内部の変数をどう定義するか検討する必要があります。

なお、余裕を見て変数を用意するのはよくありません。long型を使えばかなりの値を扱うことができますが、必要バイト数が多い分、メモリを余計に消費しますし、処理時間もかかることになります。Macのアプリでしたら贅沢にメモリやCPUパワーを使ってもよいかもしれませんが、マイコンの場合はメモリやCPUパワーはかなり貧弱ですので、プログラムを作る際はこのような観点でも注意するようにしてください。

これで一通り終わりました。随分長い間いろいろと寄り道もしてしまいましたが、ようやく入門の回路とプログラムが完成しました。いろいろとわかりづらいところもあったかもしれませんが、長い間お疲れさまでした!

チャレンジ課題

今までの知識を元にしたチャレンジ課題を作ってみました。特に解答は示しません。正解はひとつではないです。正しく動けば正解です。

  • タクトスイッチを押してブザーを止められるようにする
    上のプログラムでは、時間が経過したらブザーが鳴り続け、止めるには電源を切るしかありませんでした。せっかくタクトスイッチがありますので、時間が経過してブザーがなったら、タクトスイッチを押してブザーを止められるようにしてみましょう。
  • さらにタイマーを繰り返すようにする
    タクトスイッチを押してブザーを止められるようにできても、もう一度タイマーを動かしたい場合、電源をOFF/ONする必要があります。時間が経過してブザーが鳴ったら、タクトスイッチを押すとブザーを止めて、また初期状態に戻る、とうようにしてみましょう。
  • 1秒毎に点灯したり消灯したりする
    現在の点滅パターンは、1秒に1回、ピカッと光る感じにしています。点滅パターンを変えてみましょう。まずは0.5秒消灯して、0.5秒点灯する、というパターンにしてみましょう。次はちょっと難しいかもしレませんが、1秒毎に点滅を繰り返すようにしてみましょう。簡単そうですが、タイマーと絡むとちょっと難しくなると思います。永遠点滅を繰り返すのであれば、while(1)で「1秒消灯、1秒点灯」というパターンを繰り返せばいいですよね。でもタイマーにした場合、「1秒消灯、1秒点灯」というセットを繰り返すようにしてしまうと、偶数秒のタイマーしかできなくなってしまいます。任意の秒数で設定できるようにしてみましょう。

次回は、スイッチの根深い問題を詳しく説明します。その後、さらにチャレンジ課題を作って、この入門シリーズを終わりにしたいと思います。

更新履歴

日付 内容
2015.9.22 新規投稿
2018.12.3 新シリーズ記事紹介追加
通知の設定
通知タイミング
guest
0 コメント
新しい準
古い順 一番投票が多い
本文中にフィードバック
全てのコメントを見る
TOORU
TOORU
8 年 前

いつも誠実な、ご回答頂き、どうもありがとうございます。
仰る通りです。
途中まで、最新版を見させて頂いて、その続きを過去のものを参考にさせて頂いてやっています。
是非、この続きをやって、意図した通りの動作を実現させてみたいので、
それでは、今回、申し訳ないのですが、甘えさせていただく事にします。
別途、お問い合わせから、ご連絡させていただきますので、プログラムの方、
どうぞ、よろしくお願い致します。
お忙しいのに、色々とご面倒お掛けしてすいませんm(_ _)m

TOORU
TOORU
8 年 前

いつもお世話になります。
知識がどんどん高まり、すごく面白いです。
picマイコンって面白いなあと思います。
色々勉強させてもらってありがとうございます。
さて、実は、また、質問させて欲しい事がありましてコメントさせて頂きました。

実は、上記の完成した、スイッチオンで数回点滅後ブザーがなるプログラムについてですが、
いくらスイッチをオンにしてもそこから先の展開が行われないのです。

(ずっと、LEDが点灯しっぱなしの状態です。いくらスイッチを押しても点滅を開始しません)

何処か間違っているのかと、なんどもビルド、書き込みを繰り返してやって見ましたが、
そのたびに、プログラムの方は、問題無くビルドが出来、また書き込みも問題無く終了します。

なのに、意図した通りの(プログラム通りの)動作をしません。

それで、一旦、本サイトの上記プログラムを、そのまま、コピペをして動作させようと思いつきました。
(実は、私のプログラムは、少し、自分のオリジナルで少し弄くっています。まあ、弄くっていると言っても、
delay_msの所の数値を少し変えている程度のものですが)

で、コピペした後、ビルドしようとした所、どう言う訳かエラーになってビルド出来ない状態なんです。
エラーメッセージは以下の通りです。

make[2]: *** [build/default/production/main.p1] Error 1
make[1]: *** [.build-conf] Error 2
make: *** [.build-impl] Error 2

BUILD FAILED (exit value 2, total time: 455ms)

これは、どうしてでしょうか?
全く単純に上記プログラムをコピペしたので、一字一句間違っていないと思います。

しかし、コピペする前と後を見比べて見ると、ちょっと違う事に気が付きました。
私が使っていたプログラムソースは、『第16回 プログラムをコピペして一度動作させてみる』と時、
コピペしたプログラムです。
それを、少しずつ追加したりして、ブザーを鳴らす所まできました。

違って居るのは、例えば、
メイン関数の最初の初期設定で、

コピペ ⇒  ANSELA = 0b00000000; // すべてのピンをデジタルモードに設定

本ページの上記プログラム ⇒  ANSEL = 0x00; //すべてのピンをデジタルモードに設定

と言った様に、記載の表現方法が違います。

また、本ページの上記プログラムは、

GP5 = 0; ← このような記載表現ですが、

コピペしたほうは、

LATA5 = 0; ← このような記載表現になっています。

これは、何か、私が教えて頂いたやり方通りやってないからでしょうか?
細かい事で申し訳ないですが、是非、また、ご教示頂きたいです。

なんとか、プログラムを完成させ、意図したとおりの動作が出来る様になりたいです。
どうぞよろしくお願い致しますm(_ _)m

管理者
管理者
返信  TOORU
8 年 前

TOORUさま、
お世話になります。

すみません、記事がややこしくて申し訳ないのですが、本サイトの入門コースは以下のような状態になっています。

PIC12F683を使用した入門記事 → 完成済み
http://tool-lab.com/make-course/macpic/

PIC12F1822を使用した入門記事 → 書きかけ
http://tool-lab.com/make-course/picbasic/

いただいたコメントの内容から判断すると、PIC12F1822を使用されていて、途中からPIC12F683の記事内容をご覧いただいたのではないかと思います。

申し訳ないのですが、本業も色々と忙しく、PIC12F1822の記事はなかなか進まない状況です。PIC12F1822の記事ではPIC12F683とは違うピンに接続予定なのと、PIC12F683とPIC12F1822ではピンの扱いが少し異なるので、プログラムはそのまま流用できません。

もしPIC12F1822を使用されていて、PIC12F683の記事の通りの回路を組まれたようでしたら、その回路で動作するプログラムを作成してお送りいたしますのでご連絡いただければと思います。

目次