【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命令を記述するようにした。

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

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

カテゴリー: 未分類 パーマリンク

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

  1. ピンバック: 【HK829とマイコンを使用】にぎったら「にぎにぎ」「コメコメ」としゃべるハンドグリップを作ってみた【導入編】 | Tales of Black-Mant 2

  2. ピンバック: 【HK829とマイコンを使用】にぎったら「にぎにぎ」「コメコメ」としゃべるハンドグリップを作ってみた【回路設計・組み立て編】 | Tales of Black-Mant 2

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です