Perché gli schizzi occupano così tanto spazio e memoria?


12

Quando compilo questo schizzo per lo Yún:

int led = 7;

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

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

Ottengo:

Sketch utilizza 5.098 byte (17%) di spazio di archiviazione del programma.

Il massimo è 28.672 byte. Le variabili globali utilizzano 153 byte (5%) di memoria dinamica, lasciando 2.407 byte per variabili locali. Il valore massimo è 2.560 byte.

Anche quando compilo lo schizzo BareMinimum:

void setup() {                
  // setup    
}

void loop() {
  // loop
}

Ottengo:

Sketch utilizza 4.548 byte (15%) di spazio di archiviazione del programma.

Il massimo è 28.672 byte. Le variabili globali utilizzano 151 byte (5%) di memoria dinamica, lasciando 2.409 byte per le variabili locali. Il valore massimo è 2.560 byte.

Perché uno schizzo minimo nudo occupa il 15% dello spazio di memoria del programma assegnato? E perché uno schizzo molto semplice occupa il 17% dello spazio di archiviazione del programma? Secondo il sito web di Arduino :

È facile usarlo tutto avendo un sacco di stringhe nel tuo programma. Ad esempio, una dichiarazione del tipo: char message[] = "I support the Cape Wind project.";inserisce 33 byte in SRAM (ogni carattere accetta un byte, più il terminatore '\ 0').

Tuttavia, non ci sono stringhe dichiarate in nessuno di questi schizzi.

Sembra che potrebbero importare o utilizzare altre librerie / classi che non ho specificato. Forse importa una libreria predefinita di sistema? O è qualcos'altro?

Risposte:


6

YUN è una combo. Parte Arduino e parte OpenWRT (Linux). La tua domanda è in riferimento ad Arduino. Dove questo è in realtà un ATmega32u4 simile a un Leonardo e non un UNO (ATmega328p). 32u4 (Leo) comunica tramite porte seriali virtuali tramite USB (risposta breve: questo deve essere supportato) , in cui UNO ha una vera porta seriale (nota anche come UART). Di seguito sono riportate le statistiche sulle build dei diversi tipi di schede per i processori AVR.

Nota su UNO c'è un chip esterno che converte l'USB nel pin DTR della porta seriale che attiva o disattiva il pin di reset dell'ATmega328 quando collegato causando un riavvio del bootloader. Al contrario, l'USB / Seriale Leo / Yun è implementato nel firmware del 32u4. Quindi, per riavviare in remoto il chip 32u4 Leo o YUN, il firmware caricato deve sempre supportare il driver lato client USB. Che consuma circa 4K.

Se l'USB NON era necessario e non venivano richiamate altre risorse di libreria come nel caso di BareMinimum.ino su un UNO, per la libreria Arduino di base sono necessari solo circa 466 byte.

compilare statistiche di BareMinimum.ino su 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.

compilare statistiche di BareMinimum.ino su un 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.

compilare le statistiche di BareMinimum.ino su uno 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 si compila in molte librerie standard, interrupt, ... ecc. Ad esempio le funzioni pinMode e digitalWrite utilizzano una tabella di ricerca per capire in fase di esecuzione in cui GPIO registra per scrivere i dati. Un altro esempio è che Arduino tiene traccia del tempo, definisce alcuni interrupt di default e tutte queste funzionalità richiedono un po 'di spazio. Noterai che se estendi il programma, l'impronta cambierà solo leggermente.

Personalmente mi piace programmare i controller con il minimo indispensabile, senza "gonfiare", ma entrerai rapidamente nel mondo di EE.SE e SO perché diverse funzioni facili da usare non funzioneranno più immediatamente. Esistono alcune librerie alternative per pinMode e digitalWrite che si compongono in un footprint più piccolo, ma presentano altri svantaggi come ad esempio pin compilati statici (dove lednon può essere una variabile, ma è una costante).


Quindi in pratica si compila in tutti i tipi di librerie standard senza che tu te lo chieda? Neat.
hichris123,

Sì, di solito lo chiamo "gonfio", ma è davvero una cosa di usabilità. Arduino è un ambiente di basso livello che funziona senza pensarci troppo. Se hai bisogno di più, Arduino ti consente di utilizzare librerie alternative o puoi compilare contro bare metal. L'ultimo è probabilmente fuori portata per Arduino.SE
jippie il

Vedi la mia risposta @mpflaga. Non c'è così tanto gonfio. O almeno nella libreria principale per funzionalità minime. Non c'è davvero molta di nessuna libreria standard inclusa, a meno che non venga chiamata la bozza. Piuttosto, il 15% è dovuto al supporto USB del 32u4.
mpflaga,

4

Hai già delle risposte perfettamente valide. Sto pubblicando questo solo per condividere alcune statistiche che ho fatto un giorno mi sono posto lo stesso tipo di domande: cosa sta prendendo così tanto spazio su uno schizzo minimo? Qual è il minimo necessario per ottenere la stessa funzionalità?

