iPodから音楽データを取り出すという夢を見た。

先日突然、使っているMacBookのHDが音を立て、動かなくなりました…orz
CD-ROMブートさえもできない状況で、
仕方がないので、HDを換装しました。

バックアップを取ってなかったので、データは無くなっちゃいました。
自分の中で無くなって悲しいデータといえば、この3つ。

  • 写真
  • 音楽
  • 開発中のアプリのソース

特に、音楽データは、量がそこそこあったし、
iTunes Storeで買ったものもあったので、
なかなか痛手だなぁ、と思っていたのですが、
使っていたiPod nano(第6世代)をPCにつないで、
認証すると、あらまぁ!
購入していた曲は元通りiTunesに。

ですが、もちろんCDから取り込んだ曲は入らず。
あきらめきれないので、方策を探した結果、いろんなツールがあるもんですねぇ。
(グレーゾーンじゃないかと思うので、紹介は控えますが…)

でも、iPodから音楽データを取り出すためだけにインストールしたくないので、
ツール類をインストールせずに自力でなんとかしてみよう…

という夢を見ました。
以下では、夢の内容を記します。

続きを読む

NSArrayのindexOfObjectの戻り値とNSNotFoundについて

最近、Objective-cについて勉強し始めました。

参考にさせていただいてるのはコチラ!
Cocoaの素、Objective-Cを知ろう − @IT
分かりやすくて、とても勉強になります!ありがとうございます!!

今のところ、(4)まで進んだのですが、
サンプルプログラムを動かしてみるとWarningが出てくる。
バージョンも変わってるだろうし、仕方がない。

具体的には、リンク先の下記の部分。
配列とループ処理を理解しよう (2/4):Cocoaの素、Objective-Cを知ろう(5) - @IT

//(4)ある要素のインデックスを取得
int objectIndex = [array indexOfObject:@"pqr"];
if (objectIndex == NSNotFound) {
NSLog(@"'pqr' is not found.\n");
}

出力されるWarningはこんな感じ。

$ gcc -o main main.m -framework Foundation
main.m: In function ‘main’:
main.m:20: warning: comparison is always false due to limited range of data type

調べてみたら「比較が常にfalseになってるよ」的なことだということで、
実行ファイルを実行してみると、案の定「NSLog(@"'pqr' is not found.\n");」が出てない。

ググると似たような人発見!
NSNotFoundにはまった。 - メソッド屋の日記

なるほどー!
難しいことはさておきw、
NSNotFoundの値が変わっちゃってるっていうことね。

で、どうすりゃいいんだ、っていうことでさらに調べたところ、
リファレンスによると、indexOfObjectの戻り値はNSUIntegerなので、以下のように修正。

//  int objectIndex = [array indexOfObject:@"pqr"];
NSUInteger objectIndex = [array indexOfObject:@"pqr"];

無事、Warningも出なくなり、
「'pqr' is not found.」も出力されるようになりましたとさ。

めでたしめでたし。

職業訓練47日目 H8マイコン制御技術(LCD-SCI制御技術)

LCDの制御

使用したLCDはコチラ!(多分…)
LCDキャラクタディスプレイモジュール(16×2行バックライト無): ディスプレイ・表示器 秋月電子通商-電子部品・ネット通販

16文字×2行のアスキー文字を1文字につき5×7ドットあるいは5×10ドットで表示する。
外部との接続には、12本(2列6本)のピンヘッダを利用する。

LCDの基本構造

パス…。
またWikipediaで逃げよう…。
液晶ディスプレイ - Wikipedia

市販LCDの使用方法

表示データはアスキーコード形式で8ビットのまま、
あるいは8ビットを4ビットずつ2回に分けて読み込む。

表示データの読み込みは、すべて外部からの制御により行われる。
内部に読み込まれたデータは、文字表示位置に対応するメモリに書き込まれ、
書き改められるまで内容は保持され、表示され続ける。

LCDの駆動方法

LCDの初期設定

  1. ソフトウェアリセット
  2. ファンクションセット
  3. ディスプレイON/OFF制御
  4. エントリーモードの設定

細かい話は抜きで…。
ここでの話は全部、実習で使ったマザーボードLCD上でのお話ですし。

データの転送と表示

初期設定完了後、表示データをLCDに転送可能な状態になる。

表示データはRAM領域の特定の場所を定義し、そこに書き込む。
そのデータを中継用RAM領域に1バイトずつ順番に読み出し、LCDに転送し、表示させる。

LCD表示プログラムの流れ

