勝手な電子工作・・

勝手なオリジナル電子工作に関する記事を書きます

LCDキャラクターディスプレイを8ピンPICに接続する(コスパ最高?)

f:id:a-tomi:20201222124527j:plain

LCDキャラクター・ディスプレイは余り使われなくなってきたかもしれませんが、廉価で、OLEDと比較にならないほど容量を食わず、今でも電子工作に重宝なデバイスだと思いますがどうでしょう?

多くのマイコンでは標準ライブラリーがあり、I2Cで楽に接続できるという点、それに海外ネットで廉価で出回っている点も助かります。そういうわけで、私はESPやArduinoなどではこれをしばしば使うので手持ちが沢山あります。16桁2行と20桁4行が普通で、私の持っているのは1個あたりそれぞれ200円、400円ぐらいでした。

確認のために今eBayをちらとみると次です。もっと廉価なのも多いかもしれませんし、Aliでも廉価なものが多いだろうなと思います。表示色は青に白字が多いですが、黄色や緑に黒字、あるいは緑に白字などもあります。ただしI2C接続でないものもありますから注意が必要です。

f:id:a-tomi:20201221181656j:plain

LCD自体は相当古い日立のHD44780Uドライバーで動かす、日立液晶ディスプレイのコンパチ機です。それにTIのPCF8574(I2Cパラレルポート・エキスパンダーIC)または同等ICを接続して、I2Cで簡単に動くようになっているものです。

LCD自体は今でも日立との互換LCDであるというところがすごい!どれにも基本的には日立のA00という文字セットが含まれ、多数使われている海外でも、なんとカタカナを表示するところがとてもおかしいのですが^^;

古い日立のデータシートを見ると内蔵文字の基本パターンは次です。

f:id:a-tomi:20201221182623j:plain

海外通販で購入したもので全文字を表示してみたら、次のようになっています。

f:id:a-tomi:20201222180845j:plain

全部比較すると日立のと完全に同じですね!

これを表示したときの簡単なテストの様子は次の写真です。

f:id:a-tomi:20201221205318j:plain

Arduinoでは標準的なLiquidCrystalライブラリーを、New_LiquidCrystal_I2Cライブラリーで置き換えることで最近の型も動作します。前に次の記事の最後に書き足したかと思います。。

マイコンとPCのデータ授受ーSmallbasicなら簡単 (2020.12.05 LiquidCrystal_I2Cについて補足しました) - 勝手な電子工作・・

 

ところが、このLCDのイニシャライズの中身は理不尽なほど複雑です^^;  かつデータはドライバーから4ビットパラレル接続となっているため、ライブラリーなしで直接扱うときは1文字を2回に分けて送る必要があったりします。つまりプロトコルも輪をかけて複雑に。

今回はこの廉価なLCDを小さな8ピンPICに簡単につなぐ挑戦です。いつかはPICにつなごうと思っていましたが、他のI2Cデバイスはともかくも、このLCDだけは上に書いたような理由でコードの手書きは敬遠していました。

しかし、この週末にPICでの手作りを遂にやってみました!

 

今回のPICは5~6年前に1個60円ほどで大量購入した12F1822なのですが、手元に残っているので利用。今見ると秋月では110円に値上がりしたようですね。もちろん8ビットPICならどれでもほぼ大丈夫と思います。

この小さなPICはプログラムメモリーが2Kしかなく、RAMはわずか128バイトしかありません。しかし16MHzクロックで動きPIC Assemblerで書けばかなりのことができる優れものです。周辺装置の役割として何にでも便利に使っています。ライブラリーを使わない場合は容量は十分です。

 

まずは複雑プロトコルを解析するために、使う機能だけを選んでArduinoでごく簡単なテストをします。次のスケッチです。

/**********************************************************
      Simple test-A for protocol analysis
                        Dec. 19, 2020  by Akira Tominaga
           Remarks: Use Arduino, ESP32, or ESP8266 etc.
                    Connect SCL and SDA to LCD-I2C pins.
***********************************************************/
#include "LiquidCrystal_I2C.h" // New_LiquidCrystal_I2C library
#define i2cA 0x27           // LCD's I2C address
// addr, LCD-pins, BL  addr,en,rw,rs,d4,d5,d6,d7,bl,blpol
LiquidCrystal_I2C lcd(i2cA, 2, 1, 0, 4, 5, 6, 7, 3, POSITIVE);

void setup() { // ***** Arduino setup *****
  delay(10);

  // case #1 lcd.begin
  lcd.begin(20, 4);
  delay(300);

  // case #2 lcd.backlight() (unneeded?)
  lcd.backlight();
  delay (200);

  // case #3 lcd.clear
  lcd.clear();              // clear LCD
  delay(100);

  // case #4 cursor  line 3 ,col 15
  lcd.setCursor(15, 3);
  delay(200);

  // case #5 cursor line 2 ,col 8
  lcd.setCursor(8, 2);
  delay(300);

  // case #6 cursor line 1 ,col 5
  lcd.setCursor(5, 1);
  delay(200);

  // case #7 cursor line 0 ,col 0
  lcd.setCursor(0, 0);
  delay(100);

  // case #8 lcd.print
  lcd.print("ABC");
  lcd.print("123");

  while (true) {}         // stop here
}

void loop() { // ***** Arduino loop *****
  // do nothing
}

 

このプロトコルを記録してロジックアナライザーで解析します。

f:id:a-tomi:20201221185302j:plain

Decode結果は次のとおりです。

Time [s]	 Decoded Protocol Result
0.00000925	Setup Write to [0x4E] + ACK
150μS	1)
0.00014925	Setup Write to [0x4E] + ACK
0.0002445	0x00 + ACK
100mS	2)
0.10040025	Setup Write to [0x4E] + ACK
0.1004955	0x34 + ACK
0.10063475	Setup Write to [0x4E] + ACK
0.10073	0x30 + ACK
5mS	3)
0.105399	Setup Write to [0x4E] + ACK
0.10549425	0x34 + ACK
0.1056335	Setup Write to [0x4E] + ACK
0.10572875	0x30 + ACK
250μS	4)
0.106023	Setup Write to [0x4E] + ACK
0.106118	0x34 + ACK
0.10625725	Setup Write to [0x4E] + ACK
0.1063525	0x30 + ACK
250μS	5)
0.1066465	Setup Write to [0x4E] + ACK
0.10674175	0x24 + ACK
0.10688075	Setup Write to [0x4E] + ACK
0.106976	0x20 + ACK
250μS	6)
0.10727025	Setup Write to [0x4E] + ACK
0.1073655	0x24 + ACK
0.1075045	Setup Write to [0x4E] + ACK
0.10759975	0x20 + ACK
0.10774875	Setup Write to [0x4E] + ACK
0.107844	0x84 + ACK
0.10798325	Setup Write to [0x4E] + ACK
0.10807825	0x80 + ACK
200μS	7)
0.1082825	Setup Write to [0x4E] + ACK
0.10837775	0x04 + ACK
0.10851675	Setup Write to [0x4E] + ACK
0.108612	0x00 + ACK
0.10875625	Setup Write to [0x4E] + ACK
0.10885125	0xC4 + ACK
0.1089905	Setup Write to [0x4E] + ACK
0.10908575	0xC0 + ACK
150μS	8)
0.10922975	Setup Write to [0x4E] + ACK
0.109325	0x04 + ACK
0.10946425	Setup Write to [0x4E] + ACK
0.1095595	0x00 + ACK
0.1097035	Setup Write to [0x4E] + ACK
0.10979875	0x14 + ACK
0.109938	Setup Write to [0x4E] + ACK
0.11003325	0x10 + ACK
2mS	9)
0.11219225	Setup Write to [0x4E] + ACK
0.1122875	0x04 + ACK
0.11242675	Setup Write to [0x4E] + ACK
0.11252175	0x00 + ACK
0.112666	Setup Write to [0x4E] + ACK
0.11276125	0x64 + ACK
0.11290525	Setup Write to [0x4E] + ACK
0.1130005	0x60 + ACK
150μS	10)
0.11313975	Setup Write to [0x4E] + ACK
0.113235	0x08 + ACK
300mS	lcd Backlight
0.4133845	Setup Write to [0x4E] + ACK
0.41347975	0x08 + ACK
200mS	lcd Clear
0.6136425	Setup Write to [0x4E] + ACK
0.61373775	0x0C + ACK
0.613877	Setup Write to [0x4E] + ACK
0.61397225	0x08 + ACK
0.61411625	Setup Write to [0x4E] + ACK
0.6142115	0x1C + ACK
0.61435075	Setup Write to [0x4E] + ACK
0.614446	0x18 + ACK
	Set cursor 15,3
0.716622	Setup Write to [0x4E] + ACK
0.71671725	0xEC + ACK
0.7168565	Setup Write to [0x4E] + ACK
0.71695175	0xE8 + ACK
0.717096	Setup Write to [0x4E] + ACK
0.71719125	0x3C + ACK
0.7173305	Setup Write to [0x4E] + ACK
0.7174255	0x38 + ACK
	Set cursor 8,2
0.9175885	Setup Write to [0x4E] + ACK
0.91768375	0x9C + ACK
0.917823	Setup Write to [0x4E] + ACK
0.91791825	0x98 + ACK
0.91806225	Setup Write to [0x4E] + ACK
0.9181575	0xCC + ACK
0.91829675	Setup Write to [0x4E] + ACK
0.918392	0xC8 + ACK
	Set cursor 5,1
1.2185565	Setup Write to [0x4E] + ACK
1.21865175	0xCC + ACK
1.218791	Setup Write to [0x4E] + ACK
1.21888625	0xC8 + ACK
1.2190305	Setup Write to [0x4E] + ACK
1.21912575	0x5C + ACK
1.21926475	Setup Write to [0x4E] + ACK
1.21936		0x58 + ACK
	Set cursor 0,0
1.419528	Setup Write to [0x4E] + ACK
1.419623	0x8C + ACK
1.41976225	Setup Write to [0x4E] + ACK
1.4198575	0x88 + ACK
1.42000175	Setup Write to [0x4E] + ACK
1.420097	0x0C + ACK
1.42023625	Setup Write to [0x4E] + ACK
1.4203315	0x08 + ACK
	write "ABC123"
1.5204875	Setup Write to [0x4E] + ACK
1.52058275	0x4D + ACK
1.520722	Setup Write to [0x4E] + ACK
1.520817	0x49 + ACK
1.52096125	Setup Write to [0x4E] + ACK
1.5210565	0x1D + ACK
1.52119575	Setup Write to [0x4E] + ACK
1.521291	0x19 + ACK
1.52144025	Setup Write to [0x4E] + ACK
1.52153525	0x4D + ACK
1.5216745	Setup Write to [0x4E] + ACK
1.52176975	0x49 + ACK
1.521919	Setup Write to [0x4E] + ACK
1.52201425	0x2D + ACK
1.5221535	Setup Write to [0x4E] + ACK
1.52224875	0x29 + ACK
1.52239775	Setup Write to [0x4E] + ACK
1.522493	0x4D + ACK
1.52263225	Setup Write to [0x4E] + ACK
1.5227275	0x49 + ACK
1.52287175	Setup Write to [0x4E] + ACK
1.522967	0x3D + ACK
1.52310625	Setup Write to [0x4E] + ACK
1.52320125	0x39 + ACK
1.5233555	Setup Write to [0x4E] + ACK
1.52345075	0x3D + ACK
1.52359		Setup Write to [0x4E] + ACK
1.52368525	0x39 + ACK
1.5238295	Setup Write to [0x4E] + ACK
1.5239245	0x1D + ACK
1.52406375	Setup Write to [0x4E] + ACK
1.524159	0x19 + ACK
1.52430825	Setup Write to [0x4E] + ACK
1.52440325	0x3D + ACK
1.5245425	Setup Write to [0x4E] + ACK
1.52463775	0x39 + ACK
1.524782	Setup Write to [0x4E] + ACK
1.52487725	0x2D + ACK
1.52502125	Setup Write to [0x4E] + ACK
1.5251165	0x29 + ACK
1.52526575	Setup Write to [0x4E] + ACK
1.525361	0x3D + ACK
1.52550025	Setup Write to [0x4E] + ACK
1.52559525	0x39 + ACK
1.5257395	Setup Write to [0x4E] + ACK
1.52583475	0x3D + ACK
1.525974	Setup Write to [0x4E] + ACK
1.52606925	0x39 + ACK

 

Arduinoではこのスレーブ・アドレスは0x27ですが、通信では左に1ビットずらしてWriteの場合はビット0をオフにするので、送出アドレスは0x4Eとなります。

Initialize以外は比較的簡単にわかるプロトコルですが、lcd.setCursor(column,row)のプロトコル解析は、まるでクイズのようで少し手こずってしまいました。結局は連立方程式などを作って解き、やっとわかりました(汗)。

使おうとしているプロトコルを「まとめると、次の手順になっています。

f:id:a-tomi:20210318135940j:plain

つまり、細かい検討をしなくともこの通りに作れば必ず動く!というわけです。

今回、PICは次のように配線することにします。毎度手書きのきたない回路図ですみませんが。今回はピンが限られたハードウェアI2C機構を用いず、RA3以外ならどのピンを使ってもOKとなるようにしました。

f:id:a-tomi:20201221185551j:plain

