Autor: Dondu Jacek
Artykuł z cyklu: Silnik BLDC: Spis treści
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 ATmega8. Artykuł 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:
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):
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:
- czekaj na pierwsze przerwanie zbocza opadającego wyjścia komparatora (ACO),
- zapamiętaj stan rejestru ICR1 jako moment A,
- czekaj na kolejne przerwania od zbocza opadającego zliczając w międzyczasie przepełnienia Timer1,
- 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.
- 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
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:
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ść.
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.
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:
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...
----------------
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:
- sterowaniem silnikiem (kolejne komutacje),
- regulacją prędkości zmienianej przyciskami,
- pomiarami prędkości,
- obliczaniem wyniku do wysłania do komputera,
- 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); } } }
Brak komentarzy:
Prześlij komentarz