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

Задача измерения длины импульса, периода и частоты сводится к следующему:

1. Настраиваем шестнадцатиразрядный таймер/счетчик 1

Рабочая частота микроконтроллера(Atmega8) 8 МГц, используем предделитель на 8 для таймера/счетчика 1, т.е. частота таймера 1 МГц и 1 тик таймера будет равен 1/1000000 Гц = 1 мкс., т.е. за 65536 мкс произойдет одно прерывание таймера. Также используем входной подавитель шума, который активируется записью лог. 1 в бит ICNC1 регистра TCCR1B. Разрешаем прерывание по захвату и переполнению таймера.

2. Обрабатываем прерывания по таймеру/счетчику 1

Наш таймер считает от 0 до 65536, потом происходит прерывание по переполнению, регистр TCNT1 обнуляется и начинает тикать снова. Необходимо считать количество этих прерываний для дальнейшего подсчета длительности. С каждым прерыванием увеличиваем переменную OVF_counter.

Измеряемый сигнал подается на вывод ICP1(PB0). Текущее значение TCNT1 при каждом изменении на выводе ICP1 копируется в ICR1 и остается там до следующего изменения. Если на входе ICP1 нарастающий фронт импульса, текущее значение ICR1 помещаем в буфер rising, после обнуляем счетчик прерываний по переполнению и переключаем режим захвата по спадающему фронту.  Если на входе ICP1 спадающий фронт импульса, текущее значение ICR1 помещаем в буфер falling, после переключаем режим захвата по нарастающему фронту. Вычисляем длину импульса по формуле:

t = falling - rising_1 + (OVF_counter * 65536);

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

3. Выводим значения длительности и периода импульса(мкс) и частоты(Гц) на LCD дисплей.

Принцип измерения всех величин показан на графике:

Измерение ширины, скважности и частоты сигнала

Принципиальная схема измерителя:

Исходный код программы с подробными комментариями:

