[Из песочницы] Тюнинг toolchain для Arduino для продолжающих

habr.png

Давным-давно случилось мне поработать над проектом с Arduino, где были довольно специфические требования к предсказуемости генерации кода, а работать с чёрным ящиком местами раздражало. Так родилась идея несколько поднастроить процесс сборки и внедрить некоторые дополнительные шаги при сборке.

Как известно, язык Wiring на самом деле является полноценным C++, из которого убрана поддержка исключений ввиду их тяжеловесности для среды выполнения и спрятаны некоторые тонкости, связанные со структурой программы, такие как реализация функции main, а разработчику отданы функции setup и loop, которые вызываются из main.

А это означает, что можно пользоваться плюшками C++, даже если о них никто не рассказал.

Всё описанное в статье относится к версиям среды Arduino как минимум от 1.6. Я делал всё под Windows, но для для Linux/MacOS подходы остаются теми же, просто меняются технические детали.

Итак, приступаем.

В среде версии 1.6 по умолчанию используется компилятор GCC версии 4.8, в котором по умолчанию не включена поддержка С++11, но нам же хочется auto, varadic templates и прочие плюшки, не правда ли? А в среде версии 1.8 компилятор уже версии 4.9, поддерживающий С++11 по умолчанию, но и его можно заставить использовать С++14 с его rvalue reference, константными выражениями, разделителями разрядов, двоичными литералами и прочими радостями.

Чтобы добавить поддержку более новых стандартов, чем включены по умолчанию, надо в каталоге установки Arduino открыть файл hardware\arduino\avr\platform.txt, содержащий настройки компиляции, найти там параметр compiler.cpp.flags и указать желаемый стандарт языка: -std=gnu++11 заменить на -std=gnu++14. Если параметра нету — добавить, например в конце строки.

После сохранения файла можно попробовать пересобрать скетч — среда подхватит новые настройки сразу же.

Здесь же можно изменить настройки оптимизации, например вместо более компактного кода изменением опции -Os на -Ofast включить генерацию более быстрого кода.

Эти настройки глобальные, поэтому применимы в рамках всех проектов.

Дальше я решил получить более детальный контроль над результатом работы компилятора и добавил некоторый аналог postbuild-событий. Для этого в тот же файл надо добавить параметр recipe.hooks.postbuild.0.pattern, значение которого должно содержать имя файла, который будет запущен после сборки, и аргументы командной строки. Я присвоил ему значение "{compiler.path}stat.bat" "{build.path}/{build.project_name}". Поскольку такого файла пока нет, его надо создать в каталоге hardware\tools\avr\bin (с именем, очевидно, stat.bat).

Напишем туда следующие строки:

"%~dp0\avr-objdump.exe" -d -C %1.elf > %1.SX
"%~dp0\avr-nm.exe" %1.elf -nCS > %1.names


Первая строка запускает декомпиляцию уже собранной и оптимизированной программы с помощью objdump, создавая выходной файл с именем проекта и дополнительным расширением .SX (выбор произвольный, но будем считать, что S — традиционно ассемблер, а X указывает на факт некоторого преобразования).

Вторая строка извлекает символьные имена областей памяти запуском команды nm и формирует отчёта в файле с дополнительным расширением .names, при этом сортировка выполняется по адресам.

Здесь надо ещё добавить, что сборка ведётся во временном каталоге, и чтобы его посмотреть, нужно зайти в настройки Arduino и включить чекбокс «Показать подробный вывод» → «Компиляция», после чего запустить эту самую компиляцию и посмотреть, где идёт сборка. В моём случае (IDE версии 1.8.5) это был каталог вида %TEMP%\arduino_build_[random].

Читать дизассемблированный код в обычной жизни — удовольствие сомнительное, но может кому-то понадобится (мне доводилось).

Файл имён же в целом более полезен. В спойлере — тестовый скетч, с которым выполнялись опыты:

Тестовый скетч
int counter = 1;