図に書いたLEDは今は必要がないので特につけていません。I2Cの汎用接続が他に使える利点に加えて、3つのピンが空いており、他のセンサー等を色々つけられます^^

マイコンの容量を消耗しないよう、こういうものはアセンブラーでうまく組みます。

;U201219-I2CLCD-V00.asm			     		As of Dec. 19, 2020
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;										;
; 	Teting I2C-Liquid-Crystal-Display with PIC-12F1822 V00-00 		;
;	        (c)2020 Akira Tominaga, All rights reserved.			;
;	  Major revisions							;
;		00-00	Initial Version Dec.19, 2020				;
;										;
;	  Function								;
;		1.Show characters to LCD (New_LiquidCrystal_I2C 4bit op.)	;
;										;
;	  Input/output       							;
;		RA0 LED output when debugging					;
;		RA1 SCL for I2C  output						;
;		RA2 SDA for I2C  output  usually, and input occasionally	;
;										;
;	  Remarks								;
;		1. Clock = HFINTOSC (	16MHz)					;
;			Hence 1 step = 0.25 micro seconds			;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;
	list		p=12F1822      ; list directive to define processor
	#include	"p12F1822.inc" ; Device specific variable definitions
    __CONFIG _CONFIG1, _FOSC_INTOSC & _WDTE_OFF & _PWRTE_OFF & _MCLRE_OFF & _CP_OFF & _CPD_OFF & _BOREN_OFF & _CLKOUTEN_OFF & _IESO_OFF & _FCMEN_OFF
    __CONFIG _CONFIG2, _WRT_OFF & _PLLEN_OFF & _STVREN_OFF & _BORV_LO & _LVP_OFF
;	page
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; 	Macro definitions					;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; 	Device dependent Macros			;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;*** Setting up PIC device ***
DEVset	macro	
	BANKSEL	OSCCON		; Bank=1
	movlw	B'01111010'	; 16MHz and internal oscillator
	movwf	OSCCON
;
;	BANKSEL	INTCON		; Interrupt Con (in all banks hence comment)
	clrf	INTCON		; Disable all interrupts
;
; PORTA initialization
	BANKSEL	PORTA		; Bank=0
	clrf	PORTA
;	BANKSEL	LATA		; Bank=2
;	clrf	LATA
	BANKSEL	ANSELA		; Bank=3
	clrf	ANSELA		; No use of ADC
	BANKSEL	ADCON0		; Bank=1
	clrf	ADCON0		; No use of ADC
;
	BANKSEL	TRISA		; Bank=1
	movlw	B'11111000'	; RA0,1 and 2 are output
	movwf	TRISA
;
	BANKSEL	OPTION_REG	; Bank=1
	bcf	OPTION_REG,7	; Enable weak pull-up
	BANKSEL	WPUA		; Bank=4
	movlw	B'00111000'	; Weak Pull-up for RA3,4,and 5
	movwf	WPUA		;  
;
	clrf	BSR		; Bank=0
	InitP			; Initialize ports
	endm
;
; *** Initializing IO ports ***
InitP	macro			; Initialize ports
	movlw	B'11111110'	; All IOs on, excluding RA0
	movwf	PORTA
	endm
;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; 	I2C Macros for genral purpose		;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;		
; *** for I2C-Master protocol ***
; Sending data to I2C slave
I2Csnd	macro	slave,dataa,datalen
	movlw	slave
	movwf	I2Cadr
	movlw	dataa
	movwf	FSR0L
	movlw	datalen
	movwf	DataLen
	call	I2Csndr
	endm	
;
; *** Receiving data from I2C slave ***
I2Crcv	macro	slave,dataa,datalen
	movlw	slave
	movwf	I2Cadr
	movlw	dataa
	movwf	FSR0L
	movlw	datalen
	movwf	DataLen
	call	I2Crcvr
	endm	
;
; *** I2C start signal ***
I2Cstat macro
	call	I2Cstar
	endm
;
; *** I2C stop signal ***
I2Cstop	macro
	call	I2Cstpr
	endm
;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; 	I2C Macros for New_LiquidCrystal_I2C	;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; ***** for New LiquidCrystal-I2C *****
; *** Start I2C, send LCD adr, and get Ack, without stoP ***
LCDsA	macro
	call	lSndAr		; I2C start and send it
	endm
;
;*** Start, I2C,send LCD adr, and send Literal and stoP ***
LCDl	macro	lcdLit
	movlw	lcdLit
	call	lSndALr
	endm
;
; *** Set cursor (Col, Row) ***
LCDsetC	macro	Col,Row
 	if	Row==0
	movlw	H'80'+Col
 	endif
 	if 	Row==1
	movlw	H'C0'+Col
 	endif
 	if 	Row==2
	movlw	H'94'+Col
 	endif
 	if 	Row==3
	movlw	H'D4'+Col
	endif
 	if 	Row>3
	* row error *
	endif
	call	lSetCsr
	endm
;
; *** lcd.write a char ***
LCDw	macro	lcdChr
	movlw	lcdChr
	call	lSetChr
	endm
;
; *** lcd.clear() ***
LCDclr 	macro
	call	lClr
	endm
;	
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;	Time cosuming macros		;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
Mic	macro			;Consume 1 μS only
	goto	$+1
	goto	$+1
	endm
;
Mic2	macro	mic2p		;Consume 2 μS x n
	movlw	mic2p
	call	Mic2r
	endm
;
Mic2p5	macro 	mic25p		; Consume 2.5μS x n
	movlw	mic25p
	call	Mic25r
	endm
;
Mic5	macro	mic5p		; Consume 5μS x n
	movlw	mic5p
	call	Mic25r
	movlw	mic5p
	call	Mic25r
	endm
;
Mic50	macro	mic50p		; Consume 50μS x n
	movlw	mic50p
	call	Mic50r
	endm
;
Milli	macro	millip		; Consume mS x n
	movlw	millip
	call 	Millir
	endm
;
Mil100	macro	mil100p		; Consume 100 mS x n
	movlw	mil100p
	call 	Mil100r
	endm
; 
Secs	macro	secsp		; Consume Second x n
	movlw	secsp
	call	Secsr
	endm
;
Mins	macro	minsp		; Consume Minute x n
	movlw	minsp
	call	Minsr
	endm
;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;	Debug and Abend macros		;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
LEDon	macro			; LED on macro for debugging
	BANKSEL	TRISdbg		; Select TRIS bank
	btfsc	TRISdbg,LED	; Is LED pin input?
	bsf	Flags,LEDpin	; Yes,show LED pin was input
	bcf	TRISdbg,LED	; Set LED output mode, in any case
	clrf	BSR		; Point Bank 0
	nop			; Timing for PORTdbg changed
	bsf	PORTdbg,LED	; LED on
	goto	$+1		; Timing for PORTdbg changed
	endm
;
LEDoff	macro			; LED off macro for debugging
	goto	$+1		; Timing in case PORTdbg changed
	bcf	PORTdbg,LED	; LED off
	btfss	Flags,LEDpin	; Was LEDpin input before debug?
	goto	$+7		; No, skip the followings
	BANKSEL	TRISdbg		; Select TRIS register
	bsf	TRISdbg,LED	; Set LED input mode again
	nop			; nop for timing
	BANKSEL	WPUdbg		; Bank for WPUx
	bsf	WPUdbg,LED	; Weak Pull-up for LED port
	clrf	BSR		; Point Bank 0
	nop			; Timing for PORTdbg changed
	endm 
;
Udebug	macro 	Addr
	movf	Addr,W
	call	Udbgr
	endm
;
Trigon	macro			; DSO trigger on for debugging
	BANKSEL	TRISdbg		; Select TRIS bank
	btfsc	TRISdbg,Trig	; Is Trig pin input?
	bsf	Flags,Trigpin	; Yes, show Trig pin was input
	bcf	TRISdbg,Trig	; Set Trig output mode, in any case
	clrf	BSR		; Point Bank 0
	nop			; Timing for PORTdbg changed
	bsf	PORTdbg,LED	; Trig on
	goto	$+1		; Timing for PORTdbg changed
	endm
;
Trigoff macro			; DSO trigger off for debugging
	goto	$+1		; Timing in case PORTdbg changed
	bcf	PORTdbg,Trig	; Trig off
	btfss	Flags,Trigpin	; Was Trigpin input before debug?
	goto	$+7		; No, skip the followings
	BANKSEL	TRISdbg		; Select TRIS register
	bsf	TRISdbg,Trig	; Set Trig input mode again
	nop			; nop for timing
	BANKSEL	WPUdbg		; Bank for WPUx
	bsf	WPUdbg,Trig	; Weak Pull-up for Trig port
	clrf	BSR		; Point Bank 0
	nop			; Timing for PORTdbg changed
	endm
;
Uabend	macro	abn		; User Abnormal-end number
	movlw	abn
	goto	Uabendr
	endm
;
Ublink	macro	bno		; Blink LED for specified times
	movlw	bno
	call	Ublinkr
	endm
;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; 	Files and Equations					;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;	Files				;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
	cblock	H'20'
; For Application support
	Abyte		; LCD I2C addr with R/W bit
	Dbyte		; Data byte to save Sbyte
	Ebyte		; The same purpose as Dbyte
	Wbyte		; Working byte
;
; For I2C protocols
	I2Cadr		; Destinated slave I2C address
	DataLen		; Number of bytes to be sent/received
	Sbyte		; One byte to be sent/received
	Bitctr		; Loop counter for bits in a byte 
;
; Areas for time consuming subroutines
; Do not change the sequences from Mic5c to Minsc
;	if co-used with calculation parameters
	Mic25c
	Mic50c
	Millic
	Mil100c
	Secsc
	Minsc
;
; Areas for debugging routines
	DmpA 		; Display byte area 
	Dmp8C 		; Bit counter for loop (Initial Dmp8V =8)
	BlinkC 		; Counter for blinking (set for Debugb or Abendb)
	Abendn		; Abend number
	Ucpc		; User Check point chr to trace 
	Flags
	endc		
;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; 	Equations			;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; 	For PORTA		;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;LED	equ	0		; PORTdbg LED  for debugging
;Trig	equ	0		; PORTdbg Trig for debugging
;Dl	equ	2		; PORTI2C SDA
;Cl	equ	1		; PORTI2C SCL
;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; ***	Logical PORTI2C		; Change this when port changed
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
PORTI2C equ	PORTA		; 
Dl	equ	2		; I2C Data line = SDA
Cl	equ	1		; I2C Clock line = SCL
LATI2C	equ	LATA		; LATch for I2C port
TRISI2C equ	TRISA		; TRIS reg for I2C
;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; ***	Logical PORTdbg		; Change this when port changed
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
PORTdbg	equ	PORTA
TRISdbg	equ	TRISA
WPUdbg	equ	WPUA
LED	equ	0		; LED when debugging
Trig	equ	0		; Trigger for DSO when debugging
;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;	Values & Symbols	;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; Equations for LCD 
LCDi2cA	equ	H'27'		; LCD I2C address (for Arduino)
lcdAw	equ	LCDi2cA*2	; LCD I2C address<<1 and Write
lcdAr	equ	LCDi2cA*2+H'01' ; LCD I2C address<<1 and Read
;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; 	For Debug		;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
PORTdbg	equ	PORTA		;
Trig	equ	0		; DSO trigger pulse port for test use
;
Dmp8V	equ	D'8'		; Debug display bit counter initial value
Debugb	equ	D'8'		; number of blinkings to show Debugging
Abendb	equ	D'25'		; number of blinkings to notify Abend
; Flags byte
LEDsv	equ	0		; LED save bit in Flags byte
LEDpin	equ	1		; LED pin was input when not debugging
Trigpin	equ	2		; Trigger pin was input before debug use
;
	page
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;	Initializing						;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
	org	0
	goto	Startp		; Go to start entry
	org	4		; This is Interrupt entry
	retfie			; 
;
Startp	DEVset			; Define ports
	clrf	FSR0H		; Clear FSR0H forever
	clrf	FSR1H		; Clear FSR1H forever
	clrf	Flags		; Clear all bits of Flags
;
	Milli	D'50'		; Wait devices stabilized
	call	LCDinir	 	; Set-up LCD
;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;	Main program loop					;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
Mainp	equ	$
	LCDsetC	2,0
	LCDw	A'M'
	LCDw	A'e'
	LCDw	A'r'
	LCDw	A'r'
	LCDw	A'y'
	LCDw	A' '
	LCDw	A'C'
	LCDw	A'h'
	LCDw	A'r'
	LCDw	A'i'
	LCDw	A's'
	LCDw	A't'
	LCDw	A'm'
	LCDw	A'a'
	LCDw	A's'
;
	LCDsetC 8,1
	LCDw	A'a'
	LCDw	A'n'
	LCDw	A'd'
;
	LCDsetC 2,2
	LCDw	A'H'
	LCDw	A'a'
	LCDw	A'p'
	LCDw	A'p'
	LCDw	A'y'
	LCDw	A' '
	LCDw	A'H'
	LCDw	A'o'
	LCDw	A'l'
	LCDw	A'i'
	LCDw	A'd'
	LCDw	A'a'
	LCDw	A'y'
	LCDw	A's'
	LCDw	A'!'
;
	LCDsetC 9,3
	LCDw	A'('
	LCDw	A'8'
	LCDw	A'_'
	LCDw	A'p'
	LCDw	A'i'
	LCDw	A'n'
	LCDw	A'_'
	LCDw	A'P'
	LCDw	A'I'
	LCDw	A'C'
	LCDw	A')'
