スケッチがなぜそれほど多くのスペースとメモリを占有するのですか?


12

このスケッチをYún向けにコンパイルすると:

int led = 7;

void setup() {                
  pinMode(led, OUTPUT);     
}

void loop() {
  digitalWrite(led, HIGH);
}

私は得る:

Sketchは5,098バイト(17%)のプログラムストレージスペースを使用します。

最大は28,672バイトです。グローバル変数は、153バイト(5%)の動的メモリを使用し、ローカル変数用に2,407バイトを残します。最大は2,560バイトです。

BareMinimumスケッチをコンパイルしても:

void setup() {                
  // setup    
}

void loop() {
  // loop
}

私は得る:

Sketchは4,548バイト(15%)のプログラムストレージスペースを使用します。

最大は28,672バイトです。グローバル変数は151バイト(5%)の動的メモリを使用し、ローカル変数用に2,409バイトを残します。最大は2,560バイトです。

最低限のスケッチが、割り当てられたプログラムストレージスペースの15%を占めるのはなぜですか?そして、なぜ非常にシンプルなスケッチがプログラムのストレージスペースの17%を占めるのですか?Arduinoのウェブサイトによると:

プログラムに多くの文字列を含めると、使いやすくなります。たとえば、次のような宣言char message[] = "I support the Cape Wind project.";は、SRAMに33バイトを入れます(各文字は1バイトに「\ 0」ターミネータを加えます)。

ただし、これらのスケッチのいずれでも宣言された文字列はありません。

私が指定していない他のライブラリ/クラスをインポートまたは使用する可能性があるようです。システムのデフォルトライブラリをインポートするのでしょうか?それとも別のものですか?

回答:


6

YUNはコンボです。パートArduinoおよびパートOpenWRT(Linux)。あなたの質問はArduinoに関するものです。これは実際にはレオナルドに似たATNO32u4であり、UNO(ATmega328p)ではありません。32u4(Leo)は、USB経由で仮想シリアルポートを介して通信します(短い答え:これはサポートする必要があります)。UNOには実際のシリアルポート(別名UART)があります。以下は、AVRプロセッサのさまざまなボードタイプのビルド統計です。

UNOには、USBをシリアルポートのDTRピンに変換する外部チップがあり、接続するとATmega328のリセットピンが切り替わり、ブートローダーが再起動します。対照的に、Leo / YunのUSB to Serialは32u4のファームウェアに実装されています。したがって、LeoまたはYUNの32u4チップをリモートで再起動するには、ロードされるファームウェアが常にUSBクライアント側ドライバーをサポートする必要があります。約4Kを消費します。

USBが不要で、UNOのBareMinimum.inoの場合のように他のライブラリリソースが呼び出されなかった場合、Arduinoライブラリのコアには約466バイトしか必要ありません。

UNO(ATmega328p)でBareMinimum.inoの統計をコンパイルします

Sketch uses 466 bytes (1%) of program storage space. Maximum is 32,256 bytes.
Global variables use 9 bytes (0%) of dynamic memory, leaving 2,039 bytes for local variables. Maximum is 2,048 bytes.

Leonardo(ATmega32u4)でBareMinimum.inoの統計をコンパイルします

Sketch uses 4,554 bytes (15%) of program storage space. Maximum is 28,672 bytes.
Global variables use 151 bytes (5%) of dynamic memory, leaving 2,409 bytes for local variables. Maximum is 2,560 bytes.

Yun(ATmega32u4)でBareMinimum.inoの統計をコンパイルします

Sketch uses 4,548 bytes (15%) of program storage space. Maximum is 28,672 bytes.
Global variables use 151 bytes (5%) of dynamic memory, leaving 2,409 bytes for local variables. Maximum is 2,560 bytes.

7

Arduinoは、多くの標準ライブラリ、割り込みなどでコンパイルします。たとえば、pinMode関数とdigitalWrite関数は、ルックアップテーブルを使用して、実行時にGPIOがデータを書き込むレジスタを見つけます。別の例は、Arduinoが時間を追跡し、デフォルトでいくつかの割り込みを定義し、このすべての機能がいくらかのスペースを必要とすることです。プログラムを拡張すると、フットプリントがわずかにしか変化しないことに気付くでしょう。

私は個人的にコントローラーを「膨張」せずに最小限にプログラムしたいと思っていますが、EE.SEとSOの世界に入るのは簡単です。いくつかの使いやすい機能はすぐに使用できなくなるからです。より小さなフットプリントにコンパイルするpinModeとdigitalWriteの代替ライブラリがいくつかありますが、たとえば静的コンパイルされたピン(led変数にすることはできませんが、定数である)などの他の欠点があります。


