勝手な電子工作・・

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

省エネ用のSleep + Watch Dog Timer(Arduinoでもできる!)

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

単3電池を長期間(2~3年)もたせるため、これまではPICでSleepとWatch Dog Timer(WDT)を使うようにしていました。今回は、それをやったついでに、Arduinoで同じことができるかを試してみました。上の写真はArduino-UNOからとり出したATmega328P単体を使って実験している様子です。

WDTはマイコンの動きを監視し一定時間以上反応がなければリセットして再起動をする機能です。この記事で書くのは、何が普通でないのかといえば、WDT検出でマイコンをリセットはせず、Sleepの直後にある命令から自動的に再開したいという目的です。これがうまくできれば、別途に割り込みを作らずWDTだけですむので、しかけが単純になりとても便利です。そして、Arduinoでやってみて分かった方法を、まずはこの記事に書いておきたいというわけです。

事の発端は、2階のベランダにスズメ一家が立ち入らないようにするためのちょっとした工作です。

庭で遊んでいるときは雀の学校のようで可愛らしいのですが、どうやら早朝のトイレの教育(?)にベランダを使うというのは感心しません。最近は毎朝欠かさずですから、毎度の掃除はたいへんです。そして、「このベランダには来ないように何とかならない?」と家内に頼まれたわけです^^;

一般的にはCDを吊るすとか、鳥よけテープを使うとか、最悪ではネットを張るとか、方法は色々とあるようです。たぶんですが、いちばん簡単な方法は、眩しい超高輝度白色LEDを付近でチカチカさせることではないでしょうか。勝手な電子マンとしては、早速週末にそれを試すことに。

ベランダに設置して放置し、単3電池を数か月はもたせる必要がありそうで、どうしても省エネ処理が必要です。前に作った次のリンク先のドロボー除けと同じようなものです。

いちばん簡単なドロボー除け(かな?) - 勝手な電子工作・・

そちらは設置してから1年半近くたちますが、未だにどれも単3電池を代えずに完動中。その時はSleepに加えて外付けRCによりクロックの長周期化で、電池が数年間持つようににしたのがうまく実現できていますから。

今回は数か月もたせれば良さそうなので、内蔵クロック(16MHz)で動かすことにして回路を簡素にすることにしました。外付けは超高輝度LEDとその直列抵抗だけです。

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

超高輝度LEDは10mmφ砲弾型で、20mAで20カンデラも光るという仕様。前に千石電商で購入したものです。順方向3.3V以上が必要のため4.5V電源とし、10mA程度で点灯させるよう直列抵抗を100Ωにしました。これで消費電流を測るとSleep状態(全体時間の97%)は20μA、点灯状態(3%)はほぼ10mAです。よって平均400μA未満。これだと連続半年はもちそうです。

点滅は次の動画のようになります。動画の後ろのほうでWatchDogが吠えるのでビックリしないでください、冗談にいれてみたのですが^^

 

youtu.be

 このPICプログラムは記事の最後、Arduinoのスケッチの後ろにご参考用につけておきます。

動作確認後、ユニバーサル基板に作り電池ボックスに貼り付けました。ごく簡単なので短時間に3つ作りました。

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

そして雨除けのため、それぞれ全体をビニールのチャック袋に収めました。

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

この3つをベランダの長手方向に向けて並べて配置。翌朝はベランダへの来訪はぴたりと止まり、フンが全くありません^^ しめしめ!

とはいえ鳥も頭がよいので、そのうちに慣れてまた来るかもしれません。このまましばらく様子を見ることにします。

 

さて、省エネアプリケーションに毎度PICアセンブラを持ち出すのもどうかと思うわけですから、今回は同じことをArduinoでやってみるわけです

省エネが目的なので、Arduino UNOなどの開発ボードのまま動かす(電流ジャンジャン食う)のではなく、UNOでプログラムを書き込んだら、そこからATmega328P単体を取り出して製作に使うわけです。取り出しやすくするためには、UNOに無圧ソケットを挿しておくと楽です。

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


ATmega328Pを何度も取り出すための、無圧ソケット(ゼロプレッシャーソケット)の上手な取り付け方は、前に次の記事に詳しく書きましたのでご参照ください。

Zoom用かんたん操作ボタンをArduino-UNOで作る(その1) - 勝手な電子工作・・

 

ちょっと脱線しましたが、本論であるWatch Dog Timerによるリセットを伴わないSleepからの復帰方法についてです。

そもそもWDTは稼働していることをタイマーで見張る番犬なので、起動すればマイコンにリセットがかかる、つまり再起動させる使い方が普通なわけです。それでも省エネアプリケーションを工夫してできないことはないのですが、再起動時にはSleepの次の命令に自動的に戻って欲しいものです。

Watch Dog Timerではなく他の割り込みを使えばできるわけなのですが、毎度それを考えるのも面倒。PICのやり方と同じように、WDTにシステムリセットをしない割り込みとして使えれば、回路もプログラムも簡単になるわけです。

まずATmega328PのデータシートでWatch Dog Timerのあたりを何度も読み返してみました。一番主なところはWDTCSRというコントロールレジスターの使い方です。下の図では文言をカットしてありますが、長々と書かれています。やや複雑なのですがシツコク読みます。

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

そして、ついにMCUにResetをかけず割り込みだけしてSleepから目覚めそうな方法をUNOで試しました。結構複雑で何度かやりなおしましたが、しつこくやってやっと正解らしき方法にたどり着きました。そのスケッチを後ろにつけておきます。

省エネの電流を測るために、次のようにATmega328P単体用の回路を組みます。

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

普通に16MHzのクリスタルを使い5Vでテストしたところ、POWER-DOWNさせるSleep時でも80μAほどを消耗します。そこで、Sleepの間はADCへのPower供給を止めるようにしてみたところ、これで26.8μAしか消耗しないことがわかりました。

クロックを遅くするなど更に色々やってみることがあるかとは思いますが、とにかく今後はSleepとWatch Dog Timerの組み合わせだけで、Arduinoでも簡単に省エネ処理ができそう\(^_^)/

次が今回のArduino-UNOのテストスケッチです。その後ろにPICアセンブラーの実プログラムをつけておきます。

/*******************************************************
   Watch Dog Timer test for Arduino (ATmega328P)
      Initial version 7/18, 2021   by Akira Tominaga
 *******************************************************/
#include "avr/sleep.h"
#include "avr/wdt.h"
#define LED 13

void setup() { // ***** Arduino setup() *****
// sleep time definitions for Watch-dog-timer
#define sT1 B00000110     // 1 second
#define sT2 B00000111     // 2 seconds
#define sT4 B00100000     // 4 seconds
#define st8 B00100001     // 8 seconds
#define sleepTime sT4     // select sleep-time from above
  pinMode(LED, OUTPUT);
  digitalWrite(LED, LOW);
  // for Sleep and Watch-dog-timer
  set_sleep_mode(SLEEP_MODE_PWR_DOWN);
  setWDT(sleepTime);
}

void loop() { // ***** Arduino loop() *****
  digitalWrite(LED, HIGH); // LED on
  delay(20);              // for 20mS
  digitalWrite(LED, LOW); // and off
  deepSleep();            // sleep + ADCpower-off
}

ISR(WDT_vect) { // *** Intrpt svc rtn for WDT ISR(vect) *****
}                    // do nothing here

/**********************************
   User defined functions
 **********************************/
// ***** Setting Watch dog timer *** setWDT(seconds) *****
void setWDT(byte sleepT) {
  sleepT += B00010000;    // sleepTime + Enalbe WD-change bit-on
  MCUSR &=  B11110111;    // Prepare WDT-reset-flag in MCU-status-Reg
  WDTCSR |= B00011000;    // Enable WD-system-reset + WD-change
  WDTCSR  = sleepT;       // Set sleepTime + Enable WD-change
  WDTCSR |= B01000000;    // Finally, enable WDT-interrrupt
}
// ***** Sleep + Stopp ADC power *** deepSleep() *****
void deepSleep(void) {
  ADCSRA &= B01111111;    // disable ADC to save power
  sleep_enable();
  sleep_cpu();            // sleep until WDT-interrup
  sleep_disable();
  ADCSRA |= B10000000;    // enable ADC again
}
// *** End of program

 

細かい説明は省略させていただきますが、とにかく別途の割り込みのしかけもいらず、短くて簡単ですね。要は単に休ませるというDelayの代わりに所定時間のSleepを入れるだけですから。おそらく4秒のWatchDogTimerだけ用意しておけば、Loopさせることで1分とか10分とかにも対応しやすいかと思います。

このやり方は簡単だしメモリー消費なども殆どない点がよいかと勝手に思います。

もし正確に10分単位などでの計測が必要なら、9分56秒だけスリープさせてから次にRTCの分の値の変化タイミングで処理することなどで精密にできると思います。

 

次に今回の雀よけPICプログラムをご参考までに載せておきます。

;U210718-WDT-12F1822-V00t.asm			     		As of 7/18, 2021
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;										;
;    Sleep and Restart by WDT for PIC12F1822 V00 				;
;	        (C)2015-2021 Akira Tominaga, All rights reserved.		;
;	  Function								;
;		1. Blink bright LED to drive mischievous birds away		;
;	  Input/output       							;
;		RA0 LED output							;
;	  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_SWDTEN & _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			;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;
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'11111110'	; RA0 is output
	movwf	TRISA
