Pourquoi les croquis prennent-ils autant d'espace et de mémoire?


12

Quand je compile ce croquis pour le Yún:

int led = 7;

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

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

Je reçois:

Sketch utilise 5 098 octets (17%) d'espace de stockage de programme.

Le maximum est de 28 672 octets. Les variables globales utilisent 153 octets (5%) de mémoire dynamique, laissant 2407 octets pour les variables locales. Le maximum est de 2 560 octets.

Même lorsque je compile l'esquisse BareMinimum:

void setup() {                
  // setup    
}

void loop() {
  // loop
}

Je reçois:

Sketch utilise 4 548 octets (15%) d'espace de stockage de programme.

Le maximum est de 28 672 octets. Les variables globales utilisent 151 octets (5%) de mémoire dynamique, laissant 2409 octets pour les variables locales. Le maximum est de 2 560 octets.

Pourquoi une simple esquisse occupe-t-elle 15% de l'espace de stockage de programme alloué? Et pourquoi un croquis très simple occupe-t-il 17% de l'espace de stockage du programme? Selon le site Arduino :

Il est facile de tout utiliser en ayant beaucoup de chaînes dans votre programme. Par exemple, une déclaration comme: char message[] = "I support the Cape Wind project.";met 33 octets dans SRAM (chaque caractère prend un octet, plus le terminateur '\ 0').

Cependant, aucune chaîne n'est déclarée dans aucune de ces esquisses.

Il semble qu'ils pourraient importer ou utiliser d'autres bibliothèques / classes que je ne spécifie pas. Peut-être qu'il importe une bibliothèque par défaut du système? Ou s'agit-il d'autre chose?

Réponses:


6

Le YUN est un combo. En partie Arduino et en partie OpenWRT (Linux). Votre question fait référence à l'Arduino. Où il s'agit en fait d'un ATmega32u4 similaire à un Leonardo et non d'un UNO (ATmega328p). Le 32u4 (Leo) communique via des ports série virtuels via l'USB (réponse courte: cela doit être pris en charge) , où l'UNO dispose d'un véritable port série (alias UART). Vous trouverez ci-dessous des statistiques de génération des différents types de cartes pour les processeurs AVR.

Remarque sur l'UNO, il y a une puce externe qui convertit USB en broche DTR du port série qui bascule la broche de réinitialisation de l'ATmega328 lorsqu'il est connecté, provoquant un redémarrage du chargeur de démarrage. En revanche, l'USB vers série de Leo / Yun est implémenté dans le firmware du 32u4. Par conséquent, pour redémarrer à distance la puce Leo ou YUN 32u4, le micrologiciel chargé doit toujours prendre en charge le pilote côté client USB. Qui consomme environ 4K.

Si l'USB n'était PAS nécessaire et qu'aucune autre ressource de bibliothèque n'a été appelée comme dans le cas de BareMinimum.ino sur un UNO, seulement environ 466 octets sont nécessaires pour la bibliothèque Arduino principale.

compiler les statistiques de BareMinimum.ino sur un 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.

compiler les statistiques de BareMinimum.ino sur 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.

compiler les statistiques de BareMinimum.ino sur un 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 compile dans de nombreuses bibliothèques standard, interruptions, etc. Un autre exemple est qu'Arduino garde la trace du temps, il définit certaines interruptions par défaut et toutes ces fonctionnalités nécessitent un peu d'espace. Vous remarquerez que si vous prolongez le programme, l'empreinte ne changera que légèrement.

Personnellement, j'aime programmer des contrôleurs avec le strict minimum, sans "ballonnement", mais vous entrerez rapidement dans le monde d'EE.SE et SO car plusieurs fonctions faciles à utiliser ne fonctionneront plus hors de la boîte. Il existe des bibliothèques alternatives pour pinMode et digitalWrite qui se compilent dans une empreinte plus petite, mais viennent avec d'autres inconvénients comme par exemple les broches compilées statiques (où ledne peut pas être une variable, mais est une constante).


Donc, fondamentalement, il se compile dans toutes sortes de bibliothèques standard sans que vous le demandiez? Soigné.
hichris123

Oui, je l'appelle habituellement "ballonnement", mais c'est vraiment une chose de convivialité. Arduino est un environnement de bas niveau d'entrée qui fonctionne juste sans trop de réflexion. Si vous en avez besoin de plus, Arduino vous permet d'utiliser des bibliothèques alternatives ou vous pouvez compiler contre du métal nu. Le dernier est probablement hors de portée pour Arduino.SE
jippie

Voir ma réponse @mpflaga. Il n'y a pas autant de ballonnement. Ou du moins dans la bibliothèque principale pour une fonctionnalité minimale. Il n'y a pas vraiment beaucoup de bibliothèques standard incluses, sauf si on l'appelle l'esquisse. Les 15% sont plutôt dus à la prise en charge USB du 32u4.
mpflaga

4

Vous avez déjà de très bonnes réponses. Je poste ceci uniquement pour partager certaines statistiques que j'ai faites un jour, je me suis posé le même genre de questions: qu'est-ce qui prend autant de place sur un croquis minimal? Quel est le minimum requis pour obtenir la même fonctionnalité?

Vous trouverez ci-dessous trois versions d'un programme minimal clignotant qui fait basculer la LED sur la broche 13 chaque seconde. Les trois versions ont été compilées pour un Uno (pas d'USB impliqué) en utilisant avr-gcc 4.8.2, avr-libc 1.8.0 et arduino-core 1.0.5 (je n'utilise pas l'IDE Arduino).