使用したテキストでは、LCDにSWのON/OFF状態を表示させたり、LEDをチカチカさせたり、
わざと?冗長なプログラムにしていたので、
バッサリ表示処理にしぼって、かつ、同じ処理を自分が勝手にまとめたプログラムをお送ります。

		.CPU 300HA
		.SECTION VECT, CODE, LOCATE=H'000000

LCD_D:		.EQU	H'FFEF12
P3DR:		.EQU	H'FFFFC6
P3DDR:		.EQU	H'FFFFC4
RS:		.BEQU	4,P3DR
E_SIG		.BEQU	5,P3DR
LCD162		.EQU	H'FFEF13

		.SECTION ROM, CODE, LOCATE=H'000100

		MOV.L	#H'FFFF00, ER7
		
		MOV.B	#H'FF, R0H
		MOV.B	R0H, @P3DDR

; LCDのソフトウェアリセット
		JSR	@TIME5MS
		JSR	@TIME5MS
		JSR	@TIME5MS
		
		MOV.B	#B'00100011, R0L
		JSR	@LCD_RST
		JSR	@LCD_RST
		JSR	@LCD_RST
		MOV.B	#B'00100010, R0L
		JSR	@LCD_RST

; LCDの初期設定
		MOV.B	#B'00101000, R0L
		JSR	@LCD_SET
		MOV.B	#B'00001110, R0L
		JSR	@LCD_SET
		MOV.B	#B'00000110, R0L
		JSR	@LCD_SET

; LCDの初期画面表示
		MOV.B	#B'00000001, R0L
		JSR	@LCD_SET
		MOV.B	#32, R0L
		MOV.L	#LCD162, ER1
		MOV.L	#MOJI, ER2
INIT_DISP:	MOV.B	@ER2+, R0H
		MOV.B	R0H, @ER1
		INC.L	#1, ER1
		DEC.B	R0L
		BNE	INIT_DISP
		JSR	@LCDDSP

LOOP:		JMP	@LOOP


; ------------------------
; サブルーチン
; ------------------------

; LCD文字出力16文字×2行転送サブルーチン
LCDDSP:		PUSH.L	ER0
		PUSH.L	ER1
		MOV.B	#B'00000010, R0L
		JSR	@LCD_SET
			
; LCD表示1行目の16文字転送
		MOV.B	#16, R0L
		MOV.L	#LCD162, ER1
LCDDSP1:	MOV.B	@ER1+, R0H
		MOV.B	R0H, @LCD_D
		BSET	RS
		JSR	@LCD_OUT4
		BCLR	RS
		DEC.B	R0L
		BNE	LCDDSP1
		MOV.B	#B'11000000, R0L
		MOV.B	R0L, @LCD_D
		BCLR	RS
		JSR	@LCD_OUT4
			
; LCD表示2行目の16文字転送
		MOV.B	#16, R0L
		MOV.L	#LCD162 + 16, ER1
LCDDSP2:	MOV.B	@ER1+, R0H
		MOV.B	R0H, @LCD_D
		BSET	RS
		JSR	@LCD_OUT4
		BCLR	RS
		DEC.B	R0L
		BNE	LCDDSP2
		POP.L	ER1
		POP.L	ER0
		RTS
			
			
; LCDにデータを転送
LCD_SET:	MOV.B	R0L, @LCD_D
		BCLR	RS
		JSR	@LCD_OUT4
		JSR	@TIME5MS
		RTS
			
; LCDのソフトウェアリセットサブルーチン
LCD_RST:	MOV.B	R0L, @LCD_D
		BCLR	RS
		JSR	@LCD_OUT8
		JSR	@TIME5MS
		RTS
			
; LCDへのデータ/コマンドの転送(8ビット)
LCD_OUT8:	PUSH.L	ER0
		BSET	E_SIG
		MOV.B	@LCD_D, R0L
		MOV.B	R0L, @P3DR
		JSR	@TIME100US
		BCLR	E_SIG
		JSR	@TIME100US
		POP.L	ER0
		RTS

; LCDへのデータ/コマンドの転送(4ビット)
;  上位4ビット送出
LCD_OUT4:	PUSH.L	ER0
		BSET	E_SIG
		MOV.B	@LCD_D, R0L
		SHLR.B	R0L
		SHLR.B	R0L
		SHLR.B	R0L
		SHLR.B	R0L
		JSR	@OUT4
;  上位4ビット送出
		BSET	E_SIG
		MOV.B	@LCD_D, R0L
		JSR	@OUT4
		POP.L	ER0
		RTS

; 4ビット送出サブルーチン
OUT4:		AND.B	#B'00001111, R0L
		MOV.B	@P3DR, R0H
		AND.B	#B'11110000, R0H
		OR.B	R0H, R0L
		MOV.B	R0L, @P3DR
		JSR	@TIME5MS
		BCLR	E_SIG
		JSR	@TIME5MS
		RTS