;
	BANKSEL	OPTION_REG	; Bank=1
	bcf	OPTION_REG,7	; Enable weak pull-up
	BANKSEL	WPUA		; Bank=4
	movlw	B'00111110'	; Weak Pull-up for RA 1 to RA5
	movwf	WPUA		;  
;
	BANKSEL WDTCON		; Bank=1
	clrf	WDTCON		; WDT is set by program
;
	clrf	BSR		; Bank=0
	InitP			; Initialize ports
	endm
;
InitP	macro			; Initialize ports
	movlw	B'11111110'	; All inputs pulled-up and outputs L
	movwf	PORTA
	endm
;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;	I/O macros			;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; 	For general purpose	;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
LEDon	macro			; LED on
	goto	$+1
	bsf	PORTdbg,LED
	goto	$+1
	endm
;
LEDoff	macro			; LED off
	goto	$+1
	bcf	PORTdbg,LED
	goto	$+1
	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
;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; 	Files and Equations					;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;	Files				;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
	cblock	H'20'
;
; For Application support
;
; 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
;
	endc		
;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; 	Equations			;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
LedOnT	equ	D'25'		; LED-on Time(25mS)
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; 	For PORTA		;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;LED	equ	0
;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; ***	Logical PORTdbg		; Change this when port changed
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
PORTdbg	equ	PORTA		;
LED	equ	0		; LED for debugging
Trig	equ	0		; DSO trigger pulse port for test use
;
	page
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;	Initializing						;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
	org	0
	goto	Startp		; Go to start entry
	org	4		; This is Interrupt entry
	retfie			; 
;
Startp	DEVset			; Define ports
;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;	Main program loop					;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
Mainp	nop
;Step1	Blink LED once and wait a second
	call 	WDoffr		; Off watch-dog-timer
	LEDon			; Blink LED once
	Milli	LedOnT		; for LedOnT (25mS)
	LEDoff
	call WD1Sr		; On watch-dog-timer 1 Sec
	sleep			; This includes clrwdt
;
;Step2	Blink LED once again and wait a second
	call 	WDoffr		; Off watch-dog-timer
	LEDon			; Blink LED once
	Milli	LedOnT		; for LedOnT (25mS)
	LEDoff
	call	WD1Sr		; On watch-dog-timer 1Sec
	sleep			; This includes clrwdt
;
;Step3	Blink twice with 0.5 sec sleep and wait a few seconds
	call 	WDoffr		; Off watch-dog-timer
	LEDon			; Blink LED 1st time
	Milli	LedOnT		; for LedOnT (25mS)
	LEDoff
	call	WDqSr		; On watch-dog-timer short
	sleep			; This includes clrwdt
	LEDon			; Blink LED 2ndt time
	Milli	LedOnT		; for LedOnT (25mS)
	LEDoff
	call	WD1Sr		; On watch-dog-timer long
	sleep			; This includes clrwdt
	goto	Mainp		; Contilue infinite loop
;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;	Watch-dog-timer subrooutines				;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; WDT bit0=0 : WDToff  bit0=1 : WDTon
; WDT bit5-1: Time value
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; 	Set 1-Second WDT		;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
WD1Sr	equ	$
	BANKSEL	WDTCON
	movlw	B'00010101'	; Enable 1 sec WDT
	movwf	WDTCON
	clrf	BSR		; Set Bank=0
	return
;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; 	Set a quarter Second = 256mS WDT;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
WDqSr	equ	$
	BANKSEL	WDTCON
	movlw	B'00010001'	; Enable 256mS WDT
	movwf	WDTCON
	clrf	BSR		; Set Bank=0
	return
;l
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; 	Set 8-Second WDT		;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
WD8Sr	equ	$
	BANKSEL	WDTCON
	movlw	B'00011011'	; Enable 8 sec WDT
	movwf	WDTCON
	clrf	BSR		; Set Bank=0
	return
;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; 	Set WDT off			;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
WDoffr	equ	$
	BANKSEL	WDTCON
	bcf	WDTCON,SWDTEN	; Disable SW Watch Dog Timer
	clrf	BSR		; 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
;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;	End of program						;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
	end

 出かける寸前に慌てて書いた記事でしたため、行き届かない点があればすみません。省エネが必要な測定などにもしお役に立てば幸いです。

 

©2021 Akira Tominaga, All rights reserved.

 

 

カラーLEDテープ照明を自由にコントロール

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

今回はLED照明テープをマイコンで好きなように点灯させる方法です。LED照明テープとは、RGB LEDがテープにならべられた装飾用の照明です。

名前はRGBテープ照明とかカラーLEDテープライトとか色々な呼称があるようで、電源とコントローラ装置がついているセットが多く販売されているようです。

今回はこのテープをArduino-IDEを使って好きなように動かそうというわけです。

必要な長さに切って使うことができるので、余りが出ます。手元にある余りは次の写真で、それぞれ半端な長さです。

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

この照明テープは次の写真のように、3つのLEDが1単位で自由に切って使えます。つまり、LED3つずつが1ユニットになっており、各ユニットが1つのIC (WS2811)でコントロールされています。電源はLED3つを直列に設定しているようで12Vと書いてあります。

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

3つ並んだ銅メッキの端子ランドの位置で、ハサミで自由にカットできます。

コントロールICの電源はその12Vから内部でレギュレータを通して供給されているようです。入り口に電源供給ワイヤが余分についていたりするためDIYで扱うときは気を付けないといけませんね。これをマイコン電源につないでこわさないよう。

電源はLEDとコントロールICの両方に使う用途ですが、原理から見れば12Vでなくて9V供給でも大丈夫そうですね。LEDは非常に明るい(眩し過ぎる)LEDですし。

少し新しい型では、LED3つずつの単位ではなく、1つずつ自由にコントロールできるものがあります。この場合はLED電源とIC(WS2812B)は共通の5V供給となっています。次の写真ですが、ICとLEDとが一体になっています。

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

さらに、WS2813になると途中のICが壊れてもそこから先のICに信号が伝わるように工夫されています。長いテープの途中では電圧降下が起きるため電源の追加供給が必要となりますが、最近のWS2815では12V電源にすることで追加供給が不要にされています。

 

どれも、制御するためのデータは「1ワイヤプロトコル(1線式プロトコル)」で送信します。1線式といってもCRC(巡回冗長検査符合)などはなく、RGBの明るさ(デューティ比)を255までの数値で3つ並べた3バイトを順次送信するという簡単なものです。

ここで使ったものは1ICあたり3バイトをBRGの順に送ります。ICのユニット数分だけ、バイト間をあけずに一気に送り込む必要があります。つまりICが10個並んでいる場合は30バイトを切れ目なく送るわけです。すると照明の状態がその並びの通りにセットされて維持されます。変更するときは、リセット信号(単に50μS以上のオフ)を出して、再び新しい並び順に送ればよいわけです。

 

ところが、

LEDテープの1ワイヤプロトコルはタイミングの制約が厳しく、遅いマイコンでやるのは難しいのです。

1ビットがHとLの組み合わせで、Hが長くLが短いのが1、逆が0です。それを1.25μS周期で送らないといけません。ビット表示の間を全くあけられませんから、遅いマイコンの場合は処理が到底間に合いません。

なぜ16MHzのArduino-Unoで動くかと言えば、ライブラリー(上の例で使っているのはFastLEDライブラリ)がハードウェア端子を直接操作しているからです。こういうライブラリーを使わない限りは、普通のC(Arduino)言語では無理です。

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

Arduino UnoでFastLEDライブラリーが出している0信号(H0.5μS+L0.75μS)

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

Arduino UnoでFastLEDライブラリーが出している1信号(H1μS+L0.25μS)

16MHzのPICをアセンブラーでどうかといえば、できそうですが簡単ではありませんね。PORTのビットをオンオフするには間に2サイクル取らないといけませんから、それだけで0.5μSかかってしまいます。1サイクルにするためにはPORTレジスタでなくLATレジスタを操作しなければなりませんが、それにはメモリーバンク番号を通常ではない設定のまま動かすことに。さらに信号間の処理ができないわけで・・・これじゃ普通は堪りませんね^^; 

ですがライブラリーのインターフェイスに縛られず、Arduino-IDEで好きなように使いたいと考える人は多い事でしょうね。LEDテープは飾りや照明でなく、色々な表示に応用が利きそうですからね。

そこで

Arduino-IDEで気軽に作るには、速いマイコンを使うに限ります。ここでは手持ちのTeensy4.0を使います。ある程度速ければそこまでの速度がなくともOKですが。

処理能力比較は例えば前に次の記事に書きましたが、ここでは1CPUあたりの速度で考える必要がありますね。

https://a-tomi.hatenablog.com/entry/2021/03/19/225327

 

さて、Arduino言語にタイミングをとるためのdelayMicrosecondsはあっても、delayNanosecondsがまだない^^; というわけで、LEDテープで好き勝手をするような自作例が見当たらないわけでしょうね。そこをどうするかさえ勝手に考えればArduino-IDEで楽勝となりそう!

まずは、切りっ放しのテープの端子にワイヤーをつなぎます。この余りテープには30個のLED、つまり10ユニットが並んでいます、右のほうに。

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

