;--------------------------------------------------------------------------------
;	PIC12F675 簡易電圧計
;	Programmed by GRANADA
;	Since 2008/11/18-11/20
;
;	スタティック点灯（赤→黄→黄→緑順次点灯）にする場合はソース中２箇所変更で可
;
;--------------------------------------------------------------------------------

	LIST		P=12F675
	INCLUDE		P12F675.INC
	ERRORLEVEL	-302

CB = _CPD_OFF
CB &= _CP_OFF
CB &= _BODEN_OFF			; ブラウンアウトリセット (2.0V) OFF
CB &= _MCLRE_OFF			; 外部リセット不許可
CB &= _PWRTE_ON				; パワーオンリセット許可
CB &= _WDT_OFF				; ウォッチドッグタイマ不許可
CB &= _INTRC_OSC_NOCLKOUT	; 4MHz 内部発振モード、Port 4 も使用可能に

	__CONFIG	CB
	__IDLOCS	H'0100'

;--------------------------------------------------------------------------------
;	ファイルアドレス定義
;--------------------------------------------------------------------------------

VOLTAGE			EQU		H'20'	; 測定電圧ストア用
SLEEP_COUNTER	EQU		H'23'	; スリープまでのタイムカウント用
BLINK_COUNTER	EQU		H'24'	; LED BLINK 処理用カウンタ

W_WORK			EQU		H'30'	; 割り込み時の W レジスタ退避用（未使用）
ST_WORK			EQU		H'31'	; 割り込み時のステータスレジスタ退避用（未使用）

COUNTER1		EQU		H'40'	; 時間待ち用ワーク
COUNTER2		EQU		H'41'	; 時間待ち用ワーク
COUNTER3		EQU		H'42'	; 時間待ち用ワーク

EVENT_COUNTER	EQU		H'43'	; 特別イベント用カウンタ
DISPLAY_COUNTER	EQU		H'44'	; LED 点灯回数用カウンタ

;--------------------------------------------------------------------------------
;	定数定義
;--------------------------------------------------------------------------------

; PIN アサイン
; AN0 <AI> 太陽電池電圧
; GP1 <DO> LED1     ( H = 点灯 )
; GP2 <DO> LED2     ( H = 点灯 )
; GP3 <DI> 夜間検出（ H = 夜間、L = 昼間 ）
; GP4 <DO> LED3     ( H = 点灯 )
; GP5 <DO> LED4     ( H = 点灯 )

ANALOG_IN	EQU		D'0'	; 電圧測定ポート（アナログ）, 7 pin
LED4		EQU		D'1'	; 6 pin
LED3		EQU		D'2'	; 5 pin
DARKNESS	EQU		D'3'	; 夜間検出ポート番号, 4 pin
LED2		EQU		D'4'	; 3 pin
LED1		EQU		D'5'	; 2 pin

							; 太陽電池電圧は 1/3 に分圧されてアナログポートへ
							; 一方、基準電圧＝電源電圧＝5V なので、各電圧スレッショルドは
							; 以下のように計算できる
LED4_V		EQU		D'162'	; 9.5V の 8bit A/D 変換値 = 9.5/3 * 256/5
LED3_V		EQU		D'145'	; 8.5V の 8bit A/D 変換値 = 8.5/3 * 256/5
LED2_V		EQU		D'128'	; 7.5V の 8bit A/D 変換値 = 7.5/3 * 256/5
LED1_V		EQU		D'111'	; 6.5V の 8bit A/D 変換値 = 6.5/3 * 256/5

;--------------------------------------------------------------------------------
;	プログラム先頭
;--------------------------------------------------------------------------------

	ORG		H'0'
	GOTO	INITIALIZE

;--------------------------------------------------------------------------------
;	割り込み処理（現状未使用）
;--------------------------------------------------------------------------------

	ORG		H'4'			; 割り込み処理先頭アドレス

PUSH_REG:

	MOVWF	W_WORK			; レジスタ待避
	SWAPF	STATUS,W		; Z フラグが影響しないよう、MOVF ではなく SWAPF を使用
	MOVWF	ST_WORK
;
;	ここに何らかの割り込み処理を記述
;

POP_REG:

	SWAPF	ST_WORK,W        ;レジスタ復帰
	MOVWF	STATUS
	SWAPF	W_WORK,F
	SWAPF	W_WORK,W
;	BCF		INTCON,***		; 処理が終了した割込み対象のフラグをクリアしておく
							; フラグをクリアしておかないと次回の割り込みがかからないので注意

	RETFIE					; 割り込み処理終了

