Por que os esboços ocupam tanto espaço e memória?

12

Quando eu compilar esse esboço para o Yún:

int led = 7;

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

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

Eu recebo:

O Sketch usa 5.098 bytes (17%) do espaço de armazenamento do programa.

O máximo é 28.672 bytes. As variáveis ​​globais usam 153 bytes (5%) de memória dinâmica, deixando 2.407 bytes para variáveis ​​locais. O máximo é de 2.560 bytes.

Mesmo quando eu compilo o esboço BareMinimum:

void setup() {                
  // setup    
}

void loop() {
  // loop
}

Eu recebo:

O Sketch usa 4.548 bytes (15%) do espaço de armazenamento do programa.

O máximo é 28.672 bytes. As variáveis ​​globais usam 151 bytes (5%) de memória dinâmica, deixando 2.409 bytes para variáveis ​​locais. O máximo é de 2.560 bytes.

Por que um esboço mínimo desencapado ocupa 15% do espaço de armazenamento do programa alocado? E por que um esboço muito simples ocupa 17% do espaço de armazenamento do programa? De acordo com o site do Arduino :

É fácil usar tudo isso, tendo muitas strings no seu programa. Por exemplo, uma declaração como: char message[] = "I support the Cape Wind project.";coloca 33 bytes na SRAM (cada caractere recebe um byte, mais o terminador '\ 0').

No entanto, não há nenhuma sequência declarada em nenhum desses esboços.

Parece que eles podem importar ou usar outras bibliotecas / classes que não especifico. Talvez ele importe uma biblioteca padrão do sistema? Ou é outra coisa?

hichris123
fonte

Respostas:

6

O YUN é uma combinação. Parte Arduino e Parte OpenWRT (Linux). Sua pergunta é referente ao Arduino. Onde este é realmente um ATmega32u4 semelhante a um Leonardo e não um UNO (ATmega328p). O 32u4 (Leo) se comunica via portas seriais virtuais por USB (resposta curta: isso precisa ser suportado) , onde a UNO tem uma porta serial real (também conhecida como UART). Abaixo estão as estatísticas de compilação dos diferentes tipos de placas para os processadores AVR.

Observe no UNO que existe um chip externo que converte USB no pino DTR da porta serial que alterna o pino de redefinição do ATmega328 quando conectado, causando uma reinicialização no gerenciador de inicialização. Em contraste, o USB / Serial do Leo / Yun está implementado no firmware do 32u4. Portanto, para reiniciar remotamente o chip 32u4 do Leo ou do YUN, o firmware carregado deve sempre suportar o driver do lado do cliente USB. Que consome aproximadamente 4K.

Se o USB NÃO foi necessário e nenhum outro recurso de biblioteca foi chamado, como no caso do BareMinimum.ino em um UNO, apenas aproximadamente 466 bytes são necessários para a biblioteca principal do Arduino.

compilar estatísticas de BareMinimum.ino em um 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 estatísticas de BareMinimum.ino em um 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 estatísticas de BareMinimum.ino em um 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.
mpflaga
fonte
7

O Arduino compila em muitas bibliotecas padrão, interrupções, etc. etc. Por exemplo, as funções pinMode e digitalWrite usam uma tabela de pesquisa para descobrir em tempo de execução que o GPIO se registra para gravar dados. Outro exemplo é que o Arduino mantém o controle do tempo, define algumas interrupções por padrão e toda essa funcionalidade requer algum espaço. Você notará que, se você estender o programa, a pegada do texto mudará apenas um pouco.

Pessoalmente, gosto de programar controladores com o mínimo necessário, sem "inchaço", mas você entrará rapidamente no mundo do EE.SE e SO, porque várias funções fáceis de usar não funcionarão mais prontamente. Existem algumas bibliotecas alternativas para pinMode e digitalWrite que são compiladas em um espaço menor, mas trazem outras desvantagens, como por exemplo pinos compilados estáticos (onde lednão pode ser uma variável, mas é uma constante).

jippie
fonte
Então, basicamente, ele compila em todos os tipos de bibliotecas padrão sem você perguntar? Arrumado.
precisa saber é o seguinte
Sim, eu costumo chamar de "inchaço", mas é realmente uma coisa de usabilidade. O Arduino é um ambiente de baixo nível de entrada que simplesmente funciona sem pensar muito. Se você precisar de mais, o Arduino permite usar bibliotecas alternativas ou compilar com o bare metal. Provavelmente, o último está fora do escopo para o Arduino.SE
jippie
Veja minha resposta @mpflaga. Não há tanto inchaço. Ou pelo menos na biblioteca principal, para uma funcionalidade mínima. Não há realmente nenhuma biblioteca padrão incluída, a menos que seja chamada de sketch. Os 15% são devidos ao suporte USB do 32u4.
Mpflaga
4

Você já tem respostas perfeitamente boas. Estou postando isso apenas para compartilhar algumas estatísticas que fiz um dia e me fiz o mesmo tipo de pergunta: O que está ocupando tanto espaço em um esboço mínimo? Qual é o mínimo necessário para alcançar a mesma funcionalidade?

Abaixo estão três versões de um programa intermitente mínimo que alterna o LED no pino 13 a cada segundo. Todas as três versões foram compiladas para um Uno (sem USB envolvido) usando o avr-gcc 4.8.2, avr-libc 1.8.0 e o arduino-core 1.0.5 (não uso o IDE do Arduino).

