Tại sao các bản phác thảo chiếm quá nhiều không gian và bộ nhớ?


12

Khi tôi biên dịch bản phác thảo này cho Yún:

int led = 7;

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

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

Tôi có:

Phác thảo sử dụng 5.098 byte (17%) không gian lưu trữ chương trình.

Tối đa là 28.672 byte. Biến toàn cục sử dụng 153 byte (5%) bộ nhớ động, để lại 2.407 byte cho biến cục bộ. Tối đa là 2.560 byte.

Ngay cả khi tôi biên dịch bản phác thảo BareMinimum:

void setup() {                
  // setup    
}

void loop() {
  // loop
}

Tôi có:

Phác thảo sử dụng 4.548 byte (15%) không gian lưu trữ chương trình.

Tối đa là 28.672 byte. Biến toàn cục sử dụng 151 byte (5%) bộ nhớ động, để lại 2.409 byte cho biến cục bộ. Tối đa là 2.560 byte.

Tại sao một bản phác thảo tối thiểu chiếm 15% không gian lưu trữ chương trình được phân bổ? Và tại sao một bản phác thảo rất đơn giản chiếm 17% không gian lưu trữ chương trình? Theo trang web Arduino :

Thật dễ dàng để sử dụng tất cả bằng cách có nhiều chuỗi trong chương trình của bạn. Ví dụ: một khai báo như: char message[] = "I support the Cape Wind project.";đặt 33 byte vào SRAM (mỗi ký tự lấy một byte, cộng với dấu kết thúc '\ 0').

Tuy nhiên, không có bất kỳ chuỗi nào được khai báo trong một trong hai bản phác thảo này.

Có vẻ như họ có thể nhập hoặc sử dụng các thư viện / lớp khác mà tôi không chỉ định. Có lẽ nó nhập một thư viện mặc định hệ thống? Hay nó là cái gì khác?

Câu trả lời:


6

YUN là một kết hợp. Phần Arduino và Phần OpenWRT (Linux). Câu hỏi của bạn có liên quan đến Arduino. Trong đó đây thực sự là một ATmega32u4 tương tự như Leonardo và không phải là UNO (ATmega328p). 32u4 (Leo) giao tiếp qua Cổng nối tiếp ảo qua USB (câu trả lời ngắn gọn: điều này cần được hỗ trợ) , trong đó UNO có Cổng nối tiếp thực sự (còn gọi là UART). Dưới đây là số liệu thống kê xây dựng của các loại bo mạch khác nhau cho bộ xử lý AVR.

Lưu ý trên UNO có một chip bên ngoài chuyển đổi USB thành chân DTR của cổng Nối tiếp để bật chân đặt lại của ATmega328 khi được kết nối gây ra khởi động lại cho bộ tải khởi động. Ngược lại, USB / serial của Leo / Yun được triển khai trong phần sụn của 32u4. Do đó, để khởi động lại từ xa chip 32u4 của Leo hoặc YUN, phần sụn được tải phải luôn hỗ trợ trình điều khiển phía máy khách USB. Mà tiêu thụ khoảng 4K.

Nếu USB là KHÔNG cần thiết và không có tài nguyên thư viện nào khác được gọi như trong trường hợp BareMinimum.ino trên UNO, thì chỉ cần khoảng 466 byte cho Thư viện Arduino lõi.

tổng hợp số liệu thống kê của BareMinimum.ino trên 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.

tổng hợp số liệu thống kê của BareMinimum.ino trên 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.

tổng hợp số liệu thống kê của BareMinimum.ino trên 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 biên dịch trong rất nhiều thư viện tiêu chuẩn, ngắt, ... v.v. Ví dụ, các hàm pinMode và digitalWrite sử dụng bảng tra cứu để tìm ra thời gian chạy mà GPIO đăng ký để ghi dữ liệu. Một ví dụ khác là Arduino theo dõi thời gian, nó xác định một số ngắt theo mặc định và tất cả chức năng này đòi hỏi một số không gian. Bạn sẽ nhận thấy rằng nếu bạn mở rộng chương trình, dấu chân sẽ chỉ thay đổi một chút.

