« LEDイルミネーションの製作(第4回) マイコンの動作の概要 | メイン | HC08マイコンの使い方QY4A編 -《3》 英文・和文データシートを眺めよう »

LEDイルミネーションの製作(第5回) マイコンのプログラムの流れとポイント


 前回のマイコン動作を実際のプログラムで確認してみましょう。
ヘッダ・ファイル
#include <16F88.h>

// Clock Select
//#define  INT8M
//#define  EXT10M
#define  EXT20M

#ifdef   INT8M
   #fuses NOWDT,INT_RC, PUT, NOMCLR, BROWNOUT, NOLVP, CCPB0, NOCPD, NOWRT, NODEBUG, NOPROTECT, FCMEN, IESO
#else
   #fuses NOWDT,HS, PUT, NOMCLR, BROWNOUT, NOLVP, CCPB0, NOCPD, NOWRT, NODEBUG, NOPROTECT, FCMEN, IESO
#endif

#use fast_io(B)
#use fast_io(A)
 3行目以下の部分、ClockSelectは、使うクロック・ソースのコメントを外します。内蔵8MHz、外部10MHz、外部20MHzの三種類を想定しており、上記の例では外部20MHzを設定しています。この部分は、後で説明する定時起動タイマの時間計算に影響します。
 1行目と末行はデバイス設定に必須な記述です。

グローバル変数
unsigned int8	led[8];		// PWM作成用バッファ(明るさ設定)
unsigned int16	cond[8];		// 点灯時のカウント
unsigned int16	term[8];		// 消灯時間カウント
unsigned int8	scanc;		// PWMカウント(32カウント)
unsigned int8	timec;		// ウェイト作成用カウンタ
unsigned int32	nightcount;	// 夜間タイマ計算カウンタ
 変数led、cond、termは前回の説明にありましたが、オブジェ8個分確保するために8個の配列にしています。
 timecはメイン・プログラムで割り込み回数をカウントして指定数のウェイトを作成するのに使います。nightcountは一日のうち午後五時から十時までの間点灯させるための定時起動タイマ変数です。 

Timer0割り込み
#int_RTCC
RTCC_isr()		// 256us(204.8us:10/20MHz) timer
{
	if(timec!=0) timec--;		// ウェイト・カウンタ-1
	if(++scanc>=32) {		// PWMカウンタ+1
		scanc = 0;
	}
   // 各オブジェの点灯制御
	if (scanc>=led[0]) output_bit(PIN_A0,0); else output_bit(PIN_A0,1);
	if (scanc>=led[1]) output_bit(PIN_A1,0); else output_bit(PIN_A1,1);
	if (scanc>=led[2]) output_bit(PIN_B0,0); else output_bit(PIN_B0,1);
	if (scanc>=led[3]) output_bit(PIN_B1,0); else output_bit(PIN_B1,1);
	if (scanc>=led[4]) output_bit(PIN_B2,0); else output_bit(PIN_B2,1);
	if (scanc>=led[5]) output_bit(PIN_B3,0); else output_bit(PIN_B3,1);
	if (scanc>=led[6]) output_bit(PIN_B4,0); else output_bit(PIN_B4,1);
	if (scanc>=led[7]) output_bit(PIN_B5,0); else output_bit(PIN_B5,1);
} 
 RTCCとはPICのTimer0の特別な呼び方です。#int_TIMER0としても同じことです。この中では前半はtimec、scancのカウント、後半の8行は、scancと明るさ変数ledと比較してPWMの作成を行っています。
 
乱数計算ルーチン
// 8ビット・ランダム作成(ビルトイン関数rand()でも可)
unsigned int8	rand()
{
	static	unsigned int32	ival;

	ival1 = ival1 * 214013 + 2531011;
	ival1 = (ival1 >> 16);
	return((int8)(ival1 & 0xff));
}

// 2.62s(256)-5.24s(511)の間の乱数に変換
int16	calc_rand()
{
	int16	i;

	i=(int16)rand();
	i=i+256;
	return i;
}
 term用の乱数を作成します。まず、8ビットの乱数を作成してから16ビットに展開し、512~1024の範囲に拡張します。rand()関数は某コンパイラで使われていた簡易乱数作成サブルーチンです。CCS-Cにもビルトイン乱数発生rand()が存在するので、どちらを使ってもかまいません。

定時起動タイマ定数の宣言
#define	START_TIME	0
#ifdef   INT8M
   #define	STOP_TIME	1757812L	// 1/(256 * 40)us * 3600 * 5H
   #define	HOUR24	8437500L	// 1/(256 * 40)us * 3600 * 24H
