arduino でヒープを使いたくなったので調べてみた結果をメモっておきます.

ヒープを使うには

そもそも malloc 的な関数は使えるのかね? と思ってしらべたらありました.

http://www11.ocn.ne.jp/~akibow/avr-libc-user-manual-1.4.3/malloc.html を読むと以下のような構造になっていることがわかりました.

以下のスケッチを使って確認してみました.(arduinoarduino-0013 with atmega328 の状態です)

#include <stdlib.h>

void setup() {
  Serial.begin(9600);
  Serial.println((int)__malloc_heap_start, HEX);
  Serial.println((int)__malloc_heap_end, HEX);
  Serial.println((int)__malloc_margin, HEX);
  Serial.println((int)SP, HEX);
}
void loop() {}

出力結果はこんなんなりました.

1A2
0
20
8F9

ヒープの構造は http://www11.ocn.ne.jp/~akibow/avr-libc-user-manual-1.4.3/malloc.html にある以下の図の通りになってるので,
f:id:clayfish:20090228234524p:image

このプログラムの場合, だいたい 1800byte 強*1ヒープとして使えることがわかりました.*2

メモリ割り当て状況をしらべるには

avr-objdump と avr-nm を使ってしらべてみます.

arduino IDE が以下にインストールされている状況とします.

c:\arduino-0013\hardware\tools\avr\bin

まずはパスを通します.

set PATH=%PATH%;c:\arduino-0013\hardware\tools\avr\bin

スケッチを保存してあるフォルダ*3へ移動してセクション情報を出力します.

cd c:\sketch_090218a/applet
avr-objdump -h sketch_090228a.elf > list.lst

出力されるファイルはこんな感じになりました.


sketch_090228a.elf:     file format elf32-avr

Sections:
Idx Name          Size      VMA       LMA       File off  Algn
  0 .data         0000000c  00800100  00000730  000007a4  2**0
                  CONTENTS, ALLOC, LOAD, DATA
  1 .text         00000730  00000000  00000000  00000074  2**1
                  CONTENTS, ALLOC, LOAD, READONLY, CODE
  2 .bss          00000096  0080010c  0000073c  000007b0  2**0
                  ALLOC
  3 .debug_aranges 000002e8  00000000  00000000  000007b0  2**0
                  CONTENTS, READONLY, DEBUGGING
  4 .debug_pubnames 000005e8  00000000  00000000  00000a98  2**0
                  CONTENTS, READONLY, DEBUGGING
  5 .debug_info   00001e7c  00000000  00000000  00001080  2**0
                  CONTENTS, READONLY, DEBUGGING
  6 .debug_abbrev 00000bb4  00000000  00000000  00002efc  2**0
                  CONTENTS, READONLY, DEBUGGING
  7 .debug_line   000018c5  00000000  00000000  00003ab0  2**0
                  CONTENTS, READONLY, DEBUGGING
  8 .debug_frame  00000470  00000000  00000000  00005378  2**2
                  CONTENTS, READONLY, DEBUGGING
  9 .debug_str    000009e4  00000000  00000000  000057e8  2**0
                  CONTENTS, READONLY, DEBUGGING
 10 .debug_loc    000010bc  00000000  00000000  000061cc  2**0
                  CONTENTS, READONLY, DEBUGGING
 11 .debug_ranges 00000290  00000000  00000000  00007288  2**0
                  CONTENTS, READONLY, DEBUGGING

http://www11.ocn.ne.jp/~akibow/avr-libc-user-manual-1.4.3/mem_sections.html をしらべたところによると, セクションには以下の制限があるぽいです.*4

  • .text (コード)は上位 16bit を 0x0000 にしなければいけない.
  • .data, .bss (データ)は上位 16bit を 0x0080 にしなければいけない.

avr-objdump の出力結果のよみかたはこんな感じです.

セクション 意味
.text アドレス 0x0000 から 1840 バイト割り当て済み
.data アドレス 0x0100 から 12 バイト割り当て済み
.bss アドレス 0x010C から 150 バイト割り当て済み

atmega328 の データシート の 5.3 SRAM Data Memory にある以下の図的にもあってるようです.
f:id:clayfish:20090228234500p:image

つづきまして、avr-nm をつかってヒープ関連のシンボルを見てみたいと思います.

こんな感じでシンボル一覧を出力すると

avr-nm -n -C sketch\_090228a.elf \> syms.lst

こんな結果がでてきます

00000000 W __heap_end
00000000 a __tmp_reg__
00000000 a __tmp_reg__
00000000 a __tmp_reg__
00000000 a __tmp_reg__
00000000 a __tmp_reg__
00000000 a __tmp_reg__
00000000 a __tmp_reg__
00000000 a __tmp_reg__

〜〜〜長いので中略〜〜〜

00000730 T _etext
0000073c A __data_load_end
000008ff W __stack
00800100 D __data_start
00800100 D __malloc_margin
00800102 D __malloc_heap_start
00800104 D __malloc_heap_end
00800106 V vtable for HardwareSerial
0080010c B Serial
0080010c B __bss_start
0080010c D __data_end
0080010c D _edata
0080010e b intFunc
00800112 B timer0_overflow_count
00800116 B timer0_clock_cycles
0080011a B timer0_millis
0080011e B rx_buffer_head
00800120 B rx_buffer_tail
00800122 B rx_buffer
008001a2 B __bss_end
008001a2 N __heap_start
008001a2 N _end
00810000 N __eeprom_end

上記 avr-nm の結果とスケッチの実行結果を比較してみると,

  • *__malloc_heap_start == 0x01A2 かつ
  • __heap_start == 0x01A2 なので
  • *__malloc_heap_start == __heap_start

と正しいようです.

また, http://www11.ocn.ne.jp/~akibow/avr-libc-user-manual-1.4.3/malloc.html に記載があるとおり, __heap_start は .bss セクションの末尾に配置されていることが見てとれます. なので .data, .bss のサイズによって __heap_start のアドレスが決定することになるようです.

まとめ

なんだかとりとめない内容で申し訳ありませんが, まとめると以下 3 点となります.

  • ヒープを使うには普通に malloc をつかう(free, calloc, realloc もある)
  • 最大ヒープ量は SRAM 容量, スタック使用量, __malloc_margin, .data & .bss サイズに依存する
  • avr-objdump, avr-nm をつかってスケッチのメモリ使用状況をしらべることができる.

参照

*1:0x8F9 - 0x20 - 0x1A2 = 0x725

*2:ヒープのリスト管理領域含む

*3:ここでは c:\sketch_090228a にあるものとして説明しています

*4:ここを調べたところ, ハーバードアーキテクチャだからという理由が記述されていますが追い切れていなくて詳細不明です.