;--------------------------------------------------------------------------------
;	初期化
;--------------------------------------------------------------------------------

INITIALIZE:

	BANKSEL	OSCCAL				; Bank 1

	CALL	H'3FF'      		; 内部発振器をキャリブレーション
	MOVWF   OSCCAL

	CLRF	TRISIO				; ひとまず全ポートを出力に指定
	BSF		TRISIO,DARKNESS		; 夜間検出ポートを入力に再指定
	BSF		TRISIO,ANALOG_IN	; 電圧計測ポートを入力に再指定

	MOVLW	B'00110000'			; A/D 変換クロック -> FRC、全ポートをデジタル指定
	MOVWF	ANSEL
	BSF		ANSEL,ANALOG_IN		; アナログポートのみ再指定、他はデジタルポートのまま

	CLRF	PIE1
	BSF		PIE1,ADIE			; ADIE ビットを 1 にして AD 割り込み許可

	BANKSEL	GPIO				; Bank 0

	CLRF	GPIO				; 全ポートを L に -> 全 LED 消灯

	MOVLW	B'00000111'			; コンパレータ未使用、これを設定しないと Port 0 の挙動が怪しい
	MOVWF	CMCON

	BANKSEL	IOC
	MOVLW	B'00001000'			; PORT 3 を状態変化検出に使用。（SLEEP 時、明るさ検出により自動復帰させるため）
	MOVWF	IOC

	BANKSEL	INTCON
	MOVLW	B'00001000'			; GPIO 変化割り込み有効
	MOVWF	INTCON				; SLEEP からの復帰のみなので GIE ビットは立てない

	MOVLW	B'00000001'			; 左詰、内部 REF、AD コンバータ電源 ON
	MOVWF	ADCON0
	BSF		ADCON0,1			; AN0 でアナログ変換（アナログポート変更時はここを変更すること）

	CLRF	SLEEP_COUNTER		; スリープカウンタ用ワークをクリア

	CALL	CLEAR_EVENT			; イベント処理を初期化
	CALL	BLINK_LED			; 全 LED を点滅させて正常起動を確認

;--------------------------------------------------------------------------------
;	メインループ開始
;--------------------------------------------------------------------------------

MAIN_LOOP:

	CLRF	GPIO				; 全 LED 消灯
	BTFSC	GPIO,DARKNESS		; 明るさチェック。L なら昼間なので次の行をスキップ
	GOTO	EXEC_SLEEP			; スリープカウンタをカウントアップ

	CLRF	SLEEP_COUNTER		; スリープカウンタをクリア

;--------------------------------------------------------------------------------
;	特別イベント処理チェック（未使用）
;--------------------------------------------------------------------------------

;	DECFSZ	EVENT_COUNTER,F
;	GOTO	MAIN_LOOP2			; 所定回数に達していなければ通常の処理へ
;
;	CALL	SPECIAL_EVENT		; 特殊イベントへ

;--------------------------------------------------------------------------------
;	通常の太陽電池電圧測定〜表示
;--------------------------------------------------------------------------------

MAIN_LOOP2:

	CALL	READ_VOLTAGE		; 電圧を読んでメモリにストア
	CALL	SET_DISPLAY_COUNTER	; LED 表示回数をリセット

;--------------------------------------------------------------------------------
;	LED 表示
;--------------------------------------------------------------------------------

DISPLAY:

	CALL	DISPLAY_LED			; LED 表示

	DECFSZ	DISPLAY_COUNTER,F
	GOTO	DISPLAY				; 所定回数まで表示

	CLRF	GPIO				; 全ポート OFF
	GOTO	MAIN_LOOP

;--------------------------------------------------------------------------------
;	スリープ処理
;--------------------------------------------------------------------------------

EXEC_SLEEP:

	CALL	WAIT_20ms		; 一定時間待つ
	INCFSZ	SLEEP_COUNTER,F	; カウンタを増加。256回暗検出でスリープ処理へ
	GOTO	MAIN_LOOP		; 一定回数に達するまではメインループに戻る

							; LED 逆順点灯処理

	CLRF	GPIO			; 全ポート OFF
	BSF		GPIO,LED4		; LED4 点灯 for debug
	CALL	HOLD_LED_LONG
	CLRF	GPIO			; 全ポート OFF
	BSF		GPIO,LED3		; LED3 点灯 for debug
	CALL	HOLD_LED_LONG
	CLRF	GPIO			; 全ポート OFF
	BSF		GPIO,LED2		; LED2 点灯 for debug
	CALL	HOLD_LED_LONG
	CLRF	GPIO			; 全ポート OFF
	BSF		GPIO,LED1		; LED1 点灯 for debug
	CALL	HOLD_LED_LONG
	CLRF	GPIO			; 全ポート OFF

	CLRF	ADCON0			; A/D コンバーター電源カット（節電）

	BTFSS	GPIO,DARKNESS	; 念のためスリープ直前にもう一度明るさチェック。
							; LED 逆順点灯処理中に H になっていたら夜間なので次の行をスキップしてスリープへ
	GOTO	RESUME			; 明かるいのでスリープせずに復帰処理

	BCF		INTCON, GPIF	; SLEEP 直前に GPIF ビットをクリア
	SLEEP					; SLEEP 実行

	NOP