#else
   #define	STOP_TIME	2197266L	// 1/(204.8 * 40)us * 3600 * 5H
   #define	HOUR24	10546875L	// 1/(204.8 * 40)us * 3600 * 24H
#endif
 五時から十時までの五時間分:STOP_TIME、24時間分:HOUR24が定義されています。単位は割り込み周期を40倍した約10mSですが、ヘッダ・ファイルで設定したとおりクロック・ソースの違いで内容が異なることになるので、それを吸収するためにクロック別に宣言します。

メイン・ループ
// メイン・ループ
void	mloop()
{
	int8	i;

	for(;;) {
  		timec = 40;	// 10.24ms
		if (nightcount<STOP_TIME) {  // 5時~10時?
			for(i=0;i<8;i++) {
				cond[i]++;
				if (cond[i]<128) {     // 点灯中
					led[i]=(int8)(32-(cond[i]>>2)); 	// 32段階で暗くなる
				} else {               // 消灯中
					led[i]=0;
					if (cond[i]>term[i]) {
						cond[i]=0;
						term[i]=calc_rand();	// 消灯終了直前に次の消灯時間を計算しておく
					}
				}
			}
		} else {                     // 10時から翌日5時は強制消灯
			for(i=0;i<8;i++) led[i]=0;
			if (nightcount > HOUR24) {
				nightcount = 0;
			}
		}
		while(timec!=0);             // ゼロになるまで待つ
		nightcount++;
	}
}
 メイン・ループ内はfor(;;)の無限ループです。1回のループは割り込み周期×40で、timec変数で作成します。
 ループの初めではnightcountを調べて、午後十時以降・翌日五時までならば全部のLEDを消灯させます。それ以外は、8個のオブジェを制御します。forループで0~7までを前回の説明に沿った方法で計算し、led[]に格納します。格納された値は、先の割り込みプログラムの中でPWM点灯処理します。

メイン・プログラム
void main()
{
	int	i;

	scanc = 0;
	for(i=0;i<8;i++) cond[i]=0;
	for(i=0;i<8;i++) term[i]=calc_rand();
	nightcount=0;
	output_a(0);
	output_b(0);
	setup_oscillator(OSC_8MHz); 	// 内蔵RC発振のみ必要
	setup_adc_ports(NO_ANALOGS|VSS_VDD);
	setup_adc(ADC_OFF);
	setup_spi(FALSE);
	set_tris_a(0x00);			// RA all out
	set_tris_b(0x00);			// RB2:in other out
#ifndef   EXT20M
	setup_timer_0(RTCC_INTERNAL|RTCC_DIV_2); // 8/10MHz
#else
	setup_timer_0(RTCC_INTERNAL|RTCC_DIV_4); // 20MHz only
#endif
	setup_timer_1(T1_DISABLED);
	setup_comparator(NC_NC_NC_NC);
	setup_vref(FALSE);
	enable_interrupts(INT_RTCC);
	enable_interrupts(GLOBAL);
	id = read_eeprom(0);
	mloop();
}
 変数と内蔵モジュールの初期設定を行っています。ここでもクロック・ソースによって割り込み周期の設定が変わっています。
 すでに、おわりのこととは思いますが、定時起動タイマとは時間を計数しますが、時計ではありませんので、最初に電源を投入する時間で決まってしまいます。つまり五時~十時にしたければ、午後五時に電源を投入してください。時間精度は内蔵RCで数分/日、セラロックで数十秒/日、水晶発振子では数分/月です。
 したがって、内蔵RCでは1週間に1回程度、決まった時間に電源を入れなおす必要が出てきます。また、ずれる割合がある程度解った時は、プログラムソースのSTOP_TIMEやHOUR24を調整してしまうという手もあります。
 次回は応用編です。
 <高野慶一>

(編集部注) リストの右側が切れて表示されていますが、データは入っています。全選択で本文をコピーしていただき、メモ帳などに張り込んでいただければ、リストを取り出すことができます。
(2008/08/18 メインループプログラムの一部が欠けていたので修正。)

カテゴリ:

トラックバック

このエントリーのトラックバックURL:
http://www.eleki-jack.com/mt/mt-tb.cgi/1745

コメントを投稿

(いままで、ここでコメントしたことがないときは、コメントを表示する前にこのブログのオーナーの承認が必要になることがあります。承認されるまではコメントは表示されません。そのときはしばらく待ってください。)

カテゴリ

会社案内
情報セキュリティおよび個人情報の取り扱いについて

コメントとトラックバックは、spamを予防するために、編集担当が公開の作業をするまで非公開になっています。
コメントはそれぞれ投稿した人のものです。

Powered by
Movable Type 4.1