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

チャレンジ課題として、タイマーとしての課題を解決します。

目次

チャレンジ課題

これから3回に分けて、基礎編最後のチャレンジ課題に取り組んでいただこうと思います。

課題は今までの説明範囲内で解ける内容にしました。C言語も今まで出てきた文法の範囲内でできます。

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

どうしてもわからない場合、お問い合わせフォームかコメント欄からお問い合わせいただければと思います。

タイマーとしての課題

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

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

まず、時間になったらブザーを鳴らしましたが、「ピーッ」となり続けるので耳障りですし、アラーム音っぽくないです。

それと、時間がきてブザーが鳴ったら、電源を切らないと元に戻らない、というのもタイマーとしてどうかしてます。市販のタイマーを買ってきて説明書を見たら「スイッチを押すとタイマーがスタートします。時間がきたらアラーム音が鳴りますので、止める場合は電源をお切りください」なんていう説明が書いてあったら、とりあえず「アラーム音止める機能ぐらいつけろよ」ってツッコミますよね。

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

アラーム音を変える

最初はアラーム音を変えてみましょう。アラーム音っぽい音のパターンというのは個人で異なると思いますので、ご自分の好きなパターンで鳴らしてみましょう。

こちらでいろいろと時間調整してみたところ、

  1. 60ms鳴らす
  2. 60ms止める
  3. 60ms鳴らす
  4. 60ms止める
  5. 100ms鳴らす
  6. 600ms止める

とアラーム音を制御すると「ピピピーッ」って感じになりました。「ピピピッ」って感じにするには3回目のアラームを60ms鳴らすようにします。

ブザーを鳴らすのは「LATA4 = 1;」、止めるのは「LATA4 = 0;」でしたよね。あと指定した時間を待つのは「__delay_ms();」(単位はms)でした。

あとは、「ピピピーッ」をずっと繰り返せばいいので、while文で作れそうです。

それではアラーム音を変えて見ましょう。

スイッチを押してブザーを止めて元の状態に戻る

以前、保険のおばさんが保険を勧めてきたので掛け金や払戻金を計算してもらったところ、電卓で計算するのはいいのですが、数字を打ち間違えると電卓の電源をOFFにして、再度ONにして最初から計算していました。その時は「Cボタン(クリアボタン)があるのに、、、」と思ったのですが、よく考えると「Cボタン」と「ACボタン」って、その働きの違いとかわかりづらいので、電卓のユーザI/Fの方が悪いのかもしれません。電源ボタンはその働きは明快ですので、これを超えるようなわかりやすいボタンが必要なのかもしれません。

このようなケースもありますので、タイマーを切るには電源OFFでもいいのかもしれませんが、せっかくスイッチがありますので、スイッチで制御できるようにしましょう。

以下の仕様を追加して見てください。

「アラーム音が鳴ったら、スイッチを押すとアラーム音が停止して、最初の状態に戻る」

まず、前回作成したプログラムにこの機能を入れることを考えてみます。一番最後にブザーを鳴らすために「LATA4 = 1;」としていますので、この後にスイッチが押されるまで「while(RA3);」でそのまま待ちます。スイッチが押されたら最初に戻るように変更します。

次に、先ほどアラーム音を変えた場合ですが、こちらはちょっと工夫が必要です。

アラーム音を「ピピピッ」と鳴らしている間の処理は以下のようなものです。

  1. 60ms鳴らす
  2. 60ms止める
  3. 60ms鳴らす
  4. 60ms止める
  5. 100ms鳴らす
  6. 600ms止める

この間にスイッチが押されているかの判定を行うのですが、最後の600ms止めるところでは、おそらく「__delay_ms(600);」にしていると思います。この間にスイッチが押されて離された場合、検知ができませんので、この部分の改良が必要です。

このような実装をするものの、一つのボタンで複数の機能を持たせるのはユーザI/F観点ではわかりづらいかもしれないですね。。。

更新履歴

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

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

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

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

管理者
管理者
返信 
6 年 前

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

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

現在のプログラムの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文は使わない方がいいと思います。

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

輝
返信  管理者
6 年 前

ありがとうございます。

(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);
}

管理者
管理者
返信 
6 年 前

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

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

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

———
// アラーム音を鳴らす
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){
}

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

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

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

輝
返信  管理者
6 年 前

claynets様、

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

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

管理者
管理者
返信 
6 年 前

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

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

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

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

目次