Dlaczego szkice zajmują tyle miejsca i pamięci?


12

Kiedy kompiluję ten szkic dla Yúna:

int led = 7;

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

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

Dostaję:

Szkic wykorzystuje 5 098 bajtów (17%) miejsca do przechowywania programu.

Maksymalnie wynosi 28 672 bajtów. Zmienne globalne używają 153 bajtów (5%) pamięci dynamicznej, pozostawiając 2 407 bajtów dla zmiennych lokalnych. Maksymalnie wynosi 2560 bajtów.

Nawet kiedy skompiluję szkic BareMinimum:

void setup() {                
  // setup    
}

void loop() {
  // loop
}

Dostaję:

Sketch wykorzystuje 4548 bajtów (15%) miejsca do przechowywania programu.

Maksymalnie wynosi 28 672 bajtów. Zmienne globalne używają 151 bajtów (5%) pamięci dynamicznej, pozostawiając 2 409 bajtów dla zmiennych lokalnych. Maksymalnie wynosi 2560 bajtów.

Dlaczego szkic minimalny zajmuje 15% przydzielonej przestrzeni dyskowej programu? I dlaczego bardzo prosty szkic zajmuje 17% miejsca w pamięci programu? Według strony internetowej Arduino :

Łatwo jest to wszystko wykorzystać, mając wiele ciągów w swoim programie. Na przykład deklaracja typu: char message[] = "I support the Cape Wind project.";wstawia 33 bajty do pamięci SRAM (każdy znak zajmuje bajt plus terminator „\ 0”).

Jednak w żadnym z tych szkiców nie ma zadeklarowanych ciągów.

Wygląda na to, że mogą importować lub używać innych bibliotek / klas, których nie określam. Może importuje domyślną bibliotekę systemową? A może to coś innego?

Odpowiedzi:


6

YUN to kombinacja. Część Arduino i Część OpenWRT (Linux). Twoje pytanie dotyczy Arduino. Gdzie jest to w rzeczywistości ATmega32u4 podobny do Leonardo, a nie UNO (ATmega328p). 32u4 (Leo) komunikuje się za pośrednictwem wirtualnych portów szeregowych przez USB (krótka odpowiedź: to musi być obsługiwane) , gdzie UNO ma prawdziwy port szeregowy (inaczej UART). Poniżej znajdują się statystyki kompilacji różnych typów płyt dla procesorów AVR.

Uwaga na UNO jest zewnętrzny układ, który konwertuje USB na pin DTR portu szeregowego, który przełącza pin reset ATmega328 po podłączeniu, powodując ponowne uruchomienie bootloadera. W przeciwieństwie do portu USB na szeregowy Leo / Yun jest zaimplementowany w oprogramowaniu 32u4. Dlatego w celu zdalnego restartu układu 32u4 Leo lub YUN załadowane oprogramowanie musi zawsze obsługiwać sterownik po stronie klienta USB. Który zużywa około 4K.

Jeśli USB NIE było potrzebne i nie zostały wywołane żadne inne zasoby biblioteczne, jak w przypadku BareMinimum.ino w UNO, potrzeba tylko około 466 bajtów dla podstawowej biblioteki Arduino.

skompiluj statystyki BareMinimum.ino na 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.

skompiluj statystyki BareMinimum.ino na 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.

skompiluj statystyki BareMinimum.ino na 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 kompiluje się w wielu standardowych bibliotekach, przerwań, ... itd. Na przykład funkcje pinMode i digitalWrite używają tabeli przeglądowej do określania w czasie wykonywania, do którego rejestru GPIO zapisuje dane. Innym przykładem jest to, że Arduino śledzi czas, domyślnie definiuje niektóre przerwania i cała ta funkcjonalność wymaga trochę miejsca. Zauważysz, że jeśli rozszerzysz program, ślad stopy zmieni się tylko nieznacznie.

