7セグLED時計のプログラムの説明です
とりあえず動かすことを念頭に置いたプログラムなので、かなり雑だと思いますが・・・
プログラムのポイントとなる部分だけを解説します。
__CONFIG(UNPROTECT & LVPDIS & BORDIS & MCLREN & PWRTEN & WDTDIS & HS);
__IDLOC(F648);static unsigned int watch, stp_watch; // ウオッチ WORK
static long t_save, w_save; // 時計 WORK
static unsigned char dg_f, dgt[4], dot[4], // 桁表示 WORK
hh, mm, ss, // 時・分・秒
dsp_mode, // 表示モード
slp_on, wat_on; // スリープ、ウオッチ制御
static const char dig[]; // 数字・文字パターン(後方参照の定義)
冒頭の部分はコンフィグビットの設定と、グローバル変数の定義ですが、
static const char dig[];
の部分は、サイズ指定しない配列にC言語を見慣れない人には「?」と思われるかも知れませんが、実体をここでは定義せず、プログラムの後の方で定義する後方参照のための宣言です。
少しだけプログラムを見やすくするためで、配列の中身は、プログラムの最後で定義しています。
static const char dig[] ={ // 数字・文字定義(0がオン)
0b11000000, //0
0b11111001, //1
0b10100100, //2
0b10110000, //3
・
(中略)
・
0b11111111, //All Off
0b10101011, //n
0b11000111, //L
0b10001100 //P
};
今回使った7セグLEDはアノードコモンと呼ばれるタイプで、回路の設計上「1」が消灯、「0」が点灯としています。
数字の0-9以外はインデックスを定数宣言しています。
#define DSP_DOT 16 // LEDのドット
#define DSP_OFF 17 // LED OFF
#define DSP_n 18 // LEDのn文字
#define DSP_L 19 // LEDのL文字
#define DSP_P 20 // LEDのP文字
ついでにポートの定義です。
#define SWT_MODE RA4 // モードスイッチのポート
#define SWT_1 RB2 // スイッチ1のポート
#define SWT_2 RB3 // スイッチ2のポート
#define TRIS_1 TRISB2 // スイッチ1の入力
#define TRIS_2 TRISB3 // スイッチ2の入力
では、プログラムのロジック部分です。
disp()関数については、特に難しい部分はないと思います。
PORTAにつながった1-4桁のアノードを順次オンにし、PORTBに表示したいセグメントを8ビットでオン・オフしています。
時計のカウントは割り込処理で実行しています。
static interrupt int_server(void)
関数です。今回のプログラムではタイマ1(TMR1)をメインの時刻用にタイマ0(TMR0)はストップウオッチ等汎用のタイマーとして使用しています。
TMR1の処理を解説します。
if (TMR1IF) { // TMR1割込み
TMR1IF = 0; // フラグクリア
t_save += 0x10000; // TMR1の1週アップ分
// if (t_save > 250000) { // 4MHZの場合の1秒
if (t_save > 736000) { // 11.776MHzの場合の1秒
ss++; // 1秒追加して
t_save -= 736000; // 1秒分カウンタ差し引き
if (ss >= 60){ // 60秒=1分
mm++;
ss=0;
}
if (mm >= 60) { // 60分=1時間
hh++;
mm=0;
}
if (hh >= 24) // 24時=0時
hh = 0;
}
}
PICで正確な1秒を得るには少々工夫が必要です。私も試行錯誤の末、下記の方法を採用しました。
では、今回の回路では11.776MHzのクリスタルを使用しているので、それを前提に説明すると。
TMR1がオーバーフローして割り込みが発生するということは、TMR1が65,536をカウントした時です。16進数では0x10000と表せます。
では、1秒間にTMR1は幾つカウントが必要でしょうか?以下の式で求められます。
11,776,000÷4÷4=736,000
です。最初の「11,776,000」は水晶の周波数で、1秒間の発振数です。タイマは4クロックで1カウントですので、4で割ります。
最後にプリスケーラーを「1:4」で設定しているので、4で割ります。
1回の割り込み毎に65,536(0x10000)を足し、合計が736,000を超えたところで1秒をカウントします。この時、t_saveの値は736,000を超えているので、736,000を引き、余りを次の割り込みへのキャリーオーバーとします。
ここで重要なことは、TMR1がオーバーフローして割り込みが発生した時に、タイマ関連のレジスタを触らないことです。
最初、TMR1H、TMR1Lレジスタを弄くって0.1秒等キリのいい数字を得ようとしましたが、同じ周期で割り込みが発生せず相当悩みました。
正確に同周期で割り込みを発生させるためには、TMR1H、TMR1LやTMR0のレジスタだけでなく、TMR1IEなどのビットも変更しないよう、タイマーを回し続ける必要があります。
これらのレジスタを変更することで余分な処理が走り、割り込み周期が変わってしまいます。
このことはMPLABのシミュレータで確認できます。
※以上は「ある程度正確な」1秒です。実際にはC言語による冗長な処理が入るため精密な1秒かどうかは判定しかねます。
より、精密に処理するには、アセンブラで記述する必要があると思います。
次に、main()関数のボタン入力判定です。
while(1){ // 繰り返しループ
if (!SWT_MODE) { // モードスイッチが押された
PORTB = 0xFF; // 表示クリア
__delay_ms(50); // 100ms待って(チャタリング防止)
__delay_ms(50);
if (!SWT_MODE) { // まだ押されてる
if (slp_on == 2) { // スリープ中の場合、起きる
slp_on = 1;
・
・
・
SWT_MODEはdefineで定義したRA4ポートにつながっている押しボタンスイッチです。
スイッチはプルアップ抵抗を介して接地しているので、押されているあいだ0になるので「!SWT_MODE」としています。
チャタリング防止のため100ms待ち、まだボタンが押されているならばボタンに関する処理を行います。
モードボタンは単独でポートを占有しているので良いのですが、今回の回路ではポートに余裕がないので、他の二つのスイッチはLED出力と兼用しています。
if (dsp_mode > MODE_CLOCK) { // 時刻モード以外
TRIS_1 = 1; // スイッチ1のポートを入力にする
__delay_us(10); // 10us待って
if(!SWT_1) { // スイッチ1が押されてたら
PORTB = 0xFF;
__delay_ms(50);
__delay_ms(50);
if(!SWT_1) {
if (slp_on == 2) {
・
・
・
2行目の所でPORTAの2(TRIS_1)を入力に変え、スイッチの入力をチェックしています。
スイッチ1はRB2に接続しているので、押すとLEDの一部が意味なく光りますが・・・
ちなみに、今回のデジタル時計では6つのモードを設けています。
#define MODE_CLOCK 0 // 時計モード
#define MODE_SECOND 1 // 秒表示モード
#define MODE_WATCH 2 // ストップウオッチモード
#define MODE_ADJHH 3 // 時・設定
#define MODE_ADJMM 4 // 分・設定
#define MODE_SLEEP 5 // スリープ設定
このそれぞれのモード時で、スイッチ1とスイッチ2の役割を変えています。
スイッチ1は主に数字の操作
switch (dsp_mode) {
case MODE_SECOND: // 秒モードの時
t_save = 0; // 0リセット
ss = 0;
break;
case MODE_ADJHH: // 時設定の時、
hh++; // 時をカウントアップ
break;
case MODE_ADJMM: // 分設定の時、
mm++; // 分をカウントアップ
break;
case MODE_WATCH: // ストップウオッチの時
stp_watch = 0; // 0リセット
break;
}
スイッチ2はン・オフ切り替え
switch (dsp_mode) {
case MODE_WATCH: // ストップウオッチの時
wat_on = !wat_on; // スタート・ストップ切り替え
break;
case MODE_SLEEP: // スリープモードの時
slp_disp++; // スリープ表示の切り替え
if (slp_disp == 2) slp_on = 1;
else slp_on = 0;
if (slp_disp > 2) slp_disp = 1;
break;
}
mainの最後では、各モードにより表示する数字の桁の操作をしています。
switch (dsp_mode) { // 表示桁の抑制
case MODE_SECOND: // 秒表示:下2桁のみ
if (dg_f > 1)
dg_f = 0;
case MODE_ADJHH: // 時設定
if (blink && dg_f > 1) // ブリンクオンで上2桁を抑制
dg_f = 0; // 結果0か1
・
・
・
解説は以上です。