2017-02-17 37 views
5

Я пишу прерывание микроконтроллера, которому необходимо добавить смещение к одному из его аппаратных таймеров. Однако из-за того, что работает предварительный делитель таймера, наивный подход может вводить ошибку «за-один» в зависимости от времени выполнения прерывания относительно часов предварительного делителя.Как я могу избежать этой ошибки за один раз при добавлении смещения к предварительно установленному аппаратным таймером?

timing diagram of ISR off-by-one error

Я использую таймер 1 на ATmega328P (= Arduino) для этого. Я настроил его в обычном режиме с предварительным делителем/8, и я использую прерывание захвата таймера, чтобы вызвать это; целью прерывания является установка таймера для переполнения точно period циклов после события, которое запускает захват ввода (в случае возникновения триггера во время другого прерывания или в другой ситуации, в которой прерывания отключены).

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

код для ISR будет что-то вроде этого:

uint_16 period = 16667; 

ISR(TIMER1_CAPT_vect){ 
    TCNT1 = TCNT1 - ICR1 - period + (elapsed counter ticks during execution); 
} 

критический интервал здесь является один между моментом, когда TCNT1 читается и, когда он записывается снова.

Насколько я знаю, нет способа напрямую прочитать состояние предделителя, поэтому я не думаю, что можно просто применить другое смещение, основанное на синхронизации ISR.

Я мог бы просто сбросить предварительный делитель до ISR (GTCCR |= _BV(TSM); GTCCR |= _BV(PSRSYNC); GTCCR &= ~_BV(TSM);), чтобы синхронизировать, но это все еще вводит случайное смещение на таймер, зависящее от времени ISR.

Другой подход, который я рассматриваю, заключается в использовании таймера для генерации прерывания, синхронизированного с предделителем. Я уже использую оба регистра сравнения вывода на таймере 1, но таймер 0 делится предделителем, чтобы его можно было использовать. Однако выполнение прерывания таймера может быть отложено другим прерыванием или блоком cli, поэтому это не гарантируется.

Как я могу написать свое прерывание, чтобы избежать этой ошибки?

+1

Я предполагаю, что вычисление 'TCNT1' не _always_ отключено одним (время 2?), Просто _sometimes_ (время 1)? – chux

+0

@chux да, 'TCNT1' только иногда отключается одним. Я не совсем уверен, как объяснить это словами, но временная диаграмма, которую я включил, показывает, как это происходит. – AJMansfield

+0

Является ли процедура ISR достаточно быстрой, чтобы пробовать и, конечно, обнаруживать импульс 'clk_Tn'? Может ли 'clk_Tn' установить флаг, который может быть очищен ISR? – chux

ответ

1

Если вы пишете ISR, как

ISR(TIMER1_CAPT_vect){ 
    int counter = TCNT1 - ICR1 - period + 3; 
    asm("nop"); 
    asm("nop"); 
    TCNT1 = counter; 
} 

написание TCNT1 должно происходить ровно 24 циклов после того, как регистр читается, таким образом, в то же делителем «фазы». (Количество nop может быть скорректировано, если необходимо, например, из-за различий между различными типами микроконтроллеров). Тем не менее, решение не в состоянии принять во внимание изменение фазы предварительного делителя, имеющее место между установкой ICR1 и показанием TCNT1.