Osobiście lubię programować kontrolery z absolutnym minimum, bez „wzdęcia”, ale szybko wejdziesz do świata EE.SE i SO, ponieważ kilka łatwych w użyciu funkcji nie będzie już działać od razu po wyjęciu z pudełka. Istnieją pewne alternatywne biblioteki dla pinMode i digitalWrite, które kompilują się w mniejszy rozmiar, ale mają inne wady, takie jak na przykład statycznie skompilowane piny (gdzie lednie może być zmienną, ale jest stałą).


Więc w zasadzie kompiluje się we wszystkich typowych bibliotekach bez pytania? Schludny.
hichris123

Tak, zwykle nazywam to „wzdęciem”, ale tak naprawdę jest to rzecz użyteczna. Arduino to środowisko podstawowe, które po prostu działa bez zastanowienia. Jeśli potrzebujesz więcej, Arduino pozwala na korzystanie z alternatywnych bibliotek lub kompilację na gołym metalu. Ostatni prawdopodobnie nie wchodzi w zakres Arduino.SE
jippie

Zobacz moją odpowiedź na @mpflaga. Nie ma tak dużego wzdęcia. Lub przynajmniej w bibliotece podstawowej dla minimalnej funkcjonalności. Tak naprawdę nie ma w nim wielu standardowych bibliotek, chyba że nazywa się szkic. Raczej 15% wynika z obsługi USB 32u4.
mpflaga

4

Masz już kilka doskonale dobrych odpowiedzi. Publikuję to tylko po to, by podzielić się statystykami, które zrobiłem pewnego dnia zadałem sobie te same pytania: co zajmuje tyle miejsca na minimalnym szkicu? Jakie jest minimum potrzebne do osiągnięcia tej samej funkcjonalności?

Poniżej znajdują się trzy wersje minimalnie mrugającego programu, który przełącza diodę LED na pinie 13 co sekundę. Wszystkie trzy wersje zostały skompilowane dla Uno (bez udziału USB) przy użyciu avr-gcc 4.8.2, avr-libc 1.8.0 i arduino-core 1.0.5 (nie używam Arduino IDE).

Po pierwsze, standardowy sposób Arduino:

const uint8_t ledPin = 13;

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

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

To kompiluje się do 1018 bajtów. Używając obu avr-nmi demontażu , podzieliłem ten rozmiar na poszczególne funkcje. Od największego do najmniejszego:

 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

Na powyższej liście pierwsza kolumna ma rozmiar w bajtach, a druga kolumna informuje, czy kod pochodzi z biblioteki podstawowej Arduino (A, łącznie 822 bajtów), środowiska wykonawczego C (C, 148 bajtów) czy użytkownika (U , 48 bajtów).

Jak można zobaczyć na tej liście, największą funkcją jest procedura obsługująca przerwanie przepełnienia timera 0. Procedura ta jest odpowiedzialna za śledzenie czasu, a jest potrzebny millis(), micros()i delay(). Druga co do wielkości funkcja init(), która ustawia timery sprzętowe dla PWM, włącza przerwanie TIMER0_OVF i rozłącza USART (który był używany przez bootloader). Zarówno ta, jak i poprzednia funkcja są zdefiniowane w <Arduino directory>/hardware/arduino/cores/arduino/wiring.c.

Dalej jest wersja 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);
    }
}

Podział poszczególnych rozmiarów:

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

Jest to 132 bajty dla środowiska wykonawczego C i 26 bajtów kodu użytkownika, w tym funkcji wbudowanej _delay_ms().

Można zauważyć, że ponieważ ten program nie używa przerwań, tablica wektorów przerwań nie jest potrzebna, a zwykły kod użytkownika może zostać wstawiony na jej miejsce. Następująca wersja zestawu robi dokładnie to:

#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

Jest to złożone (z avr-gcc -nostdlib) w zaledwie 14 bajtów, z których większość jest wykorzystywana do opóźniania przełączania, aby widoczne było mrugnięcie. Po usunięciu tej pętli opóźnienia powstaje 6-bajtowy program, który miga zbyt szybko, aby można go było zobaczyć (przy 2 MHz):

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

