Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
УП МПУ-13.doc
Скачиваний:
7
Добавлен:
01.04.2025
Размер:
5.78 Mб
Скачать

5.2. Принципы программного формирования/измерения временного интервала

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

Небольшие фиксированные задержки (единицы – десятки машинных циклов) формируются из требуемого количества «пустых» операторов nop. Число операторов n = tЗ / TCLK = tЗ * fCLK, где tЗ – время задержки; TCLK и fCLK – период и частота тактирования ядра МК.

Значительные по длительности задержки формируются из простых или вложенных циклов, содержащих требуемые по длительности малые фиксированные задержки из тех же операторов nop. В этом случае расчет длительности должен учитывать время выполнения всех операторов, составляющих цикл. Изменение числа циклов позволяет получать варьируемые по длительности задержки.

В библиотеках среды программирования на языке С/С++, как правило, есть готовые функции delay_us/delay_ms(unsigned int), которые реализуют формирование задержек в микро/миллисекундах. Входной параметр должен быть константой, что при заданной частоте тактирования позволяет транслятору рассчитать требуемое число машинных циклов.

Недостатком таких способов формирования временных интервалов является нерациональное использование производительности МК (в ходе отработки задержки процессор не выполняет других задач).

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

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

Для формирования импульсов с постоянной частотой со скважностью два (tи = tп = T/2) на выходе Px.y (рис. 5.1, а) достаточно через равные промежутки времени T/2 инвертировать выход:

while(1) {

PORTx ^= (1<<y); // инверсия выхода Px.y

_delay_ms(200); // задержка на пол периода

}

Для генерации импульсов с варьируемой частотой используем принцип заполнения периода относительно короткими интервалами dT. Если заданы диапазон частот fmin … fmax и число квантов N, то короткий интервал должен быть равен или кратен dT = (Tmax – Tmin) / N = ( 1/fmin – 1/fmax ) / N. Представим его целой константой в микросекундах, для этого при вычислении периода заменим 1/f на 1000000/f. Тогда для формирования импульсов с частотой fset в диапазоне fmin … fmax потребуется формировать задержку Т/2 = NPPER * dT:

#define fmin 1000

#define fmax 2000

#define N 10

#define fset 1500

#define dT ((1000000/fmin – 1000000/fmax)/N)

// (1000 – 500)/10 = 50

#define NPPER (1000000/fset/2/dT)

// 1000000/1500/2/50 = 6

unsigned char n;

while(1) {

PORTx |= (1<<y); // уст выход Px.y

n = NPPER;

while (n) {n--; _delay_us(dT); } // задержка на пол периода

PORTx &= ~(1<<y); // сбр выход Px.y

n = NPPER;

while (n) {n--; _delay_us(dT); } // задержка на пол периода

}

При тестировании в симуляторе AVR Studio поставим точки прерывания (<F9>) на строки изменения состояния Px.y, перед каждым очередным запуском (<F5>) просматриваем и обнуляем значение StopWatch в окне Processor. При fclk = 8 МГц получаем значение полупериодов 307.63 + 308.38 мкс, период 616.0 мкс, частота 1623 Гц (погрешность 8%).

Если требуется варьировать частоту на каждом периоде, закон изменения частоты (точнее, периода), должен находиться в цикле. Например, для формирования 10 импульсов с периодом 400, 380, 360. … 220 мкс, шаг изменения полупериода 10 мкс:

unsigned char n_imp = 10, npper = 20, n;

while(n_imp) {

n_imp--;

PORTx ^= (1<<y); // инверсия выхода Px.y

n = npper;

while (n) {n--; _delay_us(10); } // задержка на пол периода

}

Вариант одноканальной ШИМ требует отдельного формирования длительности импульса tи = var и длительности зависимой паузы tп = Т – tи, T = const. Например, функция для формирования n_imp импульсов с варьируемой длительностью импульса от 10 до 990 мкс, задаваемой переменной nti от 1 до 99 соответственно и постоянным периодом (задается переменной nT в том же масштабе):

void gen_PWM(unsigned char n_imp, unsigned char nti, unsigned char nT) {

unsigned char n;

while(n_imp) {

n_imp--;

PORTx |= (1<<y); // фронт импульса на выходе Px.y

n = nti;

while (n) {n--; _delay_us(10); } // задержка на импульс

PORTx &= ~(1<<y); // срез импульса на выходе Px.y

n = nT - nti;

while (n) {n--; _delay_us(10); } // задержка на паузу

}

}

При тестировании в симуляторе с fclk = 8 МГц время выполнения функции gen_PWM(5, 15, 100); составило 5138.5 мкс, период импульса 1027.25 мкс (погрешность 2.7%), длительность импульса 154.63 мкс (погрешность 3%). Для функции gen_PWM(5, 85, 100); время выполнения 5138.5 мкс, период импульса 1027.25 мкс (погрешность 2.7%), длительность импульса 872.13 мкс (погрешность 2.2%). Для функции gen_PWM(5, 5, 10); время выполнения 526.0 мкс, период импульса 104.75 мкс (погрешность 4.7%), длительность импульса 52.13 мкс (погрешность 2.1%).

Измерение длительности однократного импульса на входе Pxy состоит из выявления фронта импульса, затем – выявление среза импульса. Первое действие состоит из частного цикла чтения входа и сравнения с предыдущим состоянием, если предыдущее состояние 0, а текущее 1, фронт выявлен. Второе действие аналогично, но срезу соответствует предыдущее состояние 1, текущее 0. Кроме того, при выявлении среза нужно подсчитывать число циклов сканирования – это и будет длительность импульса, выраженная в длительностях этого второго цикла.

//измерение длительности однократного импульса на входе PB0

static unsigned char n_cycle=0, tek=0, pred=1;

PORTB = 0x04;

DDRB=0x04;

do { pred=tek; tek=(PINB & (1<<0)); }

while(pred || !tek);//ожидание фронта

do { pred=tek; tek=(PINB & (1<<0)); n_cycle++; }

while(!pred || tek);//ожидание среза и подсчет циклов

В данном примере при частоте тактирования 10 МГц длительность первого цикла составляет 0.8 / 1.0 мкс, второго цикла – 0.9 / 1.3 мкс, при подаче на вход импульса длительностью 10 мкс n_cycle = 11, что соответствует 11 * 0.9 + 1.0 = 10.9 мкс.

Измерение периода отличается от измерения длительности импульса только тем, что надо мерить длительность интервала между последующими фронтами или срезами.

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

Решать подобным способом задачу измерения частоты вовсе неудобно. Гораздо лучше для счета импульсов использовать вход прерывания INT0, а интервал измерения по-прежнему формировать на задержках. Подобный подход уже рассматривался в примере раздела 4.2.

При низких измеряемых частотах fи и высоких требованиях к дискретности и точности целесообразно измерение частоты заменить измерением периода Ти этого же сигнала и программным расчетом значения частоты fи = 1/Ти.