// Использование таймера в режиме захвата. Измерение ширины, 
// скважности импульса и частоты сигнала
#include <avr/io.h>
#include <avr/interrupt.h>
#include <util/delay.h>
unsigned char OVF_counter, zamer_1, zamer_T, zamer_t, duty;
unsigned long t, T, f;
unsigned int rising_1, rising_2, falling;
// Прерывание по переполнению Таймера/счетчика 1
ISR(TIMER1_OVF_vect)
{
OVF_counter++; // Увеличиваем счетчик переполнений
}
// Прерывание по захвату Таймера/счетчика 1
ISR(TIMER1_CAPT_vect)
{
switch(zamer_1)
  {
    case 0: // Вычисляем ширину импульса
      switch(zamer_t)
	  {
      case 0:
	  rising_1 = ICR1; // Запоминаем значениие счётчика 
      TCCR1B &= ~(1 << ICES1); // Устанавливаем прерывание по спадающему фронту импульса
      OVF_counter = 0; // Обнуляем количество переполнений счётчика
      zamer_t = 1; // Переходим к следующему вычислению
	  break;
	  
	  case 1:	  
      falling = ICR1;           // Запоминаем значение счётчика
      TCCR1B |= (1 << ICES1);   // Устанавливаем прерывание по нарастающему фронту импульса
// Приводим все переменные к одному типу и вычисляем длительность импульса
      t = (unsigned long)falling - (unsigned long)rising_1 + ((unsigned long)OVF_counter * 65536);
      zamer_t = 0;
	  zamer_1 = 1; // Переходим к следующему вычислению
	  break;      
	  }
    break;
    case 1: // Вычисляем период импульса
      switch(zamer_T)  
      {
      case 0:
      rising_1 = ICR1; // Запоминаем значение счётчика
      OVF_counter = 0; // Обнуляем количество переполнений счётчика
      zamer_T = 1; // Переходим к следующему вычислению
      break;
  
      case 1: 
      rising_2 = ICR1; // Запоминаем значение счётчика
// Приводим все переменные к одному типу и вычисляем период импульса
      T = (unsigned long)rising_2 - (unsigned long)rising_1 + ((unsigned long)OVF_counter * 65536);
      zamer_T = 0;
      zamer_1 = 2; // Переходим к следующему вычислению
      break;
      }
    break;
  case 2:                   
  f = 1000000/T; // Вычисляем частоту сигнала в Гц
  duty = (t*100)/T; // Вычисляем скважность импульса в процентах
  zamer_1 = 0; // Переходим к следующему вычислению
  break;
  }
}
// Функции работы с LCD 
#define RS PD0 
#define EN PD2
// Функция установки курсора в указанную точку
#define lcd_gotoxy(x, y) lcd_com(0x80|(x)+((y)*0x40))
// Функция передачи команды
void lcd_com(unsigned char p)
{
PORTD &= ~(1 << RS); // RS = 0 (запись команд)
PORTD |= (1 << EN); // EN = 1 (начало записи команды в LCD)
PORTD &= 0x0F; PORTD |= (p & 0xF0); // старший нибл
_delay_us(100);
PORTD &= ~(1 << EN); // EN = 0 (конец записи команды в LCD)
_delay_us(100);
PORTD |= (1 << EN); // EN = 1 (начало записи команды в LCD)
PORTD &= 0x0F; PORTD |= (p << 4); // младший нибл
_delay_us(100);
PORTD &= ~(1 << EN); // EN = 0 (конец записи команды в LCD)
_delay_us(100);
}
// Функция передачи данных
void lcd_data(unsigned char p)
{
PORTD |= (1 << RS)|(1 << EN); // RS = 1 (запись данных), EN - 1 (начало записи команды в LCD)
PORTD &= 0x0F; PORTD |= (p & 0xF0); // старший нибл
_delay_us(100);
PORTD &= ~(1 << EN); // EN = 0 (конец записи команды в LCD)
_delay_us(100);
PORTD |= (1 << EN); // EN = 1 (начало записи команды в LCD)
PORTD &= 0x0F; PORTD |= (p << 4); // младший нибл
_delay_us(100);
PORTD &= ~(1 << EN); // EN = 0 (конец записи команды в LCD)
_delay_us(100);
}
// Функция вывода строки на LCD
void lcd_string(char *string)
{
lcd_com(0x0C);
while(*string != '\0')
{  lcd_data(*string);
string++;
}
}
// Функция вывода переменной
void lcd_num_to_str(unsigned long value, char nDigit)
{
 switch(nDigit)
 {
  case 6: lcd_data(((value/100000)%10)+'0');
  case 5: lcd_data(((value/10000)%10)+'0');
  case 4: lcd_data(((value/1000)%10)+'0');
  case 3: lcd_data(((value/100)%10)+'0');
  case 2: lcd_data(((value/10)%10)+'0');
  case 1: lcd_data((value%10)+'0');
 }
}
// Функция инициализации LCD
void lcd_init(void)
{
PORTD = 0x00;
DDRD = 0xFF;
_delay_ms(50); // Ожидание готовности ЖК-модуля
// Конфигурирование четырехразрядного режима
PORTD |= (1 << PD5);
PORTD &= ~(1 << PD4);
// Активизация четырехразрядного режима
PORTD |= (1 << EN);
PORTD &= ~(1 << EN);
_delay_ms(5); 
lcd_com(0x28); // шина 4 бит, LCD - 2 строки
lcd_com(0x08); // полное выключение дисплея
lcd_com(0x01); // очистка дисплея
_delay_us(100);
lcd_com(0x06); // сдвиг курсора вправо
lcd_com(0x0C); // включение дисплея, курсор не видим
}
int main(void)
{
TCCR1B |= (1 << ICNC1)|(1 << CS11); // Активируем входной подавитель шума, предделитель на 8
TIMSK |= (1 << TICIE1)|(1 << TOIE1); // Разрешаем прерывание по захвату и переполнению
lcd_init(); // Инициализация дисплея
_delay_ms(50);
lcd_gotoxy(0, 0);
lcd_string("F=      Hz  DUTY");
lcd_gotoxy(0, 1);
lcd_string("t=      us     %");
sei(); // Глобально разрешаем прерывания
while (1)
{
lcd_gotoxy(2, 0);
lcd_num_to_str(f, 6); // Выводим частоту сигнала на экран
lcd_gotoxy(2, 1);
lcd_num_to_str(t, 6); // Выводим ширину импульса на экран
lcd_gotoxy(12, 1);
lcd_num_to_str(duty, 3); // Выводим скважность импульса на экран
_delay_ms(250);
}
}