;	
	Secs	1		; delay
	goto	Mainp		; Continue infinite loop
;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;	LCDini= LCD initializing routine			;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
LCDinir	equ	$
	LCDsA			; Send LCD addr
	I2Cstop
;
	LCDl	H'00'		; Start,send adr & 0x00, and Stop
	Mil100	1
;
	LCDl	H'34'
	LCDl	H'30'
	Milli	5
;
	LCDl	H'34'
	LCDl	H'30'
	Mic50	4
;
	LCDl	H'34'
	LCDl	H'30'
	Mic50	4
;
	LCDl	H'24'
	LCDl	H'20'
	Mic50	4
;
	LCDl	H'24'
	LCDl	H'20'
	LCDl	H'84'
	LCDl	H'80'
	Mic50	4
;
	LCDl	H'04'
	LCDl	H'00'
	LCDl	H'C4'
	LCDl	H'C0'
	Mic50	3
;
	LCDl	H'04'
	LCDl	H'00'
	LCDl	H'14'
	LCDl	H'10'
	Milli	2
;
	LCDl	H'04'
	LCDl	H'00'
	LCDl	H'64'
	LCDl	H'60'
	Mic50	3
;
	LCDl	H'08'		; backlight on
	Milli	1
	return
;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;	LCD slave-adr sending routine			;
;	   	with address in Sbyte, getting Ack.	;
;		(start-I2C included, without stop-I2C)	;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
lSndAr	equ	$
	I2Cstat			; start I2C
	movlw	lcdAw		; get LCD slave adr
	movwf	Sbyte		; set it to Sbyte
	call	Sendr		; and send it
	return
;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;	LCD I2C adr and a Literal sending routine	;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
lSndALr	movwf	Dbyte		; save literal to Dbyte
	call	lSndAr		; send adr
	movf	Dbyte,W		; get saved literal
	movwf	Sbyte
	call	Sendr		; send it
	I2Cstop			; stop I2C
	return
;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;	Set-cursor routine for LCD called by lcdSetC	;			;
;	If W=0xab, send 0xaC, 0xa8, 0xbC, and 0xb8	;			;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
lSetCsr	movwf	Ebyte		; save Value into Ebyte
	call	lSetCss 	; send left nibble
	swapf	Ebyte,W		; set W w/ swapping nibbles 	
	call 	lSetCss		; send right nibble
;
	Mic50	1
	return
;
; 	lSetCss subroutine to send a nibble
lSetCss movwf	Wbyte
	movlw	H'0F'
	iorwf	Wbyte,F		; set xF
	movlw	H'FC'
	andwf	Wbyte,W		; change it to xC
	movwf	Wbyte		; set it to Wbyte, too
	call 	lSndALr		; send S, adr, nibble+C, and P
;
	movlw	B'11111011'	; get complement of 0C-08
	andwf	Wbyte,W		; and change C to 8, ie x8
	call	lSndALr		; send S, adr, nibble+8, and P
	return
;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;	LCD send-char routine			;
;	If W=0xab, send 0xaD, 0xa9, 0xbD, and 0xb9	;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
lSetChr movwf	Ebyte		; save Value into Ebyte
;	sending left nibble
	call	lSetChs
;
;	sending right nibble
	swapf	Ebyte,W		; replace left nibble w/ right
	call	lSetChs
	return
;
; 	LCD subroutine to send a character  ;
lSetChs	movwf	Wbyte		; W to Wbyte
	movlw	H'0F'		; set right nibble bits of 
	iorwf	Wbyte,F		;   Wbyte on
	movlw	H'FD'		; leave left nibble + right D
	andwf	Wbyte,W		;  into W
	movwf	Wbyte		; save it into Wbyte
	call 	lSndALr		; send S, adr, nibble+D, and P
;
	movf	Wbyte,W
	movlw	B'11111011'	; target bit 2 to off (D to 9)
	andwf	Wbyte,W		; make left nibble + right 9
	call	lSndALr		; send S, adr, nibble+9, and P	
	return
;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;	LCD.clear routine				;
;	Send 0x8C, 0x88, 0x0C, and 0x08			;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
lClr	equ	$
	LCDw	H'8C'
	LCDw	H'88'
	LCDw	H'0C'
	LCDw	H'08'
	Mic50	1
	return
;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;	I2C Send byte-string for general purpose	;
;         when calling,					;
;		I2C device adr to be set in I2Cadr byte	;
;		Data length in DataLen			;
;		Data Addr in FSR0L (INDF0)		;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
I2Csndr	movf	I2Cadr,W
	movwf	Sbyte
	bcf	Sbyte,0		; indicate write
;
	I2Cstat			; Start I2C
	call	Sendr

Sdata	movf	INDF0,W		; Get a sending character
	movwf	Sbyte		; Set it to Sbyte
	call	Sendr		; Send it to the device
;
	incf	FSR0L,F		; point next char
	decfsz	DataLen,F	; Have all chars sent ?
	goto	Sdata		; No, loop
;
; Stop signal and return
	I2Cstop
	Mic50	1
	return	
;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; 	I2C Receive byte-string	for general purpose	;
;         when calling,					;
;		I2C device adr to be in I2Cadr byte	;
;		Data length in DataLen			;
;		Data Addr in FSR0L (INDF0)		;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
I2Crcvr	equ	$
	I2Cstat			; Start I2C
	movf	I2Cadr,W	; Get I2C dev addr
	movwf	Sbyte		; Set it into Sbyte
;	rlf	Sbyte,F		; Shift left (if adr for Arduino)
	bsf	Sbyte,0		; indicate read
	call	Sendr		; Send slave addr to read
;
I2Crbyt	equ	$
	BANKSEL	TRISI2C		; 
	bsf	TRISI2C,Dl	; Set Dline input mode
	clrf	BSR		; Point Bank 0
;
	movlw	8		; Bits in a reciving byte
	movwf	Bitctr		; into loop counter 
I2Crblp	Mic5	1		; Timing before raising clock
	bsf	PORTI2C,Cl	; Set clock high
	Mic2p5	1		; Timing after raising clock
;
	btfsc	PORTI2C,Dl
	goto	Rbith
;	goto	Rbitl
;
Rbitl	bcf	Sbyte,0		; Clear bit 0
	goto	Rbitnxt		; and goto next
;
Rbith	bsf	Sbyte,0		; Set bit 0
Rbitnxt	Mic2p5	1		; Timing after checking
	bcf	PORTI2C,Cl	; Set clock low
;	Mic2p5	1		; Timing after clock falling
	decfsz	Bitctr,F	; Still bits?
	goto	Rbcont		; Yes, continue
	goto	Rbend		; No, end of one byte
;
Rbcont	rlf	Sbyte,F		; Shit left
	goto	I2Crblp		; and goto loop
;
Rbend	movf	Sbyte,W		; Get received byte
	movwf	INDF0		; Set it to INDF0
	incf	FSR0L,F		; increase index
	decfsz	DataLen,F	; DataLen-1
	goto	Sackr		; Send Ack and continue
	goto	Snackr		; Send Nack and stop
;
;Set Dl output mode and send Ack to conitinue
Sackr	equ	$
	BANKSEL	LATI2C		; Data line high when output mode
	bsf	LATI2C,Dl	; Lat-Dl on
	BANKSEL	TRISI2C		; 
	bcf	TRISI2C,Dl	; Set Dl output mode
	clrf	BSR		; Point Bank 0
;
	bcf	PORTI2C,Dl	; Send Ack
	Mic2p5	1		; Timing after clock falling
	bsf	PORTI2C,Cl	; Show Ack
	Mic2p5	2
	bcf	PORTI2C,Cl	; Set clock low
	Mic2p5	1
	bsf	PORTI2C,Dl	; Return Dline to high
	goto	I2Crbyt		; Next byte process
;
;Set Dl output mode and send Nack
Snackr	equ	$
	BANKSEL	LATI2C		; Data line high when output mode
	bsf	LATI2C,Dl	; Lat-Dl on
	BANKSEL	TRISI2C		; 
	bcf	TRISI2C,Dl	; Set Dl output mode
	clrf	BSR		; Point Bank 0
;
	bsf	PORTI2C,Dl	; Send Nack
	Mic2p5	1		; Timing after clock falling
	bsf	PORTI2C,Cl	; Show Nack
	Mic2p5	2
	bcf	PORTI2C,Cl	; Set clock low
	Mic2p5	2
	I2Cstop			; and stop I2C once
	Mic5	D'10'		; for 50 micro sec
;
	I2Cstat			; then start I2C again
	movf	I2Cadr,W	; Get I2C dev addr
	movwf	Sbyte		; Set it into Sbyte
	rlf	Sbyte,F		; Shift left
	bcf	Sbyte,0		; indicate write
	call	Sendr		; Send it
;
	I2Cstop
	return
;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;	I2C Start signal routine 			;
;	   	when dataline mode is output		;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
I2Cstar	bcf	PORTI2C,Dl	; set SDA low
	Mic2p5	1
	bcf	PORTI2C,Cl	; set SCL low
	Mic2p5	3
	return
;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;	I2C Stop signal routine 			;
;	   	when dataline mode is output		;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
I2Cstpr	bcf	PORTI2C,Cl	; set SCL low
	Mic2p5	1
	bcf	PORTI2C,Dl	; set SDA low
	Mic2p5	4
	bsf	PORTI2C,Cl	; Clock line rasing
	Mic2p5	2
	bsf	PORTI2C,Dl	; Data line rasing
	Mic2p5	1
	return	
;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;	I2C One-byte sending routine + getting Ack	;
;	   	with data in Sbyte			;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
Sendr	movlw	8		; Set bit count in a byte
	movwf	Bitctr		; into loop counter 
;
Sloop	btfsc	Sbyte,7		; Check top bit of Sbyte
	goto	Slpdh		; 
	bcf	PORTI2C,Dl	; If bit low then Data L
	goto	Slpnxt
Slpdh	bsf	PORTI2C,Dl	; If bit high then Data H
;
Slpnxt	Mic2p5	1
	bsf	PORTI2C,Cl	; Show bit
	Mic2p5	2
	bcf	PORTI2C,Cl	;
	Mic2p5	1
;
	rlf	Sbyte,F		; Shift left 
	decfsz	Bitctr,F        ; All bits done?
	goto	Sloop		; No, loop within a byte
;	
;Receive Ack 		
; set data line input mode
	BANKSEL	TRISI2C		; 
	bsf	TRISI2C,Dl	; Set Dline input mode
	clrf	BSR		; Point Bank 0
;
	Mic2p5	1		; wait for Ack timing
	bsf	PORTI2C,Cl	; Clock H for Acq confirmation
	Mic2p5	2		; 
	bcf	PORTI2C,Cl	; Cl L for Dl release by slave
	Mic2p5	2
;
; Set Data line output
	BANKSEL	LATI2C		; Data line high when output mode
	bsf	LATI2C,Dl	; Lat-Dl on
	BANKSEL	TRISI2C		; 
	bcf	TRISI2C,Dl	; Set Dl output mode
	clrf	BSR		; Point Bank 0
	return
;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;	Timing subrooutines for general purposes		;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; 	Make 2.0 micro S x n	(Mic2)	;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
Mic2r	movwf	Mic25c		; + Wset + call = 1 micro sec 
;
Mic2l	decfsz	Mic25c,F	; If exhausted, 1 micro S hereafter
	goto	Mic2li		; else go out (2nd time 1.75 mic sec)
	return		
;
Mic2li	goto	$+1		;            (2nd time 2.25 mic sec)
	nop			;	     (2nd time 2.5 mic sec)
	goto	Mic2l		; go back    (2nd time 3 micro sec)
;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; 	Make 2.5 micro S x n (Mic2p5)	;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
Mic25r	movwf	Mic25c		; + Wset + call = 1 micro sec 
	nop			; 1.25 micro sec
;
Mic25l	nop			; 1.5 micro sec (2nd time 4 mic sec)
	decfsz	Mic25c,F	; If exhausted, 1 micro S hereafter
	goto	Mic25li		; else go out (2nd time 2.25 mic sec)
	return		
;
Mic25li	Mic			;	      (2nd time 3.25 mic sec)
	goto	Mic25l		; go back    (2nd time 3.75 micro sec)
;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;	50 Microseconds	x n	Mic50  	;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;
Mic50r	movwf	Mic50c	 	; set how many 50 microsec (1 micro sec to here)
	nop			; 1.25 micro sec up to here
;
Mic50l	Mic2p5	D'19'		; + 47.5 = 48.75 mic sec (2nd time 98.75 mic sec)
	nop			; + 0.25 = 49 micro sec (2nd time 99 mic sec)
; 
	decfsz	Mic50c,F	; If exhausted then 1 mic S hereafter
	goto	Mic50li		; else go out (2nd time 49.75 mic sec)
	return
;
;
Mic50li	Mic			; 	  (2nd time 50.75 mic sec)
	goto	Mic50l		; go back (2nd time 51.25 mic sec)
;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;	Milliseconds x n	(Milli)	;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;
Millir	movwf	Millic 		; set how many 1 mil sec (1 mic S up to here)
	nop			; 1.25 micro sec
;
Millil	Mic50	D'19'		; + 50 mic x 19 = 951.25 mic S (2nd, 1951.25) 
	Mic2p5	D'19'		; + 47.5 mic = 998.75 micro S  (2nd, 1998.75)
	nop			; +0.25 mic = 999 micro sec    (2nd, 1999)