Cá nhân tôi thích lập trình các bộ điều khiển ở mức tối thiểu, không "phình to", nhưng bạn sẽ nhanh chóng bước vào thế giới của EE.SE và SO vì một số chức năng dễ sử dụng sẽ không còn hoạt động nữa. Có một số thư viện thay thế cho pinMode và digitalWrite biên dịch thành một dấu chân nhỏ hơn, nhưng đi kèm với các nhược điểm khác như các chân được biên dịch tĩnh (trong đó ledkhông thể là một biến, nhưng là một hằng số).


Vì vậy, về cơ bản, nó biên dịch trong tất cả các loại thư viện tiêu chuẩn mà không cần bạn hỏi? Khéo léo.
hichris123

Vâng, tôi thường gọi nó là "phình to", nhưng nó thực sự là một thứ có thể sử dụng được. Arduino là một môi trường cấp thấp, chỉ hoạt động mà không cần suy nghĩ quá nhiều. Nếu bạn cần nhiều hơn, Arduino cho phép bạn sử dụng các thư viện thay thế hoặc bạn có thể biên dịch chống lại kim loại trần. Cái cuối cùng có lẽ nằm ngoài phạm vi của Arduino.SE
jippie

Xem câu trả lời @mpflaga của tôi. Không có nhiều như vậy. Hoặc ít nhất là trong thư viện lõi cho chức năng tối thiểu. Không thực sự có nhiều thư viện tiêu chuẩn, trừ khi được gọi là bản phác thảo. Thay vào đó, 15% là do hỗ trợ USB của 32u4.
mpflaga

4

Bạn đã có một số câu trả lời hoàn toàn tốt. Tôi đang đăng bài này chỉ để chia sẻ một số thống kê tôi đã làm vào một ngày, tôi đã tự hỏi mình cùng một loại câu hỏi: Điều gì đang chiếm quá nhiều không gian trên một bản phác thảo tối thiểu? Tối thiểu cần thiết để đạt được chức năng tương tự là gì?

Dưới đây là ba phiên bản của một chương trình mờ tối thiểu làm bật đèn LED trên chân 13 mỗi giây. Tất cả ba phiên bản đã được biên dịch cho Uno (không có USB) sử dụng avr-gcc 4.8.2, avr-libc 1.8.0 và arduino-core 1.0.5 (Tôi không sử dụng Arduino IDE).

Đầu tiên, cách Arduino chuẩn:

const uint8_t ledPin = 13;

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

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

Điều này biên dịch thành 1018 byte. Sử dụng cả hai avr-nmvà tháo gỡ , tôi chia kích thước đó thành các chức năng riêng lẻ. Từ lớn nhất đến nhỏ nhất:

 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

Trong danh sách trên, cột đầu tiên là kích thước tính theo byte và cột thứ hai cho biết mã đến từ thư viện lõi Arduino (tổng A, 822 byte), thời gian chạy C (C, 148 byte) hay người dùng (U , 48 byte).

Như có thể thấy trong danh sách này, chức năng lớn nhất là thường trình phục vụ ngắt tràn bộ định thời 0. Thủ tục này có trách nhiệm theo dõi thời gian, và cần thiết millis(), micros()delay(). Chức năng lớn thứ hai là init(), thiết lập bộ định thời phần cứng cho PWM, cho phép ngắt TIMER0_OVF và ngắt kết nối USART (được sử dụng bởi bộ tải khởi động). Cả hàm này và hàm trước đều được định nghĩa trong <Arduino directory>/hardware/arduino/cores/arduino/wiring.c.

Tiếp theo là phiên bả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);
    }
}

Bảng phân tích kích thước riêng:

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

Đây là 132 byte cho thời gian chạy C và 26 byte mã người dùng, bao gồm cả hàm được nội tuyến _delay_ms().

Có thể lưu ý rằng, vì chương trình này không sử dụng các ngắt, nên bảng vectơ ngắt là không cần thiết và mã người dùng thông thường có thể được đặt vào vị trí của nó. Phiên bản lắp ráp sau đây thực hiện chính xác điều đó:

#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

Điều này được tập hợp (với avr-gcc -nostdlib) chỉ thành 14 byte, hầu hết được sử dụng để trì hoãn các toggles để có thể nhìn thấy chớp mắt. Nếu bạn loại bỏ vòng lặp trì hoãn đó, bạn sẽ thấy chương trình 6 byte nhấp nháy quá nhanh để có thể nhìn thấy (ở 2 MHz):

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

3

Tôi đã viết một bài về lý do tại sao phải mất 1000 byte để nhấp nháy một đèn LED? .

