| ホーム | 私の電子工作作品集 |
[ 初公開日:2023年12月1日 ] |
本機は携帯使用を想定した多機能のデジタル時計で、現在時刻(年、月、日、曜日、AM/PM、時、分、秒)を表示する時計機能、2種類(詳細測定、長時間測定)のストップウオッチ機能、2種類(インターバル、ウィークリー)のタイマー機能の他、
メンテナンス用途としての現在時刻の設定機能などを有しています。 長時間の携帯使用をするために、電源には 18650 リチウムイオン電池を採用しました。 そして、本機ではリアルタイムクロック(RTC)モジュールを搭載(バックアップ用のバッテリーも搭載)してあるため、リチウムイオン電池の消耗などで電源が切れたときはもちろん、 本機の不使用時の長時間の電源断にも、安定した時刻の継続が期待できます。 本機の「現在時刻の表示」機能や「ストップウオッチ(長時間)」機能などでは、長時間に亘る使用が予想されるため、一定時間スイッチの入力がない場合には自動的に LCD の電源を切り、本機の PIC をスリープ状態にさせて電池の消耗を抑えるようにしています。 |
*注. 入力ポート RB2, RB3, RB5, RB6, RB7 は、内部プルアップ機能を ON にして使用。 また、SLEEP 中には それらのポート に状態変化割り込み機能を使用。 |
電源回路の補足説明 上の回路図では、電源のリチウムイオン電池が POWER スイッチと直結されているように描かれていますが、実際の回路( プリント基板パターン図 (部品面) を参照)では、電池とスイッチとの間には、次の図のように "TP4056 充電モジュール" を挟んで配線をし、リチウムイオン電池を過放電(2.4V)、過電流から保護をするようにしています。 私は生セルタイプの 18650 リチウムイオン電池を使用することが多くなるため、安全面からもこのような保護回路は必須です。 "TP4056 充電モジュール" については、私の別ページ "197. 18650 リチウムイオン電池充電器" の TP4056 リチウムイオン電池充電モジュール の項で、詳述をしているのでそちらを参考にしてください。 このモジュールは名前からも分かるように "充電モジュール" なので、本機でもその機能を使用するようにしています。 上写真の左側にある Micro USB コネクタから +5V を供給します。 このときの注意として 充電中は POWER スイッチを OFF にしておく必要があり ます。 また、私の手持ちの関係で "TP4056 充電モジュール" には、上写真のように表面実装の Micro USB タイプを使用しましたが、使用時の外力には弱く容易にモジュール基板から剥がれてしまうことも予測されるため、新たに購入をされる方は価格が若干高くはなりますが、 Type-C の USB コネクタを使用したモジュールも存在するので、後者の方がスルーホールでがっちりと取り付けられていて安心なので、そちらをお勧めします。 | 回路図 (MultifuncDigitalClock.CE3) | ページトップ | |
約3年ほど以前に5個セットで購入をしたアマゾンでは最もポピュラーな、RTC IC DS3231 と EEPROM の AT24C32 が搭載された RTC モジュール の、手持ちの残り数が僅かとなってきたので
以前から再購入を考えていました。 しかし、この RTC モジュールは最近では価格が最も高騰しているものの1つで、あまりにも馬鹿げた値段なのでとても再購入をする気にはならず躊躇をしていたところ、この RTC モジュールとは異なった "DS3231 For PI" という、次の写真のような RTC モジュールを、 あるとき、比較的安価に販売しているショップを見つけ、どのようなものかと数個を試し買いしてみました。(ところが、商品が到着して1か月ほどが経った後になって、このショップでも他のショップと同様に高騰をしてしまいました。) 写真のように驚くほど小さなもの(実測: 13.5 x 13.5 x 高さ 12.5 mm)で、DS3231 の他には、2個のプルアップ用のチップ抵抗、パスコン用のチップコンデンサ、スポット溶接がされた電極に直接ハンダ付けがされていて簡単に交換ができないコイン電池、 5P のピンソケットが一体となっています。 前出の RTC モジュールのように、EEPROM の AT24C32 は搭載されていませんが、どうせ使用する予定は私にはないので構いません。 名前からも分かるように、この RTC モジュールは Raspberry Pi 用に作られたものらしいのですが、PIC であっても使用することは可能なので問題はありませんが、ただし、この RTC モジュールには次の左回路図のように、SDA, SCL の2本のインタフェース線しか 外部に引き出されていないので、右回路図のように SQW も外部に引き出すように簡単な改造が必要です。 SQW 信号を使用して割り込みで処理をした方が、プログラムの作成では有利になります。 丁度うまいことに、5P のピンソケットの内1つのピンが未使用 ( NC ) となっているので、この NC ピンに DS3231 の SQW (3 ピン) を接続して、下写真の(赤矢印)のように外部に引き出しました。 そして、SQW ピンには 回路図 に示すようにプルアップ抵抗が必要で、実際の取り付けは プリント基板パターン図 (部品面) を参考にしてください。 この RTC モジュールで一番問題となるのは、上述のように、コイン電池がハンダ付けされていて簡単には交換をすることができない、という点でしょうか。 コイン電池の寿命がいつ頃やってくるか分かりませんが、そのときにどうするかはそのときに考えるとして、 取り敢えずこのまま使用してみることにします。 | ページトップ | |
以下は外観6面写真です。
|
| ページトップ |
[ RTC Err! ] ..... DS3231 RTC エラーメッセージ [ Init?_ ] ..... ユーザのスイッチ応答待ち [ スイッチ操作と各メニュー項目(機能)間との遷移図 ] ENABLE START/ Start ZERO TMODE /ALARM GOAL ┌─┐ ┌─┐ ┌─┐ ┌─┐ ┌─┐ ┌─┐ ┌─┐ │□│ │○│ │○│ │○│ │○│ │○│ │○│ └□┘ └─┘ └─┘ └─┘ └─┘ └─┘ └─┘ Stop CLEAR EXIT DOWN UP LEFT RIGHT スイッチ省略名 (EXT): EXIT (DWN): DOWN (UP): UP (LEF): LEFT (RIG): RIGHT (ZER): ZERO (MOD): TIME MODE (EN): ENABLE (STA): START/GOAL (CLR): CLEAR * 印 : 3秒以上の長押し (電源 ON)─────────────┐ │ ↓ ┏━━━━━┓ ┏━━━━━┓ ┏━━━━━┓ ┃ ┃ (EXT) menu=1 ┃ ┃ (EN) + (EXT) ┃ ┃ ┃ メニュー ┃────────→┃ 現在時刻 ┃───────┐ ┃ スリープ ┃ ┃ ┃ │ ┃ ┃ │ ┃ ┃ ┃ の選択 ┃ ┌─│───→┃ の表示 ┃ (EXT) │ ┃ モード ┃ ┃ ┃ │ │ ┃ ┃───┐ │ ┃ ┃ ┗━━━━━┛ │ │ ┗━━━━━┛ │ │ ┗━━━━━┛ ↑ │ │ ↑ │ (自動) │ │ 3分 ↑ │ │ │ │ │ └─────│───│────┘ │ (ZER) or (MOD) or │ │ │ │ │ │ ↑ │ (EXT) or (EN) │ │ │ └───────│───│────│─┘ │ │ │ │ │ │ │ └─────│─│──────────────┘ │ │ │ ↑ │ │ │ │ │ │ │ │ ┏━━━━━┓ │ │ │ │ │ │ ┃ ┃ │ │ │ │ │ │menu=2 ┃ ストップ ┃ (EXT) │ │ │ │ │ └───→┃ ウオッチ ┃───┐ │ │ │ │ │ │ ┃ (詳細) ┃ │ │ │ │ │ │ │ ┃ ┃ │ │ │ │ │ │ │ ┗━━━━━┛ │ │ │ │ │ │ │ │ │ │ │ └─────│─│──────────────┘ │ │ │ ↑ │ │ │ │ │ │ │ │ ┏━━━━━┓ │ │ │ │ │ │ ┃ ┃ │ │ │ │ │ │menu=3 ┃ インター ┃ (EXT) │ │ │ │ │ └───→┃バル(間隔)┃───┐ │ │ │ │ │ │ ┃ タイマー ┃ │ │ │ │ │ │ │ ┃ ┃ │ │ │ │ │ │ │ ┗━━━━━┛ │ │ │ │ │ │ │ │ │ │ │ └─────│─│──────────────┘ │ │ │ ↑ │ │ │ │ │ │ │ │ ┏━━━━━┓ │ │ │ │ │ │ ┃ ┃ (EN) + (EXT) │ │ │ │ │ │menu=4 ┃ ストップ ┃─────┐ │ │ │ │ │ └───→┃ ウオッチ ┃ │ │ │ │ │ │ │ ┃ (長時間) ┃ (EXT) │ │ │ │ │ │ │ ┃ ┃───┐ │ │ │ │ │ │ │ ┗━━━━━┛ │ │ │ │ │ │ │ │ ↑ │ (自動) │ │ │ 1分 │ │ (EXT) or │ │ │ │ └─────│─│─│────┘ │ (EN) or │ │ │ │ │ │ │ ↑ │ (STA) │ │ │ └───────│─│─│────│─┘ │ │ │ │ │ │ │ │ └─────│─│──────────────┘ │ │ │ │ ↑ │ │ │ │ │ │ │ │ │ ┏━━━━━┓ │ │ │ │ │ │ │ ┃ ┃←────┘ │ │ │ │ │ │menu=5 ┃ 記録内容 ┃ │ │ │ │ │ └───→┃ ┃ │ │ │ │ │ │ ┃ の表示 ┃ (EXT) │ │ │ │ │ │ ┃ ┃───┐ │ │ │ │ │ │ ┗━━━━━┛ │ │ │ │ │ │ │ ↑ │ (自動) │ │ 1分 │ │ (EXT) or │ │ │ │ └─────│───│────┘ │ (RIG) or │ │ │ │ │ │ │ (LEF) │ │ │ └───────│───│──────┘ │ │ │ │ │ └─────│─│──────────────┘ │ ↑ │ │ │ │ │ │ ┏━━━━━┓ │ │ │ │ ┃ ┃ (EN) + (EXT) │ │ │ │menu=6 ┃週間タイマ┃─────┐ │ │ │ └───→┃ ーの時刻 ┃ │ │ │ │ │ ┃ 設定 ┃ (EXT*) ..│..│..... 設定終了 │ │ │ ┃ ┃───┐ │ │ │ │ │ ┗━━━━━┛ .....│..│..│..... 設定中断終了 │ │ │ │ │ │ └─────│─│──────────────┘ │ │ (LEF) + (RIG) or ↑ │ │ │ │ (RIG) + (LEF) │ │ │ ┏━━━━━┓ │ │ │ │ │ ┃ ┃←────┘ │ │ │ │menu=7 ┃週間タイマ┃ │ │ │ └───→┃ ーの設定 ┃ │ │ │ │ ┃表示と消去┃ (EXT) │ │ │ │ ┃ ┃───┐ │ │ │ │ ┗━━━━━┛ │ │ │ │ │ │ │ └─────│─│──────────────┘ │ ↑ │ │ │ │ │ │ ┏━━━━━┓ │ │ │ │ ┃ ┃←──────┘ │ │ │menu=8 ┃ 現在時刻 ┃ │ │ └───→┃ ┃ (LEF) + (RIG) or │ │ │ ┃ の設定 ┃ (RIG) + (LEF) ..... 設定中断終了 │ │ │ ┃ ┃───────┐ │ │ │ ┗━━━━━┛ (EXT) .......│..... 設定終了 │ │ │ │ │ └─│──────────────────┘ │ │ │ │ ┏━━━━━┓ │ │ ┃ ┃ (LEF) + (RIG) or │ │menu=9 ┃ スリープ ┃ (RIG) + (LEF) ..... 設定中断終了 │ └───→┃ 移行時間 ┃───────┐ │ ┃ の変更 ┃ (EXT) .......│..... 設定終了 │ ┃ ┃ │ │ ┗━━━━━┛ │ │ │ └──────────────────────────┘
[ Select_x ] ..... メニュー番号 x がブリンク表示 1行目 [ ClockDsp ] ..... 1:「現在時刻の表示」 ┐ ・ │ ・ │ 2行目 ・ │ [ SleepTim ] ..... 9:「スリープ移行時間の変更」 ┘
そして、移行をしてきた本「現在時刻の表示」機能では、"ピポッピポッ" アラーム音の出力と LED (緑) を点滅させて、本機能に動作が移ったことを知らせる。 偶数秒のとき 奇数秒のとき [ xx/xx/xx ] 1行目 ... 年月日の表示 [ (SUN) AM ] 1行目 ... 曜日 AM/PM の表示 [ xx:xx:xx ] 2行目 ... 時分秒の表示 [ xx:xx:xx ] 2行目 ... 時分秒の表示
(24H) 00 01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 21 22 23 (12H) 12 1 2 3 4 5 6 7 8 9 10 11 12 1 2 3 4 5 6 7 8 9 10 11 am am am am am am am am am am am am pm pm pm pm pm pm pm pm pm pm pm pm | 「メニューの選択」機能に戻る |
そして、移行をしてきた本「ストップウオッチ(詳細)」機能では、"ピポッピポッ" アラーム音の出力と LED (緑) を点滅させて、本機能に動作が移ったことを知らせる。
[ 00:00:00 ] ... HH:MM:SS [ (StW) 00 ] ... (StW) XX ... XX: 1/100 秒 | 「メニューの選択」機能に戻る |
そして、移行をしてきた本「インターバル(間隔)タイマー」機能では、"ピポッピポッ" アラーム音の出力と LED (緑) を点滅させて、本機能に動作が移ったことを知らせる。
[ ItvTimer ] ... インターバルタイマー [ xx:xx:xx ] ... タイマー時間の設定値 | 「メニューの選択」機能に戻る |
そして、移行をしてきた本「ストップウオッチ(長時間)」機能では、"ピポッピポッ" アラーム音の出力と LED (緑) を点滅させて、本機能に動作が移ったことを知らせる。 [ LngStopW ] ..... 本機能の略名を表示 [ xx:xx:xx ] ..... 現在時刻 "HH:MM:SS" を刻々と刻みながら表示
(スイッチ操作 (1) ENABLE スイッチ + START スイッチ とは、ENABLE スイッチ を押しながら START スイッチ を通常のちょん押しをすることを表す) [ xx:xx:xx ] ..... スプリットタイムを1秒ずつ更新しながら表示 [ xx:xx:xx ] ..... 現在時刻 "HH:MM:SS" を刻々と刻みながら表示 [ ** xx ** ] ..... xx に記録番号を表示 [ xx:xx:xx ] ..... ラップタイムを表示 [ xx:xx:xx ] ..... スプリットタイム [ Mem Full ] ..... メモリ満タン警告メッセージ [ ** 40 ** ] ..... 最終の記録番号 [ StpW End ] ..... ストップウオッチの動作が終了した [ xx:xx:xx ] ..... 最終のラップタイム [ xx:xx:xx ] ..... 現在時刻を更新しながら表示し続ける [ StpW End ] ..... ストップウオッチの動作が終了した [ xx:xx:xx ] ..... 現在時刻を更新しながら表示し続ける
| 「メニューの選択」機能に戻る |
そして、移行をしてきた本「記録内容の表示」機能では、"ピポッピポッ" アラーム音の出力と LED (緑) を点滅させて、本機能に動作が移ったことを知らせる。 [ MemoDisp ] ..... 本機能の略名を表示 [ xx:xx:xx ] ..... 現在時刻 "HH:MM:SS" を刻々と刻みながら表示 [ MemoDisp ] ..... 本機能の略名を表示 [ None ] ..... "None" がブリンク表示
[ ** xx ** ] ..... 記録番号 xx を表示 [ ] ..... 2行目には何も表示されない [ xx:xx:xx ] ..... スプリットタイムを表示 [ xx:xx:xx ] ..... ラップタイムを表示 [ MemoDisp ] ..... 本機能の略名を表示 [ xx:xx:xx ] ..... 現在時刻 "HH:MM:SS" を刻々と刻みながら表示 | 「メニューの選択」機能に戻る |
そして、移行をしてきた本「週間タイマーの時刻設定」機能では、"ピポッピポッ" アラーム音の出力と LED (緑) を点滅させて、本機能に動作が移ったことを知らせる。
[ Set EDAY ] ..... 右側に曜日 (略名) を表示 [ 00:00 0 ] ..... "HH:MM"、"曜日を表す数字" を設定
[ Set Err! ] ..... エラーメッセージ [ Mem Full ] ..... メモリ満タン警告メッセージ | 「メニューの選択」機能に戻る |
そして、移行をしてきた本「週間タイマーの設定表示と消去」機能では、"ピポッピポッ" アラーム音の出力と LED (緑) を点滅させて、本機能に動作が移ったことを知らせる。
[ Dsp www ] ..... www: 曜日 (略名) を表示 [ xx:xx nn ] ..... xx:xx 時分、nn: 登録情報の番号 [ Dsp None ] ..... "None" がブリンク表示 [ 00:00 00 ] ..... 00:00 時分、00: 登録情報の番号 | 「メニューの選択」機能に戻る |
[ Select_8 ] [ ClockAdj ] ..... 8:「現在時刻の設定」 そして、移行をしてきた本「現在時刻の設定」機能では、"ピポッピポッ" アラーム音の出力と LED (緑) を点滅させて、本機能に動作が移ったことを知らせる。
[ Date Set ] ..... 年月日設定メッセージ [ yy/nn/dd ] ..... 年月日の表示と設定 [ Time Set ] ..... 時分秒設定メッセージ [ hh:mm:ss ] ..... 時分秒の表示と設定 | 「メニューの選択」機能に戻る |
そして、移行をしてきた本「スリープ移行時間の変更」機能では、"ピポッピポッ" アラーム音の出力と LED (緑) を点滅させて、本機能に動作が移ったことを知らせる。
[ M1,M4,M5 ] ..... M1: メニュー項目 1, M4: メニュー項目 4, M5: メニュー項目 5 [ xx xx xx ] ..... 現在設定されている、それぞれの値(分)が表示される |
アセンブラで PIC16F1 ファミリーのプログラミングを行うのは、私にとって、"194. FMステレオラジオ III" 、"195. ドットマトリクス LED コントローラー" に続いて、
本機が第3作目となります。 前作では PIC16F1705 と PIC16F1709 を使用しましたが、本機には PIC16F1827 を選択してみました。 PIC16F1827 は 18 ピン PIC で、PIC16F ファミリーの PIC16F627A/628A/648A、PIC16F818/819、PIC16F87/88 などと、ピンコンパチブルとなっています。 この項では、"リニア データメモリのプログラミング"、"不揮発性メモリ EEPROM のプログラミング"、"様々なテーブルデータ選択のプログラミング"、および "プログラミング時に起きた不具合" などについての経験談を、述べてみたいと思います。 ● リニア データメモリのプログラミング 次の表 TABLE 3-3 は、PIC16F1827 の具体的な データメモリ の一部分をデータシートから抜粋したもので、黄色に彩色した 特殊レジスタ(SFR) を本機のプログラムでは使用しています。 表中の赤線より上に位置するレジスタ群は コアレジスタ と呼ばれ、バンク切り替えを行うことなくすべてのバンクからアクセスができる共通メモリです。 同表の下方に位置する 汎用レジスタ(GPR) の内、薄緑色に彩色をしたバンク 0 については、従来型データメモリ として使用をし、水色に彩色をしたバンク 1 〜 バンク 4 については、リニア データメモリ *1 として本機のプログラムでは扱っています。
下図は、上表の汎用レジスタ(GPR)について描き直したもので、本機のプログラムで使用しているリニア データメモリの使用状況を表しています。 上述のように、バンク 1 〜 バンク 4 はリニア データメモリとして扱っており、各バンクの右側に付けた 2000 番台の番号は、リニア データメモリとしてのアドレスを示しています。 リニア データメモリは、各バンクの 80 バイトずつの領域(下図の左端に示した 80 Bytes ずつの領域)を、すべてつなぎ合わせて 連続領域として使用ができる ようにした仮想領域です。 この概念は、従来の PIC16F ファミリーにはなく、 PIC16F1 ファミリーになって新しく取り入れられたもので、FSRnH と FSRnL のレジスタペアで間接アドレス指定をして、すべての領域を簡単にアクセスすることができます。 下図内のスプリットメモリについては、バンク 1 〜 バンク 3 の3つのバンクに分散をしていますが、リニア データメモリのアドレスを使用することで、連続領域としてアクセスすることができます。 これに対して、従来の PIC16F ファミリーでは、 間接アドレス指定をしても、各バンクの左側に付けた不連続なアドレスの指定になるので、プログラミングはその分かなり面倒になります。(その実例が、"184. ストップウオッチ II" の "PIC16F628A と PIC16F648A のデータメモリの構成図"、および "ソースファイル (StopWatchII.asm)" にあるので、興味がある方はそちらをご覧ください。) : : 01297 radix dec : 00002050 01303 linear_memory equ h'2000' + 80 ;リニアデータメモリの先頭アドレス > 00002050 01304 timer_memory equ linear_memory ;タイマー用メモリの先頭アドレス 00000014 01305 timer_capacity equ 20 ;タイマー用メモリの記憶容量の最大値 > 0000208C 01306 split_memory equ timer_memory + 3 * timer_capacity ;スプリット用メモリの先頭アドレス 00000032 01307 split_capacity equ 50 ;スプリットタイムの記憶容量の最大値 > 00002122 01308 next_memory equ split_memory + 3 * split_capacity ;次に使用できるメモリの先頭アドレス : :実際に、本機のプログラムでリニア データメモリ内のスプリットメモリに、 "時"、"分"、"秒" のスプリットデータを読み書きしている部分が次に示すリストで、このように各バンクをまったく意識しないでプログラミングすることができます。 (上述の "184. ストップウオッチ II" の実例と比べてみると、その違いがよくわかると思います。) ;-------------------------------------------------------------------------- ; 経過時間(スプリットタイム)を書き込み ;-------------------------------------------------------------------------- write_split_memory movlw high split_memory ;スプリット用メモリの先頭アドレス movwf FSR1H ; movlw low split_memory ; movwf FSR1L ; movf split_cnt,W btfsc STATUS,Z ;スプリット番号カウンタ = 0 か? goto wrmem02 ;Yes movwf lp_cnt2 ;ループカウンタ2 wrmem01 addfsr FSR1,3 ; decfsz lp_cnt2,F ;ループカウンタ2 - 1 = 0 か? goto wrmem01 ;No wrmem02 movf bcd_hh_s,W ; movwi FSR1++ ;時_BCD カウンタ 書き込み movf bcd_mm_s,W ; movwi FSR1++ ;分_BCD カウンタ 書き込み movf bcd_ss_s,W ; movwi FSR1++ ;秒_BCD カウンタ 書き込み incf split_cnt,F ;スプリット番号カウンタ + 1 return ;-------------------------------------------------------------------------- ; 経過時間(スプリットタイム)を読み出し ;-------------------------------------------------------------------------- read_split_memory movlw high split_memory ;スプリット用メモリの先頭アドレス movwf FSR1H ; movlw low split_memory ; movwf FSR1L ; decf split_cnt,W btfsc STATUS,Z ;スプリット番号カウンタ - 1 = 0 か? goto rdmem02 ;Yes movwf lp_cnt2 ;ループカウンタ2 rdmem01 addfsr FSR1,3 ; decfsz lp_cnt2,F ;ループカウンタ2 - 1 = 0 か? goto rdmem01 ;No rdmem02 moviw FSR1++ ; movwf bcd_hh_w ;時_BCD カウンタ 読み出し moviw FSR1++ ; movwf bcd_mm_w ;分_BCD カウンタ 読み出し moviw FSR1++ ; movwf bcd_ss_w ;秒_BCD カウンタ 読み出し return これに対して (2) では命令が実行されても FSR1 の指すポイント(FSR1H と FSR1L のレジスタペアの内容)に変化はなく、今 FSR1 が指しているポイントに対しての相対的な位置が指定されます。 1[FSR1] と書けば今 FSR1 が指しているポイントに1プラスしたアドレスを指しますが、FSR1 自体の指すポイントに変化はありません。 ↓ 0B0D 0829 05632 movf bcd_hh_s,W ; 0B0E 001E 05633 movwi FSR1++ ;(1) 0B0F 3FC0 05634 movwi 0[FSR1] ;(2) 0B10 001E 05635 movwi INDF1++ ;(3) 05636 0B11 082A 05637 movf bcd_mm_s,W ; 0B12 001E 05638 movwi FSR1++ ;(1) 0B13 3FC1 05639 movwi 1[FSR1] ;(2) 0B14 001E 05640 movwi INDF1++ ;(3) 05641 0B15 082B 05642 movf bcd_ss_s,W ; 0B16 001E 05643 movwi FSR1++ ;(1) 0B17 3FC2 05644 movwi 2[FSR1] ;(2) 0B18 001E 05645 movwi INDF1++ ;(3)ところで、上リストの (1) と (3) とを見比べてみてください。 ↓ で示した列部分が翻訳された機械語の部分ですが、まったく同じであることが分かります。 しかし、MOVWI、MOVIW 命令のどちらも同様ですが、次に示すように PIC16F/LF1826/27 のデータシート (DS41391B-page 332, page 333)には、この (3) の書き方は載っていません。 MOVIW Move INDFn to W ---------------------------------------- Syntax: [ label ] MOVIW ++FSRn [ label ] MOVIW --FSRn [ label ] MOVIW FSRn++ [ label ] MOVIW FSRn-- [ label ] MOVIW k[FSRn] [ label ] MOVIW FSRn ;(4) Operation: INDFn → W MOVWI Move W to INDFn ---------------------------------------- Syntax: [ label ] MOVWI ++FSRn [ label ] MOVWI --FSRn [ label ] MOVWI FSRn++ [ label ] MOVWI FSRn-- [ label ] MOVWI k[FSRn] [ label ] MOVWI FSRn ;(4) Operation: W → INDFnこの (3) の書き方は偶然にも私が見つけた?もので、自分が書いたプログラムの中に (1) と (3) の書き方が混在していて、(3) は誤って書いたのですがエラーにはならず、どちらも同じ機械語に翻訳されているのを見つけた次第です。 その後、紛らわしいのでとりあえずデータシート通り (1) の書き方に統一をしました。*2
しかし、データシート の中の (4) の書き方は誤りで、このように書くと次のリストのようにエラーになります。 Error[124] : Illegal argument 0B19 05648 movwi FSR1 ;(4) 0B19 3FC0 05649 movwi [FSR1] ;(5) 05650 Error[124] : Illegal argument 0B1A 05651 moviw FSR1 ;(4) 0B1A 3F40 05652 moviw [FSR1] ;(5)この場合に (5) のように角カッコ[ ]を付けるとエラーとはならず、 0[FSR1] と書いたときと同等になります。 最初の リスト の3行目 (2) の機械語と比べてみてください。 | プログラムのトップに戻る | ● 不揮発性メモリ EEPROM のプログラミング データの保存に使用する 不揮発性メモリ としては、PIC16F1705/1709 のように高書き込み耐性フラッシュメモリ *3 ではなく、PIC16F1827 でも従来の PIC16F ファミリーと同様に EEPROM が搭載 されているので、 その点ではプログラミングも比較的楽に行えます。
本機のプログラムでは、3:「インターバル(間隔)タイマー」機能、6:「週間タイマーの時刻設定」機能、7:「週間タイマーの設定表示と消去」機能、9:「スリープ移行時間の変更」機能の、4つの機能で EEPROM にアクセスをして、それらの機能で必要なバックアップデータを書き込んだり、 読み出したりをしています。 次に示すリストは、9:「スリープ移行時間の変更」機能の例で、4つの機能の中で最も単純な読み書きを行っているだけなので、例として取り上げました。 ;-------------------------------------------------------------------------- ; スリープ移行時間(分)を EEPROM へ書き込み ;-------------------------------------------------------------------------- write_sleep_time movlw high menu_1 ;menu_1 スリープ移行時間(分)カウンタのアドレス movwf FSR0H ; movlw low menu_1 ; movwf FSR0L ;間接アドレスに設定 movlw 3 ;書き込みバイト数 movwf lp_cnt1 ;ループカウンタ1 movlb 3 ;バンク 3 movlw low eep_sleep_time movwf EEADRL ;EEPROM の記録アドレスを設定 bcf EECON1,CFGS ;フラッシュプログラム/データ EEPROM メモリを選択 bcf EECON1,EEPGD ;データ EEPROM メモリにアクセス wrslp01 movlb 3 ;バンク 3 moviw FSR0++ ;BCD カウンタデータの読み出し movwf EEDATL ;書き込みデータを設定 bsf EECON1,WREN ;プログラム/消去サイクルを許可 ; bcf INTCON,GIE ;割り込みを禁止 movlw h'55' movwf EECON2 ;h'55' を EECON2 に書き込む movlw h'aa' movwf EECON2 ;h'aa' を EECON2 に書き込む bsf EECON1,WR ;書き込み開始 ; bsf INTCON,GIE ;割り込みを許可 bcf EECON1,WREN ;プログラム/消去サイクルを禁止 btfsc EECON1,WR ;書き込み 終了か? goto $ - 1 ;No incf EEADRL,F ;記録アドレスの更新 movlb 0 ;バンク 0 decfsz lp_cnt1,F ;ループカウンタ1 - 1 = 0 か? goto wrslp01 ;No return ;-------------------------------------------------------------------------- ; スリープ移行時間(分)を EEPROM から読み出し ;-------------------------------------------------------------------------- read_sleep_time movlw high menu_1 ;menu_1 スリープ移行時間(分)カウンタのアドレス movwf FSR0H ; movlw low menu_1 ; movwf FSR0L ;間接アドレスに設定 movlw 3 ;読み出しバイト数 movwf lp_cnt1 ;ループカウンタ1 movlb 3 ;バンク 3 movlw low eep_sleep_time movwf EEADRL ;EEPROM の記録アドレスを設定 bcf EECON1,CFGS ;フラッシュプログラム/データ EEPROM メモリを選択 bcf EECON1,EEPGD ;データ EEPROM メモリにアクセス rdslp01 movlb 3 ;バンク 3 bsf EECON1,RD ;読み出し制御 = ON movf EEDATL,W ;データの読み出し movwi FSR0++ ;BCD カウンタデータへ書き込み incf EEADRL,F ;記録アドレスの更新 movlb 0 ;バンク 0 decfsz lp_cnt1,F ;ループカウンタ1 - 1 = 0 か? goto rdslp01 ;No return ;========================================================================== ; EEPROM エリア ;========================================================================== org h'f000' : : ; スリープ移行時間(分)のバックアップ格納位置 eep_sleep_time de 3,1,1 ;初期(デフォルト)値 ; インターバルタイマーの格納位置 itv_timer_memory de h'00',h'00',h'00' ;初期値 ; タイマー用メモリのバックアップ格納位置 eep_timer_memory de h'00' ;初期値 ; (3 * timer_capacity) バイトサイズを使用 make -f nbproject/Makefile-default.mk SUBPROJECTS= .build-conf make[1]: Entering directory 'C:/Data/Project/PIC/MultifuncDigitalClock/MultifuncDigitalClock.X' make -f nbproject/Makefile-default.mk dist/default/production/MultifuncDigitalClock.X.production. make[2]: Entering directory 'C:/Data/Project/PIC/MultifuncDigitalClock/MultifuncDigitalClock.X' "C:\Program Files (x86)\Microchip\MPLABX\v5.35\mpasmx\mpasmx.exe" -q -p16f1827 -l"build/default/production/_ext/465711987/MultifuncDigitalClock.lst" -e"build/default/production/_ext/465711987/MultifuncDigitalClock.err" -o"build/default/production/_ext/465711987/MultifuncDigitalClock.o" "C:/Data/Project/PIC/MultifuncDigitalClock/MultifuncDigitalClock.X/MultifuncDigitalClock.asm" "C:\Program Files (x86)\Microchip\MPLABX\v5.35\mpasmx\mplink.exe" -p16f1827 -w -m"dist/default/production/MultifuncDigitalClock.X.production.map" -z__MPLAB_BUILD=1 -odist/default/production/MultifuncDigitalClock.X.production. build/default/production/_ext/465711987/MultifuncDigitalClock.o MPLINK 5.09, LINKER Device Database Version 1.50 Copyright (c) 1998-2011 Microchip Technology Inc. > Error - section '.org_3' can not fit the absolute section. Section '.org_3' start=0x00002100, length=0x0000004a Errors : 1 make[2]: Leaving directory 'C:/Data/Project/PIC/MultifuncDigitalClock/MultifuncDigitalClock.X' make[1]: Leaving directory 'C:/Data/Project/PIC/MultifuncDigitalClock/MultifuncDigitalClock.X' make[2]: *** [nbproject/Makefile-default.mk:126: dist/default/production/MultifuncDigitalClock.X.production.] Error 1 make[1]: *** [nbproject/Makefile-default.mk:91: .build-conf] Error 2 make: *** [nbproject/Makefile-impl.mk:39: .build-impl] Error 2 BUILD FAILED (exit value 2, total time: 1s)上のメッセージ中で、左端に > 印を付けた行がエラーメッセージな訳で、その行だけを再掲すると Error - section '.org_3' can not fit the absolute section. Section '.org_3' start=0x00002100, length=0x0000004aとなります。 これはリンカー MPLINK 5.09, LINKER から発せられたもので、どうやらリンクエラーのようです。 start=0x00002100, length=0x0000004a がおかしい ??? と言っているようです。 念のためアセンブルリストを確認してみても、もちろんエラーなどはどこにも見当たりません。 そして、これに似たエラーメッセージを以前にも見たことを思い出しました。 (興味のある方は "188. FMステレオラジオ II" の PIC16F684 と MPLAB IDE v8.92 のリンクエラーについて を参照のこと) このエラーになったときの、リンカーが作り出す .map ファイル の先頭部分を見てみると、 MPLINK 5.09, LINKER Linker Error Map File - Created Sun Oct 01 19:40:54 2023 *Warning* - This is only a partial map file due to a link time error. Only sections which were allocated prior to the error are shown below. CODEPAGES: Memory Start End Section Address Size(Bytes) --------- --------- --------- --------- --------- --------- page0 0x0000 0x07ff .org_0 0x0000 0x0002 .org_1 0x0004 0x0ed0 page1 0x0800 0x0fff .org_2 0x0800 0x0d08 .idlocs 0x8000 0x8003 .devid 0x8006 0x8006 .config 0x8007 0x8008 .config_8007_BUILD/DEFAULT/PRODUCTION/_EXT/46 0x8007 0x0002 .config_8008_BUILD/DEFAULT/PRODUCTION/_EXT/46 0x8008 0x0002 eedata 0xf000 0xf0ff : :と出力されています。 ここに解答がありました。 上に表示したリスト中の最下行 eedata 0xf000 0xf0ffが解答そのものでした。 EEPROM の開始アドレスは 0xf000 だ、というのです。 そうなんです。 私が書いたソースプログラムには、MPLAB X IDE に表示されたエラーメッセージの、開始アドレス start=0x00002100 を指定していたのです。 ;========================================================================== ; EEPROM エリア ;========================================================================== org h'2100' : : ; スリープ移行時間(分)のバックアップ格納位置 eep_sleep_time de 3,1,1 ;初期(デフォルト)値 ; インターバルタイマーの格納位置 itv_timer_memory de h'00',h'00',h'00' ;初期値 ; タイマー用メモリのバックアップ格納位置 eep_timer_memory de h'00' ;初期値 ; (3 * timer_capacity) バイトサイズを使用深く考えもしないで、従来の PIC16F ファミリーで指定をしていた開始アドレスを書いていました。 少し考えれば分かるはずで、この領域は PIC16F1 ファミリーでは、通常はプログラムメモリの領域です。 そこに EEPROM が存在するはずがない。 おそまつでした。 そして、正解は上述のように 0xf000 ですが、このアドレスをデータシート(DS41391B)で探してみましたが、どうやら載っていないようで見つけることができませんでした。 | プログラムのトップに戻る | ● テーブルデータ選択のプログラミング(LCD の表示位置の取得) 次に示すリスト <例−1> <例−2> は、本機の 2:「ストップウオッチ(詳細)」機能 において、時、分、秒、1/100 秒 のそれぞれを、LCD 画面上のどの位置に表示するかを指示するためのテーブルで、表示位置ポインタ dsp_point( 0〜7 )の値から LCD の DDRAM アドレスを 取得するためのものです。 従来の PIC16F ファミリーでは <例−1> のように、一般的には addwf PCL,F としてテーブルデータ選択を行っていました。 この命令を使用したときの問題点は、各テーブル要素(この例では8個)が機械語に翻訳されたときに、プログラムメモリの xxFF 〜 xx00 番地にまたがった場合には、正常な動作をしない ― という点です。
dsp_point_table movf dsp_point,W ;表示位置ポインタ addwf PCL,F ; 0 1 2 3 4 5 ; 01 23 45 ... dsp_point ; "HH" "HL" "MH" "ML" "SH" "SL" ; "xx:xx:xx" ... 桁 ; ; 01234567 dt h'00',h'01',h'03',h'04',h'06',h'07' ; ... DDRAM のアドレス (1行目) ; 6 7 ; 67 ... dsp_point ; "XH" "XL" ; "(StW) xx" ... 桁 ; ; 01234567 dt h'46',h'47' ; ... DDRAM のアドレス (2行目)この <例−1> のように addwf PCL,F とした場合には、次図に示すように PIC16F ファミリー、PIC16F1 ファミリーのどちらの場合にも、xxFF 番地までのテーブル要素に対しては正常に動作をしますが、xx00 番地以降のテーブル要素に対しては、ALU の結果(PCL + W)で オーバーフローしたビットが PCH に反映されません。 したがって、プログラムカウンタ(PC)の指すアドレスは、100 番地を差し引いたアドレスとなって希望するテーブル要素を取得することはできず、多くの場合、プログラムは暴走することになります。
dsp_point_table movf dsp_point,W ;表示位置ポインタ brw ; 0 1 2 3 4 5 ; 01 23 45 ... dsp_point ; "HH" "HL" "MH" "ML" "SH" "SL" ; "xx:xx:xx" ... 桁 ; ; 01234567 dt h'00',h'01',h'03',h'04',h'06',h'07' ; ... DDRAM のアドレス (1行目) ; 6 7 ; 67 ... dsp_point ; "XH" "XL" ; "(StW) xx" ... 桁 ; ; 01234567 dt h'46',h'47' ; ... DDRAM のアドレス (2行目)PIC16F/LF1826/27 のデータシート (DS41391B-page 328)から BRW 命令を抜粋すると、 BRW Relative Branch with W ---------------------------------------- Syntax: [ label ] BRW Operands: None Operation: (PC) + (W) → PC Status Affected: Noneと、説明があります。 この BRW 命令の動作が分かり易いように図に表してみると、次図に示すように、プログラムカウンタ(PC)の全体(15 ビット)に対して W レジスタの内容を加算するため、<例−1> の場合とは異なって、テーブル要素が xxFF 〜 xx00 番地にまたがっていても、 何の問題も起こりません。 | プログラムのトップに戻る | ● テーブルデータ選択のプログラミング(文字列の LCD 表示) 本機では9つの機能(メニュー項目)を持たせてありますが、それらのどの機能を実行させるかを、まず、UP スイッチまたは DOWN スイッチの操作よって、LCD 画面上に表示するメニュー項目の1つを選択させ、次に、EXIT スイッチを押すことによって選択が決定されて、 各メニュー項目の機能が実行されます。(「メニューの選択」機能 を参照) ここで、「メニューの選択」機能においての、テーブルデータ選択のプログラミングの1つ目を紹介します。 本機で使用している LCD のハード的な制約(8文字 x 2行)から、メニュー項目のすべてを1度に表示させることができません。 そこで、上述のように UP スイッチまたは DOWN スイッチを押すごとに、メニュー番号 menu_no を 1 〜 9 までの範囲で変更させ、その menu_no の値によってメニュー項目の名称(略名)をテーブル引きをして、1行(1項目)ずつ LCD 画面上に表示をしています。 : : movf menu_no,W ;メニュー番号 movwf work1 ;退避 clrf menu_no ; [ Select_ ] ;1行目 ... Select_ の表示 ; ( [ ClockDsp ] ) ;2行目 ... メニュー項目の表示 ; 01234567 movlw h'80' ;表示位置指定 (1行目先頭) call menu_display ;メニュー選択の表示(1行目) <<<<< movf work1,W ;復帰 movwf menu_no ;メニュー番号 main01 bcf stat_flg,6 ;スイッチ入力有りフラグ = 0 movlw h'0c' ;表示オン/オフコントロール call I2cLcd_Cmd ;D=1: 表示オン, C=0: カーソル非表示, B=0: 非ブリンク ; ( [ Select_ ] ) ;1行目 ... Select_ の表示 ; [ ClockDsp ] ;2行目 ... メニュー項目の表示 ; 01234567 movlw h'c0' ;表示位置指定 (2行目先頭) call menu_display ;メニュー項目の表示(2行目) <<<<< movlw h'87' ; call I2cLcd_Cmd ;表示位置指定 movf menu_no,W ;メニュー番号 iorlw h'30' ;数字に変換 call I2cLcd_Data ;I2C LCD へデータを1文字出力 movlw h'87' ; call I2cLcd_Cmd ;表示位置指定 movlw h'0d' ;表示オン/オフコントロール call I2cLcd_Cmd ;D=1: 表示オン, C=0: カーソル非表示, B=1: ブリンク ; 各スイッチの入力チェック : : ;-------------------------------------------------------------------------- ; メイン・メニューの表示 ;-------------------------------------------------------------------------- menu_display call I2cLcd_Cmd ;I2C LCD へコマンドを出力(表示位置を指定) movf menu_no,W ;メニュー番号 movwf addr_low lslf addr_low,F ;x 2 倍 lslf addr_low,F ;x 4 倍 lslf addr_low,F ;x 8 倍 addwf addr_low,F ;x 9 倍 movlw high menu_table ;テーブルデータの先頭 high アドレス movwf addr_high ; movlw low menu_table ;テーブルデータの先頭 low アドレス addwf addr_low,F ; btfsc STATUS,C ;オーバーフローした場合は incf addr_high,F ;high アドレスの更新 call I2cLcd_Disp ;I2C LCD へ文字列データを出力 return ; メニュー データテーブル menu_table dt "Select_ ",0 ; (例外) 0 「メニューの選択」 dt "ClockDsp",0 ;menu_no = 1 「現在時刻の表示」 dt "DtlStopW",0 ; 2 「ストップウオッチ(詳細)」 dt "ItvTimer",0 ; 3 「インターバル(間隔)タイマー」 dt "LngStopW",0 ; 4 「ストップウオッチ(長時間)」 dt "MemoDisp",0 ; 5 「記録内容の表示」 dt "WkTimSet",0 ; 6 「週間タイマーの時刻設定」 dt "WkTimDsp",0 ; 7 「週間タイマーの設定表示と消去」 dt "ClockAdj",0 ; 8 「現在時刻の設定」 dt "SleepTim",0 ; 9 「スリープ移行時間の変更」 ; 01234567上のリスト中で、menu_display サブルーチン 内で使用されている I2cLcd_Disp サブルーチン は、インクルードファイル I2C LCD 制御サブルーチン (I2C_S2_LCD.sub) 内に収容されて * いますが、 次にも掲載をしておきます。 なお、I2cLcd_Cmd、I2cLcd_Data サブルーチンについても、同 I2C_S2_LCD.sub ファイル内に収容されています。 I2cLcd_Disp call tbl_read ;テーブルデータの読み出し incf addr_low,F ;読み出すデータの low アドレスの更新 btfsc STATUS,Z ;オーバーフローした場合は incf addr_high,F ;読み出すデータの high アドレスの更新 addlw 0 btfsc STATUS,Z ;h'00' か? goto i2cldsp01 ;Yes call I2cLcd_Data ;LCD へデータを1文字出力 goto I2cLcd_Disp i2cldsp01 return ; テーブルデータの1文字読み出し tbl_read movf addr_high,W ;読み出すデータの high アドレス movwf PCLATH movf addr_low,W ;読み出すデータの low アドレス movwf PCLこの I2cLcd_Disp サブルーチンは、PIC16F1 ファミリーの場合には、FSRnH と FSRnL のレジスタペアを使用した方が何かと便利ですが、PIC16F ファミリーにおいても、同一の I2C_S2_LCD.sub ファイルに収容されているサブルーチン群を使用するため、汎用レジスタ(GPR)の addr_high、addr_low をレジスタペアとして使用しています。 * なお、上に示した I2cLcd_Disp サブルーチンは、後出の テーブルデータ選択のプログラミングで起きた不具合 で述べているように、条件によっては不具合をもたらすことが分かったため、その後、I2cLcd_Disp サブルーチン( 最新版 )のように書き改めました。 | プログラムのトップに戻る | ● テーブルデータ選択のプログラミング(ジャンプ先の振り分け) 「メニューの選択」機能においての、テーブルデータ選択のプログラミングの2つ目として、この項では FSRnH と FSRnL のレジスタペアを使用した、プログラミング例を紹介しておきます。 前項と同様にメニュー項目の1つを選択後に、EXIT スイッチを押すことによって 選択が決定され、各メニュー項目別の機能処理のプログラムへ ジャンプする部分のプログラミングです。 やはり前項と同様に menu_no の値(1 〜 9)によって、ジャンプアドレステーブルから各メニュー項目別の機能処理アドレスを取得しています。 decf menu_no,W ;0 始まりにする movwf FSR1L lslf FSR1L,F ;2 倍する movlw low jump_addr_tbl addwf FSR1L,F ;ジャンプアドレステーブルの先頭 low アドレスを足す clrf FSR1H movlw high jump_addr_tbl addwfc FSR1H,F ;ジャンプアドレステーブルの先頭 high アドレスを足す moviw 0[FSR1] ; movwf PCLATH ; moviw 1[FSR1] ; movwf PCL ;選択したメニュー項目処理にジャンプ ;-------------------------------------------------------------------------- ; メニュー番号別ジャンプアドレステーブル jump_addr_tbl dt high clock_display ;menu_no = 1 ;(ページ 0) dt low clock_display dt high detail_stopwatch ;menu_no = 2 ;(ページ 1) dt low detail_stopwatch dt high interval_timer ;menu_no = 3 ;(ページ 1) dt low interval_timer dt high long_stopwatch ;menu_no = 4 ;(ページ 1) dt low long_stopwatch dt high memory_display ;menu_no = 5 ;(ページ 1) dt low memory_display dt high wk_timer_setting ;menu_no = 6 ;(ページ 1) dt low wk_timer_setting dt high wk_timer_display ;menu_no = 7 ;(ページ 1) dt low wk_timer_display dt high clock_adjust ;menu_no = 8 ;(ページ 0) dt low clock_adjust dt high sleep_time_set ;menu_no = 9 ;(ページ 1) dt low sleep_time_set jump_addr_tbl_endPIC16F1827 ではプログラムメモリが ページ 0 〜 1 の 2 ページしかありませんが、もっと大きなプログラムメモリ(多くのページ)を搭載した PIC であっても、ジャンプアドレステーブルに同様にジャンプアドレスを並べるだけで、 どのページにあるプログラムへも簡単に確実に処理を移す(ジャンプする)ことができます。 | プログラムのトップに戻る | ● テーブルデータ選択のプログラミングで起きた不具合 本機の 6:「週間タイマーの時刻設定」機能 のプログラミングもほぼ終わって、試験実行を行ったときのお話です。「メニューの選択」機能で本機能を選択後に EXIT スイッチを押した途端、次の LCD 画面が表示されることを期待していたのですが、 予想に反していきなりの暴走で LCD 画面には意味不明な文字が表示されました。 [ Set EDAY ] ..... 右側に曜日 (略名) を表示 [ 00:00 0 ] ..... "HH:MM"、"曜日を表す数字" を設定デバッグ中にはこんなことはよくあることなんですが、やはり少なからずショックを受けるのが正直な気持ちです。 なぜ? どうして? ・・・ プログラミングもほぼ万全な気持ちで終えて試験実行に臨むわけで、いきなり予想に反して暴走するとなると、 オーバーに言えば、自分のプログラミングが否定されたような気持にさえなります。 そのときの関連する部分のリストを次に示します(部分リストのため分かりづらいかもしれませんが、できれば ソースファイル (MultifuncDigitalClock.asm) を参照してください)が、不具合なところはどこなのか? 初めはまったく見当も付きません。 あらゆるところを疑ってリストを何度も何度も見直しますが、リストを見ているだけでは、そんなに簡単には不具合な場所を見つけることはできません。(なお、この 6:「週間タイマーの時刻設定」機能 wk_timer_setting は、 プログラムメモリのページ 1 に存在しています。) ;========================================================================== ; menu_no = 6 週間タイマーの時刻設定 ;========================================================================== wk_timer_setting : : : ; 「週間タイマーの時刻設定」の初期表示 ; [ Set EDAY ] ;1行目 ... "Set ", 曜日名の表示 ; [ 00:00 0 ] ;2行目 ... 時分, 曜日番号の表示 ; 01234567 movlw h'80' ;(1行目先頭) call xI2cLcd_Cmd ; movlw 0 ;文字列 = 0 call str_disp ;"Set " を表示 ..... (1) tmset01 movlw h'0c' ;表示オン/オフコントロール call xI2cLcd_Cmd ;D=1: 表示オン, C=0: カーソル非表示, B=0: 非ブリンク movlw h'c0' ;(2行目先頭) call xI2cLcd_Cmd ;時の表示位置 call xhh_disp ;時の表示 movlw ':' ;':' call xI2cLcd_Data ;I2C LCD へデータを1文字出力 call xmm_disp ;分の表示 movlw ' ' ; call xI2cLcd_Data ;I2C LCD へデータを1文字出力 movlw ' ' ; call xI2cLcd_Data ;I2C LCD へデータを1文字出力 call week_disp ;曜日番号と曜日名の表示 clrf hold_cnt ;長押し監視カウンタの初期設定 ; 各スイッチの入力チェック : : : ;-------------------------------------------------------------------------- ; 文字列の表示 str_disp movwf addr_low ;文字列番号 lslf addr_low,F ;x 2 倍 lslf addr_low,F ;x 4 倍 addwf addr_low,F ;x 5 倍 movlw high str_table ;テーブルデータの先頭 high アドレス movwf addr_high ; movlw low str_table ;テーブルデータの先頭 low アドレス addwf addr_low,F ; btfsc STATUS,C ;オーバーフローした場合は incf addr_high,F ;high アドレスの更新 call xI2cLcd_Disp ;I2C LCD へ文字列データを出力 ..... (2) return ; 文字列 データテーブル str_table dt "Set ",0 ;文字列番号 = 0 dt "Err!",0 ; 1 dt "Dsp ",0 ; 2 dt "None",0 ; 3 dt " ",0 ; 4こんなときのために MPLAB X IDE には強力なデバッグ機能を備えている(らしい)のですが、英語力の弱い私にとっては高嶺の花というか宝の持ち腐れというか、残念ながら今の私にはまともにこのデバッグ機能を使うことができません。 そこで、仕方がないので自己流の方法でデバッグを行うことになります。 疑わしい命令をコメントにしてみたり、ときには疑わしい部分の前や後にたとえば goto $ 命令を挿入して実行を止めてみたり、また、ときには疑わしい変数(レジスタ)の内容を LCD 画面上に表示させてみたりもします。 そんなこんなで、まず、上リストの赤字で示した 命令 (1) が疑わしいということが分かりました。 上リストでは結果が出ているので簡単そうに思えるかもしれませんが、あらゆるところを疑ってのことなので、赤字命令 (1) に行き着くまでにはそれなりの時間を要しています。 そして、str_disp サブルーチン内に原因があることに大まかに絞られたのですが、次には、このサブルーチン内のどこで不具合を起こしているのか? これがまた大変でした。 上リストではもう解答が出ていて赤字で示した 命令 (2) が犯人だと分かりますが(真犯人ではない)、そこに行き着くまでにはやはり時間を要しました。 str_disp サブルーチン を、前々項で示した menu_display サブルーチン と比べてみて、扱っている文字列の文字数が異なっているだけで、文字列を LCD 画面に表示させることに違いはありません。 ページ 0 に存在している menu_display サブルーチンでは、I2cLcd_Disp サブルーチンを CALL しているのに対して、ページ 1 に存在するこの str_disp サブルーチンでは、xI2cLcd_Disp サブルーチンを CALL しているのは、 xI2cLcd_Disp サブルーチンも ページ 1 に存在しているからで、ページ 1 の他の場所で次のように定義をしています。 ですから、実体は I2cLcd_Disp サブルーチンでどちらも変わりはありません。 xI2cLcd_Disp movlp high I2cLcd_Disp call I2cLcd_Disp ;I2C LCD 1行メッセージ表示 movlp high $ return なのに、なぜ xI2cLcd_Disp サブルーチンが犯人になってしまうのか? ここが一番私が悩んだところで、なかなか真の犯人に行き着くことができなかった点でした。 結論を急ぎましょう。 既に前々項で示した I2cLcd_Disp サブルーチン をここにも再掲をしておきます。 I2cLcd_Disp call tbl_read ;テーブルデータの読み出し ..... (1) incf addr_low,F ;読み出すデータの low アドレスの更新 ..... (2) btfsc STATUS,Z ;オーバーフローした場合は ..... (3) incf addr_high,F ;読み出すデータの high アドレスの更新 ..... (4) addlw 0 ; ..... (5) btfsc STATUS,Z ;h'00' か? ..... (6) goto i2cldsp01 ;Yes ..... (7) call I2cLcd_Data ;LCD へデータを1文字出力 ..... (8) goto I2cLcd_Disp ; ..... (9) i2cldsp01 return ..... (10) ; テーブルデータの1文字読み出し tbl_read movf addr_high,W ;読み出すデータの high アドレス ..... (11) movwf PCLATH ; ..... (12) movf addr_low,W ;読み出すデータの low アドレス ..... (13) movwf PCL ; ..... (14)I2cLcd_Disp サブルーチンの冒頭で call tbl_read ;テーブルデータの読み出しのように、tbl_read サブルーチンを CALL しています。 そして tbl_read サブルーチンでは、addr_high、addr_low レジスタペアの内容を、PCLATH、PCL レジスタに代入 をしています。 これが不具合の原因でした。 いや、早まってはいけません。 I2cLcd_Disp サブルーチン、tbl_read サブルーチンともに、それ自体は何の問題もありません。 現に「メニューの選択」機能においての menu_display サブルーチンでは、何の問題もなく LCD 画面に文字列を表示させています。 では、一体何が問題なのでしょうか? 表示をさせる文字列のデータテーブルそのものに注目をしてみてください。「メニューの選択」機能では、menu_display サブルーチンと同じページ 0 に、メニュー データテーブル menu_table が存在 しています。 これに対して、本機能の 6:「週間タイマーの時刻設定」機能では、str_disp サブルーチンと同じページ 1 に、文字列データテーブル str_table が存在 しています。 このことに問題があったのです。 menu_display サブルーチンでの 変数 addr_high には、ページ 0 の high アドレスが格納 され、一方、str_disp サブルーチンでの 変数 addr_high には、ページ 1 の high アドレスが格納 されて、 同じ実体である I2cLcd_Disp サブルーチンを CALL しています。 上述したように、MPLAB X IDE のデバッグ機能を使うことができないので、プログラムを実際にトレースすることができないため、あまり正確ではない? かもしれない、私の頭の中でトレースをして行くことにしましよう。 menu_display サブルーチンでの動作には特に問題がないので、とりあえずトレースはパスをし、そして、問題の str_disp サブルーチンの場合をトレース してみます。 また、I2cLcd_Disp サブルーチンに処理が移ってから問題が発生しているはず? なので、I2cLcd_Disp サブルーチン内をトレースしますが、その前提条件を確認しておきます。 本項の始めに示した リスト 中の赤字の命令 (1) のコメントが示すように、文字列 データテーブル str_table の1行目 dt "Set ",0 ;文字列番号 = 0が表示対象となっている文字列で、その内の最初の1文字目 'S' のメモリアドレスが、str_disp サブルーチン によって addr_high、addr_low レジスタペアに代入され、文字 'S' がポイントされています。 そして、トレースにはすぐ上に示した I2cLcd_Disp サブルーチン 内の、各命令の右端に付した黒字番号 (1) 〜 (14) と、次のリストの番号 (15) を使用しながら説明をします。 なお、文字列を定義している部分の DT 命令はアセンブラ疑似命令で retlw 'S' ; ..... (15) retlw 'e' retlw 't' retlw ' ' retlw 0と書いたのと同等になります。 RETLW 命令では1行に1文字(バイト)の定義しかできないので、本項のデータテーブルのように文字列の複数文字を1度に定義したい場合には、通常は DT 疑似命令を使用します。 ちなみに、各文字列の最後に 0 を定義しているのは、 文字列を表示する I2cLcd_Disp サブルーチンが、この 0 を検出することによって文字列の終了としているためで、もし、0 を付け加えるのを忘れると、I2cLcd_Disp サブルーチンは 0 を検出するまでプログラムメモリ内を駆け巡るので、いわゆる暴走状態になります。 さて、I2cLcd_Disp サブルーチンに処理が移ってくると、いきなり 命令 (1) で tbl_read サブルーチンを CALL しています。 このように、これ以降 CALL 命令と GOTO 命令が現れたときに特に注目をしてください。 この場合には、上で示した xI2cLcd_Disp サブルーチン 内で movlp high I2cLcd_Disp 命令によって、PCLATH レジスタにはページ 0 の high アドレスが格納 されているので、左下の図にしたがって問題なく tbl_read サブルーチンが CALL されます。 すると右上の図にしたがって、ページ 1 に存在している 文字列データテーブル str_table の1行目の1文字目、すなわち、命令 (15) に処理が移ります。 そして、(15) の retlw 'S' 命令 では、 'S' 文字を W レジスタに設定した後、スタックに確保しておいた戻りアドレスをプログラムカウンタ(PC)に POP をするので、問題なく I2cLcd_Disp サブルーチン内の命令 (2) に戻ってきます。 その後、命令 (3) → (4) → (5) → (6) と処理は進み、次に (7) で GOTO 命令が現れますが、W レジスタには 'S' 文字が設定されていて h'00' ではないため、処理は次の (8) call I2cLcd_Data 命令 へと進みます。 I2cLcd_Data サブルーチンは I2cLcd_Disp サブルーチンとともに、インクルードファイル I2C LCD 制御サブルーチン (I2C_S2_LCD.sub) 内に収容されているので、その存在はページ 0 にあります。 ところがこの時点では 命令 (11) (12) によって、PCLATH レジスタには ページ 1 の high アドレスが格納 されているので、命令 (8) によって CALL をしても左上の図にしたがった動作のため、 ページ 0 に存在している I2cLcd_Data サブルーチンが CALL されることはなく、プログラムはページ 1 のメモリ内をさまよう(暴走する)ことになります。 このように、上述した 赤字で示した命令 (2) の xI2cLcd_Disp(または I2cLcd_Disp)サブルーチンそのものが不具合の原因(犯人)ではなく、文字列データテーブル str_table が存在しているメモリ位置 が原因(真犯人)であったのです。 この真犯人を見つけるまでには、恥ずかしながら数日がかかってしまいました。 また、このメモリ位置が真犯人であることの事実を証明するために、試しに、現在ページ 1 に存在している文字列データテーブル str_table を、ページ 0 に移動をさせてみました。 結果、問題なく文字列 "Set " が LCD 画面上に表示されたことを、 ここに付け加えておきます。 *(以下は余談なので、項末の 最終的な方法 の前まで読み飛ばしてください。) ただ、このままで済ませてしまうと、str_disp サブルーチン から str_table が消えてしまって(ページ 0 に移動)、プログラムの見通しが悪くなるので、実際には次に示すリストのように、新たに I2cLcd_Disp2 サブルーチンを作成して、 赤字で示した命令 (3) のように str_disp サブルーチンを書き直しました。 ;-------------------------------------------------------------------------- ; 文字列の表示 str_disp movwf addr_low ;文字列番号 lslf addr_low,F ;x 2 倍 lslf addr_low,F ;x 4 倍 addwf addr_low,F ;x 5 倍 movlw high str_table ;テーブルデータの先頭 high アドレス movwf addr_high ; movlw low str_table ;テーブルデータの先頭 low アドレス addwf addr_low,F ; btfsc STATUS,C ;オーバーフローした場合は incf addr_high,F ;high アドレスの更新 ;; call xI2cLcd_Disp ;I2C LCD へ文字列データを出力 call I2cLcd_Disp2 ;I2C LCD へ文字列データを出力 ..... (3) return ; 文字列 データテーブル str_table dt "Set ",0 ;文字列番号 = 0 dt "Err!",0 ; 1 dt "Dsp ",0 ; 2 dt "None",0 ; 3 dt " ",0 ; 4 ;-------------------------------------------------------------------------- I2cLcd_Disp2 movlp high tbl_read call tbl_read ;テーブルデータの読み出し movlp high $ addlw 0 btfsc STATUS,Z ;h'00' か? goto i2cldsp21 ;Yes call xI2cLcd_Data ;LCD へデータを1文字出力 incf addr_low,F ;読み出すデータの low アドレスの更新 btfsc STATUS,Z ;オーバーフローした場合は incf addr_high,F ;読み出すデータの high アドレスの更新 goto I2cLcd_Disp2 i2cldsp21 returnそもそも、私が文字列の表示 str_disp サブルーチンを作成した目的は、同一のサブルーチンでいろいろな文字列を表示させることと、究極は使用するプログラムメモリの量を如何に削減するかにありました。 1つ目の目的では、現在5種類の文字列だけが表示対象で、 あまり利用価値がないともいえますし、2つ目の目的でいえば新たに I2cLcd_Disp2 サブルーチンを作成したことは、メモリ量が増加してまったく理にかなっておりません。 そのため、たとえば次に示すように movlw 'S' ; call xI2cLcd_Data ;I2C LCD へデータを1文字出力 movlw 'e' ; call xI2cLcd_Data ;I2C LCD へデータを1文字出力 movlw 't' ; call xI2cLcd_Data ;I2C LCD へデータを1文字出力 movlw ' ' ; call xI2cLcd_Data ;I2C LCD へデータを1文字出力 ;; movlw 0 ; ;; call xI2cLcd_Data ;I2C LCD へデータを1文字出力と、ベタで並べた方がシンプルで、またシンプル故にこの項で紹介したような不具合なども、入り込む余地などはまったくなくて良いのかもしれません。 次々項でも述べることですが、本機で採用した PIC16F1827 のプログラムメモリは、4K ワードと少ないにもかかわらず多くの機能を盛り込んだため、プログラムの作成期間中の後半では、プログラムメモリの使用量を如何に少なくするか、ばかりに気を取られていました。 ある意味では、そのために発生した不具合ともいえる経験でした。 不具合を回避するための最終的な方法 その後、上述した、新たに I2cLcd_Disp2 サブルーチンを作成して str_disp サブルーチンを書き直す方法は、あまり良策ではないと反省をし他の方法を考えてみました。 本来は、既成のサブルーチンファイルにはあまり手を加えたくないのですが、 よりベターと思い直し、インクルードファイル I2C LCD 制御サブルーチン (I2C_S2_LCD.sub) 内に収容されている、I2cLcd_Disp サブルーチンだけを書き直す ように改めました。 上述した I2cLcd_Disp サブルーチン の、命令 (1) と (2) の間(または、命令 (4) と (5) の間のどちらでも可)に、次の命令群を追加挿入しました。 #ifdef _PIC16F1 movlp high $ #else movwf wt1cnt ;ワーク変数へ退避 movlw high $ movwf PCLATH ; movf wt1cnt,W ;ワーク変数から復帰 #endifこのように書き直しておけば、PIC16F1 ファミリー、PIC16F ファミリーのどちらにも対応することができます。 上の追加挿入リストの #else 以降、すなわち PIC16F ファミリーの場合には、ワーク変数 wt1cnt を使用していますが、本来 wt1cnt は、同サブルーチンファイル (I2C_S2_LCD.sub) 内の、26.3μ秒 ウェイトルーチン wait_26us サブルーチンの中で使用している変数で、上の追加挿入リスト部分を実行時点では未使用となるため、追加挿入した命令群の中でワークとして使用しました。 したがって、上述した str_disp サブルーチン を書き直す方法はボツとし、初めに示したリストの中の 赤字で示した (2) 行目 の call xI2cLcd_Disp 命令も、変更はしないでそのままとします。 I2C LCD 制御サブルーチンファイル (I2C_S2_LCD.sub) 内の、I2cLcd_Disp サブルーチンだけを次のように書き改めました。( 最新版 )です。 ;========================================================================== ; I2C LCD 1行メッセージ表示 ;========================================================================== ;使用レジスタ: addr_low, addr_high, wt1cnt ;<使用例> movlw h'80' ;表示位置の DDRAM のアドレスをセット ; call I2cLcd_Cmd ;LCD へコマンドを出力 ; movlw high st_msg ;メッセージデータの先頭アドレスの high アドレス ; movwf addr_high ; ; movlw low st_msg ;メッセージデータの先頭アドレスの low アドレス ; movwf addr_low ; ; call I2cLcd_Disp ;メッセージデータ1行表示 ; : ;st_msg dt "StpWatch",0 I2cLcd_Disp call tbl_read ;テーブルデータの読み出し incf addr_low,F ;読み出すデータの low アドレスの更新 btfsc STATUS,Z ;オーバーフローした場合は incf addr_high,F ;読み出すデータの high アドレスの更新 #ifdef _PIC16F1 movlp high $ #else movwf wt1cnt ;ワーク変数へ退避 movlw high $ movwf PCLATH ; movf wt1cnt,W ;ワーク変数から復帰 #endif addlw 0 btfsc STATUS,Z ;h'00' か? goto i2cldsp01 ;Yes call I2cLcd_Data ;LCD へデータを1文字出力 goto I2cLcd_Disp i2cldsp01 return ; テーブルデータの1文字読み出し tbl_read movf addr_high,W ;読み出すデータの high アドレス movwf PCLATH movf addr_low,W ;読み出すデータの low アドレス movwf PCL ● 割り込み処理のプログラミングで起きた不具合 前項と同様 6:「週間タイマーの時刻設定」機能 のプログラミングをしていたときの続きのお話です。 前項で述べた不具合もようやく解決をして、次に示す初期画面も問題なく表示がされるようになりました。 そして次には、2行目に "HH:MM"、 "曜日を表す数字" を入力してタイマー時刻を設定します。(設定方法は 「週間タイマーの時刻設定」機能 を参照) [ Set EDAY ] ..... 右側に曜日 (略名) を表示 [ 00:00 0 ] ..... "HH:MM"、"曜日を表す数字" を設定LEFT、RIGHT、DOWN、UP の各スイッチを使用してタイマー情報を入力するのですが、まったく問題なく入力ができそれらの LCD 画面への表示についても、見掛け上は何の問題もありません。 後は EXIT スイッチを押して、タイマー用メモリへ情報を書き込むだけです。 本機では、20 個(件)までのタイマー情報を設定することができ、連続して入力することができるようにしてあります。 当初、1件ずつのタイマー情報を書き込む度に、次の入力に備えて [ 00:00 0 ] のように 0 クリアをしていたのですが、前回入力をした情報の一部が再利用できるようにした方がより便利 ― と考え直し、0 クリアするのを止めて前回の情報が表示されたままになるように、 プログラムを改めたのです。 その結果、とんでもないことが起こりました。 EXIT スイッチを押した瞬間、今まで入力をして表示がされていた内容とは、まったく異なった内容が表示されるようになってしまいました。 えっ !! ― という驚きしかありません。 なぜ? どうして? これがまた、前項で述べた不具合に続いて 新たな不具合との長い戦いの始まりになりました。 しかし、表示された内容をよーく見てみると、どうやら現在時刻(時分)のようでもあります。 なぜ、ここに現在時刻(時分)が現れるのかまったく分かりません。 本「週間タイマーの時刻設定」機能では、現在時刻は不要なのでプログラムで扱ってはいないのです。 本来、現在時刻のための変数 bcd_hh、bcd_mm、bcd_ww を、本機能でも使用しました。 それは、既存の bcd_hh_inc、bcd_hh_dec、bcd_mm_inc、bcd_mm_dec、hh_disp、mm_disp など、多くのサブルーチンがそっくり使用できるため、あえて本機能用の新たな変数を設けないで、 本機能を実行中には使用されることがない(はずの)、現在時刻用の変数 bcd_hh、bcd_mm、bcd_ww を使用したのです。 ところが、タイマー用メモリへ情報を書き込むために EXIT スイッチを押した瞬間、自分が入力設定をした bcd_hh、bcd_mm、bcd_ww の内容が、現在時刻の内容に置き換わってタイマー用メモリへ書き込まれ、LCD 画面への表示もその現在時刻の内容に変更されてしまうのです。 本機では、プログラムで現在時刻を扱っているのは、1:「現在時刻の表示」機能 、4:「ストップウオッチ(長時間)」機能 、「5:記録内容の表示」機能 の3機能についてだけで、 それらでは、INT 外部割り込みを使用して RTC モジュール DS3231 から1秒ごとに割り込みが掛かって、I2C 通信で現在時刻を得ています。 したがって、上記の3機能についてだけ INT 外部割り込みを許可し、6:「週間タイマーの時刻設定」機能を始め、それ以外では INT 外部割り込みは禁止をしています。 それにも拘らず現実には bcd_hh、bcd_mm、bcd_ww の内容が、自分が入力した設定値から RTC モジュール からの読み出し値に更新がされてしまう ― という事実。 この不具合が起こっているときの本機の "割り込み処理ルーチン(interrupt)" の全リストを、次に示しておきます。 このように本機では、4種類の割り込みを使用しています。 ;========================================================================== ; 割り込み処理 ;========================================================================== org h'0004' ;割り込みベクタ interrupt movlp high $ ;ページ 0 movlb 0 ;バンク 0 ; 割り込み処理の振り分け btfsc INTCON,INTF ;INT 外部割り込みフラグ = 1 か? goto int_int ;Yes btfsc INTCON,TMR0IF ;TMR0 オーバーフロー割り込みフラグ = 1 か? goto int_tmr0 ;Yes btfsc PIR1,TMR1IF ;TMR1 オーバーフロー割り込みフラグ = 1 か? goto int_tmr1 ;Yes btfsc INTCON,IOCIF ;状態変化割り込みフラグ = 1 か? goto int_ioc ;Yes goto int_end ;(Illegal interrupt) ;---------------- INT 外部割り込み処理 ------------------------------------ int_int bcf INTCON,INTF ;INT 外部割り込みフラグ = 0 ; 現在時刻の管理 call clock_rtc_read ;RTC DS3231 から年月日、時分秒を読み出し ..... (1) movlp high time_alarm_control call time_alarm_control ;時報音、タイマー一致音フラグコントロール movlp high $ ; スリープ移行時間カウンタの更新 btfsc stat2_flg,1 ;スリープ中状態フラグ = 1 か? goto inti01 ;Yes decf slp_cnt_l,F ;slp_cnt_l - 1 incf slp_cnt_l,W ; btfsc STATUS,Z ;slp_cnt_l - 1 < 0 か? decf slp_cnt_h,F ;Yes. slp_cnt_h - 1 bsf stat2_flg,0 ;スリープ移行時間チェックフラグ = 1 inti01 movlw 4 ;4:「ストップウオッチ(長時間)」 subwf menu_no,W ; btfss STATUS,Z ;メニュー番号 = 4 か? goto inti03 ;No ; 経過時間(スプリットタイム)の管理 btfsc stat_flg,3 ;ストップウオッチ動作終了フラグ = 1 か? goto inti02 ;Yes btfss stat_flg,2 ;ストップウオッチ動作中フラグ = 1 か? goto inti03 ;No movlp high ssmmhh_inc call ssmmhh_inc ;秒、分、時_BCD カウンタのインクリメント movlp high $ bsf stat_flg,1 ;経過時間更新フラグ = 1 ; ラップタイム表示時間の監視(5 秒間) inti02 btfsc stat_flg,4 ;ラップタイム表示指示フラグ = 1 か? decfsz laptim_cnt,F ;Yes. ラップタイム表示時間カウンタ - 1 = 0 か? goto inti03 ;No bcf stat_flg,4 ;ラップタイム表示指示フラグ = 0 inti03 goto int_end ;---------------- TMR0 オーバーフロー割り込み処理 ------------------------- int_tmr0 bcf INTCON,TMR0IF ;TMR0 オーバーフロー割り込みフラグ = 0 movlw 256 - 250 ;(4/4MHz) * 250 * 256(Prescaler) = 64000μS = 64 mS movwf TMR0 incf hold_cnt,F ;長押し監視カウンタ + 1 movlw 47 + 1 ;64ms x 47 = 3,008 mS = 3 秒間の監視 subwf hold_cnt,W btfsc STATUS,C ;長押し監視カウンタ >= 47 + 1 か? bcf INTCON,TMR0IE ;Yes. TMR0 オーバーフロー割り込み許可 = 0 goto int_end ;---------------- TMR1 オーバーフロー割り込み処理 ------------------------- int_tmr1 bcf PIR1,TMR1IF ;TMR1 オーバーフロー割り込みフラグ = 0 btfsc alm_flg,7 ;アラーム音出力フラグ = 1 か? goto int1t02 ;Yes ; 「ストップウオッチ(詳細)」/「インターバル(間隔)タイマー」のとき movlw high basic_const ;(4 / 4MHz) * 10000 = 0.01 S = 10 mS movwf TMR1H ;TMR1H に設定 movlw low basic_const ; movwf TMR1L ;TMR1L に設定 movlw 2 subwf menu_no,W btfss STATUS,Z ;メニュー番号 = 2:「ストップウオッチ(詳細)」か? goto int1t01 ;No ; メニュー番号 = 2:「ストップウオッチ(詳細)」のとき bsf stat2_flg,3 ;1/100 秒更新フラグ = 1 call xxssmmhh_inc ;Yes. 1/100秒、秒、分、時_BCD カウンタのインクリメント goto int1t03 ; メニュー番号 = 3:「インターバル(間隔)タイマー」のとき int1t01 decfsz bsc_cnt,F ;1 秒作成カウンタ bsc_cnt - 1 = 0 か? goto int1t03 ;No movlw 100 movwf bsc_cnt ;10 mS * 100 = 1000 mS = 1 Sec bsf stat2_flg,3 ;1 秒更新フラグ = 1 (1/100 秒と兼用) call ssmmhh_dec ;秒、分、時_BCD カウンタのデクリメント movf bcd_ss_w,W iorwf bcd_mm_w,W iorwf bcd_hh_w,W btfss STATUS,Z ;3つすべての BCD カウンタ = h'00' か? goto int1t03 ;No movlb 1 ;バンク 1 bcf PIE1,TMR1IE ;TMR1 オーバーフロー割り込み許可 = 0 movlb 0 ;バンク 0 bsf stat2_flg,4 ;タイムアウトフラグ = 1 goto int1t03 ; ビープ音出力のとき int1t02 movf const_high,W ;音階用分周定数 high を movwf TMR1H ;TMR1H に設定 movf const_low,W ;音階用分周定数 low を movwf TMR1L ;TMR1L に設定 movlw 1 << _BZ ;ブザー音出力, 対象ビットをセット xorwf PORTA,F ;出力をビット反転 int1t03 goto int_end ;---------------- 状態変化割り込み処理 ------------------------------------ int_ioc movlb 7 ;バンク 7 clrf IOCBF ;状態変化割り込みフラグをクリアすると ;INTCON レジスタの IOCIF ビットもクリアされる movlb 0 ;バンク 0 bcf INTCON,IOCIE ;状態変化割り込み許可 = 0 bsf stat2_flg,1 ;状態変化割り込み記憶フラグ = 1 ; goto int_end ; 割り込み処理の終了 int_end retfie ;割り込みからの復帰EXIT スイッチを押した瞬間に不具合が発生するということから、"EXIT スイッチの処理サブルーチン(tim_exit_switch)" も次に示しておきます。 EXIT スイッチ処理では、通常の "ちょん押し" と3秒以上の "長押し" を区別するために、プログラムでは TMR0 オーバーフロー割り込みを使用して、 3秒間以上の時間監視を行っています。 ;-------------------------------------------------------------------------- ; EXIT スイッチの処理 ...「週間タイマーの時刻設定」から CALL ;-------------------------------------------------------------------------- ; 3 (64ms x 47 = 3,008 mS) 秒間以上 長押しの監視 ; [スイッチ操作] ; EXIT スイッチ ..... タイマー用メモリに書き込み、次の設定に移る ; EXIT スイッチ 長押し ..... 「メニューの選択」に戻る (3秒以上の長押し) tim_exit_switch btfss PORTB,_swEXT ;EXIT スイッチ = ON か? goto tmext01 ;Yes movf hold_cnt,F btfsc STATUS,Z ;長押し監視カウンタ = 0 か? goto tmext04 ;Yes ; (監視時間になる前に スイッチを OFF にした) bcf INTCON,TMR0IE ;TMR0 オーバーフロー割り込み許可 = 0 clrf hold_cnt ;長押し監視カウンタの初期設定 bsf stat_flg,7 ;; ;メモリ書き込み指示フラグ = 1 goto tmext03 tmext01 movf hold_cnt,F btfss STATUS,Z ;長押し監視カウンタ = 0 か? goto tmext02 ;No incf hold_cnt,F ;長押し監視カウンタ = 1 始まり movlw 256 - 250 ;(4/4MHz) * 250 * 256(Prescaler) = 64000μS = 64 mS movwf TMR0 bsf INTCON,TMR0IE ;TMR0 オーバーフロー割り込み許可 = 1 goto tmext03 tmext02 btfsc INTCON,TMR0IE ;TMR0 オーバーフロー割り込み許可 = 1 か? goto tmext04 ;Yes ; (時間監視を終了しても スイッチが ON のとき) btfss PORTB,_swEXT ;EXIT スイッチ = OFF か? goto $ - 1 ;No bsf stat2_flg,7 ;; ;処理終了フラグ = 1 tmext03 call xwait_15ms ;チャッタリング吸収 tmext04 return上に示した2つの抜粋リストから不具合が発生する原因が分かりますでしょうか? ここまで絞ってきても私にはまだ "これだ" という解答が出ていません。 そこで、本機能のときには、割り込み処理ルーチン内の (1) の命令が実行されないようにと、 ;---------------- INT 外部割り込み処理 ------------------------------------ int_int bcf INTCON,INTF ;INT 外部割り込みフラグ = 0 movlw 6 ;; subwf menu_no,W ;; btfsc STATUS,Z ;; ;メニュー番号 = 6 か? goto inti03 ;; ;Yes ; 現在時刻の管理 call clock_rtc_read ;RTC DS3231 から年月日、時分秒を読み出し ..... (1) : : : inti03 goto int_endのように、仮に書き直した後では EXIT スイッチを押しても、間違いなく (1) の命令は実行されないことが証明されました。 ということは逆に、本 6:「週間タイマーの時刻設定」機能の実行時に INT 外部割り込みを禁止していても、INT 外部割り込みが実行されてしまうことが確定したのです。 それではなぜこのようなことが起こるのでしょうか? ここでようやく過去にもこれに似た経験をしたことを思い出しました。 "189. ドットマトリクス 8 x 8 LED 表示時計 II ( MAX7219 版)" の 割り込み動作の再確認 で触れたことと同様ですが、再度、次に述べてみます。 PIC16F1827 のデータシート内の INTCON レジスタの項を改めて見てみると、次のように Note: が書かれています。 英語表記のデータシートのためにいつもあまり注意をしないでスルーをしているのですが、ここには大変重要なことが書かれています。 : : ; 割り込み処理の振り分け btfsc INTCON,INTF ;INT 外部割り込みフラグ = 1 か? goto int_int ;Yes btfsc INTCON,TMR0IF ;TMR0 オーバーフロー割り込みフラグ = 1 か? goto int_tmr0 ;Yes btfsc PIR1,TMR1IF ;TMR1 オーバーフロー割り込みフラグ = 1 か? goto int_tmr1 ;Yes btfsc INTCON,IOCIF ;状態変化割り込みフラグ = 1 か? goto int_ioc ;Yes goto int_end ;(Illegal interrupt) : :のように書かれており、INT 外部割り込みは TMR0 オーバーフロー割り込みよりも優先順位が高くなっています。 また、回路図 を見ていただくと、RTC モジュール DS3231 の SQW 信号(1秒ごと)が PIC の RB0 端子、すなわち INT 外部割り込み入力端子に接続がされており、"Google 翻訳" で翻訳された赤色の線を引いた部分の通り、 INT 外部割り込みが禁止(INTE = 0)されていても、INT 外部割り込みフラグは1秒ごとにセット(INTF = 1) されます。 しかし、INT 外部割り込みが禁止(INTE = 0)されているために、このフラグ(INTF = 1)による 割り込みは起こりません。 一方、EXIT スイッチが押されると、EXIT スイッチの処理サブルーチン(tim_exit_switch) で示したように、64 mS ごとに TMR0 オーバーフロー割り込み が起こります。 しかし、上の "割り込み処理の振り分け" リストで示したように、TMR0 オーバーフロー割り込みよりも INT 外部割り込み の方が優先順位が高く プログラミングがされているため、初めの TMR0 オーバーフロー割り込みのときには、TMR0 オーバーフロー割り込みの処理ルーチンは実行されずに、 先に INT 外部割り込みの処理ルーチンが実行されてしまいます。 その結果、割り込み処理ルーチン内の (1) の命令が実行されて、RTC DS3231 から年月日、時分秒が読み出され、本機能で問題となっている変数 bcd_hh、bcd_mm、bcd_ww の内容が置き換わってしまう ― という現象が起こったのです。 ちなみに、(1) の命令の サブルーチン clock_rtc_read は、本機のプログラム内の他の場所で次のように設定がされています。 また、Rtc_Read_M は私が作成した マクロ命令で、詳細は 現在の最新バージョン の "I2C RTC(DS3231/1307)モジュール 制御サブルーチン (I2C_RTCII.sub)" を参照してください。 ;-------------------------------------------------------------------------- ; RTC DS3231 から年月日、時分秒を読み出し ;-------------------------------------------------------------------------- clock_rtc_read Rtc_Read_M seconds,7 ;RTC DS3231 から年月日、時分秒を読み出し call buffer_to_counter bsf stat_flg,0 ;現在時刻更新フラグ = 1 return ;-------------------------------------------------------------------------- ; バッファと_BCD カウンタ間のデータ移動 ;-------------------------------------------------------------------------- ; バッファからカウンタへコピー buffer_to_counter movlw high seconds ;RTC バッファの先頭アドレス movwf FSR1H ; movlw low seconds ; movwf FSR1L ; moviw FSR1++ ;Seconds movwf bcd_ss ;秒_BCD カウンタ moviw FSR1++ ;Minutes movwf bcd_mm ;分_BCD カウンタ moviw FSR1++ ;Hours movwf bcd_hh ;時_BCD カウンタ moviw FSR1++ ;Day movwf bcd_ww ;曜日_BCD カウンタ moviw FSR1++ ;Date movwf bcd_dd ;日_BCD カウンタ moviw FSR1++ ;Month andlw h'1f' ; movwf bcd_nn ;月_BCD カウンタ moviw FSR1++ ;Year movwf bcd_yy ;年_BCD カウンタ return結論として、今回の不具合の回避策では、上掲した 割り込み処理ルーチン(interrupt) の "割り込み処理の振り分け" 部分を、次に示すリストのように変更をしました。 ;========================================================================== ; 割り込み処理 ;========================================================================== org h'0004' ;割り込みベクタ interrupt movlp high $ ;ページ 0 movlb 0 ;バンク 0 ; 割り込み処理の振り分け ;; btfsc INTCON,INTF ;INT 外部割り込みフラグ = 1 か? ;; goto int_int ;Yes btfsc INTCON,TMR0IF ;TMR0 オーバーフロー割り込みフラグ = 1 か? goto int_tmr0 ;Yes btfsc PIR1,TMR1IF ;TMR1 オーバーフロー割り込みフラグ = 1 か? goto int_tmr1 ;Yes btfsc INTCON,IOCIF ;状態変化割り込みフラグ = 1 か? goto int_ioc ;Yes btfsc INTCON,INTF ;; ;INT 外部割り込みフラグ = 1 か? goto int_int ;; ;Yes goto int_end ;(Illegal interrupt) : :ところが ・・・・・ これでこの件は解決 ― と一時は思ったのですが、また別の事象が現れました。 上記のリストのように順位を入れ替えた結果、"6:「週間タイマーの時刻設定」機能" への上述した影響はなくなったのですが、次の事象、チッ、チッ、チッ、チッ、 という、まさに昔のゼンマイ(懐中)時計の秒針が発するような音が、(小さいですが)出るようになったのです。 時計である本機としては、これはこれで良い ― という解釈もあるのですが、四六時中鳴っている訳でやはり耳障りだ ― という感情の方が強くなります。 そこで、次のように4通りの順位に入れ替えてみたのですが、どの順位にしても、メニュー 6 への影響、またはチッチッ音 のどちらかの事象が現れるので、結局、順位の入れ替えは止めにして 元の (a) に戻し ました。 そして、メニュー 6 への影響を避けるため、 上述 した (1) の命令が実行されない方法 にとりあえず変更をしました。 順位 メニュー6 への影響 チッチッ音 (a) 1. > btfsc INTCON,INTF 〇ある ×ない 2. btfsc INTCON,TMR0IF 3. btfsc PIR1,TMR1IF 4. btfsc INTCON,IOCIF (b) 1. btfsc INTCON,TMR0IF 2. > btfsc INTCON,INTF 〇ある ×ない 3. btfsc PIR1,TMR1IF 4. btfsc INTCON,IOCIF (c) 1. btfsc INTCON,TMR0IF 2. btfsc PIR1,TMR1IF 3. > btfsc INTCON,INTF ×ない 〇ある 4. btfsc INTCON,IOCIF (d) 1. btfsc INTCON,TMR0IF 2. btfsc PIR1,TMR1IF 3. btfsc INTCON,IOCIF 4. > btfsc INTCON,INTF ×ない 〇ある以上のように、現在は仮処置ともいえるような方法でメニュー 6 への影響を回避していますが、プログラマの私としてはやはり不本意なので、時間的余裕が十分あるときにもう一度、改めてこの件に関して検討をしてみたいと思います。 | プログラムのトップに戻る | ● コンフィギュレーションワードの設定で起きた不具合 メニュー項目 2:「ストップウオッチ(詳細)」機能と 3:「インターバル(間隔)タイマー」機能では、CLEAR スイッチを操作するのですが、当初は DOWN スイッチを CLEAR スイッチと読み替えて、同一スイッチを兼用で使用していました。 しかし、DOWN スイッチはプリント基板上の実装位置が Start / Stop スイッチから少し離れているので、兼用から独立した CLEAR スイッチを Start / Stop スイッチの近くに新たに設け、配線は左下図のように DOWN スイッチと並列にして使用していました。 2:「ストップウオッチ(詳細)」機能では DOWN スイッチ を使用しませんが、3:「インターバル(間隔)タイマー」機能では、DOWN スイッチは DOWN スイッチ の機能があり、CLEAR スイッチは CLEAR スイッチとしての機能があって、 しかし、両スイッチを使用する場面が異なるためポートを兼用にしたのですが、やはりよくありませんでした。 メニュー項目 2: や 3: ではあまり問題は起こらないのですが、その他の機能を実行中に EXIT スイッチと CLEAR スイッチを、誤って押してしまうことがしばしばあり、当然 DOWN スイッチとしての動作をしてしまいます。 __config _CONFIG1, _FOSC_XT & _WDTE_OFF & _PWRTE_ON & _MCLRE_OFF & _CP_OFF & _CPD_OFF & _BOREN_OFF & _IESO_OFF & _FCMEN_OFFまた、旧 CLEAR(DOWN)スイッチのときはポート B でしたが、新 CLEAR スイッチではポート A なので、そこらあたりのプログラムの設定替えを行ったことは言うまでもありません。 このように、ハードウエア、ソフトウエアともに変更(準備万端)して、いざ CLEAR スイッチを押した途端、 本機がリセットされてしまいました。 えっ! どうして? ・・・ 上の文のように _MCLRE_OFF と設定しているじゃないか。 解せない気持ちでいっぱいです。 なぜデジタルピンとして動作をしないのか? MCLR ピンのままなのです。 PIC16F1827 にはコンフィギュレーションワードが、次のリストに示すように 2 ワード存在します。 ;========================================================================== ; コンフィギュレーションワードの設定 ;========================================================================== __config _CONFIG1, _FOSC_XT & _WDTE_OFF & _PWRTE_ON & _MCLRE_OFF & _CP_OFF & _CPD_OFF & _BOREN_OFF & _IESO_OFF & _FCMEN_OFF __config _CONFIG2, _WRT_OFF & _PLLEN_OFF & _STVREN_OFF & _BORV_HI & _LVP_OFF ; ------------------------------------ ; CONFIGURATION WORD 1 (ADDRESS:8007H) Data Sheet DS41391B - page 50 ; ------------------------------------ ; ; CONFIG1 = 00 1001 1000 0001 ; || |||| |||| |||| ; bit2-0 || |||| |||| |+++---- FOSC<2:0>: オシレータ選択ビット ; || |||| |||| | 111 = ECH: 外部クロック、高消費電力モード(4-20 MHz): OSC1/CLKINピンに供給 ; || |||| |||| | 110 = ECM: 外部クロック、中消費電力モード(0.5-4 MHz): OSC1/CLKINピンに供給 ; || |||| |||| | 101 = ECL: 外部クロック、低消費電力モード(0-0.5 MHz): OSC1/CLKINピンに供給 ; || |||| |||| | 100 = INTOSC: 内部HFINTOSC CLKINピンはI/Oとして機能 ; || |||| |||| | 011 = EXTRC: CLKINピンに外部RC回路を接続 ; || |||| |||| | 010 = HS: OSC1とOSC2ピンの間に高速水晶/セラミック振動子を接続 ; || |||| |||| | > 001 = XT: OSC1とOSC2ピンの間に水晶/セラミック振動子を接続 ; || |||| |||| | 000 = LP: OSC1とOSC2ピンの間に低消費電力水晶振動子を接続 ; || |||| |||| | ; bit4-3 || |||| |||+ +------- WDTE<1:0>: ウォッチドッグ タイマ イネーブルビット ; || |||| ||| 11 = ON: WDTを有効 ; || |||| ||| 10 = NSLEEP: WDTを動作時は有効、スリープ中は無効 ; || |||| ||| 01 = SWDTEN: WDTをWDTCONレジスタのSWDTENビットで制御 ; || |||| ||| > 00 = OFF: WDTを無効 ; || |||| ||| _____ ; bit5 || |||| ||+---------- PWRTE: パワーアップ タイマ イネーブルビット ; || |||| || 1 = OFF: PWRTを無効 ; || |||| || > 0 = ON: PWRTを有効 ; || |||| || ; bit6 || |||| |+----------- MCLRE: MCLR/VPPピン機能選択ビット ; || |||| | LVPビット=0 の場合 ; || |||| | 1 = ON: MCLR/VPPピンの機能はMCLR、弱プルアップを有効 ; || |||| | > 0 = OFF: MCLR/VPPピンはデジタル入力、MCLRは無効、弱プルアップはWPUA3ビットで制御 ; || |||| | __ ; bit7 || |||| +------------ CP: コード保護ビット ; || |||| > 1 = OFF: プログラムメモリのコード保護を無効 ; || |||| 0 = ON: プログラムメモリのコード保護を有効 ; || |||| ___ ; bit8 || |||+-------------- CPD: データコード保護ビット ; || ||| > 1 = OFF: データメモリのコード保護を無効 ; || ||| 0 = ON: データメモリのコード保護を有効 ; || ||| ; bit10-9 || |++--------------- BOREN<1:0>: ブラウンアウト リセット イネーブルビット ; || | 11 = ON: BORを有効 ; || | 10 = NSLEEP: BORを動作時は有効、スリープ中は無効 ; || | 01 = SBODEN: BORをBORCONレジスタのSBORENビットで制御 ; || | > 00 = OFF: BORを無効 ; || | ________ ; bit11 || +----------------- CLKOUTEN: クロック出力イネーブルビット ; || FOSCビットがLP、XT、HSモードの場合 ; || > このビットを無視しCLKOUT機能を無効、CLKOUTピンはI/Oとして機能 ; || FOSCビットがその他のモードの場合 ; || 1 = OFF: CLKOUT機能を無効、CLKOUTピンはI/Oとして機能 ; || 0 = ON: CLKOUTピンのCLKOUT機能を有効 ; || ; bit12 |+------------------- IESO: 内部/外部切り換えビット ; | 1 = ON: 内部/外部切り換えモードを有効 ; | > 0 = OFF: 内部/外部切り換えモードを無効 ; | ; bit13 +-------------------- FCMEN: フェイルセーフ クロックモニタ イネーブルビット ; 1 = ON: フェイルセーフ クロックモニタと内部/外部切り換えの両方を有効 ; > 0 = OFF: フェイルセーフ クロックモニタを無効 ; ; ------------------------------------ ; CONFIGURATION WORD 2 (ADDRESS:8008H) Data Sheet DS41391B - page 52 ; ------------------------------------ ; ; CONFIG2 = 01 1000 1111 1111 ; || |||| |||| |||| ; bit1-0 || |||| |||| ||++---- WRT<1:0>: フラッシュメモリ自己書き込み保護ビット ; || |||| |||| || > 11 = OFF: 書き込み保護を無効 ; || |||| |||| || 10 = BOOT: 0000h〜01FFhを書き込み保護、0200h〜0FFFhをEECON制御で変更可能 ; || |||| |||| || 01 = HALF: 0000h〜07FFhを書き込み保護、0800h〜0FFFhをEECON制御で変更可能 ; || |||| |||| || 00 = ALL: 0000h〜0FFFhを書き込み保護、EECON制御によるアドレス変更を不可 ; || |||| |||| || ; bit3-2 || |||| |||| ++------ 未実装 ; || |||| |||| ; bit4 || |||| |||+--------- 予約 ; || |||| ||| ; bit7-5 || |||| +++---------- 未実装 ; || |||| ; bit8 || |||+-------------- PLLEN: PLLイネーブルビット ; || ||| 1 = ON: 4xPLLを有効 ; || ||| > 0 = OFF: 4xPLLを無効 ; || ||| ; bit9 || ||+--------------- STVREN: スタック オーバーフロー/アンダーフロー リセット イネーブルビット ; || || 1 = ON: スタックのオーバーフロー/アンダーフローによってリセットを発生させる ; || || > 0 = OFF: スタックのオーバーフロー/アンダーフローによってリセットを発生させない ; || || ; bit10 || |+---------------- BORV: ブラウンアウト リセット電圧選択ビット ; || | 1 = LO: ブラウンアウト リセット電圧(VBOR)のトリップポイントを低に設定 ; || | > 0 = HI: ブラウンアウト リセット電圧(VBOR)のトリップポイントを高に設定 ; || | ; bit11 || +----------------- 未実装 ; || _____ ; bit12 |+------------------- DEBUG: インサーキット デバッガモード ビット ; | > 1 = OFF: インサーキット デバッガを無効、ICSPCLKとICSPDATは汎用I/Oピンとして機能 ; | 0 = ON: インサーキット デバッガを有効、ICSPCLKとICSPDATはデバッガ専用 ; | ; bit13 +-------------------- LVP: 低電圧プログラミング イネーブルビット ; 1 = ON: 低電圧プログラミングを有効 ; > 0 = OFF: プログラミング時にMCLRに高電圧の印加が必要解答を先に出しておきます。 上(次に再掲)のリストは既に修正済みのものですが、2行目のコンフィギュレーションワード CONFIG2 の設定に問題がありました。 __config _CONFIG1, _FOSC_XT & _WDTE_OFF & _PWRTE_ON & _MCLRE_OFF & _CP_OFF & _CPD_OFF & _BOREN_OFF & _IESO_OFF & _FCMEN_OFF __config _CONFIG2, _WRT_OFF & _PLLEN_OFF & _STVREN_OFF & _BORV_HI & _LVP_OFFCONFIG2 の LVP(bit13)ビットの設定に問題があり、上リストに示すように _LVP_OFF が正解ですが、_LVP_ON と設定されていました。 プログラム の冒頭で述べたように、PIC16F1 ファミリーでのプログラミングは、"194. FMステレオラジオ III" 、 "195. ドットマトリクス LED コントローラー" に続いて本機が3作目で、このコンフィギュレーションワードの設定についてはそれらの前作をそのままコピーし、あまり深く考えもしないで異なった部分だけを修正していました。 PIC16F1827 のデータシート内の CONFIGURATION WORD 1 レジスタ(DS41391B-page 50)の項で、MCLRE(bit6)の説明だけを抜き出すと、 上述した "194. FMステレオラジオ III" 、 "195. ドットマトリクス LED コントローラー" で何の問題も起きていないのは、自分(私)ではデジタル入力ピンとしたつもりでいたのですが、実際には、どちらもこの MCLR ピンにスイッチをつないで、 デジタル入力して使用することがなかったためで、また、ICSP 用として挿入した 10 KΩ 抵抗が MCLR ピンとしてのプルアップとして機能をしているためでした。 | プログラムのトップに戻る | ● PIC16F1827 の プログラムメモリについて、など PIC16F1827 の プログラムメモリ は、ページ 0 〜 1 の 2 ページ(2K x 2 = 4K = 4,096 ワード)だけが実装 されています。 PIC16F1 ファミリーとしてはかなり少ない方だと思いますが、以前から PIC16F1827 を一度使用してみたい、 という個人的な願望もあって本機で採用してみたのですが、メモリ量としてはほぼギリギリに近い状態でした。 というのも、本機には多くの機能を盛り込んだため、その分プログラム量が増えて使用するメモリ量も多くなるわけです。 私が本機のプログラムを作成している間中、特に後半になってからは、残りのメモリ量を常に気にしながらのプログラミング作業でした。 PIC16F、PIC16F1 ファミリーでは、アセンブラ1命令 =1ワードですから、逆にいえば、4,096 ワードのプログラムメモリというのは、最大 4,096 個(行)のアセンブラ命令しか書くことができない ― ということになります。 この 4,096 行の命令数が多いかどうかは、個人によって判断が別れるとは思いますが、一般的には、アセンブラ命令でそれ程までの(大きな?)プログラムを作る人は少ない、とも思います。 ・コーディングスタイル 話が少し変わりますが、私のプログラミング(コーディング)スタイルは、ソースプログラムを見ていただくと分かるように、コメント行や空白行を多用して書いています。 空白行の使用に関して は、GOTO (または RETURN) 命令行の後 と、GOTO の飛び先の ラベルを付した命令行の前 には、例外時を除いて必ず挿入するのを基本としています。 他にも、あるまとまった命令群の前後にも挿入をしています。 そして、この空白行を挿入することで、プログラムの見通しがずば抜けて良くなります。 コメントに関しては、まず各命令行には、なるべく付けるように(努力を)しています。 コメント行に関して も、まとまった命令群(例えばサブルーチンやそれ以外の場合も多くある)の前には、これも例外時を除いて挿入するのを基本としていますし、 コメント行が複数行に亘ることもしばしばあります。 私見(自分の経験談)ですが、プログラムというものは書いている時点では集中をしていて、その内容にもよく理解をしながら書いているのですが、完成をして数か月も経つと理解をしていた内容も薄れてきて、1年も経てばすっかり忘れてしまうものです。 そして、プログラムにはバグが付きもので、理解が薄れた頃、すっかり忘れてしまった頃に、不具合が顔を出すものです。 そのときに少しでも手助けになるように、コメントは多く付ければ多いほど良い、と私は考えています。 そのための1つとして、ソースプログラムの冒頭には、プログラム全体の "機能概要" などもコメントとして付け加えています。 本機のソースプログラムでは、盛り込んだ機能が多いこともあって、この "機能概要" も極端に大きくなってしまい、ソースプログラム全体(インクルードファイルを含めて)では、何と 8,600 行を超えました。 この内、命令行は 3,840 行程度ですから、 半分以上をコメント行や空白行が占めています。 本機のプログラムは、ページ 0 とページ 1 の 2 ページに亘っていて、残っているプログラムメモリのワード数は、ページ 0 では約 140 ワードほど、ページ 1 では約 110 ワードほどで、この程度の未使用メモリを残しておかないと、 今後たぶん起こるであろう?バグの対処ができません。 ですから、あまり大きな機能追加なども今後はできません。 ・タクトスイッチのチャッタリング対策 次の図は、本機で使用しているタクトスイッチを始め、メカニカルなスイッチを ON / OFF させたときの、PIC の入力ポートに加わる電圧の変位をイメージして、図に表したものです。 ON したときも OFF したときも、どちらにもその直後にはチャタリングが現れるので、 その影響をプログラムが受けないように対処をしなければなりません。 ;-------------------------------------------------------------------------- ; LEFT スイッチの処理 ...「スリープ移行時間の変更」から CALL ;-------------------------------------------------------------------------- ; [スイッチ操作] ; LEFT スイッチ ..... ブリンク位置のポインタを - 1 ; LEFT スイッチ + ; RIGHT スイッチ ..... 「スリープ移行時間の変更」を中断し、「メニューの選択」に戻る slp_left_switch call xwait_15ms ;チャッタリング吸収 btfsc PORTB,_swLEF ;再度 LEFT スイッチ = ON か? goto sllef04 ;No bsf stat_flg,6 ;スイッチ入力有りフラグ = 1 decf blk_point,F ;ブリンク位置ポインタ - 1 incf blk_point,W movlw 2 btfsc STATUS,Z ;blk_point = -1(W=0) か? movwf blk_point ;Yes. blk_point = 2 に設定 sllef01 btfsc PORTB,_swLEF ;LEFT スイッチ = ON か? goto sllef03 ;No btfsc PORTB,_swRIG ;RIGHT スイッチ = ON か? goto sllef01 ;No bsf stat2_flg,7 ;; ;処理終了フラグ = 1 (中断終了) sllef02 comf PORTB,W ;全ビットを反転 andlw h'c0' btfss STATUS,Z ;LEFT スイッチ and RIGHT スイッチ = OFF か? goto sllef02 ;No sllef03 call xwait_15ms ;チャッタリング吸収 sllef04 returnこのウエイト時間の 15 mS が実際に妥当なのかどうかなんですが、私はこれまで PIC で有名なあるサイトの情報を参考にして、30 mS 程度のウエイト時間を使用していました。 最近になって "秋月電子" のホームページにあるタクトスイッチの データシート(cosland/ts-0606_20200714.pdf)を改めて見る機会があり、Page. 1/5 で次の図を見つけました。 ・ストップウオッチ機能(詳細測定、長時間測定)の精度 本機では、詳細測定と長時間測定との目的を別とした、2種類のストップウオッチ機能を有しています。 まず、後者の長時間測定を目的とする 4:「ストップウオッチ(長時間)」機能 では、本機の時計機能で使用した リアルタイムクロックモジュール(RTC IC DS3231)による、1秒ごとのパルスを基準クロックに使用をしているため、その精度には信頼に値するものがありますが、逆に1秒以下の測定はできないという弱点があって、最大1秒の表示誤差が生じます。 次に、前者の詳細測定を目的とする 2:「ストップウオッチ(詳細)」機能 では、本機 PIC の基準クロック ( 1 / 4MHz ) x 4 = 1 μS を、10000 回カウントして TMR1 オーバーフロー割り込みを発生させ、 すなわち 1 μS x 10000 = 1 / 100 S = 0.01 S = 10 mS を 作り出しています。 しかし、ここで問題があります。 本機では、上記 回路図 のように 4 MHz のクリスタルを使用していますが、私の今までの経験上、PIC のオシレータ入力に繋がっているクリスタルが、必ずしも 4 MHz ぴったりという正確な値では発振をしてくれないために、 その基準クロックも正確な 1 μS とはなりません。 これを 4 MHz ぴったりに発振をさせるためには、上記回路図で示すクリスタルの右隣りにある2個のコンデンサの値を調整して、ハードウエア的に発振周波数を合わせ込む必要があるのですが、トリマコンデンサに交換をしたり正確な周波数カウンタも必要になったりと、 現実的には結構面倒な作業になります。 そこで私の場合には、ハードウエア的な方法は採らず(したがって、発振周波数はズレたままで)、次に示すように、プログラム中に記した補正値 xx の値を変更することで、基準となるクロックが 10 mS に近づくように調整をしています。 xx equ -33 ;補正値 basic_const equ 65536 - (10000 + xx) ;(4 / 4MHz) * 10000 = 0.01 S = 10 mS発振周波数が 4 MHz より低い場合、すなわち 10 mS より大きくなった場合には、補正値 xx をマイナス値にしてカウント数を 10000 よりも少なくし、また逆に、発振周波数が 4 MHz より高い場合、すなわち 10 mS より小さくなった場合には、 補正値 xx をプラス値にしてカウント数を 10000 よりも多くして、ソフトウエアによる調整をしています。 上に示した補正値 xx equ -33 は今回私が製作をした本機での例で、発振周波数は使用する PIC やクリスタルの個体差によって異なってくるため、より精度を求める場合は、実際に製作をしたそれぞれの個体に合うように補正をする必要があり、 その場合には、当然プログラムのアセンブルもし直す必要があります。 この基準クロック 10 mS そのものを測定するのは現実的ではないため、実際には一定時間(たとえば 10 分、またはそれ以上)に拡大した時間を測定しています。 下に示す表は、私が製作をした本機の例で、カウント数に補正がなかったとき(xx = 0)や、補正をしたとき(xx = -32 〜 -34)の実際の実測値をまとめたものです。 測定に使用した機材は、私の別ページ "145. 長時間ストップウオッチ/タイマー" で紹介をしたもので、右下の写真や図のように接続をします。 このように、下表の測定時間欄の値は "145. 長時間ストップウオッチ/タイマー" で、本機が一定時間(10 分、または 60 分)出力する電気信号* の長さを測定したものです。
補正値 xx = 0 のときの設定時間 = 10 分というのは、本機としては 10 分間( 10'00"00 ) "L" 信号を出力しているつもりでも、"145. 長時間ストップウオッチ/タイマー" で測定すると、実際には 10'02"00 となり、+ 02"00 の誤差がある ― ということで、 10000 : X = 10'00"00 : 10'02"00 = 600.00 : 602.00 = 600 : 602の関係が成り立ちます。 ただし、= の左辺は 10 mS 当たりのカウント回数、右辺は 10 分に拡大した測定時間の秒数を表し、また、: の左側は理想とする数値、右側は実際の測定値 ― を表しています。 そこで X の値を求めると X = 10000 * 602 / 600 = 10033.333… ≒ 10033したがって、カウント回数の誤差は 10033 - 10000 = 33となり、この誤差分を補正値として実際のカウント数を 10000 から引いて少なくしてやります。 すなわち 上述 したように xx equ -33 ;補正値 basic_const equ 65536 - (10000 + xx) ;(4 / 4MHz) * 10000 = 0.01 S = 10 mSとして、TMR1 オーバーフロー割り込みの発生間隔を誤差分だけ小さくして、ストップウオッチをカウントさせるための基準クロックの調整をしています。 | プログラムのトップに戻る |
| ページトップ | |
使用したメインとなる下図 左側のプリント基板 は、"秋月電子" の "片面ガラス・ユニバーサル基板 Bタイプ(95×72mm)めっき仕上げ(通販コード P-00518)" ですが、後述の ケース加工図
で示したケース内に収めるためには、プリント基板の切断等の加工が必要です。 左図に示すように、まず基板下部の ----- 線の位置で切断をします。 そして、この切断によって失われた基板下部の取り付け用穴の代理となる、新たなΦ3.2 の穴を図のように2か所開けます。 次に、基板上部に位置するLCDモジュールの取り付けのために用いる、 木片固定用のΦ2 程度の穴を2か所開けます。 また、基板左下に位置する四角っぽいものは "TP4056 充電モジュール" ですが、このモジュール基板を本プリント基板上に固定をさせるために、縦横斜6か所の青色線で示す位置で両基板同士の穴に、 0.4 〜 0.5 mm の錫メッキ線等を通してはんだ付けをするのですが、モジュール基板側は 2.54 mm ピッチではないため注意が必要です。 このとき、左側の斜2か所の青色線で示すモジュール基板側の穴に対応する本プリント基板側の位置には、ランド穴が開いていないのでこの位置にΦ1 程度の穴を開ける必要がありますが、 この穴については、実際にモジュール基板を取り付けるときに、右側の4か所のはんだ付けをした後に現物合わせで開けるとよいと思います。 次に 右側のプリント基板 は、"秋月電子" の "片面ガラス・ユニバーサル基板 Cタイプ(72×47.5mm)めっき仕上げ(通販コード P-00517)" で、左側メイン基板の右上2個のタクトスイッチと並列に配線をし、 補助用として使用します。 図の ----- 線に従って、Y1, X1, X2, Y2 の順に切断をします。 Y1 線よりも左側の大きな部分は余りで今回は使用しません。 Y1 線で切断した右側の一番大きな基板がタクトスイッチの補助基板で、 2か所のΦ3.2 の穴を開けることも忘れないでください。 Y1, Y2 で挟まれた小さな基板は、メイン基板との接続のためのコネクタ用に使用します。 | プリント基板部品配置図 (MultifuncDigitalClockPC0.CE3) | (MultifuncDigitalClock2PC0.CE3) | ページトップ | |
・トグルスイッチ プリント基板下部に位置する POWER スイッチと Start / Stop スイッチには、下のパターン図のように 3P(1回路)のトグルスイッチで良いのですが、3P でレバーが短いタイプの持ち合わせがなく、 6P(2回路)のものがあったので私は代わりにそれを使用しました。 したがって、基板写真はパターン図と異なっていることを断っておきます。 本機では ケース加工図 で示したケース(内寸高さ 24 mm)を使用したため、レバーが通常のタイプは不可で、短いタイプでないとケース内に収めることができません。 次の写真は、レバーが通常のタイプと短いタイプとを比較するために、"秋月電子" のホームページから無断で拝借をし(ごめんなさい)、多少の加工を加えました。 ここで、トグルスイッチを使用する上での一般的な注意をしておきます。 特に初心者の方は勘違いをし易いので注意が必要です。 下の一番右側の写真では、現在レバーは左側に倒れていますが、 この状態のときには下に伸びている3本のピンの内、どのピンが接続(ON)されているでしょうか? 答えは、真ん中と右側のピン間が ON になっているのです。 レバーをこの状態から右側に倒すと、次には真ん中と左側のピン間が ON に切り替わります。 このようにトグルスイッチでは、倒したレバーの方向とはいつも逆方向のピン間が ON となることを覚えておいてください。 余談にはなりますが、これに対してスライドスイッチでは、移動させたレバー方向と ON となるピン間はいつも同方向になります。
・RTC モジュール( DS3231 For PI ) プリント基板上部左側に位置する 5P のピンヘッダ(オス)は、リアルタイムクロックモジュール の項で説明をした、RTC モジュールのピンソケット(メス)と結合をさせます。 この RTC モジュール( DS3231 For PI )には、本機で必要な SQW 信号が外部に引き出されていないので、その項で説明をした方法にしたがって SQW 信号を引き出す改造を行っておく必要があります。 ・LCDモジュール プリント基板上部中央に位置するI2C接続LCDモジュール "AQM0802A-RN-GBW" は、基板との接続用の足ピンのピッチが 1.5 mm のため、本基板の 2.54 mm ユニバーサル基板には、そのままでは搭載することができません。 そこで、このピッチの異なる両者間での接続方法を、私の別ページ "184. ストップウオッチ II" の LCDモジュールのプリント基板への取り付け で詳述 しているので、そちらを参照してください。 ・TP4056 充電モジュール プリント基板下部左側に位置する "TP4056 充電モジュール" の取り付けについては、前項 プリント基板の加工と部品配置 でも少し触れましたが、まず、縦横4か所の各青色線で示す位置の、 本基板のハンダ面側から 0.4 〜 0.5 mm の錫メッキ線等を通し、モジュール基板上面側で各青色線の対となるもう片方の穴へグルっとUターンをして、再び、本基板のハンダ面側に通した後はんだ付けをします。 このとき、モジュール基板側は 2.54 mm ピッチではないため注意が必要ですが、本基板側の穴位置は、前項 プリント基板の加工と部品配置 で示した 縦横4か所の各青色線で示したランド穴位置を選択すれば問題はありません。 次に、モジュール基板左側の斜2か所の青色線で示すモジュール基板側の穴に対応する本基板側の位置には、ランド穴が開いていないのでこの位置にΦ1 程度の穴を開ける必要があります。 穴を開けた後、前の縦横4か所のときと同要領で斜2か所についても、錫メッキ線等でモジュール基板の固定をします。 ただし、前の縦横4か所についてはその後、本基板のハンダ面側で回路の配線が必要になりますが、 この斜2か所については回路の配線はありません。 また、この充電モジュールでバッテリに充電中には、充電制御 IC TP4056 がかなり発熱をするので、手持ちにあったヒートシンクを熱伝導シール(両面粘着)で貼り付けました。 (下のプリント基板の写真では、ヒートシンクを貼り付ける前のものを掲載。) ・タクトスイッチ補助基板との接続コネクタ RTC モジュール用の 5P のピンヘッダのすぐ左下にある 6P のピンヘッダは、ICSP 端子で PIC にプログラムを書き込むときに使用するものですが、プログラムを書き込んだ後の通常時には、 6P のピンヘッダの内の RB6, RB7, GND の 3 ピンを別目的で使用します。 下の プリント基板(2)パターン図 (部品面) (ハンダ面) の項で示した、タクトスイッチの補助基板のケーブルコネクタ( 3P ピンソケット(メス))と、通常時に接続をさせておきます。 これはメイン基板の右上2個のタクトスイッチと並列に配線をすることで、ケースの蓋を閉じていても補助基板のタクトスイッチで操作を可能にするものです。 基板左下の B+ と B- 端子ですが、当初、配線をがっちりとさせるために、はんだ付けで配線を行うつもりでいて、その配線をし易くするために 3P のピンヘッダの内の、真ん中のピンを抜いたのですが、はんだ付けではやはり保守性が悪くなるので、 最終的にはコネクタ接続を行うことに変更をしました。 右写真は抜いた状態のときに撮ったもので、現状では抜いたピンをもとに戻して再び 3P にしてあります。(抜いた 2P 状態よりも 3P の方が、しっかりとコネクタ接続ができるため) また、プログラム の コンフィギュレーションワードの設定で起きた不具合 で述べたように、PIC16F1827 の 4 ピン端子と CLEAR スイッチとの配線替えを終盤になってから行っていますが、 写真を撮った時期よりもずっと後のことなので、同様に右写真には反映がされていませんが、すべて修正をしたパターン図の方が正解なので注意をしてください。 | プリント基板パターン図 (部品面) (MultifuncDigitalClockPC.CE3) | 左図から黒色文字を除いた図 (MultifuncDigitalClockPC2.CE3) | ページトップ | |
前項 プリント基板パターン図 (部品面) でも述べたように、トグルスイッチに 3P(1回路)ではなく 6P(2回路)のものを使用したため、基板写真のトグルスイッチ周りのパターンは、
左側のパターン図とは少々異なっていることに注意をしてください。 | プリント基板パターン図 (ハンダ面) (MultifuncDigitalClockPC1.CE3) | ページトップ | |
既に上の プリント基板パターン図 (部品面) でも述べていますが、次図のケーブルコネクタ( 3P ピンソケット(メス))を、メイン基板の左側に位置する ICSP 端子 6P の内の、RB6, RB7, GND の
3 ピンと接続をしておくことによって、メイン基板の右上2個のタクトスイッチについては、ケースの蓋を閉じていてもこの補助基板のタクトスイッチで同操作が可能なようにします。 そのために、この補助基板を次項 ケース加工図 の(上面図)の右上位置に、上蓋の内側から取り付け、タクトスイッチのキートップがケース外に飛び出るようにします。 タクトスイッチをプリント基板に取り付ける場合には、下図の赤文字 A の面(スイッチの底面)を、基板の部品面に押し付けて取り付けるのが一般的なようです。 タクトスイッチが取り付けられて完成品として売られている プリント基板などを見てみると、多くの場合そのような取り付けになっています。(下図は "秋月電子" のホームページに添付されているデータシートから、一部を抜粋したものに書き込みをして使用しました) 想像ですがこれらの場合、恐らく組み立て用機械(ロボット)がタクトスイッチを取り付けているのであって、人間が手動で取り付けているのではない、と思われます。 したがって、取り付け後の足ピンの形状も歪むことなく、スイッチ位置なども下図右上(P.C.B. LAYOUT)の4点の中央にきちんと配置され、美しい仕上がりになっています。 ところが、この作業を我々人間が手動で行うような場合、赤文字 A の面(スイッチの底面)を基板の部品面に押し付けるのには、かなり強い力が必要です。 しかも、足ピンの形状は歪んでしまうことが多く、スイッチの取り付け位置も右上図の4点の中央から外れてしまうこともしばしばです。 したがって、私個人としてはこのような取り付け方は、特別な場合を除いてあまり行っていません。 プリント基板上の4つの穴にタクトスイッチの各足ピンを挿入後、軽くキートップを上から押し込むと、 赤文字 B の足ピン位置(スイッチの底面が 1.8 mm 程浮いたところ)で、一旦、挿入の進行が止まります。 その少し浮かせた足ピン位置での取り付けが、通常私が行っている方法で、足ピンのはんだ付けをする前に、タクトスイッチの4本の各足ピン位置が水平、垂直ともに傾き(ゆがみなど)がないかを十分に確認をした上ではんだ付けを行います。 この取り付け方法の利点として、キートップを押し付けるのに強い力は不要で、浮かせたスイッチ下の空間に配線を通す( 例 )ことができるなどです。 しかし、この項でのタクトスイッチの取り付けでは、どちらの方法も使いません。 上図の青色で示した厚さ 1 mm 程度のものを、スイッチの底面とプリント基板の部品面との間に挟み込んで、スイッチを押し込みます。 そして、はんだ付けの後で挟み込んだものを抜き取るのですが、要は、上図の赤文字 C, D 間を 5 mm としたいのです。 この 5 mm という値は、私が所有するスペーサの中で最も多数を所有している長さであって、それを使用したいためです。 もし 4 mm 長のスペーサをお持ちであれば、何も挟まないで押し込んでください。 なお、これらの値は "秋月電子" の一般的なタクトスイッチである、DTS-63 タイプ(キートップ長が 3.5 mm のもの)を使用した場合です。 | プリント基板(2)パターン図 (部品面) (MultifuncDigitalClock2PC.CE3) | (ハンダ面) (MultifuncDigitalClock2PC1.CE3) | ページトップ | |
本機のケースは、"秋月電子" の "ABS樹脂ケース(蝶番式・中)112−TS ニシムラ (通販コード P-00277)" を使用しました。 外側(内側)寸法が 117 (112) x 84 (80) x 28 (24) mm で、可動式のフタが付いています。
・メイン基板の取り付け 上図の(下面図)はケースの裏面側から見た図で、上部の大きな点線の四角部分(72 x 89 mm)がメイン基板の取り付け位置です。 私がプリント基板をケース等に取り付ける場合、通常、両者の間には 5 mm 長のスペーサを挟んで 取り付けることが多いのですが、本機のメイン基板には 5 mm 長のスペーサを使用することができません。 上の プリント基板パターン図 (部品面) の項で、使用したトグルスイッチについて述べたように、本機ではレバーが短いタイプを使用していますが、メイン基板を上図のケース内に納めるためには 5 mm 長のスペーサでは長過ぎて、 上蓋が閉まらなくなってしまいます。 そこで本機では M3 のナットをスペーサ代わり、としました。 ウィルコ で購入したナットで、 "鉄(ニッケルめっき) 六角ナット・1種 FNT-03N" の厚さ 2.4 mm のものです。 ケースの裏面側(外側)の4か所から、それぞれ M3 x 10 サイズのビス(+なべ小ねじ)を通し、ケース内側でスペーサ代わりの M3 ナット → メイン基板 → ポリカーボネート平ワッシャー → M3 ナット、の順に取り付けます。 ポリカーボネート平ワッシャーは、プリント基板表面の傷つき防止とねじ緩み防止を兼ねて挿入をしてあります。(ねじ類の型番等については、次項の 使用部品表 を参照のこと) ・補助基板の取り付け 上図の(上面図)はケースの上面側から見た図で、ケース上蓋の右上の小さな点線の四角部分に、補助基板を上蓋の内側から取り付けます。 補助基板には前項 プリント基板(2)パターン図 (部品面) (ハンダ面) で説明をした方法で、 タクトスイッチが取り付けられていることが前提です。 ケースの上面側(外側)の2か所から、それぞれ M3 x 12 サイズのビス(+なべ小ねじ)を通し、ケース内側で M3 x 5 サイズの中空スペーサ → 補助基板 → M3 ナット、の順に取り付けます。 すると、タクトスイッチのキートップが 1.5 mm 程 ケース外に飛び出すようになります。 ・電池ホルダーの取り付け 上図の(下面図)はケースの裏面側から見た図で、下部の横に細長い点線の四角部分に、18650 リチウムイオン電池用の電池ホルダーを取り付けます。 私が使用した電池ホルダー、およびその取り付けるときの注意点、方法などについては、 別ページ "198. MP3プレーヤー I" の 電池ホルダーをケースに取り付け を参考にしてください。 | ケース加工図 (MultifuncDigitalClockCS.CE3) | ページトップ | |
(主要部品: IC, トランジスタ等) | (データシート) | |||
PICマイコン | .................... | PIC16F1827 | ||
三端子レギュレータ | .................... | S-812C33AY-B-G | ||
トランジスタ | .................... | 2SA1015 | ||
LCDモジュール | .................... | AQM0802A-RN-GBW | ||
圧電ブザー | .................... | PKM17EPP-4001 |
| 部品表 | Excel ファイル (MultifuncDigitalClock_parts.xls) | ページトップ |
PIC16F1827 データシート | .......... | https://akizukidenshi.com/download/pic16f1827.pdf | ||
145. 長時間ストップウオッチ/タイマー | .......... | http://xyama.sakura.ne.jp/hp/StopWatchTimer.html | ||
184. ストップウオッチ II | .......... | http://xyama.sakura.ne.jp/hp/StopWatchII.html | ||
189. ドットマトリクス 8 x 8 LED 表示時計 II ( MAX7219 版) | .......... | http://xyama.sakura.ne.jp/hp/Matrix88LED_ClockII.html |