【HK829とマイコンを使用】にぎったら「にぎにぎ」「コメコメ」としゃべるハンドグリップを作ってみた【回路設計・組み立て編】

目次

設計した回路を下図に示す。

全体回路図
図1: 全体回路図。画像クリックで拡大

HK829とNJM386の周辺は殆どデータシートの回路例の通りなので、何も難しい事は無い。HK829のメッセージ再生用スイッチの代わりにマイコンを接続した部分は、今回アレンジした点。HK829のスピーカー出力の部分はデータシートに386では無い別のアンプICに繋ぐ例が載っていたので、それを参照した。

導入編でも述べたが、HK829のM0~M3のピンはIC内部でプルアップされており、開放電圧を測ったらほぼ電源電圧だった。マイコンのGPIOも待機状態でHigh(=電源電圧が出力されている)であり、HK829のデータシートを読んだ限りではM0~M3にHighが入力される事は想定されていないようなのでそこが少し心配だが、HK829のHighもマイコンのHighもほぼ同電位なら殆ど電流は流れないから大丈夫だろうと勝手に納得することにした。間にトランジスタかMOSFETを挟んで開放状態を作り出す事も可能だが、面倒臭かったので。もしICが突然壊れたらこれが原因という事で対策の候補の一つになるだろう。

まずはブレッドボードで組んで実験した。ICに録音する際は、TVの音声を一旦PCに録音し編集してWalkmanに移し、Walkmanの出力をブレッドボードに接続した。PCから直接接続して録音してもノイズだらけで使い物にならなかったので、面倒だがこの手順を踏んだ。そしてテスト再生は見事に成功した。

こうして設計した回路をユニバーサル基板に組んだ。

この際、回路図上でHK829のMIC+、MIC-、MICG、/RECに接続されている部品はメッセージを再生する上では必要無いので省略した。

基板表
写真1: 基板表
基板裏
写真2: 基板裏

ケースはタカチのSW-85S。よくある72×47mmの基板の回路を収納するのにちょうどいい。

当初はモバイルバッテリを電源にしようと思っていたが、ケースに入りそうなモバイルバッテリを見つけられなかったので単4電池3本にすることにした。始めは電源電圧は5Vの想定で回路を設計していたが、データシートを見る限り4.5Vでも全てのICの定格電圧範囲に入っていたのでよしとする事にした。基板との接続はXHコネクタにして、道具を使わなくても着脱できるようにした。

電池ボックスと電源スイッチ
写真3: 電池ボックスと電源スイッチ
電源スイッチ拡大
写真4: 電源スイッチ拡大
XHコネクタ拡大
写真5: XHコネクタ拡大

ハンドグリップに付けるスイッチは秋月電子通商で売っていたSS-10GL13というレバー付きのマイクロスイッチにした。接点の定格はAC250V10.1Aで、機械等に使う工業用と思われる。ロジックのHigh、Lowを切り替えるという数mAオーダーの電流しか流れない用途には明らかにオーバースペックだが…。大きさやレバーがあることなどが今回の用途にちょうど良かったので選定した。

ハンドグリップはダイソーで25kgのものにした。ハンドグリップにスイッチを固定するのは、ホームセンターで配線を束ねるための金具を購入してスイッチのねじ穴に合うように穴を開けて加工した。

ケースにホームセンターで売っていた4mmの雄ねじが切ってあるヒートンを取り付け、ハンドグリップの輪の部分とカラビナで繋げた。

スピーカーは秋月電子通商で売っているDXYD50N-22Z-8A-F。5cm程度でフレームにねじ穴がついていてケースに取り付けるのが楽なのが選定理由。

ケース加工の寸法を下図に示す。

ケース加工図(スピーカー取り付け穴)
図2: ケース加工 蓋にスピーカー取り付け穴を開ける
ケース加工図(基板取り付け穴)
図3: ケース加工 底面に基板取り付け穴を開ける
ケース加工図(スピーカー取り付け穴)
図4: ケース加工 上面に電源スイッチとヒートン取り付け穴を開ける

図は省略したが、この他に電源スイッチとヒートン取り付け穴を開けた反対の面の中心辺りにハンドグリップに取り付けるスイッチの配線用の穴を開ける必要がある。

そして、基板に組んだ後で改めてブレッドボードでHK829と最低限の部品で回路を組み、試しにスピーカー端子に直接スピーカーを繋いでみたら、なんと結構な音量で音が鳴ってしまった。386のアンプ回路は要らなかったことになる。しかし今更基板を組み直すのも面倒なので、そのままにした。

完成写真を以下に示す。

完成写真
写真6: 完成写真
完成写真(蓋を開けた様子)
写真7: 完成写真(蓋を開けた様子)
完成写真(蓋を開けて上から見た様子)
写真8: 完成写真(蓋を開けて上から見た様子)
カテゴリー: 未分類 | 1件のコメント

【HK829とマイコンを使用】にぎったら「にぎにぎ」「コメコメ」としゃべるハンドグリップを作ってみた【マイコンプログラム編】

目次

必要な部品が揃った所でこれから開発していく(正確には部品の選定も開発の一部だが)わけだが、自分のようなマイコン初心者の場合、多分一番大変なのはマイコンプログラミングなので、まずはそこから取り掛かる。

ATtiny13Aのピン配列を下図に示す。

ATtiny13Aのピン配列
図1: ATtiny13Aのピン配列 (データシートより引用)

