2026年2月22日日曜日

TOFセンサとPICで作った近接スイッチプロトタイプ

ToFセンサVL6180とPIC12F629を使った近接スイッチのプロトタイプができました。以下、動作の様子、42秒ほどの短い動画です。

点灯している赤色LEDの代わりにSSR(Solid State Relay)を繋ぐことでLED照明のスイッチにします。

以下、ソースコードです。バグがあるかも知れません。無保証です。

VL6180Xの初期化とコンフィグはarduinoライブラリのソースを参考にしていますが、これで十分なのか過剰なのか理解できていません。データシートが英語で87ページもあって全ては読めませんでした。読んでも理解できたか不明ですが。一応動いているようなので趣味で使うにはOKとしてます。

;---------------------------------------------------------------------
; TOF近接センサVL6180Xを使った非接触スイッチ
;
;                                                      2026.02.22 naka
;  1. 概要
;     近接センサの前で手をかざすとオンオフ動作するスイッチ
;     ・距離は150mm位まではOK(動作確認時190mm程度までは反応した)#defineで設定
;     ・スイッチ動作はオルタネート(一度近づくとオン、一旦離れてもう一度でオフ)
;     ・距離測定は0.1秒毎、オンオフ動作時は1秒後に確認(チャタリング的な動作を避けるため)
;
;  2. ピンアサイン
;
;    (1). GP0  [in] : (ICSP DAT)
;    (2). GP1  [in] : (ICSP CLK)
;    (3). GP2  [out]: LED(デバッグ時に使用;最終的にSSRに置き換える)
;    (4). GP3  [in] : (ICSP VPP)
;    (5). GP4  [in] : I2C SDA
;    (6). GP5  [in] : I2C SCL
;
; 3. 備考
;   ・動作クロック :内蔵OSC 4MHz
;   ・電源         :3V(TOFセンサが2.8-3.0Vなので)
;
; 4. 改版履歴
;    2026.02.14 new
;    2026.02.22 色々修正
;
;---------------------------------------------------------------------
;---------------------------------------------------------------------
;  デバイス定義
;---------------------------------------------------------------------
	list    P=12F629
	include <P12F629.INC>
	__config _INTRC_OSC_NOCLKOUT & _WDT_OFF & _PWRTE_ON & _MCLRE_OFF & _BODEN_OFF & _CP_OFF
	ERRORLEVEL	-302	;アセンブル時のバンク警告メッセージ抑制

;---------------------------------------------------------------------
;  ピン定義
;---------------------------------------------------------------------
#define	LED	2
#define	SDA	4
#define	SCL	5

;---------------------------------------------------------------------
;  TOFセンサ VL6180X I2Cアドレス
;---------------------------------------------------------------------
#define TOFADDR	0x52

;---------------------------------------------------------------------
;  TOFセンサが反応する距離(mm) これより近いとオン/オフする
;---------------------------------------------------------------------
#define ACTDIST	D'150'

;---------------------------------------------------------------------
;  変数レジスタの定義
;---------------------------------------------------------------------
WAIT1	EQU	H'20'
WAIT2	EQU	H'21'
CNT	EQU	H'22'
TMP	EQU	H'23'
CNT1	EQU	H'24'
CNT2	EQU	H'25'
CNT3	EQU	H'26'
BUFF	EQU	H'27'	; 27-2B (LCD 5文字分)
;
binH	EQU	H'30'
binL	EQU	H'31'
bcdH	EQU	H'32'
bcdM	EQU	H'33'
bcdL	EQU	H'34'
counter	EQU	H'35'
temp	EQU	H'36'
;
OFFSET	EQU	H'37'
RANGE	EQU	H'38'
TIMOUTH	EQU	H'39'
TIMOUTL	EQU	H'3A'
TOGGLE	EQU	H'3B'	; 0ビット:トグル、1ビット:前回状態
; H'40'からI2C通信に利用(下の方で定義)
;
;---------------------------------------------------------------------
;  マクロ
;---------------------------------------------------------------------
	; TOFセンサ8ビットレジスタ設定
	
REG8BIT	MACRO	IDXH,IDXL,DATA
	RETLW	IDXH
	RETLW	IDXL
	RETLW	DATA
	ENDM
	
	; TOFセンサ16ビットレジスタ設定
REG16BIT MACRO	IDXH,IDXL,DATAH,DATAL
	RETLW	IDXH
	RETLW	IDXL
	RETLW	DATAH
	RETLW	DATAL
	ENDM
	;
	; TOFセンサレジスタ書き込み
WTREG	MACRO	HIGH,LOW,DATA
	MOVLW	TOFADDR
	CALL	I2CWSTART
	MOVLW	HIGH
	MOVWF	datao
	CALL	TX
	MOVLW	LOW
	MOVWF	datao
	CALL	TX
	MOVLW	DATA
	MOVWF	datao
	CALL	TX
	CALL	I2CSTOP
	ENDM

RDREG	MACRO	HIGH,LOW
	MOVLW	TOFADDR
	CALL	I2CWSTART
	MOVLW	HIGH
	MOVWF	datao
	CALL	TX
	MOVLW	LOW
	MOVWF	datao
	CALL	TX
	CALL	I2CSTOP
	;
	MOVLW	TOFADDR
	CALL	I2CRSTART
	CALL	RX
	CALL	I2CSTOP
	ENDM
	;
;---------------------------------------------------------------------
;  リセットベクタ
;---------------------------------------------------------------------
	ORG	00H		; リセット時の飛び込み先
	GOTO	MAIN		;
	;
	ORG	04H		; 割り込み時の飛び込み先
;---------------------------------------------------------------------
;  割り込み処理
;---------------------------------------------------------------------
INTRUPT
	RETFIE
;---------------------------------------------------------------------
;  メイン処理
;---------------------------------------------------------------------
MAIN
	CALL	DEVINIT
	CALL	LCDINIT
	;
	CALL	VL6180INIT
	CALL	VL6180CONFIG
	;
	MOVLW	D'255'
	CALL	DSPLCD
	;
	CLRF	TOGGLE
MAIN_LP
	;
	CLRF	RANGE
	CALL	READRANGE	; 測定
	;
	MOVF	RANGE,W
	SUBLW	ACTDIST		; アクティブか否かチェック
	BTFSS	STATUS,C
	GOTO	INACT
	; 前回もアクティブなら何もしない(そうしないと点滅する)
	BTFSC	TOGGLE,1
	GOTO	MAININTR
	BSF	TOGGLE,1	; 前回アクティブではないのでトグル動作
	GOTO	TOGGLESW
	;
INACT
	BCF	TOGGLE,1
	GOTO	MAININTR

TOGGLESW
	MOVF	TOGGLE,W
	XORLW	H'01'
	MOVWF	TOGGLE
	;
	BTFSS	TOGGLE,0
	GOTO	$+3
	BSF	GPIO,LED
	GOTO	$+2
	BCF	GPIO,LED
	;
	MOVF	RANGE,W
	CALL	DSPLCD		; LCD表示
	;
	CALL	W05S		; トグル動作時は1秒待つ
	CALL	W05S
	GOTO	MAIN_LP
	;
MAININTR
	CALL	W100MS		; 100ミリ秒ごとに測定
	GOTO	MAIN_LP
	;
;---------------------------------------------------------------------
;  初期化
;---------------------------------------------------------------------
DEVINIT
	CLRF	INTCON		;全割込み禁止
	;
	BANKSEL CMCON
	MOVLW	B'00000111'	; GP0-2をデジタルI/Oに設定
	MOVWF	CMCON		; デフォルトはコンパレータ

