您已经有了一些非常好的答案。我发布此内容只是为了分享有一天我做的一些统计数据,我问自己同样的问题:在最小的草图上占用这么多空间是什么?实现相同功能的最低要求是多少?
以下是最小闪烁程序的三个版本,该程序每秒切换引脚13上的LED。所有三个版本均已使用avr-gcc 4.8.2,avr-libc 1.8.0和arduino-core 1.0.5(我不使用Arduino IDE)针对Uno(不涉及USB)进行编译。
首先,标准的Arduino方式:
const uint8_t ledPin = 13;
void setup() {
pinMode(ledPin, OUTPUT);
}
void loop() {
digitalWrite(ledPin, HIGH);
delay(1000);
digitalWrite(ledPin, LOW);
delay(1000);
}
编译为1018字节。通过同时使用avr-nm
和反汇编,我将大小分解为各个功能。从最大到最小:
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
在上面的列表中,第一列是字节大小,第二列表明代码是来自Arduino核心库(A,总共822个字节),C运行时(C,148个字节)还是用户(U) ,48个字节)。
从该列表可以看出,最大的功能是为定时器0溢出中断提供服务的例程。该程序负责跟踪时间的,由需要的millis()
,micros()
和delay()
。第二大功能是init()
,它为PWM设置硬件定时器,使能TIMER0_OVF中断并断开USART(由引导程序使用)。此函数和上一个函数都在中定义
<Arduino directory>/hardware/arduino/cores/arduino/wiring.c
。
接下来是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);
}
}
各个尺寸的细分:
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
对于C运行时,这是132个字节,包括内联函数的用户代码是26个字节_delay_ms()
。
应该指出的是,由于该程序不使用中断,因此不需要中断向量表,并且可以将常规用户代码放在其位置。以下程序集版本正是这样做的:
#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
它avr-gcc -nostdlib
仅用14个字节组合在一起(带有),其中大部分用于延迟切换,以便可以看到闪烁。如果删除该延迟循环,最终将得到一个6字节的程序,该程序闪烁得太快而看不见(在2 MHz时):
sbi io(DDRB), 5 ; set PB5 as output
loop:
sbi io(PINB), 5 ; toggle PB5
rjmp loop