RESUME:

	CLRF	SLEEP_COUNTER	; SLEEP 用カウンタをクリアして
	CLRF	GPIO			; 念のためポートをいったんクリア

	MOVLW	B'00000001'		; 左詰、内部 REF、AD コンバータ電源 ON
	MOVWF	ADCON0

	CALL	BLINK_LED		; 全 LED を点滅させて復帰をアピール

	CALL	READ_VOLTAGE	; スリープ前にたまった A/D コンバーターのチャージを破棄

	GOTO	MAIN_LOOP		; メイン処理に戻る

;--------------------------------------------------------------------------------
;	A/D 変換を実行し、値をメモリにストアする
;--------------------------------------------------------------------------------

READ_VOLTAGE:

	CALL	WAIT_1ms		; サンプリングまで 2TAD 以上待つ
	BCF		PIR1,ADIF		; ADIF を 0 に（A/D 変換終了時このビットが 1 になる）
	BSF		ADCON0,GO		; GO (start) bit を立てて A/D 変換開始

EXEC_AD:					; 通常の A/D 変換

;	BTFSC	ADCON0,GO		; A/D 変換終了したか? 0 なら次をスキップ
;	GOTO	EXEC_AD			; まだ変換中

	SLEEP					; A/D 変換しながら SLEEP モードへ
	NOP						; A/D 変換が終了したら自動的に再開

	MOVF	ADRESH,W		; A/D 変換値を読み出して
	MOVWF	VOLTAGE			; メモリにストアしておく

	RETURN

;--------------------------------------------------------------------------------
;	LED1 以上の電圧?
;--------------------------------------------------------------------------------

DISPLAY_LED:

	CLRF	GPIO				; 全LED消灯

	MOVF	VOLTAGE,W			; 測定電圧値を読み出す
	SUBLW	LED1_V				; LED1_V - VOLTAGE -> W
	BTFSC	STATUS,C			; LED1_V 以上なら借り入れが発生するので C=0 となり、次の命令がスキップされる
	RETURN						; 最低電圧以下なのでメインループに戻る

	BSF		GPIO,LED1			; LED1 点灯
	CALL	HOLD_LED			; LED 点灯をしばらくホールド

;--------------------------------------------------------------------------------
;	LED2 以上の電圧?
;--------------------------------------------------------------------------------

	MOVF	VOLTAGE,W			; 測定電圧値を読み出す
	SUBLW	LED2_V				; LED2_V - VOLTAGE -> W
	BTFSC	STATUS,C			; LED2_V 以上なら借り入れが発生するので C=0 となり、次の命令がスキップされる
	GOTO	HOLD1				; 表示時間調整

	CLRF	GPIO				; 全LED消灯
	BSF		GPIO,LED2			; LED2 点灯
	CALL	HOLD_LED			; LED 点灯をしばらくホールド

;--------------------------------------------------------------------------------
;	LED3 以上の電圧?
;--------------------------------------------------------------------------------

	MOVF	VOLTAGE,W			; 測定電圧値を読み出す
	SUBLW	LED3_V				; LED3_V - VOLTAGE -> W
	BTFSC	STATUS,C			; LED3_V 以上なら借り入れが発生するので C=0 となり、次の命令がスキップされる
	GOTO	HOLD2				; 表示時間調整

	CLRF	GPIO				; 全LED消灯
	BSF		GPIO,LED3			; LED3 点灯
	CALL	HOLD_LED			; LED 点灯をしばらくホールド

;--------------------------------------------------------------------------------
;	LED4 以上の電圧?
;--------------------------------------------------------------------------------

	MOVF	VOLTAGE,W			; 測定電圧値を読み出す
	SUBLW	LED4_V				; LED4_V - VOLTAGE -> W
	BTFSC	STATUS,C			; LED4_V 以上なら借り入れが発生するので C=0 となり、次の命令がスキップされる
	GOTO	HOLD3				; 表示時間調整

	CLRF	GPIO				; 全LED消灯
	BSF		GPIO,LED4			; LED4 点灯
	CALL	HOLD_LED			; LED 点灯をしばらくホールド

	RETURN