;	BANKSEL	1
;	CALL	3FFH		; 内部OSCのキャリブレーションデータ
;	MOVWF	OSCCAL		; OSCCALレジスタにセット

	BANKSEL	TRISIO
	MOVLW	B'00000011'	; RA0,RA1 input
	MOVWF	TRISIO
	BANKSEL GPIO
	;
	BCF	GPIO,LED
	;
	RETURN
	;
;---------------------------------------------------------------------
;  文字列表示
;---------------------------------------------------------------------
DSPTXT
	MOVLW	D'5'
	MOVWF	CNT
	CLRF	TMP
	MOVLW	BUFF
	MOVWF	FSR
	;
	MOVLW	H'80' + H'0B'	; LCD1行目先頭へ
	CALL	LCDCMD
DSPTXTLP
	MOVF	CNT,W
DSPTXT1
	MOVF	INDF,W
	CALL	LCDDATA
	INCF	FSR,F
	;
	DECFSZ	CNT,F
	GOTO	DSPTXTLP
	;
	RETURN
;---------------------------------------------------------------------
;  LCD初期化(データシート通り)
;---------------------------------------------------------------------
LCDINIT
	CALL	W20MS		; 電源が安定するまでの待ち時間
	CALL	W20MS
	;
	MOVLW	H'38'		; Function set
	CALL	LCDCMD
	;
	MOVLW	H'39'		; Functino set
	CALL	LCDCMD
	;
	MOVLW	H'14'		; Intrenal OSC frequency
	CALL	LCDCMD
	;
	;			; Contrast= B'10 1000' = D'40'
	MOVLW	H'70'+H'08'	; Contrast set
	CALL	LCDCMD
	;
	MOVLW	H'54'+H'02'	; Power/ICON/Contrast control
;	MOVLW	H'51'		; +5V
	CALL	LCDCMD
	;
	MOVLW	H'6C'		; Follower control
	CALL	LCDCMD
	CALL	W200MS
	;
	MOVLW	H'38'		; Function set
	CALL	LCDCMD
	;
	MOVLW	H'0C' + H'01'	; Display ON/OFF control
	CALL	LCDCMD
	;
	MOVLW	H'01'		; Clear Display
	CALL	LCDCMD
	;
	RETURN
	;
;---------------------------------------------------------------------
;  LCDコマンド (W:CMD)
;---------------------------------------------------------------------
LCDCMD
	MOVWF	TMP		; 一時退避
	;
	MOVLW	H'7C'		; I2Cアドレス
	CALL	I2CWSTART
	MOVLW	H'00'		; Command (RS=0)
	MOVWF	datao
	CALL	TX
	MOVF	TMP,W
	MOVWF	datao
	CALL	TX
	CALL	I2CSTOP
	;
	MOVLW	H'01'		; Clear Display
	SUBWF	TMP,W
	BTFSC	STATUS,Z
	GOTO	LCDW1MS
	MOVLW	H'02'		; Return Home
	SUBWF	TMP,W
	BTFSC	STATUS,Z
	GOTO	LCDW1MS
	;
	CALL	W27US		; Normal command
	RETURN
LCDW1MS
	CALL	W1MS
	RETURN
	;
;---------------------------------------------------------------------
;  LCD データ(Wレジ内容をLCDに書き込む)
;---------------------------------------------------------------------
LCDDATA
	MOVWF	TMP		; 書き込みデータ一時退避
	;
	MOVLW	H'7C'
	CALL	I2CWSTART
	;
	MOVLW	H'40'		; Data (RS=1)
	MOVWF	datao
	CALL	TX
	;
	MOVF	TMP,W
	MOVWF	datao
	CALL	TX
	;
	CALL	I2CSTOP
	;
	RETURN
	;
;---------------------------------------------------------------------
;  LCD表示(入力W)
;---------------------------------------------------------------------
DSPLCD
	CLRF	binH
	MOVWF	binL
	CALL	_bin2bcd	; BCD変換
	;
	MOVF	bcdM,W
	ANDLW	H'0F'
	ADDLW	'0'
	MOVWF	BUFF

	SWAPF	bcdL,W
	ANDLW	H'0F'
	ADDLW	'0'
	MOVWF	BUFF+1

	MOVF	bcdL,W
	ANDLW	H'0F'
	ADDLW	'0'
	MOVWF	BUFF+2
	;
	MOVLW	'm'
	MOVWF	BUFF+3
	MOVLW	'm'
	MOVWF	BUFF+4
	;
	; ゼロサプレス(上位桁が0なら空白にする)
	MOVF	BUFF,W
	SUBLW	'0'
	BTFSS	STATUS,Z
	GOTO	DISP
	MOVLW	' '
	MOVWF	BUFF

	MOVF	BUFF+1,W
	SUBLW	'0'
	BTFSS	STATUS,Z
	GOTO	DISP
	MOVLW	' '
	MOVWF	BUFF+1
DISP
	CALL	DSPTXT
	RETURN
	;