;
	decfsz	Millic,F	; If  exhausted then 1 micro sec hereafter
	goto	Millili		; else go out (2nd, 999.75 mic S)
	return
;
Millili	Mic			; 		(2nd time 1000.75 mic S)
	goto	Millil		; go back (2nd time 1001.25 mic S)
;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;	100 Milliseconds x n	(Mil100);
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
Mil100r	movwf	Mil100c		;set how many 100 ms(1 micr sec up to here)
	nop			; 1.25 micro sec
;
Milhl	Milli	D'99'		;+1ms x 99 = 99001.25 micS (2nd,199001.25mic)
	Mic50	D'19'		; + 950 mic = 99951.25 micS(2nd.199951.25mic)
	Mic2p5	D'19'		; + 47.5 mic = 99998.75micS(2nd,199998.75mic)
	nop			; + 0.25 mic = 99999 mic S (2nd,199999 micS)
;
	decfsz	Mil100c,F	; If exhausted then 1 micro sec hereafter
	goto	Milhli		; else go out (2nd time, 99999.75 mic S)
	return
;
Milhli	Mic			;  	    	(2nd time, 100000.75 mic S)
	goto	Milhl		; 		(2nd time, 100001.25 mic S)
;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;	Seconds x n	 (Secs)		;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;
Secsr	movwf	Secsc 		; set how many sec ( 1 mic sec up to here)
	nop			; 1.25 micro sec
;
Secsl	Mil100	D'9'		; 
	Milli	D'99'		; + 999 milli sec = 999001.25 micro sec
;
	Mic50	D'19'		; + 950 mic = 999951.25 micro sec
	Mic2p5	D'19'		; + 47.5 mic = 999998.75 micro sec
	nop			; + 0.25 mic = 999999 micro sec
;
	decfsz	Secsc,F		; If exhausted then 1 micro sec hereafter
	goto	Secsli		; else, go out 
	return
;
Secsli	Mic
	goto	Secsl		; (Second time, Sec + 1.25 micro sec)
;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;	Minutes	x n	 (Mins)		;
;	 Overhead ignored, that is only	;
;	 751.25 Mic S even when 100 Min	;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
Minsr	movwf	Minsc		;set how many minutes from parameter
;
Minsl	Secs	D'60'		; 1 Seconds x 60
	decfsz	Minsc,F
	goto	Minsl
	return
;
	space
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; User debug subroutine		;
;	Show bit 7 to 0 	;
;	of specified byte	;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
Udbgr	clrf	BSR		; Set Bank=0
	movwf	DmpA		; move data to Dmpa
;
	btfss	PORTdbg,LED	; Check if LED=on (on=High)
	goto	UdbLoff		;  if LED=off(Low), skip saving process
	bsf	Flags,LEDsv	; Save LEDon status
	LEDoff			; and off LED
	Mil100	D'10'		; wait for a second in case LED has been on
;
UdbLoff	movlw	Dmp8V		; set counter 8
	movwf	Dmp8C		; to Dmp8C
;
Udblp	Ublink	Debugb		; Blink for Debug = 8 times
	btfsc	DmpA,7		; check top bit 7
	goto	UdbOn		; if on then to UdbOn
	goto	UdbOff		; if off then to UdbOff
;
UdbOn	LEDon
	Mil100	D'30'		;
	goto	Udbeck
;
UdbOff	LEDoff
	Mil100	D'30'		;	
;	goto	Udbeck
;
Udbeck	decfsz	Dmp8C,F
	goto	Udbnext
	goto	Udbend
;
Udbnext	rlf	DmpA,F
	goto	Udblp
;
Udbend	Ublink	Debugb		; end blinking and 
	Mil100	D'100'		; blank for 10 seconds to write down
;
	btfss	Flags,LEDsv	; Check if LED was on
	goto	Udbret		;  no, goto return
	LEDon			;  if it was on, then on again
	bcf	Flags,LEDsv	; Clear LED save flag
;
Udbret	return
;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; Blinking to show debug or abend	;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
Ublinkr	movwf	BlinkC
Ublinkl	LEDon			; LED on
	Milli	D'30'		; for 30ms
	LEDoff			; LED off
	Milli	D'200'		; for 200ms
;
	decfsz	BlinkC,F
	goto	Ublinkl
	return
;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; 	Abend routine			;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
Uabendr	clrf	BSR
	movwf	Abendn			; Set Uabend number
;
	Ublink	Abendb			; Blink 25 times
	Udebug	Abendn			; Show Abend number
	goto	$
;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;	End of program						;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
	end

 上のコーディングでは他のI2Cセンサーがつけられるよう汎用I2C対応ができるようにしています(ただしRead String部分は未テスト。とはいえ、1バイトずつ読めばなくてもよいですし)。このLCDではもちろんReadは要りませんが。

ディスプレイが使えることで要らなくなったデバッグ用のコーディング部分(最後のほう)もそのまま含めています。ここはすっぽり外して問題ないもの。

全体594命令から不要なデバッグ用コードをはずせば、僅か350命令です。プログラム容量(2048ワード)の17%に収まります。また、RAMメモリーは128バイトのうち、デバッグ用も含めて20バイトしか使っていませんので、RAM容量も16%未満に収めることができました。

 

これで、今後小型PICからも楽に使えることになったので、今回週末を使った甲斐がありました\(^o^)/。

 センサー装置の基盤としてはコスパ最高かも。なにせ1602LCDの場合なら300円ほどでできる環境ですから。

 

この記事がもしどなたかのお役に立つようであれば幸いです。

 

 

©2020 Akira Tominaga, All rights reserved.


 

 

かんたんにできるWiFi信号レベル表示器(テレワークに便利)

f:id:a-tomi:20201128163004j:plain

WiFiアクセスポイントからの受信信号レベルを、dBm(デシベル・ミリワット)で表示する装置の試作品です。この週末で作れるかなと構想してましたが、いざ作ってみると半日であっけなく完成 \(^o^)/ プログラムは後のほうにつけますがごく小さくできました。

WiFi信号強度はルーターから少し遠いと、ちょっとした位置の違いでかなり変化しますね。金属製の雨戸の開けたてで違ったりもしますし・・。なので、テレワーク場所の移動には気を使うことが多いです。

PCにもスマホにも大体の信号強度は表示されますが、詳しく測れる測定器を作れば最適な場所が一発で決められかと思いました!作ってみると表示は安定していて、我ながらこれは便利!!

部品はESP32 Development Boardと4桁LED表示器(TM1637)、それに3Vの電池ホルダー(スイッチ付)だけです。

まず、回路(配線図)は次です。

f:id:a-tomi:20201128165520j:plain

電源としては上図の赤と黒に電池で3Vを供給すれば、ただちに測定が開始できます。

ただし、WiFiがあまりにも弱いとそもそも接続がしにくいですから、-60 dBm台以上のところでつないでから弱い場所に行けばOKです。

一般の家庭用ルーターの近くでは-20dBmほどの値で、それを逆算すれば受信信号は0.01mWほどだとわかります。

f:id:a-tomi:20201128170658j:plain

同じ部屋の中ですと、広くなければ大体どこに行っても-30dBm~-40dBm台でたいへん強い状態です。

f:id:a-tomi:20201128170748j:plain

しかし、階の違う別の部屋ですと:

f:id:a-tomi:20201128170928j:plain

逆算すると1ナノワットぐらいなのですが、これでも今どきの優秀な機器なら何も問題なくつながります。

ところが、離れたところでは場所によって電波の死角があります。

f:id:a-tomi:20201128171324j:plain

この値では接続は心細く、だいたいの機器は接続できません。しかし数十センチ位置を変えるだけで簡単に10dB以上改善するのに気づきます。

よって、テレワークで作業する位置が、PCなどの機器の単純な表示よりもずっと簡単に選べてすぐに決められるというわけです。

f:id:a-tomi:20201128171924j:plain

 

考えてきたプログラムは次のように簡単にできました。Arduino-IDEで書き込みますが、対応するライブラリーの入れ方はこのブログのカテゴリー"ESP32 Develoment Board"をご参照ください。なお、次の記事には具体的な入れ方を書きました。

ESP32のアクセスポイント (つまりスタンドアローンWiFi-LAN) - 勝手な電子工作・・

TM1637DisplayはArduino用のライブラリーがそのままで動きます。

/************************************************************
 *  WiFi Signal-Level Meter  V.00 
 *    Initial version: November 28th, 2020
 *    Function: Measures WiFi dBm(dB milli watt )
 *              i.e.: 1nW = -60 dBm
 *                    1μW = -30 dBm  
 *                    1mW = ± 0 dBm
 *                    1W  = +30 dBm
 *             Shows dBm value on TM1637-display
 *    Remarks: use ESP32 or Arduino with wiFi
 *    (c)2020 Akira Tominaga, All rights reserved
 ************************************************************/
#include "WiFi.h"
#include "TM1637Display.h"
#define DIO GPIO_NUM_22       	// DIO for TM1637 LED
#define CLK GPIO_NUM_21       	// CLK for TM1637 LED
TM1637Display display(CLK, DIO);
uint8_t Data[] = { 0x40, 0x40, 0x40, 0x40 }; // dashes
#define tD 500                	// time (mS) for display

// define target SSID and password
#define ssid "your-SSID" 	// set SSID to be measured
#define pass "yourpassword"  	// set its password

void setup() { // ***** ESP32 setup *****
  delay(100);  // just wait for ESP32 enough stability
  display.setBrightness(0x0c); // set LED duty rate
  delay(1);
  display.setSegments(Data);   // display dashes
  WiFi.begin(ssid, pass);
  while (WiFi.status() != WL_CONNECTED) {
    display.setSegments(Data);  // display dashes
    delay(tD/2);
    display.clear(); // blink dashes while connecting done
    delay(tD/2);
  }
}

void loop () { // ***** ESP32 loop *****
  long rssi = WiFi.RSSI(); // rcvd sig strength indicator(dBm)
  display.showNumberDec(rssi, false); // show dB-milliWatt
  delay(tD);
}
// end of program

 

上のプログラムで、SSIDとpasswordの#defineを測定したいWiFiルーターに変えるだけです。

ESP32としては日本の技適付きのEspressif社純正品が載っていて、WiFi802.11b/g/n/e/iに対応しています。強弱で選定されますがふつうはこれで十分ではないかと思います。これで対応しない高速版はそもそも電波が強いところでしか使えないわけなので。

 

蛇足かもしれませんが、受信信号強度(RSSI)について付記します。

RSSIはreceived signal strength indicatorの略です。単位dBm(デシベルミリワット)は受信電力PmWの場合、次の常用対数式で計算されます。

RSSI = 10 x log (PmW/1mW)

受信信号電力に逆算するときは次の式になりますかね。^は累乗(Excel等での表現)。

P mW = 10^(RSSI / 10)  mW

 

ついでながら、試作用のブレッドボードの作りかたを少し書き足しておきます。

f:id:a-tomi:20201128174153j:plain

ESP32-Development Boardなどにはこの形が最適ですね。こんなブレッドボードはどこで買えるかといえば、今のところどこにもありませんね^^; 生活の知恵として、普通にある次のようなブレッドボードを真ん中の溝でカットして、上下端の凹凸がガッチリはまるように上下を逆に組み合わせます^^ 

f:id:a-tomi:20201128174357j:plain

 ブレッドボードをどうやって切るかといえば、金属の入っていない真ん中の溝を普通の金鋸で切ってしまえばよいのです。電池ボックスやTM1637ディスプレイは厚い両面テープでくっつければ使いやすいですね。

ちなみに私は時間節約のために、ずいぶん昔に次のような小さな道具を無理に購入して、長年大事に使ってます。これだとすぐに切れますが、基板などを直線切りするのにも楽です。歯も変えられます。

f:id:a-tomi:20201128174912j:plain

 

では今回はこのへんで。

 

どなたかのお役に立てば幸いです。

 

©2020 Akira Tominaga, All rights reserved.

かんたんな映像サーバー(Arduino-ESP32)の実験

f:id:a-tomi:20201107180035j:plain

上の写真は小さなOV2640カメラを使った、映像ストリーミングの実験です。PCのブラウザーWiFiから受信したそのままです。

カメラとマイクロSDがESP32にセットされた、ESP32-CAMというモジュールが一昨年ごろから海外で流通しています。ご存じの方は多いかと思いますが、切手サイズのESP32自体は240MHzデュアルコアで動きWiFiBlueToothも内蔵、日本の技適ももつ優れものです。

このESP32-CAMは500円未満で手に入るのは驚きです。

f:id:a-tomi:20201107181444j:plain

f:id:a-tomi:20201107200139j:plain

海外ではたくさん使われているモジュール(なので安いわけ)ですが、搭載されているESP32はEspressif社製ではなくAI Thinker(Shenzhen Anxinke Technology社)製です。ESP32Sというモデルで、欧米の適合認証は表示されていますが日本の技適がないので、国内では電波を出せません。つまり日本では、例えばSDへの録画用Still Cameraなどとして電波を出さない方法で使う必要があります。

f:id:a-tomi:20201107200050j:plain