;--------------------------------------------------------------------------------
;	LED 表示保持時間調整
;--------------------------------------------------------------------------------

HOLD1:
	CLRF	GPIO				; 全LED消灯
	CALL	HOLD_LED
HOLD2:
	CLRF	GPIO				; 全LED消灯
	CALL	HOLD_LED
HOLD3:
	CLRF	GPIO				; 全LED消灯
	CALL	HOLD_LED

	RETURN

;--------------------------------------------------------------------------------
;	LED 表示を一定時間保持
;--------------------------------------------------------------------------------

HOLD_LED:

	CALL	WAIT_1ms		; ダイナミック点灯時
;	CALL	WAIT_200ms		; スタティック点灯時

	RETURN

;--------------------------------------------------------------------------------
;	LED 表示を一定時間保持（長め）
;--------------------------------------------------------------------------------

HOLD_LED_LONG:

	CALL	WAIT_200ms
	RETURN

;--------------------------------------------------------------------------------
;	全 LED 点滅表示
;--------------------------------------------------------------------------------

BLINK_LED:

	CALL	HOLD_LED_LONG

	MOVLW	D'2'				; LED BLINK 回数
	MOVWF	BLINK_COUNTER

BLINK:

	MOVLW	B'00110110'			; 全 LED を点滅させて正常起動をアピール
	MOVWF	GPIO
	CALL	WAIT_100ms
	CLRF	GPIO
	CALL	WAIT_100ms

	DECFSZ	BLINK_COUNTER,F
	GOTO	BLINK				; 所定回数未満ならループ

	CALL	HOLD_LED_LONG

	RETURN

;--------------------------------------------------------------------------------
;	LED 表示ループの回数を初期化
;--------------------------------------------------------------------------------

SET_DISPLAY_COUNTER:

	MOVLW	D'20'				; ダイナミック表示の場合
;	MOVLW	D'1'				; スタティック表示の場合
	MOVWF	DISPLAY_COUNTER		; ダイナミック表示を行う際のカウンタ

	RETURN

;--------------------------------------------------------------------------------
;	特別イベントの初期化
;--------------------------------------------------------------------------------

CLEAR_EVENT:

	MOVLW	D'10'				; メインループ何回で特別イベント処理に飛ぶか
	MOVWF	EVENT_COUNTER

	RETURN

;--------------------------------------------------------------------------------
;	特別イベント処理
;--------------------------------------------------------------------------------

SPECIAL_EVENT:

;
;	必要があればここに特別イベント処理を書く
;	例えば一定回数毎に鉛電池側電圧を測って表示する等
;
	CALL	CLEAR_EVENT

	RETURN

;--------------------------------------------------------------------------------
;	時間待ちサブルーチン
;--------------------------------------------------------------------------------

WAIT_1ms:

	MOVLW	D'1'
	GOTO	SET_COUNTER

WAIT_5ms:

	MOVLW	D'5'
	GOTO	SET_COUNTER

WAIT_10ms:

	MOVLW	D'10'
	GOTO	SET_COUNTER

WAIT_20ms:

	MOVLW	D'20'
	GOTO	SET_COUNTER

WAIT_100ms:

	MOVLW	D'100'
	GOTO	SET_COUNTER

WAIT_200ms:

	MOVLW	D'200'

;--------------------------------------------------------------------------------
;	時間待ちサブルーチン・コア部分、1ms ループ
;	（クロック 4MHz 時 1 step = 1/4000000 x 4 (sec) = 1μs）
;--------------------------------------------------------------------------------

SET_COUNTER:

	MOVWF	COUNTER1

WAIT_LOOP1:

	MOVLW	H'F9'		; 4MHz 時 F9H で 1ms のループになる
	MOVWF	COUNTER2

WAIT_LOOP2:

	NOP
	DECFSZ	COUNTER2,F
	GOTO	WAIT_LOOP2

	DECFSZ	COUNTER1,F
	GOTO	WAIT_LOOP1

	RETURN

;--------------------------------------------------------------------------------
;	時間待ちサブルーチン、だいたい 1 秒
;--------------------------------------------------------------------------------

WAIT_1sec:

	MOVLW	D'5'
	MOVWF	COUNTER3

WAIT_LOOP3:

	CALL	WAIT_200ms

	DECFSZ	COUNTER3,F
	GOTO	WAIT_LOOP3

	RETURN

;--------------------------------------------------------------------------------
;	プログラム終了
;--------------------------------------------------------------------------------

	END
