Микроконтроллеры обычно работают напрямую с физическим миром, а не только с астрактным миром цифр. А реальность не терпит поспешностей. Иногда нужно немного подождать. Казалось бы, чего уж проще - написать процедуру задержки. А ведь нет! Написать то легко, а вот каков результат будет в машинных кодах? Три команды или тридцать три?
Вот и мне потребовалось реализовать задержку в сишной программе для ATtiny2313. Микроконтроллер ресурсами не блещет, а потому каждая команда на счету.
Первоначально, я применил подсмотренную где-то методу написания задержки:
volatile uint8_t i; for(i=0; i<8; i++) ;
На первый взгляд просто и понятно. На второй взгляд:
std Y+1,__zero_reg__ rjmp .L10 .L11: ldd r24,Y+1 subi r24,lo8(-(1)) std Y+1,r24 .L10: ldd r24,Y+1 cpi r24,lo8(8) brlo .L11
На второй взгляд хреново. Компилятор разложил цикл по учебнику и разместил переменную в памяти, она же volatile. А цикл без volatile переменной оптимизатор выкинет, как ничего полезного не делающий. Значит нам надо делать «полезную» работу в цикле, а от volatile переменной отказаться.
uint8_t i; for(i=0; i<8; i++) asm volatile ("nop");
Код уже получается не таким элегантным, и уже условно переносимым (операция nop в разных ассемблерах как правило означает одно и тоже).
ldi r25,lo8(0) .L11: nop subi r25,lo8(-(1)) cpi r25,lo8(8) brne .L11
Уже лучше, но не идеально. Есть, есть ещё куда рости. А потому проделаем следующее. Сделаем цикл не по инкременту, а по декременту (зачем нам сравнение). И выкинем из ассемблерной вставки вообще все команды:
uint8_t i; for(i=7; i; i--) asm volatile ("");
Бинго! Наш код очистился!
ldi r25,lo8(7) .L11: subi r25,lo8(-(-1)) brne .L11
Если мы возьмём этот код и скомпилируем его для ARM ядра, получим схожую картину:
mov r0, #77 .L2: subs r0, r0, #1 bne .L2
Все компиляции производились компилятором GCC, точнее его кросс-версиями avr-gcc и arm-none-eabi-gcc. Ключ оптимизации выставлялся -Os. Для других ключей и компиляторов результат может быть иной.