Di seguito sono riportate tre versioni di un programma lampeggiante minimo che attiva / disattiva il LED sul pin 13 ogni secondo. Tutte e tre le versioni sono state compilate per Uno (nessuna USB coinvolta) usando avr-gcc 4.8.2, avr-libc 1.8.0 e arduino-core 1.0.5 (non uso l'IDE Arduino).

Innanzitutto, il modo standard di Arduino:

const uint8_t ledPin = 13;

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

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

Questo si compila a 1018 byte. Usando entrambi avr-nme disassemblando , ho suddiviso quella dimensione in singole funzioni. Dal più grande al più piccolo:

 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

Nell'elenco sopra, la prima colonna è la dimensione in byte e la seconda colonna indica se il codice proviene dalla libreria principale di Arduino (A, 822 byte totali), dal runtime C (C, 148 byte) o dall'utente (U , 48 byte).

Come si può vedere in questo elenco, la funzione più grande è la manutenzione ordinaria dell'interrupt di overflow del timer 0. Questa routine è responsabile di tempo di monitoraggio, ed è necessario per millis(), micros()e delay(). La seconda funzione più grande è init(), che imposta i timer hardware per PWM, abilita l'interruzione TIMER0_OVF e disconnette USART (utilizzato dal bootloader). Sia questa che la precedente funzione sono definite in <Arduino directory>/hardware/arduino/cores/arduino/wiring.c.

La prossima è la versione 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);
    }
}

La suddivisione delle singole dimensioni:

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

Sono 132 byte per il runtime C e 26 byte di codice utente, inclusa la funzione incorporata _delay_ms().

Si potrebbe notare che, poiché questo programma non utilizza gli interrupt, la tabella dei vettori di interrupt non è necessaria e al suo posto potrebbe essere inserito un normale codice utente. La seguente versione dell'assembly fa esattamente questo:

#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

Questo è assemblato (con avr-gcc -nostdlib) in soli 14 byte, la maggior parte dei quali vengono utilizzati per ritardare le levette in modo che il battito di ciglia sia visibile. Se rimuovi quel loop di ritardo, finisci con un programma a 6 byte che lampeggia troppo velocemente per essere visto (a 2 MHz):

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

3

Ho scritto un post su Perché ci vogliono 1000 byte per far lampeggiare un LED? .

La breve risposta è: "Non ci vogliono 2000 byte per far lampeggiare due LED!"

La risposta più lunga è che le librerie Arduino standard (che non è necessario utilizzare se non si desidera) dispongono di alcune funzionalità utili per semplificare la vita. Ad esempio, è possibile indirizzare i pin per numero in fase di runtime, dove la libreria converte (diciamo) il pin 8 nella porta corretta e nel numero di bit corretto. Se si accede alla porta con codice fisso, è possibile salvare tale sovraccarico.

Anche se non li usi, le librerie standard includono il codice per contare i "tick" in modo da poter scoprire il "tempo" corrente (chiamando millis()). Per fare ciò deve aggiungere l'overhead di alcune routine di servizio di interrupt.

Se si semplifica (su Arduino Uno) questo schizzo, si riduce l'utilizzo della memoria del programma a 178 byte (su IDE 1.0.6):

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

OK, 178 byte non sono poi così tanti, e quindi i primi 104 byte sono i vettori di interruzione hardware (4 byte ciascuno, per 26 vettori).

Quindi, probabilmente, sono necessari solo 74 byte per far lampeggiare un LED. E di quei 74 byte, la maggior parte di essi è in realtà il codice generato dal compilatore per inizializzare la memoria globale. Se aggiungi abbastanza codice per far lampeggiare due 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
    }
  }

Quindi la dimensione del codice aumenta a 186 byte. Pertanto, si potrebbe sostenere che bastano 186 - 178 = 8byte per far lampeggiare un LED.

Quindi, 8 byte per far lampeggiare un LED. Mi sembra abbastanza efficiente.


Nel caso in cui sei tentato di provare questo a casa, dovrei sottolineare che mentre il codice pubblicato sopra lampeggia due LED, lo fa davvero molto rapidamente. In effetti, lampeggiano a 2 MHz - vedi screenshot. Il canale 1 (giallo) è il pin 12, il canale 2 (ciano) è il pin 13.

Lampeggiamento rapido dei pin 12 e 13

Come puoi vedere, i pin di uscita hanno un'onda quadra con una frequenza di 2 MHz. Il pin 13 cambia lo stato 62,5 ns (un ciclo di clock) prima del pin 12, a causa dell'ordine del toggling dei pin nel codice.

Quindi, a meno che tu non abbia occhi molto migliori dei miei, non vedrai alcun effetto lampeggiante.


Come extra divertente, puoi effettivamente attivare / disattivare due pin nella stessa quantità di spazio del programma di attivare / disattivare un 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

Ciò si compila in 178 byte.

Questo ti dà una frequenza più alta:

Lampeggiamento molto rapido dei pin 12 e 13

Ora siamo fino a 2,66 MHz.


Questo ha molto senso. Quindi le librerie standard sono solo le intestazioni incluse automaticamente al momento della compilazione? E come hai potuto non includerli?
hichris123,

2
Il linker elimina in modo aggressivo il codice non utilizzato. Non chiamando init()(come al main()solito), il file cablaggio.c (che contiene init) non è stato collegato. Di conseguenza, l'elaborazione per i gestori di interrupt (per millis(), micros()ecc.) È stata omessa. Probabilmente non è particolarmente pratico ometterlo, a meno che tu non abbia mai bisogno di cronometrare le cose, ma il fatto è che lo schizzo aumenta di dimensioni a seconda di ciò che hai inserito. Ad esempio, se si utilizza Seriale, sia la memoria del programma che la RAM subiscono un colpo.
Nick Gammon
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.