ATTiny13Aの場合、GPIOのPB1が外部割り込み0のINT0を兼ねるので、PB0、PB2、PB3、PB4をメッセージ再生用のピンとして使用する。PB5はRESETを兼ねていて通常はRESETであり、PB5として使うには多分ヒューズビットの書き換えが必要で面倒臭いのでそのままにしておく。

メッセージは、以下のように割り当てた。

  • PB0「にぎにぎ」
  • PB2「コメコメ」
  • PB3「ハートを」
  • PB4「シェアリンエナジー!」「コメー!」

マイコンの大まかな動作の流れはこうだ。

  1. 始めに、スイッチを押された回数を数えるカウンタは0
  1. スイッチを押す→INT0の値が変化し、外部割り込み0が発生する→カウンタ判定
  2. カウント=0: PB0が一瞬(16ms以上、以下略)Lowになる「にぎにぎ」
  3. カウントアップしカウンタが1になる
  1. スイッチを押す→INT0の値が変化し、外部割り込み0が発生する→カウンタ判定
  2. カウント=1: PB2が一瞬Lowになる「コメコメ」
  3. カウントアップしカウンタが2になる
  1. スイッチを押す→INT0の値が変化し、外部割り込み0が発生する→カウンタ判定
  2. カウント=2: PB3が一瞬Lowになる「ハートを」
  3. カウントアップしカウンタが3になる
  1. スイッチを押す→INT0の値が変化し、外部割り込み0が発生する→カウンタ判定
  2. カウント=3: PB2が一瞬Lowになる「コメコメ」
  3. カウントアップしカウンタが4になる
  1. スイッチを押す→INT0の値が変化し、外部割り込み0が発生する→カウンタ判定
  2. カウント=4: PB4が一瞬Lowになる「シェアリンエナジー!」「コメー!」
  3. カウンタを0に戻す
  1. 以下繰り返す

上記では細かい設定やタイマの動き等は省略しているので、それも含めたフローチャートを以下に示す。

マイコンプログラムのフローチャート1
図2: マイコンプログラムのフローチャート1
マイコンプログラムのフローチャート2
図3: マイコンプログラムのフローチャート2

16ms以上Lowという部分の時間をタイマ0で作っている。今回はプリスケーラが1024で、255カウントしたらオーバーフロータイマ0の割り込みが発生するようにした。クロック周波数はAVRのデフォルトの1MHzで動作し、1024クロック毎にカウンタが1上がり255でオーバーフローして割り込みが発生する。ということは、(1024×255)/(1×10^6)≒0.26秒の時間を作り出す。

マイコンで電子的に0.26秒間、HK829に接続したスイッチを押したようにするというわけだ。

以上を踏まえて、プログラムしたのが以下のソースコードだ。

;-----------------------------------------------------------------------------------
;スイッチを押す度にHK829のM0, M1, M2, M3を順番に再生するプログラム (ATtiny13Aを使用)
;-----------------------------------------------------------------------------------

.include "tn13adef.inc"

;汎用レジスタ

.def R_TEMP1	=	R16	;汎用的な変数
.def R_TEMP2	=	R17	;汎用的な変数
.def COUNTER	=	R18	;メッセージのカウンタ
.def STACK	=	R19	;ステータス・レジスタ退避用
.def U_FLAG	=	R20	;ユーザーフラグ

;ユーザーフラグのビット
.EQU B_IOVF0	=	0	;タイマ0オーバーフロー
.EQU B_INT0	=	1	;外部割り込み0


.CSEG			;コードセグメント
	RJMP MAIN	;リセット

.ORG	0x0001		;外部割り込み0
	RJMP EXT_INT0

.ORG	0x0003		;タイマ0オーバーフロー
	RJMP IOVF0


;-----------------------------
;タイマ0オーバーフロー時の処理
;-----------------------------
IOVF0:
	;ステータス・レジスタの内容を退避
	IN STACK,	SREG

	;タイマ0カウンタ停止
	LDI R_TEMP1,	0x00
	OUT TCCR0B,	R_TEMP1	

	;タイマ0オーバーフローユーザーフラグをセット
	SBR U_FLAG,	(1<<B_IOVF0)

	;ステータス・レジスタの内容を復帰
	OUT SREG,	STACK

	;割り込み前の処理に戻る
	RETI

;----------------------------------------------------------------
;スイッチを押してから0.26秒後にLow出力していたピンをHighに戻す処理
;----------------------------------------------------------------
IOVF01:
	;タイマ0オーバーフローのユーザーフラグをクリア
	CBR U_FLAG,	(1<<B_IOVF0)

	;PB0, PB2~PB4を全てHighにする
	LDI R_TEMP1,	0B00011111
	OUT PORTB,	R_TEMP1		

	;外部割り込み0許可
	IN R_TEMP1,	GIMSK
	SBR R_TEMP1,	(1<<INT0)
	OUT GIMSK,	R_TEMP1

	;待機ルーチンにジャンプ
	RJMP MAIN01

;---------------
;タイマ0起動処理
;---------------
TMR0:
	;タイマ0カウント値セット
	LDI R_TEMP1,	0x00
	OUT TCNT0,	R_TEMP1

	;タイマ0カウント開始
	LDI R_TEMP1,	0x05	;プリスケーラ1024
	OUT TCCR0B,	R_TEMP1

	;待機ルーチンにジャンプ
	RJMP MAIN01

