Помощь - Поиск - Пользователи - Календарь
Полная версия этой страницы: Тахометр на 16меге
Форум разработчиков электроники ELECTRONIX.ru > Микроконтроллеры (MCs) > AVR
DAndy_boy
Господа разработчики, подскажите пожалуйста, что я не так делаю? Задача следующая необходимо построить тахометр. На вход МК подается последовательность импульсов с частотой до 100 Гц. Нужно подсчитать количество импульсов за секунду и вывести на индикатор. Я использую 6мегу , кварц 8.0МГц. Завожу сигнал на вход T0 счетчика и снимаю с него параметры каждую секунду. Проблема в том что полученные результаты не постоянны. Т.е. значение на индикаторе скачет в пределах +-4 единицы. Думал, что не стабильно получаю секунду, так проверил на осциллографе… вроде нормально.. Всю голову сломал… У кого какие будут мысли? (пишу в AVR Studio)
Прилагаю код …
Код
#define QZ              8000000L
#define    TIME_OCR1A      ((QZ / 1024)-1)

int Speed_p;
void Timer1_init(void)
{

     OCR1A = TIME_OCR1A;        /* прерывание раз в секунду */
                TCCR1A = 0x00;
                TCCR1B = (1 << 3) | 0x05;
                TIMSK |= 1 << 4;  /* разрешить прерывание OCIE1A */
}

SIGNAL (TIMER1_COMPA_vect)
{
     Speed_p=TCNT0;
        TCNT0=0;
}


