Точные задержки на Arduino с использованием сборки nop?

Я хочу сделать очень короткий импульс после входного сигнала нарастающего фронта.

Сложность здесь в том, что я хотел бы контролировать (с высоким разрешением) время задержки перед моим импульсом и продолжительность моего импульса. Я могу легко контролировать это, просто соединяя вместе nops, жестко кодируя задержки, но я не уверен, как это сделать для какой-то произвольной задержки с тем же уровнем точности.

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

Я предполагал, что этот оператор переключения C будет тем, что я хотел (после компиляции, надеясь, что он станет эффективным и просто изменит счетчик программ на нужное место), но он приводит к очень странному поведению...

  switch(delayTime){
    case 10:
      __asm__ __volatile__("nop");
    case 9:
      __asm__ __volatile__("nop");
    case 8:
      __asm__ __volatile__("nop");
    case 7:
      __asm__ __volatile__("nop");
    case 6:
      __asm__ __volatile__("nop");
    case 5:
      __asm__ __volatile__("nop");
    case 4:
      __asm__ __volatile__("nop");
    case 3:
      __asm__ __volatile__("nop");
    case 2:
      __asm__ __volatile__("nop");
    case 1:
      __asm__ __volatile__("nop");
  }
  PORTD = 0x10;
 ...

В идеале я хотел бы, по сути, запустить какой-то код, который скомпилируется в это: (это какой-то странный псевдокод C и ассемблера, но я все еще не уверен, как это сделать на ассемблере)

0x005 Reg1 = 0xFF-val1      %(where somehow 0xFF is known? / found out?)
0x006 Reg2 =0x1FF-val2
0x007 IJMP Reg1
0x008 NOP
0x009 NOP
0x00A NOP
...
0x0FF MOV 0x40, PORTD  % assign the value 0x40 to the static variable "PORTD"
0x100 IJMP Reg2
0x101 NOP
0x102 NOP
0x103 NOP
0x104 NOP
...
0x1FF MOV 0x00, PORTD   % assign the value 0x00 to the static variable "PORTD"

Я просто в целом не уверен, как найти место в памяти для кода после/во время выполнения, чтобы аспекты «0xFF» и «0x1FF» этой программы не были такими уж плохими (похоже, это очень опасно просто, получить сборку кода, а затем жесткий код, который в... Я бы предпочел этого не делать). Кроме того, хотя легко просто залить его более чем 200 nops, как заставить команду IJMP вести себя так, как я хочу? (Честно говоря, я даже не знаю, нужна ли мне эта команда).

Думаю, в целом я ищу какую-то команду сборки (которую я не могу найти), которая позволяет мне «добавить N в счетчик программ», и я могу просто убедиться, что эта команда выполняется в сборке с по крайней мере N +1 команды сборки перед ним, жестко запрограммированные.

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


person BlueCoconut    schedule 08.10.2014    source источник
comment
Что не так с процедурами задержки в AVR Libc?   -  person Ignacio Vazquez-Abrams    schedule 08.10.2014
comment
Чтобы получить нужную мне точность, я думаю, мне нужно использовать что-то вроде __builtin_avr_delay_cycles. Однако для этой функции требуется константа (это макрос, который создается во время компиляции), а вместо этого я ищу какую-то функцию, которую я могу вызвать с переменной во время выполнения. Насколько я могу судить, простое перемещение ПК в памяти для получения точного количества nops является наиболее надежным решением, но, возможно, я что-то упустил в AVR libc. (Я вижу только способы сделать 3,4 цикла за итерацию), что недостаточно для разрешения по времени для того, что я хочу от этих часов 16 МГц.   -  person BlueCoconut    schedule 08.10.2014


Ответы (1)


Я не знаком с набором инструкций AVR, но общая идея состоит в том, чтобы использовать инструкцию CALL для помещения счетчика программ (ПК) в стек. Затем используйте POP, чтобы переместить ПК в регистр Z. Затем вы можете ADD ввести некоторое число в регистр Z и использовать IJMP для перехода к полученному адресу.

Так что что-то в этом роде

delay:  call delay1             ; push the PC onto the stack
delay1: pop  r30                ; pop the PC into the Z registers
        pop  r31
        add  r30,r0             ; add some amount to the PC value
        addc r31,r1
        ijmp                    ; use IJMP to jump to the resulting address
        nop
        nop
        nop
        ...

Случайные мысли:

  • На 8-мегабайтных машинах вам нужно третье всплывающее окно, чтобы удалить третий байт ПК из стека.
  • Z составляет всего шестнадцать бит, поэтому этот код должен находиться в первых 128 КБ памяти программы.
  • Я не уверен, какой регистр (r30 или r31) должен быть извлечен первым.
  • Значение, добавленное к Z, должно быть относительно delay1, так как call собирается поместить адрес delay1 в стек. Другими словами, минимальное количество, которое необходимо добавить, равно 6, поскольку это количество инструкций от delay1 до первых nop.
  • Минимальная задержка определяется шестью инструкциями до ijmp включительно. Вы должны соответственно увеличить r1/r0 (уменьшить количество nops).

Как я уже сказал, я не эксперт по набору инструкций AVR, поэтому вы должны воспринимать это как общее предложение и быть готовым потратить некоторое время на проработку деталей. Удачи!

person user3386109    schedule 08.10.2014