AVR: как рассчитать время выполнения команды Печать
Добавил(а) microsin   

При программировании AVR (ATmegaXX, ATtinyXX) необходимо знать, сколько времени будет выполняться код. Здесь описываются простые правила для расчета этого времени.

Время выполнения ассемблерных инструкций строго привязано к тактовой частоте микроконтроллера AVR, поэтому первое, что нужно узнать - какая у Вас установлена частота ядра F, т. е. на какой частоте работает внутренний тактовый генератор. Обычно частота тактового генератора задается кварцем, тогда она равна частоте, написанной на кварце. Иногда кварц не используется, и запускается не очень точный внутренний RC генератор (никакие внешние компоненты для генератора не нужны), тогда обычно генератор работает на частоте 1 МГц (можно настроить и на частоту 8 МГц). Какой режим используется - определяется внутренними настройками fuses (они еще называются перемычками). Например, для чипа ATmega16 по умолчанию (когда чип приходит чистый с завода) перемычки настроены на работу без кварца, от внутреннего RC генератора на частоте 1 МГц.

Итак, частоту ядра F в Герцах мы знаем. Теперь можно узнать время одного такта TCLK = 1 / F (в секундах). Каждая команда выполняется строго определенное количество тактов (какое количество - зависит от команды). Например, команды ANDI и ORI (которые часто используются при управлении ножками портов) выполняются за 1 такт. Пример расчета длительности выполнения команды ANDI:

F = 12 МГц
TCLK = 1/12000000 = 0.000000083(3) сек = 0.083(3) мкс
Время выполнения команды 0.083 * 1 = 0.083 микросекунды (команда ANDI выполняется за 1 такт).

Таблицу соответствия между командами микроконтроллера и количеством тактов можно найти в даташите на микроконтроллер, см. раздел Instructions Set Summary в конце даташита. В последнем столбце таблицы #Clocks указано число тактов, за которое выполняется команда. Если там стоит одна цифра - например 2, тогда все понятно, команда выполняется 2 такта. Но иногда там стоит 1/2 или 1/2/3. Это означает для примера 1/2, что команда может выполнится за 1 или 2 такта, что зависит от условия выполнения команды. Например, команда BREQ выполнится за 1 такт, если нет ветвления по условию, и за 2 такта, если произойдет условный переход.

[Расчет выполнения команды на языке Си]

Чтобы узнать, сколько будет выполняться команда на языке Си, нужно пройти её выполнение в отладчике (симуляторе). В режиме просмотра выполняющегося  кода есть возможность выбрать просмотр в дизассемблированном виде, тогда будут видны ассемблерные команды и соответствующие им операторы Си. Длительность каждой команды ассемблера можно вычислить, как было показано ранее. Общая длительность выполнения кода на Си будет равна сумме длительностей выполнения составляющих этот код ассемблерных инструкций.

Есть также способ прямого измерения длительности выполнения кода с помощью осциллографа. Для этого выбирают свободную ножку у микроконтроллера, и в начале измеряемого кода ставят установку ножки в 1, а по окончании измеряемого кода устанавливают ножку в 0. Запускают программу на выполнение и осциллографом измеряют длительность импульса.

[Вычисление константы для таймера/счетчика]

Таймеры/счетчики часто используются в программе для отсчета задержек времени. Я обычно настраиваю счетчик таким образом, чтобы его прерывание срабатывало примерно раз в 1 миллисекунду. В этом случае в программе удобно отсчитывать время относительно глобального счетчика (об этом далее). Покажу на таймере 1, как это настраивается и как используется.

//[timer.c]
u32 timestamp=0;
u32 uptime=0;
#define F_CPU 12000000L //частота кварца, Гц
#define TCNT1_1MS (65536-(F_CPU/(256L*1000))) // столько тиков будет делать T/C1 за 1 мс

void SetupTIMER1 (void)
{
  //  1<
  TCCR1B = (1<<CS12);
  TCNT1 = TCNT1_1MS;
  // Enable timer 1 overflow interrupt.
  TIMSK = (1<<TOIE1);
}

void timePoll (void)
{
  static u16 timecnt = 100;

  if (TIMSK & (1<<TOIE1))
     return;

  TCNT1 = TCNT1_1MS;
  TIMSK = (1<<TOIE1);

  timestamp++;
 
  if (timecnt < 1000)
  {
     timecnt++;
     return;
  }

  timecnt = 0;
  uptime++;
}

//[timerint.S]
#include <avr/io.h>
    .text
    .global TIMER1_OVF_vect
TIMER1_OVF_vect:
    ;cbi     _SFR_IO_ADDR(PORTB), PB7
    push    R24
    ;ldi     R24, ~(1<<TOIE1)
    ldi     R24, 0
    out     _SFR_IO_ADDR(TIMSK), R24
    ;out     0x39, R24
    pop     R24
    ;sbi     _SFR_IO_ADDR(PORTB), PB7
    reti

//[main.c]
int main(void)
{
  SetupTIMER1();
  ... 
  while (true)
  {
     timePoll();
     ...
  }
}

Немного комментариев к коду. Константа F_CPU задает частоту микроконтроллера в Гц (обычно частота кварца). Её удобно задать в makefile, если у Вас проект GCC для AVR Studio (тогда в коде её задавать не нужно). Процедура SetupTIMER1 настраивает предделитель частоты тактов для таймера 1 (частота микроконтроллера делится на 256, и получается частота тактов для таймера 1). Макрос TCNT1_1MS задает константу, которая загружается в таймер после срабатывания прерывания, её значение задается таким образом, что прерывание будет происходить каждую 1 мс. Обычно константу загружают в счетчик таймера при срабатывании прерывания, но в этом примере сделано несколько по-другому - обработчик прерывания таймера просто запрещает прерывание таймера (обработчик прерывания написан на ассемблере), а основная программа процедурой timePoll проверяет - запрещено ли прерывание, и по этому событию снова загружает константу TCNT1_1MS, разрешает прерывание и ведет отсчет глобального времени (в тиках 1 мс - счетчик 32-разрядный счетчик timestamp, в тиках 1 с - 32-разрядный счетчик uptime). Такой вариант обработки прерывания таймера максимально разгружает процессор для выполнения остальных прерываний, что лучше всего подходит для ресурсоемких приложений (например, с применением библиотеки V-USB).

[Отсчет времени в программе с точностью 1 мс]

Если у Вас имеется организованный таким образом глобальный счетчик миллисекундных отсчетов timestamp, то отсчет времени в программе становится тривиальной задачей. Вот пример вычисления задержки в 0.5 секунды:

u32 timecnt = timestamp+500;
while (timestamp<timecnt){}

[Ссылки]

1. Что такое перемычки fuses, как определять их значения.
2. AVR Studio: как написать обработчик прерывания.
3. Как определить тактовую частоту микроконтроллера?