void test(auto&& x) {
  static unsigned long time;
  
  long t = millis();
  Serial.print(F("Counter: "));
  Serial.println(counter);
  Serial.print(F(" At "));
  Serial.print(t);
  Serial.print(F(" value is "));
  Serial.println(x);
  Serial.print(F("Time between iterations: "));
  Serial.println(t - time);
  Serial.println();
  time = t;
}

void setup() {
  Serial.begin(9600);
}

void loop() {  
  delay(100);
  test(digitalRead(A0));
}


Получившийся файл имён целиком тоже под спойлером:

Файл имён целиком

w serialEvent()
00000000 W __heap_end
00000000 a __tmp_reg__
00000000 a __tmp_reg__
00000000 a __tmp_reg__
00000000 a __tmp_reg__
00000000 a __tmp_reg__
00000000 a __tmp_reg__
00000000 a __tmp_reg__
00000000 a __tmp_reg__
00000000 a __tmp_reg__
00000000 W __vector_default
00000000 T __vectors
00000001 a __zero_reg__
00000001 a __zero_reg__
00000001 a __zero_reg__
00000001 a __zero_reg__
00000001 a __zero_reg__
00000001 a __zero_reg__
00000001 a __zero_reg__
00000001 a __zero_reg__
00000001 a __zero_reg__
0000003d a __SP_L__
0000003d a __SP_L__
0000003d a __SP_L__
0000003d a __SP_L__
0000003d a __SP_L__
0000003d a __SP_L__
0000003d a __SP_L__
0000003d a __SP_L__
0000003d a __SP_L__
0000003e a __SP_H__
0000003e a __SP_H__
0000003e a __SP_H__
0000003e a __SP_H__
0000003e a __SP_H__
0000003e a __SP_H__
0000003e a __SP_H__
0000003e a __SP_H__
0000003e a __SP_H__
0000003f a __SREG__
0000003f a __SREG__
0000003f a __SREG__
0000003f a __SREG__
0000003f a __SREG__
0000003f a __SREG__
0000003f a __SREG__
0000003f a __SREG__
0000003f a __SREG__
00000068 T __trampolines_end
00000068 T __trampolines_start
00000068 0000001a t test(int)::__c
00000082 0000000b t test(int)::__c
0000008d 00000005 t test(int)::__c
00000092 0000000a t test(int)::__c
0000009c 00000014 T digital_pin_to_timer_PGM
000000b0 00000014 T digital_pin_to_bit_mask_PGM
000000c4 00000014 T digital_pin_to_port_PGM
000000d8 0000000a T port_to_input_PGM
000000e2 T __ctors_start
000000e4 T __ctors_end
000000e4 T __dtors_end
000000e4 T __dtors_start
000000e4 W __init
000000f0 00000016 T __do_copy_data
00000106 00000010 T __do_clear_bss
0000010e t .do_clear_bss_loop
00000110 t .do_clear_bss_start
00000116 00000016 T __do_global_ctors
00000134 T __bad_interrupt
00000134 W __vector_1
00000134 W __vector_10
00000134 W __vector_11
00000134 W __vector_12
00000134 W __vector_13
00000134 W __vector_14
00000134 W __vector_15
00000134 W __vector_17
00000134 W __vector_2
00000134 W __vector_20
00000134 W __vector_21
00000134 W __vector_22
00000134 W __vector_23
00000134 W __vector_24
00000134 W __vector_25
00000134 W __vector_3
00000134 W __vector_4
00000134 W __vector_5
00000134 W __vector_6
00000134 W __vector_7
00000134 W __vector_8
00000134 W __vector_9
00000138 00000012 T setup
0000014a 000000d8 T loop
00000222 00000094 T __vector_16
000002b6 00000018 T millis
000002ce 00000046 T micros
00000314 00000050 T delay
00000364 00000076 T init
000003da 00000052 t turnOffPWM
0000042c 00000052 T digitalRead
0000047e 00000016 T HardwareSerial::available()
00000494 0000001c T HardwareSerial::peek()
000004b0 00000028 T HardwareSerial::read()
000004d8 000000b8 T HardwareSerial::write(unsigned char)
00000590 000000a4 T HardwareSerial::flush()
00000634 0000001c W serialEventRun()
00000650 00000042 T HardwareSerial::_tx_udr_empty_irq()
00000692 000000d0 T HardwareSerial::begin(unsigned long, unsigned char)
00000762 00000064 T __vector_18
000007c6 0000004c T __vector_19
00000812 00000014 T Serial0_available()
00000826 00000086 t _GLOBAL__sub_I___vector_18
000008ac 00000002 W initVariant
000008ae 00000028 T main
000008d6 00000058 T Print::write(unsigned char const*, unsigned int)
000008ff W __stack
0000092e 0000004a T Print::print(__FlashStringHelper const*)
00000978 00000242 T Print::print(long, int)
00000bba 00000016 T Print::println()
00000bd0 0000029a T Print::println(int, int)
00000e6a 0000013a T Print::println(unsigned long, int)
00000fa4 00000002 t __empty
00000fa4 00000002 W yield
00000fa6 00000044 T __udivmodsi4
00000fb2 t __udivmodsi4_loop
00000fcc t __udivmodsi4_ep
00000fea 00000004 T __tablejump2__
00000fee 00000008 T __tablejump__
00000ff6 T _exit
00000ff6 W exit
00000ff8 t __stop_program
00000ffa A __data_load_start
00000ffa T _etext
00001016 A __data_load_end
00800100 D __data_start
00800100 00000002 D counter
00800102 00000010 V vtable for HardwareSerial
0080011c B __bss_start
0080011c D __data_end
0080011c D _edata
0080011c 00000004 b test(int)::time
00800120 00000001 b timer0_fract
00800121 00000004 B timer0_millis
00800125 00000004 B timer0_overflow_count
00800129 0000009d B Serial
008001c6 B __bss_end
008001c6 N _end
00810000 N __eeprom_end

