__builtin_avr_delay_cycles объяснение реализации - PullRequest
0 голосов
/ 03 марта 2019

давайте рассмотрим этот простой код C, скомпилированный для atmega32:

_delay_ms(1000);

Это переводится в эту сборку:

00000039  SER R18       Set Register 
0000003A  LDI R24,0x69      Load immediate 
0000003B  LDI R25,0x18      Load immediate 
0000003C  SUBI R18,0x01     Subtract immediate 
0000003D  SBCI R24,0x00     Subtract immediate with carry 
0000003E  SBCI R25,0x00     Subtract immediate with carry 
0000003F  BRNE PC-0x03      Branch if not equal 
00000040  RJMP PC+0x0001        Relative jump 
00000041  NOP       No operation 

Когда эта строка:

_delay_ms(500);

Скомпилировано так:

00000039  SER R18       Set Register 
0000003A  LDI R24,0x34      Load immediate 
0000003B  LDI R25,0x0C      Load immediate 
0000003C  SUBI R18,0x01     Subtract immediate 
0000003D  SBCI R24,0x00     Subtract immediate with carry 
0000003E  SBCI R25,0x00     Subtract immediate with carry 
0000003F  BRNE PC-0x03      Branch if not equal 
00000040  RJMP PC+0x0001        Relative jump 
00000041  NOP       No operation 

Может кто-нибудь объяснить логику сгенерированной сборки?Как разработчики встроенной программы обеспечивали точную задержку цикла из функции __builtin_avr_delay_cycles?

РЕДАКТИРОВАТЬ: не упоминалось #define F_CPU 8000000UL в верхней части файла, очевидно!

1 Ответ

0 голосов
/ 04 марта 2019

С руководством по набору инструкций AVR можно ознакомиться здесь:

http://ww1.microchip.com/downloads/en/devicedoc/atmel-0856-avr-instruction-set-manual.pdf

Давайте рассмотрим ваш код для _delay_ms(1000);.

Ser, ldi иИнструкции ldi в начале занимают 3 цикла и устанавливают 24-битный счетчик на 0x1869FF.

Тогда у нас есть цикл.Каждая итерация этого цикла уменьшает 24-битный счетчик на 1, и цикл завершается, когда счетчик достигает нуля.Таким образом, будет 0x1869FF итераций цикла.

Большинство итераций цикла занимает 5 циклов, потому что brne берет два цикла, когда он разветвляется.На последней итерации цикла brne не разветвляется, поэтому последняя итерация занимает всего 4 цикла.

Две инструкции после цикла занимают всего 3 цикла.

Складывая все, мы видимобщее число циклов:

3 + 0x1869FF * 5 - 1 + 3 = 8000000

Поскольку частота вашего процессора составляет 8 МГц, результирующая задержка составляет 1 секунду.

Хитрость для разработчиков компилятора состоит в том, чтобы выбрать, сколько итераций цикла нужно сделать, сколько rjmps сделать после цикла и сколько nops нужно сделать после этого.Поскольку добавление итераций в цикл является бесплатным с точки зрения программного пространства, вы хотите иметь как можно больше итераций цикла.Поскольку rjmp занимает меньше места, чем два nops, вы хотите иметь как можно больше rjmp после цикла.И тогда вам может понадобиться один nop после этого, чтобы сделать правильное число циклов.

Для более коротких задержек компилятор, вероятно, использует 8-битный или 16-битный счетчик.Для действительно коротких задержек он, вероятно, просто использует rjmp и nop.Для очень длительных задержек, он, вероятно, использует счетчики с более чем 24 битами.

В некоторых случаях может быть возможно сэкономить место в программе, добавив несколько nops внутри цикла, так как это может привести к уменьшению счетчика циклавозможно или может позволить вам удалить несколько инструкций в конце.Так что действительно умная реализация будет учитывать это.

...