; 5msのタイマ
TIME5MS:	MOV.L	#10000, ER6
ER6DEC:		DEC.L	#1, ER6
		NOP
		BNE	ER6DEC
		RTS
			
; 100usのタイマ
TIME100US:	MOV.L	#200, ER6
		JSR	@ER6DEC
		RTS
			
		.ALIGN 2
		.SECTION LCDDATA, DATA, LOCATE=H'000B00
MOJI:		.SDATA "H8/3048F TEST BD 1111  11111111 "

		.END

これでLCDに、

H8/3048F TEST BD
 1111  11111111 

って表示されるはず…。

手元に実機がないのにソースいじちゃったから動かない可能性大だけど…。
そして、本来は"1"の部分がSWのON/OFFで変化するんだけど…。

本当は、

(・ω・)ノ
Hello, world!

って表示したかったんだけど、アスキーコードの転送の仕方が分からず、断念しました。
アァー、残念…orz

職業訓練46日目 H8マイコン入門(アセンブラ言語による実習)

割込み制御

割込み

実行中の処理を一時停止して、他の処理を行った後に最下位する機能。

●割込み動作の流れ

  1. PCとCCRをスタックに待避(必要であれば、汎用レジスタのデータもスタックに待避)
  2. CCRの割込みマスクビットに"1"をセット(他の割込みを禁止する)
  3. メモリの割込みベクタアドレスから、割込みプログラムの開始アドレスをPCに読み込む
  4. 割込みプログラムを実行
  5. RTE命令で割込みプログラムから復帰する
  6. 待避していたPCとCCRを復元して、元の処理を再開する
IRQ端子を使った割込み

IRQ0〜5端子に"0"を入力することで、割込みが発生する。
→使用するピンによって6種類の割込みプログラムを選択できる。

IRQネーブルレジスタ(IER):端子別に許可/禁止の設定
IRQセンスコントロールレジスタ(ISCR):割込み信号の有効な動作タイミングを設定

実際に製作した回路は、
プッシュSWからシュミットトリガゲート(74LS19)を介して、
IRQ0端子に信号を入力する。

	.CPU 300HA
	.SECTION PROG13, CODE, LOCATE=H'000000

	.DATA.L	MAIN			; メイン処理は、000000番地から
	.ORG	H'000030		; IRQ0の割込みベクタアドレス
	.DATA.L	IRQ0			; 割込みプログラムの開始アドレス

IER	.EQU	H'FFFFF5		; IRQイネーブルレジスタ
ISCR	.EQU	H'FFFFF4		; IRQセンスコントロールレジスタ
P1DR	.EQU	H'FFFFC2
P1DDR	.EQU	H'FFFFC0
P5DR	.EQU	H'FFFFC8
P5DDR	.EQU	H'FFFFCA

	.SECTION ROM, CODE, LOCATE=H'000100

MAIN:	MOV.L	#H'FFF00, ER7

	MOV.B	#H'FF, R0L
	MOV.B	R0L, @P1DDR
	MOV.B	R0L, @P5DDR

	BSET	#0, @IER		; IRQ0の割込みを許可
	BSET	#0, @ISCR		; 割込みパルスの立下がりエッジで割込みを行う
	LDC	#0, CCR			; 割込み許可

	MOV.B	#B'01111111, R0L	; LED点灯データ(ポート1)
LOOP:	MOV.B	R0L, @P1DR
	JSR	@TIM2
	ROTR.B	R0L
	JMP	@LOOP

;----- 割込み処理 -----
IRQ0:	PUSH.W	R0			; レジスタの待避
	PUSH.L	ER5
	PUSH.L	ER6
	MOV.B	#D'3, R0H		; LED点灯回数データ
YET:	MOV.B	#H'FF, R0L		; LED点灯データ(ポート5)
	JSR	@TIM2
	MOV.B	#H'00, R0L
	MOV.B	R0L, @P5DR
	JSR	@TIM2
	DEC.B	R0H			; 点灯回数 - 1
	BNE	YET			; 点灯回数が0でなければ、YETへ
	POP.L	ER6			; レジスタの回復
	POP.L	ER5
	POP.W	R0
	RTE				; 割込みから復旧
;----------------------

TIM2:	MOV.W	#D'50, E5		; 0.5秒のタイマサブルーチン
L2:	JSR	@TIM1
	DEC.W	#1, E5
	BNE	L2
	RTS

TIM1:	MOV.L	#D'20000, ER6		; 10msのタイマサブルーチン
L1:	DEC.L	#1, ER6
	NOP
	BNE	L1
	RTS

	.END

