poniedziałek, 11 kwietnia 2011

Obrotomierz na AVR ATmega8.


Autor: Dondu

Obrotomierz.
Podczas realizacji projektów wykorzystujący silniki warto mieć pod ręką przyrząd pozwalający zmierzyć prędkość obrotową silnika. Tym przyrządem jest obrotomierz zwany też tachometrem  (ang. tachometer) o czym wszyscy zapewne wiedzą :-)

Ponieważ w najbliższym czasie opublikuję cykl artykułów o silniku BLDC, stąd zamieszczam schemat oraz program prostego obrotomierza wykonanego na ATmega8. Program łatwo dostosować do dowolnego innego mikrokontrolera AVR posiadającego możliwość wykorzystania timera posiadającego funkcjonalność Input Capture Unit.


Sposób pomiaru 

Do pomiaru wykorzystamy Timer1 oraz możliwość "łapania" stanu licznika w momencie pojawienia się zbocza na pinie ICP1. Szczegółów szukamy w rozdziale Input Capture Unit. Znajdziemy tam schemat timera dla wykorzystania tej funkcjonalności.

ATmega8 (AVR) Timer1 Input Capture Unit.


Jak działa Input Capture Unit?

Przyjmijmy dla uproszczenia wyjaśnienia sposobu pomiaru, że na jeden obrót silnika, przypada jeden cykl pracy transoptora, czyli dwa zbocza - jedno narastające i jedno opadające.

W skrócie pomiar realizowany jest tak:
  • do pinu ICP1 podłączamy sygnał z transoptora, który będzie naszym czujnikiem pomiarowym,
  • na tarczy silnika umieszczamy znaczniki, na które reagować będzie transoptor,
  • puszczamy swobodnie licznik w trybie normalnym bez preskalera, by pomiar był jak najbardziej dokładny,
  • ustawiamy przerwania od wystąpienia zbocza (np. narastającego) na pinie ICP1,
  • w momencie wystąpienia odpowiedniego zbocza (moment A), w rejestrze ICR1 zostanie zapamiętany aktualny stan licznika timera (TCNT1),
  • wywołane zostaje przerwanie, w którym zapamiętujemy złapany stan licznika,
  • czekamy na następne zdarzenie,
  • w momencie wystąpienia zdarzenia (moment B) ponownie zostaje zapamiętany aktualny stan licznika, który odczytujemy w przerwaniu.
W ten sposób otrzymujemy dwa stany licznika, jeden na początku, a drugi na na końcu cyklu pomiarowego.

Metoda pomiaru prędkości obrotowej silnika.


Teraz wystarczy od stanu końcowego licznika (w momencie B) odjąć stanu początkowy (w momencie A) z uwzględnieniem, iż licznik mógł się w między czasie przepełnić (raz lub więcej razy) i znając częstotliwość taktowania timera dokonać niezbędnych przeliczeń i zaprezentować wynik na LCD.

Istotą wykorzystanie  Input Capture Unit jest fakt, iż stan timera TCNT1 przepisywany jest do rejestru ICR1, dokładnie w momencie wystąpienia zdarzenia. Dlatego ewentualne nieznaczne opóźnienie obsługi przerwania od wykrycie zdarzenia, nie ma wpływu na wynik pomiaru.

Jednakże należy wziąć pod uwagę, że zbyt późne odczytanie rejestru ICR1 może doprowadzić do sytuacji, że następne zdarzenie nadpisze poprzednią wartość ICR1, zanim zdążysz ją odczytać. Dlatego ważne jest, by w czasie pomiaru przerwania nie były zbyt długo blokowane, a odczyt ICR1 powinno się wykonać, na samym początku funkcji przerwania:



Więcej impulsów na jeden obrót

Można oczywiście zastosować enkoder, który będzie dostarczał więcej impulsów na jeden obrót silnika. Należy to uwzględnić w programie. Jednakże trzeba być świadomym tego, że dokładność pomiaru pełnego obrotu jest większa niż dokładność pomiaru fragmentu obrotu. Poza tym, mniej obciążymy mikrokontroler, gdy enkoder będzie dawał jeden impuls na jeden obrót niż w sytuacji, gdy impulsów będzie więcej.

Wszystko oczywiście zależy od konkretnego celu jaki chcesz osiągnąć.


Eliminacja szumów sygnału z transoptora

Dokładność pomiarów zależy także od poprawności pracy zespołu złożonego z czujnika i enkodera. Dla wyeliminowania ewentualnych szumów sygnału z transoptora podłączonego do pinu ICP1, możemy skorzystać z wbudowanego modułu eliminatora szumów oznaczonego na schemacie blokowym timera jako Noise Canceler. W większości przypadków nie będzie jednak takiej potrzeby.


Komparator analogowy jako sygnał zdarzenia

Przy okazji zauważ, że patrząc na schemat blokowy Input Capture Unit, źródłem zdarzeń może być także komparator analogowy. Nie jest nam potrzebny do miernika prędkości, dlatego też nie będę go tutaj omawiał, ale zasada postępowania będzie podobna do omawianej w niniejszym artykule.


