第28回 チャレンジ課題(3)

チャレンジ課題で、タイマーの機能改善をしてみます!

目次

チャレンジ課題

これから3回に分けて、チャレンジ課題に取り組んでみたいと思います。

自分でブログラムを作ってみると、動いたけどこれで正解なのか不安になるかもしれません。

とりあえず動作すれば正解ですので、ぜひチャレンジしてみてください!

記事の最後に解答例がありますので参考にしてみてください。

現在のタイマーの課題

前回、やっとタイマーとして基本的な回路とプログラムが完成しました。

ただ、いろいろ不満はありますよね。

大きなところでは、設定時間になったらブザーを鳴らしましたが、3秒間「ピーッ」という音ですのでアラーム音という感じではありません

また、設定時間になってブザーが鳴ったあと、電源を切らないと元に戻らない、というのもタイマーとしてどうかしてます。

市販のタイマーを買ってきて説明書を見たら「スイッチを押すとタイマーがスタートします。時間がきたらアラーム音が鳴ります。再度タイマーを利用する倍は一度電源をお切りください」なんていう説明が書いてあったら、なんで?って感じになりますよね。

ということで、今回はチャレンジ課題としてこれらの課題を解決していただこうと思います。

課題

課題❶ アラーム音を変える

最初はアラーム音を変えてみます。

アラーム音っぽい音のパターンというのは個人でイメージが異なると思いますので、ご自分の好きなパターンで鳴らしてみてください。

ご参考に、私の方でいくつかのパターンを試したところ、次のパターンでブザーを鳴らすと「ピピピッ」という感じでアラーム音っぽくなりました。

  1. 70ms鳴らす
  2. 70ms止める
  3. 70ms鳴らす
  4. 70ms止める
  5. 70ms鳴らす
  6. 800ms止める

タイマー時間になったら、このアラーム音のパターンを3回繰り返すようにしたいと思います。

ブザーを鳴らすのはLATA4 = 1、止めるのはLATA4 = 0でしたよね。

また、一定時間待つ必要がありますが、指定した時間を待つのは__delay_ms()です。

さらに、繰り返しのfor文を使えばなんとかプログラム作成できそうです。

課題❷ アラーム音を鳴らしたあと元の状態に戻る

現在のプログラムはアラーム音を鳴らしたあと、while(1) { }で動作を停止しています。

タイマーは繰り返し使用することがありますので、アラーム音を鳴らしたあとは最初の状態に戻る、つまり「スタートボタンを押すとタイマーを開始する状態に戻る」という仕様に変更してみたいと思います。

解答例

解答例を作成してみましたが、これが唯一の正解ではありません。ご自身でいろいろと工夫したり、独自機能を追加してみてください。

課題❶ アラーム音を変える

アラーム音のパターンになるようにRA4ピンのデジタル制御をするようにコードを書いてみました。

/*
 * PICマイコン電子工作入門 基礎編 第28回 チャレンジ課題
 *  課題1 解答例
 */

#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 = 0b01011000;  // 内部クロック周波数を1MHzに設定
    ANSELA = 0b00000000;  // すべてのピンをデジタルモードに設定
    TRISA  = 0b00001000;  // すべてのピンを出力モードに設定(ただしRA3ピンは常に入力モード)

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

    // スイッチが押されるまで待つ
    while( RA3 ) {
    }
    
    // LEDを一定回数点滅する
    for(uint16_t timer=0; timer<3; timer++) {
        // LEDを950ms消灯する
        LATA5 = 0;
        __delay_ms(950);            
        // LEDを50ms点灯する
        LATA5 = 1;
        __delay_ms(50);
    }

    // アラーム音を3回鳴らす
    for(uint8_t i=0; i<3; i++) {
        // ピッ
        LATA4 = 1;
        __delay_ms(70);
        LATA4 = 0;
        __delay_ms(70);
        // ピッ
        LATA4 = 1;
        __delay_ms(70);
        LATA4 = 0;
        __delay_ms(70);
        // ピーッ
        LATA4 = 1;
        __delay_ms(70);
        LATA4 = 0;
        // 1回分の音パターンの間を少し空ける
        __delay_ms(800);
    }

    // 動作を止める
    while(1) {
    }
    
    // 以下の命令は実行されない
    return;
}

課題❷ アラーム音を鳴らしたあと元の状態に戻る

「スイッチが押されるのを待つ 〜 時間計測 〜 アラーム音を鳴らす」までをずっと繰り返せば良いので、これらの処理を丸ごとwhile(1)で囲んでみました。

/*
 * PICマイコン電子工作入門 基礎編 第28回 チャレンジ課題
 *  課題2 解答例
 */

