;******************************************************************************
;ポイントスイッチをLoconet経由で制御
;PIC16F873A 
;copyright (c) 2010-2015 名古屋電鉄　　http://www007.upp.so-net.ne.jp/nagoden/
;pointsw110.asm  2010/09/19 Ver1.10 
;pointsw200.asm  2015/01/20 Ver2.00
;  V110まではアドレスが１２７まであったので２５５までに対応
;　　EEPROM読み出しで失敗する問題の対応（バグ対応）
;  スイッチによってはチャタリングで複数回コマンドを連投してしまう問題の対応
;******************************************************************************
;
;PIC16F873AのポートＢの8本のポートにスイッチを付け、そのON-OFFでLoconetのスイッチコマンドを出力します。
;
;初回起動時
;１．EEPROMの書き込み
;　　ポートＢに接続したスイッチを全てＯＦＦ（ポートＢはPullupしているので全てHighの状態）にし、RC3（14番ピン）を
;  Highにして電源投入します。
;　　プログラムはセットアップモードになりEEPROMのアドレス00hに値00hが書き込まれます。
;２．起動
;　　RC3（14番ピン）をLowにして電源投入すれば、ポートＢのRB0からスイッチ０（ポイント番号１）
;　　になり、スイッチ７（ポイント番号８）まで対応します。
;３．スイッチ８以上に対応するの場合
;　　セットアップモードでアドレスのオフセット（加算値）をあらかじめ登録できます。
;　　例：スイッチ５６（ポイント番号５７）の場合は、５６を設定します。１６進で３８Hなのでビットにすると００１１１０００に
;　　なります。上位ビットがRB7なので　ビット１の部分をスイッチＯＮ（ポートはLOW）にしてRC3（14番ピン）を
;　　Highにして電源投入すると38HがEEPROMに書き込まれます。
;　　RB0のスイッチはスイッチ５６（ポイント番号５７）になります。
;
;　６芯モジュラーは　2番-GND　3番-Signalになります
;
;---------------------------------------------------------------------
;sw番号３の場合のコマンド
;　Loconetポイント切り替えコマンド
;　B0   02   10   5D
;　B0   02   30   7D
;      ↑　　　　↑　　　　↑
;      │　　　　│　　　　チェック用（コマンド4バイトを全てXORするとFFになるための値）
;      │　　　　10がｔ　30がｃ
;     ポイント番号-1
;-----2015/01/20 Ver200　----------------------------------------------
;   Loconet　規格では以下のようにコマンドになっているのでＤＩＰＳＷが8ビットなので２５５まで対応する
;   <0xB0>,<SW1>,<SW2>,<CHK> 
;          <SW1> =<0,A6,A5,A4,A3,A2,A1,A0>
;          <SW2> =<0,0,DIR,ON,A10,A9,A8,A7>
;　　　　　　　　　DIR=1 for Closed, DIR=0 for Thrown
;　　　　　　　　　　ON=1 for Output ON, =0 FOR output OFF
;----------------------------------------------------------------------
;  USARTモードは以下の指定をしています 
;   Mode : Asyncronous transfer mode only
;   Speed: 16K bps(12h 20Mhz) 
;   Bit  : startx1 datax8 stopx1
;　※Loconetはボーレート16600bpsと特殊です。ここでは設定値から16447bpsにしています。
; 
; RB0～RB7	スイッチ入力8本（PullUP）
; RC7 		RX（受信）（Loconet上の送信有無確認）
; RC6 		TX（送信）
; RC5　		受信データ確認（未使用）
; RC4 		BUSY信号（LED）　(LOCONETにデータ有　もしくは　送信待機中）ほとんど点灯しません
;			セットアップモードの場合EEPROM書き込み後にＬＥＤが点滅します
; RC3 		セットアップモード用入力　（1のときオフセットアドレスをEEPROMに書き込み）   
;******************************************************************************
	list      p=16f873a
	#include <p16f873a.inc>

	__CONFIG _CP_OFF & _WDT_OFF & _BODEN_ON & _PWRTE_ON & _HS_OSC & _WRT_OFF & _LVP_OFF & _CPD_OFF

;割り込みルーチンで使用
w_temp		EQU	0x20 
status_temp	EQU	0x21
;タイマーカウント用
cnt1		EQU	0x22
cnt2		EQU	0x23
cnt3		EQU	0x24
;ポート用変数
PC_DATA		EQU 0x25	;ポートＣ（オフセットアドレス設定判定ビット用）
PB_DATA 	EQU 0x26	;ポートＢ（スイッチデータ）
PC_STATUS	EQU	0x27	;ポートＣ（シリアルポートチェック用）
;スイッチの保持変数
SW_DATA		EQU 0x28	;ループ前のスイッチの状態を保持
;EEPROM用
EE_DAT		EQU 0x29	;EEPROM 書き込み用
;アドレスオフセット
OFFSET_ADR	EQU 0x2A	;スイッチ番号に加算するオフセット数
;ループ用カウンタ
cnt4		EQU	0x2B
cnt5		EQU	0x2C
cnt6		EQU	0x2D	;送信データ2バイト目（スイッチ番号）用
cnt7		EQU	0x2E	;送信前シリアルポート確認時のチェックループカウント用
cnt8		EQU 0x2F
;ワーク用
wk_data		EQU 0x30	;送信コマンド作成用（変化ビット用）
wk_data2  	EQU	0x31	;送信コマンド作成用（データビット用）
wk_data3	EQU	0x32	;シリアル送信ルーチン用
;コマンド送信時のチェックコマンド（最終バイト）
chk_sum		EQU	0x33
;*******************************************************************************
		ORG     0x000             
  		goto    INIT  				


		ORG		0x004				; 割り込みベクタ
		movwf	w_temp        	  	; ワークレジスタ退避
		movf	STATUS,w          	; ステータスレジスタをワークレジスタに
		bcf		STATUS,RP0        	; バンク０に切り替え
		bcf		STATUS,RP1        	; バンク０に切り替え
		movwf	status_temp       	; ワークレジスタ（ステータス）退避
								  	; ←ここに割り込みルーチン設定
		bcf		STATUS,RP0        	; バンク０に切り替え
		bcf		STATUS,RP1        	; バンク０に切り替え
		movf	status_temp,w     	;　退避ステータスのワークレジスタへ戻し
		movwf	STATUS            	; ワークレジスタからステータスへ戻し
		swapf	w_temp,f			;　ワークレジスタの戻し	
		swapf	w_temp,w          	; ワークレジスタの戻し
		retfie                    	; 割り込み終了


INIT
;*********************************************
;ＰＩＣの入力ポートとシリアル機能の設定
;
;*********************************************
	clrf	INTCON	 	; 割り込みを禁止
	bsf		STATUS,RP0	;Bank 1に切り替え
	movlw	b'10101000'	;RC7, RC5, RC3入力
	movwf	TRISC		;PORTCの設定
	movlw	b'11111111'	;PORTB全て入力ポート
	movwf	TRISB		;PORTBの設定

	;USARTの設定 PIC12F873 20MhZ
	movlw	020H		;↓でAsync mode　に設定 
	movwf	TXSTA		;TXSTAにセット
	movlw	d'18'		;18→ボーレート 16Kbps   16447.37=20Mhz/((18+1)*64)
	movwf	SPBRG		;SPBRGにボーレート設定		
	bcf		STATUS,RP0	;Bank 0に切り替え
	movlw	090H		;SPENとSRENに１を設定 
	movwf	RCSTA		;RCSTAにセット


MAIN_INIT
;*********************************************
;セットアップモード
;(RC3='High')の時にBポートの値を読んでEEPROMに書き込み
;*********************************************
	;セットアップモード確認
	movf 	PORTC,w
	movwf	PC_DATA
	movlw	b'00001000'	;RC3のポートチェックするのでをビットマスクするため
	andwf	PC_DATA,f
	movlw	00h			;００Ｈとマスクして１か判断
	addwf	PC_DATA,f
	btfsc	STATUS,Z	;０かどうか判定
	;*********************************************
	goto	MAIN		;セットアップモードでなければメイン処理
	;*********************************************
	;ポートＢの値取得してＥＥＰＲＯＭに書き込み
	movf 	PORTB,w
	movwf	EE_DAT 			;EEPROMに書き込むための変数にセット   
	movlw	0xff
 	xorwf	EE_DAT,w   		;FFhとｘｏｒしてビット反転  
	movwf	EE_DAT 			;EEPROMに書き込むための変数にセット   
	bcf 	STATUS,RP0 		;Bank 2  
	bsf 	STATUS,RP1		;Bank 2  
	movlw 	0x00			;EEPROMのアドレス指定  
	movwf 	EEADR 			;アドレスをEEADRへセット  
	movf 	EE_DAT,W		;内容をワークレジスタにセット  
	movwf 	EEDATA 			;EEDATAにセット
	bsf 	STATUS,RP0 		;Bank 3  
	bsf 	STATUS,RP1 		;Bank 3 
	bcf		EECON1,EEPGD	;Point to Data memory	V2.00add
 	bsf 	EECON1,WREN 	;EEPROM書き込み許可
	bcf		INTCON,GIE		;割り込み禁止  			V2.00add
	movlw 	55H 			;  
	movwf 	EECON2 			;  
	movlw 	0AAH  			;
	movwf 	EECON2  		;
	bsf 	EECON1,WR 		;EEPROM書き込み開始 
	bcf		EECON1,WREN 	;EEPROM書き込み禁止  		V2.00add  
	bsf		INTCON,GIE		;割り込み許可  			V2.00add 
WR_WAIT    
	btfsc 	EECON1,WR 		;EEPROM書き込み完了確認
	goto 	WR_WAIT  
	bcf 	STATUS,RP0 		;Bank 0  
	bcf 	STATUS,RP1 		;Bank 0  
OF_L00P
	;***************************************
	;セットアップモードでEEPROMに書き込んだら
	;電源きるまでLEDを点滅させて待ちにします
	;***************************************
	bsf		PORTC,4
	movlw	d'250'
	call 	dly_Nm
	bcf		PORTC,4
	movlw	d'250'
	call 	dly_Nm
	movlw	d'250'
	call 	dly_Nm
	movlw	d'250'
	call 	dly_Nm
	goto	OF_L00P

MAIN
;*********************************************
;メインルーチン
;
;*********************************************
 	clrf	OFFSET_ADR
	clrf	SW_DATA
	bsf		PORTC,4
	;*****************************************
	;EEPROMの読み込み　アドレスオフセット値取得
	;EEPROMのアドレス00hから読み取り、OFFSET_ADRにセット
	;*****************************************
	bcf 	STATUS,RP0 	;bank 2
	bsf 	STATUS,RP1 	;bank 2
	movlw 	0x00 		;呼び出すアドレス1セット
	movwf 	EEADR 		;EEADR(10Dh) アドレス呼び出し
	bsf 	STATUS,RP0 	;bank 3
	bsf 	STATUS,RP1 	;bank 3
;---2015/01/20 V2.00　add----------------------
	bcf		EECON1,EEPGD	;cler EEPGD Bit   Ver200 Modify
;----Addend-----------------------------------
	bsf 	EECON1,RD 	;EE Read(18Ch) readに設定 
	bcf 	STATUS,RP0 	;bank 2
	bsf 	STATUS,RP1 	;bank 2
	movf 	EEDATA,W 	;EEDATA(10Ch) 呼び出しデータをワークレジスタにセット
	movwf	OFFSET_ADR 	;読み取ったワークレジスタの内容をOFFSET_ADRにセット
	bcf 	STATUS,RP0 	;bank 0
	bcf		STATUS,RP1 	;bank 0

MAIN_LOOP
;*****************************************************************
;Bポートの情報をPB_DATAに取り込み、RB0～RB7ビットと前回ループ時の
;SW_DATAと比較し変化のあるビットのみスイッチコマンドを発行する
;*****************************************************************
	;Bポートの情報をPB_DATAに取り込み
	movf 	PORTB,w			;ポートＢの値を読み込み
	movwf	PB_DATA			;PB_DATAにセット
	movlw	0xff
 	xorwf	PB_DATA,f   	;PB_DATAとFFhをｘｏｒしてビット反転しPB_DATAにセット  
	;ビットの変化をｘｏｒで取得
	movf	PB_DATA,W		;PB_DATAの内容をワークレジスタにセット
	xorwf	SW_DATA,W		;SW_DATAとワークレジスタをXORして変化のあるビットをワークレジスタにセット
	movwf	wk_data			;ワークレジスタからWK_DATAにセット（スイッチ変化データ）
	movf	PB_DATA,w
	movwf	wk_data2		;ワークレジスタからWK_DATAにセット（ポートBのデータ）
	;ｘｏｒしたwk_dataでビットに１が立っているものはスイッチが変化しているのでxorの結果を
	;ビット７～０までループし変化のあるビットを処理対象にする
	movlw	d'8'
    movwf	cnt4
BIT_CHK
	btfss	wk_data,7	;BITチェック(7ビット目でチェック　wk_dataはローテートしてビットチェック）
	goto	BITCHK_END	;0のときはコマンド発行処理はしない	
	;***********************
	;以下、コマンド発行処理
	;***********************
;---2015/01/20 V2.00　add----------------------
	;50msウエイト（スイッチのチャタリングによる誤送信防止のため）
	movlw	d'50'
	call	dly_Nm
;----Addend-----------------------------------
	;コマンド送信
SEND
	;*******************************************
	;0xB0+ sw + (0x30 or 0x10) + chk_sum
	;chk_sum = コマンドXORした値とXORしてＦＦになるようにする
	;スイッチ０１はコマンドでは００になるので注意
	;*******************************************
	clrf	chk_sum			;クリア
	movf	cnt4,w			;cnt4の値をワークレジスタにセット
	addwf	OFFSET_ADR,w	;ワークレジスタ値をオフセット値と加算
	movwf	chk_sum			;チェック値変数にセット
	DECF	chk_sum,f		;スイッチ番号は１からであるがコマンドは０からなので-1する
	movf	chk_sum,w		;chk_sumの値をワークレジスタにセット
	movwf	cnt6			;この値はポイント番号になるのでcnt6（2バイト目）にもセット
;---2015/01/20 V2.00　add----------------------
; アドレスの最上位ビットチェック
	btfss	cnt6,7			;ｱﾄﾞﾚｽのビットの最上位7ビット目が１の時
    goto	SUM0   			;0の時
	movf	cnt6,w
	andlw	b'01111111'		;1の時　最上位ビット7を０にする
	movwf	cnt6
	movf	cnt6,w
	movwf	chk_sum			;値が変わったのでchk_sumにも値を書く	
	movlw	d'1'			;sw2のビット０に１をセットするための変数ＣＮＴ８に１をセット
	movwf	cnt8
	goto	SUM1
SUM0
	movlw	d'0'			;０の時ビット０に0をセットするための変数ＣＮＴ８に０をセット
	movwf	cnt8
SUM1
;----Addend-----------------------------------
	movlw	0xB0			;スイッチコマンドの1バイト目　0xB0
 	xorwf	chk_sum,f     	;BOとchk_sumをxorしてチェック値変数（chk_sum）にセット
	btfsc	wk_data2,7		;ポートＢの値で7ビット目でビットの状態をチェック（データがローテート）
 	goto	SUM2			;0の時（ｔの場合）		
	movlw	0x30			;1の時（ｃの場合）
;---2015/01/20 V2.00　add----------------------
	iorwf	cnt8,w			;ワークレジスタにいれた0x30に　Cnt8をOrする
;---ADDEND-------------------------------------
	xorwf	chk_sum,f		;30とchk_sumをxorしてチェック値変数（chk_sum）にセット
	goto	SUM3
SUM2
	movlw	0x10			;1Oとchk_sumをxorしてチェック値変数（chk_sum）にセット
;---2015/01/20 V2.00　add----------------------
	iorwf	cnt8,w			;ワークレジスタにいれた0x10に　Cnt8をOrする
;---ADDEND-------------------------------------
 	xorwf	chk_sum,f		;
SUM3
	movlw	0xff
 	xorwf	chk_sum,f   	;ビット反転してチェック値変数（chk_sum）にセット→（4バイト目コマンド）  

;********************************************************************
;　送信前の信号チェック   2010/09/18 Ver 1.10 追加
;	受信データの確認　1byte分のデータ（１０ビット）を　
;	RC4から60μs間隔で１０ビット分（１０回）読んで
;	０が存在しているとき（データが流れている時）は600ﾏｲｸﾛ秒待って
;	再度１０ビット分チェックする。
;	０が存在しないときは１０ビット分のチェックでループから抜ける。
;********************************************************************

PC_STA
	movlw	d'10'
	movwf	cnt7
PC_STA1	
	bcf		PORTC,4			;LED　ON
	movf 	PORTC,w			;ポートＣの値を読み込み
	movwf	PC_STATUS       	;portC→Workﾚｼﾞｽﾀ→PC_STATUS
	btfsc	PC_STATUS,7		; ０だったら600ﾏｲｸﾛ秒待って再度10回ループ
	goto 	PC_STA2			;　０でなければ　１０回ループを継続
	call    dly_600u		;600ﾏｲｸﾛ秒ウエイト
	goto	PC_STA			;ウエイト後最初からチェック
PC_STA2
	call	dly_60u			;60u秒待ち
	decfsz	cnt7,f			;cntを-1
	goto	PC_STA1			;ｃｎｔ7が０になるまでループ
 	bsf		PORTC,4			;１０回回ってＯＫならLED　OFF
;********************************************************************


CMD_TX
	;スイッチコマンド送信処理
	movlw	0xB0
	call	LOCO_TX		;送信
	movf	cnt6,w
	call	LOCO_TX		;送信
	;スイッチが変化したビットのみスイッチコマンドを出力する処理をおこなう
	btfsc	wk_data2,7	
 	goto	SEND3		;0の時		
	movlw	0x30		;1の時
;---2015/01/20 V2.00　add----------------------
	iorwf	cnt8,w			;ワークレジスタにいれた0x10に　Cnt8をOrする
;---ADDEND-------------------------------------
	goto	SEND4
SEND3
	movlw	0x10		;0の時     ###################
;---2015/01/20 V2.00　add----------------------
	iorwf	cnt8,w			;ワークレジスタにいれた0x10に　Cnt8をOrする
;---ADDEND-------------------------------------
SEND4
	call	LOCO_TX		;送信
	movf	chk_sum,w
	call	LOCO_TX		;送信

;**********************************************************
;送信コマンド（受信データ）のチェック
;**********************************************************
;本来であれば送信したデータと受信したデータ（ループバック）してきたコマンドを確認して
;衝突していれば送信データと受信データが同じでなくデータは正しく送られていないので
;再送の処理をかけたいが、ここでは省略する


;***********************************************************
BITCHK_END
	rlf		wk_data,f	;wk_dataのローテート
	rlf		wk_data2,f	;wk_data2のローテート
	decfsz	cnt4,f		;cntを-1
	goto	BIT_CHK		;ｃｎｔ4が０になるまでループ

	;**********************************************************
	;このループで取得したポートＢのデータ（PB_DATA）をSW_DATAへ保存し次回ループ時での差
	;のあるビットのみ処理
	;**********************************************************
	movf	PB_DATA,w
	movwf	SW_DATA

	goto	MAIN_LOOP

;**********************************
;時間遅延ルーチン クロック20MHz用
;**********************************

;**********************************
; 60マイクロ秒遅延
; 1/16600bps　=　60.24μｓ　=60μｓ
;**********************************

dly_60u		; 60uS
	movlw	d'60'
	movwf	cnt1
dly_60u1
	nop
	nop
	nop
	nop
	decfsz	cnt1,f
	goto	dly_60u1
	return

;**********************************
; 100マイクロ秒遅延
;**********************************
dly_100u	; 100uS
	movlw	d'100'
	movwf	cnt2
dly_100u1
	nop
	nop
	nop
	nop
	decfsz	cnt2,f
	goto	dly_100u1
	return
;**********************************
; 600マイクロ秒遅延
;**********************************
dly_600u	; 600uS
	movlw	d'6'
	movwf	cnt1
dly_600u1
	call 	dly_100u
	decfsz	cnt1,f
	goto	dly_600u1
	return
;**********************************
; nミリ秒遅延
;call 前にワークにループ数をセット
;例：movlw		d'10'
;**********************************
dly_Nm		; NmS
	movwf	cnt1
dly_Nm1
	call	dly_1m
	decfsz	cnt1,f
	goto	dly_Nm1
	return

;**********************************
; 1ミリ秒遅延
;**********************************
dly_1m		; 1ms
	movlw		d'4'
	movwf	cnt2
dly_1m1
	movlw		d'250'
	movwf	cnt3
dly_1m2		; 250us
	nop
	nop
	nop
	nop
	decfsz	cnt3,f
	goto	dly_1m2
	decfsz	cnt2,f
	goto	dly_1m1
	return


;**********************************
; 送信ルーチン
; 送信バッファにデータをセット
; ワークレジスタに値をセットしてｃａｌｌ
;**********************************
LOCO_TX
	movwf	wk_data3		;
	bsf		STATUS,RP0	;bank1
	bcf		STATUS,RP1	;bank1
LOOP_TX
	btfss	TXSTA,TRMT		;ステータスチェック
	goto	LOOP_TX
;
	bcf		STATUS,RP0	;bank0
	bcf		STATUS,RP1	;bank0
	movf	wk_data3,W	;送信データをワークレジスタにセット
	movwf	TXREG		;送信・・・ビット単位の出力は、この先PICにおまかせ・・・・
	return

	END