;---------------------------------------------------------------------
;  VL6180レジスタindex (#define名は32文字まで)
;---------------------------------------------------------------------
#define	READOUT__AVERAGING_SAMPLE_PERIOD	0x01,0x0A
#define	SYSALS__ANALOGUE_GAIN			0x00,0x3F
#define	SYSALS__INTEGRATION_PERIOD		0x00,0x40
#define	SYSALS__INTERMEASUREMENT_PERIOD		0x00,0x3E
#define	SYSRANGE__VHV_REPEAT_RATE		0x00,0x31
#define	SYSRANGE__VHV_RECALIBRATE		0x00,0x2E
#define	SYSRANGE__INTERMEASUREMENT_PERIO	0x00,0x1B
#define	SYSRANGE__MAX_CONVERGENCE_TIME		0x00,0x1C
#define	SYSTEM__INTERRUPT_CONFIG_GPIO		0x00,0x14
#define	SYSTEM__INTERRUPT_CLEAR			0x00,0x15
#define	INTERLEAVED_MODE__ENABLE		0x02,0xA3
#define	RESULT__RANGE_STATUS			0x00,0x4D
#define	RESULT__INTERRUPT_STATUS_GPIO		0x00,0x4F
#define SYSRANGE__START				0x00,0x18
#define	RESULT__RANGE_VAL			0x00,0x62
;
;---------------------------------------------------------------------
;  VL6180初期化(メモリ節約のためベタ書きではなくループを回した)
;---------------------------------------------------------------------
VL6180INIT
	CALL	W20MS
	CLRF	OFFSET
	CALL	REG8BITDT
	MOVWF	CNT1		; 設定パラメタ数
VLINIT_LP1
	MOVLW	H'52'		; アドレス
	CALL	I2CWSTART
	MOVLW	D'3'		; indexHigh, indexLow, data の3バイトを送る
	MOVWF	CNT2
VLINIT_LP2
	INCF	OFFSET,F
	CALL	REG8BITDT
	MOVWF	datao
	CALL	TX		; I2C送信
	DECFSZ	CNT2,F
	GOTO	VLINIT_LP2
	;
	CALL	I2CSTOP
	;
	DECFSZ	CNT1,F
	GOTO	VLINIT_LP1
	;
	RETURN
REG8BITDT			; 以下のテーブルから順に取り出す
	ORG	H'200'
	MOVLW	H'02'
	MOVWF	PCLATH
	MOVF	OFFSET,W
	ADDWF	PCL,F
	RETLW	D'31'		; 31パラメタ
	REG8BIT	0x02,0x07,0x01	; indexHigh, indexLow, data
	REG8BIT	0x02,0x08,0x01
	REG8BIT	0x00,0x96,0x00
	REG8BIT	0x00,0x97,0xFD	; RANGE_SCALER = 253
	REG8BIT	0x00,0xE3,0x01
	REG8BIT	0x00,0xE4,0x03
	REG8BIT	0x00,0xE5,0x02
	REG8BIT	0x00,0xE6,0x01
	REG8BIT	0x00,0xE7,0x03
	REG8BIT	0x00,0xF5,0x02
	REG8BIT	0x00,0xD9,0x05
	REG8BIT	0x00,0xDB,0xCE
	REG8BIT	0x00,0xDC,0x03
	REG8BIT	0x00,0xDD,0xF8
	REG8BIT	0x00,0x9F,0x00
	REG8BIT	0x00,0xA3,0x3C
	REG8BIT	0x00,0xB7,0x00
	REG8BIT	0x00,0xBB,0x3C
	REG8BIT	0x00,0xB2,0x09
	REG8BIT	0x00,0xCA,0x09
	REG8BIT	0x01,0x98,0x01
	REG8BIT	0x01,0xB0,0x17
	REG8BIT	0x01,0xAD,0x00
	REG8BIT	0x00,0xFF,0x05
	REG8BIT	0x01,0x00,0x05
	REG8BIT	0x01,0x99,0x05
	REG8BIT	0x01,0xA6,0x1B
	REG8BIT	0x01,0xAC,0x3E
	REG8BIT	0x01,0xA7,0x1F
	REG8BIT	0x00,0x30,0x00
	REG8BIT	0x00,0x16,0x00	;SYSTEM__FRESH_OUT_OF_RESET
	;
;---------------------------------------------------------------------
;  VL6180 configureDefault
;---------------------------------------------------------------------
VL6180CONFIG
	CALL	W20MS
	CLRF	OFFSET
	CALL	CONFIGDT
	MOVWF	CNT1		; 設定パラメタ数
VLCONFIG_LP1
	MOVLW	H'52'		; アドレス
	CALL	I2CWSTART
	MOVLW	D'3'		; indexHigh, indexLow, data の3バイトを送る
	MOVWF	CNT2
VLCONFIG_LP2
	INCF	OFFSET,F
	CALL	CONFIGDT
	MOVWF	datao
	CALL	TX		; I2C送信
	DECFSZ	CNT2,F
	GOTO	VLCONFIG_LP2
	;
	CALL	I2CSTOP
	;
	DECFSZ	CNT1,F
	GOTO	VLCONFIG_LP1
	;
	; テーブルのラストにある16bitパラメタ1個のみ
	MOVLW	D'4'		; indexHigh, indexLow, dataHigh, dataLow の4バイトを送る
	MOVWF	CNT2
VLCONFIG_LP3
	INCF	OFFSET,F
	CALL	CONFIGDT
	MOVWF	datao
	CALL	TX		; I2C送信
	DECFSZ	CNT2,F
	GOTO	VLCONFIG_LP3
	;
	CALL	I2CSTOP
	;
	RETURN
	;
CONFIGDT			; 以下のテーブルから順に取り出す
	MOVLW	H'02'
	MOVWF	PCLATH
	MOVF	OFFSET,W
	ADDWF	PCL,F
	RETLW	D'9'		; 8bit 9パラメタ + 16bit 1パラメタ
	REG8BIT	READOUT__AVERAGING_SAMPLE_PERIOD,0x30
	REG8BIT	SYSALS__ANALOGUE_GAIN,0x46
	REG8BIT	SYSRANGE__VHV_REPEAT_RATE,0xFF
	REG8BIT	SYSRANGE__VHV_RECALIBRATE,0x01
	REG8BIT	SYSRANGE__INTERMEASUREMENT_PERIO,0x09	; 100ms
	REG8BIT	SYSALS__INTERMEASUREMENT_PERIOD,0x31	; 500ms
	REG8BIT	SYSTEM__INTERRUPT_CONFIG_GPIO,0x24
	REG8BIT	SYSRANGE__MAX_CONVERGENCE_TIME,0x31	; 49ms
	REG8BIT	INTERLEAVED_MODE__ENABLE,0x00
	REG16BIT SYSALS__INTEGRATION_PERIOD,0x00,0x63	; 16bitパラメタ(1個のみ)
	;
;---------------------------------------------------------------------
;  VL6180 readRange
;---------------------------------------------------------------------
READRANGE
	; デバイスがreadyになるまで待つ
	RDREG	RESULT__RANGE_STATUS
	MOVF	datai,W
	ANDLW	H'01'
	BTFSC	STATUS,Z
	GOTO	READRANGE
	;
	; 測定開始フラグ設定
	WTREG	SYSRANGE__START,0x01
	;
	CLRF	TIMOUTH		; タイムアウトカウント用
	CLRF	TIMOUTL
READRANGLP
	CALL	CHKTIMEOUT	; タイムアウト確認
	BTFSS	STATUS,Z
	GOTO	READRANGE2
	MOVLW	D'255'		; タイムアウト
	MOVWF	RANGE
	RETURN
	;
READRANGE2
	; 測定完了確認
	RDREG	RESULT__INTERRUPT_STATUS_GPIO
	MOVF	datai,W
	ANDLW	0x07
	SUBLW	0x04
	BTFSS	STATUS,Z
	GOTO	READRANGLP
	;
	; 値を読み出す
	RDREG	RESULT__RANGE_VAL
	MOVF	datai,W
	MOVWF	RANGE
	;
	; 割り込みフラグクリア
	WTREG	SYSTEM__INTERRUPT_CLEAR, 0x01
	;
	RETURN
	;
;---------------------------------------------------------------------
;  センサのタイムアウトチェック(500ms)
;  1回の測定にざっと200usかかるとして 500ms/200us ... TIMOUTH=10
;---------------------------------------------------------------------
CHKTIMEOUT
	MOVF	TIMOUTL,W
	ADDLW	D'1'
	MOVWF	TIMOUTL
	BTFSS	STATUS,Z
	RETURN
	;
	MOVF	TIMOUTH,W
	ADDLW	D'1'
	MOVWF	TIMOUTH
	SUBLW	D'10'		; Zフラグを持ってリターン
	RETURN

;---------------------------------------------------------------------
;  ウェイト色々
;---------------------------------------------------------------------
W27US
	MOVLW	D'7'
	MOVWF	WAIT1
W27USLP
	DECFSZ	WAIT1,F
	GOTO	W27USLP
	NOP
	RETURN
	;
W1MS
	MOVLW	D'110'
	MOVWF	WAIT1
W1MSLP
	GOTO	$+1
	GOTO	$+1
	GOTO	$+1
	DECFSZ	WAIT1,F
	GOTO	W1MSLP
	GOTO	$+1
	GOTO	$+1
	NOP
	RETURN
	;
W20MS
	MOVLW	D'20'
	MOVWF	WAIT2
W20MSLP
	CALL	W1MS
	DECFSZ	WAIT2,F
	GOTO	W20MSLP
	RETURN
	;
W100MS
	MOVLW	D'100'
	MOVWF	WAIT2
W100MSLP
	CALL	W1MS
	DECFSZ	WAIT2,F
	GOTO	W100MSLP
	RETURN
	;
W200MS
	MOVLW	D'200'
	MOVWF	WAIT2
W200MSLP
	CALL	W1MS
	DECFSZ	WAIT2,F
	GOTO	W200MSLP
	RETURN
	;
W05S
	MOVLW	D'100'
	MOVWF	WAIT2
W05SLP
	CALL	W1MS
	CALL	W1MS
	CALL	W1MS
	CALL	W1MS
	CALL	W1MS
	DECFSZ	WAIT2,F
	GOTO	W05SLP
	RETURN
	;
;---------------------------------------------------------------------
;  I2Cアクセスルーチン
;  流用元:Microchip Applicatin Note : AN982
;          Interfacing I2C Serial EEPROMs to PIC10 and PIC12 Drivers
;---------------------------------------------------------------------
;~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
;
;   Filename:           i2c_routines.inc
;   Date:               March 21, 2005
;   File Version:       1.0
;   Assembled using:    MPLAB IDE 7.00.00.0
;
;   Author:             Chris Parris
;   Company:            Microchip Technology, Inc.
;
;*******************RAM register definitions**********************
    #define STARTRAM 0x40       ; Start RAM at address 0x40

    cblock  STARTRAM
        buffer                  ; Transfer/Receive bit buffer
        datai                   ; Data input byte buffer
        datao                   ; Data output byte buffer
        bitcount                ; Bit loop counter
        bytecount               ; Byte loop counter
        addressH                 ; Word address pointer
        addressL                 ; Word address pointer
        loops                   ; Delay loop counter
        loops2                  ; Delay loop counter
        pollcnt                 ; Polling loop counter
        i2caddr
    endc
;*******************Bit definitions*******************************
DO          equ     0           ; TX/RX buffer output bit
DI          equ     1           ; TX/RX buffer input bit
ACKB        equ     2           ; ACK/NO ACK select bit
;*******************Delay Macros**********************************
;           Each macro delay for 1 inst. cycle less than
;           required, as the instruction immediately
;           before the macro call provides 1 inst. cycle.
;*****************************************************************
THIGH   macro                   ; Clock high time delay (5 us)
        goto    $+1             ; 2-inst. cycle delay (2 us)
        goto    $+1             ; 2-inst. cycle delay (2 us)
        nop			; 1us
        endm

THDSTA  macro                   ; Start condition hold time delay (5 us)
        goto    $+1             ; 2-inst. cycle delay (2 us)
        goto    $+1             ; 2-inst. cycle delay (2 us)
        nop			; 1us
        endm

TSUSTA  macro                   ; Start condition setup time delay (5 us)
        goto    $+1             ; 2-inst. cycle delay (2 us)
        goto    $+1             ; 2-inst. cycle delay (2 us)
        nop			; 1us
        endm

TSUSTO  macro                   ; Stop condition setup time delay (5 us)
        goto    $+1             ; 2-inst. cycle delay (2 us)
        goto    $+1             ; 2-inst. cycle delay (2 us)
        nop			; 1us
        endm

TAA     macro                   ; Output valid from clock delay (4 us)
        goto    $+1             ; 2-inst. cycle delay (2 us)
        goto    $+1             ; 2-inst. cycle delay (2 us)
        endm
;-------------------------------------------------------------------
;  I2Cアクセス
;-------------------------------------------------------------------
I2CWSTART
	MOVWF	i2caddr		; I2Cアドレス退避
	call    BSTART              ; Generate start bit
                                    ; Now send the control byte
                                    ; for a write, to set address
	movf    i2caddr,W		    ; I2Cアドレス
	movwf   datao               ; Copy control byte to buffer
	call    TX                  ; Output control byte to device
	RETURN

I2CSTOP
	CALL    NOACK
	call    BSTOP               ; Generate stop bit
	RETURN
    ;
I2CRSTART
	IORLW	H'01'
	MOVWF	i2caddr		; I2Cアドレス退避
	call    BSTART              ; Generate start bit
                                    ; Now send the control byte
                                    ; for a write, to set address
	movf    i2caddr,W
	movwf   datao               ; Copy control byte to buffer
	call    TX                  ; Output control byte to device
	RETURN
;*******************Start bit subroutine**************************
;           This routine generates a Start condition
;           (high-to-low transition of SDA while SCL
;           is still high.
;*****************************************************************
BSTART
    bsf     GPIO,SCL            ; Make sure SCL is high
    TSUSTA                      ; Start condition setup time delay
    BANKSEL	1
    BCF     TRISIO,SDA          ; Configure SDA to be an output
    BANKSEL	0
    bcf     GPIO,SDA            ; Pull SDA low to initiate
                                ;  Start condition
    THDSTA                      ; Start condition hold time delay
    bcf     GPIO,SCL            ; Bring SCL low in preparation
                                ;  for data transfer
    BANKSEL	1
    BSF     TRISIO,SDA          ; Configure SDA to be an input
    BANKSEL	0
    retlw   0

;*******************Stop bit subroutine***************************
;           This routine generates a Stop condition
;           (low-to-high transition of SDA while SCL
;           is still high.
;*****************************************************************
BSTOP
    BANKSEL	1
    BCF     TRISIO,SDA          ; Configure SDA to be an output
    BANKSEL	0

    bcf     GPIO,SDA            ; Pull SDA low
    bsf     GPIO,SCL            ; Make sure SCL is high
    TSUSTO                      ; Stop condition setup time delay
    BANKSEL	1
    BSF     TRISIO,SDA          ; Configure SDA to be an input
    BANKSEL	0
    retlw   0

;*******************Bit output subroutine*************************
;           This routine outputs bit 'DO' in 'buffer' to
;           the serial EEPROM device.
;*****************************************************************
BITOUT
    bcf     GPIO,SCL            ; Make sure SCL is low
    btfss   buffer,DO           ; Check for state of bit to xmit
    goto    bitlow              ; If bit is low, pull SDA low
clockout                        ; If bit is high, leave SDA high
    bsf     GPIO,SCL            ; Bring SCL high to begin transfer
    THIGH                       ; Clock high time delay
    bcf     GPIO,SCL            ; Bring SCL low again
    BANKSEL	1
    BSF     TRISIO,SDA          ; Configure SDA to be an input
    BANKSEL	0
    retlw   0

bitlow
    BANKSEL	1
    BCF     TRISIO,SDA          ; Configure SDA to be an output
    BANKSEL	0
    bcf     GPIO,SDA            ; Pull SDA low
    goto    clockout

;*******************Bit input subroutine**************************
;           This routine inputs a bit of data from the
;           serial EEPROM device, and stores it in bit
;           'DO' of 'buffer'.
;*****************************************************************
BITIN
    bcf     GPIO,SCL            ; Make sure SCL is low
    BANKSEL	1
    BSF     TRISIO,SDA          ; Configure SDA to be an input
    BANKSEL	0
    bsf     buffer,DI           ; Assume input bit is high
    bsf     GPIO,SCL            ; Bring SCL high to begin transfer
    TAA                         ; Output valid from clock delay
    btfss   GPIO,SDA            ; Check for state of SDA bit
    bcf     buffer,DI           ; If SDA is low, set bit low
    bcf     GPIO,SCL            ; Bring SCL low again
    retlw   0

;*******************Data transmit subroutine**********************
;           This routine transmits the byte of data
;           stored in 'datao' to the serial EEPROM
;           device. Instructions are also in place
;           to check for an ACK bit, if desired.
;           Just replace the 'goto' instruction,
;           or create an 'ackfailed' label, to provide
;           the functionality.
;*****************************************************************
TX
    movlw   .8
    movwf   bitcount            ; Initialize loop counter to 8

txlp
    bsf     buffer,DO           ; Assume output bit is high
    btfss   datao,7             ; Check state of next output bit
    bcf     buffer,DO           ; If low, set buffer bit low
    call    BITOUT              ; Call routine to output bit
    rlf     datao,F             ; Rotate datao left for next bit
    decfsz  bitcount,F          ; Decrement counter, check if done
    goto    txlp                ; Keep looping
    CALL    ACK
    retlw   0

;*******************Data receive subroutine***********************
;           This routine reads in one byte of data from
;           the serial EEPROM device, and stores it in
;           'datai'.  It then responds with either an
;           ACK or a NO ACK bit, depending on the value
;           of 'ACKB' in 'buffer'.
;*****************************************************************
RX
    clrf    datai               ; Clear input buffer
    movlw   .8
    movwf   bitcount            ; Initialize loop counter to 8
    bcf     STATUS,C            ; make sure carry bit is low
rxlp
    rlf     datai,F             ; Rotate datai 1 bit left
    call    BITIN               ; Read a bit
    btfsc   buffer,DI
    bsf     datai,0             ; Set bit 0 if necessary
    decfsz  bitcount,F          ; 8 bits done?
    goto    rxlp                ; If not, do another
    call    BITIN               ; Call routine to read ACK bit
    RETLW   0

ACK
    bcf     buffer,DO           ; If ACKB = 1, send ACK (DO = 0)
    call    BITOUT              ; Send bit
    retlw   0
NOACK
    bsf     buffer,DO           ; If ACKB = 1, send ACK (DO = 0)
    call    BITOUT              ; Send bit
    retlw   0
;
;---------------------------------------------------------------------
;---------------------------------------------------------------------
; 16-bit binary to BCD conversion 
; pete griffiths 2007
; http://picprojects.org.uk/projects/pictips.htm
;
; These file register variables will
; need to be defined elsewhere.
; binH
; binL
; bcdH
; bcdM
; bcdL
; counter
; temp
;
; binH, binL contain the binary value to
; convert. Conversion process destroys contents.
; Result is in bcdH, bcdM, bcdL on return.
; Call _bin2bcd to perform conversion.
;
; Executes in 454 instructions


_bin2bcd    movlw     d'16'
            movwf     counter
            clrf      bcdL
            clrf      bcdM
            clrf      bcdH 

_repeat     rlf       binL,F
            rlf       binH,F
            rlf       bcdL,F
            rlf       bcdM,F
            rlf       bcdH,F

            decfsz    counter,F
            goto      _adjust
            return

_adjust     movlw     d'14'
            subwf     counter,W
            skpnc 
            goto      _repeat
            movfw     bcdL
            addlw     0x33
            movwf     temp
            movfw     bcdL
            btfsc     temp,3
            addlw     0x03
            btfsc     temp,7
            addlw     0x30
            movwf     bcdL
            movfw     bcdM
            addlw     0x33
            movwf     temp
            movfw     bcdM
            btfsc     temp,3
            addlw     0x03
            btfsc     temp,7
            addlw     0x30
            movwf     bcdM
            goto      _repeat
; we only need to do the test and add +3 for
; the low and middle bcd variables since the
; largest binary value is 0xFFFF which is
; 65535 decimal so the high bcd byte variable
; will never be greater than 6.
; We also skip the tests for the first two
; shifts.
;---------------------------------------------------------------------
	END
;---------------------------------------------------------------------
;  終わり
;---------------------------------------------------------------------

2026年2月20日金曜日

TOFセンサをPIC12F629で使う(動いた)

ようやくTOFセンサ VL6180XPIC12F629で動かすことができました。設定パラメタを何度も見返したりしましたが分かりません。他のArduinoライブラリを見たりしても大きな違いはないように見えました。

基本に戻ってI2Cのコードを見ていたら読み出しのタイミングにおかしいところがあるのに気付きました。そこを直したら難なく動いた次第。トホホです。取り合えず書いたコードなので、この3連休で少しキレイにしようかと思います。


写真は消しゴムのMONOまでの距離を表示しているところです。最終的な目的は、非接触スイッチへの利用なのでLCD表示などは不要になりますが、デバッグ時は繋げておくつもり。

手をかざすことでキッチンのシンク上にある照明のオンオフをしようと考えています。今は紐を引っ張ってオンオフするプルスイッチの蛍光灯ですが、LED照明に交換すると共に非接触化を図りたい。濡れた手でスイッチを触りたくないので。センサもPICも小さいので組み込めることを期待しています。

2026年2月15日日曜日

TOFセンサをPIC12F629で使う(使えてない)

昨日書いたChatGPTから吐き出されたコードがダメダメだった 件の続きで、コードを自力で書いてみました。先に結論を言うとまだ使えてません。VL6180Xはムズイ。

使ったのはPIC12F629という古い8ピンタイプで、メモリ1Kワード、I2Cなしです。うまく動いたArduinoのTOPセンサVL6180Xライブラリコードを見ながらアセンブラで移植しました。古いアーキのPICなので最近の便利な命令が使えません。それでも書き始めたら思い出してきてなんとかなった(なってない)。

Arduinoのライブラリを丸ごと移植するのは大変だし、メモリ容量的にも厳しい。適当に端折って移植しましたが、上手く動きません。初期化、コンフィグ、距離測定の3機能だけですが、ダメです。距離が7mmで返ってきます。最小値かな? I2C接続のLCDには表示できているのでI2Cは大丈夫だと思う。端折ってしまったところで、何か必要な設定ができていないのか、処理の呼び出し方をミスっているのかと推測。

朝からコードを見直して1か所、設定場所を間違えている箇所に気付いて修正しましたが、結果は変わらず。根を詰めてもよろしくないし、頭を切り替えたら何か思いつくかもという期待を込めて今日はここまで。

2026年2月14日土曜日

PICプログラムをchatGPTに書いてもらったけど、、、

 先日試したTOF測距センサ をPICマイコンで使いたくて、chatGPTにプログラムを書いてもらったりしていた。しかし、全然動かない。

依頼したプロンプトは以下のような感じ。

PIC12F629マイコンとTOFセンサVL6180を使って近接センサによるスイッチを作りたい。
回路(ピンの接続)とプログラムが欲しい
・あらかじめ設定した距離(cm単位で10~20cm)に近づくとオンオフ動作する
・距離の設定はプログラム内の#defineで行う
・オンオフはオルタネイト。一度近づくとオン、離れたのちもう一度近づくとオフ
・オンの間はLEDが点灯する。最終的にはこれをSSRのLEDにする。
・誤動作防止のためオンオフは指定された距離で200ms程度継続したら認識する
・オンオフの間はチャタリング防止のため500m秒程度あける(これより短い時間でオンオフしない)
・PIC12F629はI2Cモジュールがないので、ソフトウェアでI2Cを行う
・プログラムはアセンブラで書く ・適度に関数に分けたコードが欲しい(後で人手で直せるように処理内容のコメントも記載) ・プログラム書き込みに使用するピン(GP1:ICSPCLK,GP2:ISCPDAT)は使わない ・MPLABでそのままビルドできる1ファイル化 ・クロックは内蔵発信4MHz

吐き出してきたソースを見るとI2C通信のタイミングを全く無視しているし、ACKも見てない。それを指摘すると、それっぽいソースになったがまだダメです。また、TOFセンサの初期化も適当でこれで初期化できるの?という感じ。Arduinoのライブラリのソースを見ると結構多くのパラメタを設定していた。

簡単にできると思っていたのが甘かった。しかたないので諦めて自分で書くことにしました。有料のClaude codeなどを使えばすぐ動くコードを出してくれるのかな?

今日の日経に以下の記事が掲載されていた(クリックで拡大)。先日来のアンソロピックショックでITサービス系の株価が下落しているのにさらに追い打ちかけてきます。NASDAQ, FANG+が下がって痛手。 

AIが自分自身を書き換えて進化し続けたらちょっと怖いな。イーロンマスクが軌道上にAIデータセンタを作るらしいし、そこでAIが自律的に動き出したら簡単に止められない。というようなディストピアSF、映画ターミネータのスカイネットを想像してしまう。

2026年2月7日土曜日

東京アプリでポイントをゲットしたけど

先日、東京アプリによる給付について書きました が一応、ポイント申請してみました。今朝メールが届いたのでアプリで確認したら保有ポイントが11,000Pになっていた。申請してからポイントが付くまで1日半ほどでした。


しかし、dポイントへの交換でエラー発生。大丈夫? ポイントだけ消えないよね? と思いつつ何度かトライ。


無事にdポイントに変換できたようです。ただ、dポイントに交換すると+1,000ポイント増量されるという情報があったのですが、そうはなっていなかった。ちなみにdポイントのアカウント作成時に個人情報を求められて仕方なしに入れた。都がやっている施策なのに、ドコモに個人情報渡さないといけないのはなんだかな。


で、d払いアプリなるものを入れて使うらしいとのことで、入れてポイントをみたら12,345円になっていた。限定123Pというのが分からないけど。それでも1,000ポイントより多い。この12345というのはサンプル画面なのか?


その後、買い物に出かけたのでd払いできるか調べたらポイントが11,000Pに戻っていた。何これ? 結局追加の1,000ポイントはどこへ?


よく分からないけどあまり信用できそうにないのと色々操作を覚えないと使いこなせそうにないので、早めにポイントを使い切って東京アプリもdポイントアプリもアンインストールしようと思う。とにかくアプリやらポイントに振り回されず、生活をシンプルにしたい。

ドコモに渡した個人情報のみ少し後悔。こんなことなら11,000P諦めればよかった。

(追記26.2.7) 東京アプリと認証アプリはアンインストールしたけどアカウントは残ったままになるのかな?アプリを消す前にアカウント削除すべきだったか?

(追記26.2.14) 東京アプリのアカウントの削除は、アプリ内でしかできないようだったので、アプリを入れ直して、アカウント削除(退会)しました。その後再度アンインストール。アマゾンとの連携も個人情報がどこまで連携されるのか不安でやめることにした。で、dポイントはリアル店で使い始めましたが最初手間取りました。早いとこ使い切りたい。最後に残るだろう千円以下の端数ポイントは、普段dポイントを使っている知人にあげちゃうつもり。

2026年2月4日水曜日

東京アプリ生活応援事業について

東京都が実施する生活支援キャンペーンで、東京都が公式スマートフォンアプリ「東京アプリ」を通じて、現金ではなく 11,000ポイント(1ポイント = 1円相当)受け取れる給付制度です。2月2日からはじまった。

よくわからなかったのでchatGPTとチャットして色々と教えてもらったら、驚愕の事実が、、、(検証はしてないので間違いがあるかも)

まず、付与されたポイントは以下の民間のポイントサービスに1ポイント=1円で交換できるらしい。

  • dポイント
  • au PAY残高
  • 楽天ペイ(楽天ポイント)
  • Vポイント(V Point)
  • メルカリポイント

しかし個人的にはこれらのポイントサービスは使ってない。昔からポイントカードとかは面倒で使ってないのです。例外はポイントサービスの元祖であるヨドバシのポイント、アマゾンのポイント、クレジットカードのポイントだけ。

chatGPTが言うにはdポイントはアマゾンと連携して使えるらしい。でもアマゾンギフトカードは買えないよ、と教えてくれた。手順は以下のような感じ。

  • iPhoneに東京アプリを入れる、マイナンバーカードの認証アプリも必要らしい
  • アプリ上でマイナンバーカードを使ってポイントの申請をする
  • dポイントのアプリを入れてdポイントのアカウントを作る。(ドコモユーザでなくても無料で作れるらしい)
  • ポイントが支給されたら東京アプリ上でdポイントに交換する
  • アマゾンでdポイント連携を設定する
  • アマゾンで買い物するときに普段のカードではなく、dポイント連携払いにする

以上、面倒な作業が続く。「スマホが使えない独居の高齢者に無理だね」ということをchatGPTに聞いたら、「そうですね、本当に困窮している人には届かない施策」とのこと。

事業費なども聞いたら、給付用に450億円で、これは支給対象者の約1/3分しかない。要するに、元々全員に配る(申請する)とは考えてない。また、給付額を含む総事業費が799億円とのことなので349億円がシステム構築、運用管理、宣伝費など。これらには職員の人件費は含まないようなので、それを含めたらもっとかかっている計算。

  • お金かけてなんでこんな面倒な仕組みなの?
  • 現金給付じゃダメなの?(マイナンバーに給付金口座を紐づけたよね)
  • 配るならそもそも税金を下げてよ

などチャットしていたら、元々給付が大きな目的ではなくDX推進が目的らしい。東京アプリがDX推進なのか微妙だが。だから使える人だけ使ってくれればOK。また、減税より給付をしたほうが成果をアピールしやすく、「映える」からだそうな。

なんだかなぁ、と思った次第。個人的には残された時間を考えると極力手間のかかることに時間を使いたくない、と思う今日この頃。でもサーバの混雑が解消したころに申請すると思う。

2026年2月3日火曜日

2ch温度ロガーの製作(12;完成)

サクッと作ろうと思っていた温度ロガーですが、昨年の9月から始めてようやく完成。製作記事がふっとんで気力回復がイマイチなのでここまで。

特徴は次のような感じ。

  • 2chの温度が測定できる
  • WiFiに繋ぐことでntpサーバから日時を取得して記録
  • ログはmicroSDにcsvファイル形式で記録
  • 測定時間間隔を設定できる
  • リチウム電池で2~3日は動作すると思う(実測はしてない)
  • 充電はUSB-TypeC


csvファイル形式でログが記録できるので、Excelで簡単にグラフ化できる。下記のグラフは温度の校正がイマイチでちょっと高めにでている。



内部の様子。左端ケース外はリチウム電池。ケース内側左端はmicroSDのスロット(秋月製)、中央はESP32、右端は充放電モジュール(USB-TypeC)。刺さっているのはプログラム書き込み用のアダプタ


リチウム電池を入れた様子。


自分でも使い方を忘れそうなので側面にラベルを貼った。


 WiFiのアクセスポイントなどの情報は、microSD内のsetup.txtファイルに記載する。
# 温度ロガー設定ファイル
# 例)
# SSID mySSID
# PW   myPassWord
# Interval 0  (0:1秒, 1:10秒, 2:30秒, 3:1分, 4:5分, 5:10分)
#
SSID mySSID
PW myPassWord
Interval 0

側面のmicroSDスロット。


反対側側面からUSB-TypeCで充電中。小さな穴から充放電モジュールの赤色LEDが点灯しているのが分かる。


充電が完了すると青色に光る。




以下、測定までのシーケンス。

電源を入れるとWiFiに接続する。黄色いタクトスイッチを押しながら電源を入れるとWiFiには接続しない。その際のタイムスタンプは2026.1.1 00:00:00になる。WiFi接続に失敗しても同様。


接続完了。


測定時間間隔の設定。1秒、10秒、30秒、1分、5分、10分で指定でき、黄色いタクトスイッチを押すと切り替わる。デフォルトはmicroSD内のsetup.txtに記載。無操作で5秒経過すると、測定開始。


測定開始。測定は指定した時刻が時間間隔で正時になったとき。例えば10秒毎なら0秒、10秒、20秒…のときに測定し、1分毎なら毎分0秒のときに測定する。


これまでの経緯。温度センサの値を読み取るAD変換の補正法などChatGPTに聞いて試行錯誤したので最初の頃と最終版は異なっている。



需要があるか分かりませんが、回路図(クリックで拡大)とスケッチ(キレイではない)です。いずれも無保証です。


処理は1秒毎のタイマ割り込みでスリープから目覚めて、そのときの時刻が測定時間間隔の正時と一致したら測定してmicroSDに書き出して、またスリープする。一致しないとすぐにスリープ。

//---------------------------------------------------------------------------------------
//                                                                         '26.01.31 naka
//                                                                   ver.0 '25.09.14 naka
//  ESP32 2ch温度ロガー(小型液晶に時刻も表示)
//
//  ・測定の正確な日時はWiFi経由のntpサーバから取得
//  ・microSDのsetup.txtファイルに平文でWiFiのSSID,Password 及び測定時間間隔の
//    デフォルトの番号を記載(0:1秒, 1:10秒, 2:30秒, 3:1分, 4:5分, 5:10分)
//    例)SSID mySSID
//        PW   myPassWord
//        Interval 0
//  ・時間間隔は電源投入後にタクトスイッチでも変更可能(5秒経過すると測定を開始)
//  ・WiFiがない環境で使う際は、タクトスイッチを押しながら電源オン
//  ・WiWi設定があっても繋がらない場合には、複数回トライして諦める
//  ・WiWiがない場合、或いは何度トライして繋がらない場合には日時を26年1月1日00:00:00とする
//  ・ログはmicroSD内にタイムスタンプ付のcsvファイルとして格納する
//  ・ファイル名が重複する場合にはファイル名に追番が付く
//  ・WiFiに繋がない、繋がらない、ntpサーバに繋がらない際の日時は"2026-01-01 00:00:00"となる
//  ・測定は指定した時刻が時間間隔で正時になったとき
//    例)10秒毎なら0秒、10秒、20秒…のとき、1分毎なら毎分0秒のとき
//  ・スリープから1秒毎の割り込みで目覚めて正時になったか確認(再びスリープ)
//---------------------------------------------------------------------------------------
#include <WiFi.h>
#include <WiFiClientSecure.h>
#include <ESP32Time.h>
#include <SD.h>
#include "FS.h"
#include "SPIFFS.h"
#include "esp_adc_cal.h"
ESP32Time rtc;

#define SD_CS 16   // SD card chip select
#define TACT_SW 25 // タクトスイッチ(WiFi未使用指示、測定時間間隔セット用)
const int SENSOR_PIN1 = 32; // 温度センサMCP9700Aの出力ピンを接続したADCピン
const int SENSOR_PIN2 = 33; // 温度センサMCP9700Aの出力ピンを接続したADCピン

esp_adc_cal_characteristics_t adc_chars;

#define SETUP_FILE "/setup.txt"
#include <FaBoLCDmini_AQM0802A.h>
FaBoLCDmini_AQM0802A lcd;

// microSDのsetupファイルに記載されているものが設定される
char ssid[24];
char password[24];

int interval;
int interval_i = 0;
const int interval_tbl[] = {1,10,30,60,300,600}; // 測定間隔(秒)
const int interval_tbl_size = 6;
char LogFile[64]; // 記録用のcsvファイル名(タイムスタンプから自動作成)

void setup()
{
  char msg[17];
  struct tm timeinfo;
  char logfilename[64];

  pinMode(TACT_SW, INPUT);
  pinMode(SENSOR_PIN1, ANALOG);
  pinMode(SENSOR_PIN2, ANALOG);

  analogReadResolution(12); // 分解能12bit
  analogSetAttenuation(ADC_11db);

  // ADCキャリブレーション
  esp_adc_cal_characterize(
    ADC_UNIT_1,           // GPIO32?39
    ADC_ATTEN_DB_11,      // 0?3.3V
    ADC_WIDTH_BIT_12,
    1100,                 // デフォルトVref(mV)
    &adc_chars
  );

  // モニタLCD設定
  SetupLCD();
  delay(1000);

  // setupファイルを読み、WiFiのssid,password、測定間隔のデフォルトを取得
  if (!SD.begin(SD_CS, SPI, 24000000)) {
    dispLCD_msg("microSD failed  ","                ");
    while(true); 
  }
  read_setup_file(SETUP_FILE);

  bool wifi_flag = false;
  if (digitalRead(TACT_SW)==LOW) {
    dispLCD_msg("WiFi dosen't use","                ");
    while(digitalRead(TACT_SW)==LOW);
  }
  else {
    dispLCD_msg("Connecting WiFi ","                ");
    if (wifi_connect()) {
      wifi_flag = true;
    }
    else {
      dispLCD_msg("Connect failed  ","                ");
      delay(1000);
    }
  }
  delay(1000);

  if (wifi_flag) {
      // WiFi接続に成功した場合、NTPサーバに接続し、時刻設定
      configTime(9 * 3600L, 0, "ntp.nict.jp", "time.google.com", "ntp.jst.mfeed.ad.jp"); 
      int loopcnt = 0;
      while (!getLocalTime(&timeinfo)) {
        dispLCD_msg("Failed get time ","Retrying ...    ");
        if (++loopcnt>5) { // 念のため5回繰り返してダメなら抜ける
          wifi_flag = false;
          break; 
        }
        delay(500);
      }
    dispLCD_msg("                ","                ");
    wifi_disconnect(); // 以降はWiFi未接続にする
  }

  // 起動時にSWが押されていたか、WiFi接続に失敗か、ntpサーバからの時刻取得に失敗した場合は下記の日付と時刻設定
  if (!wifi_flag) {
    rtc.setTime(1767225600); // 2026-01-01 00:00:00		
  }

  // 測定時間間隔の設定(5秒無操作で戻ってくる)
  set_interval();

  // ログファイル名を決めるためにタイムスタンプ取得
  getLocalTime(&timeinfo);
  sprintf(logfilename, "%02d%02d%02d_%02d%02d%02d.csv",(timeinfo.tm_year + 1900)%100, timeinfo.tm_mon + 1, timeinfo.tm_mday,timeinfo.tm_hour, timeinfo.tm_min, timeinfo.tm_sec);

  // WiFiに繋がない場合、時刻情報を取得できないためタイムスタンプのファイル名が同じになることを防ぐ。
  get_unique_filename("",logfilename,LogFile,sizeof(LogFile));

  // 測定開始メッセージ
  dispLCD_msg("Start           ","     measurement");

  // 1,000,000us = 1sのタイマー設定
  esp_sleep_enable_timer_wakeup(1000000);

}

void loop()
{ 
  // 温度を測定してログファイルに書き出す
  measurement(interval);

  // ライトスリープ(タイマー割り込みで1秒毎に目覚める)
  esp_light_sleep_start();
}

void set_interval() {
  disp_interval(interval_i); // 現在の設定時間を表示

  int prevmsec = millis();
  while ((millis() - prevmsec)<5000) {  // 5秒反応が無ければ時間設定を抜ける
    if (digitalRead(TACT_SW)==LOW) {
      interval_i++;
      prevmsec = millis();
      if (interval_i>=interval_tbl_size) interval_i = 0;

      disp_interval(interval_i); // 現在の設定時間を表示
      while (digitalRead(TACT_SW)==LOW) {}  // SWが離されるまで待つ
      delay(20); //チャタリング対策
      prevmsec = millis();
    }
  }

  interval = interval_tbl[interval_i];
  return;
}

void disp_interval(int i) {
  int time = interval_tbl[i];
  char msg[20];

  if (time<60)
    sprintf(msg,"%11d(sec)",time);
  else
    sprintf(msg,"%11d(min)",time/60);
  dispLCD_msg("set interval    ",msg);
}

void measurement(int period) {
  char yymmddhhmmss[17],hhmmss[10],timestamp[17];
  struct tm timeinfo;
  getLocalTime(&timeinfo);

  // 測定する時間なのか確認(割り込みは1秒毎なので)
  bool flag = false;
  if (period==1) flag = true;
  else if (period==10 && timeinfo.tm_sec%10==0) flag = true; 
  else if (period==30 && timeinfo.tm_sec%30==0) flag = true; 
  else if (period==60 && timeinfo.tm_sec==0)    flag = true; 
  else if (period==120 && timeinfo.tm_sec==0 && timeinfo.tm_min%2==0) flag = true; 

  if (flag) {
    sprintf(timestamp,"%02d:%02d:%02d",timeinfo.tm_hour, timeinfo.tm_min, timeinfo.tm_sec);
    sprintf(yymmddhhmmss, "%02d%02d%02d %s",(timeinfo.tm_year + 1900)%100, timeinfo.tm_mon + 1, timeinfo.tm_mday, timestamp);

    float temp1 = temp_sensing(SENSOR_PIN1);
    float temp2 = temp_sensing(SENSOR_PIN2);
    char buff1[6],buff2[6],buff[20];
    dtostrf(temp1,4,1,buff1);
    dtostrf(temp2,4,1,buff2);
    sprintf(buff, "%4s%cC , %4s%cC",buff1,0xDF,buff2,0xDF);
    dispLCD_msg(yymmddhhmmss,buff);

    write_logfile(timestamp,buff1,buff2);  // microSDに書き込み
  }

}

float temp_sensing(const int sensor) {
  float offset;

  uint32_t voltage_mV = 0;
  const int N = 100;
  for ( int i = 0 ; i < N ; i++ ) {
    int adcRaw = analogRead(sensor);
    // ADC値 → 電圧(mV)(補正済み)
    voltage_mV += esp_adc_cal_raw_to_voltage(adcRaw, &adc_chars);
  }

  // 電圧 → 温度(℃)
  float temperatureC = ((float)voltage_mV/N - 500.0) / 10.0;

  // 温度センサ毎の校正
  if (sensor==SENSOR_PIN1) offset = -0.9;
  else offset = -1.2;

  return temperatureC + offset;
}

void dispLCD_msg(char msg1[],char msg2[]) {
  lcd.clear();
  lcd.setCursor(0, 0); // Col,Raw
  lcd.print(msg1);
  lcd.setCursor(0, 1); // Col,Raw
  lcd.print(msg2);
}

void write_logfile(char* timestamp, char* buff1,char* buff2){
  char buff[32];
  File outputFile = SD.open(LogFile,FILE_APPEND);
  
  sprintf(buff,"%s,%s,%s",timestamp,buff1,buff2);
  outputFile.println(buff);
  outputFile.close();
}

void SetupLCD() {
  delay(150);
  lcd.begin();
  lcd.command(0x38);
  delay(1);
  lcd.command(0x39);
  delay(1);
  lcd.command(0x14);
  delay(1);
  lcd.command(0x71);
  delay(1);
//  lcd.command(0x51);  // 5V
  lcd.command(0x56);  // 3.3V
  delay(2);
  lcd.command(0x6c);
  delay(300);
  lcd.command(0x38);
  delay(1);
  lcd.command(0x01);
  delay(2);
  lcd.command(0x0c);
  delay(2);
}

// Wifi接続
bool wifi_connect() {
  char msg[128];
  bool connected = false;

  for (int i=0;i<3;i++) { // WiFi接続、3回繰り返す
    WiFi.begin(ssid, password);    
    long int StartTime=millis();
    while (WiFi.status() != WL_CONNECTED) {
        delay(500);
        if ((StartTime+5000) < millis()) break; // 5秒待って繋がらないときは諦める
    } 
  
    if (WiFi.status() == WL_CONNECTED) {
      IPAddress ip = WiFi.localIP();
      sprintf(msg,"%d.%d.%d.%d",ip[0], ip[1], ip[2], ip[3]);
      dispLCD_msg("Connected IP    ",msg);
      connected = true;
      break;
    }
    delay(100);
  } 

  if (connected)
    return true;
  else {
    wifi_disconnect();
    delay(100);
    return false;
  }

}

void wifi_disconnect() {
  WiFi.disconnect(true);
  WiFi.mode(WIFI_OFF);
}

int read_setup_file(char* fileName) {
  char  buff[128];
  char  key[128],val[128];

  File setupFile = SD.open(fileName,FILE_READ);
  if (setupFile) {
    while (readLine(setupFile,buff)>=0) {
      if (buff[0]!='#') {
        sscanf(buff,"%s %s",&key,&val);
        if (strcmp(key,"SSID")==0) {
          strcpy(ssid,val);
        }
        else if (strcmp(key,"PW")==0) {
          strcpy(password,val);
        }
        else if (strcmp(key,"Interval")==0) {
          int ival;
          sscanf(val,"%d",&ival);
            if (ival>=0 && ival<interval_tbl_size) { // 異常値チェック
              interval_i = ival;
            }
          }
      }
    }
    setupFile.close();
  }

  return 0;
}

// microSDから1行読み取る
int readLine(File fp, char *buff){
  int i = 0;
  if(!fp.available()) {
    return -1;
  }
  while (fp.available()) {
    char c = fp.read();
    if (c=='\r');
    else if (c=='\n') break;
    else buff[i++] = c;
  }
  buff[i] = 0;
  
  return i;
}

/**
 * 重複しないファイル名を生成する関数 (ChatGPTに書いてもらった)
 * @param fs_prefix マウントポイント (例: "/spiffs")
 * @param base_name 元のファイル名 (例: "data.txt")
 * @param out_path  結果を格納するバッファ
 * @param max_len   バッファの最大サイズ
 */
void get_unique_filename(const char* fs_prefix, const char* base_name, char* out_path, size_t max_len) {
    char temp_path[128];
    char name_part[64];
    char ext_part[16];
    
    // 拡張子を探す
    const char *dot = strrchr(base_name, '.');
    if (dot) {
        size_t name_len = dot - base_name;
        strncpy(name_part, base_name, name_len);
        name_part[name_len] = '\0';
        strcpy(ext_part, dot);
    } else {
        strcpy(name_part, base_name);
        ext_part[0] = '\0';
    }

    // 最初は元の名前でチェック
    snprintf(out_path, max_len, "%s/%s", fs_prefix, base_name);
    struct stat st;
    int counter = 1;

    // ファイルが存在し続ける間、ループ
    while (SD.exists(out_path)) {
        // "パス/ベース名 + 数字 + 拡張子" を組み立て
        snprintf(out_path, max_len, "%s/%s_%d%s", fs_prefix, name_part, counter, ext_part);
        counter++;
        
        // 無限ループ防止(必要に応じて)
        if (counter > 999) break; 
    }
}

//---------------------------------------------------------------------------------------
// EOF
//---------------------------------------------------------------------------------------