;-----------------
;外部割り込み0処理
;-----------------
EXT_INT0:
	;ステータス・レジスタの内容を退避
	IN STACK,	SREG

	;外部割り込み0禁止
	IN R_TEMP1,	GIMSK
	CBR R_TEMP1,	(1<<INT0)
	OUT GIMSK,	R_TEMP1		

	;外部割り込み0ユーザーフラグをセット
	SBR U_FLAG,	(1<<B_INT0)

	;ステータス・レジスタの内容を復帰
	OUT SREG,	STACK

	;割り込み前の処理に戻る
	RETI

;-------------------------------------------------------
;カウンタの数によって指定したピンをLow出力する処理を分岐
;-------------------------------------------------------
EXT_INT01:
	;外部割り込み0のユーザーフラグをクリア
	CBR	U_FLAG,	(1<<B_INT0)

	CPI COUNTER,	0x00	;カウンタが0の場合
	BREQ MASSAGE0		;MASSAGE0に分岐「にぎにぎ」

	CPI COUNTER,	0x01	;カウンタが1の場合
	BREQ MASSAGE1		;MASSAGE1に分岐「コメコメ」

	CPI COUNTER,	0x02	;カウンタが2の場合
	BREQ MASSAGE2		;MASSAGE2に分岐「ハートを」

	CPI COUNTER,	0x03	;カウンタが3の場合
	BREQ MASSAGE1		;MASSAGE1に分岐「コメコメ」

	RJMP MASSAGE3		;カウンタが4の場合、MASSAGE3に分岐「シェアリンエナジー!」「コメー!」

;-----------------------------------
;カウンタが0の場合の処理「にぎにぎ」
;-----------------------------------
MASSAGE0:
	;PB0をLowにする
	LDI R_TEMP1,	0B00011110
	OUT PORTB,	R_TEMP1

	;カウンタを1増やす
	INC COUNTER

	;タイマ0起動処理にジャンプ
	RJMP TMR0

;-----------------------------------
;カウンタが1の場合の処理「コメコメ」
;-----------------------------------
MASSAGE1:
	;PB2をLowにする
	LDI R_TEMP1,	0B00011011
	OUT PORTB,	R_TEMP1

	;カウンタを1増やす
	INC COUNTER

	;タイマ0起動処理にジャンプ
	RJMP TMR0

;-----------------------------------
;カウンタが2の場合の処理「ハートを」
;-----------------------------------
MASSAGE2:
	;PB3をLowにする
	LDI R_TEMP1,	0B00010111
	OUT PORTB,	R_TEMP1

	;カウンタを1増やす
	INC COUNTER

	;タイマ0起動処理にジャンプ
	RJMP TMR0

;-----------------------------------------------------------
;カウンタが4の場合の処理「シェアリンエナジー!」「コメー!」
;-----------------------------------------------------------
MASSAGE3:
	;PB4をLowにする
	LDI R_TEMP1,	0B00001111
	OUT PORTB,	R_TEMP1

	;カウンタを0にする
	LDI COUNTER,	0x00

	;タイマ0起動処理にジャンプ
	RJMP TMR0

;---------------
;メインルーチン
;---------------
MAIN:
	CLI		;全割り込み禁止

	;PORT設定

	;メッセージ出力はPB0, PB2, PB3, PB4を使用。初期値は全て出力でHighにする
	;PB1はINT0として使用するので、初期値は入力でHighにする
	;PB5は使用しないので初期値は出力でLowにする
	;ビット6, ビット7は予約済み(未使用ビット)なので0にする
	LDI R_TEMP1,	0B00111101
	LDI R_TEMP2,	0B00011111
	OUT DDRB,	R_TEMP1
	OUT PORTB,	R_TEMP2

	;外部割り込み0関連レジスタ設定
	IN R_TEMP1,	MCUCR		;MCU制御レジスタ読み込み
	SBR R_TEMP1,	(1<<ISC01)	;ISC01ビットをセット (INT0がHigh→Lowで外部割り込み0が発生)
	OUT MCUCR,	R_TEMP1		;セットしたビットを制御レジスタに書き込み
	IN R_TEMP1,	GIMSK
	SBR R_TEMP1,	(1<<INT0)
	OUT GIMSK,	R_TEMP1		;外部割り込み0許可

	;タイマ0割り込み許可
	IN R_TEMP1,	TIMSK0		;タイマ/カウンタ0割り込みマスク・レジスタ読み込み
	SBR R_TEMP1,	(1<<TOIE0)	;TOIE0ビットをセット (タイマ0割り込み許可)
	OUT TIMSK0,	R_TEMP1		;セットしたビットをマスク・レジスタに書き込み

	;カウンタに0をセット
	LDI	COUNTER,	0x00

	;ユーザーフラグに0をセット
	LDI U_FLAG,	0x00

	SEI		;全割り込み許可

;--------------------------------
;外部割り込み0待機及びタイマ0待機
;--------------------------------
MAIN01:
	;外部割り込み0の監視
	SBRC U_FLAG,	B_INT0
	RJMP EXT_INT01

	;タイマ0オーバーフロー割り込みの監視
	SBRC U_FLAG,	B_IOVF0
	RJMP IOVF01

	RJMP MAIN01
マイコンの動作試験回路
マイコンの動作試験回路。PB0, PB2~PB4にはLEDを接続し、出力がLowになったらLEDが光るようになっている。