Tout d'abord, la méthode Arduino standard:

const uint8_t ledPin = 13;

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

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

Cela compile à 1018 octets. En utilisant les deux avr-nmet le démontage , j'ai décomposé cette taille en fonctions individuelles. Du plus grand au plus petit:

 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

Dans la liste ci-dessus, la première colonne est la taille en octets, et la deuxième colonne indique si le code provient de la bibliothèque principale Arduino (A, 822 octets au total), du runtime C (C, 148 octets) ou de l'utilisateur (U , 48 octets).

Comme on peut le voir dans cette liste, la fonction la plus importante est la routine d'entretien de l'interruption de débordement du temporisateur 0. Cette routine est responsable du suivi du temps, et il est nécessaire par millis(), micros()et delay(). La deuxième plus grande fonction est init(), qui définit les temporisations matérielles pour PWM, active l'interruption TIMER0_OVF et déconnecte l'USART (qui a été utilisé par le chargeur de démarrage). Cette fonction et la fonction précédente sont définies dans <Arduino directory>/hardware/arduino/cores/arduino/wiring.c.

Vient ensuite la version 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 répartition des tailles individuelles:

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

Cela représente 132 octets pour le runtime C et 26 octets de code utilisateur, y compris la fonction intégrée _delay_ms().

Il peut être noté que, puisque ce programme n'utilise pas d'interruptions, la table des vecteurs d'interruption n'est pas nécessaire, et un code utilisateur régulier pourrait être mis à sa place. La version d'assemblage suivante fait exactement cela:

#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

Celui-ci est assemblé (avec avr-gcc -nostdlib) en seulement 14 octets, dont la plupart sont utilisés pour retarder les basculements afin que le clignotement soit visible. Si vous supprimez cette boucle de retard, vous vous retrouvez avec un programme de 6 octets qui clignote trop vite pour être vu (à 2 MHz):

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

3

J'ai écrit un article sur Pourquoi faut-il 1000 octets pour clignoter une LED? .

La réponse brève est: "Il ne faut pas 2000 octets pour faire clignoter deux LED!"

La réponse la plus longue est que les bibliothèques Arduino standard (que vous n'avez pas besoin d'utiliser si vous ne le souhaitez pas) ont de belles fonctionnalités pour vous simplifier la vie. Par exemple, vous pouvez adresser les broches par numéro lors de l'exécution, où la bibliothèque convertit (disons) la broche 8 en un port et un numéro de bit corrects. Si vous codez en dur l'accès au port, vous pouvez économiser cette surcharge.

Même si vous ne les utilisez pas, les bibliothèques standard incluent du code pour compter les "ticks" afin que vous puissiez trouver le "temps" actuel (en appelant millis()). Pour ce faire, il doit ajouter la surcharge de certaines routines de service d'interruption.

Si vous simplifiez (sur l'Arduino Uno) à cette esquisse, vous réduisez l'utilisation de la mémoire du programme à 178 octets (sur IDE 1.0.6):

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

OK, 178 octets, ce n'est pas tant que ça, et les 104 premiers octets sont les vecteurs d'interruption matérielle (4 octets chacun, pour 26 vecteurs).

Donc, sans doute, il ne faut que 74 octets pour faire clignoter une LED. Et de ces 74 octets, la plupart sont vraiment le code généré par le compilateur pour initialiser la mémoire globale. Si vous ajoutez suffisamment de code pour faire clignoter deux 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
    }
  }

Ensuite, la taille du code augmente à 186 octets. Par conséquent, vous pourriez faire valoir qu'il ne faut que des 186 - 178 = 8octets pour faire clignoter une LED.

Donc, 8 octets pour faire clignoter une LED. Cela me semble assez efficace.


Au cas où vous seriez tenté d'essayer cela à la maison, je dois souligner que bien que le code affiché ci-dessus clignote deux LED, il le fait très rapidement. En fait, ils clignotent à 2 MHz - voir capture d'écran. Le canal 1 (jaune) est la broche 12, le canal 2 (cyan) est la broche 13.

Clignotement rapide des broches 12 et 13

Comme vous pouvez le voir, les broches de sortie ont une onde carrée avec une fréquence de 2 MHz. La broche 13 change d'état 62,5 ns (un cycle d'horloge) avant la broche 12, en raison de l'ordre de basculement des broches dans le code.

Donc, à moins d'avoir des yeux bien meilleurs que les miens, vous ne verrez aucun effet clignotant.


Comme extra amusant, vous pouvez réellement basculer deux broches dans la même quantité d'espace de programme que basculer une broche.

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

Cela se compile en 178 octets.

Cela vous donne une fréquence plus élevée:

Clignotement très rapide des broches 12 et 13

Nous sommes maintenant à 2,66 MHz.


Cela a beaucoup de sens. Les bibliothèques standard ne sont-elles donc que des en-têtes inclus automatiquement au moment de la construction? Et comment avez-vous pu ne pas les inclure?
hichris123

2
L'éditeur de liens supprime agressivement le code qui n'est pas utilisé. En n'appelant pas init()(comme d'habitude main()), le fichier câblage.c (qui s'y trouve init) n'était pas lié. Par conséquent, le traitement des gestionnaires d'interruption (pour millis(), micros()etc.) a été omis. Il n'est probablement pas particulièrement pratique de l'omettre, sauf si vous n'avez jamais besoin de chronométrer les choses, mais le fait est que l'esquisse grandit en fonction de ce que vous y mettez. Par exemple, si vous utilisez Serial, la mémoire du programme et la RAM prennent un coup.
Nick Gammon
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.