int main()
{
unsigned int g=0;
char str[10];

Timer1_init();

// Таймер0 считает входные импульсы
// Timer/Counter 0 initialization
TCCR0=0x07;
TCNT0=0x00;
OCR0=0x00;

// Port B initialization
// Func7=Out Func6=Out Func5=In Func4=In Func3=Out Func2=In Func1=In Func0=In
// State7=0 State6=0 State5=T State4=T State3=0 State2=T State1=T State0=T
PORTB=0x00;
DDRB=0xC8;

sei();
while(1)
{
   ..........

.//    Формируем строку для вывода
           g=(float)(Speed_p)*(float)1.66;
    str_out[8]=g%10;g/=10;
      str_out[7]=g%10;g/=10;
    str_out[6]=g%10;
// выводим строку      
reload();// динамическая индикация

}
muravei
Цитата(DAndy_boy @ May 25 2007, 08:17) *
Думал, что не стабильно получаю секунду, так проверил на осциллографе… вроде нормально..

Глаз-алмаз! Что можно увидеть осцилл. ?
DAndy_boy
ну как что? добавил в прерывание таймера SIGNAL (TIMER1_COMPA_vect) строку PORTA^=(1<<4) и смотрел на период.... вот и все
aesok
В reoload() запрещаются прерывания? Есть другие обработчики прерываний в проекте?

Анатолий.
muravei
Цитата(DAndy_boy @ May 25 2007, 12:43) *
смотрел на период.... вот и все

Чего на него смотреть , 40мС на 1000 не увидеть.
DAndy_boy
Цитата(aesok @ May 25 2007, 12:57) *
В reoload() запрещаются прерывания? Есть другие обработчики прерываний в проекте?

Анатолий.


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

Анатолий.
DAndy_boy
Сигнал стабильный ТТЛ... может попробывать считать входные импульсы через компоратор?... Вчером займусь...
GDI
Для подсчета частоты импульсов есть специальный режим работы таймера - режим захвата, может воспользоваться им?
Цитата
g=(float)(Speed_p)*(float)1.66;

зачем вы в переменную типа int засовываете выражение типа float, при преобразовании типов тут вполне может быть и несомненно есть потеря точности, может по-этому и прыгают цифры? Здесь надо или как то считать все в int или сделать переменную g типом float и выводить на экран float с необходимой точностью.

В присоединенном файле функция преобразования float в массив char. Функция не моя и может глючить, но для проверки подойдет, как альтернатива объемному printf-у с поддержкой float
DAndy_boy
Действительно.... с этими преобразованиями и дробными числами фигня получается.... Приташил из универа генератор.... проверил без всяких там коэффициентов все нормально работает... прыгает максимум на единицу... а после умножения на 1.66 уже на две... Придется фильтр, похоже использовать.. других я вариантов не вижу... Всем спасибо...
defunct
Цитата(DAndy_boy @ May 25 2007, 18:52) *
проверил без всяких там коэффициентов все нормально работает... прыгает максимум на единицу... а после умножения на 1.66 уже на две...

1* 2 = 2, что тут удивительного то?
Берите среднее по последний трем результатам.
DAndy_boy
Цитата(defunct @ May 26 2007, 23:19) *
1* 2 = 2, что тут удивительного то?
Берите среднее по последний трем результатам.


Так я не могу понять, почему вообще значение прыгает? Поставил кварц 7,3728МГц. Получаю точную секунду. Входная частота стабильная, с генератора. Да и величина ее не более 100Гц. По-моему вообще значения должны быть стабильными. И раз прыгает только единица, то скорее всего это в каком-то определенном месте... Как же исправить ситуацию?... Иначе погрешность вычисления уж очень большая...
defunct
Цитата(DAndy_boy @ May 27 2007, 13:09) *
Так я не могу понять, почему вообще значение прыгает?

Целое число имеет точность +-1,
если его домножить на 2, то получите точность +-2... Что ж непонятного?

Цитата
Как же исправить ситуацию?

Усреднить рез-тат.
muravei
А может лучше мерять период вх сигнала , а не частоту? А после на "скользящее среднее"
DAndy_boy
Цитата(muravei @ May 28 2007, 11:02) *
А может лучше мерить период вх сигнала , а не частоту? А после на "скользящее среднее"

Тоже мысль..... вечером попробую.... Т.е. надо завести входной сигнал на внешнее прерывание и по фронту запускать счетчик... а по другому его останавливать... и затем секунду делим на получившееся время... Так?
GDI
Если хватит диапазона то сделать так:
Код
g = Speed_p * 166;

, а при индикации тупо ставить запятую где надо. Если диапазона int не хватит , то объявить переменную как long int g. Т.е. если у вас и входная и выходная переменные целочисленные, то не к чему делать вычисления с плавающей точкой - все равно потеряете точность и как следствие получите дополнительную погрешность при преобразовании типов.
DAndy_boy
Цитата(GDI @ May 28 2007, 11:18) *
Если хватит диапазона то сделать так:
Код
g = Speed_p * 166;

, а при индикации тупо ставить запятую где надо. Если диапазона int не хватит , то объявить переменную как long int g. Т.е. если у вас и входная и выходная переменные целочисленные, то не к чему делать вычисления с плавающей точкой - все равно потеряете точность и как следствие получите дополнительную погрешность при преобразовании типов.

Я вывожу на индикатор целое число. И потом даже при увеличении погрешности если переменная Speed_p не изменяется, то и после умножения на константу любого типа выражение не должно прыгать.... По моему так..
Вообще у меня подозрения что как раз в момент когда выполняется
Код
Speed_p=TCNT0;
... и тут приходит импульс на вход тактирования таймера
        TCNT0=0;

а я его обнуляю.... возможно здесья теряю единицу.... Может это можно какнибудь отловить....
GDI
При умножении константы на константу, конечно результат будет один, вот только Speed_p скорее всего прыгает пусть даже и на единицу, которая и вырастает при умножении и преобразовании типов.
Попробуйте просто вывести на индикатор переменную Speed_p и посмотрите - как она меняется при работе.

Цитата
Speed_p=TCNT0;

Это у вас происходит внутри прерывания и все прерывания в данный момент запрещены до выхода из прерывания, т.е. если придет импульс на счетный вход, то сперва завершиться это прерывание, т.е. обнулится TCNT0, затем выполнится одна команда из основного цикла, а затем произойдет вход в новое прерывание.
DAndy_boy
Цитата(GDI @ May 28 2007, 11:31) *
...Это у вас происходит внутри прерывания и все прерывания в данный момент запрещены до выхода из прерывания, т.е. если придет импульс на счетный вход, то сперва завершиться это прерывание, т.е. обнулится TCNT0, затем выполнится одна команда из основного цикла, а затем произойдет вход в новое прерывание.

ну так ведь TCNT0 считает не зависимо от прерывания
ReAl
Цитата(DAndy_boy @ May 28 2007, 10:22) *
Вообще у меня подозрения что как раз в момент когда выполняется
Код
Speed_p=TCNT0;
... и тут приходит импульс на вход тактирования таймера
        TCNT0=0;

а я его обнуляю.... возможно здесья теряю единицу.... Может это можно какнибудь отловить....

Да, тут запросто можно потерять. Причём как не старайся - свести вероятность потери к нулю при таком подходе (с обнулением) не выйдет. Нужно дать счётчику бежать свободно и брать разницу двух последовательных показаний за интервал. При этом всё равно в соседние секундные интервалы будут разные значения - при "нецелом" числе импульсов в секунду (скажем, не 100Гц, а 99.5) гарантированно будет, что соседние секундные интервалы содержат разное число импульсов - то 99, то 100. Для 99.9Гц в девяти секундных интервалах будет 100 импульсов, а в одном - 99.
Но при свободно бегущем счётчике и взятии разности показаний ни один импульс не будет потерян вообще - он добавится к следующему результату и после фильтрации будет правильная и "твёрдо стоящая" величина.

p.s. При таких низких частотах я тоже рекомендую измерение периода и пересчёт.
INPUT CAPTURE поможет хватать содержимое счётчика. Чтобы не заморачиваться с ловлей количества переполнений таймера между двумя захватами - выберите предделитель так, чтобы на самой низкой частоте период переполнения таймера был заведомо больше, чем период входного сигнала. Тогда можно брать просто разность двух захватов в ICR и всё.
muravei
Цитата(DAndy_boy @ May 28 2007, 11:13) *
Т.е. надо завести входной сигнал на внешнее прерывание и по фронту запускать счетчик... а по другому его останавливать... и затем секунду делим на получившееся время... Так?

На вход захвата .
Можно умножить на период считаемой таймером частоты.
DAndy_boy
Я все понял... вечером буду пробовать.... о результатах сообщу... Спасибо всем.
Диm
Цитата(muravei @ May 28 2007, 13:02) *
А может лучше мерять период вх сигнала , а не частоту? А после на "скользящее среднее"

я так сделал один разsmile.gif работает на ура! а измерять за секунду число имульсов удобно когда их большое число(скажем на валу стоит диск в котором насверлено 1024 дырки тогда получится оч. хорошая разрешающая способность)
DAndy_boy
Начал разбираться с режимом захвата.... и все никак не получается в AVRStudio выставляю флаг на выходе компаратора, а захвата на таймере не происходит.... или это исключительно в симуляции... или я чего не правильно делаю... Может у кого есть какой пример... или скажите где я ошибаюсь, пожалуйста
Код
#include <avr/io.h>
#include <avr/interrupt.h>
#include <string.h>

int zahvat;

SIGNAL (ANA_COMP_vect)
{

}

SIGNAL (TIMER1_CAPT_vect)
{

zahvat=1;

}

int main()
{// Declare your local variables here
/// Declare your local variables here

// Input/Output Ports initialization
// Port A initialization
// Func7=In Func6=In Func5=In Func4=In Func3=In Func2=In Func1=In Func0=In
// State7=T State6=T State5=T State4=T State3=T State2=T State1=T State0=T
PORTA=0x00;
DDRA=0x00;

// Port B initialization
// Func7=In Func6=In Func5=In Func4=In Func3=In Func2=In Func1=In Func0=In
// State7=T State6=T State5=T State4=T State3=T State2=T State1=T State0=T
PORTB=0x00;
DDRB=0x00;

// Port C initialization
// Func7=In Func6=In Func5=In Func4=In Func3=In Func2=In Func1=In Func0=In
// State7=T State6=T State5=T State4=T State3=T State2=T State1=T State0=T
PORTC=0x00;
DDRC=0x00;

// Port D initialization
// Func7=In Func6=In Func5=In Func4=In Func3=In Func2=In Func1=In Func0=In
// State7=T State6=T State5=T State4=T State3=T State2=T State1=T State0=T
PORTD=0x00;
DDRD=0x00;

// Timer/Counter 0 initialization
// Clock source: System Clock
// Clock value: Timer 0 Stopped
// Mode: Normal top=FFh
// OC0 output: Disconnected
TCCR0=0x00;
TCNT0=0x00;
OCR0=0x00;

// Timer/Counter 1 initialization
// Clock source: System Clock
// Clock value: 7372,800 kHz
// Mode: Normal top=FFFFh
// OC1A output: Discon.
// OC1B output: Discon.
// Noise Canceler: On
// Input Capture on Rising Edge
// Timer 1 Overflow Interrupt: Off
// Input Capture Interrupt: On
// Compare A Match Interrupt: Off
// Compare B Match Interrupt: Off
TCCR1A=0x00;
TCCR1B=0xC1;
TCNT1H=0x00;
TCNT1L=0x00;
ICR1H=0x00;
ICR1L=0x00;
OCR1AH=0x00;
OCR1AL=0x00;
OCR1BH=0x00;
OCR1BL=0x00;

// Timer/Counter 2 initialization
// Clock source: System Clock
// Clock value: Timer 2 Stopped
// Mode: Normal top=FFh
// OC2 output: Disconnected
ASSR=0x00;
TCCR2=0x00;
TCNT2=0x00;
OCR2=0x00;

// External Interrupt(s) initialization
// INT0: Off
// INT1: Off
// INT2: Off
MCUCR=0x00;
MCUCSR=0x00;

// Timer(s)/Counter(s) Interrupt(s) initialization
TIMSK=0x20;

// Analog Comparator initialization
// Analog Comparator: On
// The Analog Comparator's positive input is
// connected to the Bandgap Voltage Reference
// Analog Comparator Input Capture by Timer/Counter 1: On
ACSR=0x44;
SFIOR=0x00;

// Global enable interrupts
sei();
while(1)
{
if(zahvat==1)//произошол захват
{
// подсчитываем период
...
zahvat=0;
}
}

}
aesok
Есть такое волшебное слово: volatile. avr-libc-user-manual FAQ#1, или поиск на форуме.

Анатолий.
DAndy_boy
Цитата(aesok @ May 29 2007, 14:33) *
Есть такое волшебное слово: volatile. avr-libc-user-manual FAQ#1, или поиск на форуме.

Анатолий.

Я знаю и про первое (хотя и вопрос не в этом) и выполнил второе.... и только потом задал данный вопрос....
aesok
Цитата(DAndy_boy @ May 29 2007, 14:52) *
Я знаю и про первое (хотя и вопрос не в этом) и выполнил второе.... и только потом задал данный вопрос....


а почему тогда - int zahvat; - ???

Объясните поодробнее слова "а захвата на таймере не происходит", как вы это определяете.

В хелпе на AVRStudio описано какая перефирия не симулируется, уточните там начет компоратора.

Анатолий.
DAndy_boy
Цитата(aesok @ May 29 2007, 15:12) *
а почему тогда - int zahvat; - ???

Это только для примера...
Цитата(aesok @ May 29 2007, 15:12) *
В хелпе на AVRStudio описано какая перефирия не симулируется, уточните там начет компоратора.

Это вы про это?
"Analog Comparator (AC)
Analog Comparator is not supported."
Только сейчас увидел.... даже и не думал.... что такие ограничения.....не ужели не прокатит код

Цитата(aesok @ May 29 2007, 15:12) *
Объясните поодробнее слова "а захвата на таймере не происходит", как вы это определяете.


ну как как? в симуляторе смотрю что регистр захвата не обновляется .... как был 0x0 так и остается... при любом изменениии(естественно мною) регистра ACO
muravei
Цитата(aesok @ May 29 2007, 15:12) *
В хелпе на AVRStudio описано какая перефирия не симулируется, уточните там начет компоратора.

А в Алгоритм Билдере симулируется, и еще можно на вход импульсы подать...
DAndy_boy
Господа, опять прошу помощи…. Почитав описание компаратора и режим захвата таймера я пришел к выводу, что при использовании внутреннего опорного напряжения захват будет идти только по срезу входного сигнала… Что не очень удобно, по крайне мере в моем случае. По этому пришлось использовать внешнее опорное напряжение и поменять местами входы компаратора, т.е. на не инвертирующий вход подавать измеряемый сигнал, а на другой - внешнее опорное напряжение? поскольку входной сигнал имеет форму пилы (от 0 до 100 Гц). И все-таки в процессе работы появились подозрения, что бывают моменты, когда захват происходит как по срезу, так и по фронту. И в результате никак не могу получить стабильные результаты, значения прыгают на 1,2 герца. Или тут не обойтись без усреднения за несколько интервалов? Прилагаю код, может кто увидит косяк…

Код
#define QZ              7372800L
#define    TIME_OCR1A      ((QZ / 1024))
        
volatile long Zahvat1=0;// захват первого фронта
volatile long Zahvat2=0;// захват первого фронта
volatile long TOVER=0;// то количество импульсов которое насчитали до
                                  //сброса таймера на секунде и до прихода фронта измеряемого сигнала
long Zah_end;// количество тактов Таймера между фронтами
volatile int k=0; // указывает на каком этапе находимся


SIGNAL (TIMER1_CAPT_vect)
{
    // здесь происходит захват значения таймера
if(k==0)
{// захватываем первый фронт
   Zahvat1=ICR1+TOVER;
   TOVER=0;
   k=1;
}
else if(k==1)
  {// захватываем второй фронт
    Zahvat2=ICR1+TOVER;
    TOVER=0;
  //данные готовы    для преобразования и вывода
   k=5;
   }
}


SIGNAL (TIMER1_COMPA_vect)
{
// прерывание каждую секунду
// поскольку значение таймера сбрасывается, то сохраняем количество импульсов которые насчитали
if(k!=5 && TOVER!=0){Zahvat1=Zahvat2=0;k=0;}
  if(k==0)
    {TOVER+=TIME_OCR1A-Zahvat1;Zahvat1=0;}
    else
  if(k==1)
   { TOVER+=TIME_OCR1A-Zahvat2;Zahvat2=0;}
   else
   if(k==5)TOVER=0;
    
}


/****************************************************************************
                      Timer1_init()
  Функция инициализации Timer1.
****************************************************************************/                    
void Timer1_init(void)
{
    OCR1A = TIME_OCR1A;        /* прерывание раз в секунду */
                TCCR1A = 0x00;
                TCCR1B = (1 << 3) | 0x05;
                TIMSK |=  1 << 4;  /* разрешить прерывание OCIE1A */
                TIMSK |=1<<5; // режим захвата
}



int main()
{
int g;
Timer1_init();

PORTC=0x00;
DDRC=0xFF;

// Input/Output Ports initialization
// Port A initialization
// Func7=Out Func6=Out Func5=In Func4=In Func3=In Func2=In Func1=In Func0=In
// State7=0 State6=0 State5=T State4=T State3=T State2=T State1=T State0=T
PORTA=0x00;
DDRA=0xC0;


DDRA|=(1<<4);

// Port B initialization
// Func7=Out Func6=Out Func5=In Func4=In Func3=Out Func2=In Func1=In Func0=In
// State7=0 State6=0 State5=T State4=T State3=0 State2=T State1=T State0=T
PORTB=0x00;
DDRB=0x00;


// Port D initialization
// Func7=Out Func6=Out Func5=Out Func4=Out Func3=In Func2=In Func1=Out Func0=Out
// State7=0 State6=0 State5=0 State4=0 State3=T State2=T State1=0 State0=0
PORTD=0x0C;
DDRD=0xF3;

// Analog Comparator initialization
// Analog Comparator: On
// Analog Comparator Input Capture by Timer/Counter 1: On
ACSR=0x04;
SFIOR=0x00;

sei();

do
{
  if(k==5)
   {// значения готовы
       if(Zahvat2>Zahvat1)
       { // значения верны
                       str_out[0]=0; // выводим флаг ошибки (все удачно)
             Zah_end=(Zahvat2-Zahvat1);
                       g=(TIME_OCR1A)/Zah_end; // вычисляем период
           TOVER=0;
    
           str_out[5]=g%10;g/=10;
             str_out[4]=g%10;g/=10;
           str_out[3]=g%10;g/=10;
           str_out[2]=g%10;g/=10;  
      }
                else
                 // значения не верны след. не учитываем их     
                   str_out[0]=1; // выводим флаг ошибки
           k=0;
   }

   reload(); // динамическая индикация
}while(1);

}
VDV
значения могут прыгать из-за прескалера еще.
его бы тоже обнулять надо.
делал как-то сам тахометр-спидометр на tyni2313 с выводом на жк экран, все работало.
считал скорость, обороты, километраж.
в аттаче код асмовский с комментариями.
посмотрите, мож, поможет.
GDI
Цитата
поскольку входной сигнал имеет форму пилы (от 0 до 100 Гц). И все-таки в процессе работы появились подозрения, что бывают моменты, когда захват происходит как по срезу, так и по фронту.

Такое может быть ТОЛЬКО из-за помех, т.е. где то около максимума пилы происходит выброс который вызывает ложный захват. Выход вы правильно придумали - усреднение. Ещё можно сделать какой то аппаратный формирователь входного сигнала, чтобы он делал из пилы меандр , к примеру, ну и предусмотреть в нем какую то фильтрацию помех во входном сигнале.
Для просмотра полной версии этой страницы, пожалуйста, пройдите по ссылке.
Invision Power Board © 2001-2025 Invision Power Services, Inc.