Warum nehmen Skizzen so viel Platz und Gedächtnis in Anspruch?


12

Wenn ich diese Skizze für den Yún zusammenstelle:

int led = 7;

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

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

Ich bekomme:

Sketch belegt 5.098 Byte (17%) des Programmspeicherplatzes.

Das Maximum liegt bei 28.672 Bytes. Globale Variablen belegen 153 Bytes (5%) des dynamischen Speichers, während für lokale Variablen 2.407 Bytes verbleiben. Maximal sind 2.560 Bytes möglich.

Auch wenn ich die BareMinimum-Skizze kompiliere:

void setup() {                
  // setup    
}

void loop() {
  // loop
}

Ich bekomme:

Sketch belegt 4.548 Byte (15%) des Programmspeicherplatzes.

Das Maximum liegt bei 28.672 Bytes. Globale Variablen belegen 151 Byte (5%) des dynamischen Speichers, während für lokale Variablen 2.409 Byte übrig bleiben. Maximal sind 2.560 Bytes möglich.

Warum belegt eine einfache Skizze 15% des zugewiesenen Programmspeicherplatzes? Und warum belegt eine sehr einfache Skizze 17% des Programmspeicherplatzes? Laut der Arduino-Website :

Es ist einfach, alles zu nutzen, indem Sie viele Zeichenfolgen in Ihrem Programm haben. Beispiel: Bei einer Deklaration wie char message[] = "I support the Cape Wind project.";folgt werden 33 Bytes in den SRAM geschrieben (jedes Zeichen benötigt ein Byte plus das Abschlusszeichen '\ 0').

In keiner dieser Skizzen sind jedoch Zeichenfolgen deklariert.

Es scheint, als würden sie andere Bibliotheken / Klassen importieren oder verwenden, die ich nicht spezifiziere. Vielleicht importiert es eine Systemstandardbibliothek? Oder ist es etwas anderes?

Antworten:


6

Das YUN ist eine Kombination. Teil Arduino und Teil OpenWRT (Linux). Ihre Frage bezieht sich auf den Arduino. Wobei es sich eigentlich um einen ATmega32u4 ähnlich einem Leonardo und nicht um einen UNO handelt (ATmega328p). Der 32u4 (Leo) kommuniziert über Virtual Serial Ports über USB (kurze Antwort: Dies muss unterstützt werden) , wobei die UNO einen echten seriellen Port (auch bekannt als UART) hat. Nachfolgend finden Sie eine Statistik der verschiedenen Kartentypen für die AVR-Prozessoren.

Beachten Sie, dass es auf der UNO einen externen Chip gibt, der USB in den DTR-Pin des seriellen Anschlusses umwandelt, der den Reset-Pin des ATmega328 umschaltet, wenn er angeschlossen ist, wodurch ein Neustart des Bootloaders verursacht wird. Im Gegensatz dazu ist das USB to Serial des Leo / Yun in der Firmware des 32u4 implementiert. Um den 32u4-Chip von Leo oder YUN aus der Ferne neu zu starten, muss die geladene Firmware immer den USB-Client-seitigen Treiber unterstützen. Welches verbraucht ca. 4K.

Wenn der USB NICHT benötigt wurde und keine anderen Bibliotheksressourcen wie im Fall von BareMinimum.ino auf einem UNO aufgerufen wurden, werden für die Arduino-Kernbibliothek nur ungefähr 466 Bytes benötigt.

Kompiliere die Statistiken von BareMinimum.ino auf einem UNO (ATmega328p)

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.

Kompiliere die Statistiken von BareMinimum.ino auf einem Leonardo (ATmega32u4)

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.

Kompiliere die Statistiken von BareMinimum.ino auf einem Yun (ATmega32u4)

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 kompiliert in vielen Standardbibliotheken, Interrupts usw. Zum Beispiel verwenden die Funktionen pinMode und digitalWrite eine Nachschlagetabelle, um zur Laufzeit herauszufinden, in welche GPIO-Register Daten geschrieben werden sollen. Ein weiteres Beispiel ist, dass Arduino die Zeit verfolgt, standardmäßig einige Interrupts definiert und für all diese Funktionen etwas Platz benötigt. Sie werden feststellen, dass sich der Footprint nur geringfügig ändert, wenn Sie das Programm erweitern.

