記事一覧

7セグLED時計の製作③

2009.06.12

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



解説は以上です。

タグ:PIC 16F648A 7セグLED

7セグLED時計の製作②

2009.06.12

今日はとりあえずPICCのソースリストを公開

リストはこちら

次回は内容を説明します

タグ:PIC 16F648A 7セグLED

7セグLED時計の製作①

2009.06.11


秋月電子にて、「7セグLED 赤色2文字(アノードコモン)(4個入)」なるものを買いました。
2桁x4個で100円とお買い得。
早速、PIC16F648Aと組み合わせて、4桁表示のデジタル時計の作成に挑戦しました。
4桁分をブレッドボードに挿してテストするのは困難なので、LED、抵抗、トランジスタなどをひとまずユニバーサル基盤に半田付けし、リード線を引き出してブレッドボードに挿し込んでいます。

水晶発信器11.776MHZあと、特価の水晶発振器(11.776MHz)が5個で100円だったので、こちらも実装しました。
デジタル時計として実用の精度を期待しています。
試したところ、2日で1秒程度の誤差でした。
現在はニッケル水素電池4本で動作させていますが、電池の持ちは一週間程度といったところでしょうか。
いずれACアダプタ等に変更したいと思います。

ファイル 1-2.png 回路図はこちら。
LEDはアノードコモンタイプ。
カソードはRB0-7に接続し、アノードにつないだRA0-3でダイナミック点灯をしています。
時計のモード切替はRA4にプルアップ抵抗とプッシュスイッチでOKですが、時刻合わせとストップウオッチのスタートなど、あと2つスイッチが欲しかったのでRB2,3につなぎ、プログラム上で出入力を一瞬切り替えてスイッチを検出するようにしています。
ここはカソードと兼用のラインなので、スイッチを押している間は表示が変になりますが、一応実用の範囲内。
左上のポートはPICKit2につなぐコネクタです。5Vの外部電源を印加することにより回路上でPICのプログラムの書き換えを可能としています。

最初、外部電源の事を忘れていてPickit2の電源だけでプログラミングしていたため、書き換えが不安定で悩みました。
単三x4本の電源を付け加えることで書き換えが安定するようになりました。

時計の機能
・モード切替
1)通常の時分表示(時と分の間はピリオド表示)
2)秒だけの表示(下2桁分のLED使用)
3)ストップウオッチ(0.1秒単位で999.9秒まで表示可)
4)時、分の設定
5)スリープモードの設定

タグ:PIC 16F648A 7セグLED

ページ移動