概要
ここまで,Arduinoでのブートシーケンスに従いアセンブリ言語でプログラムを記述し,C言語で定義したmain
関数を呼び出すところまで作成した.そして,gdb
を使って実際にmain
関数が呼び出されるところまで確認した.ここでは,実際にArduino Unoを使用して,組み込み開発のHello Worldである,LEDの点灯をやってみる.
LEDを点灯させる
LEDを点灯させるには,以下のプログラムを追加します.
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
からこの関数を呼び出します.
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での入出力は,以下の手順で行うことなっている.
- ピンごとに入力用か出力用かを設定
- 入力,または出力用レジスタに/から,値を書き込む/値を読み込む.
そして,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点灯プログラムを見てみる.
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