Как это читать? Давайте разбираться.

В контроллерах AVR две независимые области памяти: память программы и память данных. Соотвественно, указатель на код и указатель на данные — это не одно и то же. Про EEPROM пока молчу, там отдельный разговор про адресацию. В дампе память программы расположена по смещению 0 и с него начинается выполнение, и там же располагается таблица векторов прерываний, по 4 байта на элемент. В отличие от x86, каждый вектор прерываний содержит не адрес обработчика, а инструкцию, которую надо выполнить. Обычно (я не видел других вариантов) — jmp, то есть переход в нужный адрес. К векторам прерываний мы ещё потом ненадолго вернёмся, а пока смотрим дальше.

Память данных состоит из двух частей: регистровый файл со смещения 0 и данные в оперативной памяти (SRAM) со смещения 0×100. Когда выводится дамп памяти, то к адресам SRAM добавляется смещение 0×00800000, которое ничего не значит по сути, но просто удобно. Отмечу также, что смещение EEPROM имеет значение 0×00810000, но нам это не нужно.

Давайте начнём с данных, тут всё немного проще и привычнее, а потом вернёмся к коду. Первый столбец — смещение, второй — размер, дальше тип (пока не обращаем внимание), дальше до конца строки — имя.

Идём на смещение 0×00800100 и видим там записи:

00800100 D __data_start
00800100 00000002 D counter
00800102 00000010 V vtable for HardwareSerial
0080011c D __data_end


Это аналог секции .data и содержит инициализированные глобальные и статические переменные, а также таблицы виртуальных методов. Эти переменные будут проинициализированы bootstrap-кодом, что займёт некоторое время.

Дальше идём (пускай вас не смущает, что __bss_start и __data_end идут в произвольном порядке — они имеют общий адрес).

0080011c B __bss_start
0080011c 00000004 b test(int)::time
00800120 00000001 b timer0_fract
00800121 00000004 B timer0_millis
00800125 00000004 B timer0_overflow_count
00800129 0000009d B Serial
008001c6 B __bss_end


Это секция неинициализированных данных (аналог .bss). Она даётся даром, поскольку заполнена нулевыми байтами. Здесь несколько вспомогательных переменных, имена который начинаются с timer0_, глобальный объект Serial и наша статическая локальная переменная.