Обсуждение статьи на форуме


Архив для статьи "Использование таймера в режиме захвата. Измерение ширины, скважности и частоты сигнала"
Описание: Проект AVRStudio4
Размер файла: 36.34 KB Количество загрузок: 2 181 Скачать

Комментарии  

+3 #1 skullhead 29.11.2014 00:15
спасибо за статью.
Сообщить модератору
0 #2 yks2003 22.12.2014 16:41
Можете выложить скомпелированый файл в формате __.hex
Сообщить модератору
0 #3 batir 02.06.2015 18:35
dobriy den . horosshaya statya . a net ishodnik dlya MK PIC .ya sam proboval ni kak ne poluchaetsya
Сообщить модератору
0 #4 topor 04.11.2016 13:07
Жаль-- при скважности например 5 и частоте 6кГц--не работает
Сообщить модератору
0 #5 Артем0288 19.04.2017 16:00
Хорошая статья! помогла) но столкнулся с ограничением - могу померять до 170кгц при частоте кварца 16мгц. Не знаете как можно расширить хотя бы ещё на 50кгц ?
Сообщить модератору
0 #6 oniksan 18.04.2018 07:29
Если в момент обработки прерывания TIMER1_CAPT_vec t наступит прерывание TIMER1_OVF_vect , то ширина импульса подсчитается неверно с гигантской ошибкой в ~65536 мкс.
Сообщить модератору
0 #7 Offik 19.12.2018 15:31
подскажите пожалуйста, как подключить к такому устройству делитель частоты, и как изменится код программы?
Сообщить модератору
+1 #8 avr777 16.06.2019 14:46
Всем привет! чтоб измерить частоту ниже нужен контроллер с 16 бит таймером.
Вот мой код измеряет даже 1 герц. при этом работает от частоты 16 мГц. что позволяет измерять и большую частоту. эсли у вас кварц на 20 мГц f меняем 16000000/T_1 на f 20000000/T_1
Вот код под codevision. atmega644.
// Timer1 input capture interrupt service routine
interrupt [TIM1_CAPT] void timer1_capt_isr(void)
{
{
switch(zamer_1)
{
case 0: // Вычисляем ширину импульса
switch(zamer_t)
{
case 0:
rising_1_l = (unsigned char) ICR1L; // Запоминаем значениие счётчика
rising_1_h = (unsigned char) ICR1H;
rising_1 = (rising_1_h
Сообщить модератору
0 #9 avr777 16.06.2019 14:51
Код:
// Timer1 overflow interrupt service routine
interrupt [TIM1_OVF] void timer1_ovf_isr(void)
{
// Place your code here
OVF_counter++; // Увеличиваем счетчик переполнений
}

// Timer1 input capture interrupt service routine
interrupt [TIM1_CAPT] void timer1_capt_isr(void)
{
{
switch(zamer_1)
{
case 0: // Вычисляем ширину импульса
switch(zamer_t)
{
case 0:
rising_1_l = (unsigned char) ICR1L; // Запоминаем значениие счётчика
rising_1_h = (unsigned char) ICR1H;
rising_1 = (rising_1_h << 8) + rising_1_l;
TCCR1B = (1<<ICNC1) | (0<<ICES1) | (0<<WGM13) | (0<<WGM12) | (0<<CS12) | (0<<CS11) | (1<<CS10); // Устанавливаем прерывание по спадающему фронту импульса
OVF_counter = 0; // Обнуляем количество переполнений счётчика
zamer_t = 1; // Переходим к следующему вычислению
break;

case 1:
rising_1_l = (unsigned char) ICR1L; // Запоминаем значениие счётчика
rising_1_h = (unsigned char) ICR1H;
falling = (rising_1_h << 8) + rising_1_l; // Запоминаем значение счётчика
TCCR1B = (1<<ICNC1) | (1<<ICES1) | (0<<WGM13) | (0<<WGM12) | (0<<CS12) | (0<<CS11) | (1<<CS10); // Устанавливаем прерывание по нарастающему фронту импульса
// Приводим все переменные к одному типу и вычисляем длительность импульса
t = (unsigned long)falling - (unsigned long)rising_1 + ((unsigned long)OVF_counter * 65536);
zamer_t = 0;
zamer_1 = 1; // Переходим к следующему вычислению
break;
}
break;
case 1: // Вычисляем период импульса
switch(zamer_T)
{
case 0:
rising_1_l = (unsigned char) ICR1L; // Запоминаем значениие счётчика
rising_1_h = (unsigned char) ICR1H;
rising_1 = (rising_1_h << 8) + rising_1_l;
OVF_counter = 0; // Обнуляем количество переполнений счётчика
zamer_T = 1; // Переходим к следующему вычислению
break;

case 1:
rising_1_l = (unsigned char) ICR1L; // Запоминаем значениие счётчика
rising_1_h = (unsigned char) ICR1H;
rising_2 = (rising_1_h << 8) + rising_1_l;
// Приводим все переменные к одному типу и вычисляем период импульса
T_1 = (unsigned long)rising_2 - (unsigned long)rising_1 + ((unsigned long)OVF_counter * 65536);
zamer_T = 0;
zamer_1 = 2; // Переходим к следующему вычислению
break;
}
break;
case 2:
f = 16000000/T_1; // Вычисляем частоту сигнала в Гц 1000000/
duty = (t*100)/T_1; // Вычисляем скважность импульса в процентах
zamer_1 = 0; // Переходим к следующему вычислению
break;
}
}
}
Сообщить модератору
0 #10 avr777 16.06.2019 14:57
Цитирую avr777:
Всем привет! чтоб измерить частоту ниже нужен контроллер с 16 бит таймером.
Вот мой код измеряет даже 1 герц. при этом работает от частоты 16 мГц. что позволяет измерять и большую частоту. эсли у вас кварц на 20 мГц f меняем 16000000/T_1 на f 20000000/T_1
Вот код под codevision. atmega644.
// Timer1 input capture interrupt service routine
interrupt [TIM1_CAPT] void timer1_capt_isr(void)
{
{
switch(zamer_1)
{
case 0: // Вычисляем ширину импульса
switch(zamer_t)
{
case 0:
rising_1_l = (unsigned char) ICR1L; // Запоминаем значениие счётчика
rising_1_h = (unsigned char) ICR1H;
rising_1 = (rising_1_h

Забыл написать делитель у таймер 1 не ставил никакой. Таймер клацает с частотой 16 мГц
Сообщить модератору
0 #11 avr777 16.06.2019 15:17
Еще переменные нужно добавить
unsigned int rising_1_l, rising_1_h;
Сообщить модератору
0 #12 avr777 16.06.2019 15:26
такую unsigned long t, T, f; переменную codevision не захавал перделал на unsigned long t, T_1, f;
Сообщить модератору
0 #13 AntonChip 17.06.2019 21:48
Цитирую avr777:
Всем привет! чтоб измерить частоту ниже нужен контроллер с 16 бит таймером.
Вот мой код измеряет даже 1 герц. при этом работает от частоты 16 мГц. что позволяет измерять и большую частоту. эсли у вас кварц на 20 мГц f меняем 16000000/T_1 на f 20000000/T_1
Вот код под codevision

avr777 пожалуйста выложите этот код на форуме radioparty.ru/forum/avr/915, позже ваши сообщения удалю
Сообщить модератору
0 #14 Вася__ 06.05.2021 10:08
Зачем лишняя переменная zamer_t, можно просто смотреть разряд ICES1?
Сообщить модератору
0 #15 Вася__ 06.05.2021 10:17
"Если в момент обработки прерывания TIMER1_CAPT_vec t наступит прерывание TIMER1_OVF_vect ". Обрабатываем CAPT не долго, поэтому значение ICR1 близко к 65536 и погрешность не большая.
Сообщить модератору