プログラムアドレス処理
PIC16Fシリーズで使用される命令体系で直接扱える最大アドレス範囲は 0〜2047 です。今回使用しているPIC16F873はプログラムメモリを4Kワード実装しています。ですから、2048番地以降のプログラムメモリにアクセスするためには PCLATHレジスタの内容をその都度設定する必要があります。
PIC16Fシリーズではプログラムメモリは2Kワード単位で管理されます。この単位はページと呼ばれます。PCLATHレジスタの3ビット目および4ビット目でプログラムメモリのページが指定されます。
各ページを超えてGOTOまたはCALLでジャンプするためには LGOTO または LCALL を使います。
これらは擬似命令と呼ばれるもので、PIC16F自体の命令ではありません。これらの命令が書かれたソースコードをアセンブルすると以下のようにアセンブラで自動的に変換されます。
この例ではページ0からページ1のINIT(ページ内アドレス:h'3FE")にジャンプする例です。
ソースコード
LGOTO INIT アセンブル結果
158A BSF PCLATH,3
120A BCF PCLATH,4
2BFE GOTO h'3FE'
PCLATHの3ビット目に"1"を設定し、プログラムページを1に切り替えています。
GOTOのようにジャンプするだけならば上記のようにLGOTOで自動的に処理されます。
問題は LCALL の場合です。
この場合はサブルーチンから戻ったあとでPCLATHを設定する処理が必要になります。
LCALLもサブルーチンにジャンプする際にはPCLATHを自動的に書き換えてくれます。
以下の例はページ1からページ0にあるサブルーチン(ページ内アドレス:h'ED")を呼び出す場合です。
ソースコード
LCALL GET_TBL_DATA アセンブル結果
118A BCF PCLATH,3
120A BCF PCLATH,4
20ED CALL h'ED"
PCLATHの3ビット、4ビットをクリアしてページ0に切り替えています。
サブルーチンから元の処理に戻るためにサブルーチンの最後で RETURN が実行されます。
RETURN ではスタックメモリに書かれた戻り番地を参照して元の場所にジャンプしますが、この際PCLATHの値は書き換えません。
ですから、戻った先で自分のいるページをPCLATHに設定しないといけません。そうしないとサブルーチンのあるページの同じ番地のプログラムが実行されてしまいます。 LRETURN という擬似命令はありません。
今回のプログラムでは setpchマクロ でPCLATHの設定を行っています。
setpch macro
if ($ >= 0) && ($ < 0x800)
bcf PCLATH,3
else
bsf PCLATH,3
endif
endm
"$"は自分のアドレスを示します。PIC16F873の場合プログラムメモリは4Kワード、すなわち2ページしか使用していないのでPCLATHの3ビット目のみを設定しています。
LCALLでサブルーチンを呼んだあとに必ず setpchマクロが入れられています。
2290行目のLCALLに関するアセンブルリストは以下のようになっています。
M-ADRは16進表示の機械語アドレス、M-CODEは機械語コード、S-ADRはソースコードアドレスです。
この場合、プログラム位置はh'800'以降なので、ページ1になります。ですから、SETPCHマクロではPCLATHの3ビット目を1に設定するコードのみが展開されています。
プログラムの配置
基本的にはサブルーチン関係はページ0に格納し、メインルーチン、割り込み処理ルーチンはページ1に格納しています。
初期化ルーチン
03433 ;####################################################################
03434 ;
03435 ; Initial routine
03436 ;
03437 ;####################################################################
電源を投入して 0 番地からスタートすると、この処理にジャンプします。初期化ルーチンではPICの動作環境の設定を行います。
以下にSFRの設定内容を一覧にします。
|
レジスタ名 | 初期値 | 説 明 |
PORTA
PORTB
PORTC | 00000000 | 各ポートレジスタの内容をクリア。 |
RCSTA | 10010000 | シリアルポートを使用。連続受信を設定。 |
ADCON0 | 00000000 | A/D変換機能は使用しない。 |
ADCON1 | 00000110 | 全てのポートをデジタルポートとして使用。 |
TRISA | 00010000 | RA4を入力モード。他は出力モード。 |
TRISB | 00001100 | RB2,RB3を入力モード。他は出力モード。 |
TRISC | 11000000 | RC6,RC7を入力モード。他は出力モード。 |
OPTION_REG | 00000000 |
内部プルアップは使用しない。タイマーは内部クロックを使用。
プリスケーラーはTMR0タイマーに使用。プリスケーラー値は1:2 |
SPBRG | 01010010 | 9,600bpsを指定 ( 値82 ) |
TXSTA | 00100110 |
8ビット通信。送信機能を使用。非同期モード。高速モード。
TRMTビットを"1"に設定しているが読出専用なので不要。 |
T2CON | 00000110 | タイマー2のポストスケーラーは1:1。タイマー2を使用。プリスケーラーは1:16。 |
TMR2 | 00000000 | タイマー2の値をクリア |
PR2 | 00010011 |
タイマー2周期を設定。カウント値(20)より1つ少なく設定する。
(1/12.8MHz)*(4サイクル/クロック)*(20カウント(PR2))*(16(プリスケーラ))
0,078125uS*4*20*16=100uS |
PIE1 | 00100010 | USART受信割込機能を使用。TMR2とPR2との一致機能を使用。 |
INTCON | 11000000 | 全割込禁止を解除。周辺機器割込可。 |
|
上記以外に以下のような初期化処理が行われます。
各種ワークエリアの初期化
時刻エリアの初期化
EEPROMから時刻表示モードの読込
液晶ディスプレーの初期化
メインルーチン
03536 ;####################################################################
03537 ;
03538 ; Main routine
03539 ;
03540 ;####################################################################
メインルーチンでは以下の処理が行われます。
初期メッセージの表示 ソフトフローチャート(1/11) |
|
初期メッセージは電源投入直後2秒間だけ表示されます。
|
時刻表示の更新 ソフトフローチャート(1/11) |
|
clock_main というサブルーチンを使用して1秒毎にディスプレーの表示を更新します。サブルーチンの呼び出しは1秒に関係なくサイクリックに行われていますが、サブルーチンの中で1秒経過しかがどうかを判断しています。サブルーチンにしている理由は時刻設定が行われているときでも時刻表示更新をするためです。キー入力待ちの際には常に clock_main サブルーチンが呼び出され、時刻表示更新が行われます。
|
時刻設定キーの処理 ソフトフローチャート(2/11)、ソフトフローチャート(3/11) |
|
メインルーチンの中でステップが多いのがこの処理です。
最初はメニューキーが押されたかどうかがチェックされます。最初に押されるべきキーはメニューキーで、その他のキー押下は無効です。
キー操作の待ち時間は10秒に設定されます。その後の操作をせずに10秒経過するとそれまでのキー操作は無効になります。
03672 CALL DISP_MAIN ;Time display
上記の処理はソフトフローチャート(1/11)の右下に書かれているサブルーチン呼び出しです。 disp_main サブルーチンは clock_main サブルーチンの中でも使用されているので、一見、必要無いように思えます。しかし、 clock_main サブルーチンでは1秒毎にしか表示を更新しないので、キー処理を抜けるときに disp_main サブルーチンを呼び出して時刻をすぐ表示するようにしています。キー操作時にはディスプレーにはキー操作のメッセージが表示されていて時刻は表示されていません。
最初のメニューキー以降は操作するキーによって「時刻表示モード切替」または「タイマーの設定」が行われます。
|
clock_main サブルーチン ソフトフローチャート(4/11) |
|
このサブルーチンではディスプレーの表示タイミングとデコードのタイミングが判断されます。ディスプレー表示は0秒毎に disp_main サブルーチンを呼び出して更新されます。また、400ミリ秒毎に dec_time サブルーチンを呼び出して電波に乗せられてきた情報を分析し時刻情報にする処理を行います。
|
clock_main サブルーチン ソフトフローチャート(5/11) |
| この処理はLCDに時刻またはメッセージを表示する処理です。 |
割り込み処理
03973 ;####################################################################
03974 ;
03975 ; Interruption processing
03976 ;
03977 ;#################################################################### 今回の装置では「タイマー2による周期的割り込み」と「シリアル回線の受信割り込み」の2種類の割り込みが使われています。割り込み種類の判断処理では前記の2種類以外は発生しないとしてそれ以外の割り込み種別の場合でもタイマー2割り込みと同じ処理を行っています。異常動作を想定すると割り込み終了処理 ( int_vec1 ) にジャンプさせた方が無難です。
03979 BTFSC PIR1,TMR2IF ;timer2(100uS) int ?
03980 GOTO TIMER_INT ;Yes.
03981 BTFSC PIR1,RCIF ;RX int ?
03982 GOTO SIO_INT ;Yes.
GOTO INT_VEC1 ;No. Jump to return タイマー2による100μ秒毎の周期的割り込み
100μ秒割り込み処理では以下のような処理が行われます。
・ 100uSカウンターの減算 ( 100uS毎 ) ソフトフローチャート(6/11)
今回のソフトウェアではLCDの制御等を100uS単位で制御しています。 ( delay100u サブルーチン )
この時間を作るために100uSの割り込み処理でカウンター値を減算します。
・ 10mSカウンターの減算 ( 10mS毎 ) ソフトフローチャート(6/11)
100uS割り込みを基に10mSに時間を計測し、10mS単位のカウンター減算を行います。 ( delay10m サブルーチン )
・LED点灯時間制御 ( 10mS毎 ) ソフトフローチャート(6/11)
正秒、正分、正時、正日、正月の信号は100mSだけ出力します。点灯から100mS後に消灯する制御が行われます。
・ TCO( Time Code Output ) の情報分析 ( 10mS毎 ) ソフトフローチャート(7/11)、ソフトフローチャート(8/11)
TCOの信号には3種類あり、それぞれ以下のような規格および判定基準で動作しています。
種類 | 規格 | 処理での判定基準 |
マーカー | 200mS ± 5mS | 80mS 〜280mS |
1信号 | 500mS ± 5mS | 400mS 〜 600mS |
0信号 | 800mS ± 5mS | 700mS 〜 880mS |
TCO信号は信号の立ち下がり時点から立ち上がるまでの時間を計測し、信号の種類を判定しています。
検出されたTCOコードは tco_code に格納され、時間デコード処理 ( dec_time ) で年、月、日、曜日、時、分、秒の分析に使用されます。
TCO情報には月、日の情報は無く、1月1日からの通算日および閏年情報が送られます。それを基に月、日の情報に変換しています。
・ キー押下検出 ( 10mS毎 ) ソフトフローチャート(9/11)、ソフトフローチャート(10/11)
キーのなかで F2, F3, F4 および タイマーOFF はLCDの制御ポートと共用で使用されています。そのためキー状態を検出するときだけ RB4, RB5, RB6, RB7 を出力モードから入力モードに切り替えてキー状態の確認をしています。キー状態検出動作が終了すると再び出力モードに戻します。
キーの押下を検出すると押されたキーの番号を検出します。検出したキーの番号は key_dat に格納されます。
キーが連続的に押された場合、押されている時間によりキー処理状態 ( key_sts ) を設定するタイミングを変え、キー操作を簡便にしています。キー状態管理メモリ( key_sts )はキー押下検出で "1" に設定し、キー処理要求があることを表示します。フローチャートでは key_sts の設定/解除の箇所を全て描いてはいません。詳しくはソースコードを参照してください。
この機能はタイマー時刻設定の場合にキーを連続的に押していることにより設定値の更新を高速で行い、設定を容易にするために設けられています。
連続押下時間により更新速度は以下のように変化します。連続的に押している時間が長いほど速く更新するようになります。
連続押下時間 | 更新時間 |
〜500mS | 自動更新しない |
500mS 〜 2400mS | 300mS毎に更新 |
2400mS 〜 | 100mS毎に更新 |
この機能は時刻設定だけではなく、全てのキー操作で働きます。ですから、時刻表示の状態から Menu キーを連続的に押した場合、時刻設定 → タイマー選択 → 12H/24Hモード切替 → 時刻表示 ・・・ というように設定画面が高速で切り替わることになってしまいます。この防止処理はしていません。
シリアル通信回線でのキャラクタ受信割り込み ソフトフローチャート(11/11)
シリアル回線で1文字のデータが受信される都度、割り込みが発生します。
受信処理自体はPICのハードウェアで実行されます。受信割り込みが発生したからといっても、正常に受信されたとは限りません。そのため、受信割り込みの最初では正常に受信出来たかどうかの確認を行います。受信動作異常には以下の2種類があります。
・ フレーミングエラー : シリアル回線の信号でストップビットが検出できない場合
・ オーバーランエラー : 受信バッファーの内容を読み取らないうちに次のデータが回線から送られてきた場合
上記のエラーが発生した場合には受信データは破棄され、受信回路の初期化が行われます。
フレーミングエラーは雑音により発生しましが、それ以外に送信側の速度指定と受信側の速度指定が正しくない場合にも発生します。オーバーランエラーはソフトウェアの受信処理が正常に動作せず、ハードウェアで受信したデータをソフトウェアで引き取れない場合に発生します。ソフトウェアにバグが無ければ発生しません。
上記のエラーチェックの結果正常に受信できている場合には rx_ptr が示すメモリアドレスに受信データが格納されます。受信データとしてCR( Carriage Return : 印字ヘッドを元に戻す)情報を受信するとコマンドの受信が終了したことになります。ちなみにLF( Line Feed ) は1行分紙を進めるという信号です。昔のタイプライター式のプリンターが使われていたころからのデータ形式です。
サブルーチン
今回のソフトウェアで使用しているサブルーチンの一覧を以下に示します。 |
名 称 | 機 能 |
lcd_init | LCDモジュールを初期化する |
lcd_e_pulse | LCDモジュールのE端子にパルスを出力する |
print | 1 文字を表示する |
messout | 文字列を表示する |
cls | 表示をクリアする |
locate | カーソルを移動する |
csr_on | カーソルを表示する |
csr_off | カーソルを消去する |
lcd_iwr | LCDインストラクションレジスタにデータを書き込む |
lcd_dwr | LCDデータレジスタにデータを書き込む |
disp_second | 秒を表示する |
disp_minute | 分を表示する |
disp_hour | 時を表示する |
disp_day | 日を表示する |
disp_month | 月を表示する |
disp_year | 年を表示する |
disp_wday | 曜日を表示する |
disp_nday | 通算日を表示する |
disp_time | 時刻を表示する |
disp_date | 日付を表示する |
disp_ok | 電波受信OKを表示する |
disp_ng | 電波受信NGを表示する |
disp_timer | タイマー設定画面1行目を表示する |
disp_tm_set | タイマー設定値を表示する |
| |
sio_job | シリアル通信処理 |
cmd_check | シリアル受信コマンドをチェックする |
sout_time | 時刻をシリアルポートに出力する |
sout_date | 日付をシリアルポートに出力する |
sout_stime | 秒に同期した時刻出力フラグをセットする |
sout_status | 時刻修正ステータスをシリアルポートに出力する |
cmd_set_timer | タイマーを設定する |
sout_timer | タイマー設定値をシリアルポートに出力する |
sout_decode | 時刻デコード状況をシリアルポートに出力する |
cmd_timer_on | タイマー出力オンする |
cmd_timer_off | タイマー出力オフする |
sout_err | エラーメッセージをシリアルポートに出力する |
sout | 1文字をシリアルポートに出力する |
ssout | 文字列をシリアルポートに出力する |
sout_second | 秒をシリアルポートに出力する |
sout_minute | 分をシリアルポートに出力する |
sout_hour | 時をシリアルポートに出力する |
sout_day | 日をシリアルポートに出力する |
sout_month | 月をシリアルポートに出力する |
sout_year | 年をシリアルポートに出力する |
sout_wday | 曜日をシリアルポートに出力する |
cv_str2num | 2桁の文字列をバイナリ値に変換する |
sout_num2d | 2桁の数値をシリアルポートに出力する |
|
|
名 称 | 機 能 |
get_tbl_data | データテーブルからデータを取得する |
| |
inc_time | 時間を1秒進める |
inc_date | 日付を1日進める |
inc_dec_time | デコードバックアップ時間を1分進める |
cvt_nd2day | 通算日を月日に変換する |
| |
addw | 加算 [ BCCH,BCCL = BCCH,BCCL + ACCH,ACCL ] |
mulib | 積和演算 [ BCCH,BCCL = BCCH,BCCL + (ACCL * W) ] |
divb | 除算 [ ACCH = ACCL / W ] |
us_subw | 減算 [ ACCH,ACCL = ACCH,ACCL - BCCH,BCCL (桁下がり無し) ] |
| |
init_time | 時刻、日付レジスタを初期化する |
init_dec_time | デコード時刻、日付レジスタを初期化する |
init_bk_dec_time | デコードバックアップ時刻、日付レジスタを初期化する |
init_tm_chk_cnt | デコードチェックカウンタをクリアする |
chk_tm_chk_cnt | デコードチェックカウンタをチェックする |
adjust_0s | 時刻の0秒修正を行う |
copy_time | デコード時刻を表示時刻にコピーする |
dec_time | 時刻をデコードする |
search_mark | 0秒マーカーを検出する |
dec_time_m | 分をデコードする |
dec_time_h | 時をデコードする |
dec_time_nd | 通算日をデコードする |
dec_time_y | 年をデコードする |
dec_time_wd | 曜日をデコードする |
delay100u | 100μS単位の遅延処理 |
delay10m | 10mS単位の遅延処理 |
| |
chk_eep_dat | EEPROMのデータをチェックし、無効なら初期化する |
eeprom_init | EEPROMを初期化する |
rd_eeprom | EEPROMからデータを読む |
wr_eeprom | EEPROMにデータを書き込む |
chk_timer | タイマー時刻のチェックし、設定時刻であれば出力する |
timer_off | タイマー出力を停止する |
comp_timer | タイマー設定と現在時刻を比較する |
set_tmbf_data | タイマー時刻をEEPROMに書き込む |
get_tmbf_data | タイマー時刻をEEPROMから読み出す |
get_tm_data | タイマー設定時刻をEEPROMから読み出す |
set_tm_data | タイマー設定時刻をEEPROMに書き込む |
set_tm_lim | タイマー設定上限値をセットする |
|
|