Mikrokontrolery - Jak zacząć?

... czyli zbiór praktycznej wiedzy dot. mikrokontrolerów.
Pokazywanie postów oznaczonych etykietą Silnik BLDC. Pokaż wszystkie posty
Pokazywanie postów oznaczonych etykietą Silnik BLDC. Pokaż wszystkie posty

piątek, 25 marca 2011

Silnik BLDC: Monitorowanie prędkości obrotowej przez RS-232 z wykorzystaniem programu SimPlot


Autor: Dondu Jacek

Artykuł z cyklu: Silnik BLDC: Spis treści

Silnik BLDC - Pomiar prędkości obrotowej i monitorowanie za pomocą komputera przez RS-232 z wykorzystaniem programu SimPlot.
Każdy silnik BLDC wymaga sterowania za pomocą elektronicznego komutatora w celu przełączania faz w czasie obrotu silnika. Proces ten realizuje np. mikrokontroler w momentach określanych za pomocą czujników Halla lub sygnałów Back EMF.

Skoro więc momenty komutacji są ściśle określone przez położenie wirnika, to mierząc odstępy czasu pomiędzy komutacjami, możemy określić jego prędkość obrotową. Znajomość prędkości może nam posłużyć do jej stabilizacji na wybranej wartości, a także do jej monitorownia np. za pomocą komputera.

W niniejszym artykule przedstawię:
  • sposób pomiaru prędkości obrotowej,
  • uśrednianie wyników,
  • komunikację za pomocą RS-232,
  • generowanie w czasie rzeczywistym wykresu na komputerze.

W artykule wykorzystuję silnik z czujnikami Halla, którego sterowanie omówiłem w artykule: Silnik BLDC z czujnikami Halla. Artykuł ten powinieneś poznać, zanim przeczytasz niniejszy.

A oto co czeka Ciebie w dalszej części artykułu:




Jak to zrobiłem? A tak ...


Pomiar prędkości obrotowej

W sterowaniu silnikiem z czujnikami Halla wykorzystaliśmy dwa timery do generowania 3 sygnałów PWM (po jednym dla każdej fazy). Były to Timer1 oraz Timer2 mikrokontrolera ATmega8. Mamy więc do dyspozycji jeszcze Timer0, którym możemy mierzyć czasy pomiędzy występującymi komutacjami i na tej podstawie określać prędkość obrotową.

Jednakże Timer0 możemy chcieć wykorzystać w jeszcze innym celu stąd warto byłoby oprzeć się o już wykorzystywane timery. W przypadku pomiaru prędkości obrotowej wzorować się będziemy na obrotomierzu zaprezentowanym w artykule: Obrotomierz na AVR ATmega8Artykuł ten powinieneś poznać, zanim przeczytasz niniejszy.

W powyższym artykule przedstawiłem metodę pomiaru z wykorzystaniem Input Capture Unit, czyli modułu, który jest dodatkiem do Timera1. Moduł ten umożliwia złapanie tzw. time stamp, czyli tzw. znaczników czasu wystąpienia jakiegoś zdarzenia. Innymi słowy zapamiętuje on w rejestrze ICR1 wartości licznika Timera1 (TCNT1) w momencie, który uznamy za stosowne. Wartość uchwyconą w rejestrze ICR1 zapamiętujemy w zmiennej i czekamy do kolejnego momentu, w którym ponownie występuje jakieś zdarzenie i łapiemy ponownie stan Timera1 do rejestru ICR1, skąd odczytamy go do drugiej zmiennej.

W ten sposób mamy uchwycony czas, który upłynął pomiędzy dwoma zdarzeniami, a znając częstotliwość taktowania Timera1 możemy dokładnie określić, czas pomiędzy obydwoma zdarzeniami uwzględniając dodatkowo przepełnienia Timera1, które w międzyczasie mogły nastąpić.

Jeżeli więc doprowadzimy do tego, że podczas komutacji faz wyłapiemy za pomocą tej funkcjonalności Timera1 dwa momenty, w których znamy położenie wirnika i zmierzymy w ten sposób czas zawarty pomiędzy tymi momentami, będziemy w stanie określić prędkość obrotową wirnika.


Określanie momentu zdarzenia

Zaglądamy do dokumentacji (datasheet) mikrokontrolera w celu stwierdzenia jakie możliwości określenia momentu zapamiętania stanu licznika Timera1 w rejestrze ICR1 mamy:


Atmega8 - Input Capture Unit - schemat blokowy.


Jak widać na schemacie blokowym tej funkcjonalności momentem zapamiętania (zatrzaśnięcia) w rejestrze ICR1 stanu licznika Timera1 (rejestru TCNT1), jest sygnał ICF1. Sygnał ten może być zboczem narastającym lub opadającym (zależnie od ustawień bitu ICES) i pochodzić może z:
  • pinu wejściowego ICP1
  • lub wyjścia komparaotra (ACO).

Ponieważ w naszym sterowniku z czujnikami Halla (podobnie w przypadku Back-EMF) wykorzystujemy komparator do określania momentów komutacji, stąd naturalnym jest wykorzystanie tej możliwości także do pomiaru prędkości.

W związku z tym, wybieramy rozwiązanie polegające na takim ustawieniu modułu Input Capture Unit, by komparator jednym ze zboczy sygnału powodował zapamiętanie w rejestrze ICR1 stanu licznika Timer1 (czyli rejestru TCNT1).

Pomocny nam będzie fakt, że zmiana stanu wyjścia komparatora służy nam do wygenerowania przerwania w celu dokonania następnej komutacji. Wykorzystamy ten fakt także w celu określania prędkości obrotowej.



Określanie prędkości obrotowej

Zacznijmy od tego, że silnik który stosuję ma parametr krotności równy cztery. Oznacza to, że na pełny jeden obrót następują cztery cykle komutacji (każdy po sześć komutacji).

Aby nasze pomiary były dokładne, zastosujemy pomiar czasu pełnego jednego obrotu. Na jeden obrót przypada, aż dziewięć przerwań komparatora (na przemian zbocza opadające i narastające wyjścia komparatora, czyli ACO):


Silnik BLDC - Oznaczenie momentu generowania przerwań przez fazę W za pomocą komparatora.


By zmierzyć czas trwania jednego pełnego obrotu wirnika, nie ma potrzeby śledzenia wszystkich dziewięciu przerwań. Wystarczy śledzić co drugie wybierając np. zbocze opadające, co pozwoli nam się zamknąć w pięciu przerwaniach.

W uproszczeniu algorytm będzie więc następujący:
  1. czekaj na pierwsze przerwanie zbocza opadającego wyjścia komparatora (ACO),
  2. zapamiętaj stan rejestru ICR1 jako moment A,
  3. czekaj na kolejne przerwania od zbocza opadającego zliczając w międzyczasie przepełnienia Timer1,
  4. gdy zostanie wywołane piąte przerwanie zbocza opadającego komparatora (ACO) oznacza to jeden pełny obrót i koniec pomiaru co oznacza, że należy zapamiętać stan ICR1 jako moment B.
  5. na podstawie rozdzielczości timera, częstotliwości zegara taktującego mikrokontroler, preskalera timera, momentu A, momentu B oraz ilości przepełnień obliczyć czas trwania jednego obrotu, i w konsekwencji prędkość obrotową.

Wydaje się zawiłe? No to do dzieła :-)


Schemat

Bazujemy na schemacie z poprzedniego artykułu, dodając do niego:
  • dwa przyciski do sterowania prędkością silnika,
  • wyjście TxD modułu USART, które wykorzystamy do przesyłania danych do komputera co opisałem w artykule: RS-232: Komunikacja ATmega8 z komputerem

Silnik BLDC - Schemat sterownika z pomiarem prędkości obrotowej oraz wysyłaniem danych do komputera za pomocą interfejsu RS-232.


Zauważ, że pomiar prędkości realizować będziemy w całości na bazie wewnętrznych zasobów mikrokontrolera i nie przeznaczymy na to żadnego dodatkowego pinu. Jedynie w celu dokonania testów i obserwacji wyników pomiarów wykorzystamy dwa piny na przyciski i jeden na wyjście RS-232.

Jako element sterujący wykorzystałem drajwer silnika BLDC, który opisałem w artykule: Silnik BLDC: Drajwer DIY



Program

Program poniższy wykorzystuje omówione wcześniej rozwiązania:

Jak zwykle także i ten program jest dokładnie komentowany, co pozwoli Ci na zrozumienie zasady jego działania w połączeniu z opisem przedstawionym wyżej. Jeżeli nie będziesz czegoś rozumiał, przeczytaj powyżej wymienione artykuły, a jeżeli nadal będziesz miał wątpliwości pytaj w komentarzach do niniejszego artykułu.

W przypadku obrotomierza różnica polega na tym, że w niniejszym rozwiązaniu nie stosuję przerwania od wystąpienia zdarzenia co sygnalizuje ICF1, ponieważ w dokładnie tym samym momencie (zamiana stanu wyjścia ACO komparatora) następuje przerwanie komparatora.

Dlatego też nie ma sensu ustanawiać dwóch przerwań skoro są wywoływane przez ten sam sygnał. W związku z tym przerwanie komparatora będzie także obsługiwać pomiar prędkości.


SimPlot

Do obrazowania prędkości użyłem prostego, ale bardzo przydatnego programu SimPlot:


SimPlot - przykład wykresu prędkości obrotowej silnika BLDC.


Oficjalna strona: https://code.google.com/p/projectsimplot/
Do pobrania wersja 1.2 użyta przeze mnie: SimPlot 1.2 (kopia)

Wyjaśnienia wymaga natomiast format danych przesyłanych do programu SimPlot za pomocą interfejsu RS-232. Format ten jest z góry określony przez wymagania programu. Dla dwóch danych (kanałów), które chcemy obserwować:
  • aktualny pomiar.
  • średnią prędkość.
format ten wygląda następująco:

Indeks Ilość bajtów Wartość domyślna Opis
0 2 0xCDAB Nagłówek (stała wartość)
1 2 0x04 Liczba bajtów danych (stała wartość)
2 2 dana typu int Dana kanału nr 1
3 2 dana typu int Dana kanału nr 2


Stąd nasz bufor będzie wyglądał następująco:
unsigned int  usart_bufor[4];
usart_bufor[0]  = 0xCDAB;           //nagłówek (header)
usart_bufor[1]  = 2 * sizeof(int);  //długość danych w bajtach
usart_bufor[2]  = predkosc_aktualna; 
usart_bufor[3]  = predkosc_srednia; 

Komunikacja z programem SimPlot jest bardo prosta. Każdy nowy pakiet danych (w naszym przypadku dwie liczby int obrazujące prędkość aktualnie zmierzoną oraz prędkość średnią) należy zapisać w buforze pokazanym wyżej w polach o indeksach 2 oraz 3. Następnie tak przygotowany bufor w całości wysyłamy bajt po bajcie za pomocą RS-232.