Câu trả lời ngắn gọn là: "Không cần 2000 byte để nhấp nháy hai đèn LED!"

Câu trả lời dài hơn là các thư viện Arduino tiêu chuẩn (mà bạn không phải sử dụng nếu bạn không muốn) có một số chức năng hay để đơn giản hóa cuộc sống của bạn. Ví dụ, bạn có thể giải quyết các chân theo số trong thời gian chạy, trong đó thư viện chuyển đổi (giả sử) chân 8 sang cổng chính xác và số bit chính xác. Nếu bạn truy cập cổng mã cứng, bạn có thể lưu chi phí đó.

Ngay cả khi bạn không sử dụng chúng, các thư viện tiêu chuẩn bao gồm mã để đếm "tick" để bạn có thể tìm ra "thời gian" hiện tại (bằng cách gọi millis()). Để làm điều này, nó phải thêm chi phí của một số thói quen dịch vụ ngắt.

Nếu bạn đơn giản hóa (trên Arduino Uno) cho bản phác thảo này, bạn sẽ giảm mức sử dụng bộ nhớ chương trình xuống còn 178 byte (trên IDE 1.0.6):

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

OK, 178 byte không phải là nhiều, và trong đó 104 byte đầu tiên là các vectơ ngắt phần cứng (mỗi 4 byte, cho 26 vectơ).

Vì vậy, có thể nói, chỉ có 74 byte cần thiết để nhấp nháy đèn LED. Và trong số 74 byte đó thực sự là mã do trình biên dịch tạo ra để khởi tạo bộ nhớ chung. Nếu bạn thêm đủ mã để nhấp nháy hai đèn 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
    }
  }

Sau đó, kích thước mã tăng lên 186 byte. Vì vậy, bạn có thể lập luận rằng nó chỉ mất 186 - 178 = 8byte để nhấp nháy đèn LED.

Vì vậy, 8 byte để nhấp nháy một đèn LED. Âm thanh khá hiệu quả với tôi.


Trong trường hợp bạn muốn thử điều này ở nhà, tôi nên chỉ ra rằng trong khi mã được đăng ở trên nhấp nháy hai đèn LED, thì nó thực sự rất nhanh. Trên thực tế, chúng nhấp nháy ở mức 2 MHz - xem ảnh chụp màn hình. Kênh 1 (màu vàng) là chân 12, kênh 2 (màu lục lam) là chân 13.

Nhấp nháy nhanh chân 12 và 13

Như bạn có thể thấy, các chân đầu ra có sóng vuông với tần số 2 MHz. Chân 13 thay đổi trạng thái 62,5 ns (một chu kỳ xung nhịp) trước chân 12, do thứ tự chuyển đổi của các chân trong mã.

Vì vậy, trừ khi bạn có đôi mắt tốt hơn tôi nhiều, bạn sẽ không thực sự thấy bất kỳ hiệu ứng nhấp nháy nào.


Là một bổ sung thú vị, bạn thực sự có thể chuyển đổi hai chân trong cùng một không gian chương trình như chuyển đổi một 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

Nó biên dịch thành 178 byte.

Điều này cung cấp cho bạn tần suất cao hơn:

Nhấp nháy rất nhanh chân 12 và 13

Bây giờ chúng tôi lên đến 2,66 MHz.


Điều này làm cho một tấn ý nghĩa. Vì vậy, các thư viện tiêu chuẩn chỉ là tiêu đề bao gồm tự động tại thời điểm xây dựng? Và làm thế nào bạn có thể không bao gồm chúng?
hichris123

2
Trình liên kết mạnh mẽ loại bỏ mã không được sử dụng. Bằng cách không gọi init()(như bình thường main()), thì tệp dây.c (có inittrong đó) không được liên kết. Do đó, việc xử lý các trình xử lý ngắt (ví dụ millis(), micros()v.v.) đã bị bỏ qua. Có lẽ nó không thực sự đặc biệt để bỏ qua nó, trừ khi bạn không bao giờ cần thời gian, nhưng thực tế là bản phác thảo tăng kích thước tùy thuộc vào những gì bạn đặt trong đó. Ví dụ: nếu bạn sử dụng Nối tiếp, cả bộ nhớ chương trình và RAM đều bị ảnh hưởng.
Nick Gammon
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.