上記のプログラムを実行すると、
0.5秒ごとにポート1の点灯するLEDが1個ずつ移動していき、
プッシュSWが押されると、ポート1のLEDが停止し、ポート5のLEDが3回点滅、
その後、またポート1のLEDが移動し始めるはず。

NMI端子を使った割込み

NMIとは、マスク(禁止)できない「ノンマスカブル割込み」のこと。
CCRの割込みマスクビットを"0"にしても禁止できない、優先度の高い割込み。
NMI端子の入力エッジは、システムコントロールレジスタ(SYSCR)で設定。

製作した回路は、上記のIRQ端子を使った回路と一緒で、
割込み信号を入力する端子がNMI端子になっただけ。

	.CPU 300HA
	.SECTION PROG14, CODE, LOCATE=H'000000

	.DATA.L	MAIN			; メイン処理は、000000番地から
	.ORG	H'00001C		; NMIの割込みベクタアドレス
	.DATA.L	IRQ0			; 割込みプログラムの開始アドレス

SYSCR	.EQU	H'FFFFF2		; システムコントロールレジスタ
P1DR	.EQU	H'FFFFC2
P1DDR	.EQU	H'FFFFC0
P5DR	.EQU	H'FFFFC8
P5DDR	.EQU	H'FFFFCA

	.SECTION ROM, CODE, LOCATE=H'000100

MAIN:	MOV.L	#H'FFF00, ER7

	MOV.B	#H'FF, R0L
	MOV.B	R0L, @P1DDR
	MOV.B	R0L, @P5DDR

	BCLR	#2, @SYSCR		; 割込みパルスの立下がりエッジで割込みを行う

	MOV.B	#B'11111110, R0L	; LED点灯データ(ポート1)
LOOP:	MOV.B	R0L, @P1DR
	JSR	@TIM2
	ROTL.B	R0L			; IRQ端子のプログラムとは逆の向き
	JMP	@LOOP

;----- 割込み処理 -----
IRQ0:	PUSH.W	R0			; レジスタの待避
	PUSH.L	ER5
	PUSH.L	ER6
	MOV.B	#D'3, R0H		; LED点灯回数データ
YET:	MOV.B	#H'FF, R0L		; LED点灯データ(ポート5)
	JSR	@TIM2
	MOV.B	#H'00, R0L
	MOV.B	R0L, @P5DR
	JSR	@TIM2
	DEC.B	R0H			; 点灯回数 - 1
	BNE	YET			; 点灯回数が0でなければ、YETへ
	POP.L	ER6			; レジスタの回復
	POP.L	ER5
	POP.W	R0
	RTE				; 割込みから復旧
;----------------------

TIM2:	MOV.W	#D'50, E5		; 0.5秒のタイマサブルーチン
L2:	JSR	@TIM1
	DEC.W	#1, E5
	BNE	L2
	RTS

TIM1:	MOV.L	#D'20000, ER6		; 10msのタイマサブルーチン
L1:	DEC.L	#1, ER6
	NOP
	BNE	L1
	RTS

	.END

IRQ端子の例とやってることは一緒ですね。

職業訓練45日目 H8マイコン入門(アセンブラ言語による実習)

A-D, D-Aコンバータの制御

A-Dコンバータ

アナログデータをデジタルデータに変換する装置。

  • 分解能:10ビット
  • 方式:逐次変換方式
  • チャネル数:8
  • 変換を行うアナログ電圧の範囲:0〜5V
  • 最大4入力の変換結果を16ビットのA-Dデータレジスタ4個に保持可能。

複数の入力を扱う場合、入力端子を切り替えながら順次変換を行う。

  • 単一モード:1チャネルのみを扱う
  • スキャンモード:指定した最大4チャネルを扱う

変換の実行→A-Dコントロール/ステータスレジスタのADST(ビット5)を"1"にセット
変換の終了→A-Dコントロール/ステータスレジスタのADF(ビット7)が"1"にセットされる

分解能が10ビットで、0〜5Vの電圧を入力した場合
5V / 2^10 = 5 / 1024 = 0.00488...V ≒ 4.88mV
→アナログ電圧がおよそ4.88mV変化するごとに、デジタルデータが"1"変化する

実習で製作した回路は、
5V電源→10kΩの可変抵抗→チャネル0のAN0端子に入力
ポート1のLED0〜7に変換結果を出力
といった感じ。

では、A-Dコンバータを利用したプログラムを。

	.CPU 300HA
	.SECTION PROG11, CODE, LOCATE=H'000000