3

Napisałem post na temat: Dlaczego mrugnięcie jednej diody LED zajmuje 1000 bajtów? .

Krótka odpowiedź brzmi: „Miganie dwóch diod LED nie zajmuje 2000 bajtów !”

Dłuższą odpowiedzią jest to, że standardowe biblioteki Arduino (z których nie musisz korzystać, jeśli nie chcesz) mają fajną funkcjonalność, która uprości twoje życie. Na przykład, możesz adresować piny według numeru w czasie wykonywania, gdzie biblioteka konwertuje (powiedzmy) pin 8 na właściwy port i prawidłowy numer bitu. Jeśli masz sztywny kod dostępu do portu, możesz zaoszczędzić ten narzut.

Nawet jeśli ich nie używasz, standardowe biblioteki zawierają kod zliczający „tiki”, dzięki czemu możesz dowiedzieć się o bieżącym „czasie” (dzwoniąc millis()). W tym celu należy dodać narzut niektórych procedur obsługi przerwań.

Po uproszczeniu (w Arduino Uno) do tego szkicu zużycie pamięci programu spadnie do 178 bajtów (w IDE 1.0.6):

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

OK, 178 bajtów to niewiele, a pierwsze 104 bajty to wektory przerwań sprzętowych (po 4 bajty na 26 wektorów).

Prawdopodobnie więc wystarczy 74 bajty, aby mrugnąć diodą LED. A z tych 74 bajtów większość to tak naprawdę kod generowany przez kompilator w celu zainicjowania pamięci globalnej. Jeśli dodasz wystarczającą ilość kodu, aby mrugnąć dwie diody 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
    }
  }

Następnie rozmiar kodu wzrasta do 186 bajtów. Dlatego można argumentować, że 186 - 178 = 8miganie diody LED zajmuje tylko bajty.

8 bajtów migania diody LED. Brzmi dla mnie dość wydajnie.


Jeśli masz ochotę spróbować tego w domu, powinienem zauważyć, że chociaż powyższy kod miga dwie diody LED, robi to naprawdę bardzo szybko. W rzeczywistości migają przy 2 MHz - patrz zrzut ekranu. Kanał 1 (żółty) to styk 12, kanał 2 (cyjan) to styk 13.

Szybkie mruganie pinów 12 i 13

Jak widać, piny wyjściowe mają prostokątną falę o częstotliwości 2 MHz. Pin 13 zmienia stan 62,5 ns (jeden cykl zegara) przed pinem 12, ze względu na kolejność przełączania pinów w kodzie.

Więc jeśli nie masz znacznie lepszych oczu niż moje, nie zobaczysz żadnego efektu mrugania.


Jako zabawny dodatek, możesz faktycznie przełączać dwa piny w tej samej przestrzeni programu, co przełączanie jednego pinu.

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

To kompiluje się w 178 bajtów.

To daje wyższą częstotliwość:

Bardzo szybkie mruganie pinów 12 i 13

Teraz jesteśmy do 2,66 MHz.


To ma sens. Czy więc standardowe biblioteki są tylko nagłówkami dołączanymi automatycznie podczas kompilacji? Jak mogłeś ich nie uwzględnić?
hichris123

2
Linker agresywnie usuwa kod, który nie jest używany. Poprzez niestawienie init()(jak normalny main()robi), a następnie wiring.c plik (który ma initw nim) nie był związany w. W rezultacie przetwarzanie dla przerwań ładowarki (do millis(), micros()itd.) Został pominięty. Prawdopodobnie nie jest szczególnie praktyczne pominięcie go, chyba że nigdy nie musisz mierzyć czasu, ale faktem jest, że szkic powiększa się w zależności od tego, co w nim umieścisz. Na przykład, jeśli używasz portu szeregowego, pamięć programu i pamięć RAM są trafione.
Nick Gammon
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.