Ich persönlich programmiere gerne Controller mit dem Nötigsten, ohne "aufblähen" zu müssen, aber Sie werden schnell in die Welt von EE.SE und SO einsteigen, da einige benutzerfreundliche Funktionen nicht mehr sofort funktionieren. Es gibt einige alternative Bibliotheken für pinMode und digitalWrite, die auf kleinerem Raum kompiliert werden können, aber auch andere Nachteile aufweisen, z. B. statisch kompilierte Pins (bei denen es ledsich nicht um Variablen, sondern um Konstanten handeln kann).


Also im Grunde kompiliert es in allen Arten von Standardbibliotheken, ohne dass Sie fragen? Ordentlich.
Hichris123

Ja, ich nenne es normalerweise "aufblähen", aber es ist wirklich eine Sache der Benutzerfreundlichkeit. Arduino ist eine niedrige Einstiegsumgebung, die einfach funktioniert, ohne zu viel nachzudenken. Wenn Sie mehr benötigen, können Sie mit Arduino alternative Bibliotheken verwenden oder gegen Bare Metal kompilieren. Der letzte ist wahrscheinlich außerhalb des Bereichs für Arduino.SE
jippie

Siehe meine @ mpflaga Antwort. Es gibt nicht so viel Aufblähung. Oder zumindest in der Kernbibliothek für ein Minimum an Funktionalität. Es sind nicht wirklich viele Standardbibliotheken enthalten, es sei denn, sie werden als Skizze bezeichnet. Vielmehr sind die 15% auf die USB-Unterstützung des 32u4 zurückzuführen.
Mpflaga

4

Sie haben bereits einige sehr gute Antworten. Ich poste dies nur, um einige Statistiken zu teilen, die ich eines Tages gemacht habe. Ich habe mir die gleichen Fragen gestellt: Was nimmt so viel Platz in einer minimalen Skizze ein? Was ist das Minimum, um die gleiche Funktionalität zu erreichen?

Im Folgenden finden Sie drei Versionen eines minimalen Blinkprogramms, mit dem die LED an Pin 13 jede Sekunde umgeschaltet wird. Alle drei Versionen wurden für ein Uno (ohne USB) mit avr-gcc 4.8.2, avr-libc 1.8.0 und arduino-core 1.0.5 kompiliert (ich verwende die Arduino-IDE nicht).

Erstens, der Standard-Arduino-Weg:

const uint8_t ledPin = 13;

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

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

Dies kompiliert auf 1018 Bytes. Bei beiden avr-nmund bei der Demontage habe ich diese Größe in einzelne Funktionen unterteilt. Vom größten zum kleinsten:

 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

In der obigen Liste ist die erste Spalte die Größe in Bytes und die zweite Spalte gibt an, ob der Code aus der Arduino-Kernbibliothek (A, insgesamt 822 Bytes), der C-Laufzeit (C, 148 Bytes) oder dem Benutzer (U) stammt 48 Bytes).

Wie in dieser Liste zu sehen ist, ist die größte Funktion die Routine, die den Timer 0-Überlaufinterrupt bedient. Diese Routine ist verantwortlich Zeit der Verfolgung und benötigt wird , um durch millis(), micros()und delay(). Die zweitgrößte Funktion ist init(), die die Hardware-Timer für PWM einstellt, den TIMER0_OVF-Interrupt aktiviert und den USART (der vom Bootloader verwendet wurde) trennt. Sowohl diese als auch die vorherige Funktion sind in definiert <Arduino directory>/hardware/arduino/cores/arduino/wiring.c.

Als nächstes kommt die C + avr-libc-Version:

#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);
    }
}

Die Aufteilung der einzelnen Größen:

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

Dies sind 132 Byte für die C-Laufzeit und 26 Byte für den Benutzercode, einschließlich der Inline-Funktion _delay_ms().

Es kann angemerkt werden, dass, da dieses Programm keine Interrupts verwendet, die Interruptvektortabelle nicht benötigt wird und regulärer Benutzercode an ihre Stelle gesetzt werden könnte. Die folgende Assembly-Version macht genau das:

#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

Dies ist (mit avr-gcc -nostdlib) in nur 14 Bytes zusammengefasst, von denen die meisten verwendet werden, um die Umschaltvorgänge zu verzögern, so dass das Blinken sichtbar ist. Wenn Sie diese Verzögerungsschleife entfernen, erhalten Sie ein 6-Byte-Programm, das zu schnell blinkt, um gesehen zu werden (bei 2 MHz):

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