それで基本的にそれはあなたに尋ねることなくあらゆる種類の標準ライブラリでコンパイルしますか?きちんとした。
hichris123 14

はい、私は通常「肥大化」と呼びますが、それは本当に使いやすさの問題です。Arduinoは、あまり考えずに機能する低エントリレベルの環境です。さらに必要な場合は、Arduinoで代替ライブラリを使用するか、ベアメタルに対してコンパイルできます。最後は、おそらくArduino.SEの範囲外である
jippie

@mpflagaの回答を参照してください。それほど肥大化はありません。または、最低限の機能のために少なくともコアライブラリで。スケッチと呼ばれない限り、実際には標準ライブラリの多くは含まれていません。むしろ、15%は32u4のUSBサポートによるものです。
mpflaga 14

4

あなたはすでにいくつかの完全に良い答えを持っています。これを投稿するのは、ある日自分がやった統計を共有するためだけです。私は同じような質問を自問しました。同じ機能を実現するために最低限必要なものは何ですか?

以下は、ピン13のLEDを1秒ごとに切り替える最小点滅プログラムの3つのバージョンです。3つのバージョンはすべて、avr-gcc 4.8.2、avr-libc 1.8.0、およびarduino-core 1.0.5(Arduino IDEは使用しません)を使用してUno(USBを使用しない)用にコンパイルされています。

まず、標準のArduinoの方法:

const uint8_t ledPin = 13;

void setup() {
    pinMode(ledPin, OUTPUT);
}

void loop() {
    digitalWrite(ledPin, HIGH);
    delay(1000);
    digitalWrite(ledPin, LOW);
    delay(1000);
}

これは1018バイトにコンパイルされます。両方avr-nmと分解を使用して、そのサイズを個々の機能に分解しました。最大から最小へ:

 148 A ISR(TIMER0_OVF_vect)
 118 A init
 114 A pinMode
 108 A digitalWrite
 104 C vector table
  82 A turnOffPWM
  76 A delay
  70 A micros
  40 U loop
  26 A main
  20 A digital_pin_to_timer_PGM
  20 A digital_pin_to_port_PGM
  20 A digital_pin_to_bit_mask_PGM
  16 C __do_clear_bss
  12 C __init
  10 A port_to_output_PGM
  10 A port_to_mode_PGM
   8 U setup
   8 C .init9 (call main, jmp exit)
   4 C __bad_interrupt
   4 C _exit
-----------------------------------
1018   TOTAL

上記のリストの最初の列はバイト単位のサイズで、2番目の列はコードがArduinoコアライブラリ(A、合計822バイト)、Cランタイム(C、148バイト)、またはユーザー(U 、48バイト)。

このリストからわかるように、最大​​の機能はタイマー0オーバーフロー割り込みを処理するルーチンです。このルーチンは、時間を追跡する責任がある、とによって必要とされmillis()micros()そしてdelay()。2番目に大きい関数はinit()、PWMのハードウェアタイマーを設定し、TIMER0_OVF割り込みを有効にし、USART(ブートローダーで使用されていた)を切断します。この関数と前の関数は両方ともで定義されてい <Arduino directory>/hardware/arduino/cores/arduino/wiring.cます。

次はC + avr-libcバージョンです。

#include <avr/io.h>
#include <util/delay.h>

int main(void)
{
    DDRB |= _BV(PB5);     /* set pin PB5 as output */
    for (;;) {
        PINB = _BV(PB5);  /* toggle PB5 */
        _delay_ms(1000);
    }
}

個々のサイズの内訳:

104 C vector table
 26 U main
 12 C __init
  8 C .init9 (call main, jmp exit)
  4 C __bad_interrupt
  4 C _exit
----------------------------------
158   TOTAL

これは、Cランタイムでは132バイト、インライン関数を含む26バイトのユーザーコード_delay_ms()です。

このプログラムは割り込みを使用しないため、割り込みベクターテーブルは不要であり、通常のユーザーコードを代わりに配置できることに注意してください。次のアセンブリバージョンは、まさにそれを行います。

#include <avr/io.h>
#define io(reg) _SFR_IO_ADDR(reg)

    sbi io(DDRB), 5  ; set PB5 as output
loop:
    sbi io(PINB), 5  ; toggle PB5
    ldi r26, 49      ; delay for 49 * 2^16 * 5 cycles
