Существует много схемных решений и способов измерения частоты сигнала, в том числе и при помощи микроконтроллеров. В этой статье разберем пример самого простого частотомера построенного на контроллере Atmega8. Схема частотомера показана на рисунке 1. Для обеспечения хорошей точности измерения микроконтроллер тактируется от генератора с внешним кварцем частотой 8MHz. Измеренные показания выводятся на LCD 1602 с контроллером HD44780. VD1 стабилитрон ограничивает амплитуду входного сигнала, R2 токоограничительный резистор.
Рисунок 1.
Чтобы измерить частоту сигнала необходимо подсчитать количество импульсов, поступающих на вход микроконтроллера, за единицу времени. Для этого в нашей программе используем два типа перерывания: прерывание по переполнению таймера T0 и внешнее прерывание по изменению сигнала на входе INT0. Количество поступающих на вход сигналов будем подсчитывать за время - 1 секунда. Восьмибитный таймер T0 будет работать с частотой 1MHz, для этого включим предделитель на 8. Обработчик прерывания по переполнению таймера вызывается 4000 раз в секунду, при этом переменная counter увеличивает свое значение на единицу. Как только эта переменная станет равна 4000, т.е. пройдет 1 секунда, на дисплей уйдет информация о переменной edgecounter, затем обе переменные обнуляются. Все это происходит уже в главном цикле. Переменная edgecounter увеличивает свое значение на единицу каждый раз когда на входе INT0 происходит смена фронта сигнала, т.е. поступает 1 импульс.
Исходный код программы с подробными комментариями показан ниже:
// Измерение частоты сигнала с помощью микроконтроллеров AVR. Простой частотомер. #include<avr/io.h> #include<avr/interrupt.h> #include<util/delay.h> volatile unsigned int edgecounter = 0, counter = 0; // Обработчик прерывания по переполнению Т0, вызывается 4000 раз в секунду ISR(TIMER0_OVF_vect) { TCNT0 = 6; // Счетчик Т0 начинает считать с 6, т.к. 1MHz/(256-6) = 4000Hz counter++; } // Обработчик внешнего прерывания ISR(INT0_vect) { edgecounter++; } // Функции работы с LCD #define RS PD0 #define EN PD1 // Функция передачи команды 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(unsigned char command, char *string) { lcd_com(0x0C); lcd_com(command); while(*string != '\0') { lcd_data(*string); string++; } } // Функция вывода переменной void lcd_num_to_str(unsigned int value, unsigned char nDigit) { switch(nDigit) { case 4: lcd_data((value/1000)+'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) { _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) { // Настройка портов ввода/вывода DDRD = 0b11110011; PORTD = 0x00; // Настройка таймера T0 TCCR0 |= (1 << CS01); // Предделитель на 8, частота таймера 1 MHz TIMSK |= (1 << TOIE0); // Разрешаем прерывание от таймера Т0 // Настройка внешнего прерывания GICR |= (1 << INT0); // Разрешаем внешнее прерывание на входе INT0 MCUCR |= (1 << ISC01)|(1 << ISC00); // Внешнее прерывание формируется по переднему фронту sei(); // Глобально разрешаем прерывания lcd_init(); // Инициализация дисплея lcd_com(0x01); lcd_string(0x80, "Frequency Meter"); lcd_string(0xC0, "F = Hz"); while(1) { // Выводим показания на дисплей if(counter == 4000) { lcd_com(0xC4); lcd_num_to_str(edgecounter, 4); counter = 0; edgecounter = 0; } } }
Архив для статьи "Измерение частоты сигнала с помощью микроконтроллеров AVR. Простой частотомер." | |
Описание: Проект AVRStudio4 | |
Размер файла: 29.72 KB Количество загрузок: 3 318 | Скачать |
Комментарии
Подскажите пожалуйста, как можно упростить данный код ( radioparty.ru/.../... ), чтобы он был без LCD, а результат выдавал на конкретный светодиод.
Например: при частоте меньше 20Гц горит один светодиод, при частоте более 40Гц горит другой светодиод, в диапазоне 20-40 не горят.
Заранее благодарю.
Код:
while(1)
{
if(counter == 4000)
{
if(edgecounter < 20) // Если частота меньше 20
PORTD |= (1 << PD0); // Включаем зеленый светодиод
else
PORTD &= ~(1 << PD0); // Выключаем зеленый светодиод
if(edgecounter > 40) // Если частота больше 40
PORTD |= (1 << PD1); // Включаем красный светодиод
else
PORTD &= ~(1 << PD1); // Включаем красный светодиод
counter = 0;
edgecounter = 0;
}
}
Но все же код работает не так как надо.
Светодиод загорается при любой частоте, единственно раньше или позже - как-будто он ждет пока насобирается определенное число тиков.
Я примерно так тоже экспериментиров ал вчера и результаты были такие же.
Может я выбрал не тот тип генератора в Протеусе (DCLOCK)?
if(counter == 4000) на f(counter == 1000)
и частоты с 20 на 200 и с 40 на 400.
В этом случае работает, но при частоте, допустим, 300 все равно загорается PD1.
Может еще надо поиграться с настройкой таймера или прерываний или еще что подстроить?
counter = 0;
edgecounter = 0;
заработал промежуток, в котором не должены гореть светодиоды.
Единственно, что пока работает на тех частотах, не те которые мне нужны.
Но и за это спасибо.
А какие настройки ставили в генераторе, кроме вольтажа?
Когда стоит отметк "Bi" - не зависимо от частоты загорается PD1
Когда ставлю "Uni" - всегда загорается PD0
Почему так?
radioparty.ru/.../...
"TCNT0 = 6; // Счетчик Т0 начинает считать с 6, т.к. 1MHz/(256-6) = 4000Hz"
Можно ли сделать, чтобы таймер тикал не каждую секунду, а каждые две?
Измеряемая частота это количество импульсов за 1 секунду, рабочая частота Atmega8 - 8MHz, настроен делитель таймера на 8, рабочая частота таймера 1MHz, считает таймер до максимума т.е. до 0xFF(255), за 1 секунду он переполнится 1000000/255 = 3921,56, получается нецелое число, это нас не устраивает, поетому таймер можно заставить считать не с начала, а например с 5, 1000000/(255-5) = 4000.
Почему в статье стоит TCNT0 = 6; потому что из-за переходных процессов счетчик добавляет 3 тика, число 6 можно смело увеличить до 8
Цитирую Alex_och:
Таймер не тикает каждую секунду, за 1 секунду просто обновляются данные, если за 2 секунды, то можно здесь
Код:
if(counter == 4000)
увеличить число 4000 до 8000
Можно, только считать будет не правильно
Зараннее спасибо
Помогите пожалуйста совместить два проекта: ЧАСТОТОМЕР и ТЕРМОМЕТР (DS18B20). Необходима контролировать одновременно температуру радиатора и частоту оборотов кулера. Два проекта я совместил, но работает только что-то одно частотомер или термометр (видимо мешают друг другу).
Как и на какой индикатор выводите данные?
Прибор измеряет интервалы от 10мкс до 30мин elektor-magazine.com/.../...
На LCD 1602 HD44780.
Пиши и выкладывай свой исходник в форуме, помогу
Тему создал radioparty.ru/.../...
Интересный проект!
Хотел бы попробовать да нет прошивки и установки фьюзов.
Я не спец в этом
Но задача усложняется. Вентилятор с герконом нужно подключить к мобильному телефону (микрофону), а сама схема измерителя частоты к другому мобильному в гнездо наушников. Но возникла проблемка - получается как бы дребезг от геркона и дополнительный шум в эфире, что дает на устройстве дополнительные прерывания и ошибочный результат. Добавил в коде пару строчек для маленькой задержки, помогло, но хотелось бы лучше.
Пришла идея, чтобы на вентиляторе тоже поставить такую схему, а результат выдавался бы звуком определенной частоты в зависимости от скорости ветра и вращения вентилятора. Думаю, звук будет передаваться точнее. Получится что-то типа старого модема.
Или Вы посоветуете как-то по-другому сделать, может сталкивались с подобным вопросом.
Заранее благодарю.
Можно, но тогда измерение будет происходить 1 раз в 10 секунд
Роман, если Вы снимаете показания с датчика АЦП, значит Вы измеряете напряжение. Следовательно чем больше обороты, тем больше напряжение на датчике. Тогда Вы можите измерять частоту измеряя напряжение, только для этого нужно воспользоваться отдельным тахометром для калибровки устройства - найти соответствие напряжения к оборотам в нескольких точках т.к. в разном диапазоне зависимость можит быт не линейной. Это мое мнение и в том случае если датчик по принципу моторчика.
Поможет использование более продвинутого контроллера, например ATmega1280 имеет 4 16 битных таймера/счетчик а
дороговат он дешевле четыре таких собрать только дисплей бы сегментный сделать и все. Ладно спасибо за совет.
counter в 8 раз убавил. Частотомер работает. А если предделитель на 64 оставить, а тайметр Т2 использовать, то частотомер в два раза меньше реальной частоты показывает. Моделировал в AVRStudio4.19. Подскажите что не так.
Вот, почитайте, метод позволяет измерять частоту входного сигнала до Timer_Clock_fre quency/2.5 ww1.microchip.com/.../...
И как можно увеличить измеряемую частоту и, чтобы еще можно было измерять период(время)?
В этой статье например такого не делают
engineersgarage.com/.../...
Здравствуйте. Существует несколько способов измерения частоты сигнала с помощью AVR, у каждого есть свои недостатки и преимущества,
в данном примере таймер/счетчик отсчитывает только интервал времени равным 1 с.
У меня есть 16 и 12 МГц, но 16-й я хочу в другое устройство на atmega64...
1500000/(256-6) = 6000 переполнений нужно для подсчета 1 секунды
4000 меняем на 6000