今回も自分は初心者なのでいろいろ躓いた。始めは全く動かなくて、原因を調べた。

そこで判明したのは、ATtiny13Aは機能を絞っているからか、マイコンの機能を設定するI/Oレジスタはアドレス0x0020~0x005Fの範囲にしかない。汎用レジスタとこれらのレジスタ間のデータのやり取りは全てIN、OUT命令で出来るということだが、この事を見過ごしてATmega328Pの時の癖で外部割り込みの設定にLDS、STS命令を使っていた。なので、外部割り込みが全く設定出来ていなかった。

次に、スイッチを押すとPB0がLowになるが、そのままそこで止まってしまうという症状が出た。

当初はユーザーフラグ(マイコンには元々用意されていない自分で勝手に作ったフラグ)は使用せず、割り込みが発生したら直接カウンタを判定して指定したラベルにジャンプするように記述していたのだが、それだと次の割り込みが発生しない。

何故なら、AVRは割り込みが発生したら自動的に全割り込み禁止にし、RETI命令で割り込み処理から割り込み前の処理に戻る時に再び全割り込みが許可される。

また、割り込みが発生するとプログラムアドレス(現在実行しているプログラムが何行目かみたいなもの)を一旦マイコン内部のメモリに保存して、RETI命令が実行される時にこのメモリの値を参照して元のプログラムに戻り、同時にメモリに保存されたアドレスの値を消去しているが、これも実行されないのでおかしな事になる。

割り込み処理から他の処理にジャンプするときにこれらRETI命令がやっている事を手動でプログラムすればこの問題は解決すると思ったが、今後のメンテナンス性等が悪くなりそうだったので、ユーザーフラグを用意してちゃんとRETI命令を記述するようにした。

これで、遂にマイコンが意図した通りに動くようになったので、いよいよ回路設計だ。

回路設計・組み立て編に続く。

カテゴリー: 未分類 | 2件のコメント

【HK829とマイコンを使用】にぎったら「にぎにぎ」「コメコメ」としゃべるハンドグリップを作ってみた【導入編】

目次

シリーズ19作目で17代目のデリシャスパーティープリキュア。今回は早い段階で追加プリキュアの登場を匂わせ、おもちゃ等いろんな方面から情報を小出しにして全く隠す気がなかった。自分はスイート♪プリキュアのキュアミューズの情報がシリーズ後期までかなり厳重に隠されていたのが印象的なので、今回の追加プリキュアの情報を全く隠す気がない公式の動きにまた別の強い印象を覚えることになった。

ここ数週間はキュアフィナーレの登場で界隈が盛り上がっているところではあるが、今回はキュアプレシャスについての話だ。

ゆいがキュアプレシャスに変身する時の始めの方で、ゆいとコメコメの「にぎにぎ」「コメコメ」「ハートを」「コメコメ」「シェアリンエナジー!」「コメー!」というやり取りがある。

これは仕草としてはゆいがおむすびを握っているものなのだが、この変身シーンを何度か見ている内に、握る→握るといえばハンドグリップ→ハンドグリップを握ってゆいの声で「にぎにぎ」としゃべったら面白そう!と何故か突然閃いてしまった。閃いてしまったので作るしかない。どうやって作るか考えてみた。

今回は秋月電子通商で売っている音声録音再生IC「HK829」を使用する。最大4メッセージまで録音でき、このようなおもちゃを作るのに持って来いだ。このICにあらかじめTVの音声を録音しておいて、スイッチを押す度に順番にメッセージを再生するような回路を作れば良い。

HK829応用回路例
図1: HK829応用回路例(データシートより引用)(画像クリックで拡大)

データシートの図によれば、通常はメッセージ毎にそれぞれスイッチを取り付け、再生したいメッセージに対応したスイッチを押して使用する。4メッセージを再生するならスイッチは4つ必要になる。

しかし、今回は「ハンドグリップを握るたびに指定された順番でそれぞれのメッセージを流す」という仕様で、ハンドグリップを握った事を検知して装置が反応するということで「スイッチは一つ」だ。これはこのICだけでは実現できない。こういった機能を出来るだけ単純な回路で実装しようと思ったら、マイコンの登場だ。

HK829のデータシートを更に読み込むと、メッセージ再生用のピンが16ms以上Low(GND電位)になるとメッセージが再生されるとある。HK829のM0~M3ピンの周辺回路を下図に示す。

HK829のM0~M3ピン周辺回路
図2: HK829のM0~M3ピン周辺回路

通常はこれらのピンの電位はHighで、スイッチを押した時だけLowになる。このスイッチと同じ役割をマイコンにやらせることも出来る。HK829メッセージ再生用の4つのピンにそれぞれマイコンのGPIOピンを繋ぎ、一つしかないスイッチもマイコンに繋ぎ、スイッチを押す度に指定した順番でマイコンの対応するメッセージのピンを16ms以上Lowにすれば良い。下図がマイコンとHK829を接続した状態だ。

HK829とマイコンを接続し、一つのスイッチで制御する回路
図3: HK829とマイコンを接続し、一つのスイッチで制御する回路

以上を踏まえて、必要な部品を選定する。

まずは音声録音再生IC「HK829」。これが無いと始まらない。