Po wysłaniu takiej paczki (łącznie 8 bajtów) nastąpi jej narysowanie na ekranie. Aby wysłać kolejne dane należy ponownie uzupełnić właściwe pola danych (indeks 2 i 3) i ponownie wysłać całą paczkę (bufor).

Dlatego też w programie w pliku main.c na początku ustaliłem dwa pierwsze pola jako stałe. ponieważ nie zmieniają się w czasie pracy programu:

//--- SimPlot ------------------------------------------
//przygotuj niezmienną (stałą) część pakietu danych SimPlot
usart_bufor[0]  = 0xCDAB;      //nagłówek (header)
usart_bufor[1]  = 2 * sizeof(int); //długość danych w bajtach

Pozostałe pola uzupełniam danymi przed wysłaniem całej paczki:
//--- SimPlot ------------------------------------------
//uzupełnij pakiet w buforze o dane z pomiaru
usart_bufor[2]  = (unsigned int) predkosc_rpm_akt;
usart_bufor[3]  = (unsigned int) predkosc_rpm_srednia;

Na filmie (patrz początek artykułu) pokazałem jak należy skonfigurować program SimPlot, by wykorzystać poniższy program do prezentacji danych na ekranie komputera.


Odstępy pomiędzy pomiarami

Aby pomiary prędkości odbywały się co założony odcinek czasu potrzebny był jakiś timer odmierzający stałe odcinki czasu. Mogłem użyć niewykorzystany Timer0, ale by nie być rozrzutnym i nie marnować zasobów, które mogą Tobie być potrzebne do innych celów, wykorzystałem do tego celu Timer2, który uczestniczy w generowaniu sygnału PWM dla jednej z faz silnika.

Sposób jest prosty:
  • Timer2 za pomocą przerwania od jego przepełnienia zlicza ilość przepełnień,
  • w pętli głównej w sposób nieblokujący sprawdzane jest, czy licznik przepełnień Timera2 osiągnął określony poziom, 
  • jeżeli tak, to następuje uruchomienie pomiaru, 
  • po zakończeniu pomiaru licznik przepełnień Timera2 jest zerowany, by odmierzać ponownie czas przerwy pomiędzy pomiarami.

Podczas pracy oraz na powyższym filmie możesz jednak zaobserwować, że w przypadku prędkości obrotowej zbliżonej do minimalnej, czas wysyłania kolejnych pomiarów do SimPlot, wydłuża się powyżej założonego w programie. Jest to wynikiem tego, że pomiar wykonywany jest dla jednego pełnego obrotu, a przy małych prędkościach trwa on dłużej niż założony czas pomiędzy pomiarami.


Średnia prędkość (cyfrowy filtr dolnoprzepustowy)

W programie zastosowałem uśrednianie prędkości obrotowej za pomocą średniej ważonej.

Średnia ważona, to średnia, która składa się z co najmniej dwóch danych, z których każda ma swoją wagę. Wagą tą jest po prostu mnożnik zwiększający lub zmniejszający udział wybranej danej w sumie wartości danych.

W naszym przypadku do obliczenia aktualnej średniej są brane pod uwagę tylko dwie dane:
  • poprzednia wartość średniej,
  • aktualny pomiar.
Stąd wzór, który zastosowałem wygląda następująco:



gdzie WagaSredniej jest liczbą całkowitą większą od zera. Pomiar aktualny ma wagę równą jeden dlatego też nie potrzeba jej ujmować we wzorze. Jeżeli więc ustalę wagę średniej na 3, to aktualny pomiar ma wpływ na średnią prędkość zaledwie w 25%, ponieważ suma składa się z:
  • 3 wartości poprzedniej średniej (75%),
  • oraz jednego aktualnego pomiaru (25%).

W programie wagę poprzedniej średniej zmienia się w definicji:

//Waga poprzedniej średniej prędkości podczas liczenia aktualnej średniej
#define WAGA_SREDNIEJ 3

Możesz więc zmniejszyć lub zwiększyć jej wpływ na obliczanie aktualnej średniej prędkości, a tym samym zmieniać parametry filtru dolnoprzepustowego. W konsekwencji średnia prędkość będzie mniej czuła na minimalne zmiany prędkości obrotowej, ale kosztem opóźnienia w stosunku do aktualnej prędkości obrotowej. Nie ma to większego znaczenia przy powolnych zmianach prędkości, ale przy szybkich zmianach możesz zaobserwować na filmie jak średnia nie nadąża za faktyczną zmianą prędkości.


Problem algorytmu

Na wykresach można zauważyć, że aktualny pomiar czasami generuje małe szpilki co przy jednostajnej prędkości obrotowej oznacza, że pomiar zostaje dokonany nieprawidłowo:


Przykład błędu pomiaru prędkości obrotowej silnika.


Prawdopodobną przyczyną jest zbieg okoliczności dot. jednoczesnego wykorzystania mikrokontrolera do sterowania silnikiem oraz dokonywania pomiarów. Dlatego zastosowanie algorytmu filtru dolnoprzepustowego do liczenia średniej prędkości obrotowej praktycznie niweluje ten problem.

Jednakże oznacza to, że niniejszy algorytm (program) nie jest idealny i można go poprawić. Jeżeli więc będziesz dociekliwy i odkryjesz co jest przyczyną tych szpilek liczę, że podzielisz się z nami tą wiedzą.



Zajętość pamięci

Wynik kompilacji poniższego programu (kompilator GCC optymalizacja -Os):

AVR Memory Usage
----------------
Device: atmega8

Program:    1614 bytes (19.7% Full)
(.text + .data + .bootloader)

Data:         31 bytes (3.0% Full)
(.data + .bss + .noinit)

Build succeeded with 0 Warnings...


Czy to dużo, czy mało?

Program zajmuje się zadaniami:

  1. sterowaniem silnikiem (kolejne komutacje),
  2. regulacją prędkości zmienianej przyciskami,
  3. pomiarami prędkości,
  4. obliczaniem wyniku do wysłania do komputera,
  5. wysyłaniem przez RS-232 do komputera.

Nie jest to mało zadań, ale też nie jest to dużo. Na pewno program da się nieco skrócić zmniejszając wykorzystanie pamięci programu oraz danych. Program został napisany w taki sposób, by można było go łatwo analizować i zrozumieć jego działanie. Droga do jego optymalizacji jest więc otwarta, a jeżeli takiej dokonasz, to z przyjemnością ją opublikujemy.


Do pobrania

Do pobrania kompletny projekt AVR Studio 4.18 wraz z plikiem .hex dla 8MHz: BLDC-11.zip (kopia)


dd_bldc.h
/*************************************************************************
* Sterownik silnika BLDC.
*
* Sterowanie silnikiem z wykorzystaniem czujników Halla wraz z pomiarem
* prędkości obrotowej i jej monitorowaniem za pomocą RS-232
* z wykorzystaniemprogramu SimPlot
*
* Data:   05 marca 2014r.
* Autor:  Dondu
*
* Plik:   dd_bldc.h
* Opis:   Plik nagłówkowy silnika BLDC, w tym pomiaru prędkości
*
* Mikrokontroler: ATmega8
* F_CPU:      8MHz (ustaw w opcjach projektu)
*
* Szczegóły: http://mikrokontrolery.blogspot.com/2011/03/Silnik-BLDC-Obrotomierz-monitorowanie-przez-RS232-SimPlot.html
*************************************************************************/


//---  B E Z P I E C Z N I K  ---------------------------------------

//LED bezpiecznika
#define BEZP_LED_DDR    DDRC
#define BEZP_LED_PORT   PORTC
#define BEZP_LED_PIN    PC3

//---  S I L N I K  ---------------------------------------

#define KROTNOSC_SILNIKA  4

//Faza U
//tranzystor górny
#define U_TR_G_PORTx    PORTB
#define U_TR_G_DDRx     DDRB
#define U_TR_G_PINx     PINB
#define U_TR_G_PIN      PB1
#define U_TR_G_USTAW_DDR  U_TR_G_DDRx  |=  (1<<U_TR_G_PIN); //ustaw port
#define U_TR_G_PIN_L    U_TR_G_PORTx &= ~(1<<U_TR_G_PIN); //ustaw niski
#define U_TR_G_ON       TCCR1A     |=  (1<<COM1A1);   //włącz tranzystor
#define U_TR_G_OFF      TCCR1A     &= ~(1<<COM1A1);   //wyłącz tranzystor
#define U_TR_G_SPRAW_STAN (TCCR1A & (1<<COM1A1))        //warunek stanu 
//tranzystor dolny
#define U_TR_D_PORTx    PORTD
#define U_TR_D_DDRx     DDRD
#define U_TR_D_PINx     PIND
#define U_TR_D_PIN      PD4
#define U_TR_D_USTAW_DDR  U_TR_D_DDRx  |=  (1<<U_TR_D_PIN); //ustaw port
#define U_TR_D_PIN_L    U_TR_D_PORTx &= ~(1<<U_TR_D_PIN); //ustaw niski
#define U_TR_D_ON       U_TR_D_PORTx |=  (1<<U_TR_D_PIN); //włącz tranz.
#define U_TR_D_OFF      U_TR_D_PORTx &= ~(1<<U_TR_D_PIN); //wyłącz tranz.
#define U_TR_D_SPRAW_STAN (U_TR_D_PINx  &  (1<<U_TR_D_PIN)) //warunek stanu

//Faza V
//tranzystor górny
#define V_TR_G_PORTx    PORTB
#define V_TR_G_DDRx     DDRB
#define V_TR_G_PINx     PINB
#define V_TR_G_PIN      PB3
#define V_TR_G_USTAW_DDR  V_TR_G_DDRx  |=  (1<<V_TR_G_PIN); //ustaw port
#define V_TR_G_PIN_L    V_TR_G_PORTx &= ~(1<<V_TR_G_PIN); //ustaw niski
#define V_TR_G_ON       TCCR2    |=   (1<<COM21);   //włącz tranzystor
#define V_TR_G_OFF      TCCR2    &=  ~(1<<COM21);   //wyłącz tranzystor
#define V_TR_G_SPRAW_STAN (TCCR2 & (1<<COM21))        //warunek stanu 
//tranzystor dolny
#define V_TR_D_PORTx    PORTD
#define V_TR_D_DDRx     DDRD
#define V_TR_D_PINx     PIND
#define V_TR_D_PIN      PD5
#define V_TR_D_USTAW_DDR  V_TR_D_DDRx  |=  (1<<V_TR_D_PIN); //ustaw port
#define V_TR_D_PIN_L    V_TR_D_PORTx &= ~(1<<V_TR_D_PIN); //ustaw niski
#define V_TR_D_ON       V_TR_D_PORTx |=  (1<<V_TR_D_PIN); //włącz tranz.
#define V_TR_D_OFF      V_TR_D_PORTx &= ~(1<<V_TR_D_PIN); //wyłącz tranz.
#define V_TR_D_SPRAW_STAN (V_TR_D_PINx  &  (1<<V_TR_D_PIN)) //warunek stanu


