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

プログラムにブザーを制御する部分を追加して、基礎編の回路とプログラムを完成させます!

今回の説明

回路を完成させるために以下の順序で説明しています。このエントリの説明は(6)「ベース回路にブザーを追加する」のプログラム作成の部分になります。

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

 

動作処理の方針

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

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

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

  1. 初期状態の設定として、2番ピン(RA5ピン)をONにして、LEDを点灯させる(電源が入ってPICマイコンが動いたことを示すため)。また3番ピン(RA4ピン)をOFFにしてブザーをOFFにする
  2. スイッチピンの4番ピン(RA3ピン)を監視し続けて、スイッチがONになるまで待つ
  3. スイッチが押されたら、LEDを1秒に1回、点滅させる。この点滅は指定秒数繰り返す
  4. 指定秒数経過したら、3番ピン(RA4ピン)をONにしてブザーを鳴らす。また2番ピン(RA5ピン)をONにして発光ダイオードを点灯させる。
  5. (4)の状態を続ける

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

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

LATA4 = 0;

でよさそうです。

(2)は変更がありませんのでそのままです。

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

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

LATA4 = 1;

でOKですよね。またLEDを点灯させるのは

LATA5 = 1;

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

while(1){
}

とすることにします。

 

プログラム作成

動作処理部分は上のように変更すればよさそうですが、他の部分についても検討します。何度もしつこくてすみませんが、プログラムの構成を再度確認しましょう。

Program structure

今回もコメント部分は変更履歴をメモしておくことにします。

次のヘッダファイルインクルード部分ですが、ブザーの制御はピンの制御だけで済みますので、今までどおりのヘッダファイルで問題ありません。PICマイコンコンフィグレーション設定部分とその他設定部分も、特に変更はありません。

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

main関数基本構造

まず内部クロック設定は特に変更ありません。

ピンの設定ですが、現在はすべてのピンがデジタル設定ですのでANSELAについては変更なしです。

入出力設定(TRISA)については、RA3ピンが入力の設定で、他のピンは出力の設定です。今回は新規にRA4ピンを出力ピンとしてブザーを制御しますが、すでに出力ピンになっていますので、この部分も変更なしです。

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

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

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

/*
 * File:   main.c
 * 変更履歴
 *    2016.11.20: スイッチ制御部分を追加
 *    2016.12.05: 時間計測とブザー制御部分を追加
 */

#include <xc.h>

// PIC12F1822 Configuration Bit Settings
// CONFIG1
#pragma config FOSC = INTOSC    // Oscillator Selection (INTOSC oscillator: I/O function on CLKIN pin)
#pragma config WDTE = OFF       // Watchdog Timer Enable (WDT disabled)
#pragma config PWRTE = OFF      // Power-up Timer Enable (PWRT disabled)
#pragma config MCLRE = OFF      // MCLR Pin Function Select (MCLR/VPP pin function is digital input)
#pragma config CP = OFF         // Flash Program Memory Code Protection (Program memory code protection is disabled)
#pragma config CPD = OFF        // Data Memory Code Protection (Data memory code protection is disabled)
#pragma config BOREN = ON       // Brown-out Reset Enable (Brown-out Reset enabled)
#pragma config CLKOUTEN = OFF   // Clock Out Enable (CLKOUT function is disabled. I/O or oscillator function on the CLKOUT pin)
#pragma config IESO = OFF       // Internal/External Switchover (Internal/External Switchover mode is disabled)
#pragma config FCMEN = OFF      // Fail-Safe Clock Monitor Enable (Fail-Safe Clock Monitor is disabled)
// CONFIG2
#pragma config WRT = OFF        // Flash Memory Self-Write Protection (Write protection off)
#pragma config PLLEN = OFF      // PLL Enable (4x PLL disabled)
#pragma config STVREN = OFF     // Stack Overflow/Underflow Reset Enable (Stack Overflow or Underflow will not cause a Reset)
#pragma config BORV = LO        // Brown-out Reset Voltage Selection (Brown-out Reset Voltage (Vbor), low trip point selected.)
#pragma config LVP = OFF        // Low-Voltage Programming Enable (High-voltage on MCLR/VPP must be used for programming)


// クロック周波数指定
// __delay_ms()関数が使用する
#define _XTAL_FREQ 1000000