また、マイコンが必要だ。上記のような単純な動作は多分どんなマイコンでも出来るが、自分はAVRの開発環境が整っているのでAVR ATtiny13Aを使用する。スイッチ入力を検知するための外部割り込み機能があり、それに使用するピンを除いてもGPIOが5つある。後はタイマも内蔵されている。つまり、必要なマイコンの機能は外部割り込みとタイマだ。後はスイッチが押された回数を記憶する機能。これらは多分殆ど全てのマイコンが持っていると思うので、自分の知識や環境に合わせてPICでもArduinoでも好きなマイコンを使えばいいと思う。

後、自分は元々ATmega328Pならいくつか持っていたので別にそれでもいいのだが、外部割り込みとタイマしか使わない割にはオーバースペックであり、DIP28ピンもあり基板上で場所を取る。ATtiny13AはDIP8ピンで小さく、必要な機能が揃っているという事で選定し、新たに購入して用意した。

また、スイッチ入力はチャタリングが付き物だが、対策としてシュミットトリガインバータとコンデンサを組み合わせた回路が有効だ。しかし、ロジックICで定番の74HCシリーズではシュミットトリガインバータが内部に6個も実装されているが実際に使うのは1個であり、DIP14ピンで場所も取る。これも秋月電子通商に1回路入りのTC7S14FというSOT23パッケージに似た米粒より小さいとても小型の物があったのでそれを使用した。折角小さいのだが、逆に小さすぎて使いづらいので変換基板でDIP6にしてしまったが…。それでも74HCシリーズのDIP14ピンに比べれば大分小さい。

それと、後に必要無い事が判明してしまうのだが、HK829で直接スピーカーを鳴らすのは難しいかと思い、定番のアンプICである386を使用した。

マイコンプログラム編に続く。

カテゴリー: 未分類 | 2件のコメント

豊橋7時48分発普通浜松行きとホームライナー静岡36号は乗り換えなしで行けるか

結論:行けない

この記事の話は2021年3月13日現在の土休日ダイヤにのみ言及している。平日はホームライナー静岡36号が運転されないので注意。

豊橋7時48分発普通浜松行き(土休日の列車番号は5920M)は普通列車だが、特急型車両の373系で運転される。

この列車は浜松に8時26分に到着し、引き続き東海道本線の静岡方面へ土休日は最短で浜松8時29分発ホームライナー静岡36号(列車番号5386M)が接続する。ホームライナーなので、当然車両も373系だ。

1年位前のダイヤではこのホームライナーよりの後に浜松始発の普通熱海行きがあり、ホームライナーに乗ったとしても結局静岡でこの熱海行きに乗り換えることになる。当時のダイヤでは熱海へはそれに乗って行くのが最速だったので、ホームライナー静岡の存在は知っていたがわざわざ追加料金を払い乗り換え回数を増やしてまで乗ることは無かった。

豊橋から浜松までの普通列車が373系で、間3分で浜松始発のホームライナーの存在…これは、ホームライナー浜松3号が20時10分に浜松に到着し、そのまま普通豊橋行きに切り替わって20時12分に浜松を発車する事を彷彿させる…。時刻表で見た瞬間、そう思った。

しかし、実際に乗ってみると、豊橋発の5920Mは浜松駅の3番線に到着し、ホームライナー静岡36号は1番線からの発車であった。つまり、373系から別の編成の373系へ乗り換えが必要であった。

急いでざっと見た感じ、浜松駅の上りホームに乗車整理券売機は見当たらなく、3分の乗り換え時間で改札の外に出て乗車整理券を買う事は不可能なので、乗車整理券は乗車してから車掌から購入した(周りの大半の乗客もそうであった)。

青春18きっぷで東海道本線の静岡県区間下りを快適に移動するのは、沼津18時31分発ホームライナー浜松3号にのり、そのまま373系で豊橋まで行けてしまう。

同区間の上りの移動は浜松で乗り換えが必要なのが難点だが、豊橋7時48分発普通浜松行きと浜松8時29分発ホームライナー静岡36号を推したい。下りと比べて距離は若干短くなるが、豊橋から静岡まで373系で快適に移動出来る。

カテゴリー: 未分類 | コメントする

AVRマイコンATmega328Pでロータリーエンコーダを回す

序文

マイコン初心者の筆者が、作業を始めてから3日くらいかかり、何とか一応意図通り動くところまで漕ぎ着けた軌跡の話。

筆者は30代半ばにして、初めてマイコンを触った。なんとなくのイメージだが、(趣味で)人がマイコンを初めて触るという経験をするのは10代が多い気がする。

10年くらい前にマイコンを始めようと思って買った参考書(そのまま10年放置状態だったがw)がアセンブリ言語での説明で、今回の開発はこの本を大いに参考にしたのでこのブログも説明は基本的にアセンブリ言語で行う。筆者が参考にした本を下記に示す。以下「参考書」という。

AVRマイコン・プログラミング入門 – 廣田修一 著

筆者のレベルと想定する読者

筆者は、学生時代に電気、電子回路のことは一応学んだが、殆ど忘れていて基本的な事しか覚えていないというレベル。例えば、トランジスタ、ダイオード、コイル、コンデンサ等の素子がどういう動きをするか位は大まかに分かる。電気に関する簡単な計算は出来る。しかし、複素数、微分、積分、行列を使うか、それ以上のレベルの計算のやり方は忘れた。