3

Ich schrieb einen Beitrag über Warum braucht man 1000 Bytes, um eine LED zu blinken? .

Die kurze Antwort lautet: "Es dauert nicht 2000 Bytes, um zwei LEDs zu blinken !"

Die längere Antwort ist, dass die Standard-Arduino-Bibliotheken (die Sie nicht verwenden müssen, wenn Sie nicht möchten) einige nette Funktionen haben, um Ihr Leben zu vereinfachen. Beispielsweise können Sie Pins zur Laufzeit nach Nummer adressieren, wobei die Bibliothek Pin 8 in den richtigen Port und die richtige Bitnummer konvertiert. Wenn Sie den Portzugriff fest codieren, können Sie diesen Aufwand sparen.

Selbst wenn Sie sie nicht verwenden, enthalten die Standardbibliotheken Code zum Zählen von "Ticks", sodass Sie die aktuelle "Zeit" (durch Aufrufen millis()) herausfinden können . Dazu muss der Overhead einiger Interrupt-Serviceroutinen hinzugefügt werden.

Wenn Sie (auf dem Arduino Uno) diese Skizze vereinfachen, wird der Programmspeicher auf 178 Byte (auf IDE 1.0.6) reduziert:

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

OK, 178 Bytes sind nicht so viel, und davon sind die ersten 104 Bytes die Hardware-Interrupt-Vektoren (jeweils 4 Bytes für 26 Vektoren).

Es sind also nur 74 Bytes erforderlich, um eine LED zu blinken. Und von diesen 74 Bytes sind die meisten wirklich der Code, der vom Compiler generiert wird, um den globalen Speicher zu initialisieren. Wenn Sie genügend Code hinzufügen, um zwei LEDs zu blinken:

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

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

Dann erhöht sich die Codegröße auf 186 Bytes. Man könnte also argumentieren, dass es nur 186 - 178 = 8Bytes braucht, um eine LED zu blinken.

Also, 8 Bytes, um eine LED zu blinken. Klingt für mich ziemlich effizient.


Falls Sie versucht sind, dies zu Hause zu versuchen, sollte ich darauf hinweisen, dass der oben angegebene Code zwar zwei LEDs blinkt, dies jedoch sehr schnell geschieht. Tatsächlich blinken sie mit 2 MHz - siehe Screenshot. Kanal 1 (gelb) ist Pin 12, Kanal 2 (cyan) ist Pin 13.

Schnelles Blinken der Stifte 12 und 13

Wie Sie sehen, haben die Ausgangspins eine Rechteckwelle mit einer Frequenz von 2 MHz. Pin 13 ändert den Zustand 62,5 ns (ein Taktzyklus) vor Pin 12 aufgrund der Reihenfolge des Umschaltens der Pins im Code.

Wenn Sie also nicht viel bessere Augen als meine haben, werden Sie tatsächlich keinen blinkenden Effekt sehen.


Als amüsantes Extra können Sie zwei Pins auf der gleichen Programmfläche umschalten wie einen Pin.

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

Das kompiliert in 178 Bytes.

Dies gibt Ihnen eine höhere Frequenz:

Sehr schnelles Blinken der Pins 12 und 13

Jetzt sind wir auf 2,66 MHz.


Das macht eine Menge Sinn. Sind die Standardbibliotheken also nur Header, die beim Erstellen automatisch eingefügt werden? Und wie konnten Sie sie nicht einbeziehen?
Hichris123

2
Der Linker entfernt aggressiv Code, der nicht verwendet wird. Durch die nicht Aufruf init()(wie der normale main()Fall ist) , dann die Datei wiring.c (das hat initdarin) wurde nicht verbunden. Als Ergebnis wird die Verarbeitung für die Interrupt - Handler (für millis(), micros()etc.) weggelassen wurde. Es ist wahrscheinlich nicht besonders praktisch, es wegzulassen, es sei denn, Sie müssen nie etwas zeitlich festlegen. Fakt ist jedoch, dass die Größe der Skizze von Ihrer Eingabe abhängt. Wenn Sie beispielsweise Seriell verwenden, werden sowohl der Programmspeicher als auch der Arbeitsspeicher betroffen.
Nick Gammon
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.