とはいえその場合はカメラとSDカードに割り当ててないピン数が限られ、制御には工夫がいります。WiFiBlueToothも使えないわけで、シリアル通信でコントロールするのがやっとというところです(Debug用のシリアルへの出力はオフにして)。私も今までのところそのようにして電波を出さない防犯カメラとして使用中(それでも十分安価^^)。シリアル以外に使えるピンとしてはIO0だけかもしれません(プログラム書き込み以外のときのみ)。

 

ところが、搭載されているESP32Sを、日本の技適のあるモデル(Espressif社の各モデルなど)に換装してもピン配列が同じならちゃんと動くわけです。勇敢にも実際に試された方がいるのがすばらしい!次のリンク。

ESP32-CAMを試してみた | machinanette Blog

あ、それならやってみようかと、映像ストリーミングを実験してみたもの(FCC適合認証があるので実験は総務省へのオンライン届け出で即日可能、最後に少し書き足しておきます)。PCからWiFiでアクセスしたのが最初に掲げた写真です。ごく簡単なプログラムで作れArduino IDEで書き込みますが、プログラムを記事の最後のほうに載せておきます。ESP32にArduino-IDEで書き込むには、IDEに事前にESP32ライブラリーを追加する必要があります。この入れ方は以前に次の記事に書いたとおりです。

ESP32のアクセスポイント (つまりスタンドアローンWiFi-LAN) - 勝手な電子工作・・

 

またUSB接続端子はないので、プログラムを書き込む際にはUSB-TTL変換モジュールが必要ですが、種類はどれでも大丈夫。リセットのピンはESP32-CAMにあるボタンが使えるので不要。安価なCH340gで十分です。製品のPCBにはショートをさけるための透明ビニールがかぶされていますが、それをはがした写真は次。

f:id:a-tomi:20201107201415j:plain

f:id:a-tomi:20201107201440j:plain

これ自体の回路に3.3Vを供給するために、搭載されているレギュレータの3.3V出力ピンから自分のVCCピンへと黄色のジャンパーで供給します。

ピンの並びは色々ありますが、海外ネットでの価格はどれも概して100円未満。一例は次。

f:id:a-tomi:20201107201837j:plain

プログラムを書き込むときは、これとESP32-CAMとを次のように接続します。

f:id:a-tomi:20201107202008j:plain

f:id:a-tomi:20201107202026j:plain

青のワイヤーは書き込み時専用です。実行時はこれを外します。

写真では少しわかりにくいので、次に書き直します、汚くてすみませんが。

f:id:a-tomi:20201108115223j:plain

Arduino-IDEでの書き込みはスンナリといきますが、IDEの出力メッセージ欄でもし「.....」が繰り返されるときは、ESP32-CAMにある小さなリセットボタンを1度だけおせば進行します。

書き終わったらワイヤーを全て外して、上図左上の5VピンとGndピンの間に5Vの電源を接続し、リセットを押せば、独立のWiFiサーバーつまり独立のアクセスポイントとして立ち上がります(この記事の最後につけるプログラムの場合です)。立ち上げのためのWiFi環境は必要ありません。

他のサイトを見る限り、どれも既存のWiFi環境を必要とする作り方ですが、ここではそういう環境がいらず、どこでも使えるアクセスポイントにしています。よって、SSIDIPアドレスもパスワードもプログラムで自分が指定した通りにできます。

画像ストリーミング中の電流はわずか166mA(実測)ですので、電池で動かせ移動が容易です。ただしESP32は切手サイズに240MHzデュアルコアが入っているため温度はかなり上昇しますので、放熱に注意が必要。(実測温度は、時間のあるときに記事の後ろに書き足しておこうと思います)

 

テストではつぎのように洗濯ばさみなどで挟むと、たてるにも使いやすい^^;

f:id:a-tomi:20201107203712j:plain

最後のほうにこの実験用プログラムをつけておきますが、Arduino-IDEで書き込む際のボード指定は”AI Thinker ESP32 CAM"です。これを実用に使う際は技適の表示があるESP32に換装する必要があるのは前に述べた通りで、電波法に違反しないために必須です(実験でも「技適未取得機器を用いた実験等の特例制度」による届け出が必要で、この実験もそうしています)。

この目的と同様なプログラムは、Arduino-IDEでファイル→スケッチ例から "ESP32"→"Camera"とすれば出てくるのですが、そのままだとWiFi環境の中でしか動きません。セキュリティのためにMACアドレスフィルタリングをつかっている場合など、いちいちつなぐのが面倒ですし、WiFi環境が変わったり、ないところでも使えるようにしたいわけ。よって書き換えたものです。

実験はたったのいっぺんでスンナリと動いたので却ってびっくり。デュアルコアの威力でビデオストリーミングしながらマルチスレッドで動くようです。PCからでもスマホからでも問題なく動きます。ただし、一度に複数クライアントへの配信はつながりはしますが速度に無理があります。

/****************************************************************
*  ESP32-CAM (ESP32+OV2640  module) as an Access Point		*
*	V00: Image streaming     Nov.07, 2020 Akira Tominaga	*
*            Remarks: Specify board as "AI Thinker ESP32 CAM",	*
*		      though Example exists at ESP32 -> Camera.	*
*		      Change WiFi to Access-Point-server type.	* 
*****************************************************************/
#include "esp_camera.h"
#include "WiFi.h"
#define CAMERA_MODEL_AI_THINKER
#include "camera_pins.h"
const char ssid[]  = "HOGE-007";
const char pass[] = "hoge007desu";
const IPAddress ip(192, 168, 32, 7);
const IPAddress subnet(255, 255, 255, 0);
WiFiServer server(80);

void startCameraServer();

void setup() {	// ***** ESP32 setup *****
  Serial.begin(9600);
  Serial.setDebugOutput(true);
  Serial.println();

  camera_config_t config;
  config.ledc_channel = LEDC_CHANNEL_0;
  config.ledc_timer = LEDC_TIMER_0;
  config.pin_d0 = Y2_GPIO_NUM;
  config.pin_d1 = Y3_GPIO_NUM;
  config.pin_d2 = Y4_GPIO_NUM;
  config.pin_d3 = Y5_GPIO_NUM;
  config.pin_d4 = Y6_GPIO_NUM;
  config.pin_d5 = Y7_GPIO_NUM;
  config.pin_d6 = Y8_GPIO_NUM;
  config.pin_d7 = Y9_GPIO_NUM;
  config.pin_xclk = XCLK_GPIO_NUM;
  config.pin_pclk = PCLK_GPIO_NUM;
  config.pin_vsync = VSYNC_GPIO_NUM;
  config.pin_href = HREF_GPIO_NUM;
  config.pin_sscb_sda = SIOD_GPIO_NUM;
  config.pin_sscb_scl = SIOC_GPIO_NUM;
  config.pin_pwdn = PWDN_GPIO_NUM;
  config.pin_reset = RESET_GPIO_NUM;
  config.xclk_freq_hz = 20000000;
  config.pixel_format = PIXFORMAT_JPEG;
  //init with high specs to pre-allocate larger buffers
  if(psramFound()){
    config.frame_size = FRAMESIZE_UXGA;
    config.jpeg_quality = 10;
    config.fb_count = 2;
  } else {
    config.frame_size = FRAMESIZE_SVGA;
    config.jpeg_quality = 12;
    config.fb_count = 1;
  }

  // camera init
  esp_err_t err = esp_camera_init(&config);
  if (err != ESP_OK) {
    Serial.printf("Camera init failed with error 0x%x", err);
    return;
  }

  sensor_t * s = esp_camera_sensor_get();
  if (s->id.PID == OV3660_PID) {
    s->set_vflip(s, 1);//flip it back
    s->set_brightness(s, 1);//up the blightness a little
    s->set_saturation(s, -2);//lower the saturation
  }
  //less frame size for higher initial frame rate
  s->set_framesize(s, FRAMESIZE_QVGA);

  // ***** setting as an Access Ppoint
  WiFi.softAP(ssid, pass);
  delay(100);
  WiFi.softAPConfig(ip, ip, subnet);
  IPAddress myIP = WiFi.softAPIP();
  Serial.print("SSID= ");
  Serial.println(ssid);
  Serial.print("Fixed IP addr= ");
  Serial.println(myIP);
  Serial.println("Server starts!");

  startCameraServer();

  Serial.print("Camera OK! Use 'http://");
  Serial.println(WiFi.localIP());
}

void loop() { 		// ***** ESP32 Loop ****
  delay(10000);  	// do nothing for a test 
  Serial.println("Looping");
}

 では今回の実験はこのへんで。

 

2020.11.09追記:画像ストリーミング中の発熱

室温24.3℃の場合、次のように発熱します。

f:id:a-tomi:20201109145754j:plain

ESP32自体は基板へ上手に放熱しているようでむしろ裏側が熱くなります。一番熱いのは上側にある 5Vto3.3V のレギュレータで、46.5℃になっています。周囲が暑いときはもっと上がるかも。少ない消費電流とはいえサイズも小さいですからね。

もしESP32をきちんと換装し本番に使う場合は、放熱を考慮しないとまずいかもしれません。ファンをつけたくない場合ケースには閉じこめずソケット等を通じ別基板に逃がすのがよいかなあ・・。

 

 2020.11.15追記:複数回追記してすみません。週末がきたのでやってみました。まず、かんたんな48mmx34㎜基板を作製。

f:id:a-tomi:20201115182254j:plain

三脚がつけられるよう小さな箱に取り付け、ヘダーピン雌ソケットにESP32-CAMを挿す。

f:id:a-tomi:20201115182541j:plain


そして30分間ストリーミングしたらところで測ると次の温度です。本日はだいぶ暖かく部屋の温度は25.0℃です。

f:id:a-tomi:20201115182643j:plain

モジュールの最高温度は5℃下がりました。主にグラウンドからよく伝わると思いきや意外に下がらないのはモジュールにくっついてきたピンヘダーのオスが鉄製だから(鉄は熱伝導率が悪く銅の約5分の1)。

実験期間終了(180日間でこの場合は来年4月末)までに、ESP32換装を実行する際、銅に交換する?とはいえ表面実装ICの除去はいたしかたないとして、スルーホールからの除去は苦手なので、まあこの程度の放熱で我慢するか・・。

 

以下は蛇足ですが、実験の届け出はマイナンバーカードとその読取器があればすぐできます。FCCとCEの適合認証があるので比較的簡単。

技適未取得機器を用いた実験等の特例制度

この記事でぐちゃぐちゃ説明するより、「技適未取得機器を用いた実験の特例制度」をググってみていただくのが良いと思います。なお期限までに廃止届も忘れずに出す必要がありますが。

 

f:id:a-tomi:20201115183329j:plain

 以上、お役に立てば幸いです。

 

©2020 Akira Tominaga, All rights reserved.

 

Zoom用美顔照明ー眩しくない簡単調光器 (後ろに少し追記2020.11.05)

f:id:a-tomi:20201027111732j:plain

小型コントローラーで連続調光をするオンライン会議用の顔照明です。ディスプレイ上のカメラの両脇にセットすることで、顔に影を作らずに照明します。

市販ではこういうのが見つからないので、そこらへんの材料で自作したものですが^^; レンズのそばにセットできるので顔に影がでないわけです。かなり明るくもできますが、Zoomの場合は、ほのかな明るさでも十分です。

10月は例年少し忙しいので、投稿がとどこおりがちなのですがそうはいきません。今回はごく簡単な電子工作について説明したいと思います。

今や普通になったオンライン会議では、自分の元気な表情を見せることが自分のためでなく他の参加者の気持ちのために大事ですね。ところが顔が暗くなって見えたり影になっている参加者が多いのに気づきます。例えば次の写真で顔の色だけをご覧ください。せっかく背景がよくても顔が暗いと今一つになりがち。

f:id:a-tomi:20201027112835j:plain

顔の明るさは会議全体の雰囲気にも影響しそうです。

スマホの場合は市販のリングライトスマホの周りを取り囲む)を使えばよいですが、デスクトップのディスプレイからではそうはいきません。大体32インチ以内なら、今回の照明でずいぶん改善されますが、より大きいディスプレイでは画面の中心からレンズまでだいぶ遠いので一工夫が要ります。それについては後日書きたすことにします。

なにしろ普通の部屋では、ほぼ例外なく照明が天井のほうにあるため、禿頭をピカリと強調したり、顔の下半分に影を作ったりし勝ちですね。

この電子工作は、このブログでは珍しくマイコンなど使わない簡単なもので、照明の光源にはLEDモジュールを使います。クルマの車内照明用に12V対応の白色LEDモジュールがネットでたくさん売られていますが、とても明るく超低消費電力です。次の写真のものは昨年eBayで購入、1個1ドルでした。

f:id:a-tomi:20201027132941j:plain

これは5センチx2センチの大きさで36個のLEDエレメントが配置されています。

測定すると12Vでは100mAですが明るすぎて直視できません。11Vでも十分すぎる明るさで電流は50mAです。もう少し低くてちょうど良い感じすが、この場合は0.5Wほどなので放熱をほとんど気にしなくてすみます。

ネットにはいろんな種類がありますが、ほぼ同様と思われます。使う場合はもちろん事前に測る必要はあるでしょう。

f:id:a-tomi:20201027133941j:plain

 

自由に明るさを変えるための調光器はごく単純な次の回路としました。

f:id:a-tomi:20201027134416j:plain

テストするとこれで計算通りになります。

15V入力は秋月の超小型ACアダプターで電流は十分です。