プログラミングのレベルとしては、高級言語プログラミングの基本的な事は分かる。例えばExcel VBAでちょっとしたアプリケーションは作れる。しかし、C言語は学生の頃に少し習ったが、殆ど完全に忘れている。何故わざわざアセンブリ言語で開発しているのかと言えば、昔たまたま買ったマイコンの参考書がアセンブリ言語で書かれていて、C言語を勉強するのが面倒臭いというのが大きいw

なので、想定する読者も基本的な電気の知識はあり、高級言語プログラミングの基本的な事は知っている(変数、条件分岐、ループ等その辺の概念は分かる)と言う前提で話を進める。

回路の概要

ロータリーエンコーダの出力をマイコンで受け取るにはどうすれば良いかいろいろ検索したのだが、上位にヒットするのは大抵ArduinoかPICの情報で、言語はSketchと呼ばれるC言語に似たもの(PICはC言語)だ。AVRのアセンブリ言語で開発しようとしてる筆者にはプログラミングとしてはあまり参考にならない。

この記事は、AVRをアセンブリ言語で開発し、ロータリーエンコーダの出力を受け取るにはどうすればいいか示すのを目的にしている(そんな需要あるのか?w)。今回はロータリーエンコーダがCWかCCWのどちらの方向に回ったかだけを判定し、速さなどは問わないものとする。電子ボリュームやセレクタとして使うことを想定している。以下に、ブレッドボードで組んだ回路の写真と全体回路図を示す。

回路を組んだ写真

写真1 回路をブレッドボードに組んだ写真

全体回路図

図1 全体回路図

ATmega328P(以下、マイコンと言う)のPORTBの全ビットにLEDを接続してある。PORTBの各ビットが変化すると、それに対応したLEDの点灯状態も変化する。始め、LEDに直列に接続している抵抗は1kΩにしていたのだが、眩し過ぎるので途中で10kΩに変更した。これでも十分に明るい。点灯しているか否か分かれば良いだけならもっと暗くても大丈夫だ。

LEDのカソード側がマイコンのピンに繋がっているので、ピンがLになると点灯する。参考書にも、マイコンのHの出力電流よりもLの入力電流の方が大きいので、ピンに接続するトランジスタやLEDはLになった時に動作するようにした方が良いと書いてあった。

ロータリーエンコーダは千石電商で売っている100円程度の安い物で、インクリメンタル方式でクリックありの物を使用した。インクリメンタル方式のロータリーエンコーダを回すとどのように出力するか分からない人は、各自で検索して欲しい。

ロータリーエンコーダのA相をトランジスタで組んだNOTゲートを通してマイコンの外部割りこみ0のピンINT0に接続する。

ロータリーエンコーダのB相は10kΩの抵抗でVccからプルアップしてマイコンのPINC0に接続する。

ロータリーエンコーダを回すと、A相の出力がH→L、L→Hと変化し、NOTゲートを通してマイコンのINT0はL→H、H→Lと変化する。

何故わざわざNOTゲートを通すのかは、後述の筆者による勘違いのためなのだが、上手く動作した後にこのNOTゲートを外したらまた上手く動かなくなったので、最終的にこのままにしてある。多分NOTゲートを外しても抵抗とコンデンサの値を適切に設定すれば上手く動くと思うがメンドクサイ

この時、マイコンのINT0がH→Lと変化したタイミングで外部割りこみを発生させ、ロータリーエンコーダのB相が接続されているPINC0の状態により、回転方向がCWかCCWか判定する仕組みだ。

回路設計において、最終的にうまくいった要因はマイコンのピン4 INT0とGND及びピン23 PC0とGND間に接続したコンデンサC0とC1による。これを接続しないとロータリーエンコーダのチャタリングノイズを拾ってしまい、右に1クリック回した時に「右に1クリック、左に1クリック回した」動作をし、左に1クリック回すとその逆になったりする。

酷いと左に1クリック回したはずなのに右に1クリック回した動作をしたり、左に速く数クリック回すと右に数クリック回した動作をしたりと全く安定しなかった。今回の開発は3日くらいかかったが、そのうち2日以上はこの原因を取り除くのに費やしてしまった。

マイコンでロータリーエンコーダを使用する際は、ノイズ対策のコンデンサが必須。これ重要。

ノイズ対策としてコンデンサを接続すると良いという情報は下記の記事を参考にした。Arduinoの記事だが、ロータリーエンコーダからマイコンまでの回路設計と言う意味では非常に参考になった。

第二十一項 ロータリーエンコーダとノイズ対策・割り込み – kusamura

この記事ではノイズ対策として0.01μFのコンデンサを入れているが、手元に無かったので試しに0.047μFのコンデンサを入れてみた。しかし、あまり変化が無かったので、思い切って0.47μFのコンデンサにしてみたところ、容量が大きすぎて誤動作すると言うこともなく、寧ろ非常にスムーズに動作するようになった。

レジスタとは

レジスタとは何か、割り込みとは何かというレベルから説明し始めると、それこそ一冊の本が出来上がるくらいの分量になってしまうので、そこら辺を正確に知りたい人はネットや本で調べて欲しい。

筆者はアセンブリ言語が良く分からない内は高級言語の知識に絡めて以下のように理解した(アセンブリ言語の正確な概念から鑑みれば甚だ不正確な表現が含まれるのは承知いただきたい)。

汎用レジスタ

高級言語の変数のように使用する。ただし、入れられるデータ型はVisual BASICで言うところのバイト型。一つのレジスタには0~255の値までしか代入出来ない。AVRのR0~R15は制約が多いので、初心者の内は特に理由がない場合はR16から使った方が良い。

