В статье habrahabr.ru/post/151544/ и комментариях к ней возник вопрос, почему разные компиляторы выдают код не только разного размера, но и разной эффективности. Если с первым фактором еще можно мириться, то второй может свести все ваши усилия в написании программы на нет. Это на PC еще можно добавить памяти до 32 Гб и поставить 16 ядерный процессор на 10 ГГц, чтобы косынка на NET8.0 не тормозила, а embedded система — девица более изысканная и утонченная.
Итак, сравниваем компиляторы:
1. BASCOM-AVR
2. Wiring
3. С CodeVisionAVR.
Прошивки, любезно предоставленные для исследования товарищем vvzvlad, дизассемблируем с помощью инструмента всех времен и народов IDA. Исходные коды приведены в комментарии habrahabr.ru/post/151544/#comment_5141741.
В результате изучения ассемблерных «исходников» можно отметить, что BASCOM и С CVAVR делают практически одинаковый и эффективный код, ардуиновский Wiring по сравнению с ними ловит тунцов. :)
Аперитив
Коды инициализации особого интереса не представляют, но они есть. BASCOM и C используют только то, что требуется программе, Wiring имеет чуть более чем больший инициализационный код, а также обрабатывает прерывание по таймеру Timer0, хотя в программе он нам не нужен.
К примеру, инициализация по версии BASCOM:
ROM:0034 __RESET: ; CODE XREF: ROM:0000
ROM:0034 ser r24
ROM:0035 out SPL, r24
ROM:0036 ldi r28, -0x20 ; 'р'
ROM:0037 ldi r30, -0x38 ; 'L'
ROM:0038 mov r4, r30
ROM:0039 ldi r24, 4
ROM:003A out SPH, r24
ROM:003B ldi r29, 4
ROM:003C ldi r31, 4
ROM:003D mov r5, r31
ROM:003E wdr
ROM:003F in r24, MCUSR
ROM:0040 mov r0, r24
ROM:0041 andi r24, -9
ROM:0042 out MCUSR, r24
ROM:0043 ldi r24, 0x18
ROM:0044 clr r25
ROM:0045 sts WDTCSR, r24
ROM:0047 sts WDTCSR, r25
ROM:0049 ldi r30, -2 ; '¦'
ROM:004A ldi r31, 3
ROM:004B ldi r26, 0
ROM:004C ldi r27, 1
ROM:004D clr r24
ROM:004E
ROM:004E loc_4E: ; CODE XREF: __RESET+1C
ROM:004E st X+, r24
ROM:004F sbiw r30, 1
ROM:0050 brne loc_4E
ROM:0051 clr r6
ROM:0052 sbi DDRB, DDB5
Cи CVAVR хочет от мелкоконтроллера почти того же самого, но кода побольше:
ROM:0034 __RESET: ; CODE XREF:
ROM:0034 cli
ROM:0035 clr r30
ROM:0036 out EECR, r30
ROM:0037 ldi r31, 1
ROM:0038 out MCUCR, r31
ROM:0039 out MCUCR, r30
ROM:003A ldi r31, 0x18
ROM:003B wdr
ROM:003C in r26, MCUSR
ROM:003D andi r26, -9
ROM:003E out MCUSR, r26
ROM:003F sts WDTCSR, r31
ROM:0041 sts WDTCSR, r30
ROM:0043 ldi r24, 0xD
ROM:0044 ldi r26, 2
ROM:0045 clr r27
ROM:0046
ROM:0046 loc_46: ; CODE XREF: __RESET+14
ROM:0046 st X+, r30
ROM:0047 dec r24
ROM:0048 brne loc_46
ROM:0049 ldi r24, 0
ROM:004A ldi r25, 4
ROM:004B ldi r26, 0
ROM:004C ldi r27, 1
ROM:004D
ROM:004D loc_4D: ; CODE XREF: __RESET+1B
ROM:004D st X+, r30
ROM:004E sbiw r24, 1
ROM:004F brne loc_4D
ROM:0050 ldi r30, 0
ROM:0051 out GPIOR0, r30
ROM:0052 ser r30
ROM:0053 out SPL, r30
ROM:0054 ldi r30, 4
ROM:0055 out SPH, r30
ROM:0056 ldi r28, 0
ROM:0057 ldi r29, 2
ROM:0058 jmp loc_5A
ROM:005A
ROM:005A loc_5A:
ROM:005A ldi r30, -0x80 ; 'А'
ROM:005B sts CLKPR, r30
ROM:005D ldi r30, 0
ROM:005E sts CLKPR, r30
ROM:0060 ldi r30, 0x20 ; ' '
ROM:0061 out DDRB, r30
Инициализация Wiring №7:
ROM:0061 __RESET: ; CODE XREF: j___RESET
ROM:0061
ROM:0061 ; FUNCTION CHUNK AT ROM:015B SIZE 00000002 BYTES
ROM:0061
ROM:0061 clr r1
ROM:0062 out SREG, r1
ROM:0063 ser r28
ROM:0064 ldi r29, 4
ROM:0065 out SPH, r29
ROM:0066 out SPL, r28
ROM:0067 ldi r17, 1
ROM:0068 ldi r26, 0
ROM:0069 ldi r27, 1
ROM:006A ldi r30, -0x46 ; '¦'
ROM:006B ldi r31, 2
ROM:006C rjmp loc_6F
ROM:006D ; ---------------------------------------------------------------------------
ROM:006D
ROM:006D loc_6D: ; CODE XREF: __RESET
ROM:006D lpm r0, Z+
ROM:006E st X+, r0
ROM:006F
ROM:006F loc_6F: ; CODE XREF: __RESET
ROM:006F cpi r26, 0
ROM:0070 cpc r27, r17
ROM:0071 brne loc_6D
ROM:0072 ldi r17, 1
ROM:0073 ldi r26, 0
ROM:0074 ldi r27, 1
ROM:0075 rjmp loc_77
ROM:0076 ; ---------------------------------------------------------------------------
ROM:0076
ROM:0076 loc_76: ; CODE XREF: __RESET+18
ROM:0076 st X+, r1
ROM:0077
ROM:0077 loc_77: ; CODE XREF: __RESET+14
ROM:0077 cpi r26, 9
ROM:0078 cpc r27, r17
ROM:0079 brne loc_76
ROM:007A call sub_C9
ROM:007C jmp loc_15B
За строкой «007A call sub_C9» притаились вложенные функции, которые что-то зачем-то инициализируют ;)
Но, собственно, какие проблемы, стартовать системе 1 мс или 10 мс или даже 100 мс? Подробные подробности следующим письмом оставим на закуску.
Основное блюдо
Что делает с полезным кодом компилятор BASCOM:
1.hex
Do
Portb = 0
Portb = 255
Loop
Превращается в:
ROM:0053 loc_53: ; CODE XREF: __RESET+23
ROM:0053 ldi r23, 0
ROM:0054 out PORTB, r23
ROM:0055 ser r23
ROM:0056 out PORTB, r23
ROM:0057 jmp loc_53
Просто, надежно, эффективно.
И программа:
2.hex
Do
Toggle Portb.5
Loop
Превратилась в:
ROM:0053 loc_53: ; CODE XREF: __RESET+21
ROM:0053 ldi r24, 0x20 ; ' '
ROM:0054 out PINB, r24
ROM:0055 jmp loc_53
Фокус с постоянным выводом «1» в порт возможен благодаря особенностям архитектуры (чудо просто эти AVR). Чистый ассемблер и никакого мошенничества.
Cи CVAVR сделал то, что ему сказали и ни грамма байта больше:
7.hex
while (1)
{
PORTB.5=1;
PORTB.5=0;
};
}
Ассемблерный код:
ROM:0062 loc_62: ; CODE XREF: __RESET+30
ROM:0062 sbi PORTB, PORTB5
ROM:0063 cbi PORTB, PORTB5
ROM:0064 rjmp loc_62
У кода Arduino найти полезный участок займет не одну минуту, я справился с этой задачей загнав прошивку в симулятор протеуса.
3.hex
void loop() {
digitalWrite(13, HIGH);
digitalWrite(13, LOW);
}
Вот он, вызов функции digitalWrite:
ROM:0080 sub_80: ; CODE XREF: sub_121:loc_129
ROM:0080 ldi r24, 0xD
ROM:0081 ldi r22, 1
ROM:0082 call sub_CD ; HERE<<<<
ROM:0084 ldi r24, 0xD
ROM:0085 ldi r22, 0
ROM:0086 call sub_CD ; HERE<<<<
ROM:0088 ret
Саму функцию можно представить в виде следующей картинки (примите извинения за радикал, уважаемые читатели):
Кода много, код ветвист и изобилует коллами и джампами, смотрите его лучше сразу в дизассемблере.
Следующий вариант удачнее:
4.hex
void loop() {
PORTB ^= 1<<5;
}
Что мы имеем с этого супа:
Главный цикл:
ROM:00D1 loc_D1: ; CODE XREF: sub_C9
ROM:00D1 ; sub_C9
ROM:00D1 call sub_80
ROM:00D3 sbiw r28, 0
ROM:00D4 breq loc_D1
ROM:00D5 call j___RESET
ROM:00D7 rjmp loc_D1
И функция вывода:
ROM:0080 sub_80: ; CODE XREF: sub_C9:loc_D1
ROM:0080 in r24, PORTB
ROM:0081 ldi r25, 0x20 ; ' '
ROM:0082 eor r24, r25
ROM:0083 out PORTB, r24
ROM:0084 ret
Следующий! (с) анекдот
5.hex
void loop() {
PORTB=1;
PORTB=255;
}
Напомнило первый случай BASCOM с точностью до бита:
ROM:0080 sub_80: ; CODE XREF: sub_C9:loc_D1
ROM:0080 ldi r24, 1
ROM:0081 out PORTB, r24
ROM:0082 ser r24
ROM:0083 out PORTB, r24
ROM:0084 ret
А вызывается оно вот отсюда, из главного цикла:
ROM:00D1 loc_D1: ; CODE XREF: sub_C9
ROM:00D1 ; sub_C9+E
ROM:00D1 call sub_80
ROM:00D3 sbiw r28, 0
ROM:00D4 breq loc_D1
ROM:00D5 call j___RESET
ROM:00D7 rjmp loc_D1
Еще парочку! (с) Булгаков
Почти то же, что и в прошивке номер 5:
6.hex
void loop() {
while(1){
PORTB=1;
PORTB=255;
}
}
И результат прогнозируем:
ROM:0080 sub_80: ; CODE XREF: sub_C9:loc_D1
ROM:0080 ldi r25, 1
ROM:0081 ser r24
ROM:0082
ROM:0082 loc_82: ; CODE XREF: sub_80
ROM:0082 out PORTB, r25
ROM:0083 out PORTB, r24
ROM:0084 rjmp loc_82
Второе блюдо
Почему мне не понравился Wiring? Ладно, вы уговорили меня не использовать тяжелые библиотечные функции, будем писать все на ассемблере напрямую в порты и регистры, и куда теперь идут все наработанные сообществом библиотеки?
А вот эта фраза: «Управдом друг человека!» «ROM:0061; FUNCTION CHUNK AT ROM:01B3 SIZE 00000002 BYTES» А означает она, что выполняли мы код, а потом неожиданно свалили по джампу в другое место. Ради одной команды, между прочим.
ROM:015B loc_15B: ; CODE XREF: __RESET+1B
ROM:015B cli
ROM:015C
ROM:015C loc_15C: ; CODE XREF: __RESET:loc_15C
ROM:015C rjmp loc_15C
Wiring тут не одинок, видел я проекты на Сях и под AVR и под PIC, где функция разбита на кучу кусочков, распиханных в разные места памяти программ и связана кучей джампов (+2 такта на выполнение) и коллов (+1 место в стеке, + 4 такта на AVR). Переходим к тому кусочку — а нам затычка в виде ret ( + 4 такта на AVR) :), ибо компилятор так посчитал нужным. А вдруг возникает желание отладить не на языке высокого уровня, а в железе, потрогать, так сказать, его за регистры. И летает программа туда-сюда, с нулевого адреса на сотый и обратно, готовый антихакинг антиреверсинг… Эффективность? 640 кБ хватит всем ;)
Обещанная закуска
А на закуску я взял протеус, 7 чипов, 7 прошивок, запустил и сравнил. Сверху вниз 1, 2, 3, 4, 5, 6 и, конечно же, 7.
Время старта (инициализация переда началом выполнения полезной функиции):
А в победителях у нас оказался....Wiring! Значит функция инициализации тут не такая уж и навороченная оказалась ;) Фавориты BASCOM и CVAVR идут ноздря в ноздрю, но на почетном втором месте.
Ну и время выполнения главной задачи — пульсирующий выход.
Впереди бесспорно CVAVR, на втором месте Wiring v.6, 3 и 4 места заслуженно у BASCOMа.
Подведем итоги. Что же выбрать в качестве языка? Если вы хотите использовать железо на 101% и не тратить впустую его ресурсы — ассемблер ваше все. Если вы доверяете трату ресурсов авторам компиляторов, а писать хотите быстро и эффективно — берите С или даже BASCOM. Если хотите создать код быстро, ресурсов у вас много, а задача состоит помигать светодиодом, нет острой необходимости в пошаговой отладке кода в железе, а также очень желательно защитить код от реверсинжиниринга и не давать никому исходников — используйте Wiring, библиотечные оптимизированные функции и прочие Це два плюса.
Всем пис :)
Автор: Int_13h