LEDモジュール複数を並列につないで明るさを比較すると、低い電圧の時に個体間のばらつきが少しありますが、どれでも特に問題ない感じです。

f:id:a-tomi:20201027134716j:plain

これを入れるケースは、ディスプレイの上にセットするわけなので脚部に少し工夫を要します。ここでは、不要となった古いWebカメラの脚を流用しました。

f:id:a-tomi:20201027135209j:plain

10年以上前のものなので、ディスプレイにひっかける足の前部が深さ12mmもあるため、現代のディスプレイの縁幅を超えて画面を隠してしまいます。

よって次のようにカットします。

f:id:a-tomi:20201027135423j:plain

黒い丸いものは、カメラが自由に回転したり角度を変えられるようにするだいじな部品です。

そして適当なプラ容器にねじ止めしてセットします。

f:id:a-tomi:20201027135725j:plain

ちなみにこの容器は百均で4個百円だった小さな食品容器です。念のために天端に5mmの放熱穴をあけてあります。一番明るくしない場合はほぼ必要ない感じですが、その穴から取り付けねじを締めることができます。

そして、デフューザーとして蓋の内側にトレーシングペーパーを二つ折りにして小さな両面テープで中央部に張り付けてあります。これにより眼の負担が軽減されます。短時間でうまくできました、我ながら^^

調光器は秋月の一番小さなプラケースに収容するため、基板は60mmx30mmにします。また深さがないので、1A用の可変電圧レギュレータはまげて取り付ける必要がありますし、これに大型の可変抵抗器をセットしなければなりません。

よって、回路図自体は同じなのですが次のような配線図(上からみた図です)とします。

f:id:a-tomi:20201027140709j:plain

これを次のような60mmx30mmの基板にします。銅箔面なので左右が逆ですが。

f:id:a-tomi:20201027140905j:plain

CNCルーターで削って出来上がり。

f:id:a-tomi:20201027141009j:plain

表からは次。

f:id:a-tomi:20201027141052j:plain

ずいぶん小さく組みあがりました。

f:id:a-tomi:20201027141313j:plain

そして32インチディスプレイにとりつけ。上の棚にぎりぎりですが、幸いうまく収まりました。

f:id:a-tomi:20201027141432j:plain

カメラのレンズからの距離を同じあたりにセットしています。

早速PCをオンにします(百ボルト電源はPC元電源からとっています)。

f:id:a-tomi:20201027141942j:plain

いい感じですね。

コントローラーもごく小さいので置き場所に困りません。

f:id:a-tomi:20201027142053j:plain

このPCからは過去にオンライン会議には出てませんが今後使う予定。早速Zoomで実験。

おお!これはいい!

顔に影がなく明るく映ります!頭の輝きが強調されませんでさらによい^^;

小さなプーさんを出してみます。

f:id:a-tomi:20201027142525j:plain

これならバーチャル背景にまけませんね。第一、頭や額がピカっとしてない(笑)

というわけで400円ほどのこの工作はメデタシメデタシ。。。

 

なお、もしさらに大きなディスプレイを使う場合は、照明を上にセットするだけでは不十分なことがあります。また、メガネの反射をどうするかなどの留意点もありますね。時間のある時に記事の後ろに書き足したいと思います。

とにかく、こういう工夫をして出ると会議が明るくなりますね。プロの面々がでるときはみなさん顔を明るくして表情豊かですね。それが他の参加者のためなのですから。次の例のように。

f:id:a-tomi:20201027143655j:plain

 

海外では、オンライン会議で自分の顔の照明にずいぶん気を付けて参加される方も多いようです。たとえばですが、、

youtu.be

では今回はこのへんで。もし皆様のお役に立てば幸いです。

 

2020.11.5追記:大画面の場合の勝手なやりかたと、メガネの反射改善について、少し書き足させていただきます。

大画面ですとカメラの位置が中心からだいぶはずれますし、照明もなかなか難しいところです。次の例は事務所から43インチ・ディスプレイで出る場合の、勝手な照明の方法です。主にディスプレイ背面の大きな白い壁の反射による間接照明のつもりですが、さらに工夫するとだいぶ改善されます。

f:id:a-tomi:20201105150159j:plain

この43インチ・ディスプレイは4K画素なので精細で、ウィンドウがあちこち配置できて楽です。しかし、できるだけカメラに近い目線となるようにするために、Zoomウィンドウや共有する際のウィンドウの位置を、カメラに近い上のほうにもっていくようにしています。そして余分に「照明ウィンドウ」を表示しておきます。白い画面ならなんでもOK。ついでに、大事な仕事の場合にはこの左側にある別PCマルチディスプレイである27インチも、「照明ウィンドウ」として動員。

しかし、これらだけではどうしても下からの照明が少し不足がち。そこで、左下に今回の記事と同様の眩しくない程度の照明をおいて改善をすることができました。実は、上にご紹介した工作よりも数か月前に試作して使っているものですが。回路は同じですがユニバーサル基板にちょこっと作ったものです。ケースは百均のもう少しだけ大きい食品容器(2個百円なり^^)です。

f:id:a-tomi:20201105151728j:plain

全体で1W程度は発熱するわけなので、アルミ板にはりつけてあります。また、上に放熱穴をあけましたが、実際は熱くはなりませんからそこまでしなくともよいかも。

蓋の内側には同様にトレーシングペーパーを二つ折りにしてはりつけてあるので、柔らかい白色照明となっています。これで顔に影がなくなりましたが机上の反射も役立っているようです。

f:id:a-tomi:20201105152043j:plain

 

さて、カメラの近くから照明するとメガネをかけたときに反射が気になるわけですが、こちらの照明方法では殆ど気になりません。カメラの両脇につけるパターンの場合には、カメラからある程度は左右に離すことでそれなりに改善されるのに気づきました。

以上、追加の記述でした。

 

©2020 Akira Tominaga, All rights reserved.

勝手なリモート・サーボ?!(ステッパーとESP32)

f:id:a-tomi:20200928101554g:plain

ESP32の実験ついでにステッパーで遠隔サーボを作ってみました。ここではSP32 Dev Board (38ピン)を2つ使い、片側(Slave)で位置の信号を作り、もう一方(Master)へ送り、そこでステッパーをサーボとして動かしています。上の動画では両方をすぐ隣においてあります^^ が、部屋の中ではかなり遠くでも問題ありません。どこまで届くかは実験してませんが。

これを前にはWiFiで苦労して作りましたが、こういう用途ではBlueToothがずいぶん楽だと気付きましたので、Bluetooth SPP(Serial Port Profile)にしました。送受の両方のESP32プログラムはこの記事の最後につけておきます。

Arduino IDEでプログラミングしましたが、ESP32をArduino IDEで開発する際のソフトウェア導入方法については、次の記事の中に簡単に書いておきました。

ESP32のアクセスポイント (つまりスタンドアローンWiFi-LAN) - 勝手な電子工作・・

 なお、強力なESP32Devlopment Boardは海外ネットではずいぶん安価になり、この例で使ったのは1つあたり3.6ドルでした。

 

まず、マスター側の結線は次のとおりです。毎度汚い手書きですみませんが。

f:id:a-tomi:20200928103519j:plain

これをブレッドボードに次のように試作しました。

f:id:a-tomi:20200928103624j:plain

ごらんのとおりへんなブレッドボードですが、真ん中にある普通のブレッドボードの外側上下に、もう1枚のブレッドボードを2つに割ったものをはめ込んだだけ。ブレッドボードの受け金具のはいってないところで自由に切ればよいわけ。金鋸でもバンドソーでも何でも簡単に切ることができますから^^ うまく作るコツは、組み合わせの凹凸部がはまるように考えるだけです。あるいは切らずに組み合わせるだけでOKですね(ただし余分な面積が増える)。

次にスレーブですが、これはもう簡単そのもの、結線図というほどのものではありません。この場合、もしVRにセンターノッチがはいっているとサーボコントロールに使いやすいですが、特に必要はありません。

f:id:a-tomi:20200928104404j:plain

これはブレッドボード(1列6ピン)1枚に試作。

f:id:a-tomi:20200928104513j:plain

電源はUSB供給でもいいですが、持ち歩きのリモコンにする場合は単三電池3本(4.5V)をV5端子に入れるとよいです。

 

ステッパーをサーボとして使うときに一番難しいのは、回転の絶対位置を何で決めるかということ。リミッターとかセンサーとかなんらかの「とりつくしま」を付ければごく簡単なことなのですが、そこをつけずにやりたいもの。実は、A4988コントローラーなどのリセット機能を用いてHome positionを決め、後は決まったステップ数単位で動かせば再現性をもってできます。前に次のレーザーカッターの記事に書いたとおりです。

オリジナル・レーザープロッター その2 Arduinoでの刻印プログラム - 勝手な電子工作・・

今回は荒っぽい実験でとくにそうはしてませんがほとんど狂いません。

f:id:a-tomi:20200928104838j:plain

我ながらこれはなかなか面白い実験でした^^

なお、サーボの位置を細かく決めるには、スレーブ側のVRをCoarse(右の10Kオーム)+Fine(左の500オーム)と組み合わせると微調整が効きます。VRのケースは秋月で売っている一番小さなプラケースです。また、A4988で分周比の設定ができますので、角度はさらに細かい単位にすることができます。

f:id:a-tomi:20200928105136j:plain

このVRは2.4GHz電波の近傍にさらされるわけなので、どちらの方法でもVRへのワイヤはシールドワイヤが望ましいかも。そうしないと電波を拾ってわずかながら不安定になる感じで、その場合のセンター位置(511)の受信例が次。

f:id:a-tomi:20200928105946j:plain

あるいはVRの抵抗値を下げて例えば10Kオームを2Kオームにすればノイズは拾いにくくなるし、また、高周波をカットするようにC(キャパシター)をADC端子間に入れる手もよいでしょうね。

 

最後にそれぞれのプログラムを添付しますが、Arduino IDEからESP32 Dev Boardに書き込みます。

まずスレーブ側で、とても小さなプログラムです。

/* ************************************************************************
  ESP32 Serial communication with Bluetooth-SPP as a Slave
    Example; ADC data sending to master's remote-Servo
      Initial version Sept.26, 2020   (c)Akira Tominaga
 *************************************************************************/
#include "BluetoothSerial.h"
BluetoothSerial btSPP;
int ADpin = 36;                   // ADC pin
uint16_t adVal = 0;               // ADC value
byte mH;                          // High byte for ADC measured value
byte mL;                          // Low byte for ADC measured value

void setup() { // ***** ESP32 setup *****
  Serial.begin(9600);
  btSPP.begin("HOGEHOGE-Slave-xx"); //Bluetooth name of this slave
  Serial.println("");             // NewLine
  uint8_t mA[6];                  // for blue-tooth MAC adr
  esp_read_mac(mA, ESP_MAC_BT);   // get my blue-tooh MAC adr
  Serial.printf("%02X:%02X:%02X:%02X:%02X:%02X\r\n", mA[0], mA[1], mA[2], mA[3], mA[4], mA[5]);
  // upper result to be used by master
  Serial.println("btSPP started, pair with master.");
  // for AD conversion
  pinMode(ADpin, INPUT);
  adcAttachPin(ADpin);
  analogReadResolution(10);       // ADC with 10 bits (from 0 to 1023)
  delay(5);
}

void loop() { // ***** ESP32 Loop *****
  while (btSPP.available() < 3) {} // wait for master message
  String s = btSPP.readString();   // read whole message
  Serial.println(s);
  adVal = analogRead(ADpin);
  Serial.println(adVal);
  mH = adVal >> 8;
  mL = adVal - mH * 256;
  Serial.print("mH=");  Serial.print(mH, HEX);
  Serial.print(" mL=");  Serial.println(mL, HEX);
  btSPP.write(mH);              // send measured-high byte
  btSPP.write(mL);              // send measured-low byte
}

 

次にマスター側です。これもあまり大きくありません。

 

/* ************************************************************************
  Serial communication with Bluetooth - Serial Port Profile (Master)
      Example: Stepper as a remote-Servo
      Initial version Sept.26, 2020  (c)Akira Tominaga
 *************************************************************************/
#include "BluetoothSerial.h"
BluetoothSerial SerialBT;
uint8_t Adr[6]  = {0x7C, 0x9E, 0xBD, 0xF4, 0xDB, 0x56}; // slave adr
String name = "HOGEHOGExxx";
byte mH;                            // high byte of measured ADC at slave 
byte mL;                            // low byte of measured ADC at slave
uint16_t adVal;                     // ADC value reproduced from mH&mL
#define adVmax 1023                 // 10bits-ADC max value
#define adVmid 512                  // 10 bits-ADC center value
uint16_t adVsv = adVmid;            // ADC value saved last time
// for stepper with A4988
int16_t Move;                       // moving-steps for stepper
const char Roger[] = "Rgr";         // ready message to slave
uint16_t vRot;                      // value for rotation steps (oneRot=200)
gpio_num_t Dir = GPIO_NUM_33;       // Direction for A4988
gpio_num_t Step = GPIO_NUM_25;      // Step for A4988
gpio_num_t Nsleep = GPIO_NUM_26;    // !sleep for A4988
gpio_num_t Nreset = GPIO_NUM_27;    // !reset for A4988
#define tmStp 2500                  // stepper phase timing in μS
#define oneRot 200                  // steps req'd for one rotation (360°)
#define sMax  150.0                 // max steps as a servo (270°)

