Elektroda.pl
Elektroda.pl
X
Elektroda.pl
Proszę, dodaj wyjątek dla www.elektroda.pl do Adblock.
Dzięki temu, że oglądasz reklamy, wspierasz portal i użytkowników.

[ATTiny2313/C] Przekłamanie w liczeniu czasu impulsu

ADI-mistrzu 08 Paź 2010 17:36 2899 21
  • #1 08 Paź 2010 17:36
    ADI-mistrzu
    Poziom 30  

    Witam!

    Napisałem program, który ma liczyć ile czasu trwał impuls i jaki jest odstęp między nimi.
    Problem wygląda tak, że mocno przekłamuje, ponad 2x i nie wiem z czego to wynika.
    Pomysł był taki, aby za pomocą przerwania od zrównania licznika uzyskiwać czas, czyli jedno przerwanie to u mnie 10µs.
    Gdy pojawia się impuls, ogólny czas między impulsami jest wpisywany do jednej zmiennej, liczniki są zerowane i program czeka na zanik impulsu. Gdy to następuje, wpisuje zliczoną wartość do drugiej zmiennej i znowu czeka ale tym razem na pojawienie się impulsu. Gdy od następuje, wpisuje ogólny czas do pierwszej zmiennej, resetuje wszystko i tak w kółko...

    Rezonator kwarcowy to 10MHz, układ liczy do 100 czyli przy braku dzielnika zegara wychodzi mi przerwanie co 10µs.
    W efekcie tak nie jest...

    Code:
    volatile uint16_t m_czas_wtrysk=0, m_czas_obr=0; //dokładność od 0.01ms do 655.35ms
    
    volatile uint16_t licznik=0;
    volatile uint8_t znacznik=1;

    ISR(INT1_vect)
    {
        GIMSK &= ~_BV(INT1); //wyłączenie przerwania od pinu
        if( bit_is_set(PIND, PD3) )
        {
            TCNT1 = 0; //zerowanie licznika
            m_czas_obr = licznik; //wpisz czas obrotu wału
            licznik = 0; //zerowanie zmiennej licznikowej
            znacznik = 0; //zerowanie zmiennej znacznika
        }
        if( bit_is_clear(PIND, PD3) )
        {
            m_czas_wtrysk = licznik;
        }
        EIFR = (1<<INTF1);
        GIMSK |= _BV(INT1); //włączenie przerwania od pinu
    }

    ISR(TIMER1_COMPA_vect){ //przerwanie od zrównania licznika
        TCNT1 = 0; // zerowanie licznika
        licznik++; //zwiększ zmienną o 1
    }
    //_______________________________________________________________

    uint32_t wynik=0; //wskaźnim na zmienną "wynik"

    int main(void){

    DDRB = 0xFF;
    PORTB = 0x0F;

    DDRD = ~_BV(PD3);//0xFF;
    PORTD = 0x00;

    MCUCR |= _BV(ISC10); //Wszelka zmiana na INT1 powoduje przerwanie
    GIMSK |= _BV(INT1);// | _BV(INT0); //Włączenie przerwania od INT1 (PD3)

    TIMSK |= _BV(OCIE1A); //Przerwanie od zrównania z OCR1A
    TCCR1B |= _BV(CS10); //start timera (przerwanie przez zrównanie)
    OCR1A = 100; //Przerwanie gdy wartość licznika równa 100 (co 0.01ms)

        sei();
    while(1)
    {
            if(znacznik==0)
            {
                cli();
                write_command(0xC0);
                write_to_lcd( '0'+( m_czas_wtrysk/10000)     );
                write_to_lcd( '0'+((m_czas_wtrysk/1000 )%10) );
                write_to_lcd( '0'+((m_czas_wtrysk/100  )%10) );
                write_to_lcd(0x2C);
                write_to_lcd( '0'+((m_czas_obr/10  )%10) );
                write_to_lcd( '0'+( m_czas_obr      %10) );

                write_to_lcd(' ');
               
                write_to_lcd( '0'+( m_czas_wtrysk/10000)     );
                write_to_lcd( '0'+((m_czas_wtrysk/1000 )%10) );
                write_to_lcd( '0'+((m_czas_wtrysk/100  )%10) );
                write_to_lcd(0x2C);
                write_to_lcd( '0'+((m_czas_wtrysk/10   )%10) );
                write_to_lcd( '0'+( m_czas_wtrysk       %10) );

                znacznik = 1;
                sei();
            }
    }
    }


    Wie ktoś dlaczego tak może się dziać? Układ mierzy obroty silnika w samochodzie przez pobieranie informacji z wtryskiwaczy. Czyli odstęp między wtryskami to to czas jednego obrotu (wtrysk grupowy, czyli pierwszy wtrysk na ssaniu, drugi na wydechu).

    Pozdrawiam

    0 21
  • #2 08 Paź 2010 18:49
    Andrzej__S
    Poziom 28  

    Powinieneś ustawić bity WGM13:WGM10 na tryb CTC. Zerowanie licznika wewnątrz obsługi przerwania nie da raczej zbyt precyzyjnego czasu.

    Poza tym na pewno lepiej do tego typu pomiarów wykorzystywać pin Input Capture (ICP).

    0
  • #3 08 Paź 2010 21:34
    ADI-mistrzu
    Poziom 30  

    Dopisałem do rejestru TCCR1B bit WGM12 który według tabeli ustawia CTC, oraz skasowałem zerowanie licznika w funkcji przerwania.
    [ATTiny2313/C] Przekłamanie w liczeniu czasu impulsu
    Przeglądają jeszcze dokumentację natknąłem się, że częstotliwość tego równa jest połowie częstotliwości zegara, tak więc zwiększyłem wartość do porównania 2x i teraz nawet wychodzi tyle ile powinno być mniej więcej.

    Co do ICP to nie wiem jak to zastosować aby zliczało czas impulsu oraz odstęp między nimi. W sumie to z tego jeszcze nie korzystałem i nie wiem za bardzo jak się za to zabrać.

    Poniżej to co mieniłem:

    Code:
    ...
    
    ISR(TIMER1_COMPA_vect){ //przerwanie od zrównania licznika
        licznik++; //zwiększ zmienną o 1
    }
    ...
    MCUCR |= _BV(ISC10); //Wszelka zmiana na INT1 powoduje przerwanie
    GIMSK |= _BV(INT1);// | _BV(INT0); //Włączenie przerwania od INT1 (PD3)

    TIMSK |= _BV(OCIE1A); //Przerwanie od zrównania z OCR1A
    TCCR1B |= _BV(WGM12) | _BV(CS10); //start timera (przerwanie przez zrównanie)
    OCR1A = 200; //Przerwanie gdy wartość licznika równa 100 (co 0.01ms)
    ...


    Tak to miało być czy inaczej? Bo błąd teraz jest jakieś 8% według tego co wskazuje obrotomierz, ale to może też być jego wina.

    0
  • #4 09 Paź 2010 15:32
    Andrzej__S
    Poziom 28  

    ADI-mistrzu napisał:

    Przeglądają jeszcze dokumentację natknąłem się, że częstotliwość tego równa jest połowie częstotliwości zegara, tak więc zwiększyłem wartość do porównania 2x i teraz nawet wychodzi tyle ile powinno być mniej więcej.

    Coś pomyliłeś. Chyba masz na myśli częstotliwość generowaną na pinie OC1A, ale to nie ma tu zastosowania. Jeśli chcesz uzyskać przerwanie co 10us musisz ustawić OCR1A na wartość 100, a nie 200.

    ADI-mistrzu napisał:

    Code:

    ..............
            if(znacznik==0)
            {
                cli();
                write_command(0xC0);
                write_to_lcd( '0'+( m_czas_wtrysk/10000)     );
                write_to_lcd( '0'+((m_czas_wtrysk/1000 )%10) );
                write_to_lcd( '0'+((m_czas_wtrysk/100  )%10) );
                write_to_lcd(0x2C);
                write_to_lcd( '0'+((m_czas_obr/10  )%10) );
                write_to_lcd( '0'+( m_czas_obr      %10) );

                write_to_lcd(' ');
               
                write_to_lcd( '0'+( m_czas_wtrysk/10000)     );
                write_to_lcd( '0'+((m_czas_wtrysk/1000 )%10) );
                write_to_lcd( '0'+((m_czas_wtrysk/100  )%10) );
                write_to_lcd(0x2C);
                write_to_lcd( '0'+((m_czas_wtrysk/10   )%10) );
                write_to_lcd( '0'+( m_czas_wtrysk       %10) );

                znacznik = 1;
                sei();
            }
    ................


    Domyślam się, że ten fragment kodu ma wyświetlić wynik na wyświetlaczu.
    Po pierwsze - do zamiany 'unsigned int' na ciąg znaków masz gotową funkcję w avr-libc o nazwie utoa, więc nie wiem, dlaczego tak kombinujesz.
    Po drugie, nie wiem dlaczego przed przecinkiem pierwszej zmiennej masz m_czas_wtrysk, a po przecinku m_czas_obr.

    Ogólnie zastosowana przez Ciebie metoda nie będzie zbyt dokładna. Jeżeli procedura obsługi przerwania INT1 będzie dłuższa niż ilość cykli pomiędzy przerwaniami timera 1 (a jest to dosyć prawdopodobne) to podczas jej wykonywania możesz gubić impulsy zliczane w zmiennej licznik.

    Co do Input Capture, działa podobnie jak przerwanie zewnętrzne (reaguje na zbocze opadające lub narastające w zależności od ustawienia bitu ICES1 w rejestrze TCCR1B), ale dodatkowo zapamiętuje w tym samym momencie aktualną wartość timera/licznika (TCNT1) w rejestrze ICR1. Można zapamiętać tę wartość w jakiejś zmiennej i podczas następnego przerwania odjąć ją od nowej wartości ICR1. Różnica to właśnie czas (ilość impulsów) pomiędzy przerwaniami. Po szczegóły proponuję sięgnąć do literatury, bo temat jest zbyt obszerny, by opisywać go na forum.

    0
  • #5 10 Paź 2010 13:12
    ADI-mistrzu
    Poziom 30  

    Co do utoa to wiem że jest, ale mój sposób jest mniej pamięciożerny. Może gdyby poprawić nieco kod do wyświetlania znaków to wyszło by to samo, ale to najwyżej później się pobawię.

    Jeśli INT1 będzie dłuższe, to raczej nie wypadnie tyle impulsów aby wynik wyszedł 2x za mały. Po za tym zmniejszyłem dokładność do 0.1ms czyli czas pomiędzy przerwaniami zwiększył się 10x ale wynik wychodzi taki sam.

    Input Capture z tego co piszesz działał by idealnie do obliczania obrotów, ale do wyliczania czasu impulsu już nie, bo reaguje na jedno lub na drugie zbocze. A ja potrzebuję znać czas trwania impulsu oraz odstępu między nimi.

    Code:
    ...
    
    MCUCR |= _BV(ISC10); //Wszelka zmiana na INT1 powoduje przerwanie
    GIMSK |= _BV(INT1); //Włączenie przerwania od INT1 (PD3)
    TIMSK |= _BV(OCIE1A); //Przerwanie od zrównania z OCR1A
    TCCR1B |= _BV(WGM12) | _BV(CS10);
    OCR1A = 100;//Przerwanie gdy wartość licznika równa 100 (co 0.01ms)
    ...
            if(znacznik==0)
            {
                cli();
                write_command(0xC0);
                SET_RS;
                m_czas_obr = 6000000/m_czas_obr;
                write_to_lcd( '0'+( m_czas_obr/1000)     );
                write_to_lcd( '0'+( m_czas_obr/100 )%10  );
                write_to_lcd( '0'+((m_czas_obr/10  )%10) );
                write_to_lcd( '0'+( m_czas_obr      %10) );

                write_to_lcd(' ');
               
                wynik = m_czas_wtrysk;
                write_to_lcd( '0'+( wynik/10000)     );
                write_to_lcd( '0'+((wynik/1000 )%10) );
                write_to_lcd(0x2C);
                write_to_lcd( '0'+((wynik/100  )%10) );
                write_to_lcd( '0'+((wynik/10   )%10) );
                write_to_lcd( '0'+( wynik       %10) );

                znacznik = 1;
                sei();
            }
    }

    I przy takim kodzie dochodzę chyba powoli do problemu. Wyświetla mi teraz jakieś 700 obrotów z kawałkiem ale za to wartość zmiennej m_czas_wtrysk wychodzi co najwyżej kilka (nie przekracza 10).
    Teraz dlaczego zlicza tak mały czas stanu wysokiego?

    Jeszcze co mi przyszło do głowy do konflikt między przerwaniem wewnętrznym a INT1. Gdy wewnętrzne następuje a jednocześnie zanika stan na INT1, wtedy jest to pomijane i liczenie trwa dalej, dlatego wychodzą mi czasy 2x za długie. Ale z drugiej strony ciężko by było za każdym razem trafiać idealnie co drugie przejście i nie tłumaczy to tego dlaczego wartość czasu impulsu jest zazwyczaj równa 0.

    0
  • #6 10 Paź 2010 15:25
    Andrzej__S
    Poziom 28  

    ADI-mistrzu napisał:

    Input Capture z tego co piszesz działał by idealnie do obliczania obrotów, ale do wyliczania czasu impulsu już nie, bo reaguje na jedno lub na drugie zbocze. A ja potrzebuję znać czas trwania impulsu oraz odstępu między nimi.

    Przecież wewnątrz obsługi przerwania Input Capture możesz sobie zmienić zbocze. Warunek jest tylko taki, żeby obsługa przerwania była krótsza niż czas mierzonego impulsu, ale to chyba nie jest problem, bo czasy impulsów są dosyć spore (relatywnie do czasu trwania jednego taktu zegara procesora).

    ADI-mistrzu napisał:

    Gdy wewnętrzne następuje a jednocześnie zanika stan na INT1, wtedy jest to pomijane i liczenie trwa dalej...

    Jeżeli przerwanie zewnętrzne pojawia się w czasie trwania obsługi przerwania timera, to flaga INTF1 zostaje ustawiona i po zakończeniu obsługi przerwania timera program przechodzi do obsługi przerwania zewnętrznego. Z tego wniosek, że nie zostanie pominięte, co najwyżej odrobinę (max. o czas trwania obsługi przerwania timera) odroczone.

    ADI-mistrzu napisał:

    ...
    Problem wygląda tak, że mocno przekłamuje, ponad 2x i nie wiem z czego to wynika.
    ...
    ... tak więc zwiększyłem wartość do porównania 2x i teraz nawet wychodzi tyle ile powinno być mniej więcej.
    ...
    Jeśli INT1 będzie dłuższe, to raczej nie wypadnie tyle impulsów aby wynik wyszedł 2x za mały.
    ...

    Ja wcale nie twierdziłem, że czas obsługi przerwania INT1 to jest przyczyna tak dużego błędu :)
    Nie znam się za dobrze na silnikach spalinowych, ale mogłeś przyjąć złą koncepcję. Prawdopodobnie sygnał z wtryskiwacza występuje co drugi obrót. To byłoby nawet zgodne z logiką, bo po co wtrysk podczas wydechu? Dodatkowo sam napisałeś, że jak zwiększyłeś OCR1A dwukrotnie (czyli w efekcie dwukrotnie zmniejszyłeś zmienną 'licznik'), to wynik był (pawie) prawidłowy.

    Dodano po 42 [minuty]:

    Jeszcze jedna uwaga:
    ADI-mistrzu napisał:

    Code:

                m_czas_obr = 6000000/m_czas_obr;
                write_to_lcd( '0'+( m_czas_obr/1000)     );
                write_to_lcd( '0'+( m_czas_obr/100 )%10  );
                write_to_lcd( '0'+((m_czas_obr/10  )%10) );
                write_to_lcd( '0'+( m_czas_obr      %10) );


    Zastanawiałeś się, co się stanie, kiedy przed zakończeniem wyświetlania zmiennej 'm_czas_obr' pojawi się przerwanie INT1, które nadpisze Ci wartość tej zmiennej?
    Być może akurat w tym przypadku zdążysz to wyświetlić, ale generalnie lepiej unikać takich sytuacji, czyli np. zachować wartość obliczenia 6000000/m_czas_obr w innej zmiennej. W niektórych przypadkach, kiedy korzystasz z tych samych zmiennych wielobajtowych zarówno w programie głównym jak i wewnątrz obsługi przerwania (szczególnie dotyczy to przerwań o nieprzewidzianym czasie wystąpienia, czyli np. zewnętrznych), należałoby - na czas odczytu tych zmiennych lub wykonywania obliczeń z ich udziałem w programie głównym - wyłączyć przerwania.

    0
  • #7 14 Maj 2011 12:38
    ADI-mistrzu
    Poziom 30  

    Trochę czasu minęło, no ale...

    Trochę inaczej to rozwiązałem, jeden układ bada impulsy i wysyła do drugiego który między innymi wyświetla te dane.

    Kod: c
    Zaloguj się, aby zobaczyć kod

    Podłączony jest na kwarcu 10MHz.

    Pierwszy problem to że obr wylicza w czasie braku sygnału jako 335. Przypuszczam ze spowodowane jest to próbą dzielenia przez 0?

    Drugi problem, a w zasadzie pytanie, to przekłamanie. Podłączyłem to pod autko ale tak jak by zawyża mi obroty o jakieś 250/300 obrotów.
    I teraz czy błąd jest w programie czy w liczniku i to on przekłamuje. Możliwe aby na zimnym silnik miał około 1550 obrotów/min ?
    Czy może niedokładność kwarcu to może powodować?

    Pozdrawiam

    0
  • #8 14 Maj 2011 19:13
    asembler
    Poziom 32  

    Nie rozumiem dlaczego do testów nie podłączysz przebiegu o znanej czestotliwości.
    Czas obrotów = czasobrotów + czas wtrysku a nie tylko czas obrotow to moze dlatego ci przekłąmuje bo na zimnym silniku czas wtrysku jest dość znaczny.

    0
  • #9 14 Maj 2011 20:03
    ADI-mistrzu
    Poziom 30  

    Obroty są spisywane zawsze gdy stan pinu jest niski, czyli co zamknięcie wtrysku czyli niby co pełny obrót.

    Szukam jakiegoś generatora bo sam osobiście nie posiadam, ale to poboczne pytanie, bo zdaje sobie sprawę że bez jakiegoś znanego wzorca nie sprawdzę tego.

    0
  • #12 17 Maj 2011 22:42
    ADI-mistrzu
    Poziom 30  

    Właśnie zapomniałem że mogę na innym procku zrobić generator :oops:

    Sprawdziłem i przy częstotliwości 20Hz pokazuje 50Hz.
    Ustawiłem aby każdy ze stanów logicznych trwał po 25ms, czytnik pokazuje natomiast 10ms :|

    Dobrze ustawiłem tryb CTC?
    Nie mam pojęcia gdzie szukać tego zgubionego czasu.

    Jeszcze jedna rzecz przyszła mi do głowy, czy licznik po zrównaniu się z rejestrem OCR1A i wygenerowaniu przerwania zeruje się automatycznie?

    0
  • Pomocny post
    #14 18 Maj 2011 12:57
    asembler
    Poziom 32  

    Ja bym nie zerował licznika tylko puscił go wolno a w przerwaniu odczytywał tylko wartosc tego licznika. Teraz wystarczy odjac obie wartosci i masz czas od zbocza do zbocza. Sumujac oba czasy otrzymujesz obroty oczywiscie po przeliczeniu odpowiednim.
    Ten sposób zapewnie że do pomiarów obrotów i czasu wtrysku używasz tylko jednego licznika. Dodatkowo takie puszczenie "wolno" licznika powoduje to że mozesz na nim jeszcze pare innych rzeczy wykonać.

    0
  • #15 18 Maj 2011 16:23
    McMonster
    Poziom 32  

    asembler napisał:
    (...) Dodatkowo takie puszczenie "wolno" licznika powoduje to że mozesz na nim jeszcze pare innych rzeczy wykonać.


    Tu muszę koledze podziękować, bo tym zdaniem rozwiązał się mój zupełnie inny i niepowiązany z tematem problem, gdzie bardzo brakowało mi timerów.

    0
  • #16 18 Maj 2011 16:27
    asembler
    Poziom 32  

    Ciekawe jak ? To znaczy ja wiem że jeszcze mozna z 6 funkcji zrobic ale ciekawy jestem co kolega wymyslił.

    0
  • #17 18 Maj 2011 19:48
    McMonster
    Poziom 32  

    Nic szczególnego, na najprostsze pomysły zwyczajnie najtrudniej wpaść, bo w moim projekcie został jeden timer i kilka rzeczy, które z niego by miały korzystać.

    0
  • #18 18 Maj 2011 21:20
    ADI-mistrzu
    Poziom 30  

    Źle rejestr był ustawiony, powinien być WGM01 włączony.

    asembler, ale licznika nie mogę wolno puścić, przekroczył by swoją maksymalną wartość kilkukrotnie zanim by na przykład doliczył do np. 75ms.
    Wiem że można ustawić dzielnik, ale niema równych jak np. 10 czy 100 więc i w obliczeniach ms musiał bym to uwzględnić. Chyba że kwarc bym zmienił na inny z 10MHz np. na 12,8MHz, wtedy przy dzielniku 128 wyszedł by impuls co 10us, może w późniejszym projekcie faktycznie tak zrobię.

    Teraz pokazuje 25,3ms, czyli przekłamanie jest już minimalne :D

    0
  • #19 18 Maj 2011 21:34
    asembler
    Poziom 32  

    Ale wystarczy obsłuzyc przerwanie od przepełnienia które i tak musisz przecież obsłuzyc. Błąd nawet ten minimlny prawdopodobnie pojawia sie własnie dlatego że ni epuszczas "wolno" licznika.

    0
  • #20 18 Maj 2011 21:47
    ADI-mistrzu
    Poziom 30  

    Czyli jak dobrze rozumiem, powiedzmy że mamy zmienną licznik w której przetrzymywany jest czas trwania impulsu.
    Gdy licznik się przepełni, powiedzmy ten 8bit, dodaje do licznika 25,5µs (przy kwarcu 10MHz zegar cyka co 100ns, czyli przerwanie od przepełnienia nastąpi po 25500ns -> 25,5µs).
    Gdy sygnał zaniknie, wystarczy dodać zawartość już zliczonej wartości licznika do zmiennej licznik i mamy "precyzyjny" czas impulsu?

    0
  • #21 18 Maj 2011 22:10
    asembler
    Poziom 32  

    Poprostu bedziesz miał liczbe 3 bajtową. Idąc dalej możes w ten sposób liczyc nawet dni na przykład "bezczynnosci czyli czas postoju" .Tylko po co ci dokładnośc do 1 taktu? Ja bym jednak poszukał kwarcu o odpowiedniej czestotliwości albo druga metoda to ustawic preskaler na przyklad na 128 a potem uwzglednic to w wyliczeniach

    0
  • #22 19 Maj 2011 07:44
    Andrzej__S
    Poziom 28  

    Zdaje się, że przerwanie timera TIMER0_COMPA w trybie CTC pojawia się co (OCR0A + 1) impulsów, bo przecież licznik liczy od 0, więc aby uzyskać przerwanie co 100 impulsów należałoby ustawić OCR0A na wartość 99.

    0