ADCSR	.EQU	H'FFFFE8		; ADコントロール/ステータスレジスタ
ADDRA	.EQU	H'FFFFE0		; ADデータレジスタ
P1DR	.EQU	H'FFFFC2
P1DDR	.EQU	H'FFFFC0

	.SECTION ROM, CODE, LOCATE=H'000100	

	MOV.L	#H'FFFF00, ER7

	MOV.B	#H'FF, R0L
	MOV.B	R0L, @P1DDR		; ポート1を出力に設定

	MOV.B	#H'00, R0H		; 単一モード、チャネル選択(AN0)データ
	MOV.B	R0H, @ADCSR		; ADCSR設定
LOOP:	BSET	#5, @ADCSR		; AD変換スタート
L1:	BTST	#7, @ADCSR		; 変換終了チェック
	BEQ	L1
	MOV.B	@ADDRA, R1L		; 変換結果の取り出し
	NOT.B	R1L			; "1"で点灯するように反転
	MOV.B	R1L, @P1DR
	BCLR	#7, @ADCSR		; AD変換終了フラグのリセット

	JMP	@LOOP

	.END

上記のプログラムを実行すると、
可変抵抗をクルクル回すと、LEDがチカチカするはず。
アナログの量(電圧)が、デジタル(LED)に変換されているわけですね。

D-Aコンバータ

デジタルデータをアナログデータに変換する装置。

  • 分解能:8ビット
  • チャネル数:2

分解能が8ビットで、0〜5Vの電圧を出力する場合
5V / 2^8 = 5 / 256 = 0.0195...V ≒ 19.5mV
→デジタルデータが"1"変化するごとに、アナログ電圧がおよそ19.5mV変化する

製作した回路は、
ポート2のDIPスイッチ8ビットを入力、
ポート1のLED8ビットにデジタルデータを出力、
ポート7にテスタを接続し、アナログデータを測定しました。

では、プログラムはこちら。

	.CPU 300HA
	.SECTION PROG12, CODE, LOCATE=H'000000

DACR	.EQU	H'FFFFDE		; DAコントロールレジスタ
DADR0	.EQU	H'FFFFDC		; DAデータレジスタ
P1DR	.EQU	H'FFFFC2
P1DDR	.EQU	H'FFFFC0
P2DR	.EQU	H'FFFFC1
P2DDR	.EQU	H'FFFFC3
P2PCR	.EQU	H'FFFFD8

	.SECTION ROM, CODE, LOCATE=H'000100	

	MOV.L	#H'FFFF00, ER7

	MOV.B	#H'00, R0L
	MOV.B	#H'FF, R0H
	MOV.B	R0H, @P1DDR
	MOV.B	R0L, @P2DDR
	MOV.B	R0H, @P2PCR		; ポート2のプルアップ有効

LOOP:	MOV.B	@P2DR, R1L		; ポート2からデータ入力
	MOV.B	R1L, @P1DR
	NOT.B	R1L
	MOV.B	R1L, @DADR0		; DAデータレジスタに転送

	MOV.B	#H'40, R1H		; DA変換設定データ
	MOV.B	R1H, @DACR		; DA変換スタート

	JMP	@LOOP

	.END

DIPスイッチを上げたり下げたりすると、テスタの値が変化するのが観測できるはず。

職業訓練44日目 H8マイコン入門(アセンブラ言語による実習)

パルスモータの制御

パルスモータとは

パルス電力を入力として回転する(→マイコン制御と相性が良い)
長所:正確な回転(位置決め)を制御できる。

N極とS極をもつロータ(回転子)の周囲に電磁石(固定子)を配置した構造になっていて、
電磁石を磁化するパルスデータによって正確な位置決め制御ができる。

制御方式

  • 1相励磁方式:1相のみに電流を流す
  • 2相励磁方式:2相に電流を流す、1相励磁の2倍のトルクが得られる
  • 1-2相励磁方式:1相励磁と2相励磁を交互に行う、移動角度が1相励磁の半分になる
パルスモータの回転制御

面倒なので回路図は割愛…。

回路の特徴としては、

といったあたりかな。

  • 1相励磁方式
	.CPU 300HA
	.SECTION PROG6, CODE, LOCATE=H'000000

P1DR	.EQU	H'FFFFC2
P1DDR	.EQU	H'FFFFC0
P6DR	.EQU	H'FFFFCB
P6DDR	.EQU	H'FFFFC9

	.SECTION ROM, CODE, LOCATE=H'000100

	MOV.L	#H'FFFF00, ER7

	MOV.B	#H'FF, R0L
	MOV.B	R0L, @P1DDR
	MOV.B	R0L, @P6DDR
	MOV.B	#B'10001000, R0H		; 回転データ

