¿Por qué los bocetos ocupan tanto espacio y memoria?


12

Cuando compilo este boceto para el Yún:

int led = 7;

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

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

Yo obtengo:

Sketch utiliza 5.098 bytes (17%) del espacio de almacenamiento del programa.

El máximo es de 28,672 bytes. Las variables globales usan 153 bytes (5%) de memoria dinámica, dejando 2,407 bytes para las variables locales. El máximo es de 2.560 bytes.

Incluso cuando compilo el boceto BareMinimum:

void setup() {                
  // setup    
}

void loop() {
  // loop
}

Yo obtengo:

Sketch utiliza 4.548 bytes (15%) del espacio de almacenamiento del programa.

El máximo es de 28,672 bytes. Las variables globales usan 151 bytes (5%) de memoria dinámica, dejando 2,409 bytes para las variables locales. El máximo es de 2.560 bytes.

¿Por qué un simple boceto mínimo ocupa el 15% del espacio de almacenamiento del programa asignado? ¿Y por qué un boceto muy simple ocupa el 17% del espacio de almacenamiento del programa? Según el sitio web de Arduino :

Es fácil usarlo todo teniendo muchas cadenas en su programa. Por ejemplo, una declaración como: char message[] = "I support the Cape Wind project.";pone 33 bytes en SRAM (cada carácter toma un byte, más el terminador '\ 0').

Sin embargo, no hay cadenas declaradas en ninguno de estos bocetos.

Parece que podrían importar o usar otras bibliotecas / clases que no especifico. ¿Tal vez importa una biblioteca predeterminada del sistema? ¿O es otra cosa?

Respuestas:


6

El YUN es un combo. Parte Arduino y Parte OpenWRT (Linux). Su pregunta es en referencia al Arduino. Donde esto es realmente un ATmega32u4 similar a un Leonardo y no un UNO (ATmega328p). El 32u4 (Leo) se comunica a través de puertos serie virtuales a través del USB (respuesta corta: esto debe ser compatible) , donde la UNO tiene un puerto serie real (también conocido como UART). A continuación se muestran estadísticas de compilación de los diferentes tipos de placas para los procesadores AVR.

Tenga en cuenta que en UNO hay un chip externo que convierte el USB en el pin DTR del puerto serie que alterna el pin de reinicio del ATmega328 cuando está conectado, lo que provoca un reinicio del cargador de arranque. Por el contrario, el USB a serie de Leo / Yun está implementado en el firmware del 32u4. Por lo tanto, para reiniciar de forma remota el chip 32u4 de Leo o YUN, el firmware cargado siempre debe ser compatible con el controlador del lado del cliente USB. Que consume aproximadamente 4K.

Si NO se necesitaba el USB y no se llamaron otros recursos de la biblioteca como en el caso de BareMinimum.ino en una UNO, solo se necesitan aproximadamente 466 bytes para la Biblioteca Arduino central.

compilar estadísticas de BareMinimum.ino en 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.

compilar estadísticas de BareMinimum.ino en 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.

compilar estadísticas de BareMinimum.ino en 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 compila en muchas bibliotecas estándar, interrupciones, ... etc. Por ejemplo, las funciones pinMode y digitalWrite usan una tabla de búsqueda para determinar en tiempo de ejecución en qué GPIO se registra para escribir datos. Otro ejemplo es que Arduino realiza un seguimiento del tiempo, define algunas interrupciones por defecto y toda esta funcionalidad requiere algo de espacio. Notarás que si extiendes el programa, la huella solo cambiará ligeramente.

Personalmente, me gusta programar controladores con un mínimo, sin "hinchazón", pero entrará rápidamente en el mundo de EESE y SO porque varias funciones fáciles de usar ya no funcionarán de inmediato. Hay algunas bibliotecas alternativas para pinMode y digitalWrite que se compilan en una huella más pequeña, pero vienen con otras desventajas, como por ejemplo pines compilados estáticos (donde ledno puede ser una variable, pero es una constante).


Entonces, ¿básicamente se compila en todo tipo de bibliotecas estándar sin preguntar? Ordenado.
hichris123

Sí, generalmente lo llamo "hinchazón", pero en realidad es algo de usabilidad. Arduino es un entorno de bajo nivel de entrada que simplemente funciona sin pensarlo demasiado. Si necesita más, Arduino le permite usar bibliotecas alternativas o puede compilar contra el metal desnudo. El último probablemente esté fuera del alcance de Arduino.SE
jippie

Vea mi respuesta @mpflaga. No hay tanta hinchazón. O al menos en la biblioteca principal para una funcionalidad mínima. Realmente no hay muchas bibliotecas estándar incluidas, a menos que se llame boceto. Más bien, el 15% se debe al soporte USB del 32u4.
mpflaga

4

Ya tienes algunas respuestas perfectamente buenas. Estoy publicando esto solo para compartir algunas estadísticas que hice un día. Me hice el mismo tipo de preguntas: ¿Qué está tomando tanto espacio en un boceto mínimo? ¿Cuál es el mínimo necesario para lograr la misma funcionalidad?

A continuación se presentan tres versiones de un programa mínimo de parpadeo que alterna el LED en el pin 13 cada segundo. Las tres versiones se han compilado para Uno (sin USB) usando avr-gcc 4.8.2, avr-libc 1.8.0 y arduino-core 1.0.5 (no uso el IDE de Arduino).