void setup() { // ***** Arduino (ESP32) Setup *****
  pinMode(Nsleep, OUTPUT);
  sSleep();                         // sleep A4988 to begin with 
  pinMode(Dir, OUTPUT);
  pinMode(Step, OUTPUT);
  digitalWrite(Step, LOW);
  Serial.begin(9600);
  pinMode(Nreset, OUTPUT);
  sReset();                         // set stepper at home position
  
  // Bluetooth slave shoud be on, first
  SerialBT.begin(name, true);
  Serial.println("master started");
  if (SerialBT.connect(Adr)) {
    ;     // connect with slave adr
    Serial.println("Connected!");
  } else {
    while (!SerialBT.connected(10000)) {
      Serial.println("Failed to connect!");
    }
  }
  delay(5);
  for (int n = 0; n < 3; n++) {
    SerialBT.write(Roger[n]);              // send ready to slave
  }
}

void loop() { // ***** Arduino (ESP32) Loop *****
  //Serial.println("Loop");
  while (SerialBT.available() < 2) {}     // wait for 2 bytes from slave
  mH = SerialBT.read();
  mL = SerialBT.read();
  adVal = mH * 256 + mL;
  //Serial.print(" adVal=");
  Serial.println(adVal);
  Move = adVal - adVsv;
  adVsv = adVal;

  if (Move > 0) {
    vRot = (float)(Move * sMax / adVmax);
    //Serial.print("Positive ");
    //Serial.println(vRot);
    sP(vRot);
  }
  if (Move < 0) {
    Move = -Move;
    vRot = (float)(Move * sMax / adVmax);
    //Serial.print("Negative ");
    //Serial.println(vRot);
    sN(vRot);
  }
  if (Move = 0) {
    // do nothing
  }
  for (int n = 0; n < 3; n++) {
    SerialBT.write(Roger[n]);              // send ready to slave
  }
}

/**********************************************
      User defined functions
 **********************************************/
//   *** sP(n-rotations) *** move stepper positive
void sP(int nRot) {
  digitalWrite(Dir, LOW);
  mvS(nRot, tmStp);
}
//  *** sN(n-rotations) *** move steppe negative
void sN (int nRot) {
  digitalWrite(Dir, HIGH);
  mvS(nRot, tmStp);
}
//  *** mvS(nRot, ndelay) *** move stepper
void mvS(int nRot, long nDelay) {
  sWake();          // wake-up A4988
  for (int l = 0; l < nRot; l++) {
    digitalWrite(Step, HIGH);
    delayMicroseconds(nDelay);
    digitalWrite(Step, LOW);
    delayMicroseconds(nDelay);
  }
  sSleep();         // Sleep A4988 to avoid heat
}

// *** sReset() *** Reset at current position & sleep
void sReset() {
  sWake();
  digitalWrite(Nreset, LOW);
  delay(10);
  digitalWrite(Nreset, HIGH);
  //delay(1);
  sSleep();
}
// *** sSleep() *** Sleep stepper to avoid heat
void sSleep(void) {
  delay(2);           // wait 2mS until end of inertia 
digitalWrite(Nsleep, LOW); } // *** sWake() *** Wake-up stepper void sWake(void) { digitalWrite(Nsleep, HIGH); delay(2); // wait for charge-pump full } // end of program

ステッパー停止時には、いちいちA4988をSleep させていますが、こうするとA4988もステッパーも熱をもたせずにすみます(逆に停止時にSleepさせず放置すると直流が流れるのですぐ過熱しますからご注意)。このあたりは、前に次の記事などに詳しく書きましたのでご興味のある方は次をご覧ください。

a-tomi.hatenablog.com

 

以上、自分のまとめとして書いた記事ですが、これはなかなか楽しい実験でした。この応用が皆様の何かのお役に立てば幸いです。

 

 

©2020 Akira Tominaga, All rights reserved.

 

ESP32のアクセスポイント (つまりスタンドアローンWiFi-LAN)

f:id:a-tomi:20200927201624g:plain

この週末にESP32で2つの実験をしたので、簡単にまとめます。プログラムは記事の後ろのほうにつけておきます。

1つ目はスタンドアロンーンのWeb.サーバー(つまり既存のWiFi LANを使わないアクセスポイント)。2つ目は勝手な「リモート・サーボ」をESPとステッパーで作ってみたので紹介。

この記事は、まず1つ目の独立WiFiサーバーです。ありきたりのようにも見えますが、これが便利だと思う勝手な理由は次の3つ。

①ふつうは立ち上げ時にメインのWiFi-LANにつないで、その中のIPアドレスとして動かすのですが、場合によりいちいちMACアドレスフィルタリングを通さないといけません。その手間がたいへん(MACアドレスフィルタリングを使ってない場合は手間はないですがSecurity心配ですね)。

②AlexaやGoogleHomeなどがたくさん加わり、WiFiルーターによっては数の限界に近づいています。そして電子工作を楽しむ人にとっては、とにかくESPマイコンの最近の低価格化はありがたい事でESP8266Development Boardに至っては、海外ネットで今や200円台で買えますから、何にでも使うわけでどんどん増えるわけ^^;

③既存のWiFi-LANの中で動かすと、場所を変える場合には、最初の接続SSIDの変更または追加を要する。スタンドアローンのアクセスポイントにすれば、その手間がいらない、つまりプログラムをいじらなくてよい。

 

ここで使うESP32DevelopmentBoardは次の38ピンのもので、次のように結線。

f:id:a-tomi:20200927204436j:plain

電源はUSBからの5Vでよいですが、自由に持ち回るときは上のように電池をつなぎます。最近のESP development Boardは比較的消費電流が小さくなりました(とはいえ実行中は100mA程度)。

ブレッドボードは、普通の1列5ピンのは使いにくいので6ピン配置のもの。これは秋月でも売っています。

f:id:a-tomi:20200927204901j:plain

とはいっても、実はどんなブレッドボードでも工夫すれば自由に使えます。そのことは次回の記事で書くことにします。

なお、Arduino-IDEでESP32ボードを開発するためには、ESP32用のボードマネジャーをArduino IDEへインストールする必要があります。導入法はネットの色々なところに出ていますが、Windowsでの現時点の入れ方を簡単に書いておきます。

====================

・ESP32をArduino-IDEで開発するためのソフト導入

Arduino IDE自体は次のアドレスから導入します。https://www.arduino.cc/

そしてIDEを開いたら、上のメニューバーにある「ファイル」→「環境設定」をクリックします。

環境設定画面の下のほうにある「追加のボードマネジャのURL欄」にボードマネジャーのあるURLアドレスを入力しますが、ESP32の場合は次を入力。

https://dl.espressif.com/dl/package_esp32_index.json

次に、IDE本体のメニューバーで「ツール」→「ボード」→「ボードマネジャ」と進むと使えるボードマネジャーのメニューが表示されるので、その中から

esp32by Espressif Systemsと表示されている欄内の右のほうにある「インストール」をクリックすれば、導入が始まり、しばらくすると終わります。

====================

IDEで後述するプログラムを作って、「ツール」で「ESP32 Dev Module」を選んで書き込む際に、画面の下の欄に赤色で書き込み開始の合図「・・・___」が始まったらESP32開発ボード上でUSB端子の隣にあるBootボタンを押すと書き込まれます。Arduinoと違って書き込み量が多いので少し時間がかかります。

そうすると「勝手なWiFi-LANサーバー」が立ち上がり、勝手に決めたアドレスが独立のアクセスポイントとなります。PCやスマホなどでこれを選んで(PWはもちろん入れて)接続します。もちろんPCやスマホでなく、工作用には他のESPからでもよいわけですが、、(あたりまえか)。

ここでのプログラム(この記事の後ろのほうに載せます)ではIPアドレス192.168.32.2を指定しています。PCやスマホならインターネットブラウザーでそこをアクセスすると次の画面。PCでも同じです。簡単な実験のためにテキストだけのページですが。

そしてリンクをタッチすれば指定LEDが点きます。

f:id:a-tomi:20200927205313j:plain

この実験装置全体の様子は次です。下の写真ではサーバーの電源をUSBで供給しています。

f:id:a-tomi:20200927205638j:plain

このサーバーの電源としては、前述のように電池(単三電池を3本直列)をつかっても問題ありません。そうすれば動くものやロボカーなどにも便利です。

さて、こうやって工作室に勝手なWiFi-LANをどんどん作るとどうなるか?

PCやスマホSSIDがじゃんじゃん表示されますし、運用場所によっては近所からも丸見えですからあまり変なSSID名を使わないのがいいですね^^;

f:id:a-tomi:20200927210049j:plain

 

最後にこの実験用に作ったプログラムをつけておきます。

/********************************************************
 * Independent Access-Point - ESP32 simple example  
 *        Initial version V.00 Sept.26 2020
 *                                   by Akira Tominaga
********************************************************/
#include "WiFi.h"
const char ssid[]  = "HOGEHOGE-00X"; 	// *** set any ssid ***
const char pass[] = "hoge00xpw";	// *** set any pw   ***
const IPAddress ip(192, 168, 32, 2); 	// *** set any addr ***
const IPAddress subnet(255, 255, 255, 0); 
WiFiServer server(80);
// ESP32 GPIO pin definitions (should not be #define)
gpio_num_t Red = GPIO_NUM_21;               // Red LED
gpio_num_t Green = GPIO_NUM_22;             // Green LED
gpio_num_t Blue = GPIO_NUM_23;              // Blue LED

void setup() { // ***** Arduino (ESP32) Setup *****
  Serial.begin(9600);
  pinMode(Red, OUTPUT);                     // Red pin as output
  digitalWrite(Red, LOW);
  pinMode(Green, OUTPUT);                   // Green pin as output
  digitalWrite(Green, LOW);
  pinMode(Blue, OUTPUT);                    // Blue pin as output
  digitalWrite(Blue, LOW);
  delay(100);

  WiFi.softAP(ssid, pass);
  delay(100);
  WiFi.softAPConfig(ip, ip, subnet);
  IPAddress myIP = WiFi.softAPIP();
  server.begin();
  Serial.print("SSID= ");
  Serial.println(ssid);
  Serial.print("Fixed IP addr= ");
  Serial.println(myIP);
  Serial.println("Server starting!");
}

void loop() { // ***** Arduino (ESP32) Loop *****
  WiFiClient client = server.available();
  if (client) {                             // if accessed
    Serial.println("Accessed");
    String inMsg = "";
    while (client.connected()) {            // loop while client connected
      if (client.available()) {             // if a message,
        char c = client.read();             // read each byte, and
        Serial.write(c);                    // write to Serial
        if (c == '\n') {                    // if  LF and
          if (inMsg.length() == 0) {        //  if new, send response
            client.println("HTTP/1.1 200 OK");
            client.println("Content-type:text/html");
            client.println();               // send NewLine
            client.print("
- Touch to switch LED on/off -
"); client.print("

 

 

 

    "); client.print("
  • Switch Off
");
            client.println();               // send another NewLine
            break;                          // go out of while loop
          } else {                          // if not new
            inMsg = "";                     // initialize inMsg
          }
        } else if (c != '\r') {             // when not (c == '\n') ,too
          inMsg += c;                       // add to inMsg
        }

        if (inMsg.endsWith("GET /R")) {
          digitalWrite(Red, HIGH);          // Red on
        }
        if (inMsg.endsWith("GET /G")) {
          digitalWrite(Green, HIGH);        // Green on
        }
        if (inMsg.endsWith("GET /B")) {
          digitalWrite(Blue, HIGH);         // Blue on
        }
        if (inMsg.endsWith("GET /F")) {
          digitalWrite(Red, LOW);           // Red off
          digitalWrite(Green, LOW);         // Green off
          digitalWrite(Blue, LOW);          // Blue off
        }
      } // end of (client.available())
    }  // end of (client.connected())
    client.stop();
    Serial.println("Client Disconnected.");
  } // end of (client)
}
// end of program

上のプログラムはPreとCodeで挟みましたが、肝心のHTMLを送るところはソースが表示されず、結果が表示されてしまいますね。はてなブログでのタグの認識防止方法がまだしらべきれてないので、当面はそこの行以下最後までを次に直接かいておきます^^;  良い方法がわかった時点で直しますが、たいへんすみません<(_ _)>


if (c == '\n') { // if LF and
if (inMsg.length() == 0) { // if new, send response
client.println("HTTP/1.1 200 OK");
client.println("Content-type:text/html");
client.println(); // send NewLine
client.print("<font size=15 clolor=black><br>- Touch to switch LED on/off -<br>");
client.print("<ul><li>Switch <a href=\"/R\"><font color=red>Red-on</font></a></li><br>");
client.print("<li>Switch <a href=\"/G\"><font color=green>Green-on</font></a></li><br>");
client.print("<li>Switch <a href=\"/B\"><font color=blue>Blue-on</font></a></li><br>");
client.print("<li>Switch <a href=\"/F\"><font color=black>Off</font></a></li></ul></font>");
client.println(); // send another NewLine
break; // go out of while loop
} else { // if not new
inMsg = ""; // initialize inMsg
}
} else if (c != '\r') { // when not (c == '\n') ,too
inMsg += c; // add to inMsg
}

if (inMsg.endsWith("GET /R")) {
digitalWrite(Red, HIGH); // Red on
}
if (inMsg.endsWith("GET /G")) {
digitalWrite(Green, HIGH); // Green on
}
if (inMsg.endsWith("GET /B")) {
digitalWrite(Blue, HIGH); // Blue on
}
if (inMsg.endsWith("GET /F")) {
digitalWrite(Red, LOW); // Red off
digitalWrite(Green, LOW); // Green off
digitalWrite(Blue, LOW); // Blue off
}
} // end of (client.available())
} // end of (client.connected())
client.stop();
Serial.println("Client Disconnected.");
} // end of (client)
}
// end of program

 

