LED点灯

### 概要

ここまで，Arduinoでのブートシーケンスに従いアセンブリ言語でプログラムを記述し，C言語で定義した`main`関数を呼び出すところまで作成した．そして，`gdb`を使って実際に`main`関数が呼び出されるところまで確認した．ここでは，実際にArduino Unoを使用して，組み込み開発のHello Worldである，LEDの点灯をやってみる．

### LEDを点灯させる
LEDを点灯させるには，以下のプログラムを追加します．

```nasm:led.s
lightOn:
        ldi     R16,    0b00100000 ; PB5 is Output, Others are Input
        out     0x04,   R16        ; 0x04 = DDRB
        ldi     R16,    0b00100000 ; PB5 is High (1 = High, 0 = Low)
        out     0x05,   R16        ; 0x05 = PORTB
        ret
```

そして，`main`からこの関数を呼び出します．

```c:main.c
void lightOn(void);

int main(void) {
    lightOn();
    return 0;
}
```
そして，PCにArduino Unoをつないで，`avrdude`で書き込みます(以下の`DEV`は環境に合わせる)

```Makefile
PREFIX = avr-elf
BASEDIR = /usr/local/core/$(PREFIX)
BINDIR = $(BASEDIR)/bin

AS = $(BINDIR)/avr-as
NM = $(BINDIR)/avr-nm
CC = $(BINDIR)/avr-gcc
LD = $(BINDIR)/avr-ld
AVRDUDE = $(BINDIR)/avrdude
CONF = $(BASEDIR)/etc/avrdude.conf
READELF = $(BINDIR)/avr-readelf
OBJDUMP = $(BINDIR)/avr-objdump
OBJCOPY = $(BINDIR)/avr-objcopy
GDB = $(BINDIR)/avr-gdb
DEV=/dev/cu.usbmodem142101 # PORT

(略)
write:
	$(AVRDUDE) -C $(CONF) -c arduino -P $(DEV) -p m328p -b 115200 -D -U flash:w:$(TARGET).hex:i
```
これで

```shell
>make write
```
とすると，Arduinoにプログラムが書き込まれ，LEDが光ります．

### Arduino Unoの仕組み

前述のプログラムでLEDを点灯させることはできるが，なぜこのプログラムで点灯するかを概説していく．私も，この辺のことには疎く，今回勉強したので備忘録を兼ねている．

