Схема
Проверяя работу частотомера, будьте аккуратны – не подайте на вход микроконтроллера повышенного напряжения! Логика программы
В начале программы выполняется конфигурирование портов ввода/вывода (вывод ICP настраивается на вход и включается подтягивающий резистор), настройка таймера Т1 и инициализация символьного LCD.
Затем микроконтроллер попадает в бесконечный цикл, где каждые N мс считывает содержимое буфера, вычисляет частоту и выводит ее значение на дисплей. Также на дисплей выводится содержимое регистра захвата.
Затем микроконтроллер попадает в бесконечный цикл, где каждые N мс считывает содержимое буфера, вычисляет частоту и выводит ее значение на дисплей. Также на дисплей выводится содержимое регистра захвата.
По положительному фронту сигнала на выводе ICP (PD6 для mega8535) блок захвата таймера Т1 сохраняет значение счетного регистра и генерирует запрос на прерывание. В обработчике прерывания счетный регистр обнуляется, а значение регистра захвата записывается в буфер.
Структура проекта
Проект состоит из 4-ех программных модулей.bcd.c – содержит функцию для перевода 16-ти разрядных двоичных чисел в двоично-десятичные. На ней мы останавливаться не будем.
timer.c – содержит функцию инициализации таймера Т1, обработчик прерывания по событию захват и функцию возвращающую содержимое буфера.
lcd_lib.c – библиотека для работы с символьным дисплеем.
main.c - основная программа.
Рассмотрим содержимое модуля timer.c
Инициализация таймера/счетчика Т1
void TIM_Init(void){
TIMSK = (1<<TICIE1);
TCCR1A=(0<<WGM11)|(0<<WGM10);
TCCR1B=(0<<ICNC1)|(1<<ICES1)|(0<<WGM13)|(0<<WGM12)|(0<<CS12)|(0<<CS11)|(1<<CS10);
TCNT1 = 0;
}
В проекте используется прерывание по событию захват, это прерывание должно быть разрешено установкой бита TICIE1 в регистре TIMSK.
TIMSK = (1<<TICIE1);
Для конфигурирования таймера Т1 служат регистры TCCR1A, TCCR1B, TCCR1C. С помощью них устанавливаются режим работы таймера, коэффициент деления тактовой частоты, настройки блока захвата и еще много того, что не понадобится для реализации частотомера.
Режим работы таймера определятся значениями битов WGM13…WGM10. В нашем случае режим работы таймера – Normal, поэтому все биты WGM равны нулю.
Коэффициент деления тактовой частоты определяется битами CS12…CS10. Чтобы получить максимально возможную точность измерений, частота работы таймера должна быть как можно выше. Делаем предделитель равным 1 - (0<<CS12)|(0<<CS11)|(1<<CS10)
Для управления блоком захвата в регистре TCCR1B есть два бита – ICNC1 и ICES1. ICNC1 включает/выключает схему подавления помех. Если он сброшен в 0, то схема подавления помех выключена и захват производится по первому же активному фронту на выводе ICP. Если бит установлен в 1, схема подавления помех включена и захват производится только в случае 4-ех одинаковых выборок, соответствующих активному фронту сигнала.
ICES1 определяет активный фронт сигнала, то есть фронт по которому блок захвата будет выполнять сохранение счетного регистра. Если бит сброшен в 0, активным является спадающий фронт. Если установлен в 1, то активным будет нарастающий фронт.
В нашем случае схема подавления помех выключена, активный фронт нарастающий - (0<<ICNC1)|(1<<ICES1)TCCR1A=(0<<WGM11)|(0<<WGM10);
TCCR1B=(0<<ICNC1)|(1<<ICES1)|(0<<WGM13)|(0<<WGM12)|(0<<CS12)|(0<<CS11)|(1<<CS10);
Обнуляем на всякий случай счетный регистр.
TCNT1 = 0;
Прерывание по событию захват
В простейшем случае обработчик прерывания выглядел бы следующим образом:volatile unsigned int tachBuf = 0; //буфер
#pragma vector=TIMER1_CAPT_vect
__interrupt void Timer1Capt(void)
{
TCNT1 = 0; //обнуляем счетный регистр
tachBuf = ICR1; //сохраняем значение регистра захвата в буфере
}
Вполне рабочий вариант, но лучше вычислять значение частоты по нескольким измерениям периода. Это сделает показания частотомера более стабильными, они не будут дергаться.
volatile unsigned int tachBuf = 0;
unsigned long tachFltr = 0;
unsigned char count = 0;
//прерывание по событию захват
#pragma vector=TIMER1_CAPT_vect
__interrupt void Timer1Capt(void)
{
TCNT1 = 0;
//накапливаем 8 измерений и вычисляем среднее арифметическое
tachFltr += ICR1;
count++;
if (count == 8)
{
tachBuf = (unsigned int)(tachFltr >> 3);
tachFltr = 0;
count = 0;
}
}
Функция, возвращающая содержимое буфера
__monitor unsigned int TIM_GetTachBuf(void){
unsigned int tmp = tachBuf;
tachBuf = 0;
return tmp;
}
Копируем содержимое буфера во временную переменную, очищаем буфер, а значение временной переменной возвращаем. Функция объявлена как мониторная, чтобы операции чтения и очистки буфера не могли быть прерваны прерыванием.
Основная программа
//****************************************************// Author(s)...: Pashgan http://ChipEnable.Ru
// Target(s)...: ATMega8535
// Compiler....: IAR 5.11A
// Description.: Таймер Т1. Прерывание по событию захват.
// Простой частотомер на AVR
// Data........: 13.02.10
//*****************************************************
#include <ioavr.h>
#include <intrinsics.h>
#include "lcd_lib.h"
#include "bcd.h"
#include "timer.h"
int main(void)
{
unsigned int tachValue;
PORTD = 0xff;
DDRD = 0x00;
LCD_Init();
TIM_Init();
__enable_interrupt();
while(1){
__delay_cycles(1000000);
//берем “захваченное” значение и выводим его на lcd
tachValue = TIM_GetTachBuf();
LCD_Goto(0,1);
BCD_5IntLcd(tachValue);
//вычисляем значение частоты и выводим его на lcd
tachValue = (unsigned int) (8000000UL/tachValue);
LCD_Goto(0,0);
BCD_5IntLcd(tachValue);
}
return 0;
}
Алгоритм работы программы был разобран выше, единственное на чем стоит остановиться это то, как вычисляется частота сигнала.
Значение, которое сохраняет блок захвата, соответствует числу тиков таймера укладывающихся в период измеряемого сигнала. Предделитель таймера Т1 равен 1, соответственно тактовая частота таймера равна тактовой частоте микроконтроллера – 8 МГц. Отсюда период измеряемого сигнала = ICR * 1/80000000. А частота = 1 /(ICR *1/8000000) = 8000000/ICR.
tachValue = (unsigned int) (8000000UL/tachValue);
UL – указание компилятору, что тип констаныт unsigned long. Конечно, он и сам может догадаться, но это не будет лишним.
(unsigned int) – указание компилятору преобразовать результат вычисления к типу unsigned int.
Погрешность
Компилируем проект и прошиваем микроконтроллер. Подаем на вывод ICP прямоугольный сигнал с генератора и смотрим на LCD. Показания похожи на правду, но с повышением частоты измеряемого сигнала, наш частотомер начинает врать. В чем дело? Похоже мы не учли какой-то нюанс. А нюанс действительно есть.
Во-первых, между изменением состояния входа блока захвата и копированием значения счетного регистра проходит время величиной в 2,5...3,5 такта микроконтроллера. При включении схемы подавления помех задержка увеличивается еще на 4 такта.
Во-вторых, наименьшее время отклика для любого прерывания составляет 4 такта, в течении которых происходит сохранение счетчика команд в стеке. В течении следующих 2-3 тактов выполняется команда перехода к подпрограмме обработки прерывания. (Если прерывание произойдет во время выполнения команды длящейся несколько тактов, то генерация прерывания произойдет только после выполнения этой команды)
И в третьих. В обработчике прерывания по событию захват мы обнуляем счетный регистр. Если заглянуть в ассемблерный листинг модуля timer.c, то мы увидим, что это происходит не сразу, так как в начале прерывания несколько регистров сохраняются в стеке.
20 __interrupt void Timer1Capt(void)
\ Timer1Capt:
21 {
\ 00000000 93AA ST -Y, R26
\ 00000002 939A ST -Y, R25
\ 00000004 938A ST -Y, R24
\ 00000006 93FA ST -Y, R31
\ 00000008 93EA ST -Y, R30
\ 0000000A 934A ST -Y, R20
\ 0000000C 933A ST -Y, R19
\ 0000000E 932A ST -Y, R18
\ 00000010 931A ST -Y, R17
\ 00000012 930A ST -Y, R16
\ 00000014 B74F IN R20, 0x3F
22 TCNT1 = 0;
\ 00000016 E000 LDI R16, 0
\ 00000018 BD0D OUT 0x2D, R16
\ 0000001A BD0C OUT 0x2C, R16
.....
Каждая инструкция ST – это два такта микроконтроллера . IN, LDI, OUT – один такт. Итого 24 такта. (Кстати эта задержка может меняться в зависимости от компилятора)
В итоге получается, что между захватом и обнулением счетного регистра проходит время равное (2,5..3,5) + 4 + (2..3) + 24 ~ 34 тактам и это время мы не учитываем при расчете частоты.
Исправим этот момент. volatile unsigned int tachBuf = 0;
unsigned long tachFltr = 0;
unsigned char count = 0;
#define ERROR 34
//прерывание по событию захват
#pragma vector=TIMER1_CAPT_vect
__interrupt void Timer1Capt(void)
{
TCNT1 = 0;
tachFltr += (ICR1 + ERROR);
count++;
if (count == 8)
{
tachBuf = (unsigned int)(tachFltr >> 3);
tachFltr = 0;
count = 0;
}
}
Прошиваем микроконтроллер и снова проверяем частотомер. Ого, намного лучше!!!
Файлы
Частотомер на AVR. IARЧастотомер на AVR. WINAVR
Частотомер на AVR. CodeVision
Проект для Proteus`a
PS
Значение ERROR можно определить и экспериментально. Подаем на вывод ICP сигнал частотой 1 КГц и смотрим на значение счетного регистра (мы ведь не случайно выводим его на дисплей!). При тактовой частоте микроконтроллера в 8МГц значение счетного регистра должно быть равно 8000. Поправка ERROR будет равна 8000 – текущее значение счетного регистра.
Если честно, я так и сделал. Но это нисколько не обесценивает вышеизложенного объяснения!!! Продолжение - Частотомер на микроконтроллере. Улучшенная версия
Comments
Неплохо бы было бы закоментировать в коде процедурку для вывода на 7-сегментник)
А какие пределы измерения?
Как можно расширить?
Quote: ~200..50000Гц. При меньшей частоте происходит переполнение счетного регистра. При большей - микроконтроллер не успевает обрабатывать прерывания. Чтобы расширить диапазон нужно:
- переключать предделитель таймера в зависимости от измеряемой частоты - так мы избежим переполнения
- использовать внешний счетчик для предварительног о деления частоты
Quote:
Это выражение справедливо для измерения частоты по одному периоду, что не всегда даст приемлемый результат. Более полное выражение измерения частоты таким методом
Fx=Fo*Nx/nx, где
Fx-измеряемая частота
Fo-частота тактирования таймера
Nx-целое число периодов Fx за время измерения
nx-целое число периодов Fo за время Nx
Quote: Это не так. Копирование происходит на аппаратном уровне и занимает всего один тик контроллера.
http://pro-radio.ru/controllers/5978/
Есть еще один момент, который (возможно компилятор это делает при инициализации)н е отражен в программе,
а именно, сброс флага ICF перед началом измерения.
"Both the Input Capture pin (ICP1) and the Analog Comparator output (ACO) inputs are sampled
using the same technique as for the T1 pin (Figure 16-1 on page 135). The edge detector is also
identical."
"The synchronization and edge detector logic introduces a delay of 2.5 to 3.5 system clock cycles
from an edge has been applied to the T1/T0 pin to the counter is updated."
Quote: Видел я в интернете проекты частотомеров, но у меня изначально не стояло задачи добиваться высоких параметров. Я просто показал, как можно использовать блок захвата.
Quote: Сброс флага происходит аппаратно, когда выполняется прерывание.
чтоб по переднему фронту он очищался
а по заднему срабатывал захват
то есть можно было померить длительность
положительного импульса
Всем привет. Паша и друзья, очень нужна ваша помощь. Сразу хочу заметить, что я новичок и самоучка, поэтому, если можно, подробней объясните где и что я не так делаю.
Значит так; работаю с atmega128 и ultrasonic Sensor (HC-SR04). Решил написать код который будет измерять положительный импульс эхо сенсора, обрабатывать его выдавать расстояние.
Базу программы взял Пашину, выучил, почитал комментарии (особенно совета как можно при помощи прерывания блока .
Если есть добровольцы, которые готовы помочь, то я выложу программу.
Очень прошу помощи.
Зарание благодарен
TCCR1A = (1
Так вот, как использовать таймер в случае подсчёта периода обращения колеса для формирования задержки вывода на индикацию?
Я с выводом информации-то разобрался. Но вот как засинхронизиров ать изображение, чтобы оно оставалось в статичном виде?
Но мне непонятно одна вещь, а именно:
когда вызывается прерывание по событию захвата? Если посмотреть по коду, то в перывании есть накопленное ICR1, непонятно за какой период оно накапливается.
Друзья нужна ваша помощь.
Работаю с: Atmega128 в CodeVision AVR (2.04.4a).
Запустил первый счетчик на измерение длительности положительного импульса (с прерыванием по захвату) - работает как часы.
Теперь необходимо запустить третий счетчик для точно такой же функции. Вроде все просто, но... Компайлер ругается; "undefined symbol ICR3" и "undefined symbol TCNT3". В чем проблема, не могу понять. Очень прошу вашей помощи.
Зарание благодарен.
// Timer/Counter 3 initialization
// Clock source: T3 pin Falling Edge
// Mode: Normal top=0xFFFF
// OC3A output: Disconnected
// OC3B output: Disconnected
// OC3C output: Disconnected
// Noise Canceler: Off
// Input Capture on Falling Edge
// Timer3 Overflow Interrupt: Off
// Input Capture Interrupt: On
// Compare A Match Interrupt: Off
// Compare B Match Interrupt: Off
// Compare C Match Interrupt: Off
TCCR3A=(0
TCCR3A=(0<<COM3A1) | (0<<COM3A0) | (0<<COM3B1) | (0<<COM3B0) | (0<<COM3C1) | (0<<COM3C0) | (0<<WGM31) | (0<<WGM30);
TCCR3B=(0<<ICNC3) | (0<<ICES3) | (0<<WGM33) | (0<<WGM32) | (1<<CS32) | (1<<CS31) | (0<<CS30);
TCNT3H=0x00;
TCNT3L=0x00;
ICR3H=0x00;
ICR3L=0x00;
OCR3AH=0x00;
OCR3AL=0x00;
OCR3BH=0x00;
OCR3BL=0x00;
OCR3CH=0x00;
OCR3CL=0x00;
ETIMSK=(1<<TICIE3) | (0<<OCIE3A) | (0<<OCIE3B) | (0<<TOIE3) | (0<<OCIE3C) | (0<<OCIE1C);
Esche posmotri pravelniy vhodnoi pin
F = Fcpu/(ICR*Pre), где Fcpu - тактовая частота микроконтроллер а, ICR захваченное значение таймера, Pre - предделитель таймера.
"Функция объявлена как мониторная, чтобы операции чтения и очистки буфера не могли быть прерваны прерыванием."
"мониторная функция" - сам придумал?
RSS feed for comments to this post