LOOP:	MOV.B	R0H, @P6DR
	MOV.B	R0H, R1H
	NOT.B	R1H
	MOV.B	R1H, @P1DR			; 回転データをLEDに出力
	JSR	@TIM2
	ROTR.B	R0H				; 右に1ビットローテイト
	JMP	@LOOP

TIM2:	MOV.W	#D'30, E5			; 0.3秒のタイマサブルーチン
L2:	JSR	@TIM1
	DEC.W	#1, E5
	BNE	L2
	RTS

TIM1:	MOV.L	#D'20000, ER6			; 10msのタイマサブルーチン
L1:	DEC.L	#1, ER6
	NOP
	BNE	L1
	RTS

	.END
  • 2相励磁方式

上記コードの回転データを"1100"に変更する。

;	MOV.B	#B'10001000, R0H		; 回転データ
	MOV.B	#B'11001100, R0H		; 回転データ
  • 1-2相励磁方式

1相励磁と2相励磁を交互に出力する。
実行すると、1相励磁に比べてステップ角が半分になる。

	.CPU 300HA
	.SECTION PROG7, CODE, LOCATE=H'000000

P1DR	.EQU	H'FFFFC2
P1DDR	.EQU	H'FFFFC0
P6DR	.EQU	H'FFFFCB
P6DDR	.EQU	H'FFFFC9

	.SECTION ROM, CODE, LOCATE=H'000100

	MOV.L	#H'FFFF00, ER7

	MOV.B	#H'FF, R0L
	MOV.B	R0L, @P1DDR
	MOV.B	R0L, @P6DDR
	MOV.B	#B'10001000, R0L		; 回転データ1
	MOV.B	#B'11001100, R0H		; 回転データ2

LOOP:	MOV.B	R0L, @P6DR			; 回転データ1を出力
	MOV.B	R0L, R1H
	NOT.B	R1H
	MOV.B	R1H, @P1DR
	JSR	@TIM2
	MOV.B	R0H, @P6DR			; 回転データ2を出力
	MOV.B	R0H, R1H
	NOT.B	R1H
	MOV.B	R1H, @P1DR
	JSR	@TIM2
	
	ROTR.B	R0L
	ROTR.B	R0H

; 0.3秒のタイマサブルーチン
TIM2:	MOV.W	#D'30, E5
L2:	JSR	@TIM1
	DEC.W	#1, E5
	BNE	L2
	RTS

; 10msのタイマサブルーチン
TIM1:	MOV.L	#D'20000, ER6
L1:	DEC.L	#1, ER6
	NOP
	BNE	L1
	RTS

	.END

DCモータの制御

ドライバICによる回転方向制御

使用したドライブIC:TA7257P
→DCモータの回転を、正転、逆転、ストップ、ブレーキの4パターンを制御できる。

TA7257Pの動作

IN1 IN2 OUT1 OUT2 動作
0 0 Z Z ストップ
0 1 L H 正転
1 0 H L 逆転
1 1 L L ブレーキ

※Zはハイインピーダンス(ってWikipediaに書いてあった…

回路図はサックリと割愛…。

	.CPU 300HA
	.SECTION PROG8, CODE, LOCATE=H'000000

P5DR	.EQU	H'FFFFCA
P5DDR	.EQU	H'FFFFC8
P2DR	.EQU	H'FFFFC3
P2DDR	.EQU	H'FFFFC1
P1PCR	.EQU	H'FFFFD8

	.SECTION ROM, CODE, LOCATE=H'000100

	MOV.L	#H'FFFF00, ER7

	MOV.B	#H'00, R0L
	MOV.B	#H'FF, R0H
	MOV.B	R0L, @P2DDR
	MOV.B	R0H, @P2PCR		; ポート2のプルアップを有効
	MOV.B	R0H, @P5DDR

LOOP:	MOV.B	@P2DR, R0L		; ポート2のDIPスイッチからデータを入力
	NOT.B	R0L			; ONで"1"とするため反転
	BTST	#0, R0L			; DIPスイッチ1をチェック
	BEQ	L1			; スイッチがOFFの場合、L1へ
	BSET	#0, R1L			; ビット0のLED点灯
	BSET	#2, R1L			; モータICの1ピンを"1"
	JMP	@B1
L1:	BCLR	#0, R1L			; ビット0のLED消灯
	BCLR	#2, R1L			; モータICの1ピンを"0"

B1:	BTST	#1, R0L			; DIPスイッチ2をチェック
	BEQ	L2			; スイッチがOFFの場合、L2へ
	BSET	#1, R1L			; ビット1のLED点灯
	BSET	#3, R1L			; モータICの2ピンを"1"
	JMP	@B2
L2:	BCLR	#1, R1L			; ビット1のLED消灯
	BCLR	#3, R1L			; モータICの2ピンを"0"

B2:	MOV.B	R1L, @P5DR		; ポート5へ点灯、回転データを出力

	JMP	@LOOP
	.END

上記のプログラムを実行すると、

  • DIPスイッチ1がOFF、DIPスイッチ2がONの場合、正転
  • DIPスイッチ1がON、DIPスイッチ2がOFFの場合、逆転

となるはず。

テキストには、
「モータの回転軸に触れながら制御を行えば、ストップとブレーキの違いを体感できる」
とありましたが、鈍感な自分には残念ながら…。

PWM機能による速度制御

ITUのPWMモードでDCモータを動作させる。

  1. カウンタクロックの設定(クロックの分周を設定する)
  2. カウンタクリア条件の設定(GRAまたはGRBでクリアする、今回はGRBでクリアとする)
  3. PWMモードの設定
  4. GRAの設定(PWM波形が"1"に立ち上がるタイミングを設定する)
  5. GRBの設定(PWM波形が"0"に立ち上がるタイミングを設定する)
  6. カウント開始

例)出力周波数が10kHz(周期10^-4 = 0.0001s)として、
  クロックが0.625μsで、分周を1/8に設定すると、以下の式でGRAが割り出せる。
  →0.0001s / (0.0625μs * 8) = 0.0001s / 0.0000005s = 200 = C8
  デューティー比50%のPWM波形を発生させるなら、
  GRAには"C8"を設定し、GRBにはC8 * 50% = 100なので"64"を設定する。