void main(void) {

    // PICマイコン設定
    OSCCON = 0b01011010;  // 内部クロック周波数を1MHzに設定
    ANSELA = 0b00000000;  // すべてのピンをデジタルモードに設定
    TRISA  = 0b00001000;  // すべてのピンを出力モードに設定(ただしRA3ピンは常に入力モード)
    
    // 時間計測の変数
    unsigned char timer;

    // LEDを点灯する
    LATA5 = 1;
    
    // ブザーをOFFにする
    LATA4 = 0;

    // スイッチが押されるまで待つ
    while(RA3){
    }
        
    // LED点滅処理(10秒間繰り返す)
    for(timer=0; timer<10; timer++){
        // LEDを950ms消灯する
        LATA5 = 0;
        __delay_ms(950);
        // LEDを50ms点灯する
        LATA5 = 1;
        __delay_ms(50);            
    }
    
    // LEDを点灯する
    LATA5 = 1;
    
    // ブザーをONにする
    LATA4 = 1;
    
    // 何もしないで待つ
    while(1){
    }

    // 以下の命令は実行されない
    return;

}

 

動作確認

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

ただ、残念ながら時間が経つとブザーは鳴りっぱなしですので、ブザーを止めるには電池ボックスのスイッチを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
 * 変更履歴
 *    2016.11.20: スイッチ制御部分を追加
 *    2016.12.05: 時間計測とブザー制御部分を追加
 */

#include <xc.h>

// PIC12F1822 Configuration Bit Settings
// CONFIG1
#pragma config FOSC = INTOSC    // Oscillator Selection (INTOSC oscillator: I/O function on CLKIN pin)
#pragma config WDTE = OFF       // Watchdog Timer Enable (WDT disabled)
#pragma config PWRTE = OFF      // Power-up Timer Enable (PWRT disabled)
#pragma config MCLRE = OFF      // MCLR Pin Function Select (MCLR/VPP pin function is digital input)
#pragma config CP = OFF         // Flash Program Memory Code Protection (Program memory code protection is disabled)
#pragma config CPD = OFF        // Data Memory Code Protection (Data memory code protection is disabled)
#pragma config BOREN = ON       // Brown-out Reset Enable (Brown-out Reset enabled)
#pragma config CLKOUTEN = OFF   // Clock Out Enable (CLKOUT function is disabled. I/O or oscillator function on the CLKOUT pin)
#pragma config IESO = OFF       // Internal/External Switchover (Internal/External Switchover mode is disabled)
#pragma config FCMEN = OFF      // Fail-Safe Clock Monitor Enable (Fail-Safe Clock Monitor is disabled)
// CONFIG2
#pragma config WRT = OFF        // Flash Memory Self-Write Protection (Write protection off)
#pragma config PLLEN = OFF      // PLL Enable (4x PLL disabled)
#pragma config STVREN = OFF     // Stack Overflow/Underflow Reset Enable (Stack Overflow or Underflow will not cause a Reset)
#pragma config BORV = LO        // Brown-out Reset Voltage Selection (Brown-out Reset Voltage (Vbor), low trip point selected.)
#pragma config LVP = OFF        // Low-Voltage Programming Enable (High-voltage on MCLR/VPP must be used for programming)


// クロック周波数指定
// __delay_ms()関数が使用する
#define _XTAL_FREQ 1000000

void main(void) {

    // PICマイコン設定
    OSCCON = 0b01011010;  // 内部クロック周波数を1MHzに設定
    ANSELA = 0b00000000;  // すべてのピンをデジタルモードに設定
    TRISA  = 0b00001000;  // すべてのピンを出力モードに設定(ただしRA3ピンは常に入力モード)
    
    // 時間計測の変数
    unsigned short timer;

    // LEDを点灯する
    LATA5 = 1;
    
    // ブザーをOFFにする
    LATA4 = 0;

    // スイッチが押されるまで待つ
    while(RA3){
    }
        
    // LED点滅処理(5分=300秒間繰り返す)
    for(timer=0; timer<300; timer++){
        // LEDを950ms消灯する
        LATA5 = 0;
        __delay_ms(950);
        // LEDを50ms点灯する
        LATA5 = 1;
        __delay_ms(50);            
    }
    
    // LEDを点灯する
    LATA5 = 1;
    
    // ブザーをONにする
    LATA4 = 1;
    
    // 何もしないで待つ
    while(1){
    }

    // 以下の命令は実行されない
    return;

}

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

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

なお、余裕を見て変数を用意するのはよくありません。long型を使えばかなりの値を扱うことができますが、必要バイト数が多い分、メモリを余計に消費しますし、処理時間もかかることになります。

WindowsやMacのアプリでしたら贅沢にメモリやCPUパワーを使ってもよいかもしれませんが、マイコンの場合はメモリやCPUパワーはかなり貧弱ですので、プログラムを作る際はこのような観点でも注意するようにしてください。

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

次回から、基礎編の締めくくりとしてチャレンジ課題に挑戦しましょう!

 

更新履歴

日付 内容
2016.12.5 新規投稿
2018.11.24 プログラムテンプレートをMPLABX IDE v5.10版に変更