Czujniki stykowe

Ten sposób pomiaru uniemożliwia wykorzystanie czujników stykowych z pominięciem sprzętowej eliminacji drgań styków. Innymi słowy jeżeli chcesz wykorzystać jako czujnik np. kontaktron, przycisk krańcowy, czy nawet do testów, zwykły przycisk, to musisz wyeliminować sprzętowo drgania jego styków.


Transoptor

Transoptor może być praktycznie dowolnym transoptorem odbiciowym lub szczelinowym.

Jeżeli zastosujesz transoptor szczelinowy, wymagane będzie wykonanie jakiegoś elementu, który będzie połączony z wirnikiem i będzie przerywał strumień światła pomiędzy diodą LED, a fototranzystorem transoptora.

Przykładowe transoptory szczelinowe.
Rys. Przykładowe transoptory szczelinowe.


Jeżeli zastosujesz transoptor odbiciowy, powinieneś na wirniku przygotować enkoder w postaci namalowanych czarnych i białych pasków. Ważne by kontrast był jak największy, co zapewni dobry poziom sygnału wyjściowego z enkodera.


Przykładowe transoptory odbiciowe.
Rys. Przykładowe transoptory odbiciowe.


Enkodery

Tarcze enkoderów (plik PDF).
Możesz skorzystać z:



Schemat

Do budowy obrotomierza zastosujemy ATmega8 w wersji, która może pracować z kwarcem 16MHz (np. ATmega8L nie może pracować z kwarcem powyżej 8MHz).

Jako LCD wykorzystamy najpopularniejszy wyświetlacz LCD 2x16 znaków kompatybilny z HD44780.

Schemat jest bardzo prosty (dbając oczywiście o podstawy podłączania mikrokontrolera):


Schemat obrotomierza na ATmega8 (AVR).


Ponieważ transoptor ma wyjście typu otwarty kolektor, stąd do podciągnięcia go do Vcc, wykorzystamy wewnętrzny rezystor pull-up mikrokontrolera, włączając go za pomocą programu.


Program

Poniżej kompletny program pierwszej najprostszej wersji obrotomierza. Wszelkie wyjaśnienia znajdziesz w opisach kodu. Program nie zawiera uśredniania wyników, ale możesz to dodać we własnym zakresie.

/*
OBROTOMIERZ v.1

Obrotomierz przeznaczony do prostych pomiarów prędkości obrotowej.
Pokazuje:
- czas jednego obrotu,
- liczbę obrotów na sekundę (rps),
- liczbę obrotów na minutę (rpm).

Szczegóły: http://mikrokontrolery.blogspot.com/2011/04/obrotomierz-diy.html 

Autor: Dondu 
Data:  2012.11.25 
*/


#include <avr/io.h>
#include <stdio.h>
#include <avr/interrupt.h> 
#include <util/delay.h>
#include "HD44780.h"

//tutaj ustaw własne zasady pomiaru
#define POMIAR_ILOSC_IMPULSOW_NA_OBROT 4  //ilość inpulsów na jeden obrót
#define POMIAR_ILOSC_OBROTOW           10 //ile obrotów ma trwać pomiar


volatile unsigned long int  pomiar_ilosc_cykli;
volatile unsigned int       ilosc_przepelnien;
volatile unsigned char      pomiar_zbocze_nr;
volatile unsigned char      pomiar_gotowy;

volatile unsigned int       ICR1_moment_A;
volatile unsigned int       ICR1_moment_B;

char  wynik[16];        //bufor wyświetlacza

//------------------------------------------------------

ISR(TIMER1_CAPT_vect){

 //Obsługa zdarzenia na ICP1 

 static unsigned int ICR1_aktualny; //wartość ICR1 złapana w aktualnie 
                                    //złapanym zboczu

 //odczytaj złapany stan licznika
 ICR1_aktualny = ICR1;

 //czy to pierwsze złapane zbocze?
 if(pomiar_zbocze_nr == 1){
  
  //tak, pierwsze zbocze pomiarowe, 
  
  //uruchamiamy zliczanie przepełnień Timer1

  //zgaś flagę przerwania od przepełnienia
  TIFR |= (1<<TOV1);
  
  //włącz przerwania od przepełnienia Timer1
  TIMSK |= (1<<TOIE1);

  //zeruj licznik przepełnień Timera1
  ilosc_przepelnien = 0;

  //zapamiętujemy stan licznika początku obrotu (moment A)
  ICR1_moment_A = ICR1_aktualny;

 }else{
   
  //każde następne zbocza pomiarowe

  //czy to ostatnie zbocze (koniec pełnego obrotu silnika)?
  if(pomiar_zbocze_nr ==  POMIAR_ILOSC_IMPULSOW_NA_OBROT 
                          * POMIAR_ILOSC_OBROTOW){

   //tak, wykryto ostatnie zbocze pomiarowe, 
   //czyli wykonał się pełny ostatni obrót tarczy

   //wyłączamy przerwania z Input Capture Unit 
   //oraz przepełnienia Timer1
   TIMSK &= ~( (1<<TICIE1) | (1<<TOIE1) );
   
   //zapamiętujemy zmienne dla main()
   ICR1_moment_B  = ICR1_aktualny;
   pomiar_gotowy  = 1;

  }
  
  //w pozostałych przypadkach nic nie robimy i czekamy na zliczanie 
  //dalszych zboczy
 
 }

 //zwiększ licznik wykrytych zboczy
 pomiar_zbocze_nr++;

}