//Faza W
//tranzystor górny
#define W_TR_G_PORTx    PORTB
#define W_TR_G_DDRx     DDRB
#define W_TR_G_PINx     PINB
#define W_TR_G_PIN      PB2
#define W_TR_G_USTAW_DDR  W_TR_G_DDRx  |=  (1<<W_TR_G_PIN); //ustaw port
#define W_TR_G_PIN_L    W_TR_G_PORTx &= ~(1<<W_TR_G_PIN); //ustaw niski
#define W_TR_G_ON       TCCR1A     |=  (1<<COM1B1);   //włącz tranzystor
#define W_TR_G_OFF      TCCR1A     &= ~(1<<COM1B1);   //wyłącz tranzystor
#define W_TR_G_SPRAW_STAN (TCCR1A & (1<<COM1B1))        //warunek stanu 
//tranzystor dolny
#define W_TR_D_PORTx    PORTC
#define W_TR_D_DDRx     DDRC
#define W_TR_D_PINx     PINC
#define W_TR_D_PIN      PC5
#define W_TR_D_USTAW_DDR  W_TR_D_DDRx  |=  (1<<W_TR_D_PIN); //ustaw port
#define W_TR_D_PIN_L    W_TR_D_PORTx &= ~(1<<W_TR_D_PIN); //ustaw niski
#define W_TR_D_ON       W_TR_D_PORTx |=  (1<<W_TR_D_PIN); //włącz tranz.
#define W_TR_D_OFF      W_TR_D_PORTx &= ~(1<<W_TR_D_PIN); //wyłącz tranz.
#define W_TR_D_SPRAW_STAN (W_TR_D_PINx &   (1<<W_TR_D_PIN)) //warunek stanu


//Wspólna definicja wyłączająca wszystkie tranzystory
#define WYLACZ_TRANZYSTORY  U_TR_G_OFF; U_TR_D_OFF; V_TR_G_OFF; V_TR_D_OFF; W_TR_G_OFF; W_TR_D_OFF;



//---  P W M ---------------------------------------
#define PWM_START 255 //wartość wypełnienia PWM podczas startu silnika
#define PWM_MAX   255 //maksymalna wartość wypełnienia PWM do stałej pracy
#define PWM_MIN   20  //minimalna wartość wypełnienia PWM, dla której
//silnik będzie w stanie się kręcić (dobrane
//doświadczalnie)

//--- T I M E R Y ---------------------------------------
#define TIMERY_PRESCALER  8 //tutaj wybierz preskaler
//dozwolone wartości 1, 8, 64, 256, 1024
//Preskaler 8 to optymalna wielkość
#if   TIMERY_PRESCALER == 8
#define TIMER1_PRESCALER  ((1<<CS11))
#define TIMER2_PRESCALER  ((1<<CS21))
#elif   TIMERY_PRESCALER == 64
#define TIMER1_PRESCALER  ((1<<CS11) | (1<<CS10))
#define TIMER2_PRESCALER  ((1<<CS22))
#elif   TIMERY_PRESCALER == 256
#define TIMER1_PRESCALER  ((1<<CS12))
#define TIMER2_PRESCALER  ((1<<CS22) | (1<<CS21))
#elif   TIMERY_PRESCALER == 1024
#define TIMER1_PRESCALER  ((1<<CS12) | (1<<CS10))
#define TIMER2_PRESCALER  ((1<<CS22) | (1<<CS21) | (1<<CS20))
#else //domyślnie prescaler = 1
#define TIMER1_PRESCALER  ((1<<CS10))
#define TIMER2_PRESCALER  ((1<<CS20))
#endif




//---  K O M P A R A T O R  ---------------------------------------
#define KOMP_STAN_AKT   (ACSR & (1<<ACO)) //aktualny stan wyjścia 
//komparatora
#define KOMP_ZBOCZE_DOWOLNE ACSR &= ~((1<<ACIS1) | (1<<ACIS0)) //dowolne zbocze

//---  P R Z E R W A N I A   I N T ---------------------------------------
#define INT0_ZBOCZE_DOWOLNE   ((0<<ISC01) | (1<<ISC00))
#define INT1_ZBOCZE_DOWOLNE   ((0<<ISC11) | (1<<ISC10))


//--- F U N K C J E ---------------------------------------
extern void bldc_bezpiecznik(void);
extern void bldc_bezpiecznik_stop(void);
extern void bldc_hall_inicjuj_sterownik(void);
extern void bldc_start(void);


//--- S T A T U S   P O M I A R U  ---------------------------
#define POMIAR_OCZEKUJE   0
#define POMIAR_ROZPOCZETY 1
#define POMIAR_ZAKONCZONY 2

//tutaj ustaw własne zasady pomiaru
#define CZAS_POMIEDZY_POMIARAMI_MS    250UL
#define POMIAR_ILOSC_IMPULSOW_NA_OBROT  5 //ilość inpulsów na jeden obrót
#define POMIAR_ILOSC_OBROTOW            1 //ile obrotów ma trwać pomiar


//extern volatile unsigned long int  pomiar_ilosc_cykli;
extern volatile unsigned int       Timer1_ilosc_przepelnien;
extern volatile unsigned int       Timer2_ilosc_przepelnien;
extern volatile unsigned char      pomiar_zbocze_nr;
extern volatile unsigned char      pomiar_status;

extern volatile unsigned char       ICR1_moment_A;
extern volatile unsigned char       ICR1_moment_B;

extern void wykonaj_pomiar(void);



dd_bldc.c
/*************************************************************************
* Sterownik silnika BLDC.
*
* Sterowanie silnikiem z wykorzystaniem czujników Halla wraz z pomiarem
* prędkości obrotowej i jej monitorowaniem za pomocą RS-232
* z wykorzystaniemprogramu SimPlot
*
* Data:   05 marca 2014r.
* Autor:  Dondu
*
* Plik:   dd_bldc.c
* Opis:   Funkcje silnika BLDC, w tym pomiaru prędkości
*
* Mikrokontroler: ATmega8
* F_CPU:      8MHz (ustaw w opcjach projektu)
*
* Szczegóły: http://mikrokontrolery.blogspot.com/2011/03/Silnik-BLDC-Obrotomierz-monitorowanie-przez-RS232-SimPlot.html
*************************************************************************/


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


volatile unsigned int       Timer1_ilosc_przepelnien;
volatile unsigned int       Timer2_ilosc_przepelnien;
volatile unsigned char      pomiar_zbocze_nr;
volatile unsigned char      pomiar_status;

volatile unsigned char       ICR1_moment_A;
volatile unsigned char       ICR1_moment_B;



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

void bldc_bezpiecznik_stop(void)
{

  //Funkcja wyłącza wszelkie tranzystory oraz przechodzi w stan sygnalizacji
  //błędu komutacji. Funkcja ta razem z funkcją bezpiecznik() pełni rolę
  //zabezpieczenia przeciwzwarciowego dla błędnie działającego algorytmu
  //komutacji w czasie pisania i testów programu.

  //wyłącz przerwania
  cli();

  //natychmiast wyłącz tranzystory
  WYLACZ_TRANZYSTORY

  //i ustaw stany niskie na pinach sterujących
  U_TR_G_USTAW_DDR
  V_TR_G_USTAW_DDR
  W_TR_G_USTAW_DDR
  U_TR_D_USTAW_DDR
  V_TR_D_USTAW_DDR
  W_TR_D_USTAW_DDR
  U_TR_G_PIN_L
  V_TR_G_PIN_L
  W_TR_G_PIN_L
  U_TR_D_PIN_L
  V_TR_D_PIN_L
  W_TR_D_PIN_L

  //ustaw pin LED jako wyjście
  BEZP_LED_DDR  |= (1<<BEZP_LED_PIN);

  //zatrzymaj program w pętli nieskończonej sygnalizując błąd
  while(1) {

    //zmień stan LED na przeciwny
    BEZP_LED_PORT  ^= (1<<BEZP_LED_PIN);

    //co 100ms
    _delay_ms(100);

  }
}


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

void bldc_start(void)
{

  //Funkcja odpowiedzialna za:
  //1. pozycjnowanie wirnika przed uruchomieniem silnika,
  //2. wykonanie ruchu silnikiem w kierunku obrotów, co ma
  //   zainicjować start silnika

  //W funkcji używamy opóźnień, które należy odpowiednio wydłużyć,
  //jeśli wirnik silnika ma dużą bezwładność

  //ustaw wypełnienie podczas startu (wymagana duża moc silnika)
  OCR1A = PWM_START;
  OCR1B = PWM_START;
  OCR2  = PWM_START;

  //pozycjonowanie wirnika
  U_TR_D_ON
  W_TR_G_ON
  bldc_bezpiecznik();
  _delay_ms(1000);  //odczekaj na przekręcenie się wirnika
  //do pozycji startowej

  //wyłącz tranzystory
  U_TR_D_OFF
  W_TR_G_OFF

  //włącz przerwania
  sei();

  //start poprzez zakręcenie wirnikiem w odpowiednim kierunku
  //resztę zrobią już funkcje przerwań
  U_TR_G_ON
  V_TR_D_ON
  bldc_bezpiecznik();
  _delay_ms(100);

  //ustaw wypełnienie podczas startu (wymagana duża moc silnika)
  OCR1A = PWM_MIN;
  OCR1B = PWM_MIN;
  OCR2  = PWM_MIN;

}



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

void bldc_bezpiecznik(void)
{


  //Sprawdzamy, czy nie występuje konflikt sterowania, powodujący
  //jednoczene otwarcie tranzystora górnego i dolnego w tej samej fazie,
  //co oznacza wystąpienie zwarcia !!!

  if(U_TR_G_SPRAW_STAN && U_TR_D_SPRAW_STAN) {

    //Faza U - oba tranzystory są włączone - sytuacja niedopuszczalna!!!
    bldc_bezpiecznik_stop();


  } else if(V_TR_G_SPRAW_STAN && V_TR_D_SPRAW_STAN) {

    //Faza V - oba tranzystory są włączone - sytuacja niedopuszczalna!!!
    bldc_bezpiecznik_stop();


  } else if(W_TR_G_SPRAW_STAN && W_TR_D_SPRAW_STAN) {

    //Faza W - oba tranzystory są włączone - sytuacja niedopuszczalna!!!
    bldc_bezpiecznik_stop();

  }

}



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