Маленькое лирическое отступление: обратите внимание, что объект Serial изначально пуст, и он будет проинициализирован, когда на нём будет вызван метод begin. Для этого есть несколько причин. Во-первых, он может вообще не использоваться (тогда он вообще не будет создан и место под него выделяться не будет), а во-вторых, мы же знаем, что порядок инициализации глобальных объектов не определён, и до входа в main строго говоря контроллер не инициализирован корректно.

По более высоким адресам располагается стек. Если объём памяти контроллера 2 килобайта (платы на базе процессора atmega328), то последний адрес будет 0×008008FF. С этого адреса начинается стек и растёт он в сторону уменьшения адресов. Нет никаких барьеров, и в принципе стек может наползти на переменные, испортив их. Или данные могут испортить стек — как повезёт. В этом случае ничего хорошего не произойдёт. Можно это назвать неопределённым поведением, но лучше всегда в памяти оставлять достаточно места для стека. Сколько — сложно сказать. Чтобы хватило на локальные переменные и кадры стека всех вызовов плюс для самого тяжёлого обработчика прерывания со всеми вложенными вызовами. Я, честно говоря, более-менее честного решения относительно того, как диагностировать использование стека, не нашёл.

Давайте вернёмся к коду. Когда я говорил, что там хранится код, я немного лукавил. На самом деле там могут храниться и данные, доступные только для чтения, но извлекаются они оттуда специальной инструкцией процессора LPM. Так вот, константные данные можно размещать там и размещать даром, потому как на их инициализацию не тратится время. Обратите внимание на макросы F (…) в коде. Они как раз объявляют строки размещёнными в программной памяти и дальше делает немножко магии. Не будем вдаваться в подробности магии, а поищем их в памяти. Такие макросы идут в функции test, и в дампе мы их увидим как

00000068 0000001a t test(int)::__c
00000082 0000000b t test(int)::__c
0000008d 00000005 t test(int)::__c
00000092 0000000a t test(int)::__c


Несмотря на то, что они имеют одинаковые имена, они находятся по разным адресам. Потому что макрос F (…) создаёт область видимости в фигурных скобках, и переменные (точнее константы) друг другу не мешают. Если посмотреть дизассемблерный дамп, то это и будут адреса строк в программной памяти.

И напоследок обещанное о прерываниях. Библиотеки используют некоторые ресурсы контроллера для поддержки работы периферии: таймеры, коммуникационные интерфейсы и т.п. Они регистрируют свои обработчики прерывания, хранят в памяти свои переменные, а в программной памяти — константы. Если скомпилировать пустой скетч, то дамп памяти будет такой:

Дамп памяти пустого скетча
w serialEventRun()
00000000 W __heap_end
00000000 a __tmp_reg__
00000000 a __tmp_reg__
00000000 a __tmp_reg__
00000000 a __tmp_reg__
00000000 W __vector_default
00000000 T __vectors
00000001 a __zero_reg__
00000001 a __zero_reg__
00000001 a __zero_reg__
00000001 a __zero_reg__
0000003d a __SP_L__
0000003d a __SP_L__
0000003d a __SP_L__
0000003d a __SP_L__
0000003e a __SP_H__
0000003e a __SP_H__
0000003e a __SP_H__
0000003e a __SP_H__
0000003f a __SREG__
0000003f a __SREG__
0000003f a __SREG__
0000003f a __SREG__
00000068 T __ctors_end
00000068 T __ctors_start
00000068 T __dtors_end
00000068 T __dtors_start
00000068 W __init
00000068 T __trampolines_end
00000068 T __trampolines_start
00000074 00000010 T __do_clear_bss
0000007c t .do_clear_bss_loop
0000007e t .do_clear_bss_start
0000008c T __bad_interrupt
0000008c W __vector_1
0000008c W __vector_10
0000008c W __vector_11
0000008c W __vector_12
0000008c W __vector_13
0000008c W __vector_14
0000008c W __vector_15
0000008c W __vector_17
0000008c W __vector_18
0000008c W __vector_19
0000008c W __vector_2
0000008c W __vector_20
0000008c W __vector_21
0000008c W __vector_22
0000008c W __vector_23
0000008c W __vector_24
0000008c W __vector_25
0000008c W __vector_3
0000008c W __vector_4
0000008c W __vector_5
0000008c W __vector_6
0000008c W __vector_7
0000008c W __vector_8
0000008c W __vector_9
00000090 00000002 T setup
00000092 00000002 T loop
00000094 00000002 W initVariant
00000096 00000028 T main
000000be 00000094 T __vector_16
00000152 00000076 T init
000001c8 T _exit
000001c8 W exit
000001ca t __stop_program
000001cc A __data_load_end
000001cc a __data_load_start
000001cc T _etext
000008ff W __stack
00800100 B __bss_start
00800100 D _edata
00800100 00000001 b timer0_fract
00800101 00000004 B timer0_millis
00800105 00000004 B timer0_overflow_count
00800109 B __bss_end
00800109 N _end
00810000 N __eeprom_end