と、ここでPWM波形をオシロスコープで確認するプログラムを書くところですが、
だんだん面倒になってきたので割愛…。

PWM機能を使ってDCモータを動作する回路ですが、回路図はやっぱり割愛するとして、
回路の主なパーツは以下の通り。

	.CPU 300HA
	.SECTION PROG10, CODE, LOCATE=H'000000

TSTR	.EQU	H'FFFF60		; タイマスタートレジスタ
TMDR	.EQU	H'FFFF62		; タイマモードレジスタ
TCR0	.EQU	H'FFFF64		; タイマコントロールレジスタ
GRA0	.EQU	H'FFFF6A		; ジェネラルレジスタGRA0
GRB0	.EQU	H'FFFF6CA		; ジェネラルレジスタGRB0
P4DR	.EQU	H'FFFFC7
P4DDR	.EQU	H'FFFFC5
P4PCR	.EQU	H'FFFFDA

	.SECTION ROM, CODE, LOCATE=H'000100

	MOV.L	#H'FFFF00, ER7

	MOV.B	#H'00, R0L
	MOV.B	#H'FF, R0H
	MOV.B	R0L, @P4DDR
	MOV.B	R0H, @P4PCR

	MOV.B	#B'00100011, R0H	; GRAによりクリア、1/8分周データ
	MOV.B	R0H, @TCR0
	BSET	#0, @TMDR		; PMWモード設定

	MOV.W	#H'00C8, E1
	MOV.W	E1, @GRA0		; GRA0設定

LOOP:	BTST	#4, @P4DR		; ポート4のスイッチをチェック
	BEQ	D50			; スイッチがONならD50へ
	MOV.W	#H'00C8, E1		; GRB0データ(デューティー比100%)
	MOV.W	E1, @GRB0		; GRB0設定
	JMP	@D100

D50:	MOV.W	#H'0064, E1		; GRB0データ(デューティー比50%)
	MOV.W	E1, @GRB0		; GRB0設定

D100:	BSET	#0, @TSTR		; ITUカウンタスタート

	JMP	@LOOP

	.END

上記のプログラムを実行すると、
スイッチ1を押すとモーターがゆっくり(デューティー比50%)で回転し、
スイッチ1を離すとモーターが速く(デューティー比100%)で回転するはず。

以上、モーターの制御でした!
あー、まとめるのに何日かかったのだろうか…orz

職業訓練43日目 H8マイコン入門(アセンブラ言語による実習)

LEDの制御

LEDの点滅

LEDを点滅させるにはタイマプログラムを作る必要があり、
タイマには2つの実現方法がある。

  1. 繰り返し処理で時間をかせぐ
  2. 内蔵タイマ(ITU)を利用する

まずは1.の繰り返し処理を利用したコードから。
下記のコードを実行すると、5秒経過するたびにLEDの点灯がローテイトするはず。

	.CPU 300HA
	.SECTION PROG4, CODE, LOCATE=H'000000

P1DR	.EQU	H'FFFFC2
P1DDR	.EQU	H'FFFFC0

	.SECTION ROM, CODE, LOCATE=H'000100

	MOV.L	#H'FFFF00, ER7

	MOV.B	#H'FF, R0L
	MOV.B	R0L, @P1DDR		; ポート1を出力に設定

	MOV.B	#'01111111, R0L		; LED点灯データ