void bldc_hall_inicjuj_sterownik(void)
{

  //Funkcja inicjująca pracę sterownika.
  //-konfiguruje wszystkie wykorzystywane moduły mikrokontrolera
  //-ustawia piny mikrokontrolera tak, by wszystkie tranzystory
  // były wyłączone
  //-ustawia licznikia PWM na początkową wartość DutyCycle

  //ustaw stan początkowy wyjść sterujących tranzystorami
  U_TR_G_USTAW_DDR
  V_TR_G_USTAW_DDR
  W_TR_G_USTAW_DDR
  U_TR_D_USTAW_DDR
  V_TR_D_USTAW_DDR
  W_TR_D_USTAW_DDR
  U_TR_G_PIN_L
  V_TR_G_PIN_L
  W_TR_G_PIN_L
  U_TR_D_PIN_L
  V_TR_D_PIN_L
  W_TR_D_PIN_L

  //sprawdzamy, czy nie ma stanu zabronionego na tranzystorach
  //ZAWSZE WYWOŁUJ TĘ FUNKCJĘ, GDY ZMIENIASZ STAN TRANZYSTORÓW!!!
  bldc_bezpiecznik();



  //--- T I M E R Y    P W M   -------------------

  //Timer1
  //Obsługa PWM dla wyjść OC1A oraz OC1B
  //Mode 5 (Fast PWM, 8bit)
  //Clear OC1A/OC1B on Compare Match, set OC1A/OC1B at BOTTOM,
  //(non-inverting mode)
  //preskaler wybierz w pliku bldc.h
  TCCR1A  |= (1<<WGM10) | (1<<COM1A1) | (1<<COM1B1);
  TCCR1B  |= (1<<WGM12) | TIMER1_PRESCALER;


  //Timer2
  //Obsługa PWM  dla wyjścia OC2 oraz odstępów pomiędzy pomiarami
  //Mode 3 (Fast PWM, 8bit)
  //Clear OC2 on Compare Match, set OC2 at BOTTOM, (non-inverting mode)
  //preskaler wybierz w pliku bldc.h
  TCCR2 |= (1<<WGM21) | (1<<WGM20) | (1<<COM21) | TIMER2_PRESCALER;

  //przerwanie przepełnienia dla pomiaru prędkości
  TIMSK |= (1<<TOIE2);

  //ustaw wartość początkową PWM
  OCR1A   = PWM_MIN;
  OCR1B   = PWM_MIN;
  OCR2  = PWM_MIN;

  //Pomiar prędkości
  ACSR  |= (1<<ACIC); //połącz wyjście ACO komparatora z Timerem1
  //w celu wyzwolenia zapamiętania aktualnej
  //wartości licznika Timer1 w rejestrze ICR1



  //--- K O M P A R A T O R ---
  //Wykrywanie zboczy sygnału czujnika Halla fazy W

  //napięcie referencyjne komparatora na wejście AIN0
  ACSR  |= (1<<ACBG);   //włącz BANDGAP na AIN0

  //wejście AIN1 czujnika fazy W
  DDRD  &= ~(1<<PD7); //pin jako wejście

  //zbocze komparatora
  //dowolne zbocze jest domyślnie ustawione po resecie
  //nie musimy nic ustawiać, ale na wszelki wypadek
  KOMP_ZBOCZE_DOWOLNE;  //dowolne zbocze

  //włącz przerwania z komparatora
  ACSR  |= (1<<ACIE);



  //--- P r z e r w a n i a  INT0 i INT1 ---
  //Wykrywanie zboczy sygnałów czujników Halla faz U oraz V

  DDRD  &= ~((1<<PD3) | (1<<PD2));  //INT0 iINT1 jako wejścia
  MCUCR   |= INT1_ZBOCZE_DOWOLNE | INT0_ZBOCZE_DOWOLNE; //oba zbocza
  GICR  |= (1<<INT1) | (1<<INT0); //włącz przerwania


  //na wszelki wypadek
  WYLACZ_TRANZYSTORY
  bldc_bezpiecznik();

}


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


ISR(INT0_vect)
{

  //Przerwanie czujnika fazy U
  //odpowiada za dokonanie następnej komutacji

  //w zależności, które zbocze sygnału U
  if((PIND & (1<<PD2))) {

    //Komutacja nr 4

    //wyłącz tranzystory
    U_TR_G_OFF
    V_TR_D_OFF
    W_TR_G_OFF
    W_TR_D_OFF

    //włącz tranzystory
    U_TR_D_ON
    V_TR_G_ON

    bldc_bezpiecznik();


  } else {

    //Komutacja nr 1

    //wyłącz tranzystory
    U_TR_D_OFF
    V_TR_G_OFF
    W_TR_G_OFF
    W_TR_D_OFF

    //włącz tranzystory
    U_TR_G_ON
    V_TR_D_ON

    bldc_bezpiecznik();

  }

}

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


ISR(INT1_vect)
{

  //Przerwanie czujnika fazy V
  //odpowiada za dokonanie następnej komutacji

  //w zależności, które zbocze sygnału V
  if(PIND & (1<<PD3)) {

    //Komutacja nr 2

    //wyłącz tranzystory
    U_TR_D_OFF
    V_TR_G_OFF
    V_TR_D_OFF
    W_TR_G_OFF

    //włącz tranzystory
    U_TR_G_ON
    W_TR_D_ON

    bldc_bezpiecznik();

  } else {

    //Komutacja nr 5

    //wyłącz tranzystory
    U_TR_G_OFF
    V_TR_G_OFF
    V_TR_D_OFF
    W_TR_D_OFF

    //włącz tranzystory
    U_TR_D_ON
    W_TR_G_ON

    bldc_bezpiecznik();

  }
}


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