12V端子へオレンジ、Dinと記された端子へ黄色、そしてGnd端子に黒2本をはんだ付けし、接続部全体を透明熱収縮チューブで保護しました。LED電源としてオレンジと黒の間に9VDCを供給します。そこに念のために100μFを並列に入れてあります。

そして、Teensy4.0で、デジタル14ピンに信号を出すようにします。次の写真のように。

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

テストプログラムは次のように作りました。9ユニット分にそれぞれ別の色を出し、10番目のユニットには送りません(光らせない)。

/**************************************************
 *  LED-Tape test V.00                            *
 *        1st version 6/30,2021(c)Akira Tominaga  *
 *     Remarks:                                   *
 *        WS281X IC on the tape                   *
 **************************************************/
#define Out 14                // output pin d14
// *** color definitions
#define Blue (128,0,0)
#define Red (0,128,0)
#define Green (0,0,128)
#define Purple (62,66,0)
#define Yellow (0,64,64)
#define Orange (0,100,32)
#define BlueGreen (64,0,64)
#define BluePurple (88,40,0)
#define White (43,43,43)
#define Black (0,0,0)
// 
uint8_t colorD[3];            // 3 values for BRG 
byte Byte;                    // working byte 

void setup() { // ***** Teensy setup() *****
  pinMode(Out, OUTPUT);
  digitalWrite(Out, LOW);
  delay(3000);                // time to stabilize pwr
  Rst();                      // reset LED controller
  sendColor Red;              // unit 1
  sendColor Orange;           // unit 2
  sendColor Yellow;           // unit 3
  sendColor Green;            // unit 4
  sendColor BlueGreen;        // unit 5
  sendColor Blue;             // unit 6
  sendColor BluePurple;       // unit 7
  sendColor Purple;           // unit 8
  sendColor White;            // unit 9
  // *** add more units if required 
  Rst();                      // reset LED-controller
  while (true) {}             // stop w/ keeping lights
}
void loop() {  // ***** Teensy loop() ***** unused
}
/************************************************
 *    User defined functions			*
 * **********************************************/
// ***** Send Color *** sendColor(blue,red,green) *****
void sendColor(byte b, byte r, byte g) {
  Byte = b;
  sendByte();
  Byte = r;
  sendByte();
  Byte = g;
  sendByte();
}
// ***** send Byte *** sendByte() ***** 
void sendByte(void) {
  for (int k = 0; k < 8; k++) {
    if (Byte &(B10000000>>k)) {
      One();
    } else {
      Zero();
    }
  }
}
// ***** send a bit 0 *** Zero() *****
void Zero(void) {
  digitalWrite(Out, HIGH);
  shortDelay();
  digitalWrite(Out, LOW);
  delayMicroseconds(1);
}
// ***** send a bit 1 *** One *****
void One(void) {
  digitalWrite(Out, HIGH);
  delayMicroseconds(1);
  digitalWrite(Out, LOW);
  shortDelay();
}
// ***** delay about 0.25μS *** shortDelay() *****
// Adjust this, on your micro-controller speed
void shortDelay(void) {
  for (uint8_t m = 0; m < 20; m++) {
    delayMicroseconds(0);
  }
}
// ***** Reset LED controller *** Rst() *****
void Rst(void) {
  digitalWrite(Out, LOW);
  delayMicroseconds(100);
}
// end of program

 ライブラリーは何も使いませんが、かなり単純にできますね。いきなり実際のテストはせず、まずは動かして出力信号のタイミングをオシロで確認します。

点灯したいユニットの数だけsendColorを並べるだけですし、残りのユニットは点灯しません。実際の数より多くの信号を送った場合は、存在するユニット数だけが点灯します。

1を送る際は オン1μS+オフ0.25μS とし、0を送る際は

オン0.25μS+オフ1μS としました。

0.25μSを作るには、本当はdelyNanoseconds(250)とか、delayMicroseconds(0.25)とか書きたいわけなのですが、そんなことできませんね。ここではdelayMicroseconds(0)として、呼び出しのオーバーヘッド時間を使うことにしました。

オシロで見ながらぴったりになるように調整した結果、Teensy4.0の場合は空呼び出しを20回程度すればちょうどよいのですが、他の高速マイコンを使う場合は、速度に応じてshortDelay(関数内にあるこの回数を調整してください。

さてこうしてから動かしますと、問題なく意図通りに動きます!LEDが明るすぎるので、全体に色の数値を下げた状態が上のスケッチですが、それ以外は問題はないようです。これなら簡単に好きなようにプログラムをすることができますね。RGBの値を変数にしたりしてやりたい放題が^^

おお!これでカラーLEDテープ照明が自由自在だ!

 

ところがですよ、とても明るくきれいに光っているLEDテープをうまく写真に撮るのは結構難しい。露出を数段下げたぐらいじゃ色がリアルにでない。照明用LEDなので明るすぎますからね。次の写真の下段のように、ピントをわざと外すとようやくリアルに近い色が表現されます^^. 

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

では今回はこのへんで。

LEDテープをDIYで好き勝手に動かしたい方のお役にたてば幸いです。

 

©2021 Akira Tominaga, All rights reserved.

 

 

Nixie管時計を安直に作る(番外編)

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

外出自粛で週末は例によって片付けを開始。そこへ古いNixie管が出てきました。数本なら思い切って断捨離というところですが、9本も出たのでとてもそんなことできません^^;

見るとどれも1982年ソ連製のIN-12A(ИH-12A)で揃っています。

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

ピンの傷みもない逸品で、たぶん1990年頃に秋葉原の「日米商事」で購入した使い残しと思います。その頃多忙でも週1回ぐらいは帰りに寄り道して珍品をみつけていました^^;

今寄せ集めたデータシートの主なページは次です。

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

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

LOWになったピンにつながった数値を表示をするという単純な機構です。1番ピンがアノード、2番から11番がその陰極用で0、9~1の数値表示、12番はIN-12Aは空きでIN-12Bはドット表示。

仕様では200V電源から抵抗を介してアノードに接続、LOWにした陰極がネオンガスで光るわけで、グローランプや冷陰極管みたいなものです。

電流は1~2mA(表示によって少し違う)となるよう、またアノード・カソード間電位差が150V以下(140Vあたりが適)になるように制御します。仮に1.5mAで50Vの電位差を作るにはアノード抵抗に33kΩが必要です。もし1.1mAなら45kΩなので、安全側の47kΩでテストしました。

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

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

寿命は7500時間程度とありますが、与える電圧や電流次第です。アノード抵抗47kΩの場合、電源を190Vに固定し各数値表示の電流を測ると、どの数値もほぼ1.1mA前後となりました。

ピンのどれか1つをLOWにして残りをハイインピーダンスするには、例えば①BCDデコーダーICで数値を各ピンの信号に変換(今なら直接マイコンで出す手もないわけではありませんね、ピン数を多く消耗しますが)。そこから②高耐圧トランジスタMOSFET)アレイなどのベース(ゲート)に接続しておき、オープンコレクター(オープンドレイン)接続にするわけです。

 

やり方とデコーダICなどについて詳しく書かれている次のサイトを見つけました。英語ですがGoogleで訳してもほぼ大丈夫。たいへん役立ちます。

IC drivers for Nixie Tubes – GRA & AFCH

 

表示方法は単純なのですが、そういうわけで1本の管を使うのにBCDデコーダ1個と11要素の高圧トランジスタアレイを使う必要があるわけで、配線はどうしても複雑になりますね。どうやって短時間で工作できるか考え込んでしまいました。数値表示の価値がその昔、いかに高かったかの証拠でもありますね^^;

 

時間を消耗したくないので、ここからは安直な方法へと直行です!!

ネットでまずは基板がないか探してみます。eBayなどを漁ると次がありました。

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

これだとまだまだ面倒ですから、さらに探していくと次が見つかりました。

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

SMDのはんだ付けが不要なので楽そうです。ただ、各管のメスピン48本をどうするか(デュポンメスピンでいけるかも)と、試すにはちと高価か・・ということで次にAli-expressで次を見つけました。

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

送料を足せば似たような価格になりますが、専用ピンがそろっているのがいいですね。組立にはあまり時間がかかりそうもない。管がない以外はほぼ出来上がり製品といえるかもしれません。

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

主要作業は48本のピンのはんだ付けだけですから、これでちゃんと動くのなら「勝手なオリジナル工作」とはいえない番外編の安直版になります・・。まあしかし時間はいちばん大事ですからね。

注文後10日ほどで到着しましたので、週末に組み立てました。ハンダ付けを含めてたったの1時間半ほどで完了!

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

コンパクトでなかなか恰好良いではありませんか!

 

ところがPCB表面のシルク印刷がダサくて気にいりません。当面はラベルライターのテープを貼って、Nerd云々の文字を隠すことにしました。あとで表を全部黒に塗り直そうかな、などと考えています・・^^;

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

Webの出来上がり写真として次が載っていますが、支柱の使い方が不適切ではないかな・・と勝手に思います。

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

一番短い支柱をPCBと底板の間に使うべきではないかと。そうしないと天板からのNixie管等の飛び出しが大きすぎますから。つまり次のようにするとよいと思います。

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

管球用のメスピンのはんだ付けを上手に行う方法についてですが、予めメスピンを各管にしっかり挿しておき、管ごとそのまま基板にはんだ付けしていくと、正確で楽ちんにできました。

