LPC1114FN28/102入門(書き込み、Lチカ、タイマー割り込み)

### はじめに
LPC1114FN28/102とは、NXPセミコンダクターズのマイコンである。
28ピン、600milのDIPパッケージであり、電源は3.3V程度、レジスタは32ビット長である。
[秋月電子通商](http://akizukidenshi.com/catalog/top.aspx) (通販コード:I-06071)などで購入できる。

詳しい情報は[ユーザーマニュアル](http://www.nxp.com/documents/user_manual/UM10398.pdf)を参考にするとよい。
ただし、このマニュアルにはLPC11CxxやLPC1100XLなどの他のICの情報も載っているので、間違えないように注意が必要である。

### プログラムの書き込み方
初期状態(プロテクトをかけていない状態)では、`PIO0_1`(24ピン)にLOW(≒0V)を入力した状態で起動(リセット)をすることで、
マイコンに内蔵されたプログラムを書き込むためのプログラムを起動できる。
このプログラムを起動したら、[Tera Term](https://osdn.jp/projects/ttssh2/)などを用いてシリアルポート(USBシリアル変換器など)からプログラムの書き込みが可能である。
マイコンのTXD(16ピン)をシリアルポートのRXに、マイコンのRXD(15ピン)をシリアルポートのTXに接続すること。
Tera Termの場合は、「端末」から送信改行コードをCR+LFに、受信改行コードをAUTOに設定し、
「シリアルポート」からデータを8bit、パリティをnone、フロー制御をXon/Xoffに設定するとよい。

なお、Backspaceを用いてコマンドの打ち間違いを訂正することはできない。
打ち間違えたら、ESCを送信(Tera TermではEscキーを押す)することで、コマンドをキャンセルできる。
返ってくるエラーコードの意味は、ユーザーマニュアルの26.5.15 UART ISP Return Codesに載っている。

#### プログラム書き込みモードの初期化
`PIO0_1`(24ピン)にLOW(≒0V)を入力した状態で起動したら、まず`?`を送信する。
すると、マイコンから`Synchronized(改行)`という文字列が送られてくるので、`Synchronized(改行)`を送信する。
さらにマイコンから`OK(改行)`が送られてきたら、マイコンのクロック周波数をkHz単位で送信する。
例えば、デフォルトの内蔵クロックの12MHzの場合、`12000(改行)`を送信する。
マイコンから`OK(改行)`が送られてきたら、初期化は完了である。

#### 書き込むデータをマイコンに送る
Write to RAMコマンド(ユーザーマニュアル26.5.4)を用いると、プログラムのデータをマイコンのRAMに送ることができる。
まず、`W (書き込み先のアドレス) (書き込むバイト数)(改行)`を送信する。
アドレスとバイト数はいずれも十進数であり、4の倍数でなければならない。
マイコンから`0(改行)`が送られてきたら、[uuencode](https://ja.wikipedia.org/wiki/Uuencode)したデータを送信する。
データの各行は61文字(データ45バイト)以下でなければならない。
また、20行送信したか、データの最後まで送信したら、チェックサムとしてその20行以下のブロックのデータの全てのバイトの和を10進数で送信する。
チェックサムが正しければマイコンから`OK(改行)`が送られる。もし`RESEND(改行)`が送られてきたら、そのブロックを再送するべきである。

#### 送ったデータを書き込む
まず、Unlockコマンド(ユーザーマニュアル26.5.1)を用いて、フラッシュメモリへの書き込みを許可させる。
`U 23130(改行)`を送信すれば良い。

次に、フラッシュメモリの書き込もうとする場所の内容を消去する。
4kB以内の短いプログラムの場合は、`P 0 0(改行)`でフラッシュメモリの最初のセクタに書き込む準備をした後、
`E 0 0(改行)`でフラッシュメモリの最初のセクタの内容を消去するとよい。

そして、フラッシュメモリにマイコンのRAMに送ったデータを書き込む。
`P 0 0(改行)`で書き込むセクタの準備をしたあと、Copy RAM to flash(ユーザーマニュアル26.5.7)コマンドで書き込む。
このコマンドは、`C (書き込み先のアドレス) (書き込み元のアドレス) (書き込むサイズ)(改行)`である。
アドレスとサイズは十進数である。書き込み先のアドレスは256の倍数でなければならない
書き込むサイズは256、512、1024、4096のいずれかである。

### プログラムを書く

#### レジスタ、即値
汎用レジスタは、`R0`～`R7`の8個がある。一部の命令からは、`R8`～`R12`の5個も利用可能である。
スタックポインタを表すレジスタは`SP`、サブルーチンに飛んだ時の戻り番地を格納するレジスタは`LR`である。
(ユーザーマニュアル 28.4.1.3 Core registers)

オペランドの即値は、先頭に`#`をつけて書く。

#### 命令セット
プログラムを書くには、命令セットの知識が必要である。
命令のニモニックはユーザーマニュアルの28.5 Instruction setに載っている。
具体的なオペコードの情報は、例えば http://www.coranac.com/files/gba/ で入手できた。
オペコードの情報があれば理論上手動でプログラムが書けるが、
手動で機械語を書いていくのはつらいのでニモニックから機械語に自動変換できると便利である。
そこで、[NASM](http://www.nasm.us/)を用いて自動変換を行えるようにするためのマクロファイルを作成した。
[GitHubから入手可能](https://github.com/mikecat/lpc1114_devel/blob/master/lpc1114_macro.inc)である。

#### プログラムデータの先頭
プログラムデータの先頭は、スタックポインタの初期値、リセット時の実行開始位置、割り込みハンドラの位置を表す4バイト×48要素の「配列」となる。(ユーザーマニュアル 28.4.3.4 Vector table)
この「配列」の0番目は、スタックポインタの初期値である。
1番目は、リセット時の実行開始位置である。
16番目～47番目の32個が、割り込みハンドラの位置である。
リセット時の実行開始位置や割り込みハンドラの位置は、実際の命令の開始位置(命令は全て2バイトまたは4バイトなので、必ず2の倍数である)に1を足した値を書き込まなければならない。
また、マイコンにプログラムを実行させるためには、この「配列」の0番目から7番目までの和が0になるように7番目の値を調整しないといけない。(ユーザーマニュアル26.3.3)

### サンプルプログラム
#### Lチカ (ビジーループ版)
ビジーループを用いて適当な時間ウェイトを入れ、ポート(GPIO)に接続されたLEDを点滅させるプログラムである。
このプログラムでは、出力に`PIO0_3`(26ピン)を用いる。

最初に`PIO0_3`を出力モードに設定する。
そして、実際のデータを出力する。GPIOへのデータの入出力は、アドレスを用いてマスクがかけられる。(ユーザーマニュアル 12.4.1 Write/read data operation)

```:ltika_bysuloop.asm
%include "lpc1114_macro.inc"

start:
; Initial SP value
	dd 0x10000FF0
; reset
	dd reset - start + 1
; NMI
	dd nmi - start + 1
; HardFault
	dd hardfault - start + 1
; reserved
	dd 0
	dd 0
	dd 0
	dd -(0x10000FF0 + (reset - start + 1) + (nmi - start + 1) + (hardfault - start + 1))
	dd 0
	dd 0
	dd 0
; SVCall
	dd svcall - start + 1
; reserved
	dd 0
	dd 0
; PendSV
	dd pendsv - start + 1
; SysTick
	dd systick - start + 1
; IRQn
	dd irq0 - start + 1
	dd irq1 - start + 1
	dd irq2 - start + 1
	dd irq3 - start + 1
	dd irq4 - start + 1
	dd irq5 - start + 1
	dd irq6 - start + 1
	dd irq7 - start + 1
	dd irq8 - start + 1
	dd irq9 - start + 1
	dd irq10 - start + 1
	dd irq11 - start + 1
	dd irq12 - start + 1
	dd irq13 - start + 1
	dd irq14 - start + 1
	dd irq15 - start + 1
	dd irq16 - start + 1
	dd irq17 - start + 1
	dd irq18 - start + 1
	dd irq19 - start + 1
	dd irq20 - start + 1
	dd irq21 - start + 1
	dd irq22 - start + 1
	dd irq23 - start + 1
	dd irq24 - start + 1
	dd irq25 - start + 1
	dd irq26 - start + 1
	dd irq27 - start + 1
	dd irq28 - start + 1
	dd irq29 - start + 1
	dd irq30 - start + 1
	dd irq31 - start + 1
	times (63 - 48 + 1) dd 0

nmi:
hardfault:
svcall:
pendsv:
systick:
irq0:
irq1:
irq2:
irq3:
irq4:
irq5:
irq6:
irq7:
irq8:
irq9:
irq10:
irq11:
irq12:
irq13:
irq14:
irq15:
irq16:
irq17:
irq18:
irq19:
irq20:
irq21:
irq22:
irq23:
irq24:
irq25:
irq26:
irq27:
irq28:
irq29:
irq30:
irq31:
	BX LR

reset:
	; set PIO0_3 as output
	LDR R7, GPIO0DIR
	MOVS R6, #8
	STR R6, [R7, #0]
	; output to PIO0_3
	LDR R7, GPIO0DATA_PIO0_3
mainloop:
	MOVS R6, #8
	STR R6, [R7, #0]
	BL busyloop
	MOVS R6, #0
	STR R6, [R7, #0]
	BL busyloop
	B mainloop

busyloop:
	PUSH {R7}
	LDR R7, busyloop_num
busyloop_loop:
	SUBS R7, #1
	BPL busyloop_loop
	POP {R7}
	BX LR
	align 4
busyloop_num:
	dd 1500000

	align 4
GPIO0DATA_PIO0_3:
	dd 0x50000020
GPIO0DIR:
	dd 0x50008000
```

#### Lチカ(タイマー割り込み版)
タイマーを用いてきれいな時間周期でポートに接続されたLEDを点滅させる。
このプログラムでは、出力に`PIO0_11`(4ピン)を使用している。

この`PIO0_11`は、初期状態では`R`(予約)に割り当てられており、GPIOとして利用できない。
そこで、最初に`IOCON`を利用してGPIOに割り当てる設定を行う。
この設定を行うためには、まず`SYSAHBCLKCTRL`を利用して`IOCON`へクロックを供給するようにしなければならない。
この設定をしたら、ビジーループ版と同様に`PIO0_11`を出力に設定する。

次に、タイマーの設定を行う。
タイマーを利用するので、まず`SYSAHBCLKCTRL`を利用しタイマーにクロックを供給させる。
16ビットのタイマーが2個と32ビットのタイマーが2個利用可能であるが、今回は、16ビットのタイマーを1個利用する。
まずは、タイマーのプリスケーラ、すなわちシステムのクロック何回でカウントが1進むかを設定する。
そして、タイマーのカウント値がいくつになった時にイベントを起こすかをmatch registerで設定し、それらに対応するアクションを設定する。
アクションには、割り込みを起こす、タイマーをリセットして0からカウントさせる、タイマーを停止する、が利用可能である。
さらに、システムの割り込みを有効にする設定もしなければならない。
割り込みを有効にするには、`ISER` (ユーザーマニュアル 28.6.2.2 Interrupt Set-enable Register)を利用する。
このレジスタのビットに1を書き込むと、その位置に対応する割り込みが有効になる。
今回利用する16ビットのタイマー0の割り込み番号は16なので、下から16番目(0-origin)のビットに1を書き込む。
この設定をしたら、タイマーを一応リセットし、動作させる。
今回は、プリスケーラの値を12000に設定することで、12MHzのクロックにおいて1msごとにカウントが1進むようにした。
そして、match registerの設定は、900(ms)で割り込み、1000(ms)で割り込みとリセット、とした。
ここで、1000(ms)というのは、900(ms)の割り込みからさらに1000(ms)ではなく、900(ms)の100(ms)後のことである。

最後に、割り込みハンドラを書く。
900(ms)での割り込みでLEDを点灯させ、1000(ms)での割り込みでLEDを消灯させるようにした。
こうすると、LEDが1秒ごとにピカッ、ピカッ、…という感じで光るようになる。

```:ltika_timer.asm
%include "lpc1114_macro.inc"

start:
; Initial SP value
	dd 0x10000FF0
; reset
	dd reset - start + 1
; NMI
	dd nmi - start + 1
; HardFault
	dd hardfault - start + 1
; reserved
	dd 0
	dd 0
	dd 0
	dd -(0x10000FF0 + (reset - start + 1) + (nmi - start + 1) + (hardfault - start + 1))
	dd 0
	dd 0
	dd 0
; SVCall
	dd svcall - start + 1
; reserved
	dd 0
	dd 0
; PendSV
	dd pendsv - start + 1
; SysTick
	dd systick - start + 1
; IRQn
	dd irq0 - start + 1
	dd irq1 - start + 1
	dd irq2 - start + 1
	dd irq3 - start + 1
	dd irq4 - start + 1
	dd irq5 - start + 1
	dd irq6 - start + 1
	dd irq7 - start + 1
	dd irq8 - start + 1
	dd irq9 - start + 1
	dd irq10 - start + 1
	dd irq11 - start + 1
	dd irq12 - start + 1
	dd irq13 - start + 1
	dd irq14 - start + 1
	dd irq15 - start + 1
	dd irq16 - start + 1
	dd irq17 - start + 1
	dd irq18 - start + 1
	dd irq19 - start + 1
	dd irq20 - start + 1
	dd irq21 - start + 1
	dd irq22 - start + 1
	dd irq23 - start + 1
	dd irq24 - start + 1
	dd irq25 - start + 1
	dd irq26 - start + 1
	dd irq27 - start + 1
	dd irq28 - start + 1
	dd irq29 - start + 1
	dd irq30 - start + 1
	dd irq31 - start + 1
	times (63 - 48 + 1) dd 0

nmi:
hardfault:
svcall:
pendsv:
systick:
irq0:
irq1:
irq2:
irq3:
irq4:
irq5:
irq6:
irq7:
irq8:
irq9:
irq10:
irq11:
irq12:
irq13:
irq14:
irq15:
irq17:
irq18:
irq19:
irq20:
irq21:
irq22:
irq23:
irq24:
irq25:
irq26:
irq27:
irq28:
irq29:
irq30:
irq31:
	BX LR

irq16:
	; interrupt from timer 0
	LDR R0, TMR16B0IR
	LDR R3, GPIO0DATA_PIO0_11
	LDR R1, [R0]
	MOVS R2, #1
	TST  R1, R2
	BEQ irq16_no_mr0
	STR R2, [R0] ; clear MR0 interrupt
	MOVS R2, #1
	LSLS R2, #11
	STR R2, [R3] ; set PIO0_11 to HIGH
irq16_no_mr0:
	MOVS R2, #2
	TST R1, R2
	BEQ irq16_no_mr1
	STR R2, [R0] ; clear MR1 interrupt
	EORS R2, R2
	STR R2, [R3] ; set PIO0_11 to LOW
irq16_no_mr1:
	BX LR

reset:
	; enable clock for IOCON
	LDR R0, SYSAHBCLKCTRL
	LDR R1, [R0]
	MOVS R2, #1
	LSLS R2, #16
	ORRS R1, R2
	STR R1, [R0]

	; disable pull-up register on PIO0_11 and select function PIO0_11
	LDR R0, IOCON_R_PIO0_11
	MOVS R1, #0xC1
	STR R1, [R0]
	; set PIO0_11 to output, other PIO0_n to input
	LDR R0, GPIO0DIR
	MOVS R1, #1
	LSLS R1, #11
	STR R1, [R0]
	;set PIO0_11 to LOW
	LDR R0, GPIO0DATA_PIO0_11
	EORS R1, R1
	STR R1, [R0]

	; disable clock for IOCON and enable clock for timer 0
	LDR R0, SYSAHBCLKCTRL
	LDR R1, [R0]
	BICS R1, R2
	MOVS R2, #0x80
	ORRS R1, R2
	STR R1, [R0]

	; set timer prescaler to 12000 = 0x2EE0
	LDR R0, TMR16B0PR
	MOVS R1, #0x2E
	LSLS R1, #8
	ADDS R1, #0xE0
	STR R1, [R0]
	; set match register 0 to 900 = 0x384
	LDR R0, TMR16B0MR0
	MOVS R1, #0x03
	LSLS R1, #8
	ADDS R1, #0x84
	STR R1, [R0]
	; set match register 1 to 1000 = 0x3E8
	LDR R0, TMR16B0MR1
	MOVS R1, #0x03
	LSLS R1, #8
	ADDS R1, #0xE8
	STR R1, [R0]
	; reset the timer
	LDR R0, TMR16B0TCR
	MOVS R1, #2
	STR R1, [R0]
	; interrupt on MR0, interrupt and reset on MR1
	LDR R0, TMR16B0MCR
	MOVS R1, #0x19
	STR R1, [R0]
	; enable IRQ16 interrupt
	LDR R0, ISER
	MOVS R1, #1
	LSLS R1, #16
	STR R1, [R0]
	; start the timer
	LDR R0, TMR16B0TCR
	MOVS R1, #1
	STR R1, [R0]

	; endless loop
mainloop:
	B mainloop

	align 4
SYSAHBCLKCTRL:
	dd 0x40048080
IOCON_R_PIO0_11:
	dd 0x40044074
GPIO0DIR:
	dd 0x50008000
GPIO0DATA_PIO0_11:
	dd 0x50002000
ISER:
	dd 0xE000E100
TMR16B0IR:
	dd 0x4000C000
TMR16B0TCR:
	dd 0x4000C004
TMR16B0PR:
	dd 0x4000C00C
TMR16B0MCR:
	dd 0x4000C014
TMR16B0MR0:
	dd 0x4000C018
TMR16B0MR1:
	dd 0x4000C01C
```

#### 使用した命令の概要
詳細はユーザーマニュアルの28.5 Instruction setなどを参考にしてほしい。

##### 分岐
* `B ラベル`  
  指定した位置に無条件ジャンプする。
* `BL ラベル`  
  戻り先を`LR`レジスタに書き込んだ後、指定した位置(サブルーチン)にジャンプする。
* `BX レジスタ`  
  レジスタで指定された場所にジャンプする。`BX LR`でサブルーチンから戻る。
* `BEQ ラベル`  
  直前の比較結果が「等しい/ゼロ」のとき、指定した位置にジャンプする。
* `BLO ラベル`  
  直前の比較結果が「引かれる数の方が符号なしで小さい」のとき、指定した位置にジャンプする。
* `BHI ラベル`  
  直前の比較結果が「引かれる数の方が符号なしで大きい」のとき、指定した位置にジャンプする。
* `BPL ラベル`  
  直前の計算結果が「ゼロ以上」のとき、指定した位置にジャンプする。

##### メモリ等へのアクセス
* `LDR レジスタ, [レジスタ, オフセット]`  
  後ろのレジスタで指定したアドレスの内容を前のレジスタに読み込む。オフセットが0の場合は省略できる。
* `LDR レジスタ, ラベル`  
  ラベルで指定したアドレスの内容をレジスタに読み込む。
* `STR レジスタ, [レジスタ, オフセット]`  
  前のレジスタの内容を後ろのレジスタで指定したアドレスに書き込む。オフセットが0の場合は省略できる。
* `PUSH {レジスタリスト}`  
  指定したレジスタの内容をスタックに積む。
* `POP {レジスタリスト}`  
  スタックからデータを取り出し、指定したレジスタに書き込む。

##### 演算
* `MOVS レジスタ, 即値`  
  即値をレジスタに書き込む。
* `ADDS レジスタ, 即値`  
  即値をレジスタに加算する。
* `SUBS レジスタ, 即値`  
  即値をレジスタから減算する。
* `CMP レジスタ, 即値`  
  比較のために即値をレジスタから減算し、結果を格納しない。
* `TST レジスタ, レジスタ`  
  比較のためにレジスタ同士の論理積を計算し、結果を格納しない。
* `LSLS レジスタ, 即値`  
  レジスタの値を即値ビット左シフトする。
* `LSRS レジスタ, 即値`  
  レジスタの値を即値ビット右シフトする。
* `EORS レジスタ, レジスタ`  
  レジスタ同士の排他的論理和を計算し、左のレジスタに格納する。
* `ORRS レジスタ, レジスタ`  
  レジスタ同士の論理和を計算し、左のレジスタに格納する。
* `BICS レジスタ, レジスタ`  
  右のレジスタをビット反転したものと左のレジスタの論理積を計算し、左のレジスタに格納する。

### あれを知りたい時、ユーザーマニュアルのどこを見るべきか

* ハードウェアのピンアサイン : 10.4 Pin configuration (LPC1112/14)
* 各周辺機器に対応する割り込み番号(IRQ) : 6.4 Interrupt sources
* ポートの設定(機能、プルアップするかなど) : Chapter 7: LPC1100/LPC1100C/LPC1100L series: I/O configuration (IOCONFIG)
* GPIO(入力か出力かの設定、ポートの読み書き) : Chapter 12: LPC111x/LPC11Cxx General Purpose I/O (GPIO)
* 16ビットタイマーの使い方 : Chapter 18: LPC1100/LPC1100C/LPC1100L series: 16-bit counter/timer CT16B0/1
* プログラムの書き込み方 : Chapter 26: LPC111x/LPC11Cxx Flash programming firmware
* プログラムデータの先頭の情報の書き方 : 28.4.3.4 Vector table
* 命令セット : 28.5 Instruction set
* 割り込みの有効化、無効化、優先順位の設定 : 28.6.2 Nested Vectored Interrupt Controller