//------------------------------------------------------

ISR(TIMER1_OVF_vect){

 //powiększ licznik przepełnień Timera1
 ilosc_przepelnien++;

}

//------------------------------------------------------


void wykonaj_pomiar(void){

 //Funkcja włącza pomiar 

 //zerujemy licznik przepełnień Timer0
 ilosc_przepelnien = 0;

 //ustawiamy nr pierwszego zbocza
 pomiar_zbocze_nr = 1;

 //zerujemy flagę gotowości pomiaru
 pomiar_gotowy = 0;

 //włącz przerwania z ICP1 Timer1
 TIMSK |= (1<<TICIE1);

}


//------------------------------------------------------

int main(void){

 //zmienne do obliczeń
 double czas_jednego_obrotu;
 double obrotow_na_sekunde;

 PORTB |= (1<<PB0);   //włącz pullup transoptora

 LCD_Initalize();       //inicjalizacja LCD i napis początkowy
 LCD_Clear();
 LCD_GoTo(0, 0);         
 LCD_WriteText("Obrotomierz");

 //Ustawienie Timer1
 TCCR1A = 0;
 TCCR1B = (1<<CS10);   //preskaler 1, bez noise 
                             //canceler dla ICP1
 
 //lub z modułem Noise Canceler:
 //TCCR1B = (1<<CS10) | (1<<ICNC1); //preskaler 1, włącz 
                                         //noise canceler dla ICP1
 
 sei();       //włącz przerwania globalne

 wykonaj_pomiar();     //włącz pierwszy pomiar

 //pętla główna
 while(1){
  
  if(pomiar_gotowy){
   
   // gdy pomiar został wykonany

   //obliczenia
   
   pomiar_ilosc_cykli  =  65536 * ilosc_przepelnien - ICR1_moment_A 
                          + ICR1_moment_B;
   
   czas_jednego_obrotu = (double) pomiar_ilosc_cykli 
                         / POMIAR_ILOSC_OBROTOW / F_CPU ;

   obrotow_na_sekunde = 1.0 / czas_jednego_obrotu;

   //wyświetlenie wyników

   LCD_Clear();

   LCD_GoTo(13, 0);
   LCD_WriteText("rpm");

   //czas jednego obrotu
   sprintf(wynik,"%5.2fms/obr", 1000.0 * czas_jednego_obrotu);
   LCD_GoTo(0, 0);
   LCD_WriteText(wynik);

   //obroty na sekundę
   sprintf(wynik,"%5.2frps ", obrotow_na_sekunde);
   LCD_GoTo(0, 1);
   LCD_WriteText(wynik);
  
   //obroty na minutę
   sprintf(wynik,"%7u ", (int) (60.0 * obrotow_na_sekunde));
   LCD_GoTo(9, 1);
   LCD_WriteText(wynik);
      
   //pomiary co pół sekundy
   _delay_ms(500);   

   //wykonaj następny pomiar
   wykonaj_pomiar();

  }
 }
}
Do pobrania program wraz z projektem AVR Studio 4 + plik hex: OBROTOMIERZ-v1.zip


Problem znaku zapytania

Jeżeli w trakcie realizacji własnego projektu (nie opartego o powyższy plik AVR Studio 4) napotykasz na problem, że zamiast liczby wyświetla się znak zapytania, przeczytaj artykuł: Problem znaku zapytania podczas konwersji float do znaków ASCII w celu wyświetlenia na np. LCD itp.



Efekt końcowy



Akualizacje programu

Ponieważ w miarę pisania o silnikach BLDC jest prawdopodobne, że będę rozbudowywał program obrotomierza, stąd jego aktualną wersję będę zamieszczał w niniejszym temacie.

UWAGA!
Jeżeli ktoś na bazie niniejszego kodu opracuje własną wersję obrotomierza na ten, czy inny mikrokontroler i zechce się nią podzielić z czytelnikami bloga, to proszę o kontakt i z góry w ich imieniu dziękuję :-)



Artykuł wykorzystano do:


Link do artykułu na forum: Pompa perystaltyczna

... wykorzystasz artykuł, opiszesz w sieci, daj nam znać zamieścimy link :-)


