LED点灯


概要

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

LEDを点灯させる

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

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からこの関数を呼び出します.

main.c
void lightOn(void);

int main(void) {
    lightOn();
    return 0;
}

そして,PCにArduino Unoをつないで,avrdudeで書き込みます(以下のDEVは環境に合わせる)

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

これで

>make write

とすると,Arduinoにプログラムが書き込まれ,LEDが光ります.

Arduino Unoの仕組み

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

Atmeta328p

Arduino Unoにはatmega328pというMCUが搭載されている.以下がatmega328pのピンの入出力
atmega328pの仕様書

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の例

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

Arduino Uno

Arduino Unoは,atmega328pをMCUとして備え,周辺機器が内蔵されたボードである.以下がピンのレイアウト

レイアウト図の見方がよくわからなかったが,これは中央左に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のデータ用メモリマップである.

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

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

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

LEDを点灯させる(再掲)

これまでの内容を踏まえて,再度LED点灯プログラムを見てみる.

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