#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 = 0b01011000;  // 内部クロック周波数を1MHzに設定
    ANSELA = 0b00000000;  // すべてのピンをデジタルモードに設定
    TRISA  = 0b00001000;  // すべてのピンを出力モードに設定(ただしRA3ピンは常に入力モード)

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

    // 「スイッチが押されるのを待つ 〜 時間計測 〜 アラーム音を鳴らす」までをずっと繰り返す
    while(1){
        // スイッチが押されるまで待つ
        while( RA3 ) {
        }

        // LEDを一定回数点滅する
        for(uint16_t timer=0; timer<3; timer++) {
            // LEDを950ms消灯する
            LATA5 = 0;
            __delay_ms(950);            
            // LEDを50ms点灯する
            LATA5 = 1;
            __delay_ms(50);
        }

        // アラーム音を3回鳴らす
        for(uint8_t i=0; i<3; i++) {
            // ピッ
            LATA4 = 1;
            __delay_ms(70);
            LATA4 = 0;
            __delay_ms(70);
            // ピッ
            LATA4 = 1;
            __delay_ms(70);
            LATA4 = 0;
            __delay_ms(70);
            // ピーッ
            LATA4 = 1;
            __delay_ms(70);
            LATA4 = 0;
            // 1回分の音パターンの間を少し空ける
            __delay_ms(800);
        }
    }
    
    // 以下の命令は実行されない
    return;
}

更新履歴

日付内容
2016.12.5新規投稿
2025.4.18課題内容変更。解答例追加
通知の設定
通知タイミング
guest
6 コメント
新しい準
古い順 一番投票が多い
本文中にフィードバック
全てのコメントを見る
輝
7 年 前

再度、ご教示お願いします。

「スイッチが押されたら最初に戻る」が戻りません。
スイッチを押すと、LEDの点滅が始まります。
return で終了したり、while でLED点灯からアラームのループ抜け出しまで全て括っても同じでした。
「RA3」に 「1」を代入したら止まるかと思ったのですがダメでした。

私の老いた頭脳では、これが限界かもしれません。
以前からアルゴリズムは苦手でした。
厚かましいお願いですが、もう少しヒントを頂けないでしょうか。

管理者
管理者
返信 
7 年 前

輝さま、
ご質問どうもありがとうございます。

「元に戻る」ということが具体的にどのような動作なのか検討するとプログラムが作成できると思います。

現在のプログラムのmain()は以下のようになっています。

main() {
 (1) PICマイコンの初期設定など
 (2) LEDやブザーを初期状態にする
 (3) スタートボタンが押されるまで待つ – while(RA3)文
 (4) 指定秒数LEDを点滅させながらカウントする – for文
 (5) LEDを点灯させてブザーを鳴らす
 (6) 何もしない – while()文
}

このままだと(6)で while(){}となり、「何もしない」をずっと実行することになります。(「何もしない」ということ「する」というのは哲学的な感じがしますが…)

なお、チャレンジ課題で(6)をアラーム音を鳴らすように変更しているかもしれませんが、わかりやすくするために、上の基本的なプログラムを変更することにします。

タイマーの設定時間がきて、(5)でブザーが鳴り、LEDを光らせたら(6)で何もしないで待っているわけですが、この待っている間にスイッチが押されたことを確認すればうまくいきそうです。

そこで、(6)を(3)のようにすると、、、

main() {
 (1) PICマイコンの初期設定など
 (2) LEDやブザーを初期状態にする
 (3) スタートボタンが押されるまで待つ – while(RA3)文
 (4) 指定秒数LEDを点滅させながらカウントする – for文
 (5) LEDを点灯させてブザーを鳴らす
 (6) スタートボタンが押されるまで待つ
}

となります。あとは(6)でスタートボタンが押されたら、(2)に戻ればいいので、

main() {
 (1) PICマイコンの初期設定など
 while() {
  (2) LEDやブザーを初期状態にする
  (3) スタートボタンが押されるまで待つ – while(RA3)文
  (4) 指定秒数LEDを点滅させながらカウントする – for文
  (5) LEDを点灯させてブザーを鳴らす
  (6) スタートボタンが押されるまで待つ – while(RA3)文
 }
}

とすれば(2)から(6)を永遠に繰り返すことになります。これでブザーが鳴ってLEDが点灯したら、スタートボタンを押すと電源投入時の状態に戻り、またスタートボタンを押すとタイマーカウントするようになるはずです。

今回はwhile文を使用しましたが、goto文でもプログラムできます。ただ、goto文を使うことは好まれないのでなるべくgoto文は使わない方がいいと思います。

実際のプログラムがわからないようでしたらまたご連絡いただければと思います。

輝
返信  管理者
7 年 前

ありがとうございます。

(5) LEDを点灯させてブザーを鳴らす
でのコードにすると、意図通りに走るのですが
アラームにするとダメでした。
どうも私のアラームを鳴らすコードとスイッチの検知が
間違っているようです。
拙いコードなので見られるのが恥ずかしいのですが
よろしくお願いします。