42 komentarze:

  1. Nie żebym się czepiał, ale jak tak patrzę na ten program w C to zdaje mi się że to samo w bascom zajęło by znacznie mniej miejsca.

    Pozdrawiam serdecznie.

    OdpowiedzUsuń
  2. Powyższy program jest napisany w celu pokazania metody pomiarowej krok po kroku i przygotowany do dowolnej modyfikacji przez czytelnika, zgodnie z zasadą:

    cyt. "Chcemy jasno dać do zrozumienia, że stworzenie programu, który działa, to jeszcze nie koniec pracy - kod musi być dobrze napisany, łatwy do modyfikowania i zoptymalizowany. Znaczy to tyle, że kod powinien być jasny i czytelny dla każdego, a nie tylko dla autora"
    źródło: Noll

    Zoptymalizowany program przygotowany dla konkretnych parametrów obrotomierza wyglądałby znacząco inaczej.

    A jak duże są możliwości optymalizacji kodu, można zobaczyć w projektach uczestniczących w konkursach dot. C: www.ioccc.org

    OdpowiedzUsuń
  3. Witam bardzo zaciekawili mnie twój projekt obrotomierza, nawet udało mi się coś takiego zbudować;) co jest dla mnie wielka sztuka,( nigdy wcześniej nie miałem styczności z programowaniem mikrokontrolerów, a z elektronika to juz wgle ) natomiast studia mnie do tego zmusiły, mam pytanie czy mozna taki uklad podlaczyc do komputera przez rs232 albo com, i polaczyc z labview zeby informacje nie tylko byly wyswietlane na lcd ale takze na ekranie monitora?

    OdpowiedzUsuń
  4. Witaj, oczywiście można i nawet niedługo taki tutaj zaprezentuję. Na ten moment możesz spróbować zrobić transmisję danych po RS-232 na bazie tego artykułu: Komunikacja ATmega8 z komputerem

    Będę to także opisywał w cyklu art. o Silnikach BLDC, gdzie sterownik sam będzie mierzył prędkość silnika i podawał ją przez RS-232.

    OdpowiedzUsuń
  5. oki, musze to sobie na spokojnie rozkminic, ale rodza mi sie kolejne pytania, jak zrobic samo przekierowanie w programie glownym, (bo domyslam sie ze musze go troszke przebudowac) samych informacji dotyczacych obrotow?, w tym momencie mam do plytki ktora wytrawilem podlaczony wentylator od coolera procka, na 3 kabelki, ale dalej nie wiem jak zrobic przekierowanie zebranych danych do komputera, moglbys mi to jeszcze jakos troszke bardziej uszczegolowic(wiem napewno zadaje trywialne i banalne pytania, niestety jestem w tym temacie typowym laikiem, moja specjalizacja mocno odbiega od tego tematu) niestety musze wykonac taki projekt, na zaliczenie a szczerze przyznam ze ten przedmiot mnie mocno przerasta.

    OdpowiedzUsuń
  6. W pierwszym linku, który Ci podałem, wysyłanie przez RS-232 oparte jest na przerwaniach, czyli nie będzie przeszkadzało w realizacji Twojego głównego zadania.

    Takie projekty jak Twój należy wykonywać etapami. Skoro chcesz zastosować RS-232, to najpierw zapoznaj się jak ten temat można zrealizować na bazie artykułu, który Ci wskazałem. Dopiero gdy zrozumiesz, wykonasz i sprawdzisz transmisję RS-232, powinieneś zabrać się za jej dodanie do Twojego projektu.

    W artykule opisałem dokładnie sposób transmisji oraz każdą linijkę kodu. Nie powinieneś mieć problemu ze zrozumieniem.

    OdpowiedzUsuń
  7. Bardzo ciekawy portal. Projekt jak dla mnie bardzo na czasie, jednak przydałby się w wersji z wyświetlaczem LED.
    Pozdrawiam.
    Bobo

    OdpowiedzUsuń
  8. Hej, zbudowałem ten projekt i od strony płytki wszystko działa ale mam mały problem. Ustawiłem zewnętrzny kwarc 16MHz i wgrałem HEX zawartego w podanej paczce i wszystko działa. Ale kiedy importuje projekt do ATMEL Studio 6.1, linkuje 2 biblioteki libprint_flt.a i libm.a (C:\WinAVR-20100110\avr\lib) i wgrywam to już zamiast rps i ms/obr pokazują się pytajniki. Mógłbyś mi powiedzieć co robię źle?

    OdpowiedzUsuń
  9. Witam, ja mam malutki problem. Użyłem programu,jednak w innym celu. Robię stroik do gitary, jeśli chodzi o hardware to sygnał gitarowy wzmocniłem za pomocą wzmacniacza operacyjnego (komparator z histerezą) tak,że na wyjściu mam idealny przebieg prostokątny o opowiedniej częstotliwości dla każdej strun. Multimetr również pokazuje mi częstotliwość taka jaka powinna być, zaś to co wyświetla się na LCD bardziej przypomina generator liczb losowych :) dla jednej struny i dla prawie idealnego prostokąta na wyświetlaczu pokazują się liczby nieraz rózniące się o cały rząd jedności. Serdecznie pozdrawiam

    OdpowiedzUsuń
    Odpowiedzi
    1. Witaj.
      Ciekawy przypadek użycia obrotomierza :-) W sumie powinien działać prawidłowo tak jak przy pomiarze obrotów (w zakresie częstotliwości, które jest w stanie zmierzyć). Nie jestem w stanie odpowiedzieć co może być przyczyną takiego objawu jaki opisujesz, nie mając układu "w ręku".

      Usuń
  10. Witam, również mam mały problem. Zbudowałem obrotomierz z tego artykułu, działa bez problemu, obroty zmierzone są tak jak powinny, chciałem dodać do niego pewną funkcję. Zamiast rps chciałem wyświetlać status silnika (na podstawie tego czy płyną sygnały z transoptora wyświetlać start lub stop na lcd). Tak jak z elektroniką analogową miałem trochę do czynienia i zagadnienia z tym związane są dla mnie zrozumiałe, tak z programowania jestem kompletnie zielony (ucze się; :) ). Chciałem to zrealizować przez funkcję:

    if (warunek którego nie potrafię dobrać){
    LCD_GoTo(0, 1);
    LCD_WriteText("Stat:OK");

    }
    else{
    LCD_GoTo(0, 1);
    LCD_WriteText("Stat:STOP");
    }

    Próbowałem dodać ją w rózynych miejscach kodu, nie potrafię zrobić tego poprawnie.

    Pozdrawiam serdecznie
    Bartosz

    OdpowiedzUsuń
  11. Witaj.

    Zamiast fragmentu:

    //obroty na sekundę
    sprintf(wynik,"%5.2frps ", obrotow_na_sekunde);
    LCD_GoTo(0, 1);
    LCD_WriteText(wynik);

    wstaw:

    //status
    LCD_GoTo(0, 1);
    if(obrotow_na_sekunde<1) {
    LCD_WriteText("Stat:STOP");
    }else{
    LCD_WriteText("Stat:OK");
    }

    i daj znać czy działa tak jak oczekiwałeś :-)

    OdpowiedzUsuń
    Odpowiedzi
    1. Niestety nie działa. Przy prędkościach powyżej 80rpm pokazuje napis Stat:ok, poniżej Stat:STOP. Przy zatrzymaniu silnika na prędkości powyżej 80rpm zostaje napis STAT:OK

      Usuń
  12. Mam pomysł na zrealizowanie tego w oparciu o wykrywanie zbocza. Jeżeli na przykład pomiędzy wykryciem zbocza A, a zbocza B, minie powiedzmy 500ms -> komenda do wyświetlacza Stat:STOP, w innym wypadku Stat:OK. Miałbyś ochotę pomóc? :) Sam również cały czas próbuje, na razie nic nie działa tak jak powinno.

    Bartosz

    OdpowiedzUsuń
    Odpowiedzi
    1. Poradziłeś sobie już?

      Usuń
    2. Poradziłem sobie w taki sposób:
      while(1){

      if(pomiar_gotowy){
      [...]
      LCD_GoTo(0, 1);
      LCD_WriteText("Stat: OK");

      //pomiary co 500ms
      _delay_ms(500);

      //wykonaj następny pomiar
      wykonaj_pomiar();

      }
      else{
      _delay_ms(1000);
      LCD_GoTo(0, 1);
      LCD_WriteText("Stat:STOP");
      }
      }

      Takie rozwiązanie mnie zadowala, przy zatrzymaniu silnika napis zmienia się, nie jest to uzależnione od rpm. Dzięki za pomoc. Buduje większe urządzenie dla siebie i jeżeli byłbyś zainteresowany po zakończeniu podzielę się tym co uda mi się uzyskać. Czekam tylko na przesyłkę z Hongkongu :)

      Usuń
  13. Witam. Fajny projekt ale zauważyłem pewną nieścisłość w obliczeniach.
    Ilość złapanych zboczy to iloczyn ilości obrotów i ilości impulsów na obrót:

    if(pomiar_zbocze_nr == POMIAR_ILOSC_IMPULSOW_NA_OBROT
    * POMIAR_ILOSC_OBROTOW
    natomiast w obliczeniach ilość impulsów na obrót jest pominięta.

    czas_jednego_obrotu = (double) pomiar_ilosc_cykli
    / POMIAR_ILOSC_OBROTOW / F_CPU ;

    Obliczenia są prawidłowe tylko dla ilości impulsów na obrót =1

    OdpowiedzUsuń
    Odpowiedzi
    1. Witaj.

      Jest prawidłowo, ponieważ liczone są zawsze pełne obroty, a nie fragment. Film pokazuje dokładnie działanie tego programu i jak na nim widać prawidłowo określa prędkość silnika.

      Ilość impulsów na jeden obrót wynika z zastosowanego enkodera o 4 polach. Dlatego też if() który podałeś zawiera odpowiednio policzoną ilości impulsów dla pełnej ilości obrotów całego okresu pomiarowego. Warunek ten pilnuje czasu trwania całego okresu pomiarowego. W przykładowym programie mamy 4 impulsy na obrót i 10 obrotów:

      #define POMIAR_ILOSC_IMPULSOW_NA_OBROT 4 //ilość inpulsów na jeden obrót
      #define POMIAR_ILOSC_OBROTOW 10 //ile obrotów ma trwać pomiar

      co daje łącznie 40 impulsów na cały okres pomiarowy.

      Następnie liczymy dla całego okresu (10 pełnych obrotów) ilość cykli timera:

      pomiar_ilosc_cykli = 65536 * ilosc_przepelnien - ICR1_moment_A + ICR1_moment_B;

      Pozostało nam już tylko policzenie czasu jednego obrotu, w którym uwzględniamy nasze 10 obrotów na cały okres pomiarowy oraz częstotliwość zegara taktującego mikrokontroler:

      czas_jednego_obrotu = (double) pomiar_ilosc_cykli / POMIAR_ILOSC_OBROTOW / F_CPU ;

      a na koniec prędkość obrotową:

      obrotow_na_sekunde = 1.0 / czas_jednego_obrotu;

      Czy teraz już wszystko jest jasne?



      Usuń
  14. Dziękuję za wyjaśnienie, teraz to pojąłem. Myliłem się, obliczenia są poprawne. Mam jeszcze jedno pytanie.
    Chciałem uzyskać zerowanie wskazania po zatrzymaniu silnika. Po wprowadzeniu warunku sprawdzającego czy silnik się kręci, wyniki pomiaru nie są stabilne i skaczą.
    if(pomiar_gotowy){ ....
    .....
    k=0; //zerowanie wskaźnika
    }
    else {
    k++;
    _delay_ms(100);
    if (k==10) {
    obrotow_na_sekunde=0;
    sprintf(wynik,"%5.2frps ", obrotow_na_sekunde);
    LCD_GoTo(0, 1);
    LCD_WriteText(wynik);
    }
    }

    Zeruję tylko zmienną "obrotow_na_sekunde" .

    Przykładowo obroty wahają mi się w granicach +/-1 do 2 obrotów na minutę a po wprowadzeniu instrukcji sprawdzającej skaczą +/- 15 do 30 obrotów na moinutę przy prędkości około 1000obr/min.
    Nie wiem co może być przyczyną.

    OdpowiedzUsuń
    Odpowiedzi
    1. Rozwiązaniem problemu skaczących obrotów okazało się wyeliminowanie funkcji "_delay_ms" , która w czasie trwania pomiaru wprowadzała zakłócenia.
      Uruchomienie Timera0 i sprawdzenie warunku czy zwiększył się numer złapanego zbocza po wielokrotnym przepenieniuTimera0 działa bardzo poprawnie.
      Wskazanie obrotów na minutę jest bardzo stabilne w zakresie 30-2000obr/min, kolejne wskazania przy stabilnych orotach różnią się najwyżej o 1obr/min a po zatrzymaniu silnika w ciągu niecałej sekundy zeruje wskazanie.

      Usuń
  15. Jestem początkującym w programowaniu mikrokontrolerów i również próbowałem uruchomić ten obrotomierz. Trochę się namęczyłem bo posiadam układ 328p ale dzięki temu nauczyłem się że nazwy rejestrów, ich bitów jak i samych przerwań różnią się w zależności od rodzaju atmegi:) Zamiast silnika zrobiłem sobie generator impulsów z jednego z timerów(dzięki czemu pomiar idealnie mierzył czas) a później również uzyskałem sygnał z wyjścia audio komputera dzięki programowi Soundcard Scope. I wszystko zaczęło poprawnie działać dopiero po zmianie
    czas_jednego_obrotu = (double) pomiar_ilosc_cykli
    / POMIAR_ILOSC_OBROTOW / F_CPU ;
    na
    czas_jednego_obrotu = (double) pomiar_ilosc_cykli
    / POMIAR_ILOSC_OBROTOW-1 / F_CPU ;
    a bierze się to stąd że dopiero po dwóch impulsach można policzyć czas trwania jednego obrotu. Poza tym również zauważyłem zakłócenia pomiaru przy użyciu "_delay_ms" (fajnie byłoby gdyby ktoś wytłumaczył dlaczego) i chciałbym wiedzieć jak ją zastąpić

    OdpowiedzUsuń
  16. Witam :)
    Mam takie pytanie, czy jeśli nie dysponuję wyświetlaczem lcd mogę np wyświetlać prędkość obrotową w postaci "suwaka" złożonego z powiedzmy 10 diod led?
    Pozdrawiam

    OdpowiedzUsuń
    Odpowiedzi
    1. np tak:
      #define o_min 10 // zakres obrotów obrazowany
      #define o_max 190 // na suwaku
      #illed 10 // ilość diod
      rozdzielczość = (o_max-o_min)/(illed-1) //określamy co ile obrotów
      //ma zapalić się następna dioda
      switch (obrotow_na_sekunde - o_min)/rozdzielczość // wynikiem warunku jest liczba całkowita
      // z przedziału 1 do 10
      {
      case 1:
      PORTD = 1; // zapalenie diody podłączonej pod pd0
      break;
      case 2:
      PORTD = 3; // zapalenie diody podłączonej pod pd0 i pd1
      break;
      case 3:
      PORTD = 7;
      break;
      case 4:
      PORTD = 15;
      break;
      case 5:
      PORTD = 31;
      break;
      case 6:
      PORTD = 63;
      break;
      case 7:
      PORTD = 127;
      break;
      case 8:
      PORTD = 255;
      break;
      case 9:
      PORTD = 255;
      PORTC = 1;
      break;
      case 10:
      PORTD = 255;
      PORTC = 3;
      break;
      }

      Uważaj tylko żeby te 10 diod nie pobierało za dużo prądu bo możesz uszkodzić mikrokontroler :)

      Usuń
  17. Hej! Mam problem, ponieważ próbuję rozbudować ten program, aby dodatkowo przy określonym położeniu wału silniczka zapalały się poszczególne wyjścia, więc chcę użyć dwóch przerwań, jedno na zbocze opadające (INT0) i jedno na rosnące (INT1) jednak kiedy je włączam:

    GICR |= 1<<INT0| 1<<INT1

    wtedy program nie liczy prędkości obrotowej a przerwania nie działają dalej. W programi może być coś co w tym przeszkadza?

    OdpowiedzUsuń
  18. Witam. Zrobiłem ten projekt i mam problem z odczytem. Polega on na tym że jak przykładam transoptor do tarczy to nic się nie zmienia, a jak poruszam trochę energicznie transoptorem to coś tam się wyśietla na LCD ale jakieś wyniki z du** . Gdzie mogę szukać błędu? Bo program sprawdziłem 4 razy i nie znalazłem błędu.

    OdpowiedzUsuń
    Odpowiedzi
    1. Opisz problem na forum, bo błąd musi gdzieś być po Twojej stronie :)

      Usuń
  19. Wykonałem prototyp zapłonu do silnika spalinowego wzorując się na tym projekcie. Od siebie mogę dodać że mój transoptor potrzebował dodatkowego rezystora podciągającego kolektor do Ucc - 1kOhm, bo bez niego czasy jego załączania były zbyt duże i wprowadzało to błąd pomiarowy. Rezystor w Atmedze8 ma 20-50kOhm co jest wartością zbyt dużą. Ten prosty zabieg ZNACZĄCO poprawia dokładność urządzenia.

    OdpowiedzUsuń
  20. Ten komentarz został usunięty przez autora.

    OdpowiedzUsuń
  21. Witam.
    Chcę użyć tego obrotomierza jako miernika prędkości obrotowej silnika diesla o obrotach max 2400rpm. Który enkoder jest lepszy 2-polowy czy 4-polowy? Autor napsiał że jeden impuls na obrót jest lepszy niż więcej, a podał wzory enkoderów z 4 impulsami.z góry thz za odpowiedz

    OdpowiedzUsuń
  22. Chce zaimplementowac powyzszy program w pomiarze ruchu obrotowego małego modelu rakiety i przesylac dane na komputer przez przejsciowke usb-rs232. Oprocz podstawowej inicjalizacji rs232 dodalem przerwanie z artykulu o przesylaniu danych przez rs232: ISR(USART_UDRE_vect){

    //przerwanie generowane, gdy bufor nadawania jest już pusty,
    //odpowiedzialne za wysłanie wszystkich znaków z tablicy usart_bufor[]

    //sprawdzamy, czy bajt do wysłania jest znakiem końca tekstu, czyli zerem
    if(usart_bufor[usart_bufor_ind]!= 0){

    //załaduj znak do rejestru wysyłki i ustaw indeks na następny znak
    UDR = usart_bufor[usart_bufor_ind++];

    }else{

    //osiągnięto koniec napisu w tablicy usart_bufor[]
    UCSRB &= ~(1<<UDRIE); //wyłącz przerwania pustego bufora nadawania
    }
    }
    oraz funkcje wyslij wynik:
    void wyslij_wynik(void){

    //funkcja rozpoczyna wysyłanie, wysyłając pierwszy znak znajdujący się
    //w tablicy usart_bufor[]. Pozostałe wyśle funkcja przerwania,
    //która zostanie wywołana automatycznie po wysłaniu każdego znaku.

    //dodaj do tekstu wyniku znaki końca linii (CR+LF), by na
    //ekranie terminala wyniki pojawiały się w nowych liniach
    unsigned char z;
    for(z=0; z<30; z++){
    if(usart_bufor[z]==0){ //czy to koniec takstu w tablicy
    //tak znaleziono koniec tekstu, dodajemy znak CR
    usart_bufor[z] = 13; //znak powrotu karetki CR (Carrige Return)
    usart_bufor[z+1] = 10; //znak nowej linii LF (Line Feed)
    usart_bufor[z+2] = 0; //znak końca ciągu tekstu w tablicy
    break;
    }
    }


    //Zaczekaj, aż bufor nadawania będzie pusty
    while (!(UCSRA & (1<<UDRE)));

    //bufor jest pusty można wysłać

    //następny znak do wysyłki to znak nr 1
    usart_bufor_ind = 0;

    //włącz przerwania pustego bufora UDR, co rozpocznie transmisję
    //aktualnej zawartości bufora
    UCSRB |= (1<<UDRIE);

    }
    NAJWAZNIEJSZE wymuszalem warunek na if(1) i sprawdzalem przez funkcje wyslij wynik co sie kryje pod pewnymi zmiennymi, jak probowalem sprawdzic jakie wartosci sie kryja pod ICR1 to widzialem tylko zero. Jestem poczatkujacym w temacie mikrokontrolerow, wiec nie wiem czy zaklocam kod przerwaniami, czy to wina tranoptora. Dorzucam jeszcze petle while z main:
    while(1){
    //if(pomiar_gotowy)
    if(pomiar_gotowy){



    pomiar_ilosc_cykli = 65536 * ilosc_przepelnien - ICR1_moment_A
    + ICR1_moment_B;

    czas_jednego_obrotu = (double) pomiar_ilosc_cykli
    / POMIAR_ILOSC_OBROTOW / F_CPU ;

    obrotow_na_sekunde = 1.0 / czas_jednego_obrotu;

    //wyświetlenie wyników
    //normalnie dac tam czas jednego obrotu do wyslania
    dtostrf(czas_jednego_obrotu, 12, 5, usart_bufor);


    //sprintf(wynik,"%5.2frps ", obrotow_na_sekunde);

    wyslij_wynik(); //rozpocznij wysyłanie wyniku przez RS-232


    //pomiary co pół sekundy

    _delay_ms(500);

    //wykonaj następny pomiar
    wykonaj_pomiar();

    }

    OdpowiedzUsuń
    Odpowiedzi
    1. Skoro nie jesteś pewien działania transoptora, to najpierw sprawdziłbym transoptor pisząc prosty program, który miałby zapalać i gasił diodę. Dioda powinna migać w zależności od przysłaniania transoptora.

      Następnie sprawdziłbym, czy podanie stałego wyniku w funkcji dtostrf() poprawnie wysyła dane.

      Usuń
    2. Zadziałało, po prostu przykładałem transoptor zbyt daleko od enkodera (dopiero potem przeczytalem twoj artykul o transoptorach). Dzięki wielkie za odpowiedź :)

      Usuń
  23. Hmm, zmontowałem układ, zaprogramowałem mikrokontroler ale licznik pokazywał fałszywe wyniki i nie zerował wskazań po zakończeniu pomiaru. Sprawdziłem oscyloskopem przebieg na wejściu pin 14 był wadliwy (mój czarny odbijał IR) po usunięciu problemu nadal złe wskazania. Jaka może być przyczyna? Program wczytywałem z gotowego projektu na stronie bez modyfikacji.

    OdpowiedzUsuń
    Odpowiedzi
    1. Najważniejsza jest jakość sygnału. Jeśli jest prawidłowa, a program pobrany z udostępnionego pliku, to musi działać, tak jak na filmie.

      Na wszelki wypadek zapytam: Jak masz ustawione fusebity?

      Usuń
  24. Nie mam ustawionych. Właśnie odrabiam lekcje i próbuję doczytać. Dam znać jak mi poszło lub nie poszło.

    OdpowiedzUsuń
    Odpowiedzi
    1. Już wszystko działa. Nie ustawiłem Fuse Bitów na kwarc zew...
      Dziękuję za naprowadzenie i super blog.
      Pozdrawiam
      J.

      Usuń
  25. Witam miałbym prośbę o uaktualnienie linku do wykonanych przez Pana tarcz enkodera bo w tej chwili link nie jest aktywny.

    OdpowiedzUsuń
  26. Witam, czy jest możliwość przerobienia programy aby prętkość była wyświetlana na wyświetlaczu 7-segmentowym?

    OdpowiedzUsuń
    Odpowiedzi
    1. Moim zdaniem nie ma problemu, trzeba tylko napisać kod do obsługi wyświetlacza 7-segmentowego ( multipleksowanie np na innym Timerze)

      Usuń
  27. //zgaś flagę przerwania od przepełnienia
    TIFR |= (1<<TOV1);

    Proszę o informację - po co w programie jest ta linijka kodu co ona robi, po co gaszona jest flaga?

    OdpowiedzUsuń
  28. Mam dziwnie niestabilny pomiar choć badam prędkość układu o dużej bezwładności - ciężka tarcza gramofonu jest napędzana przez przekładnię 1:29 i silnik na którego osi jest enkoder z 18 otworami. Podałem napięcie stałe z zasilacza lab. i mam odczyt około 25 obr/sekundę, ale raz na jakiś czas wskakuje wartość 28 z hakiem. Chciałbym do tego dodać regulator PID i stabilizować obroty, ale z tak niestabilnym pomiarem traci to sens. Co robić?

    OdpowiedzUsuń