#### Atmeta328p
Arduino Unoには`atmega328p`というMCUが搭載されている．以下がatmega328pのピンの入出力
[atmega328pの仕様書](https://ww1.microchip.com/downloads/en/DeviceDoc/ATmega48A-PA-88A-PA-168A-PA-328-P-DS-DS40002061A.pdf)

![atmega328p.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/57629/fb5c57e1-81e9-9233-d6e4-32e2ed4bd5cb.png)

Arduino Unoには28個のピンがあり，そのうち23本は入出力用となっている．そして，この23本はB~Dにグループ化されている(PBx, PCx, PDx)．これを見ると，PB0-7, PC0-6, PD0-7まであることがわかる．

そして，Arduinoでの入出力は，以下の手順で行うことなっている．

1. ピンごとに入力用か出力用かを設定
2. 入力，または出力用レジスタに/から，値を書き込む/値を読み込む．

そして，Arduinoでは，入出力を決めるためのレジスタとしてDDRx(x=A,B,C,D), 出力用のレジスタとしてPORTx(x=A,B,C,D)，入力用のレジスタとしてPINx(x=A,B,C,D)が用意されている．以下，PBxの例

<img width="1097" alt="Screen Shot 2019-06-02 at 10.38.41.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/57629/26b6649e-aadb-9bff-989d-b59e121a89f4.png">


例えば，PB5を出力用に設定し，電圧をかけるには，DDRBレジスタのDDB5を1に設定し，PORTB5に1を書き込めば良い，ということになる．

#### Arduino Uno
Arduino Unoは，atmega328pをMCUとして備え，周辺機器が内蔵されたボードである．以下がピンのレイアウト
![arduino_uno_layout.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/57629/0eaf83ff-2dce-a069-c9c9-826bd64ab14b.png)

レイアウト図の見方がよくわからなかったが，これは中央左にatmaga328pが配置され，周辺にピンを指すポートが配置されている．そして，MCUの入出力のピン番号が，周辺のポートの灰色の部分に記載されている．そして，周辺のピンはDIGITAL用とか，ANALOG INという用途ごとにまとめられていて，それぞれ番号がついている(e.g., ANALOGはA0,A1，DIGITALは0から13まで)．
よって，例えばDIGITALピンのの0番を操作したければ，対応するMCUが2番だから，atmega328pのPD0ポートを操作すれば良い，ということになる．

#### 改めて，LED点滅を考える
Arduino Uno内臓のLEDは，13番ポートに接続されている．この意味は，Digitalピンの13番に接続されているという意味である(回路図を見てもよくわからないが，経験的にこう判断した)．
これまでの説明から，13番ポートに対応するのは，atmega328pの`PB5`であることがわわかる．

よって，DDRBレジスタのDDB5を出力用(1)に設定し，PORTB5を1に設定してあげる(電圧をかける)，13番ポートに電流が流れ，LEDが点灯する，という仕組みになっている．

最後は，このDDRBやPORTBを設定するにはどうすればよいか？ということ．組み込み用CPUはメモリーマップドI/Oを採用していることが多く，例外なくatmega328pもこれを採用している．すなわち，特定のレジスタにアクセスするためには，予め決められたメモリアドレスに値を書き込み，読込することによってレジスタにアクセスできる．以下は，atmega328pのデータ用メモリマップである．

<img width="901" alt="Screen Shot 2019-06-02 at 10.23.47.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/57629/1105ab3f-81a8-51e9-ff56-f57e772d09de.png">

これを見ると，I/Oのためのアドレスは64個用意されており，0x0020-0x005Fでアクセスできる事がわかる．ただし，atmega328pの仕様とIN/OUTとにつけられた0x0000-0x001Fに注意する．

<img width="838" alt="Screen Shot 2019-06-02 at 10.29.29.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/57629/f1781bec-2c77-8e67-973b-d6895c8d0c78.png">

実は，I/Oレジスタには2つのアドレスがつけられており，INとかOUTという命令を使うときには，0x0000 - 0x001Fのアドレスを使えと書いてある．そして，DDRB, PORTBの仕様を見ると，2種類のアドレスが書いてあることがわかる.

<img width="1092" alt="Screen Shot 2019-06-02 at 10.38.49.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/57629/2cfde941-eab6-c51d-5630-53a1a72d953d.png">

例えばPORTBでは，Load/Store系の命令を使うときには0x25，IN/OUT命令を使うときには0x05がアサインされていることがわかる．

#### LEDを点灯させる(再掲)
これまでの内容を踏まえて，再度LED点灯プログラムを見てみる．

```nasm:led.s
lightOn:
        ldi     R16,    0b00100000 ; PB5 is Output, Others are Input
        out     0x04,   R16        ; 0x04 = DDRB
        ldi     R16,    0b00100000 ; PB5 is High (1 = High, 0 = Low)
        out     0x05,   R16        ; 0x05 = PORTB
        ret
```

まず，LEDが13番ポートに接続されている情報をもとに，PORTB, DDRBを操作しなければならないことを知る．そして，PORTBのPB5を出力ように設定するためにDDB5を1に設定し，その後PORTB5に1を書き込むことでLEDを点灯させている．

LED点灯プログラムは以下のコマンドで実行することができる

```
>git clone https://github.com/hiro4669/iosv.git
>cd iosv
>git branch led_ver1 origin/led_ver1
>git checkout led_ver1
>cd led
>make
>make write
```