LOOP:	MOV.B	R0L, @P1DR		; ポート1へ点灯データを出力
	JSR	@TIM2			; タイマサブルーチンの呼び出し
	ROTR.B	R0L			; 右に1ビットローテイト
	JMP	@LOOP

TIM2:	MOV.W	#D'500, E5		; 5秒のタイマサブルーチン
L2:	JSR	@TIM1
	DEC.W	#1, E5
	BNE	L2
	RTS

TIM1:	MOV.L	#D'20000, ER6		; 10msのタイマサブルーチン
L1:	DEC.L	#1, ER6			; ER6から1を引く
	NOP				; 何もしない、時間稼ぎ
	BNE	L1			; ER6≠0ならL1にジャンプ
	RTS

	.END

細かい計算はというと、
H8命令セットから、各処理のステート数を調べて、
どれぐらいの時間がかかっているのか算出する。

基準となる1ステート(クロック)の時間は、
CPUの動作周波数が16MHzなので、
1ステート = 0.0625μs
(1s/16000000Hz=0.0000000625s=0.0000625ms=0.0625μs)となる。

各処理のステート数は以下の通り。

ニーモニック ステート数
DEC.L 2
NOP 2
BNE 4

つまり、サブルーチンL1の中では、
0.0625μs * 8ステート = 0.5μs かかっているので、
それを20000回繰り返すことによって、
10msのサブルーチン(TIM1)が作られている。
(0.5μs * 20000 = 10000μs = 10ms)

10msのサブルーチン(TIM1)を500回繰り返して、
5秒ずつ点滅する処理の出来上がり、というわけですね。

では、続きまして「2. 内蔵タイマ(ITU)を利用する」バージョンを。

インテグレーテッドタイマの使用

ITU(Integrated Timer Unit):5チャネル16ビットのタイマ機能

ITU内のカウンタ(TCNT)は、0000H〜FFFFHまでカウントアップした後、
0000Hに戻ってカウントを始める。
FFFFHまでカウントするのにどれくらいかかるかというと、
1クロック = 0.0625μsなので、
0.0625μs * FFFFH =
0.0625μs * 65535 = 4095μs ≒ 約4ms で数え終わる計算になります。

4msでは早過ぎる場合は、クロック周波数を低くするプリスケーラ機能(分周機能)を利用する。
この機能を使えば、周波数を1/2、1/4、1/8に分周して、カウント時間を延ばすことが可能。
1/8に分周したとすると、FFFFHまでカウントするのに、
4ms * 8 = 32ms かかることになる。

プリスケーラの設定含め、ITUの具体的な設定方法は以下を参照。
(タイトルに思いっきり3052Fと書いてありますが…)
http://homepage1.nifty.com/rikiya/program/h83052f_itu1/h83052f_itu1.html

ITUを利用してLEDを点滅させるプログラムはコチラ。

	.CPU 300HA
	.SECTION PROG5, CODE, LOCATE=H'000000

P1DR	.EQU	H'FFFFC2
P1DDR	.EQU	H'FFFFC0
TSTR	.EQU	H'FFFF60		; タイマスタートレジスタ
TCR0	.EQU	H'FFFF64		; タイマコントロールレジスタ0
TSR0	.EQU	H'FFFF67		; タイマステータスレジスタ0

	.SECTION ROM, CODE, LOCATE=H'000100

	MOV.L	#H'FFFF00, ER7

	MOV.B	#H'FF, R0L
	MOV.B	R0L, @P1DDR		; ポート1を出力に設定

	MOV.B	#H'03, R0H		; 1/8分周データ
	MOV.B	R0H, @TCR0		; プリスケーラ設定

	MOV.B	#'11111110, R0L		; LED点灯データ

	BSET	#0, @TSTR		; ITUカウントスタート
L1:	MOV.B	R0L, @P1DR
L2:	BTST	#2, @TSR0		; オーバーフローフラグのチェック
	BEQ	L2			; フラグが"0"の場合、L2へジャンプ
	ROTL.B	R0L
	BCLR	#2, @TSR0		; オーバーフローフラグのクリア
	JMP	@L1

	.END

カウンタのスタート後に、
タイマステータスレジスタ(TSR0)の2ビット目(オーバーフローフラグ:OVF)をチェックして、
OVFが"0"の場合、チェックを続ける(BEQ L2)。
OVFが"1"になったら、LED点灯データを左にローテートし、
オーバーフローフラグを"0"にリセットし、点灯データを出力する。

実行すると、32msごとにLEDの点灯がローテートするはず。