Primero, la forma estándar de Arduino:

const uint8_t ledPin = 13;

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

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

Esto compila a 1018 bytes. Utilizando ambos avr-nmy el desmontaje , dividí ese tamaño en funciones individuales. De mayor a menor:

 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

En la lista anterior, la primera columna es el tamaño en bytes, y la segunda columna indica si el código proviene de la biblioteca principal de Arduino (A, 822 bytes en total), el tiempo de ejecución C (C, 148 bytes) o el usuario (U , 48 bytes).

Como se puede ver en esta lista, la función más grande es la rutina que da servicio a la interrupción por desbordamiento del temporizador 0. Esta rutina es responsable del seguimiento del tiempo, y es necesaria por millis(), micros()y delay(). La segunda función más grande es init(), que establece los temporizadores de hardware para PWM, habilita la interrupción TIMER0_OVF y desconecta el USART (que fue usado por el gestor de arranque). Tanto esta como la función anterior se definen en <Arduino directory>/hardware/arduino/cores/arduino/wiring.c.

La siguiente es la versión 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);
    }
}

El desglose de los tamaños individuales:

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

Esto es 132 bytes para el tiempo de ejecución C y 26 bytes de código de usuario, incluida la función en línea _delay_ms().

Cabe señalar que, dado que este programa no utiliza interrupciones, la tabla de vectores de interrupción no es necesaria y se puede colocar el código de usuario normal en su lugar. La siguiente versión de ensamblaje hace exactamente eso:

#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

Esto se ensambla (con avr-gcc -nostdlib) en solo 14 bytes, la mayoría de los cuales se utilizan para retrasar los conmutadores para que el parpadeo sea visible. Si elimina ese ciclo de retraso, termina con un programa de 6 bytes que parpadea demasiado rápido para ser visto (a 2 MHz):

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

3

Escribí una publicación sobre ¿Por qué se necesitan 1000 bytes para parpadear un LED? .

La breve respuesta es: "¡No se necesitan 2000 bytes para parpadear dos LED!"

La respuesta más larga es que las bibliotecas Arduino estándar (que no tienes que usar si no quieres) tienen una buena funcionalidad para simplificar tu vida. Por ejemplo, puede direccionar los pines por número en tiempo de ejecución, donde la biblioteca convierte (digamos) el pin 8 en el puerto correcto y el número de bit correcto. Si codifica el acceso al puerto, puede guardar esa sobrecarga.

Incluso si no los usa, las bibliotecas estándar incluyen código para contar "ticks" para que pueda averiguar el "tiempo" actual (llamando millis()). Para hacer esto, debe agregar la sobrecarga de algunas rutinas de servicio de interrupción.

Si simplifica (en el Arduino Uno) a este boceto, el uso de la memoria del programa se reduce a 178 bytes (en IDE 1.0.6):

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

OK, 178 bytes no es mucho, y de eso los primeros 104 bytes son los vectores de interrupción de hardware (4 bytes cada uno, para 26 vectores).

Podría decirse que solo se requieren 74 bytes para parpadear un LED. Y de esos 74 bytes, la mayoría son realmente el código generado por el compilador para inicializar la memoria global. Si agrega suficiente código para parpadear dos 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
    }
  }

Luego, el tamaño del código aumenta a 186 bytes. Por lo tanto, podría argumentar que solo se necesitan 186 - 178 = 8bytes para parpadear un LED.

Entonces, 8 bytes para parpadear un LED. Suena bastante eficiente para mí.


En caso de que sienta la tentación de probar esto en casa, debo señalar que si bien el código publicado arriba parpadea dos LED, lo hace muy rápidamente. De hecho, parpadean a 2 MHz, mira la captura de pantalla. El canal 1 (amarillo) es el pin 12, el canal 2 (cian) es el pin 13.

Parpadeo rápido de los pines 12 y 13.

Como puede ver, los pines de salida tienen una onda cuadrada con una frecuencia de 2 MHz. El pin 13 cambia el estado 62.5 ns (un ciclo de reloj) antes del pin 12, debido al orden de alternar los pines en el código.

Entonces, a menos que tenga ojos mucho mejores que los míos, en realidad no verá ningún efecto de parpadeo.


Como un extra divertido, puede alternar dos pines en la misma cantidad de espacio de programa que alternar 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

Eso se compila en 178 bytes.

Esto te da una frecuencia más alta:

Parpadeo muy rápido de los pines 12 y 13.

Ahora estamos hasta 2.66 MHz.


Esto tiene mucho sentido. Entonces, ¿las bibliotecas estándar son solo encabezados incluidos automáticamente en el momento de la compilación? ¿Y cómo pudiste no incluirlos?
hichris123

2
El enlazador elimina agresivamente el código que no se usa. Al no llamar init()(como lo main()hace normalmente ), entonces el archivo alambrado.c (que contiene init) no se vinculó. Como resultado, se omitió el procesamiento de los manejadores de interrupciones (para millis(), micros()etc.). Probablemente no sea particularmente práctico omitirlo, a menos que nunca necesite cronometrar las cosas, pero el hecho es que el boceto crece en tamaño dependiendo de lo que ponga en él. Por ejemplo, si usa Serial, tanto la memoria del programa como la RAM reciben un golpe.
Nick Gammon
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.