第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を型宣言するときは実用的な時間が計測できるようにします。型としては、

があります。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パワーはかなり貧弱ですので、プログラムを作る際はこのような観点でも注意するようにしてください。

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

 

チャレンジ課題

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

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

 

更新履歴

日付 内容
2015.9.22 新規投稿
2018.12.3 新シリーズ記事紹介追加