RTC(リアルタイムクロック)用のボタン電池を入れれば完成。電池は普通は使っていないCR1220(3V)ですので慌てましたが、幸いトヨタ某車のキーに使っていたのと同じでした。

電源をつなぐと問題なく稼働しました!

 

さて使い方です。

電源はUSBで1Aとなっていますが、測ると450mA程度でした。

全ての操作は次の写真の下の方にある2つのキー(UpとDown)だけで行います。

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

この2つのキーの短押しと長押しの使いわけをしますがそれなりに難しい^^;

例えば時刻合わせの場合、Down短押しで各桁の設定モードになりUp/Downで操作桁を移動。Down長押しで数値を変化。Downをしばらくホールドすると設定が終わる(終わらないときもある(・・?)。

Ali-expressの同じ商品の説明の中で、いちばん詳しく書いてあるのを選んでコピーしておくのが良いと思います。その他にバックライトカラーの変更やらSleepモードやら色々あってややこしいですから。

 

最後に勝手な余談を少し。

ここで使ったNixie管を探すとeBayやSOVTUBEなどではまだ扱われているのがわかります。昨日見たときは安いのは1本500~600円程度ですかね。使用時間が7500時間程度の管なので、中古でなく新古がいいですね。Amazonにもありそうですが高価。

それと不使用時は電源オフがいいですね。時刻自体は電池で維持されますので。

さらにホントの余談:

1970年代から数値表示には7セグメントLED等が使われ始めたと思いますが、ソ連では1980年代初めまでNixie管全盛とか。ちらちら調べてみると、1976年秋、ソ連から低空飛行で函館に亡命着陸(!)したベレンコ中尉のミグ25戦闘機事件。機内にはNixie管どころか真空管までも使われ、機体に鉄材もあった事に西側諸国がビックリ。「枯れた技術は絶対確実」の典型例かも・・。ソユーズなどでトラブルが殆どなかったし。

 

断捨離・片付けのつもりが、とんだゴミづくり(?)と時間の消耗になってしまいました。レトロ表示がお好きな方々のご参考にでもなれば幸いです。

 

©2021 Akira Tominaga, All rights reserved.

 

3.5インチTFT-LCD(480x320)をArduinoで試してビックリ

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

しばらく前に注文した3.5インチのTFT-LCD (480x320)が先週末にやっと到着。受取りと評価の連絡が必要なので、とりあえず簡単にテストをしようと・・忙しい時に限って色々到着するのは不思議ですが^^; 

海外での最近の梱包は多くが日本より丁寧になってきたのを感じます。また、商品への表示も的確になっています。これは段ボール箱にパッキンでくるんで入ってきました。

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

袋にはドライバーIC名がILI9488、接続がSPIとちゃんと表示されています。SKU(Stock-keeping Unit)としてLCDのモデル名(MSP3521)が書かれているのはナンですが、過去の表示なしとは大違い。

320x480と書かれているので縦長が基本でしょう。しかしソフトで回転すれば横長になるのでどちらでもいいわけですが。

テストしようかと、ネットで検索してみると、ESP32用のライブラリーは簡単に見つかります。

GitHub - loboris/ESP32_TFT_library: Full featured TFT library for ESP32 with demo application

ESP32だけということもないでしょうね。Teensy4.0ではみつかりませんが、Arduinoは当然あるでしょうね。小さいArduino-Mega-Pro-Miniの手持ちが生かせることでもありますし。

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

Arduino-Mega-Pro-Mini

Arduinoでの例を検索するとたくさんヒットしますが、その殆どは海外Forum投稿です。「Arduinoではうんともすんとも動かない・・・」とのQが散見され、回答も色々で正解が出てない感じ。まさかね??

注文する前に調べておけばよかったか?と不安になりましたが、今やほぼすべてのLCD機種のガイドやソフトが”LCD-WIKI”にあるわけなので、きっと妥当なガイドがあることでしょう。

http://www.lcdwiki.com/Main_Page

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


モデル名MSP3520で検索するとMSP3521(同じ型でタッチパネル装備)が出てきます。そのモデルなら同じ(サブセットとなる)わけですが。あるいはILI9488で検索してもそのページへたどり着くことができます。

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

ご覧のとおり仕様や関係マニュアル、ラズパイあるいはマイコン機種ごとの使えるソフトなど、網羅的に詳しく掲載されています。グラフィック表示装置を調べたり、関連資料やソフトウェアをダウンロードしたいときに便利に使える百科事典のようなものです。言語は英語か中国語だけなのですが、有難いことです。

というわけで、そこに表示されているArduino用のLCDWIKIライブラリーをダウンロードしてテストすることにしました。グラフィック表示なのでUnoでは容量が厳しいことになるのでArduino-MEGAで試します。

SampleスケッチでSPI接続を試すと、ありゃ? バックライトが点灯するだけでうんともすんとも・・・。

今はたまたま時間がないところなのですが、なにやら落とし穴がある感じですね。こうなったらしかたがない。プロトコルの点検に備え、サンプルプログラムでなく、ごく単純なテストプログラムを作りました(記事の後ろのほうに載せておきます)。テスト結果は当然ですが最初は同じこと。

そこで些細な情報を含めて一応はすべて見ることに。そしたら、Arduinoでデモをする際の注意事項という小さなリンク先があるのに気づきました。そこを見ると、次のことがわかりにくく(・・? 書いてあるのです!!  超要約をするなら

「ILI9488はこれまでのICと違い5VトレラントではないのでArduinoでは次のどちらかの対応が必要。

①すべての信号レベルを3.3Vに変換する。

LCD側のVcc等に無理に5Vを突っ込む。その際のやりかた。ただし発熱して寿命が縮むとあります。(え?そんなの選択肢なの? 短時間のデモには使えるとしても ・・・だからデモする際の注意か・・・)。

そういうことならしかたがないですね。インターフェイスのロジックレベル変換をします・・・。手持ちの8チャネル変換モジュールを使うことに。ピンボケ写真ですみませんが次のものです。

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

信号レベル変換モジュールはトランジスタ(FET)と抵抗の組み合わせです。HVに5Vを、LVに3.3Vを入れることで各信号の両方向レベル変換をします。価格は海外ネットでは1個数十円のものです。

これを入れたらなんのことはない、一発であっけなく動きました。

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

このLCDは比較的新しい製品で、試したものは今年の製造。コントラストも強く色もきれい。そして鮮明です。この大きさ、画素数と解像度で千円もしないとは。素晴らしいコストパフォーマンスだと思います!

簡単なテストプログラムを次につけておきます。サンプルプログラムは複雑すぎてテストや解析の用途には今一つですから。短時間で勝手に作ったスケッチで大したものではありませんが一応。

なお、カーブの表示を単純にdrawPixelで描きましたが、drawLineでつなぐ方がきれいに描けます。とりあえずはやってませんが。

/*************************************************************
 * Test program for 480x320 TFT-LCD w/ILI9488(18bit-color)
 * for Arduino Mega, using LCDWIKI libraries.
 *                May 22, 2021              Akira Tominaga
 * ***********************************************************/
#include "LCDWIKI_GUI.h"     // Core graphics library
#include "LCDWIKI_SPI.h"     // SPI library
#define MODEL ILI9488_18     // LCD's IC model selection
// HW-SPI pins: DI(MOSI)=51, SDO(MISO)=50(unused), and SCK=52
// the other LCD pins are to any pins 
#define CS   45              // or to ground if no other SPI used
#define CD   49
#define RST  47
#define LED  -1             // to declare unused (set to 3.3V)
// generate class lcd
LCDWIKI_SPI lcd(MODEL, CS, CD, RST, LED);
uint16_t wW;
uint16_t hH;
uint16_t testNum = 0;

void setup() { // ***** Arduino setup() *****
  Serial.begin(9600);
  wW = lcd.Get_Display_Height();
  hH = lcd.Get_Display_Width();
  Serial.print("*** TFT-LCD ");
  Serial.print(wW);
  Serial.print("x");
  Serial.println(hH);
  lcd.Init_LCD();
  lcd.Fill_Screen(0x0);
  lcd.Set_Rotation(1);   // rotate screen by 90 degrees
  //display main panel
  lcd.Set_Draw_color(16, 16, 96);
  lcd.Fill_Rectangle(0, 0, wW - 1, 19); // draw header bar
  lcd.Set_Text_Mode(1);
  lcd.Set_Text_Size(2);
  lcd.Set_Text_colour(255, 255, 255);
  lcd.Set_Text_Back_colour(0);
  lcd.Print_String("TFT-LCD test with LCDWIKI", CENTER, 3);
  //
  lcd.Fill_Rectangle(0, hH - 19, wW - 1, hH - 1); // draw footer
  lcd.Print_String("<https://a-tomi.hatenablog.com/>", CENTER, hH - 17);
  //
  lcd.Set_Draw_color(255, 0, 0);
  lcd.Draw_Rectangle(0, 20, wW - 1, hH - 20);
}

void loop() { // ***** Arduino loop() *****
  // *** Print_String
  String s = "Test ";
  s.concat(String(testNum));
  lcd.Set_Draw_color(0, 0, 0);
  lcd.Fill_Rectangle(100, 24, 340, 48);
  lcd.Set_Text_Size(3);
  lcd.Set_Text_colour(255, 255, 0);
  lcd.Print_String(s, CENTER, 24);
  // *** Draw  pixel for sin wave
  lcd.Set_Draw_color(0, 255, 255);
  for (int i = 1; i < wW - 1; i++)  {
    lcd.Draw_Pixel(i, hH / 2 + 3 + (sin((i * 3.14 + testNum * 20) / 180) * 100));
  }
  // *** Fill_Circle(x,y,r)
  lcd.Set_Draw_color(testNum * 5, 0, 255 - testNum * 5);
  lcd.Fill_Circle(wW / 2, hH / 2 + 1, testNum * 2);
  //
  delay(1000);
  testNum++;
  if (testNum > 51) {
    Serial.println("*** End ***");
    while (true) {}
  }
}
/* End of Test Program */

パラメータにcolor と colourが混じっていますが区別があります(統一するとコンパイルエラーになります^^)。このあたり、中華料理の調理並みに大雑把(?)なライブラリーにみえます。突っ込みどころ多数ですね。

TeensyでもESPでもコンパイルエラーになりますが・・。中身をどこかで解析して直すか、各マイコン専用のを使うのがよさそうです。

 

以上、メモもかねて勝手な記事にしました。何らかのお役に立てば幸い。

 

©2021 Akira Tominaga, All rights reserved.

 

手軽なWiFi信号スキャナー(今時役立つかも)

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

WiFi信号スキャナーをM5スタックで作ったのでご紹介します。

昨年来、住宅地の多くで近隣のWiFiネットワークが混みあってきているのではないかなと勝手に思います。この測定器はすぐできるうえ表示も見やすいのではないかと・・(自画自賛^^)

作った経緯:居間のテレビにつないである小型PCのWiFi通信が、最近ずいぶん遅くなった事に気づきました。ひょっとすると・・と、接続を802.11g(2.4GHz)から 802.11a(5GHz)に変更しました。ルーターから結構遠いので子機(アンテナ)もロッド4本のものに交換しました。これで通信が見違えるほど高速で快適になりました。遅くなっていた原因は、このところのテレワーク急増で付近の2.4GHz帯が混み出したためではないかな・・と考えついたわけでした。

比較的よく飛ぶ2.4GHz帯ですがBluetoothにも邪魔されやすいため、我が家では周辺装置も有線接続にしていますから、きっと周囲からの影響かなと考えたわけです。そこで、すぐでき操作も簡単、ポケットにいれて持ち歩ける測定器を作ったわけでした。

これを試してビックリ。近隣に意外に多くのWiFiルーターが林立しています! 1年前の何倍も多い感じです。PCで行う普段の作業はイーサネット接続なので、気づかなかっただけと思いますが。これでみると日付や時間帯によって登場するルーターの顔ぶれが少し違うようなのも不思議。スマホからのテザリングは見当たりませんので、ひょっとすると不使用時にルーター電源をオフにする方がおられるのでしょうかね?

当地は住宅の特別な密集地でもないのに、冒頭の写真のように20個のWiFiネットワークがひしめき合っています。表示は信号の強さ(dBm=デシベルミリワット)とそのSSIDで、右側にe-cと表示してあるのは各WiFiの暗号化タイプ(Encryption Type)と使用中のチャネル(Channel)番号です。続きの2ページ目を表示すると次です。

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

チャネル11に多く集まっているのは少し不思議なのですが、機種による特徴もありそうですね。

使っているM5スタックは、Espressif社のWiFi&Bluetooth付きマイコンESP32に、LCDやボタン3つ等をつけて大きさ約5cmの正方形に纏め、専用ライブラリーが使えるラクチンなマイコンです。数か月前に秋月電子で3500円で購入したM5 Stack Basicなのですが、今オンラインで覗いて見ると4500円ほどに値上がりしたようです。

さて、プログラムも他のマイコンよりは簡単です。Arduino-IDEで次のように作成しました。

/****************************************************
 * WiFi signal scanner for M5 Stack Basic           *
 *  Initial Version 00 5/4,2021 (c)Akira Tominaga   *
 *  Function:                                       *
 *    Scan 802.11 b/g/n WiFi networks and           *
 *    Show dBm,SSID, Encrypt.type, & channel number.*
 *  Input/Output:                                   *
 *    No external devices.                          *
 *  Revisions:                                      *
 *    (No new versions yet)                         *
 ****************************************************/
#include "M5Stack.h"
#include "WiFi.h"
uint8_t nNW;    		// number of networks
uint8_t nN;     		// NW sequence number

void setup() { // ***** Arduino setup() *****
  M5.begin();
  M5.Lcd.setTextSize(2);
  WiFi.mode(WIFI_STA);  	// station mode
  WiFi.disconnect();    	// clear connections
  delay(100);
}

void loop() { // ***** Arduino loop() *****
#define numL 13        		// # of lines per page
  M5.Lcd.fillScreen(BLACK);    	// clear screen
  M5.Lcd.setTextColor(GREEN);
  M5.Lcd.setCursor(50, 100);
  M5.Lcd.println("Scanning..");
  nNW = WiFi.scanNetworks();
  if (nNW == 0) { 		// if no network
    M5.Lcd.setCursor(50, 100);
    M5.Lcd.println("No WiFi found");
  } else {      		// if WiFi found
    uint8_t nPages = nNW / numL + 1; // # of pages
    uint8_t lastPlines = nNW % numL; // last page lines
    if (lastPlines == 0) {	// if 0 in last page
      nPages--;			// adjust nPages
    }
    for (uint8_t page = 0; page < nPages; page++) {
      // *** draw page header
      M5.Lcd.fillScreen(BLACK);
      M5.Lcd.setTextColor(GREEN);
      M5.Lcd.setCursor(0, 0);
      M5.Lcd.print("dBm  ");   // dB mW
      M5.Lcd.print(nNW);       // number of networks
      M5.Lcd.print(" SSIDs page");
      M5.Lcd.print(page + 1);  // page number
      M5.Lcd.print("/");
      M5.Lcd.print(nPages);    // total number of pages
      M5.Lcd.println(" e-c");  // encrypt.type & channel
      // *** draw page body
      if (page == nPages - 1) { // if last page
        for ( nN = page * numL; nN < nNW; nN++) {
          listNW();
        }
      } else {
        for ( nN = page * numL; nN < (page + 1)*numL; nN++) {
          listNW();
        }
      }
      delay(10);
      if (page < nPages - 1) {
        M5.Lcd.setTextColor(GREEN);
        M5.Lcd.println("Button A for next page");
      }
      if (page >= nPages - 1) break;
      while (M5.BtnA.read() == 0) {} // wait A button
    }
  }
  M5.Lcd.setTextColor(GREEN);
  M5.Lcd.setCursor(0, 224);
  M5.Lcd.println("     Button B to re-scan");
  while (M5.BtnB.read() == 0) {} // wait B button
}

/***********************************************
  User defined function(s)
***********************************************/
// *** listNW() *** list-up detected SSIDs ***
void listNW(void) {
  int8_t dBm = WiFi.RSSI(nN);
  M5.Lcd.setTextColor(WHITE);    // default White
  if (dBm > -50) {	   	 // if strong,
    M5.Lcd.setTextColor(ORANGE); // then Orange
  } else if (dBm > -85) {      // if medium,
    M5.Lcd.setTextColor(YELLOW); // then Yellow
  }
  M5.Lcd.print(dBm); 		// dB milliWatt
  M5.Lcd.print(" ");
  String ss = WiFi.SSID(nN);	// SSID name
  uint8_t sspadlen = 18 - ss.length();
  String space = "             ";
  ss.concat(space.substring(0, sspadlen));
  M5.Lcd.print(ss);		// show SSID
  M5.Lcd.print(WiFi.encryptionType(nN));
  M5.Lcd.print("-");
  M5.Lcd.println(WiFi.channel(nN));
}
// *** end of program

 

M5 Stackは二次電池も内蔵して小さく、ポケットに収まるので持ち歩き使用にも便利ですね。

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

なお、信号強度のdBm(デシベルミリワット)の説明は次の記事の中に書きましたので、もし必要な方はご参照ください。

かんたんにできるWiFi信号レベル表示器(テレワークに便利) - 勝手な電子工作・・

 

今回はごく簡単なご紹介でしたが、皆様のお役に立てば幸いです。

 

©2021 Akira Tominaga, All rights reserved.

24時間の動きを表示するといいかな・・温湿度気圧計

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

特別な工夫はないのですが(^^;、小さな画面にグラフ表示して過去1日の変化を示す気象計です。今14時ちょうどですが、昨日の今に比べてずいぶん気圧が下がったのに気づきます。これだと今晩雨が降るかもしれませんね。

昨日から2階の和室に1日置いたのですが、窓や入口の開閉に応じて温度や湿度が少し上下します。

こういう気象計が売られているのは見かけませんが、この表示方法がほかの工作にも応用できそうなので記事にするとします。前に書いたCO2濃度計(右のカテゴリーのCO2測定から参照してください)やガンマ線表示計(同じくガイガーカウンターから)などで、この表示ならかなり便利そう。それらは時間ができたらやってみるとして。

この表示を試したのは単純なきっかけからです。マイコンの容量と速度が増し、Teensy4.0などもArduino-IDEに対応して手軽に使える有難い時代になりました、これまでのESPもですが。Raspberry Pi Picoは少し試した限りではArduino-IDEとの相性が今一つですが。

ともあれ、そういうマイコンの表示はGraphicsディスプレイにしたいものです。そこで見易いOLEDを5種類ほど試してみましたが、簡単な用途向けではサイズがまだまだ小さくて拡大鏡が必要ですね・・^^; 下の写真はTeensy4.0にSSD1306OLEDをつないでみたもの。

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

表示装置付きのM5-stackを使うのも便利ですが、テストでなく組み込むには無理があるし。。。

もう少し大きい表示装置が何かないかなーと手持ちの部品を漁り、このTFTカラー液晶シールドに気づきました。mcufriendとシルク印刷された2.4インチの表示器で、かつてArduino-Unoで使っていました。なぜかUnoのピンをほぼ全部塞ぐだけでなくUNOの容量が次第に不足気味となりもはや用済み、部品箱で長らく眠っていたのでした^^

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

裏は次です。

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

480x320ピクセルのカラー2.4インチ。それならいいかも!・・・。

ただし、これはUNOに合わせたシールドなのでピンが扱いにくい。そこで思いついて、まずはArduino-Megaに挿してみました。内蔵のマイクロSDを使うには少し問題があり、該当ピンがUNOのSPI接続にとられるため、その4本のピンをカットしてMEGAのSPI各ピンに配線でつなぐのがよいかと思われます。とりあえずはこれを無視して放置し、表示だけをテストすることにしました。このシールドをMEGAに次のように取り付けます。

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

そしてセンサーとRTC(リアルタイムクロック)と配線した部分は次です。

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

 

このようにするとArduinoからの3.3V出力もふさがってしまうので、裏面から配線で取り出しています。 

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

そしてプログラムを作りますが、まずはArduinoの普通のTFT-LCDライブラリーではうんともすんとも^^; いったいどんな接続だったっけな(・・? 

この製品に書いてあるmcufriendをググってみるとMCUFRIEND_kbv libraryライブラリーにたどり着きます。Arduinoのモデルを選ばないオールマイティとか。それを使ったらなんと即、問題なく動きました。

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

1日放置したのが上の写真ですが、大きさも鮮明さも良い感じです。測ると電流を200mAほど消耗しているのにはびっくりですが。プログラムは色使いや位置等を少し調整して冒頭に掲げた画面を出しているのが次のスケッチです。作るのと改訂に半日以上使ってしまいましたが。

/****************************************************************
  Weather station with TFT-LCD 320x240 & BME280 -  V.02
    Initial version V.01 on Apr.18,2021   (c) Akira Tominaga
  Function:
    Display temperature-C, relative humidity-% & pressure-hPa.
    24-hour-plot wraps round every 24 hours.
  Pin connections:
    TFTLCD shield(MCUFRIEND_kbv) attached to Arduino-MEGA.
    Arduino-Mega 3.3V,G,SDA&SCL to sensor&RTC's VCC,G,SDA&SCL.
  Revisions:
    V01: Changed design of grid and position-marker.  4/19/2021
    V02: Minor changes to characters and some colors. 4/23/2021
  Remarks:
   Librarie's licensing rules & (c)s are attached in the bottom.
  **************************************************************/
#include "UTFTGLUE.h"     // MCUFRIEND_kbv library
#include "Wire.h"         // I2C for RTC-DS3231 and BME280
#include "Adafruit_Sensor.h"
#include "Adafruit_BME280.h"
// *** for LCD ***
UTFTGLUE lcd(0, A2, A1, A3, A4, A0);
#define wW 320            // TFT width 
#define hH 240            // TFT height
#define bX 43             // x @ begin-graph
#define eX wW-36          // x @ end-graph(284 in this case)
int lX = (eX - 1) - (bX + 1) + 1; // x-axis len (240 this case)
#define bY 18             // y @ begin graph
#define eY hH-15          // y @ end graph
// *** for colors ***
#define White (192,192,192)
#define Black (0,0,0)
#define Red (255,48,48)
#define Green (0,255,0)
#define Blue (0,100,255)
#define Yellow (255,255,0)
#define Purple (255,0,255)
#define rGray (127,127,127)
#define dGray (32,32,64)
// *** for application use ***
char Temp[] = "xx.xC";    // 5+1 bytes for temperature
char rH[] = "xx.x%rH";    // 7+1 bytes for relative humidity
char hPa[] = "xxxx.xhPa"; // 9+1 bytes for pressure
#define iniX bX+1         // graph initial x-position
#define lastX eX-1        // graph last x-position
uint16_t curX = iniX;     // set curX at initial position
uint16_t posY;            // position Y
// *** for measurement ***
Adafruit_BME280 bme;      // name as bme
// to keep measurement for 24hours in lX positions;
uint16_t intervalM = round(24 * 60.0 / lX); // interval minutes
uint8_t moV;              // current month
uint8_t daV;              // current day
uint8_t hrV;              // current hour
uint8_t mnV;              // current minute
uint8_t mnVsv = 99;       // former minute save area
char MDhm[] = "MM/DD hh:mm"; // editing area for clock

void setup() { // ***** Arduino setup() *****
  Serial.begin(9600);
  Wire.begin();
  lcd.InitLCD();
  lcd.clrScr();
  //////////////////////////////////////
  // *** Draw frame ***               //
  //////////////////////////////////////
  lcd.setColor Black;     // make a blackboard
  lcd.fillRect(0, 0, wW - 1, hH - 1);
  lcd.setColor Yellow;
  lcd.drawRect(0, 0, wW - 1, bY - 1); // top bar
  // *** fill texts in top bar ***
  lcd.setFont(SmallFont);
  lcd.setBackColor Black; // text's back-color
  dspVal();               // initial text in top bar
  //////////////////////////////////////
  //*** draw axes, grid and scales ***//
  //////////////////////////////////////
  // *** vertical lines and scales***
  lcd.setColor rGray;
  lcd.drawLine(eX, bY, eX, eY); // right edge (graph limit+1)
  for (uint8_t tU = 0; tU < 25; tU += 2) {
    uint16_t xL = bX + 1 + round(lX * (tU / 24.0));
    lcd.setColor rGray;
    lcd.drawLine(xL, bY, xL, eY);
    lcd.setColor White;    //
    char hhC[3];
    sprintf(hhC, "%2d", tU);
    lcd.print(hhC, xL - 8, hH - 12);
  }
  lcd.print("Hour", 8, hH - 12);
  // *** vertical scales and horizontal lines ***
#define tsMax 50          // temperature max scale
#define tsMin 0           // temperature minimum scale
#define tsStep (tsMax-tsMin)/10
#define hsMax 100         // rel. humidity max scale
#define hsMin 0           // relative hum. min scale
#define hsStep (hsMax-hsMin)/10
#define psMax 1040        // pressure max scale 1040
#define psMin 940         // pressure min scae 940
#define psStep (psMax-psMin)/10
  char tsC[4];            // chars for temperature
  char hsC[4];            // chars for rel.humidity
  char psC[5];            // chars for pressure
  int tS = tsMin;         // init. temperature scale
  int hS = hsMin;         // init. humidity scale
  int pS = psMin;         // init. pressure scale
  // draw horizontal lines and scales in a loop
  for (int g = 0; g < 10; g++) {
    int iy = round(eY - (eY - bY) * g / 10.0);
    lcd.setColor rGray;
    lcd.drawLine(bX + 1, iy, eX, iy); // horizontal line
    sprintf(tsC, "%2d", tS); // temp val. text
    sprintf(hsC, "%2d", hS); // rHum val. text
    sprintf(psC, "%4d", pS); // pres val. text
    lcd.setColor Green;   // for temperature
    lcd.print(tsC, 5, iy - 7);
    lcd.setColor Blue;    //  for humidity
    lcd.print(hsC, 26, iy - 7);
    lcd.setColor Red;     // for pressure
    lcd.print(psC, eX + 5, iy - 7);
    tS = tS + tsStep;     // increase pos. for temp.
    hS = hS + hsStep;     // increase pos. for humi.
    pS = pS + psStep;     // increase pos. for pres.
  }
  //////////////////////////////////////
  //*** prepare BME280 sensor      ***//
  //////////////////////////////////////  
  if (!bme.begin()) {     // check validity of BME280
    Serial.println(F("Invalid BME280."));
    Serial.print("SensorID=0x");
    Serial.println(bme.sensorID(), HEX);
    Serial.println(F(" ID 0x56-0x58 for BMP280."));
    Serial.println(F(" ID 0x61 for BME680."));
    while (true) {}       // if error, stop here
  }
  delay(1000);            // wait sensor stabilized
}
void loop() { // ***** Arduino loop() *****
  // *** display clock and check timing to measure   
#define aMomnt 1000       // mS to wait for a moment
waitTiming:
  geTime();               // get time and calendar
  if (mnV == mnVsv) {     // if no change in minute
    delay(aMomnt);        // then wait for a moment
    goto  waitTiming;     // repeat checking
  }
  // *** display clock in top bar each second
  sprintf(MDhm, "%02d/%02d %02d:%02d", moV, daV, hrV, mnV);
  lcd.setColor White;    // for Clock
  lcd.print(MDhm, 15, 4); // display date and clock
  // *** check graph-drawing timing or not
  if ((mnV % 6) > 0) {    // if not multiple of 6
    delay(aMomnt);        // then wait for a momentr
    goto  waitTiming;     // repeat checking
  }
  // *** now measurement start
  mnVsv = mnV;            //save time (min) processed this time
  uint16_t vMin = hrV * 60 + mnV; // cur. time in minute
  uint16_t xPos = round(lX * (float)(vMin / (60.0 * 24))); 
  curX = bX + 1 + xPos;   // get current hilizontal position
  // *** get measured data and edit them into top bar
  uint16_t Tempx10 = round(10.0 * bme.readTemperature());
  uint8_t temp = Tempx10 / 10;  // get integer part
  uint8_t tempD = Tempx10 % 10; // remainder after dot
  sprintf(Temp, "%2d", temp);   // edit integer 2chars
  Temp[2] = '.';
  sprintf(Temp + 3, "%01d", tempD); // edit after dot
  uint16_t Humix10 = round(10.0 * bme.readHumidity());
  uint8_t humi = Humix10 / 10;  // get integer
  uint8_t humiD = Humix10 % 10; // remainder after dot
  sprintf(rH, "%2d", humi);     // edit integer 2chars
  rH[2] = '.';
  sprintf(rH + 3, "%01d", humiD); // edit after dot
  uint16_t Presx10 = round(bme.readPressure() / 10.0);
  uint16_t pres = Presx10 / 10; // get integer part
  uint16_t presD = Presx10 % 10; // remainder after dot
  sprintf(hPa, "%4d", pres);    // edit integer 4chars
  hPa[4] = '.';
  sprintf(hPa + 5, "%01d", presD); // edit after dot
  dspVal();               // show the values in top bar
  // *** draw position marker line ***
  // erase the past marked line
  lcd.setColor Black;     // with blackboard color
  lcd.drawLine(curX, bY, curX, eY - 1); // erase
  // re-mark grid positions
  lcd.setColor rGray;     // with grid color
  for (int g = 0; g < 10; g++) {
    int iy = round(eY - (eY - bY) * g / 10.0);
    lcd.drawPixel(curX, iy); // re-draw grids
  }
  // ** if on the hour line, then recover it
  if ((curX - (bX + 1)) % (round(lX / 12.0)) == 0) { // if 2hours x N
    lcd.drawLine(curX, bY, curX, eY); // recover the hour line
  }
  // *** draw measurement graphs ***
  lcd.setColor Green;     // temperature
  posY = (eY - 1) - round((Tempx10 / 10.0) * (eY - bY) / 50.0);
  lcd.drawPixel(curX, posY);
  lcd.setColor Blue;      // relative humidity
  posY = (eY - 1) - round((Humix10 / 10.0) * (eY - bY) / 100.0);
  lcd.drawPixel(curX, posY);
  lcd.setColor Red;       // pressure
  posY = (eY - 1) - round(((Presx10 / 10.0) - 940) * (eY - bY) / 100.0);
  lcd.drawPixel(curX, posY);
  // increase x-position for next measurement
  curX++;                 // get next position
  if (curX > lastX) {
    curX = iniX;
  }
  /// *** draw vertical marker line ***
  lcd.setColor Yellow;    // with Yellow color
  lcd.drawLine(curX, bY, curX, eY - 1);
} // *** end of Arduino loop() ***
/**************************************************
    User defined functions
 **************************************************/
// ***** dspVal() *** display values measured *****
void dspVal(void) {
  lcd.setColor Green;     // for temperature C
  lcd.print(Temp, 120, 4);
  lcd.setColor Blue;      // for relative humidity %
  lcd.print(rH, 171, 4);
  lcd.setColor Red;       // for pressure hPa
  lcd.print(hPa, 238, 4);
}
// *** geTime() *** get month,day,hr & min from DS3231
void geTime(void) {
#define aRTC 0x68       // RTC I2C addr
#define mdm 2           // minute position in RTC data
#define mdh 3           // hour position in RTC data
#define mdD 5           // day position in RTC data
#define mdM 6           // month position in RTC data
  byte  vR[8];          // values in RTC format
  Wire.beginTransmission(aRTC);
  Wire.write(0x00);
  Wire.endTransmission();
  Wire.requestFrom(aRTC, 7);
  while (Wire.available() < 7) {}  // wait for data
  for (int i = 1; i < 8; i++) {
    vR[i] = Wire.read();
  }
  Wire.endTransmission();
  moV = ((vR[mdM] & B00010000) / 16) * 10 + (vR[mdM] & B00001111);
  daV = ((vR[mdD] & B01110000) / 16) * 10 + (vR[mdD] & B00001111);
  hrV = ((vR[mdh] & B00100000) / 32) * 20 + ((vR[mdh] & B00010000) / 16) * 10 + (vR[mdh] & B00001111);
  mnV = ((vR[mdm] & B01110000) / 16) * 10 + (vR[mdm] & B00001111);
}
// Library providers' copyrights and lisences are as follows:
/************* UTFTGLUE library *********************************
   TFT-LCD 320x240 pixels.
    MCUFRIEND_kbv (C)2015 Rinky-Dink Electronics, All rights
    reserved.   web: http://www.RinkyDinkElectronics.com/
****************************************************************/
/**** for Adafruit-GFX-Library used by UTFTGLUE *****************
  Software License Agreement (BSD License)  (c) 2012 Adafruit
  Industries, All rights reserved. Redistributions in binary form
  must reproduce copyright notice. See BSD lisence conditions.
***************************************************************/
/***********ふFor Adafruit_BME280 ******************************
  Designed specifically to work with the Adafruit BME280
   http://www.adafruit.com/products/2650
  Use I2C or SPI interface. I2C adrs is 0x76 or 0x77. Adafruit
  invests time and resources providing this open source code,
  please support Adafruit and open hardware by purchasing
  products from Adafruit!
  By Limor Fried & Kevin Townsend for Adafruit Industries.
  BSD license: all above must be included in any redistribution.
  See LICENSE file for details in the library.
*****************************************************************/
// end of program

ピンをよく確認して、これを他のマイコンで動かすのにチャレンジしてみたいと思います。電流が多いので電源の与え方に注意しないといけないかも。。。

と考えたのですが、現在は似た製品が海外ネットで色々出回っていますね。タッチパネルでなければ価格も1000円未満で手ごろそうです。省エネなども少しは改善されたことでしょう。いっそ、そちらを入手して試してみる方がいいかな・・早速今注文してしまいました^^

 

2021.4.26追記 ================================

今回使用したTFT-LCDのピンの記述を調べた結果、次のものと完全に同じです。

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

同型の一般的な製品ではMCUにILI9341が使われているようですが、MCUFRIENDのこのTFT-LCDシールドではILI9320が使われています。それがMCUFRIENDのライブラリーを必要としている理由かなと推察。

ついでに、動作中の接続状態を調べたらSD用の4本(上の表で下4つ)以外はすべて使用しており、Arduinoとのデータのやりとりは8ビットパラレルです。表示速度は速いですが、マイコンの限られたピンを15本消耗するのは厳しい^^;。節約してもせいぜい-4本(電源を外部供給、RSTをHIGHにしCSをLOWにして固定)、それでも11本は表示用にとられます。そのうちに、目的であるTeensyで試そうと思ってはいます。。。

そういうわけで、今回注文したものはSPI接続だけででき、画面サイズがもう少し大きな3.5インチにしました。例によって到着までに数週はかかるでしょうが、ボチボチやるのでちょうどいいかもしれません。

================================== 追記end

 

以上、簡単に書かせていただきましたが、何らかのお役に立てば幸いです。

 

©2021 Akira Tominaga, All rights reserved.

楽々フーリエ変換!(快速マイコンをArduino-IDEで使う)

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

トライアングルの音色は澄んでいるようで意外に複雑です。今回はマイコンでできるフーリエ変換をご紹介したいと思います。

上のグラフはトライアングルの音をマイクで採りつつフーリエ変換した結果で、周波数の成分を表しています。なんどやっても同じグラフになります。たぶんトライアングルには振動の腹が主に3つあるからだと思われます。とはいえ百均で入手したおもちゃなので、プロが使う本物とはそれなりに違うかもしれませんが^^;

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

このトライアングルの音の波形は次です。

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

よい音が長く続きますが、意外に複雑な波形です。

これをマイコンでどうやってフーリエ解析して、周波数成分の分布を知るかを説明します。また、これでいかに正確な分析ができるかについても、ついでにテストしてみたのでご紹介します。

今回の回路は次です。

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



快速マイコンTeensy4.0をArduinoIDEでプログラミングします。ここでの勝手なスケッチは次です。

/*****************************************************************
 *  Fast Foourier Transform & Data Recording, using Teensy 4.0
 *    Initial version V00:  April 3, 2021 (c) Akira Tominaga
 *    Function:
 *      1) Quick sampling and FFT, applying Hamming-window
 *      2) Show FFT graph, with Serial Plotter
 *      3) Record measured data to micro SD with name "FFTnnn.txt"
 *      4)Perform next measurement if tact-switch pushed
 *    Input and output: 
 *      1) Pin A2(16) for signal input (keep within 0 to 3.3V)
 *      2) Pin 10,11,12 and 13 for SPI interface to micro SD
 *      3) Pin 2 for tact-switch to continue to next FFT
 *    Remarks: Arduino-FFT library is used under GNU-GPLicense.
 *    Revisions: (not yet)
 *****************************************************************/
#include "SD.h"       // use SPI micro-SD-card-reader/writer 
#include "SPI.h"      // SPI MOSI=11, MISO=12,CLK=13
const int cS = 10;    // SPI Chip-Select = 10
File sdF;
#include "arduinoFFT.h" // use Fast Fourier Transform
// for 0~20kHz FFT, 40kHz sampling & 1024 samples=512 bins
#define sFreq 40000   // sampling Hz <55000 (ADC 17.5 micro sec)
#define nSamp 1024    // number of samplings 
#define serB 115200   // serial Baud 115.2,57.6,38.4,19.2,14.4,9.6 k
#define aPin  A2      // signal input pin
#define sW 2          // tact switch at D2
arduinoFFT FFT = arduinoFFT();
unsigned int sDur;    // duration micro sec. between samplings
unsigned long micS;   // elapsed time in microsec (max about 70min)
double vR[nSamp];     // Real part of Vector
double vI[nSamp];     // Imaginal part of Vector

void setup() { // ***** Teensy setup() *****
  Serial.begin(serB);
  while (!Serial) {}  // wait for Serial-ready
  pinMode(sW, INPUT_PULLUP); // pull-up tact switch
  sDur = round(1000000 * (1.0 / sFreq)); // calc. sampling dur.μS
  if (!SD.begin(cS)) { // start SD
    Serial.println(F("Init-SD err."));  return;
  }
}

void loop() { // ***** Teensy loop() *****
  // *** perform Fast Fourier Transform
  for (int i = 0; i < nSamp; i++) { // do a cycle of samplings
    micS = micros();  // save sampling-start-time
    vR[i] = analogRead(aPin);
    vI[i] = 0;        // set zero to imaginal part
    while (micros() < (micS + sDur)) {} // wait until cycle-end
  }
  // apply Hamming windowing
  FFT.Windowing(vR, nSamp, FFT_WIN_TYP_HAMMING, FFT_FORWARD);
  FFT.Compute(vR, vI, nSamp, FFT_FORWARD); // calculate FT
  FFT.ComplexToMagnitude(vR, vI, nSamp); // mag. to vR 1st half
  double peak = FFT.MajorPeak(vR, nSamp, sFreq); // get peak freq
  // get peak element number
  int ip = round((nSamp / 2) * peak * 1.0 / (sFreq / 2));
  
  // *** prepare SD to create file=FFTnnn.txt
  char fN[11] =  "FFTnnn.txt"; // file name
  uint16_t fnum = 0;  // file number for character nnn
  char fnumc[4];      // file number in character
  while (true) { // if the name existsts, then set nnn+1
    sprintf(fnumc, "%03d", fnum);
    for (int i = 0; i < 3; i++) {
      fN[i + 3] = fnumc[i];
    }
    if (!SD.exists(fN)) break; // if not exists, go out
    fnum ++; // if it exists, increase nnn
  }
  sdF = SD.open(fN, FILE_WRITE); // create it as output file
  if (!sdF) {		// if error, then
    Serial.println(F("Open-SD err."));
    while (true) {} // if open-err, stop here
  }
  // convert results in vR[] into simple integer hh to record to SD
  for (int i = 0; i < (nSamp / 2); i++) {
    uint16_t ff = round(i * 20000.0 / (nSamp / 2.0)); // get Hz value
    uint16_t hh = round(10000.0 * (vR[i] * 1.0 / vR[ip])); // relative Mag.
    Serial.println(hh); // plot to Serial-plotter
    // *** record( Hz, Mag.) to SD in CSV format
    sdF.print(ff);
    sdF.print(",");
    sdF.println(hh);
  }
  sdF.close();		// close the file when all data recorded
  while (digitalRead(sW) == HIGH) {} // wait for sW pushed
  // *** Peform next measurement and recording when sW pushed
}
// end of program

 

Arduino-FFT高速フーリエ変換)ライブラリーは、もともとGoogle社が開発し、今はArduinoIDEのほかにGCCなどでも使える素晴らしいライブラリーです。ArduinoIDEにこれを追加する方法は普通の入れ方と同じで、「ツール」→「ライブラリーを管理」→「ライブラリーマネジャー画面」で検索バーを使ってFFTを検索し「ArduinoFFT」を選び→該当の「インストール」をクリックします。

今回使っているマイコンTeensy4.xは、フローティング処理も含めて非常に速いので、こういう用途にはぴったりです。もちろん、このFFTはUNOなどでも使えるには使えるわけですが、非常に低い周波数領域しかカバーできません。

サンプリング周波数を40kHzにしている(44kHzもよいかな)理由は、可聴音域の20kHzまでを分析したいからです(FFTではサンプリング周波数の半分までしか分析できません)。

また、Arduino-IDEのシリアルプロッターを使う場合は、横軸を512点しか表示しませんので横軸をそれに合わせるために、サンプリング個数を1024にしています。FFT関数は周波数分解能であるその半分の512個(単位はビン)にわけて答を返してきます。上のプログラムでおわかりかもしれませんが、関数に引き渡すパラメータベクトルのリアル部分用であるvR[ ]の前半分に収めて返してくれます。

 信号測定にはADCを使います。Teensyの迅速AD変換方法はあるようですが、普通のAD変換で実測した結果17.5μS以下でできるので、この用途(40kHzでのサンプリングと処理)用には十分に速いと分かります。

最初に示したトライアングルの測定等では、コンデンサーマイクを用いています。ここでは次のものを使っています。

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

これを信号入力部分につぎのように接続します。

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

マイクは適当な保持具にもたせます。

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

そして動かしシリアルプロッターに表示すると、トライアングルの音の周波数は次のように表示されます。

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

 X軸は20kHzまでの周波数として、等間隔で512点をプロットしますが、横軸の目盛り表示はおしきせで思うに任せません。

また、左端の12点(つまり0~469Hz)は縦軸がかぶさり表示されません(ただし、左端には商用電源のノイズなどが載りやすいため、表示が要らないといえば要らないわけなのですが^^;)余談ですが、上のプログラムでは、peak = FFT.MajorPeak(vR, nSamp, sFreq);により支配的な周波数を取り出しています。試しに50Hzをまぜてみた限り、この(左端に近い)あたりのBinの値がたとえ一番大きくなっても、FFT.MajorPeak関数ではそこはピークとみなされない仕様になっている感じです。

じっくり分析するには、シリアルプロッターではなくシリアルから信号を表示するプログラムを作るか、測定結果をSDにでも記録してExcelで分析するのがよいかということで、とりあえずは各回の測定記録をマイクロSDに書くようにしたわけです。

さて、このフーリエ変換の精度はどうかを調べたくなりますね。

そこで、ファンクションジェネレータの正確な波形を信号として入れてみることにします。まずは2.000kHzの正確な正弦波(sin波)です。

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

オシロで表示すると次。

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

上の写真の右下をごらんになると、正確に2.0000kHzであるのがわかりますね。最後の0.02Hzの半端はおそらくこのオシロ側での有効数字6桁末尾の許容誤差だと思います。

これを測定した結果は次です。

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

高調波の全くない、実に正確なサイン波であることが納得できますし、逆に今回作ってみたFFT装置の精度も高いものであることが理解できます。 

では、同じ周波数のブロック波(square波)をいれてみます。

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

オシロの波形は次です。

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

これを測定すると次の結果になりました。

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

理屈通りの3倍、5倍、7倍、9倍の周波数成分が正確に出ていることがわかります!

(2021年4月8日追記)上のグラフの横軸の分解能はサンプリング数÷2の512ビンで、この場合はビン幅約39Hzへの確率分布で、いわば少しならされた値になります。論理的には幅のない成分の高さは、そのため若干下に表示されるわけです。

例えば、3倍の6kHzなら論理的には3.33、5倍の10kHzなら2.00ですが、そういうわけで表示は少しだけ低くなります。ビン幅を狭めるために、ためしにサンプリング数を16倍の16384回にしてみます。上のプログラムなら#define中の1行を、#define nSamp 16384 に変えるだけのことですのでやってみましたら次のグラフになりました。

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

横軸の分解能は2.44Hzになり波形は前より尖り、高さも理論値に少し近づくのがわかりますね。(以上2021.4.8追記)

 どっちにしてもファンクションジェネレータが正確な事と、今回のFFTの精度が高い点の両方がよくわかりました^^ 

それでは・・・、ということで気をよくして、今度はトイピアノを持ち出してきて測ってみます。

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

一番上の「ド」を鳴らします。

オシロでみると次です。

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

なんだか頼りない音ですね。でも測定結果は次です。

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

おお、あるべき基本周波数1046.5Hzを実に正確に出しています!驚き。高調波も1オクターブ上の2093Hz、そして2オクターブ上の4186Hzに!おもちゃピアノといっても馬鹿にできませんね。

今や玩具でもクリスタル付きマイコンで電子音を出してるわけですから、むしろあたりまえですかね^^

 

これに気をよくして、次はファンクションジェネレータからこれぞホワイトノイズのAWGNを出してこれにいれてみます。

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

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

そして測ってみると;

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

ありゃ、平たんに近いのではなくおおいに癖があるのですね。まあ、人間が数式でこしらえている波である限りは、一部を取り出せばこうなるのがむしろ当たり前かもしれませんね。しかしこれはちょっと拍子抜け・・^^;

 

では今回の記事はこのへんにしたいと思います。皆様のお役に少しでも立てば幸いです。

 

©2021 Akira Tominaga, All rights reserved.