きっと、マイコンに慣れてきて汎用レジスタの中にも特殊な役割があるものを知ったり、数が足りなくなったりしたら自然とR0~R15を使うことも出てくるだろう。

標準IOレジスタ

マイコンの入出力や各機能を使用するのに用いる。ある意味高級言語の関数に近い。例えば入力のPINBというレジスタは、PBx(xは0~7の整数)のピンの値が変化すると同様に変化し、それを読み込むことでピンの値を読み取る事が出来る。

IN  R16,	PINB

例えば上記のアセンブリ言語のソースは、高級言語風に表現すれば

R16 = PINB()

R16という変数が存在して、PINB()はマイコンのピンB群の状態を返す関数で、その値を変数に代入する…みたいな意味だ。勿論、実際のアセンブリ言語のソースでこのように表記してもエラーになるので注意。

このレジスタに値を代入すると、定められたマイコンの機能が起動したり、機能の動きを変える事が出来る…とイメージすると良いと思う。

拡張IOレジスタ

高級言語はCPUの挙動やメモリ構造をあまり意識せずに使用出来るが、マイコンをアセンブリ言語で開発すると言うのはマイコンと言うCPUの動きそのものを命令することなので、大変だがマイコンのメモリ構造は理解していないと使いこなすことは出来ない。ここでは深くは触れないので、本やネットで頑張って調べよう。

標準IOレジスタも拡張IOレジスタも、マイコンの機能を起動したりその動きを変えたりと役割は殆ど同じ。違いは、マイコンの中でどのメモリアドレスに配置されているかという部分だ。

マイコンはメモリのあるアドレスに値を代入するという操作一つを取っても、アドレス群ごとに命令が異なる。高級言語のように何でも「変数 = 値」と代入は出来ないのだ。

何故なのかとか考えてはいけない。そういうものなのだ。

フローチャート

プログラムのフローチャートを下図に示す。

フローチャート1

図2 フローチャート1/2

フローチャート2

図3 フローチャート2/2

要約すると、ロータリーエンコーダが回されたらA相からの出力変化により外部割りこみ0を発生させ、B相からの出力をPINC0で読み取りCWかCCWか判定し、PORTBに接続した8つのLEDを2進数表示で点灯させる仕組みだ。

右に回すと数字が増えていき、255クリック目で全点灯状態になる。

左に回すと数字が減っていき、全点灯状態から255クリック目で全消灯状態になる。

チャタリング防止の為に回路にコンデンサを入れているが、ソフトウェアでもA相の出力変化からPINC0の値を読み取るのに約2msの遅延時間を入れて対策している。

プログラム全文

プログラム全文を下記に示す。

;ロータリーエンコーダを回してLED点灯を変化させるプログラム

.include "m328pdef.inc"

;汎用レジスタ
.def STACK	=	R16
.def R_TEMP1	=	R17
.def R_TEMP2	=	R18
.def R_FLAG	=	R19

;定数定義
.EQU B_INT0	=	0	;外部割り込み0
.EQU B_TMR0	=	2	;タイマ0
.EQU B_RTY_ENC	=	0	;ロータリーエンコーダがCWかCCWか判定するビット

.CSEG
	RJMP MAIN
.ORG	0x0002
	RJMP EXT_INT0	;外部割り込み0
.ORG	0x0020
	RJMP TMR0	;タイマ0

;外部割り込み0
EXT_INT0:
	;ステータス・レジスタの内容を退避
	IN  STACK,	SREG

	;外部割りこみ0発生フラグセット
	SBR R_FLAG,	(1<<B_INT0)

	;ステータス・レジスタの内容を復帰
	OUT SREG,	STACK

	RETI

;タイマ0
TMR0:
	;ステータス・レジスタの内容を退避
	IN  STACK,	SREG
	
	;カウンタ停止
	LDI R_TEMP1,	0x00
	OUT TCCR0B,	R_TEMP1

	;タイマ0割り込み発生フラグセット
	SBR R_FLAG,	(1<<B_TMR0)

	;ステータス・レジスタの内容を復帰
	OUT SREG,	STACK

	RETI

;メインルーチン
MAIN:
	CLI	;全割り込み禁止

;PORT設定
	LDI R_TEMP1,	0B11111111	
	LDI R_TEMP2,	0B11111111	;全ポートを1にしてLEDを消灯させる
	OUT DDRB,	R_TEMP1
	OUT PORTB,	R_TEMP2

	LDI R_TEMP1,	0B11111110	;PORTCのピン0を入力にする
	LDI R_TEMP2,	0B00000000	;PORTCのピン0をプルアップなしにする
	OUT DDRC,	R_TEMP1
	OUT PORTC,	R_TEMP2

	LDI R_TEMP1,	0B11111011	;PORTDのピン2はINT0なので入力にする
	LDI R_TEMP2,	0B00000000	;PORTDのピン2をプルアップなしにする
	OUT DDRD,	R_TEMP1
	OUT PORTD,	R_TEMP2

;外部割り込み関連 レジスタ設定
	LDS R_TEMP1,	EICRA
	CBR R_TEMP1,	(1<<ISC00)
	SBR R_TEMP1,	(1<<ISC01)	;INT0がH→Lで割り込み発生
	STS EICRA,	R_TEMP1
	SBI EIMSK,	INT0	;外部割り込み0許可