この記事は、自分のまとめのために書きましたが、もしもお役に立つことがあれば幸いです。

次回はESPとステッパーで作った「勝手なリモートサーボ」について、近日中に書きたいと思います。

 

©2020 Akira Tominaga, All rights reserved.

 

ガチャポンの回転ダイヤルをデジタル化

f:id:a-tomi:20200919104840g:plain

上の回転ダイヤルは、ガチャポンから出てくる玩具で、なぜかこの春から突然人気となったようです。ネット通販で4つセットが売られていましたので5月末に予約して最近ようやく手に入れました^^;

小さいですが精巧によくできていて、ダイヤルが戻る感触は抜群です、なるほど・・・人気に納得できそう。

f:id:a-tomi:20200919133446j:plain

説明書には対象年齢がまさかの「15歳以上」と書いてありますが、こんな電話機はひょっとすると50歳以上しか使ったことがないかもしれませんね。

単に回すだけで楽しむ玩具ですが、キーボードに飽きたらこれを使ったデジタル入力も面白そう。

裏蓋を開けてみると中にはゼンマイ仕掛けのごく小さなギアボックスが入っているだけです。それでもご丁寧にちゃんとネジ2本を使って正確に蓋をしてあります。これならデジタル化できないものかと、ふと考えついてしまったのが事の始まり^^;

ギアボックス自体は開かないし、軸のでっぱりなど、余計なものもありません。もしそういうのがあれば磁石を仕込むとか、何らかのしかけができそyですが、一見取りつくしまがない感じ。こうなるとますます挑戦したくなりますね^^

しばらくは考えるだけでしたが、この4連休週末がきたので、ついにやってみることにしました。

 

気付いた中で一番かんたんそうな解き方:ダイヤルが戻る際の音の時間を測定してはどうかな!

戻る時間を測ってみた結果、回転量で安定して決まるようです。たぶんぜんまいギアの油による定速と思われます。それならきちんとマイクで音を拾って詳しく調べてみることに。

というわけで小さなコンデンサマイク・モジュールを仕込むことにしました。

f:id:a-tomi:20200919135241j:plain

f:id:a-tomi:20200919135322j:plain

1ドルで数個買えるモジュールですがゲインの調整もできるので一応使えそう。やってみると中になぜかぴったりと収まり、蓋をするとちょうどうまく固定されます。

f:id:a-tomi:20200919135541j:plain

よけいな飾りの鎖部分はカットして、その付近にそれらしくケーブルを通しました。

表から見るとサマになってますね(・・?

f:id:a-tomi:20200919135711j:plain

ダイヤルをしながらオシロスコープで測定します。

マイクモジュールは若干のノイズ(というより明らかに一定の寄生発振)を出していますが、この用途には問題ないですね。この際音のゲインが振り切るように調整しました。ダイヤル5を回すと次の出力になります。

f:id:a-tomi:20200919140030j:plain

オフセットは2.5V(電源電圧の丁度半分)で、ダイヤル音で0ボルトから電源電圧(5V)まで振り切るようにゲインを調整しました。

これでダイヤルごとの戻り時間を調べた結果、つぎのようになっています。

f:id:a-tomi:20200919140301j:plain

 

式に従って計算をし、次の判定表を作りました。

f:id:a-tomi:20200919140445j:plain

とはいえ、音の出ている間の波形は次のように乱れていますから、ダイヤルの識別には少し工夫が要ります。

f:id:a-tomi:20200919140649j:plain

絶対値のオンオフだけで識別するわけにはいきません。しかし1回の測定に数ミリセカンドかかっても問題ないわけなので、多数回のサンプリングをして「オフセットからのずれ幅が一定割合以上起きている場合にダイヤルが戻り中」と判定すればよいと考えつきました。

というわけで値をTM1637ィスプレイに表示するとして、次の回路にしました。ATmega328pで書いてありますが、Arduino-UnoでOKです。毎度きたない手書きのメモですみませんが、今回は回路を書くほどのものではありませんね。

f:id:a-tomi:20200919141341j:plain

実験した写真は次です。Arduinoのプログラムはこの記事の最後につけておきます。

f:id:a-tomi:20200919141601j:plain

これでテストすると、マイクを閉じ込めただけあって周囲の雑音はほぼ拾いません。玩具をガンガンたたいたりすると時々1が表示されます。そこで、短い信号を無視したり、定数を調整したり、ダイヤルがずれにくいように滑り止めマットを敷いたりして、無事にうまくできあがりました。

調整には思いのほか時間がかかりました^^;

ちなみに、このおもちゃはガチャポン(たぶん¥300)での購入用のようですが、次のネット販売で5月末に予約して9月に入手しました。1個250円程でした。

f:id:a-tomi:20200919142604j:plain

1パックに4個はいっています。どれも精巧ですばらしい!

f:id:a-tomi:20200919153056j:plain

 

というわけで、今回試験したArduinoスケッチをつけておきます。あまりきれいな書き方ではありませんが。

/* *********************************************************
   Rotating dial to TM1637 (for Arduino demonstration)
      Initial version V00  Sept. 19, 2020 (c) Akira Tominaga
 *  ********************************************************/
// for measurement
#define aPin 0                // A0 pin for analogRead 
uint32_t onS = 0;             // signal-on started time
uint32_t Now;                 // current time mS from start
uint8_t nSamp;                // number of voltage samplings
#define maxSamp 40            // max number of samplings
uint8_t nOn;                  // counter of signal-on
#define thOn 10               // threshold to detect signal-on
#define Vcenter 511           // analog voltage center value
#define Vth 300               // threshold offset from center 
uint32_t sigLen;              // signal length in milli second

#define offSt 0               // last satus was off
#define onSt 1                // last satus was on 
uint8_t lastSt = offSt;       // last status code

uint16_t aVal;                // analog read value
uint16_t vVal;                // offset = | Vcenter - aVal |

// each dial max duration milli seconds
#define d1max 417
#define d2max 546
#define d3max 674
#define d4max 802
#define d5max 931
#define d6max 1059
#define d7max 1188
#define d8max 1316
#define d9max 1445
#define d0max 1650
#define d1min 340
#define dAdj 27               // adjusting lenght for handling

// for led TM1637
#define lDIO 6                // DIO for TM1637 LED
#define lCLK 7                // CLK for TM1637 LED
#define lBrt 0x03             // duty-r 1/16x 02:4,03:10,05:12,07:14
#define lTu 50                // time unit in micro-second
byte lChr;                    // single byte sent to LED
byte Data[] = { 0x08, 0x08, 0x08, 0x08 }; // LED init 8888

void setup() { // ***** Arduino setup *****
  pinMode(lCLK, OUTPUT);
  pinMode(lDIO, OUTPUT);
  digitalWrite(lCLK, HIGH);
  digitalWrite(lDIO, HIGH);
  Serial.begin(9600);
  lDispData();                // display 8888
  delay(1000);
  for (uint8_t i = 0; i < 4; i++) {
    Data[i] = 0x10;           // display blank
  }
  lDispData();
  delay(1000);
  Serial.println("Starting");

  /*****************************************
     Measure voltage to find signal
   *****************************************/
mainLp:
  nOn = 0;                    // set sig counter zero
  for (nSamp = 0; nSamp < maxSamp; nSamp++) {
    aVal = analogRead(aPin);
    vVal = Vcenter - aVal;
    if (Vcenter < aVal) {     // get abs offset vVal
      vVal = aVal - Vcenter;
    }
    if (vVal > Vth) {
      nOn++;
    }
  }
  Now = millis();
  if (nOn >= thOn) {
    goto onRtn;
  }
  // goto offRtn;

offRtn:
  if (lastSt == offSt) {
    goto mainLp;
  }
  sigLen = Now - onS;
  if (sigLen < (d1min+dAdj)) goto mainLp; // if short, noise
  lastSt = offSt;
  onS = 0;

  // valid on length here
  //Serial.println(sigLen);
  if (sigLen <= (d1max+dAdj)) {
    dispDial(0x01);
    goto mainLp;
  }
  if (sigLen <= (d2max+dAdj)) {
    dispDial(0x02);
    goto mainLp;
  }
  if (sigLen <= (d3max+dAdj)) {
    dispDial(0x03);
    goto mainLp;
  }
  if (sigLen <= (d4max+dAdj)) {
    dispDial(0x04);
    goto mainLp;
  }
  if (sigLen <= (d5max+dAdj)) {
    dispDial(0x05);
    goto mainLp;
  }
  if (sigLen <= (d6max+dAdj)) {
    dispDial(0x06);
    goto mainLp;
  }
  if (sigLen <= (d7max+dAdj)) {
    dispDial(0x07);
    goto mainLp;
  }
  if (sigLen <= (d8max+dAdj)) {
    dispDial(0x08);
    goto mainLp;
  }
  if (sigLen <= (d9max+dAdj)) {
    dispDial(0x09);
    goto mainLp;
  }
  if (sigLen <= (d0max+dAdj)) {
    dispDial(0x00);
    goto mainLp;
  }
  dispDial(0x11);  // for others, too long signal "L"
  goto mainLp;

  /*****************************************
     Signal on case
   *****************************************/
onRtn:
  if (lastSt == offSt) {
    onS = Now;
  }
  lastSt = onSt;
  goto mainLp;

}             // end of Setup

void loop() { // ****** Arduino Loop *****
  // never comes here
}

/**************************************
      User defined functions
* *********************************** */
// *** dispDial(#) *** display dial number ***
void dispDial(byte dN) {
  for (int k = 0; k < 3; k++) {
    Data[k] = Data[k + 1];    // shift Data up
  }
  Data[3] = dN;               // set dial number to digit 3
  lDispData();
}

// *** lDispData *** display data to 4dig LED TM1637
void lDispData(void) {
#define showDat 0x40          // show this is data
#define showAd0 0xC0          // LED data addr is zero
#define showDcB 0x88+lBrt     // show dCtl + brightness
  lStart();                   // start signal
  lChr = showDat;             // identifier for data
  lSend();                    // send it
  lStop();                    // stop signal
  lStart();                   // and restart
  lChr = showAd0;             // identifier for address
  lSend();                    // send it
  for (int j = 0; j < 4; j++) { // for Data[0] to Data[3]
    byte edChr = Data[j];       // set a byte to edChr for edit
    switch (edChr) {
      case 0x00: lChr = 0x3F; break; // 0
      case 0x01: lChr = 0x06; break; // 1
      case 0x02: lChr = 0x5B; break; // 2
      case 0x03: lChr = 0x4F; break; // 3
      case 0x04: lChr = 0x66; break; // 4
      case 0x05: lChr = 0x6D; break; // 5
      case 0x06: lChr = 0x7D; break; // 6
      case 0x07: lChr = 0x07; break; // 7
      case 0x08: lChr = 0x7F; break; // 8
      case 0x09: lChr = 0x6F; break; // 9
      case 0x10: lChr = 0x00; break; // blank
      case 0x11: lChr = 0x38; break; // L for too large value
      default:   lChr = 0x79;        // E for Error
    }
    lSend();                  // send each byte continuously
  }                           // end of for bytes
  lStop();                    // stop signal
  lStart();                   // restart
  lChr = showDcB;             // identifier for display brightness
  lSend();                    // send it
  lStop();                    // stop signal
}

// *** lSend *** send a charater in lChr to TM1637
void lSend(void) {
#define LSb B00000001         // Least Significant bit
  for (int i = 0; i < 8; i++) { // do the following for 8bits
    if ((lChr & LSb) == LSb) {  // if one then
      digitalWrite(lDIO, HIGH); // set lDIO high
    } else {                    // else
      digitalWrite(lDIO, LOW);  // set lDIO low
    }
    lCLKon();                 // clock on to show lDIO
    lChr = lChr >> 1;         // shift bits to right
  }
  digitalWrite(lDIO, LOW);    // pull down during Ack
  lCLKon();                   // clock on to simulate reading
}

// *** lStart *** send start signal to TM1637
void lStart(void) {
  digitalWrite(lDIO, LOW);
  delayMicroseconds(lTu);
  digitalWrite(lCLK, LOW);
  delayMicroseconds(lTu);
}

// *** lStop *** send stop signal to TM1637
void lStop(void) {
  digitalWrite(lCLK, HIGH);
  delayMicroseconds(lTu);
  digitalWrite(lDIO, HIGH);
  delayMicroseconds(lTu);
}

// *** lCLKon ** clock on to show data to TM1637
void lCLKon(void) {
  digitalWrite(lCLK, HIGH);
  delayMicroseconds(lTu);
  digitalWrite(lCLK, LOW);
  delayMicroseconds(lTu);
}
/* End of program */

 何とかできることが分かったので、同じ処理を小さなPICなどに組んでケース内に収め、本物の回転ダイヤルと同じように数値パルスを出せば完璧な模型となりますね^^

 

では今回はこのへんで。

 

©2020 Akira Tominaga, All rights reserved.