Точная синхронизация с микроконтроллером PIC18?

Я пытаюсь написать серийную реализацию программного обеспечения, и у меня проблемы со временем. Я использую Timer0 на PIC18F242, и по какой-то причине он кажется не очень точным.

Я пытался отладить проблему, переключая контакт на микроконтроллере и наблюдая за частотой на моем осциллографе. Осциллограф показывает, что период больше, чем должен быть, и я не знаю, почему.

С моим полным кодом период был примерно на 10 мкс слишком длинным, если я правильно помню, но даже в очень простом примере, таком как приведенный ниже, время не работает.

Вот пример:

#include <p18f242.h>

// fuse configurations
#pragma config OSC = HSPLL
#pragma config OSCS = OFF 
#pragma config PWRT = OFF 
#pragma config BOR = OFF 
#pragma config WDT = OFF
#pragma config LVP = OFF 
#pragma config CP0 = OFF

// CALCULATIONS:
// 8Mhz crystal with PLL enabled
// FOSC = 4*8Mhz = 32MHz
// TOSC = 1/FOSC = 31.25ns
// TCY = 4*TOSC = 125 ns
// timer has 1:8 prescaler, therefore timer increments every 8*TOSC = 1us.

main(void)
{
    // port configuration
    TRISBbits.RB1 = 0; // make RB1 an output

    // configure Timer0
    T0CONbits.TMR0ON = 0; // disable (stop) the timer
    T0CONbits.T08BIT = 0; // 16-bit timer
    T0CONbits.T0CS   = 0; // use internal instruction clock
    T0CONbits.PSA    = 0; // use prescaler (prescaler assigned below)
    T0CONbits.T0PS0  = 0; // 010 = 1:8 prescaler
    T0CONbits.T0PS1  = 1;
    T0CONbits.T0PS2  = 0;
    T0CONbits.TMR0ON = 1; // enable (start) the timer

    while(1) 
    {
        if(TMR0L >= 100)
        {
            PORTBbits.RB1 ^= 1;

            // reset the timer
            TMR0H = 0; // MUST SET TMR0H FIRST
            TMR0L = 0;
        }   
    }
}

Мой осциллограф говорит, что период этого сигнала составляет 204,0 мкс, что означает, что вывод переключается каждые 102,0 мкс. Если мой осциллограф исправен, то вывод каждый раз переключается на 2 мкс слишком поздно.

Интересно, что изменение if(TMR0L >= 100)результатов if(TMR0L >= 102)за тот же период, но понижение 100или повышение 102приводит к уменьшению и увеличению периода соответственно.

Почему это происходит?

Кроме того, как я уже упоминал, наличие дополнительного кода в основном цикле усугубляет проблему. С чего бы это?

Ответы (6)

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

Здесь вы видите задержку между моментом, когда таймер достигает 100 (что означает, что ваше условие истинно) и моментом, когда вы его сбрасываете. В вашем случае это 2 тика таймера, что означает, что вы получаете более длительный период. Как уже было сказано, если вы хотите иметь точное время, используйте прерывания. Кроме того, вы можете настроить таймер на переполнение точно в нужный момент. Таким образом, вам просто нужно проверить, не произошло ли переполнение, и не нужно сбрасывать его.

Это не самый эффективный способ использования таймера. Если вы хотите точное время, установите прерывание, когда таймер переворачивается. Подпрограмма прерывания должна устанавливать флаг таймера и делать все быстро, с чем необходимо справиться критическим по времени способом, и сбрасывать счетчик таймера (считывая текущее значение таймера, чтобы вы могли учитывать любые задержки, связанные с обслуживанием прерывания). Все, что не быстро, вы вставляете в цикл if, который проверяет флаг таймера. Первое, что вы делаете в этом цикле, — это сбрасываете флаг таймера, а затем выполняете все некритичные по времени вещи, связанные с вашим циклом.

Дополнительная задержка связана со временем, которое требуется для выполнения инструкций while, if compare, toggle и reset, потому что, когда вы обнуляете таймер, вы теряете все эти подсчеты, в зависимости от того, когда именно таймер достиг 100 в этом конкретном цикле. . Tcy составляет 125 нс, поэтому для учета дополнительной задержки в 2 мкс требуется всего 16 ассемблерных инструкций. Взгляните на листинг сборки, и вы поймете, сколько накладных расходов вы добавляете.

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

Если вы не хотите использовать прерывания (но должны), также установите период так, чтобы он переполнялся, и проверьте флаг переполнения. Это позволит вам не обнулять таймер и сохранит рассчитанный период, но у вас будет джиттер.

Если вы используете предварительный скаляр с Таймером 0, невозможно точно генерировать прерывания со скоростью, которая не является степенью двойки, кратной 256. Если вы хотите генерировать частоту, которая не является кратной степени двойки 256 , лучше всего, вероятно, использовать Таймер 2, который настраивается для любого числа в форме (A*B*C), где A — любое число 1–256, B — любое число 1–16, а C — допустимая мощность. из двух (я забыл, какие разрешены). В противном случае, если вы хотите эффективно использовать таймер 0 в 8-битном режиме без предскаляра, скажемTMR0L += Nпродвинет таймер на N-3 цикла. Таким образом, если вы хотите, чтобы таймер 0 сбрасывался каждые 200 циклов, вы могли бы после каждого его сворачивания добавлять к нему (259-200) циклов (поскольку опережение таймера на 56 циклов после его сворачивания оставило бы 256-56 циклов, т.е. 200). Обратите внимание, что при использовании этого метода не имеет значения, когда именно происходит «добавление», при условии, что оно происходит достаточно быстро, чтобы не подтолкнуть таймер «через край».

Это может быть проблемой, если вы не знаете ассемблера, но вставка подпрограмм ассемблера в код C может помочь вам добиться точного времени. Если я правильно помню, чтобы вставить сборку в PIC C, вы используете

_как м

введите свой ассемблерный код

_эндазм