;タイマ0関連 レジスタ設定
	LDS R_TEMP1,	TIMSK0
	SBR R_TEMP1,	(1<<TOIE0)
	STS TIMSK0,	R_TEMP1		;タイマ0割り込み許可


	;全割り込み許可
	SEI

MAIN01:
	;外部割り込み発生フラグ OFF
	;タイマ0桁溢れフラグOFF
	CLR R_FLAG

MAIN02:
	;外部割り込み判定
	SBRC R_FLAG,	B_INT0
	RJMP MAIN03	;外部割り込み0が発生

	RJMP MAIN02

MAIN03:
	;タイマ0セット(外部割り込みが発生してから2ms待つ)
	LDI R_TEMP1,	0x02	;プリスケーラ8
	OUT TCCR0B,	R_TEMP1

	LDI R_TEMP1,	0x06	;割り込み時間2ms
	OUT TCNT0,	R_TEMP1

MAIN04:
	;タイマ0桁溢れ判定
	SBRC R_FLAG,	B_TMR0
	RJMP MAIN05	;タイマ0桁溢れ

	RJMP MAIN04

MAIN05:
	;ロータリーエンコーダの回転方向を判定
	SBIC PINC,	B_RTY_ENC	;PINCのビット0が1ならCW、0ならCCW(PINCのビット0が0なら1行スキップ)
	RJMP MAIN10	;ロータリーエンコーダの回転方向がCWの場合
	RJMP MAIN20	;ロータリーエンコーダの回転方向がCCWの場合

MAIN10:
	;ロータリーエンコーダの回転方向がCWの場合
	IN  R_TEMP1,	PORTB
	CPI R_TEMP1,	0x00	;PORTBが0かどうか判定
	BREQ MAIN01	;PORTBが0なら何もせずにメインルーチンのループに戻る
	DEC R_TEMP1		
	OUT PORTB,	R_TEMP1		;PORTBが0でないならデクリメントする

	RJMP MAIN01

MAIN20:
	;ロータリーエンコーダの回転方向がCCWの場合
	IN  R_TEMP1,	PORTB
	CPI R_TEMP1,	0xFF	;PORTBがFFかどうか判定
	BREQ MAIN01	;PORTBがFFなら何もせずにメインルーチンのループに戻る
	INC R_TEMP1
	OUT PORTB,	R_TEMP1		;PORTBがFFでないならインクリメントする

	RJMP MAIN01

思い返せば初歩的な部分だと思うが、つまずいた部分としては…

	LDI R_TEMP1,	0B11111011	;PORTDのピン2はINT0なので入力にする
	LDI R_TEMP2,	0B00000000	;PORTDのピン2をプルアップなしにする
	OUT DDRD,	R_TEMP1
	OUT PORTD,	R_TEMP2

このPORTDのピン2を入力に設定する部分。ネットを検索すると、入力に設定しなくてもピンの値が変化すれば割り込みが発生すると書いてあるが、出力に設定しているピンの値を変化させるには、普通はソフトウェア的な処置が必要だろう。

今回のように、ロータリーエンコーダやスイッチで値を入力する場合は、やはり外部割りこみを兼ねるピンは入力に設定するべきだ。

もう一つ、外部割りこみ制御レジスタの部分。

;外部割り込み関連 レジスタ設定
	LDS R_TEMP1,	EICRA
	CBR R_TEMP1,	(1<<ISC00)
	SBR R_TEMP1,	(1<<ISC01)	;INT0がH→Lで割り込み発生
	STS EICRA,	R_TEMP1
	SBI EIMSK,	INT0	;外部割り込み0許可
ビット 7 6 5 4 3 2 1 0
EICRA ISC11 ISC10 ISC01 ISC00

表1 EICRAレジスタの構造

ISC01 ISC00 割りこみ発生条件
0 0 INT0がLow
0 1 INT0がLow→High、High→Low
1 0 INT0がHigh→Low
1 1 INT0がLow→High

表2 外部割りこみ0発生条件

始め、このレジスタのISC01、ISC00の部分を「01」と設定すると、「INT0がLow→High→Lowとなった時に外部割り込み0が発生する」とずっと勘違いしていた。つまり、形が「凸」のような波形を受信した時に割り込みが発生すると思っていた。

実際にはISC01、ISC00を「01」と設定しているとINT0がLow→Highになった時も、High→Lowになった時も、つまり波形の立ち上がりと立ち下がりの両方で外部割り込み0が発生する。

ロータリーエンコーダを1クリック回した時に外部割りこみ0の動作を2回してしまうのは何故か分からない原因の一つだった。

回路が上手く動かなくてハマっている2日余りのうち1日余りはこのレジスタ設定の勘違いに気付いていないからだった。

ロータリーエンコーダA相の出力とマイコンのINT0の間にNOTゲートを入れたのもこの勘違いによるもの。

まとめ

ロータリーエンコーダはチャタリング対策として、マイコンの入力端子とGNDの間にコンデンサを接続しよう。

ロータリーエンコーダやスイッチで外部割りこみを発生させるときは、そのピンは入力に設定しよう。

AVRの外部割りこみに「波形が立ち上がって立ち下がった時」という発生条件は無い。

以上、マイコン初心者が実験でこの知見を得るのに3日かかった…というお話でした!w

これで、ロータリーエンコーダを値セレクタとして使う準備は整ったので、次の段階に進める。

カテゴリー: 未分類 | コメントする