delay:
    sbiw r24, 1
    sbci r26, 0
    brne delay
    rjmp loop

これは(でavr-gcc -nostdlib)14バイトのみにアセンブルされ、そのほとんどがトグルを遅らせて点滅が見えるようにします。その遅延ループを削除すると、6バイトのプログラムが点滅して速すぎて見られなくなります(2 MHzで)。

    sbi io(DDRB), 5  ; set PB5 as output
loop:
    sbi io(PINB), 5  ; toggle PB5
    rjmp loop

3

私は約記事を書いた理由を、それは1 LEDを点滅さ1000バイトを取るのでしょうか?

簡単な答えは、「2つの LED を点滅させるのに2000バイトは必要ありません!」です。

長い答えは、標準のArduinoライブラリ(必要ない場合は使用する必要がない)には、生活を簡素化する優れた機能があるということです。たとえば、実行時にピンを番号でアドレス指定できます。この場合、ライブラリは(たとえば)ピン8を正しいポートと正しいビット番号に変換します。ポートアクセスをハードコーディングすると、そのオーバーヘッドを節約できます。

それらを使用しない場合でも、標準ライブラリには「ティック」をカウントするコードが含まれているため、現在の「時間」を調べることができます(呼び出すことによりmillis())。これを行うには、いくつかの割り込みサービスルーチンのオーバーヘッドを追加する必要があります。

(Arduino Unoで)このスケッチを簡略化すると、プログラムのメモリ使用量が178バイト(IDE 1.0.6で)になります。

int main ()
  {
  DDRB = bit (5);
  while (true)
    PINB = bit (5);
  }

178バイトはそれほど多くありません。最初の104バイトはハードウェア割り込みベクトルです(26ベクトルの場合はそれぞれ4バイト)。

したがって、ほぼ間違いなく、LEDの点滅に必要なのは74バイトだけです。そして、その74バイトのほとんどは、実際にはグローバルメモリを初期化するためにコンパイラによって生成されたコードです。2つのLEDを点滅させるのに十分なコードを追加する場合:

int main ()
  {
  DDRB = bit (5);  // pin 13
  DDRB |= bit (4);  // pin 12

  while (true)
    {
    PINB = bit (5); // pin 13
    PINB = bit (4); // pin 12
    }
  }

その後、コードサイズは186バイトに増加します。したがって186 - 178 = 8、LEDを点滅させるのに必要なのはバイトだけであると言えます。

したがって、8バイトでLEDを点滅させます。私には非常に効率的に聞こえます。


自宅でこれを試してみたいと思う場合、上記の投稿されたコードが2つのLEDを点滅させている間、それは実際に非常に急速に行われることを指摘する必要があります。実際、2 MHzで点滅します-スクリーンショットを参照してください。チャンネル1(黄色)はピン12、チャンネル2(シアン)はピン13です。

ピン12および13の高速点滅

ご覧のとおり、出力ピンには2 MHzの周波数の方形波があります。コード内のピンの切り替え順序により、ピン13はピン12の前に62.5 ns(1クロックサイクル)状態を変更します。

したがって、私の目よりもはるかに良い目がなければ、まばたき効果は実際には見られません。


面白い追加として、実際には1つのピンを切り替えるのと同じ量のプログラム空間で2つのピンを切り替えることができます。

int main ()
  {
  DDRB = bit (4) | bit (5);  // set pins 12 and 13 to output

  while (true)
    PINB =  bit (4) | bit (5); // toggle pins 12 and 13
  } // end of main

これは178バイトにコンパイルされます。

これにより、より高い頻度が得られます。

ピン12および13の非常に速い点滅

現在、最大2.66 MHzです。


これは非常に理にかなっています。それでは、標準ライブラリはビルド時にヘッダーのみに自動的に含まれますか?そして、どうやってそれらを含められなかったのですか?
hichris123

2
リンカは、使用されていないコードを積極的に取り除きます。呼び出していないことでinit()(通常のようにmain()行います)その後、(持っているファイルwiring.c initそれには)にリンクされていなかった。その結果、割り込みハンドラの処理は(のためにmillis()micros()など)を省略しました。物事の時間を計る必要がない限り、それを省略することはおそらく実用的ではありませんが、実際には、スケッチは何を入れるかに応じてサイズが大きくなります。たとえば、シリアルを使用する場合、プログラムメモリとRAMの両方がヒットします。
ニックギャモン
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.