avr-libc
2.0.0
Standard C library for AVR-GCC
|
AVR Libc Home Page |
AVR Libc Development Pages |
||||
Main Page |
User Manual |
Library Reference |
FAQ |
Example Projects |
ここまでにGNUツールをシステムにインストールし、構築して設定なければなりません。この章では、AVRプロジェクトでのGNUツールの簡単な使用例を紹介しています。この章を読んだあとに、ツールがどのように使われるか、Makefile
がどのように設定されるかの感触がつかめるでしょう。
このプロジェクトは、2秒ごとにLEDをパルス幅変調(PWM
)で徐々に点滅させます。AT90S2313プロセッサをコントローラとして使用します。このデモンストレーション用回路を回路図に示します。もし開発キットを持っていれば、このプロジェクトのために回路を作るよりも、開発キットを使えます。
ソースコードはdemo.cにあります。この例のために、ソースコードdemo.c
を作成してください。コードの重要な点は次の通りです。
iocompat.h
ファイルは、プリプロセッサの#ifdef
命令文を用いてこれら全ての違いを抽象化しようとしています。そして、実際のプログラムでは、共通のシンボル名のセットを用いて操作するこができます。このファイルで定義されているマクロは次の通りです。OCR
OCRレジスタ名(通常OCR1やOCR1A)で、PWM制御に使用DDROC
OC出力のためのDDR(データ方向レジスタ)の名前OC1
ポート内のOC1[A]のピン番号TIMER1_TOP
PWMに用いるタイマーのTOP値(10-bit PWMは1023、8-bit PWMしか扱えないデバイスは255)TIMER1_PWM_INIT
10ビット(または8ビット)の位相と周波数が正しいPWMモードに適切に設定する1A制御レジスタの初期化ビットTIMER1_CLOCKSOURCE
PWMタイマーをスタートするための制御レジスタのクロックビットのセットで、通常は10ビットPWMではCPUクロックでタイマーを動作させ、8ビットPWMでは、分周波されたクロックでタイマーを動かす。PWM
が10ビットモードで使われているため、現在の値を記憶するために16ビットの変数が必要になります。PWM
の新しい値を決定します。PWM
レジスタにロードします。割込み処理の中にいるため、レジスタに16ビットの代入を使うことは安全です。割込みの外であって、割込み処理がこのレジスタ(または、TEMP
)を使用する別のレジスタ)にアクセスすることができたという可能性があるならば、代入は割込みを使用不可能にして実行されなければなりません。FAQの項目を参考にしてください。PWM
の初期化と割込みを許可します。sleep_mode()
は、次の割込みまでプロセッサを休止状態にし、電力を削減します。もちろん、LEDを動かしているので、それは顕著ではないでしょう。ただ、基本原則のデモをここでは述べているだけです。最初にしなければならないことは、ソースをコンパイルすることです。コンパイル時に、コンパイラに-mmcu
オプションを指定することでプロセッサのタイプを知らせる必要があります。-Os
オプションは、コンパイラに効果的な空間使用(コードの実行速度は我慢できる範囲で遅くなる)でコードを最適化するように指示します。 -g
は、デバック情報の埋め込みに使います。デバッグ情報は逆アセンブルに有用で、.hexファイル上には結果として残らないため、私は通常使っています。最後に、
-c
は、コンパイルして終る、リンクしないことをコンパイラに指示します。このデモは、コンパイルして1つにリンクすることは、十分小さいステップです。しかし、現実のプロジェクトでは、モジュールがいくつもあり、一般的にいくつもコンパイルして分割されてプロジェクトに組み入れていて、1つにリンクされる必要があります。
$ avr-gcc -g -Os -mmcu=atmega8 -c demo.c
コンパイラはdemo.o
ファイルを作ります。次に、demo.elf
と呼ばれるバイナリファイルにリンクします。
$ avr-gcc -g -mmcu=atmega8 -o demo.elf demo.o
リンクの時にMCUタイプを指定することが重要です。コンパイラは、スタートアップファイルと一緒にリンクされるランタイムライブラリを選択するため、-mmcu
オプションを使用します。このオプションを指定しないと、確実にあなたが望んでいない、コンパイラが8515プロセッサ環境に初期設定されます。
現在、バイナリファイルができあがりました。生成したオブジェクトファイルを操作(または、プロセッサに書き込む)のに役立つツール、GNU Binutilsスイートがあります。1つのツールにavr-objdump
があります。これは、オブジェクトファイルから情報を取り出し、役立つ情報を表示します。このコマンド自体を入力すると、オプションを一覧表示します。
たとえば、アプリケーションのサイズの雰囲気をつかむために、-h
オプションを使うことができます。このオプションの出力は、各セクションでどれだけのスペースを使用しているかを表示します。(.stabや
.stabstrセクションは、デバッグ情報を持っていて、ROMファイルには含まれません。)
さらに役に立つオプションは-S
です。このオプションはバイナリ・ファイルを逆アセンブリして、出力でソースコードを点在させてくれます!この方法は、ライブラリやベクターテーブルの内容からの処理を含んでいるため、コンパイラで-S
を付けるより私は良い方法と考えています。また、全ての"フィックスアップ"が満たされています。言い換えると、このオプションによって生成されるリストは、プロセッサが実際に動作するコードを反映しています。
$ avr-objdump -h -S demo.elf > demo.lst
次にdemo.lst
ファイルに保存された内容を示します。
demo.elf: file format elf32-avr Sections: Idx Name Size VMA LMA File off Algn 0 .text 000000d0 00000000 00000000 00000094 2**1 CONTENTS, ALLOC, LOAD, READONLY, CODE 1 .data 00000000 00800060 000000d0 00000164 2**0 CONTENTS, ALLOC, LOAD, DATA 2 .bss 00000003 00800060 00800060 00000164 2**0 ALLOC 3 .stab 0000075c 00000000 00000000 00000164 2**2 CONTENTS, READONLY, DEBUGGING 4 .stabstr 00000d21 00000000 00000000 000008c0 2**0 CONTENTS, READONLY, DEBUGGING 5 .comment 00000011 00000000 00000000 000015e1 2**0 CONTENTS, READONLY Disassembly of section .text: 00000000 <__ctors_end>: 0: 20 e0 ldi r18, 0x00 ; 0 2: a0 e6 ldi r26, 0x60 ; 96 4: b0 e0 ldi r27, 0x00 ; 0 6: 01 c0 rjmp .+2 ; 0xa <.do_clear_bss_start> 00000008 <.do_clear_bss_loop>: 8: 1d 92 st X+, r1 0000000a <.do_clear_bss_start>: a: a3 36 cpi r26, 0x63 ; 99 c: b2 07 cpc r27, r18 e: e1 f7 brne .-8 ; 0x8 <.do_clear_bss_loop> 00000010 <__vector_8>: #include "iocompat.h" /* Note [1] */ enum { UP, DOWN }; ISR (TIMER1_OVF_vect) /* Note [2] */ { 10: 1f 92 push r1 12: 0f 92 push r0 14: 0f b6 in r0, 0x3f ; 63 16: 0f 92 push r0 18: 11 24 eor r1, r1 1a: 2f 93 push r18 1c: 8f 93 push r24 1e: 9f 93 push r25 static uint16_t pwm; /* Note [3] */ static uint8_t direction; switch (direction) /* Note [4] */ 20: 80 91 62 00 lds r24, 0x0062 24: 88 23 and r24, r24 26: f1 f0 breq .+60 ; 0x64 <__SREG__+0x25> 28: 81 30 cpi r24, 0x01 ; 1 2a: 71 f4 brne .+28 ; 0x48 <__SREG__+0x9> if (++pwm == TIMER1_TOP) direction = DOWN; break; case DOWN: if (--pwm == 0) 2c: 80 91 60 00 lds r24, 0x0060 30: 90 91 61 00 lds r25, 0x0061 34: 01 97 sbiw r24, 0x01 ; 1 36: 90 93 61 00 sts 0x0061, r25 3a: 80 93 60 00 sts 0x0060, r24 3e: 00 97 sbiw r24, 0x00 ; 0 40: 39 f4 brne .+14 ; 0x50 <__SREG__+0x11> direction = UP; 42: 10 92 62 00 sts 0x0062, r1 46: 04 c0 rjmp .+8 ; 0x50 <__SREG__+0x11> 48: 80 91 60 00 lds r24, 0x0060 4c: 90 91 61 00 lds r25, 0x0061 break; } OCR = pwm; /* Note [5] */ 50: 9b bd out 0x2b, r25 ; 43 52: 8a bd out 0x2a, r24 ; 42 } 54: 9f 91 pop r25 56: 8f 91 pop r24 58: 2f 91 pop r18 5a: 0f 90 pop r0 5c: 0f be out 0x3f, r0 ; 63 5e: 0f 90 pop r0 60: 1f 90 pop r1 62: 18 95 reti static uint8_t direction; switch (direction) /* Note [4] */ { case UP: if (++pwm == TIMER1_TOP) 64: 80 91 60 00 lds r24, 0x0060 68: 90 91 61 00 lds r25, 0x0061 6c: 01 96 adiw r24, 0x01 ; 1 6e: 90 93 61 00 sts 0x0061, r25 72: 80 93 60 00 sts 0x0060, r24 76: 8f 3f cpi r24, 0xFF ; 255 78: 23 e0 ldi r18, 0x03 ; 3 7a: 92 07 cpc r25, r18 7c: 49 f7 brne .-46 ; 0x50 <__SREG__+0x11> direction = DOWN; 7e: 21 e0 ldi r18, 0x01 ; 1 80: 20 93 62 00 sts 0x0062, r18 84: e5 cf rjmp .-54 ; 0x50 <__SREG__+0x11> 00000086 <ioinit>: void ioinit (void) /* Note [6] */ { /* Timer 1 is 10-bit PWM (8-bit PWM on some ATtinys). */ TCCR1A = TIMER1_PWM_INIT; 86: 83 e8 ldi r24, 0x83 ; 131 88: 8f bd out 0x2f, r24 ; 47 * Start timer 1. * * NB: TCCR1A and TCCR1B could actually be the same register, so * take care to not clobber it. */ TCCR1B |= TIMER1_CLOCKSOURCE; 8a: 8e b5 in r24, 0x2e ; 46 8c: 81 60 ori r24, 0x01 ; 1 8e: 8e bd out 0x2e, r24 ; 46 #if defined(TIMER1_SETUP_HOOK) TIMER1_SETUP_HOOK(); #endif /* Set PWM value to 0. */ OCR = 0; 90: 1b bc out 0x2b, r1 ; 43 92: 1a bc out 0x2a, r1 ; 42 /* Enable OC1 as output. */ DDROC = _BV (OC1); 94: 82 e0 ldi r24, 0x02 ; 2 96: 87 bb out 0x17, r24 ; 23 /* Enable timer 1 overflow interrupt. */ TIMSK = _BV (TOIE1); 98: 84 e0 ldi r24, 0x04 ; 4 9a: 89 bf out 0x39, r24 ; 57 sei (); 9c: 78 94 sei 9e: 08 95 ret 000000a0 <main>: void ioinit (void) /* Note [6] */ { /* Timer 1 is 10-bit PWM (8-bit PWM on some ATtinys). */ TCCR1A = TIMER1_PWM_INIT; a0: 83 e8 ldi r24, 0x83 ; 131 a2: 8f bd out 0x2f, r24 ; 47 * Start timer 1. * * NB: TCCR1A and TCCR1B could actually be the same register, so * take care to not clobber it. */ TCCR1B |= TIMER1_CLOCKSOURCE; a4: 8e b5 in r24, 0x2e ; 46 a6: 81 60 ori r24, 0x01 ; 1 a8: 8e bd out 0x2e, r24 ; 46 #if defined(TIMER1_SETUP_HOOK) TIMER1_SETUP_HOOK(); #endif /* Set PWM value to 0. */ OCR = 0; aa: 1b bc out 0x2b, r1 ; 43 ac: 1a bc out 0x2a, r1 ; 42 /* Enable OC1 as output. */ DDROC = _BV (OC1); ae: 82 e0 ldi r24, 0x02 ; 2 b0: 87 bb out 0x17, r24 ; 23 /* Enable timer 1 overflow interrupt. */ TIMSK = _BV (TOIE1); b2: 84 e0 ldi r24, 0x04 ; 4 b4: 89 bf out 0x39, r24 ; 57 sei (); b6: 78 94 sei ioinit (); /* loop forever, the interrupts are doing the rest */ for (;;) /* Note [7] */ sleep_mode(); b8: 85 b7 in r24, 0x35 ; 53 ba: 80 68 ori r24, 0x80 ; 128 bc: 85 bf out 0x35, r24 ; 53 be: 88 95 sleep c0: 85 b7 in r24, 0x35 ; 53 c2: 8f 77 andi r24, 0x7F ; 127 c4: 85 bf out 0x35, r24 ; 53 c6: f8 cf rjmp .-16 ; 0xb8 <main+0x18> 000000c8 <exit>: c8: f8 94 cli ca: 00 c0 rjmp .+0 ; 0xcc <_exit> 000000cc <_exit>: cc: f8 94 cli 000000ce <__stop_program>: ce: ff cf rjmp .-2 ; 0xce <__stop_program>
avr-objdump
はとても役に立ちますが、時にリンカによって生成されるリンクについての情報を見なければならないことがあります。マップファイルはこの情報を含みます。マップファイルは、あなたのコードとデータのサイズを見るのに役立ちます。それも、モジュールがどこにロードされるか、ライブラリからのどもモジュールが読み込まれたか示してくれます。これは、アプリケーションも別の見方になります。マップファイルえるために、通常はリンクコマンドに-Wl,-Map,demo.map
を追加します。demo.map
を生成するために、次のコマンドを使って再リンクします。(一部を下に示します。)
$ avr-gcc -g -mmcu=atmega8 -Wl,-Map,demo.map -o demo.elf demo.o
demo.map
ファイルには、いくつかの興味深いポイントがあります。
The .text セグメント(プログラム命令を格納)は、0x0の場所から始まります。
.textセグメントの最後のアドレスは、
0x114
(_etext
のことを示す)で、命令はFLASHを276バイト使用しています。
.dataセグメント(静的変数の初期値を格納)は、0x60から開始しています。これは、ATmega8プロセッサのレジスタバンクの後の最初のアドレスです。
.dataセグメントの次に使用できるアドレスは
0x60
ですから、このアプリケーションには初期化されたデータがありません。
.bssセグメント(初期化されていないデータを格納)は、
0x60
の場所から開始しています。
.bssセグメントの次に使用できるアドレスは、
0x63
ですから、このアプリケーションは、初期化されないデータで3バイト使用します。
.eepromセグメント(EEPROMの変数を格納)は、0x0から開始します。
.eepromセグメントの次に使用できるアドレスは0x0ですから、EEPROMの変数はありません。
アプリケーションのバイナリファイルがありますが、どうやってプロセッサに入れますか?(全てでないにしても)多くのプログラマー・書込み機は、実行可能なGNU実行ファイルを入力ファイルとして認めないため、もう少し処理する必要があります。次のステップは、バイナリの一部を抽出して、.hexファイルに情報を保存することです。これをするGNUユーティリティは、
avr-objcopy
と呼ばれます。
ROMコンテンツは、プロジェクトのバイナリから取り出すことができ、次のコマンドを使って、demo.hexファイルに入れることができます。
$ avr-objcopy -j .text -j .data -O ihex demo.elf demo.hex
結果のdemo.hex
ファイルのには次の内容が含まれます。
:1000000020E0A0E6B0E001C01D92A336B207E1F700 :100010001F920F920FB60F9211242F938F939F93DD :10002000809162008823F1F0813071F4809160004A :100030009091610001979093610080936000009718 :1000400039F41092620004C08091600090916100C8 :100050009BBD8ABD9F918F912F910F900FBE0F90E6 :100060001F90189580916000909161000196909387 :100070006100809360008F3F23E0920749F721E001 :1000800020936200E5CF83E88FBD8EB581608EBD81 :100090001BBC1ABC82E087BB84E089BF78940895BA :1000A00083E88FBD8EB581608EBD1BBC1ABC82E01B :1000B00087BB84E089BF789485B7806885BF8895C1 :1000C00085B78F7785BFF8CFF89400C0F894FFCF3D :00000001FF
-j
オプションにより、.textと
.dataセグメントから情報をえることを指示します。EEPROMセグメントを指定すると、EEPROMをプログラムするのに使用する
.hexファイルを生成することができます。
$ avr-objcopy -j .eeprom --change-section-lma .eeprom=0 -O ihex demo.elf demo_eeprom.hex
ここでは、demo_eeprom.hex
ファイルは何も書かれるものはなく、空のファイルになります。
GNU binutilsのバージョン2.17からは、avr-objcopy
は、.eepromセクションが空の入力であると空のEEPROMファイルを生成せずに直ちに中止されます。これを捕らえることでMakefileにエラー通知し、空のファイルが生成されていないことをメッセージで表示させます。
何度もこれらのコマンドを入力しなくても、makeファイルに全てを設定することができます。デモプロジェクトの構築にmake
を使用しており、Makefile
と呼ばれるつぎのファイルに保存しています。
Makefile
は、GNUバージョンのmake
でのみ使用できます。