ISR(ANA_COMP_vect)
{

  //Przerwanie czujnika fazy W
  //odpowiada za dokonanie następnej komutacji

  //UWAGA!!! Sygnał ten jest zanegowany w przeciwieństwie do fazy U i V
  //ponieważ Hallotron fazy W podłączony jest do wejścia odwracającego
  //komparatora. Dlatego też komutacje są zamienione miejscami (3 z 6)

  //w zależności, które zbocze sygnału W
  if(KOMP_STAN_AKT) {

    //Komutacja nr 3

    //wyłącz tranzystory
    U_TR_G_OFF
    U_TR_D_OFF
    V_TR_D_OFF
    W_TR_G_OFF

    //włącz tranzystory
    V_TR_G_ON
    W_TR_D_ON

    bldc_bezpiecznik();



  } else {

    //Komutacja nr 6

    //wyłącz tranzystory
    U_TR_G_OFF
    U_TR_D_OFF
    V_TR_G_OFF
    W_TR_D_OFF

    //włącz tranzystory
    V_TR_D_ON
    W_TR_G_ON

    bldc_bezpiecznik();


    if(pomiar_status  == POMIAR_ROZPOCZETY) {

      //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

        //zeruj licznik preskalera
        //UWAGA!!! W ATmega8 bit PSR10 zeruje licznik preskalera
        //zarówno dla Timer1 jak i Timer0
        SFIOR |= (1<<PSR2) | (1<<PSR10);

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

        //włącz przerwania od przepełnienia Timer1
        TIMSK |= (1<<TOIE1);

        //zapamiętujemy stan licznika początku obrotu (moment A)
        ICR1_moment_A = (unsigned char) ICR1_aktualny & 0xff;

      } else {

        //to kolejne zbocza

        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 = (unsigned char) ICR1_aktualny & 0xff;

          pomiar_status  = POMIAR_ZAKONCZONY;

        } else {

          //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
  Timer1_ilosc_przepelnien++;

}


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

ISR(TIMER2_OVF_vect)
{

  //powiększ licznik przepełnień Timera2
  Timer2_ilosc_przepelnien++;

}


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


void wykonaj_pomiar(void)
{

  //Funkcja włącza pomiar

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

  //ustawiamy nr pierwszego zbocza
  pomiar_zbocze_nr = 1;

  //zerujemy flagę gotowości pomiaru
  pomiar_status = POMIAR_ROZPOCZETY;

}



dd_usart.h
/*************************************************************************
* Sterownik silnika BLDC.
*
* Sterowanie silnikiem z wykorzystaniem czujników Halla wraz z pomiarem
* prędkości obrotowej i jej monitorowaniem za pomocą RS-232
* z wykorzystaniemprogramu SimPlot
*
* Data:   05 marca 2014r.
* Autor:  Dondu
*
* Plik:   dd_usart.h
* Opis:   Plik nagłówkowy komunikacji RS-232
*
* Mikrokontroler: ATmega8
* F_CPU:      8MHz (ustaw w opcjach projektu)
*
* Szczegóły: http://mikrokontrolery.blogspot.com/2011/03/Silnik-BLDC-Obrotomierz-monitorowanie-przez-RS232-SimPlot.html
*************************************************************************/


//USART - tablica bufora wysyłki oraz zmienna indeksu tablicy
#define USART_BUFOR_DLUGOSC   4   //rozmiar bufora
extern volatile unsigned int  usart_bufor[USART_BUFOR_DLUGOSC]; //bufor
extern volatile unsigned char *usart_bufor_wsk; //wskaźnik na bajty bufora
extern volatile unsigned char usart_bufor_ind;  //indeks bufora

//Funkcje
extern void usart_inicjuj(void);
extern void usart_wyslij_bufor(void);



dd_usart.c
/*************************************************************************
* Sterownik silnika BLDC.
*
* Sterowanie silnikiem z wykorzystaniem czujników Halla wraz z pomiarem
* prędkości obrotowej i jej monitorowaniem za pomocą RS-232
* z wykorzystaniemprogramu SimPlot
*
* Data:   05 marca 2014r.
* Autor:  Dondu
*
* Plik:   dd_usart.c
* Opis:   Funkcje komunikacji RS-232
*
* Mikrokontroler: ATmega8
* F_CPU:      8MHz (ustaw w opcjach projektu)
*
* Szczegóły: http://mikrokontrolery.blogspot.com/2011/03/Silnik-BLDC-Obrotomierz-monitorowanie-przez-RS232-SimPlot.html
*************************************************************************/


#include <avr/io.h>
#include <avr/interrupt.h>

#include "dd_usart.h"

//USART - tablica bufora wysyłki oraz zmienna indeksu tablicy
volatile unsigned int usart_bufor[USART_BUFOR_DLUGOSC]; //bufor
volatile unsigned char *usart_bufor_wsk; //wskaźnik na bajty bufora
volatile unsigned char usart_bufor_ind;  //indeks bufora


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

void usart_inicjuj(void)
{
  //definiowanie parametrów transmisji za pomocą makr zawartych w pliku
  //nagłówkowym setbaud.h. Jeżeli wybierzesz prędkość, która nie będzie
  //możliwa do realizacji otrzymasz ostrzeżenie:
  //#warning "Baud rate achieved is higher than allowed"

#define BAUD 38400         //tutaj podaj żądaną prędkość transmisji
#include <util/setbaud.h>  //linkowanie tego pliku musi być 
  //po zdefiniowaniu BAUD

  //ustaw obliczone przez makro wartości
  UBRRH = UBRRH_VALUE;
  UBRRL = UBRRL_VALUE;
#if USE_2X
  UCSRA |= (1<<U2X);
#else
  UCSRA &= ~(1<<U2X);
#endif

  //Ustawiamy pozostałe parametry moduł USART
  //zobacz: http://mikrokontrolery.blogspot.com/2011/04/Pulapki-AVR-Rejestry-pod-tym-samym-adresem.html
  UCSRC = (1<<URSEL) | (1<<UCSZ1) | (1<<UCSZ0);   //bitów danych: 8
  //bity stopu:   1
  //parzystość:   brak
  //włącz nadajnik
  UCSRB |= (1<<TXEN);
}


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

void usart_wyslij_bufor(void)
{

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

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

  //ustaw wskaźnik na początek bufora
  usart_bufor_wsk = (unsigned char *) usart_bufor;
  //zeruj indeks bufora
  usart_bufor_ind = 0;


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

}


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

ISR(USART_UDRE_vect)
{

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

  //każdy bajt z tablicy bufora
  if(usart_bufor_ind < sizeof(usart_bufor)) {

    //załaduj bajt danych do rejestru wysyłki i ustaw wskaźnik
    //na następny bajt bufora
    UDR = * usart_bufor_wsk++;

    //zwiększ licznik bajtów wysłanych
    usart_bufor_ind++;

  } else {

    //wysłano wszystkie dane z bufora usart_bufor[]
    UCSRB &= ~(1<<UDRIE); //wyłącz przerwania pustego bufora nadawania

  }
}




main.c
/*************************************************************************
* Sterownik silnika BLDC.
*
* Sterowanie silnikiem z wykorzystaniem czujników Halla wraz z pomiarem
* prędkości obrotowej i jej monitorowaniem za pomocą RS-232
* z wykorzystaniemprogramu SimPlot
*
* Data:   05 marca 2014r.
* Autor:  Dondu
*
* Plik:   main.c
*
* Mikrokontroler: ATmega8
* F_CPU:      8MHz (ustaw w opcjach projektu)
*
* Szczegóły: http://mikrokontrolery.blogspot.com/2011/03/Silnik-BLDC-Obrotomierz-monitorowanie-przez-RS232-SimPlot.html
*************************************************************************/


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

#include "dd_bldc.h"
#include "dd_usart.h"


//Pull-up przycisków zmiany prędkości obrotowej (do testów)
//przycisk zwiększania
#define PRZYCISK_ZWIEKSZ_PORTx  PORTC
#define PRZYCISK_ZWIEKSZ_PINx PC4
//przycisk zmniejszania
#define PRZYCISK_ZMNIEJSZ_PORTx PORTC
#define PRZYCISK_ZMNIEJSZ_PINx  PC2

//Waga poprzedniej średniej prędkości podczas liczenia aktualnej średniej
#define WAGA_SREDNIEJ 3

volatile unsigned long int  predkosc_rpm_akt;
volatile unsigned long int  predkosc_rpm_srednia;

unsigned char PWM_akt = PWM_MIN;

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

int main(void)
{

  //opóźnienie na ustabilizowanie zasilania (można usunąć)
  _delay_ms(10);

  //Włącz rezystory pull-up przycisków
  PRZYCISK_ZWIEKSZ_PORTx  |= (1<<PRZYCISK_ZWIEKSZ_PINx);
  PRZYCISK_ZMNIEJSZ_PORTx |= (1<<PRZYCISK_ZMNIEJSZ_PINx);

  //Inicjuj RS-232
  usart_inicjuj();

  //inicjujemy sterownik BLDC
  bldc_hall_inicjuj_sterownik();

  //uruchom silnik
  bldc_start();

  //--- SimPlot ------------------------------------------
  //przygotuj niezmienną (stałą) część pakietu danych SimPlot
  usart_bufor[0]  = 0xCDAB;      //nagłówek (header)
  usart_bufor[1]  = 2 * sizeof(int); //długość danych w bajtach
  //dwie serie danych typu int


  //pętla główna
  while(1) {


    //Tutaj Twój program ....



    //--- Inicjuj pomiar --------------------------
    //Wykonaj pomiar, gdy nadszedł właściwy moment.
    //ilość przerwań od przepełnienia Timer2 na jedną sekundę
    //obliczamy F_CPU/256 (Timer2 ma 8 bitów stąd 256)
    //1000 ponieważ odstępy czasowe pomiarów podajemy w milisekundach
    if((pomiar_status == POMIAR_OCZEKUJE)
        && (Timer2_ilosc_przepelnien >
            (unsigned int)(CZAS_POMIEDZY_POMIARAMI_MS * F_CPU / 256 / TIMERY_PRESCALER /
                           3 / 1000))
      ) {
      wykonaj_pomiar();
    }



    //--- Czy dokonano już pomiaru? --------------------------
    if(pomiar_status == POMIAR_ZAKONCZONY) {

      predkosc_rpm_akt = 60UL * F_CPU / (TIMERY_PRESCALER  *
           (256UL * Timer1_ilosc_przepelnien + ICR1_moment_B - ICR1_moment_A))
           /POMIAR_ILOSC_OBROTOW;

      predkosc_rpm_srednia = (WAGA_SREDNIEJ * predkosc_rpm_srednia +
                              predkosc_rpm_akt) / (WAGA_SREDNIEJ + 1);


      //--- SimPlot ------------------------------------------
      //uzupełnij pakiet w buforze o dane z pomiaru
      usart_bufor[2]  = (unsigned int) predkosc_rpm_akt;
      usart_bufor[3]  = (unsigned int) predkosc_rpm_srednia;

      //wyślij dane SimPlot
      usart_wyslij_bufor();


      //--- Pozostałe --------------------------------------
      //zmień status pomiaru
      pomiar_status = POMIAR_OCZEKUJE;

      //zeruj licznik przepełnień Timer2
      Timer2_ilosc_przepelnien = 0;

    }



    //--- Przycisk zwiększania prędkości --------------------------
    if(!(PINC & (1<<PC4))) {

      //nie przekraczamy maksymalnej wartości
      if(PWM_akt < PWM_MAX) {
        PWM_akt++;
      }

      //ustaw aktualne PWM
      OCR1A = PWM_akt;
      OCR1B = PWM_akt;
      OCR2  = PWM_akt;

      //opóźnienie autorepetycji przycisku
      _delay_ms(5);
    }



    //--- Przycisk zmniejszania prędkości --------------------------
    if(!(PINC & (1<<PC2))) {

      //nie przekraczamy minimalnej wartości
      if(PWM_akt > PWM_MIN) {
        PWM_akt--;
      }

      //ustaw aktualne PWM
      OCR1A = PWM_akt;
      OCR1B = PWM_akt;
      OCR2  = PWM_akt;

      //opóźnienie autorepetycji przycisku
      _delay_ms(5);
    }
  }
}



Oceń artykuł.
Wasze opinie są dla nas ważne, gdyż pozwalają dopracować poszczególne artykuły.
Pozdrawiamy, Autorzy
Ten artykuł oceniam na:

Silnik BLDC z czujnikami Halla


Autor: Dondu Jacek

Artykuł z cyklu: Silnik BLDC: Spis treści

W poprzednich odcinkach moment kolejnych komutacji ustalaliśmy na podstawie sygnałów Back EMF. Ta metoda ma istotną wadę polegającą na tym, że sygnały BEMF są bardzo słabe przy małych prędkościach obrotowych. Skutkiem tego jest znaczne utrudnienie startu silnika i utrzymania malutkich prędkości obrotowych.

Dlatego też do silników wprowadzono dodatkową elektronikę w postaci czujników Halla z odpowiednim układem elektronicznym. W tym artykule pokażę jak wykorzystać taki układ do sterowania silnika BLDC.

W poprzednich odcinkach wykorzystywałem dwa silniki z czego jeden był wyposażony w czujniki Halla, a drugi nie. W niniejszym artykule posłużę się więc tym pierwszym, a jego elektronika wygląda następująco:


Silnik BLDC - Elektronika z czujnikami Halla.




Moduł czujników - inżynieria odwrotna

Najpierw ustaliłem z jakim układem scalonym mam do czynienia. W dobie internetu było to oczywiście proste, a okazał się nim układ zgodny z LM2901 zawierający cztery komparatory.

Datasheet: LM2901 (kopia)

Ustaliłem więc wyprowadzenia układu:


LM2901 - Schemat ideowy oraz wyprowadzeń.


Na płytce zlokalizowałem czujniki Halla (wiedząc, że każdy ma cztery wyprowadzenia) i przyjąłem numerację pinów wyjściowych modułu:


Silnik BLDC - Elektronika z czujnikami Halla - rozmieszczenie czujników.


Z wykorzystaniem multimetru, znajomości wyprowadzeń układu scalonego oraz wyznaczonych wcześniej miejsc i kolejności podłączenia faz silnika ustaliłem schemat wraz z kolejnością pinów na złączu elektroniki:


Silnik BLDC - Schemat elektroniki (modułu) z czujnikami Halla.


Jak ustaliłem, który czujnik należy do której fazy?

Bardzo prosto - podłączyłem diody LED do wyjść wzmacniaczy operacyjnych wyprowadzonych na złączu modułu w taki sposób:


Schemat podłączenia testowych diod LED.



Po włączeniu zasilania kręcąc wirnikiem silnika zgodnie ze wskazówkami zegara poznałem kolejność przełączania czujników Halla, co pozwoliło mi ustalić kolejność faz (przełączanie się kolejnych diod LED). Fazy silnika ustaliłem w sposób, który opisałem w artykule Oznaczenie faz co łącznie z powyższym testem dało mi podstawy do odpowiedniego oznaczenia pinów złącza modułu czujników Halla jak na powyższych schematach.


Poziomy napięć na wyjściu modułu

Następnym krokiem było sprawdzenie jak zachowuje się ten moduł dla napięcia, którym zasilać będę mikrokontroler, czyli 5V. Podłączyłem więc zasilanie do pinów Vcc i GND i za pomocą multimetru ustaliłem, że na pinach wyjściowych modułu (wyjścia wzmacniaczy operacyjnych H_U, H_V i H_W):
  • stan wysoki, to 0,94V
  • stan niski, to  0,11V
Na oscyloskopie (kręcąc wirnik ręką) sygnał z komparatora każdego z czujników wyglądł następująco:


Silnik BLDC - Czujniki Halla - Oscylogram sygnałów wyjściowych.


W związku z tym, że mikrokontroler (do którego podłączę te czujniki) będzie zasilany z 5V i będzie to ATmega8 sprawdziłem, czy napięcie 0,94V będzie rozpoznane jako stan wysoki. Pisałem o tym szczegółowo w artykule: Jak mikrokontroler "widzi" sygnał cyfrowy?

Rzut oka na dokumentację:


AVR ATmega8 - wykres napięcia przełączenia dla jedynki logicznej na wejściu mikrokontrolera.


Niestety powyższy wykres pokazuje, że pin będący wejściem cyfrowym uznaje jedynkę dopiero od poziomu 1,9V, czyli o 1V więcej niż moduł silnika podaje na wyjściu sygnału Hallotronów.

W związku z tym musiałem jakoś podnieść napięcie do odpowiedniego poziomu. Mogłem to zrobić dodatkowym tranzystorem, ale zerknąłem najpierw na wewnętrzną strukturę komparatora zastosowanego układu 2901 gdzie stwierdziłem, że ma on wyjście typu otwarty kolektor (ang. open collector):


LM2901 - schemat wewnętrzny komparatora z zewnętrznym rezystorem pull-up.



Mogłem więc podłączyć zewnętrzny rezystor pull-up lub wykorzystać wewnętrzny dostępny w mikrokontrolerze, co zrobiłem i sprawdziłem jak wygląda sygnał z czujnika po podłączeniu do pinu mikrokontrolera i włączeniu programowo jego rezystora pull-up:


Silnik BLDC - Czujniki Halla - Oscylogram sygnałów wyjściowych z włączonym rezystorem pull-up mikrokontrolera.


Jak widzisz włączając rezystor pull-up mikrokontrolera ATmega8 otrzymałem sygnał prawie idealnie prostokątny, który można wykorzystać jako sygnał cyfrowy ponieważ przekracza próg uznania jedynki (1,9V) przez wejście mikrokontrolera. Innymi słowy w ten prosty sposób mikrokontroler poprawnie będzie rozróżniał stan wysoki i niski uzyskany na wyjściach modułu czujników Halla.

Jednakże w drodze testów zauważyłem, że powyższy poziom sygnałów nie dawał pewności prawidłowej pracy. Wyłączyłem więc rezystory pull-up pinów mikrokontrolera i dodałem zewnętrzne rezystory pull-up o wartości 10kΩ.

Takie rozwiązanie spowodowało, że sygnał zawierał się pomiędzy 0V, a 5V, a silnik pracował prawidłowo.





Sygnały czujników Halla vs komutacje

Kolejnym etapem, który należy poznać jest ustalenie zależności sygnałów czujnika i momentów komutacji sygnałów sterujących uzwojeniami silnika. W tym celu podłączyłem wyjścia czujników modułu bezpośrednio do diod LED wraz z rezystorami ograniczającymi ich prąd zgodnie z wcześniej pokazanym:


Schemat podłączenia testowych diod LED.


Powolutku zacząłem kręcić wirnikiem w jednym kierunku (zgodnie ze wskazówkami zegara) oznaczając na obudowie silnika miejsca, w których następuje zmiana stanu dowolnej z trzech diod LED. Możesz to zaobserwować na filmie załączonym w dalszej części niniejszego artykułu.

Na bazie obserwacji ustaliłem zależność i wygląda ona (oczywiście) następująco:


Silnik BLDC - Wykresy świecenia diod testowych podłączonych do wyjść modułu czujników Halla.


Na powyższym wykresie stan wysoki oznacza świecenie diody, stan niski jej wygaszenie, a na silniku ukazał się następujący obraz:


Silnik BLDC - Zaznaczone na obudowie miejsca komutacji.



Gdy porównamy z artykułem, w którym wyznaczaliśmy krotność silnika zauważysz, że zmiana sygnałów czujników Halla (kreski niebieskie) występuje w tych samych momentach (pokrywają się z czerwonymi), a dodatkowo w połowie odcinków między nimi.

Momenty zmian stanów (zbocza narastające i opadające) poszczególnych czujników Halla wyznaczają momenty komutacji, stąd nasz wykres uzupełnimy o numery kolejnych komutacji:


Silnik BLDC - Wykresy świecenia diod testowych podłączonych do wyjść modułu czujników Halla z zaznaczonymi momentami komutacji.



Ale ponieważ jak już wspomniałem wcześniej wykresy powyższe pokazują świecenie diody LED (stan wysoki) i zgaszenie (stan niski) diod, a tranzystor na wyjściu komparatora wprowadza negację, stąd faktyczny stan wyjściowy sygnałów z modułu, które trafią na piny mikrokontrolera będzie zanegowany, czyli następujący:


Silnik BLDC - Wykresy sygnałów na wyjściach modułu czujników Halla z zaznaczonymi momentami komutacji.





Decyzje

Nadszedł czas na podjęcie decyzji dot. konkretnych zasad sterowania silnikiem BLDC z wykorzystaniem czujników Halla za pomocą mikrokontrolera.

Mamy trzy czujniki na których zmianę stanu (zbocza przebiegów) powinniśmy szybko zareagować, by dokonać kolejnej komutacji. Mamy do wyboru co najmniej dwie możliwości:
  1. podłączyć sygnały do dowolnych pinów cyfrowych i za pomocą programu sprawdzać w pętli stany wszystkich trzech wejść,
  2. podłączyć sygnały do pinów przerwań zewnętrznych i reagować na wykrywane zbocza.

Pierwszy sposób uniezależnia nas od typu mikrokontrolera i wolnych (dostępnych) pinów. Możemy więc tak przygotować program by był uniwersalny, ale skutkiem tego będzie znaczne obciążenie mikrokontrolera, co oznacza, że nie we wszystkich przypadkach będziemy mogli się na to zgodzić.

Drugi sposób ogranicza nas do mikrokontrolerów, które posiadają co najmniej trzy piny przerwań zewnętrznych i w dodatku wszystkie trzy powinny być dostępne (niewykorzystane). W zamian za to mikrokontroler praktycznie nie odczuje, że silnikiem steruje ponieważ przerwania zminimalizują obciążenie rdzenia mikrokontrolera (CPU), moglibyśmy go więc spokojnie wykorzystywać do wielu innych zadań (np. inne funkcje robota). W tym zakresie przydatne mogą być przerwania typu INT lub Pin Change.

Niestety w przypadku mikrokontrolera ATmega8, który stosuję w cyklu artykułów o silnikach BLDC, ma on dostępne zaledwie dwa przerwania zewnętrzne INT0 oraz INT1 i nie posiada przerwań Pin Change.

Co zrobić?

Możemy kombinować z dodaniem dodatkowych układów zewnętrznych (bramki, tranzystory, diody, itp.), ale ja proponuję wykorzystać inną możliwość. Mikrokontroler ten posiada bowiem komparator analogowy, który wykorzystywaliśmy w sterowaniu z Back-EMF. Służył on nam do porównywania poszczególnych sygnałów BEMF z zsumowanym BEMF wszystkich faz i wykrywaniu ich przecięć, co oznaczało moment w którym należało dokonać komutacji.

W przypadku wykorzystania czujników Halla takiej potrzeby nie mamy, możemy więc wykorzystać komparator w innym celu. W związku z tym podjąłem decyzję, że sygnały hallotronów będą podpięte do:
  • dwóch pinów INT,
  • pinu komparatora, który także będzie generował przerwania w momencie wykrycia zbocza podłączonego do niego sygnału.

W przypadku pinów i przerwań INT sprawa jest prosta wystarczy odpowiednio ustawić rejestry.

Jednak w przypadku komparatora musiałem podjąć kolejną decyzję ponieważ mamy do dyspozycji dwa jego wejścia, które możemy wykorzystać na dwa sposoby:
  1. sygnał czujnika podłączyć do AIN0, a do AIN1 dzielnik rezystorowy ustalający np. połowę napięcia sygnału czujnika jako napięcie referencyjne komparatora,
  2. sygnał czujnika podłączyć do AIN1, a jako napięcie referencyjne komparatora ustawić wewnętrzne napięcie BANDGAP.

Zerkamy na komparator:


ATmega8 - Struktura komparatora analogowego.


oraz tabelę pokazującą wartość napięcia BANDGAP:


ATmega8 - Ustawienia napięcia odniesienia komparatora.


Mikrokontroler zasilać będziemy ze źródła napięcia 5V, a ponieważ  sygnały wychodzące z mojego modułu czujników Halla mają bardzo dobry poziom stanu niskiego (0,1V), stąd niskie napięcie referencyjne komparatora na poziomie nawet minimalnym 1,15V jest wystarczająco wysokie, by przy stromych zboczach jakie są w sygnale z czujników komparator prawidłowo generował przerwania.

Zdecydowałem się na to drugie rozwiązanie, stąd ostateczna wersja schematu wygląda następująco:


Silnik BLDC z czujnikami Halla - Schemat sterownika na mikrokontrolerze ATmega8.



W porównaniu z wersją z Back EMF musiałem dokonać nieznacznych zmian w przeznaczeniu niektórych pinów mikrokontrolera. Także w pliku bldc.h dokonałem odpowiednich poprawek.

Złącze drajwera (driver) pokazane na schemacie dotyczy tego: Drajwer silnika BLDC (DIY)
ale możesz oczywiście zastosować dowolny własny.





Wykrywanie położenia wirnika

Każdy pełny cykl komutacji składa się z sześciu komutacji. W każdym pełnym cyklu występują więc dwa zbocza sygnału danego czujnika. Powstaje więc pytanie: Jak rozróżnić, czy jest to zbocze opadające, czy narastające?

Mamy dwie możliwości:
  1. przerwania są ustawione na konkretny rodzaj spodziewanego następnego zbocza (narastającego lub opadającego) na czujniku danej fazy,
  2. przerwania są ustawione na wykrywanie dowolnego zbocza, ale program w funkcji obsługi przerwania natychmiast sprawdza jaki poziom sygnału jest na wejściu, które przerwanie wywołało.

Oba sposoby są dobre, a ja wybrałem ten drugi, czyli w momencie wykonania się przerwania sprawdzam jaki stan na pinie występuje:


Silnik BLDC - Sposób określania położenia wirnika na podstawie sygnałów z czujników Halla.
Silnik BLDC - Sposób określania położenia wirnika
na podstawie sygnałów z czujników Halla.


Ponieważ zbocze zawsze będzie występować jako pierwsze, a dopiero później sprawdzać będziemy stan wejścia, stąd zawsze prawidłowo rozpoznamy, które zbocze wystąpiło (opadające, czy narastające).


Jeżeli więc po wejściu do funkcji przerwania stwierdzimy, że wejście sygnału:
  • jest w stanie niskim, to oznacza zbocze opadające,
  • jest w stanie wysokim, to oznacza zbocze narastające.
Prawda, że proste?





Testowanie

Na poniższym filmie zobaczysz:
  1. określanie kolejności faz na wyjściu modułu,
  2. test programu za pomocą drajwera,
  3. efekt pracy programu załączonego poniżej.




W czasie testów wykorzystałem wydrukowaną podziałkę, którą załączam:
Silnik-BLDC-nakladka-na-silnik.pdf (kopia)





Program

Do testowania użyłem drajwera, który możesz wykonać samodzielnie na bazie artykułu: Drajwer silnika BLDC (DIY). Pozwala on łatwo testować poprawność pisanego programu.

W programie standardowo stosuję bezpiecznik programowy, który zabezpiecza przed popełnieniem błędu w sterowaniu drajwerem, a który to może spowodować uszkodzenie tranzystorów: Bezpiecznik programowy

Do programu wykorzystałem opracowane wcześniej biblioteki (bldc.h i bldc.c) nieco je modyfikując według potrzeb.


Zmiana częstotliwości zegara vs preskalery timerów

Poniższy program jest przygotowany dla 1MHz, ale równie dobrze będzie pracował z 16MHz. Jednakze w zależności od potrzeb (parametry silnika) możesz dowolnie zmieniać częstotliwość sygnału PWM, poprzez zmianę częstotliwości taktowania mikrokontrolera (F_CPU) i/lub zmianę ustawień preskalera timerów Timer1 i Timer2. Istotne jest, by PWM generowany przez oba timery miał tę samą częstotliwość, co pozwoli na równą pracę silnika.


Plik: bldc.h
/*

Plik: bldc.h

Sterownik silnika BLDC.
Sterowanie silnikiem z wykorzystaniem czujników Halla

Częstotliwość F_CPU: co najmniej 1MHz (ustaw w opcjach projektu)
      ale w zależności od częstotliwośći powinieneś dobrać
      preskalery timerów - szczegóły w artylkule

Szczegóły: http://mikrokontrolery.blogspot.com/2011/03/Silnik-BLDC-Czujniki-Halla-sterowanie.html

Dondu, 05 marca 2014r.

*/


//--- D E F I N I C J E   D O T.  B E Z P I E C Z N I K A  ------

//LED bezpiecznika
#define BEZP_LED_DDR  DDRC
#define BEZP_LED_PORT  PORTC
#define BEZP_LED_PIN  PC3

//--- D E F I N I C J E   D O T.  S I L N I K A  ------

//Faza U
//tranzystor górny
#define U_TR_G_PORTx  PORTB
#define U_TR_G_DDRx   DDRB
#define U_TR_G_PINx   PINB
#define U_TR_G_PIN   PB1
#define U_TR_G_USTAW_DDR U_TR_G_DDRx  |=  (1<<U_TR_G_PIN); //ustaw port
#define U_TR_G_PIN_L  U_TR_G_PORTx &= ~(1<<U_TR_G_PIN);    //ustaw niski
#define U_TR_G_ON    TCCR1A   |=  (1<<COM1A1);             //włącz tranzystor
#define U_TR_G_OFF   TCCR1A   &= ~(1<<COM1A1);             //wyłącz tranzystor
#define U_TR_G_SPRAW_STAN (TCCR1A & (1<<COM1A1))           //warunek stanu 
//tranzystor dolny
#define U_TR_D_PORTx  PORTD
#define U_TR_D_DDRx   DDRD
#define U_TR_D_PINx   PIND
#define U_TR_D_PIN   PD4
#define U_TR_D_USTAW_DDR U_TR_D_DDRx  |=  (1<<U_TR_D_PIN);  //ustaw port
#define U_TR_D_PIN_L  U_TR_D_PORTx &= ~(1<<U_TR_D_PIN);     //ustaw niski
#define U_TR_D_ON    U_TR_D_PORTx |=  (1<<U_TR_D_PIN);      //włącz tranz.
#define U_TR_D_OFF   U_TR_D_PORTx &= ~(1<<U_TR_D_PIN);      //wyłącz tranz.
#define U_TR_D_SPRAW_STAN (U_TR_D_PINx  &  (1<<U_TR_D_PIN)) //warunek stanu

//Faza V
//tranzystor górny
#define V_TR_G_PORTx  PORTB
#define V_TR_G_DDRx   DDRB
#define V_TR_G_PINx   PINB
#define V_TR_G_PIN   PB3
#define V_TR_G_USTAW_DDR V_TR_G_DDRx  |=  (1<<V_TR_G_PIN); //ustaw port
#define V_TR_G_PIN_L  V_TR_G_PORTx &= ~(1<<V_TR_G_PIN);    //ustaw niski
#define V_TR_G_ON    TCCR2   |=   (1<<COM21);              //włącz tranzystor
#define V_TR_G_OFF   TCCR2   &=  ~(1<<COM21);              //wyłącz tranzystor
#define V_TR_G_SPRAW_STAN (TCCR2 & (1<<COM21))             //warunek stanu 
//tranzystor dolny
#define V_TR_D_PORTx  PORTD
#define V_TR_D_DDRx   DDRD
#define V_TR_D_PINx   PIND
#define V_TR_D_PIN   PD5
#define V_TR_D_USTAW_DDR V_TR_D_DDRx  |=  (1<<V_TR_D_PIN);  //ustaw port
#define V_TR_D_PIN_L  V_TR_D_PORTx &= ~(1<<V_TR_D_PIN);     //ustaw niski
#define V_TR_D_ON    V_TR_D_PORTx |=  (1<<V_TR_D_PIN);      //włącz tranz.
#define V_TR_D_OFF   V_TR_D_PORTx &= ~(1<<V_TR_D_PIN);      //wyłącz tranz.
#define V_TR_D_SPRAW_STAN (V_TR_D_PINx  &  (1<<V_TR_D_PIN)) //warunek stanu


//Faza W
//tranzystor górny
#define W_TR_G_PORTx  PORTB
#define W_TR_G_DDRx   DDRB
#define W_TR_G_PINx   PINB
#define W_TR_G_PIN   PB2
#define W_TR_G_USTAW_DDR W_TR_G_DDRx  |=  (1<<W_TR_G_PIN); //ustaw port
#define W_TR_G_PIN_L  W_TR_G_PORTx &= ~(1<<W_TR_G_PIN);    //ustaw niski
#define W_TR_G_ON    TCCR1A   |=  (1<<COM1B1);             //włącz tranzystor
#define W_TR_G_OFF   TCCR1A   &= ~(1<<COM1B1);             //wyłącz tranzystor
#define W_TR_G_SPRAW_STAN (TCCR1A & (1<<COM1B1))           //warunek stanu 
//tranzystor dolny
#define W_TR_D_PORTx  PORTC
#define W_TR_D_DDRx   DDRC
#define W_TR_D_PINx   PINC
#define W_TR_D_PIN   PC5
#define W_TR_D_USTAW_DDR W_TR_D_DDRx  |=  (1<<W_TR_D_PIN);  //ustaw port
#define W_TR_D_PIN_L  W_TR_D_PORTx &= ~(1<<W_TR_D_PIN);     //ustaw niski
#define W_TR_D_ON    W_TR_D_PORTx |=  (1<<W_TR_D_PIN);      //włącz tranz.
#define W_TR_D_OFF   W_TR_D_PORTx &= ~(1<<W_TR_D_PIN);      //wyłącz tranz.
#define W_TR_D_SPRAW_STAN (W_TR_D_PINx &   (1<<W_TR_D_PIN)) //warunek stanu


//Wspólna definicja wyłączająca wszystkie tranzystory
#define WYLACZ_TRANZYSTORY U_TR_G_OFF; U_TR_D_OFF; V_TR_G_OFF; V_TR_D_OFF; W_TR_G_OFF; W_TR_D_OFF;    


//--- D E F I N I C J E   P W M ---
#define PWM_START 255 //wartość wypełnienia PWM podczas startu silnika
#define PWM_MAX  255  //maksymalna wartość wypełnienia PWM do stałej pracy
#define PWM_MIN  20   //minimalna wartość wypełnienia PWM, dla której
                      //silnik będzie w stanie się kręcić (dobrane
                      //doświadczalnie)

//--- D E F I N I C J E   K O M P A R A T O R A ----
#define KOMP_STAN_AKT  (ACSR & (1<<ACO)) //aktualny stan wyjścia komparatora 
#define KOMP_ZBOCZE_DOWOLNE ACSR &= ~((1<<ACIS1) | (1<<ACIS0)) //dowolne zbocze

//--- D E F I N I C J E   P R Z E R W A N   I N T ----
#define INT0_ZBOCZE_DOWOLNE  ((0<<ISC01) | (1<<ISC00))
#define INT1_ZBOCZE_DOWOLNE  ((0<<ISC11) | (1<<ISC10))


//--- F U N K C J E ---
extern void bldc_bezpiecznik(void);
extern void bldc_bezpiecznik_stop(void);
extern void bldc_hall_inicjuj_sterownik(void);
extern void bldc_start(void);







Plik: bldc.c
/*

Plik: bldc.c

Sterownik silnika BLDC.
Sterowanie silnikiem z wykorzystaniem czujników Halla

Częstotliwość F_CPU: co najmniej 1MHz (ustaw w opcjach projektu)
      ale w zależności od częstotliwośći powinieneś dobrać
      preskalery timerów - szczegóły w artylkule

Szczegóły: http://mikrokontrolery.blogspot.com/2011/03/Silnik-BLDC-Czujniki-Halla-sterowanie.html

Dondu, 05 marca 2014r.

*/


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


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

void bldc_bezpiecznik_stop(void){

 //Funkcja wyłącza wszelkie tranzystory oraz przechodzi w stan sygnalizacji
 //błędu komutacji. Funkcja ta razem z funkcją bezpiecznik() pełni rolę
 //zabezpieczenia przeciwzwarciowego dla błędnie działającego algorytmu
 //komutacji w czasie pisania i testów programu.

 //wyłącz przerwania
 cli();

 //natychmiast wyłącz tranzystory
 WYLACZ_TRANZYSTORY

 //i ustaw stany niskie na pinach sterujących 
 U_TR_G_USTAW_DDR
 V_TR_G_USTAW_DDR
 W_TR_G_USTAW_DDR
 U_TR_D_USTAW_DDR
 V_TR_D_USTAW_DDR
 W_TR_D_USTAW_DDR
 U_TR_G_PIN_L
 V_TR_G_PIN_L
 W_TR_G_PIN_L
 U_TR_D_PIN_L
 V_TR_D_PIN_L
 W_TR_D_PIN_L

 //ustaw pin LED jako wyjście
 BEZP_LED_DDR |= (1<<BEZP_LED_PIN);

 //zatrzymaj program w pętli nieskończonej sygnalizując błąd 
 while(1){ 

  //zmień stan LED na przeciwny
  BEZP_LED_PORT  ^= (1<<BEZP_LED_PIN);  

  //co 100ms
  _delay_ms(100);

 }
}


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

void bldc_start(void){

 //Funkcja odpowiedzialna za:
 //1. pozycjnowanie wirnika przed uruchomieniem silnika,
 //2. wykonanie ruchu silnikiem w kierunku obrotów, co ma 
 //   zainicjować start silnika

 //W funkcji używamy opóźnień, które należy odpowiednio wydłużyć, 
 //jeśli wirnik silnika ma dużą bezwładność

 //ustaw wypełnienie podczas startu (wymagana duża moc silnika)
 OCR1A = PWM_START;
 OCR1B = PWM_START;
 OCR2 = PWM_START;

 //pozycjonowanie wirnika
 V_TR_D_ON
 W_TR_G_ON
 bldc_bezpiecznik();
 _delay_ms(1000); //odczekaj na przekręcenie się wirnika
      //do pozycji startowej

 //wyłącz tranzystory
 V_TR_D_OFF
 W_TR_G_OFF

 //włącz przerwania
 sei();

 //start poprzez zakręcenie wirnikiem w odpowiednim kierunku
 //resztę zrobią już funkcje przerwań
 U_TR_G_ON
 V_TR_D_ON  
 bldc_bezpiecznik();
 _delay_ms(25); 

}



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

void bldc_bezpiecznik(void){



 //Sprawdzamy, czy nie występuje konflikt sterowania, powodujący
 //jednoczene otwarcie tranzystora górnego i dolnego w tej samej fazie,
 //co oznacza wystąpienie zwarcia !!!

 if(U_TR_G_SPRAW_STAN && U_TR_D_SPRAW_STAN){

  //Faza U - oba tranzystory są włączone - sytuacja niedopuszczalna!!!
  bldc_bezpiecznik_stop();


 }else if(V_TR_G_SPRAW_STAN && V_TR_D_SPRAW_STAN){

  //Faza V - oba tranzystory są włączone - sytuacja niedopuszczalna!!!
  bldc_bezpiecznik_stop();

  
 }else if(W_TR_G_SPRAW_STAN && W_TR_D_SPRAW_STAN){

  //Faza W - oba tranzystory są włączone - sytuacja niedopuszczalna!!!
  bldc_bezpiecznik_stop();

 }

}



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


void bldc_hall_inicjuj_sterownik(void){

 //Funkcja inicjująca pracę sterownika.
 //-konfiguruje wszystkie wykorzystywane moduły mikrokontrolera
 //-ustawia piny mikrokontrolera tak, by wszystkie tranzystory
 // były wyłączone
 //-ustawia licznikia PWM na początkową wartość DutyCycle

 //ustaw stan początkowy wyjść sterujących tranzystorami
 U_TR_G_USTAW_DDR
 V_TR_G_USTAW_DDR
 W_TR_G_USTAW_DDR
 U_TR_D_USTAW_DDR
 V_TR_D_USTAW_DDR
 W_TR_D_USTAW_DDR
 U_TR_G_PIN_L
 V_TR_G_PIN_L
 W_TR_G_PIN_L
 U_TR_D_PIN_L
 V_TR_D_PIN_L
 W_TR_D_PIN_L

 

 //sprawdzamy, czy nie ma stanu zabronionego na tranzystorach
 //ZAWSZE WYWOŁUJ TĘ FUNKCJĘ, GDY ZMIENIASZ STAN TRANZYSTORÓW!!!
 bldc_bezpiecznik();



 //--- T I M E R Y    P W M   -------------------

 //Timer1
 //Obsługa PWM dla wyjść OC1A oraz OC1B
 //Mode 5 (Fast PWM, 8bit)  
 //Clear OC1A/OC1B on Compare Match, set OC1A/OC1B at BOTTOM, 
 //(non-inverting mode)
 //preskaler 1
 TCCR1A |= (1<<WGM10) | (1<<COM1A1) | (1<<COM1B1);
 TCCR1B |= (1<<WGM12) | (1<<CS10);
  
 
 //Timer2
 //Obsługa PWM  dla wyjścia OC2
 //Mode 3 (Fast PWM, 8bit)  
 //Clear OC2 on Compare Match, set OC2 at BOTTOM, (non-inverting mode)
 //preskaler 1
 TCCR2 |= (1<<WGM21) | (1<<WGM20) | (1<<COM21) | (1<<CS20);

 //ustaw wartość początkową PWM
 OCR1A  = PWM_MIN;
 OCR1B  = PWM_MIN;
 OCR2  = PWM_MIN;


 //--- K O M P A R A T O R ---
 //Wykrywanie zboczy sygnału czujnika Halla fazy W
 
 //napięcie referencyjne komparatora na wejście AIN0
 ACSR |= (1<<ACBG); //włącz BANDGAP na AIN0
 
 //wejście AIN1 czujnika fazy W
 DDRD &= ~(1<<PD7); //pin jako wejście
 
 //zbocze komparatora
 //dowolne zbocze jest domyślnie ustawione po resecie
 //nie musimy nic ustawiać, ale na wszelki wypadek
 KOMP_ZBOCZE_DOWOLNE; //dowolne zbocze

 //włącz przerwania z komparatora
 ACSR |=  (1<<ACIE);


 
 //--- P r z e r w a n i a  INT0 i INT1 ---
 //Wykrywanie zboczy sygnałów czujników Halla faz U oraz V

 DDRD &= ~((1<<PD3) | (1<<PD2)); //INT0 i INT1 jako wejścia
 MCUCR  |= INT1_ZBOCZE_DOWOLNE | INT0_ZBOCZE_DOWOLNE; //oba zbocza
 GICR  |= (1<<INT1) | (1<<INT0); //włącz przerwania


 //na wszelki wypadek
 WYLACZ_TRANZYSTORY
 bldc_bezpiecznik();

}


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


ISR (INT0_vect){

 //Przerwanie czujnika fazy U
 //odpowiada za dokonanie następnej komutacji

 //w zależności, które zbocze sygnału U
 if((PIND & (1<<PD2))){

    //Komutacja nr 4

    //wyłącz tranzystory
    U_TR_G_OFF
    V_TR_D_OFF  
    W_TR_G_OFF
    W_TR_D_OFF  

    //włącz tranzystory
    U_TR_D_ON
    V_TR_G_ON

    bldc_bezpiecznik();

 }else{

    //Komutacja nr 1

    //wyłącz tranzystory
    U_TR_D_OFF  
    V_TR_G_OFF
    W_TR_G_OFF
    W_TR_D_OFF  

    //włącz tranzystory
    U_TR_G_ON
    V_TR_D_ON

    bldc_bezpiecznik();  

 }

}

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


ISR (INT1_vect){

 //Przerwanie czujnika fazy V
 //odpowiada za dokonanie następnej komutacji

 //w zależności, które zbocze sygnału V
 if(PIND & (1<<PD3)){

    //Komutacja nr 2
  
    //wyłącz tranzystory
    U_TR_D_OFF  
    V_TR_G_OFF
    V_TR_D_OFF  
    W_TR_G_OFF

    //włącz tranzystory
    U_TR_G_ON
    W_TR_D_ON

    bldc_bezpiecznik(); 

 }else{

    //Komutacja nr 5

    //wyłącz tranzystory
    U_TR_G_OFF
    V_TR_G_OFF
    V_TR_D_OFF  
    W_TR_D_OFF  

    //włącz tranzystory
    U_TR_D_ON  
    W_TR_G_ON

    bldc_bezpiecznik();

 }
}


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


ISR (ANA_COMP_vect){
 
 //Przerwanie czujnika fazy W
 //odpowiada za dokonanie następnej komutacji

 //UWAGA!!! Sygnał ten jest zanegowany w przeciwieństwie do fazy U i V
 //ponieważ Hallotron fazy W podłączony jest do wejścia odwracającego
 //komparatora. Dlatego też komutacje są zamienione miejscami (3 z 6)

 //w zależności, które zbocze sygnału W
 if(KOMP_STAN_AKT){

    //Komutacja nr 3

    //wyłącz tranzystory
    U_TR_G_OFF
    U_TR_D_OFF  
    V_TR_D_OFF  
    W_TR_G_OFF

    //włącz tranzystory
    V_TR_G_ON
    W_TR_D_ON

    bldc_bezpiecznik();  

 }else{
 
    //Komutacja nr 6

    //wyłącz tranzystory
    U_TR_G_OFF
    U_TR_D_OFF  
    V_TR_G_OFF
    W_TR_D_OFF  

    //włącz tranzystory
    V_TR_D_ON
    W_TR_G_ON

    bldc_bezpiecznik();

 }
}



Plik: main.c
/*

Plik: main.c

Sterownik silnika BLDC.
Sterowanie silnikiem z wykorzystaniem czujników Halla

Częstotliwość F_CPU: co najmniej 1MHz (ustaw w opcjach projektu)
      ale w zależności od częstotliwośći powinieneś dobrać
      preskalery timerów - szczegóły w artylkule

Szczegóły: http://mikrokontrolery.blogspot.com/2011/03/Silnik-BLDC-Czujniki-Halla-sterowanie.html

Dondu, 05 marca 2014r.

*/


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


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

int main (void) 
{

 //opóźnienie na ustabilizowanie zasilania (można usunąć)
 _delay_ms(10);

 //inicjujemy sterownik BLDC
 bldc_hall_inicjuj_sterownik();

 //uruchom silnik
 bldc_start();

 //pętla główna
 while(1){
  
  //Tutaj Twój program ....
  
  
  /* Odkomentuj ten fragment, by uruchomić przykład z filmu.

  //--- P R Z Y K Ł A D ----
  //Przykład zmieniający prędkość obrotową silnika

  //ustaw nową wartość PWM w celu zmiany prędkości
  OCR1A = PWM_MIN;
  OCR1B = PWM_MIN;
  OCR2  = PWM_MIN;
  _delay_ms(5000);

  //ustaw nową wartość PWM w celu zmiany prędkości
  OCR1A = PWM_MAX;
  OCR1B = PWM_MAX;
  OCR2  = PWM_MAX;
  _delay_ms(2000);

  */


 } 
}

Do pobrania

Do pobrania kompletny projekt AVR Studio 4.18 wraz z plikiem .hex dla 1MHz: BLDC-07.zip (kopia)





Zalety BLDC z czujnikami Halla

Ponieważ silnik BLDC zmusza nas do budowy układu elektronicznego komutatora, niezmiernie istotnym jest posiadanie wiedzy na temat położenia wirnika.

W przypadku wykorzystania do tego celu sygnałów Back EMF występuje ograniczenie, że przy małych prędkościach obrotowych sygnały te są zbyt słabe i uniemożliwiają poprawne określenie położenia wirnika. Konsekwencją jest oczywiście bardzo znaczące ograniczenie minimalnej prędkości obrotowej, jaką można osiągnąć z tym typem sterowania.

Silnik BLDC z czujnikami Halla pozbawiony jest tej wady przez co pozwala na jego poprawne sterowanie z prędkościami bliskimi zero. Dodatkową zaletą jest fakt, że przy odpowiednio napisanym programie podczas ruszania z miejsca już po pierwszym zboczu dowolnego z sygnałów z czujnika jesteśmy w stanie określić dokładne położenie wirnika silnika, dzięki czemu możemy od razu wystartować z pełną mocą szybko doprowadzając do osiągnięcia prędkości maksymalnej.

Jeżeli więc Twój projekt wymaga kontroli silnika przy małych prędkościach obrotowych, zastosowanie czujników Halla jest dobrym rozwiązaniem. Jeżeli natomiast Twój silnik nie ma pracować z minimalnymi prędkościami, to metoda Back EMF jest równie dobra.


Oceń artykuł.
Wasze opinie są dla nas ważne, gdyż pozwalają dopracować poszczególne artykuły.
Pozdrawiamy, Autorzy
Ten artykuł oceniam na:

Działy
Działy dodatkowe
Inne
O blogu




Dzisiaj
--> za darmo!!! <--
1. USBasp
2. microBOARD M8


Napisz artykuł
--> i wygraj nagrodę. <--


Co nowego na blogu?
Śledź naszego Facebook-a



Co nowego na blogu?
Śledź nas na Google+

/* 20140911 Wyłączona prawa kolumna */
  • 00

    dni

  • 00

    godzin

  • :
  • 00

    minut

  • :
  • 00

    sekund

Nie czekaj do ostatniego dnia!
Jakość opisu projektu także jest istotna (pkt 9.2 regulaminu).

Sponsorzy:

Zapamiętaj ten artykuł w moim prywatnym spisie treści.