Primeiro, a maneira padrão do Arduino:

const uint8_t ledPin = 13;

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

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

Isso compila para 1018 bytes. Usando ambos avr-nme desmontagem , eu dividi esse tamanho em funções individuais. Do maior para o 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

Na lista acima, a primeira coluna é o tamanho em bytes, e a segunda coluna informa se o código é proveniente da biblioteca principal do Arduino (A, 822 bytes no total), do tempo de execução C (C, 148 bytes) ou do usuário (U , 48 bytes).

Como pode ser visto nesta lista, a maior função é a rotina de manutenção da interrupção de estouro do timer 0. Essa rotina é responsável pelo rastreamento do tempo e é necessária por millis(), micros()e delay(). A segunda maior função é init(), que define os temporizadores de hardware para o PWM, permite a interrupção do TIMER0_OVF e desconecta o USART (que foi usado pelo carregador de inicialização). Tanto esta como a função anterior são definidas em <Arduino directory>/hardware/arduino/cores/arduino/wiring.c.

A seguir está a versão 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);
    }
}

A discriminação dos tamanhos individuais:

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

São 132 bytes para o tempo de execução C e 26 bytes de código do usuário, incluindo a função embutida _delay_ms().

Pode-se observar que, como esse programa não usa interrupções, a tabela de vetores de interrupção não é necessária e o código do usuário comum pode ser colocado em seu lugar. A seguinte versão do assembly faz exatamente isso:

#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

Isso é montado (com avr-gcc -nostdlib) em apenas 14 bytes, a maioria dos quais é usada para atrasar as alternâncias para que o piscar fique visível. Se você remover esse loop de atraso, você terminará com um programa de 6 bytes que pisca rápido demais para ser visto (a 2 MHz):

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

Eu escrevi um post sobre Por que são necessários 1000 bytes para piscar um LED? .

A resposta breve é: "Não são necessários 2000 bytes para piscar dois LEDs!"

A resposta mais longa é que as bibliotecas padrão do Arduino (que você não precisa usar se não quiser) têm algumas funcionalidades agradáveis ​​para simplificar sua vida. Por exemplo, você pode endereçar pinos por número no tempo de execução, onde a biblioteca converte (digamos) o pino 8 na porta e no número de bits corretos. Se você codificar o acesso à porta, poderá salvar essa sobrecarga.

Mesmo que você não as use, as bibliotecas padrão incluem código para contar "ticks" para que você possa descobrir a "hora" atual (ligando millis()). Para fazer isso, é necessário adicionar a sobrecarga de algumas rotinas de serviço de interrupção.

Se você simplificar (no Arduino Uno) este esboço, o uso da memória do programa é reduzido para 178 bytes (no IDE 1.0.6):

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

OK, 178 bytes não são muito, e dos primeiros 104 bytes são os vetores de interrupção de hardware (4 bytes cada, para 26 vetores).

Então, sem dúvida, existem apenas 74 bytes necessários para piscar um LED. E desses 74 bytes, a maioria é realmente o código gerado pelo compilador para inicializar a memória global. Se você adicionar código suficiente para piscar dois LEDs:

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

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

Em seguida, o tamanho do código aumenta para 186 bytes. Portanto, você pode argumentar que são necessários apenas 186 - 178 = 8bytes para piscar um LED.

Então, 8 bytes para piscar um LED. Parece bastante eficiente para mim.


Caso você esteja tentado a fazer isso em casa, devo salientar que, embora o código postado acima pisque dois LEDs, ele o faz muito rapidamente. De fato, eles piscam a 2 MHz - veja a captura de tela. O canal 1 (amarelo) é o pino 12, o canal 2 (ciano) é o pino 13.

Piscar rápido dos pinos 12 e 13

Como você pode ver, os pinos de saída têm uma onda quadrada com uma frequência de 2 MHz. O pino 13 muda de estado 62,5 ns (um ciclo de relógio) antes do pino 12, devido à ordem de alternância dos pinos no código.

Portanto, a menos que você tenha olhos muito melhores que os meus, não verá nenhum efeito piscante.


Como um extra divertido, você pode realmente alternar dois pinos na mesma quantidade de espaço do programa que alternar um.

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

Isso é compilado em 178 bytes.

Isso fornece uma frequência mais alta:

Pisca muito rápida dos pinos 12 e 13

Agora estamos com 2,66 MHz.

Nick Gammon
fonte
Isso faz muito sentido. Então, as bibliotecas padrão são apenas cabeçalhos incluídos automaticamente no momento da criação? E como você foi capaz de não incluí-los?
precisa saber é o seguinte
2
O vinculador retira agressivamente o código que não é usado. Por não chamar init()(como o normal, main()faz), então o wiring.c arquivo (que tem initnele) não estava ligada. Como resultado, o processamento para os manipuladores de interrupção (para millis(), micros()etc.) foi omitido. Provavelmente não é particularmente prático omití-lo, a menos que você nunca precise cronometrar as coisas, mas o fato é que o esboço cresce em tamanho dependendo do que você coloca nele. Por exemplo, se você usar Serial, a memória do programa e a RAM serão afetadas.
Nick Gammon