多重入れ子を抜けるのに goto を使っています(^^ゞ

// ユーザ定義関数の宣言
void b_on();
void b_off();

int main(int argc, char** argv) {

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

// 時間計測の変数
unsigned short timer;

//アラームの変数
unsigned char i;

// LEDを点灯する
LATA5 = 1;

// ブザーをOFFにする
LATA4 = 0;

// スイッチが押されるまで待つ
while(RA3){
}

while(1){

// LED点滅処理(90秒間繰り返す)
for(timer=0; timer<10; timer++){
// LEDを950ms消灯する
LATA5 = 0;
__delay_ms(950);
// LEDを50ms点灯する
LATA5 = 1;
__delay_ms(50);
}

// LEDを点灯する
LATA5 = 1;

// アラーム音を鳴らす
while(1){

b_on();
if(!RA3) break;

b_off();
if(!RA3) break;

b_on();
if(!RA3) break;

b_off();
if(!RA3) break;

b_on();
if(!RA3) break;

b_on();
if(!RA3) break;

for(i=0; i<10; i++){
b_off();
if(!RA3) goto OUT;
}
}

OUT:

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

// スイッチが押されるまで待つ
while(RA3){
}

}

// 以下の命令は実行されない
return (EXIT_SUCCESS);
}

//ブザーを60ms鳴らす
void b_on(){
LATA4 = 1;
__delay_ms(60);
}

//ブザーを60ms止める
void b_off(){
LATA4 = 0;
__delay_ms(60);
}

管理者
管理者
返信 
7 年 前

輝さま、
プログラムどうもありがとうございます。

すみません、時間がなく実機で確認できていないのですが、動作しない原因と思われるところがありますので取り急ぎ返信いたします。

アラームを鳴らしたところのプログラムの要点をまとめると、

———
// アラーム音を鳴らす
while(1){
 「RA3をチェックして0だったらwhile文を抜ける」
}

// LEDを点灯する
LATA5 = 1;
// ブザーをOFFにする
LATA4 = 0;
// スイッチが押されるまで待つ
while(RA3){
}

「最初に戻ってタイマーカウント開始)」
———

になると思います。ここで実際の動作を考えてみます。

アラームが鳴っている時にスイッチを押しますが、スイッチを押す時間(押してから離すまでの時間)は0.2〜0.3秒ぐらいだと思います。つまり、スイッチを押すと、0.2〜0.3秒程度RA3が0の状態が続くことになります。

プログラムでは、アラームが鳴っている時に if(!RA3) でスイッチが押されたことを検知して、その後、元の状態に戻す(LEDをON/ブザーをOFF)、すぐにスイッチ状態の確認をします。ここで処理時間について調べて見ると、if(!RA3)の判定→LEDをON→ブザーをOFF→スイッチ状態確認までの処理時間は数十マイクロ秒(=0.00002〜0.00003秒)です。

つまり、人間が(のんびり?)スイッチを押している間に、アラームのループを抜け、LEDをONにして、ブザーをOFFにして、すぐにスイッチの状態確認をしますので、このスイッチ状態の確認時にはまだスイッチは押したままの状態となり、タイマーカウントを始めてしまいます。

どうしたら良いか、色々とアイデアはあると思いますが、一般的な対応は以下のようになると思います。

まずスイッチは押した瞬間と離した瞬間でチャタリングが発生することを考慮する必要があります。これを踏まえて動作を考えてみます。

———
// アラーム音を鳴らす
while(1){
 「RA3をチェックして0だったらwhile文を抜ける。
  つまり、スイッチが押された瞬間、while文を抜ける」
}

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

// 押した瞬間のチャタリングが継続しているはずなのでちょっと待つ
__delay_ms(20);

// おそらくまだ押している状態なので、スイッチが離されるまで待つ
while(!RA3){
}

// スイッチを離した瞬間のチャタリングが継続しているはずなのでちょっと待つ
__delay_ms(20);

// スイッチが押されるまで待つ
while(RA3){
}

「最初に戻ってタイマーカウント開始)」
———

すみません、動作確認していないので考え方に不備があるかもしれません(=動作しないかもしれません…)

まずは考え方ということで取り急ぎご回答いたします。

輝
返信  管理者
7 年 前

claynets様、

ご教示通り、コピペしたコードで意図通りに走りました。
恥ずかしい話、「チャタリング」の単語は初耳で
ネット検索をし、キーボードの連打との記述になるほどと理解した次第です。

もっと、スマートなアルゴリズムを目指すべきなのかも知れませんが、このまま前に進みます。
素早いご回答、ありがとうございました。

管理者
管理者
返信 
7 年 前

輝さま、
動作した、とのことでよかったです。ただ、すみません、チャタリングについては今回説明していませんでした。

実は、この入門シリーズの元となるところではチャタリングの説明をしていました。記事はこちらになります。

http://tool-lab.com/make/macpic-startup-27/

この記事では、今のタイマー回路に接続しているLEDを「スイッチを押すたびにON→OFF→ON…を繰り返す」プログラムを作成します。一見簡単に見えますが意外に難しいので、アルゴリズムを考える練習になると思います。もしご興味があれば参考にしていただければと思います。

目次