Немало, неправда ли?

А вот код:

Дизассемблированный код
/empty.cpp.elf:     file format elf32-avr

Disassembly of section .text:

00000000 <__vectors>:
   0:	0c 94 34 00 	jmp	0x68	; 0x68 <__ctors_end>
   4:	0c 94 46 00 	jmp	0x8c	; 0x8c <__bad_interrupt>
   8:	0c 94 46 00 	jmp	0x8c	; 0x8c <__bad_interrupt>
   c:	0c 94 46 00 	jmp	0x8c	; 0x8c <__bad_interrupt>
  10:	0c 94 46 00 	jmp	0x8c	; 0x8c <__bad_interrupt>
  14:	0c 94 46 00 	jmp	0x8c	; 0x8c <__bad_interrupt>
  18:	0c 94 46 00 	jmp	0x8c	; 0x8c <__bad_interrupt>
  1c:	0c 94 46 00 	jmp	0x8c	; 0x8c <__bad_interrupt>
  20:	0c 94 46 00 	jmp	0x8c	; 0x8c <__bad_interrupt>
  24:	0c 94 46 00 	jmp	0x8c	; 0x8c <__bad_interrupt>
  28:	0c 94 46 00 	jmp	0x8c	; 0x8c <__bad_interrupt>
  2c:	0c 94 46 00 	jmp	0x8c	; 0x8c <__bad_interrupt>
  30:	0c 94 46 00 	jmp	0x8c	; 0x8c <__bad_interrupt>
  34:	0c 94 46 00 	jmp	0x8c	; 0x8c <__bad_interrupt>
  38:	0c 94 46 00 	jmp	0x8c	; 0x8c <__bad_interrupt>
  3c:	0c 94 46 00 	jmp	0x8c	; 0x8c <__bad_interrupt>
  40:	0c 94 5f 00 	jmp	0xbe	; 0xbe <__vector_16>
  44:	0c 94 46 00 	jmp	0x8c	; 0x8c <__bad_interrupt>
  48:	0c 94 46 00 	jmp	0x8c	; 0x8c <__bad_interrupt>
  4c:	0c 94 46 00 	jmp	0x8c	; 0x8c <__bad_interrupt>
  50:	0c 94 46 00 	jmp	0x8c	; 0x8c <__bad_interrupt>
  54:	0c 94 46 00 	jmp	0x8c	; 0x8c <__bad_interrupt>
  58:	0c 94 46 00 	jmp	0x8c	; 0x8c <__bad_interrupt>
  5c:	0c 94 46 00 	jmp	0x8c	; 0x8c <__bad_interrupt>
  60:	0c 94 46 00 	jmp	0x8c	; 0x8c <__bad_interrupt>
  64:	0c 94 46 00 	jmp	0x8c	; 0x8c <__bad_interrupt>

00000068 <__ctors_end>:
  68:	11 24       	eor	r1, r1
  6a:	1f be       	out	0x3f, r1	; 63
  6c:	cf ef       	ldi	r28, 0xFF	; 255
  6e:	d8 e0       	ldi	r29, 0x08	; 8
  70:	de bf       	out	0x3e, r29	; 62
  72:	cd bf       	out	0x3d, r28	; 61

