Микроконтроллеры обычно работают напрямую с физическим миром, а не только с астрактным миром цифр. А реальность не терпит поспешностей. Иногда нужно немного подождать. Казалось бы, чего уж проще - написать процедуру задержки. А ведь нет! Написать то легко, а вот каков результат будет в машинных кодах? Три команды или тридцать три?
Вот и мне потребовалось реализовать задержку в сишной программе для 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. Для других ключей и компиляторов результат может быть иной.
comments powered by Disqus