00000074 <__do_clear_bss>:
  74:	21 e0       	ldi	r18, 0x01	; 1
  76:	a0 e0       	ldi	r26, 0x00	; 0
  78:	b1 e0       	ldi	r27, 0x01	; 1
  7a:	01 c0       	rjmp	.+2      	; 0x7e <.do_clear_bss_start>

0000007c <.do_clear_bss_loop>:
  7c:	1d 92       	st	X+, r1

0000007e <.do_clear_bss_start>:
  7e:	a9 30       	cpi	r26, 0x09	; 9
  80:	b2 07       	cpc	r27, r18
  82:	e1 f7       	brne	.-8      	; 0x7c <.do_clear_bss_loop>
  84:	0e 94 4b 00 	call	0x96	; 0x96 
88: 0c 94 e4 00 jmp 0x1c8 ; 0x1c8 <_exit> 0000008c <__bad_interrupt>: 8c: 0c 94 00 00 jmp 0 ; 0x0 <__vectors> 00000090 : 90: 08 95 ret 00000092 : 92: 08 95 ret 00000094 : 94: 08 95 ret 00000096
: 96: 0e 94 a9 00 call 0x152 ; 0x152 9a: 0e 94 4a 00 call 0x94 ; 0x94 9e: 0e 94 48 00 call 0x90 ; 0x90 a2: 80 e0 ldi r24, 0x00 ; 0 a4: 90 e0 ldi r25, 0x00 ; 0 a6: 89 2b or r24, r25 a8: 29 f0 breq .+10 ; 0xb4 aa: 0e 94 49 00 call 0x92 ; 0x92 ae: 0e 94 00 00 call 0 ; 0x0 <__vectors> b2: fb cf rjmp .-10 ; 0xaa b4: 0e 94 49 00 call 0x92 ; 0x92 b8: 0e 94 49 00 call 0x92 ; 0x92 bc: fb cf rjmp .-10 ; 0xb4 000000be <__vector_16>: be: 1f 92 push r1 c0: 0f 92 push r0 c2: 0f b6 in r0, 0x3f ; 63 c4: 0f 92 push r0 c6: 11 24 eor r1, r1 c8: 2f 93 push r18 ca: 3f 93 push r19 cc: 8f 93 push r24 ce: 9f 93 push r25 d0: af 93 push r26 d2: bf 93 push r27 d4: 80 91 01 01 lds r24, 0x0101 d8: 90 91 02 01 lds r25, 0x0102 dc: a0 91 03 01 lds r26, 0x0103 e0: b0 91 04 01 lds r27, 0x0104 e4: 30 91 00 01 lds r19, 0x0100 e8: 23 e0 ldi r18, 0x03 ; 3 ea: 23 0f add r18, r19 ec: 2d 37 cpi r18, 0x7D ; 125 ee: 20 f4 brcc .+8 ; 0xf8 <__vector_16+0x3a> f0: 01 96 adiw r24, 0x01 ; 1 f2: a1 1d adc r26, r1 f4: b1 1d adc r27, r1 f6: 05 c0 rjmp .+10 ; 0x102 <__vector_16+0x44> f8: 26 e8 ldi r18, 0x86 ; 134 fa: 23 0f add r18, r19 fc: 02 96 adiw r24, 0x02 ; 2 fe: a1 1d adc r26, r1 100: b1 1d adc r27, r1 102: 20 93 00 01 sts 0x0100, r18 106: 80 93 01 01 sts 0x0101, r24 10a: 90 93 02 01 sts 0x0102, r25 10e: a0 93 03 01 sts 0x0103, r26 112: b0 93 04 01 sts 0x0104, r27 116: 80 91 05 01 lds r24, 0x0105 11a: 90 91 06 01 lds r25, 0x0106 11e: a0 91 07 01 lds r26, 0x0107 122: b0 91 08 01 lds r27, 0x0108 126: 01 96 adiw r24, 0x01 ; 1 128: a1 1d adc r26, r1 12a: b1 1d adc r27, r1 12c: 80 93 05 01 sts 0x0105, r24 130: 90 93 06 01 sts 0x0106, r25 134: a0 93 07 01 sts 0x0107, r26 138: b0 93 08 01 sts 0x0108, r27 13c: bf 91 pop r27 13e: af 91 pop r26 140: 9f 91 pop r25 142: 8f 91 pop r24 144: 3f 91 pop r19 146: 2f 91 pop r18 148: 0f 90 pop r0 14a: 0f be out 0x3f, r0 ; 63 14c: 0f 90 pop r0 14e: 1f 90 pop r1 150: 18 95 reti 00000152 : 152: 78 94 sei 154: 84 b5 in r24, 0x24 ; 36 156: 82 60 ori r24, 0x02 ; 2 158: 84 bd out 0x24, r24 ; 36 15a: 84 b5 in r24, 0x24 ; 36 15c: 81 60 ori r24, 0x01 ; 1 15e: 84 bd out 0x24, r24 ; 36 160: 85 b5 in r24, 0x25 ; 37 162: 82 60 ori r24, 0x02 ; 2 164: 85 bd out 0x25, r24 ; 37 166: 85 b5 in r24, 0x25 ; 37 168: 81 60 ori r24, 0x01 ; 1 16a: 85 bd out 0x25, r24 ; 37 16c: ee e6 ldi r30, 0x6E ; 110 16e: f0 e0 ldi r31, 0x00 ; 0 170: 80 81 ld r24, Z 172: 81 60 ori r24, 0x01 ; 1 174: 80 83 st Z, r24 176: e1 e8 ldi r30, 0x81 ; 129 178: f0 e0 ldi r31, 0x00 ; 0 17a: 10 82 st Z, r1 17c: 80 81 ld r24, Z 17e: 82 60 ori r24, 0x02 ; 2 180: 80 83 st Z, r24 182: 80 81 ld r24, Z 184: 81 60 ori r24, 0x01 ; 1 186: 80 83 st Z, r24 188: e0 e8 ldi r30, 0x80 ; 128 18a: f0 e0 ldi r31, 0x00 ; 0 18c: 80 81 ld r24, Z 18e: 81 60 ori r24, 0x01 ; 1 190: 80 83 st Z, r24 192: e1 eb ldi r30, 0xB1 ; 177 194: f0 e0 ldi r31, 0x00 ; 0 196: 80 81 ld r24, Z 198: 84 60 ori r24, 0x04 ; 4 19a: 80 83 st Z, r24 19c: e0 eb ldi r30, 0xB0 ; 176 19e: f0 e0 ldi r31, 0x00 ; 0 1a0: 80 81 ld r24, Z 1a2: 81 60 ori r24, 0x01 ; 1 1a4: 80 83 st Z, r24 1a6: ea e7 ldi r30, 0x7A ; 122 1a8: f0 e0 ldi r31, 0x00 ; 0 1aa: 80 81 ld r24, Z 1ac: 84 60 ori r24, 0x04 ; 4 1ae: 80 83 st Z, r24 1b0: 80 81 ld r24, Z 1b2: 82 60 ori r24, 0x02 ; 2 1b4: 80 83 st Z, r24 1b6: 80 81 ld r24, Z 1b8: 81 60 ori r24, 0x01 ; 1 1ba: 80 83 st Z, r24 1bc: 80 81 ld r24, Z 1be: 80 68 ori r24, 0x80 ; 128 1c0: 80 83 st Z, r24 1c2: 10 92 c1 00 sts 0x00C1, r1 1c6: 08 95 ret 000001c8 <_exit>: 1c8: f8 94 cli 000001ca <__stop_program>: 1ca: ff cf rjmp .-2 ; 0x1ca <__stop_program>


Задействовано прерывание таймера, 9 байт на обслуживание этого таймера, и некоторое количество инфраструктурного кода.

На сегодня, пожалуй, хватит. Надеюсь, кому-нибудь это пригодится.

© Habrahabr.ru