Sterowanie 3-fazowym silnikiem BLDC – algorytm 6-step

Osoby zajmujące się modelarstwem znakomicie znają napęd, jakim jest 3-fazowy bezszczotkowy silnik prądu stałego (BLDC). Jest to podstawowy rodzaj silnika stosowany do budowy takich konstrukcji, jak quadrokoptery czy hexakoptery. Składając wielowirnikowiec, korzystamy z gotowych sterowników ESC (Electronic Speed Control), które przetwarzają modelarski sygnał PWM na odpowiednie sygnały sterujące silnikiem. W jaki sposób właściwie sterowany jest silnik BLDC? Czym wyróżnia się silnik bezszczotkowy i dlaczego nazywany jest napędem przyszłości? Na te pytania postaram się odpowiedzieć w poniższym artykule.

W materiale przedstawię budowę i zasadę działania silnika BLDC oraz podstawowy algorytm sterowania 6-step, który zaimplementuję przy użyciu sterownika STSPIN230 (shield X-NUCLEO-IHM11M1) oraz zestawu Nucleo-F401RE.

Budowa 3-fazowego silnika BLDC

Silnik BLDC to bezszczotkowy silnik prądu stałego. Jak sama nazwa wskazuje (w odróżnieniu od najpopularniejszych napędów DC, jakimi są silniki szczotkowe) silnik BLDC wykorzystuje konstrukcję pozbawioną szczotek i komutatora. Zbudowany jest zatem ze stojana z nawiniętymi cewkami oraz wirnika zawierającego magnes trwały.

Przepływający prąd przez cewki powoduje generowanie zmiennego pola magnetycznego, które oddziałuje na pole magnetyczne magnesu trwałego i powoduje powstanie momentu obrotowego, a w konsekwencji obrót wirnika. W 3-fazowych silnikach BLDC stojan ma minimum 3 cewki, ale w praktyce stosuje się więcej uzwojeń połączonych ze sobą w trzy obwody oznaczane literami U, V i W. Silnikiem steruje się podając cyklicznie napięcie na pary cewek, co prezentuje poniższa grafika (źródło: STMicroelectronics UM2771).

Dlaczego wykorzystano taką konstrukcję? Szczotki to „najsłabsze ogniwo” standardowych silników DC – szybko się zużywają, są awaryjne i powodują iskrzenie, co wyklucza zastosowanie ich w niektórych środowiskach. Poza tym silniki BLDC mają znacznie lepszy stosunek wagi do mocy, mogą być sterowane w szerokich zakresach prędkości obrotowych oraz są bardzo ciche. Problem z zastosowaniem silników BLDC pojawia się, gdy musimy zaimplementować algorytm sterowania. W przypadku szczotkowych silników DC wystarczy podać napięcia na zaciski silnika (lub sygnał PWM, jeśli chcemy sterować prędkością) i wał wyjściowy się obraca. Za prawidłowe przełączanie napięcia i zmianę kierunku momentu obrotowego odpowiada zespół komutator-szczotki. W przypadku silników BLDC funkcję tą wykonuje sterownik oraz odpowiedni algorytm.

Zasada działania algorytmu 6-step

Podstawy algorytmu

Podstawowy algorytm stosowany do sterowania 3-fazowymi silnikami BLDC to algorytm 6-krokowy (6-step). Swoją nazwę bierze stąd, że cały cykl sterowania dzielony jest na 6 kroków, w których we właściwy sposób sterujemy fazami silnika (źródło: STMicroelectronics UM2771).

Podobnie jak w przypadku innych napędów, do sterowania silnikiem BLDC stosujemy układ zbudowany z tranzystorów. Ponieważ każdą z faz musimy dołączyć raz do dodatniej linii zasilania, a raz do masy, potrzebujemy układu z 6 tranzystorami.

W przypadku, gdy chcemy, aby prąd płynął od fazy U do V, musimy załączyć tranzystor UH oraz VL. Analogicznie postępujemy z każdym krokiem algorytmu. To, jakie fazy załączane są w poszczególny krokach cyklu sterowania, przedstawia poniższa tabela.

Numer krokuUHVHWHULVLWL
1 (0-60°)ONOFFOFFOFFONOFF
2 (60-120°)ONOFFOFFOFFOFFON
3 (120-180°)OFFONOFFOFFOFFON
4 (180-240°)OFFONOFFONOFFOFF
5 (240-300°)OFFOFFONONOFFOFF
6 (300-360°)OFFOFFONOFFONOFF

Jest to podstawowa wersja algorytmu 6-krokowego. Kolejnym etapem jest sterowanie prędkością. Aby ją regulować, musimy zastosować sygnał PWM przy sterowaniu tranzystorami UH, VH i WH. Tranzystory UL, VL i WL pozostają cały czas włączone.

Komplementarny sygnał PWM

Przy stosowaniu PWM w momencie, gdy sygnał przyjmuje wartość 0, tranzystor UH nie przewodzi, a w obwodzie nadal istnieje tzw. prąd recyrkulacji. Większość tego prądu przepływa przez diodę tranzystora UL (rysunek poniżej po prawej), przy okazji wydzielając ciepło i zmniejszając wydajność sterowania, zwłaszcza przy hamowaniu. Prąd ten zaniknie, jeżeli okres sygnału PWM będzie dostatecznie duży.

Możemy jednak zastosować tutaj inne rozwiązanie, które nie będzie wymagało wydłużania okresu PWM. Aby zmniejszyć wpływ prądu recyrkulacji, tranzystor UL może być włączony podczas fazy wyłączenia PWM. Taki efekt uzyskamy stosując komplementarny sygnał PWM. Jest to sygnał PWM o przeciwnym stanie tzn., że gdy sygnał PWM przyjmuje stan wysoki, sygnał komplementarny PWMN przyjmuje stan niski. Zasadę działania sygnałów komplementarnych przedstawia poniższy wykres.

Czas martwy

Przy zastosowaniu sygnałów komplementarnych PWM należy zwrócić uwagę na jeszcze jedną rzecz. Wykres przedstawiony powyżej przedstawia sytuację idealną, w której tranzystory przełączają się pomiędzy stanami bez żadnego opóźnienia. W rzeczywistości przełączenie z jednego stanu w drugi trwa określony w dokumentacji danego tranzystora czas. Pomimo, że on jest względnie krótki (poniżej 1 ms), w momencie przełączania występuje moment, kiedy jeden tranzystor nie zdąży się jeszcze wyłączyć, a drugi już się załączy, co spowoduje krótkotrwałe zwarcie zasilania. Będzie to negatywnie wpływało na działanie całego układu. Aby temu zaradzić, należy zastosować tzw. czas martwy (dead time), czyli celowe opóźnienie załączenia drugiego tranzystora względem pierwszego.

Dzięki temu unikniemy zwarcia zasilania silnika, a tym samym poprawimy działanie całego systemu sterowania.

Algorytm 6-step z komplementarnymi sygnałami PWM

Dodając do podstawowego algorytmu 6-step sygnały komplementarne PWM, uzyskamy przebiegi przedstawione na poniższym wykresie.

Tak opracowane sterowanie powinno pozwolić nam na uruchomienie 3-fazowego silnika BLDC.

Shield X-NUCLEO-IHM11M1 ze sterownikiem STSPIN230

Jak w przypadku każdego silnika, do sterowania silnikiem BLDC potrzebujemy układu mostka z tranzystorami. W przypadku silnika 3-fazowego sterownik powinien mieć 6 tranzystorów. Na rynku dostępnych jest wiele różnych sterowników z różnymi interfejsami wejściowymi. Aby w pełni pokazać, jak sterować silnikiem BLDC z wykorzystaniem wyjść komplementarnych, wybrałem sterownik STSPIN230 firmy STMicroelectronics, a właściwie shield X-NUCLEO-IHM11M1 z tym sterownikiem.

STSPIN230 to sterownik przeznaczony do niewielkich silników BLDC. Może operować na napięciach z zakresu od 1,8 V do 10 V z maksymalnym prądem wyjściowym 1,3 Arms. Diagram blokowy sterownika przedstawia poniższa grafika.

W pomarańczowej ramce zaznaczyłem układ 6 tranzystorów MOSFET. Jako sygnały sterujące układ przyjmuje 6 wejść zgodnych z algorytmem, jaki opisałem wcześniej.

Shield X-NUCLEO-IHM11M1 poza samym układem STSPIN230 implementuje jeszcze kilka funkcjonalności przydatnych przy sterowaniu silnikami BLDC. Najważniejsze z nich to układ z diodami i rezystorami pozwalający na pomiar napięcia indukowanego w uzwojeniach, który może nam posłużyć jako informacja zwrotna o położeniu wału. Poza tym znajdziemy wyjście z informacją o poborze prądu przez uzwojenia oraz złącze pozwalające podłączyć czujniki Halla. Szczegóły można znaleźć na schemacie w dokumentacji płytki.

Dla nas najważniejszą informacją potrzebną do zaimplementowania algorytmu sterowania z wykorzystaniem tej płytki jest to, pod jakie wyjścia na płytce Nucleo-F401RE podłączone będą linie sterujące po wpięciu shieldu. Informacje te zebrałem w tabeli poniżej (należy porównać tabelę 1 z dokumentacji X-NUCLEO-IHM11M1 z rozkładem pinów dla NUCLEO-F401RE dostępnej w dokumentacji Nucleo).

Nazwa sygnałuNumer pinu na CN10Pin na Nucleo-F401RE
U_High23PA8
V_High21PA9
W_High33PA10
U_Low30PB13
V_Low28PB14
W_Low24PB1
ENABLE29PB5
STBY/RESET19PC7

Konfiguracja mikrokontrolera

Znając podstawy działania algorytmu sterowania 3-fazowego silnika BLDC możemy przejść do konfiguracji wyjść i wejść mikrokontrolera. Zaimplementujemy go dla układu STM32F401RE na zestawie Nucleo-F401RE w środowisku STM32CubeIDE.

Na początku skonfigurujemy dwa wejścia sterujące STSPIN230: pin EN/FAULT oraz STBY/RESET. Dostępne są one na pinach PB5 oraz PC7. Oba piny konfigurujemy jako wyjście. jednocześnie w zakładce po lewej stronie wybieramy System Core->GPIO i dla obu pinów ustawiamy GPIO Output level jako High. Możemy też przypisać tym pinom etykiety ENABLE i SBY_RST.

Teraz przejdziemy do sedna konfiguracji, czyli Timera i wyjść PWM. Wybieramy Timer1 (Timers->TIM1 w zakładce po lewej) i konfigurujemy Channel1, Channel2 oraz Channel3 odpowiednio jako PWM Generation CH1 CH1N, PWM Generation CH2 CH2N oraz PWM Generation CH3 CH3N.

Przechodzimy do konfiguracji ustawień Timera. Na początku musimy dobrać ogólne parametry takie, jak Prescaler, Mode i Period. Silniki BLDC sterowane są zazwyczaj sygnałami PWM o dość dużej częstotliwości, dlatego skonfigurujemy Timera tak, aby uzyskać sygnał 20 kHz. W Datasheet mikrokontrolera sprawdzamy, ze TIM1 podłączony jest do szyny APB2, która jest taktowana zegarem 84 MHz.

Zgodnie ze wzorem:

PWM_Freq = Timer_Freq / (Prescaler * Counter Period)

dobieramy wartości Prescaler jako 42 oraz Period jako 100, dzięki czemu uzyskamy sygnał PWM o czestotliwości 20 kHz i rozdzielczości sterowania 100. Co do trybu pracy timera, zastosujemy tutaj tryb Center Aligned Mode 1. Jest to tryb, w którym sygnał PWM jest wyrównany do środka, co ułatwi odczyt BEFM. Dokładnie opiszę to w kolejnej części artykułu, gdzie będziemy implementowali odczyt informacji zwrotnej z uzwojeń silnika, ponieważ właśnie wtedy tryb Center Aligned Mode 1 znajdzie zastosowanie.

Wszystkie sygnały PWM konfigurujemy jako PWM mode 1, bez trybu Fast Mode, Polarity jako High oraz Idle State jako Reset. Wartość wypełnienia Pulse może pozostać jako 0, ponieważ i tak ustawiać będziemy ją z poziomu kodu.

Aby skonfigurować czas martwy, wybieramy w obszarze konfiguracji Break and Dead Time Management następujące wartości:

  • Automatic Output State – Enable
  • OSSR – Enable
  • OSSI – Enable
  • Lock Configuration – OFF
  • Dead Time – 16

Dokładny opis tego, co oznacza wartość 16 znajdziemy w Reference Manual na stronie 312. Ponieważ Tdts taktowaniu 84 MHz wynosić będzie 12 ns, wartość 16 wpisana do rejestru da nam czas martwy równy ok. 192 ns. Wartość Dead Time dobrałem na podstawie informacji z dokumentacji STSPIN230, która mówi, że czas załączania i wyłączania tranzystorów wynoszą odpowiednio 125 ns i 140 ns (ustawiłem więc trochę dłuższy czas, aby mieć „zapas”). Pełną konfigurację Timera przedstawia poniższa grafika (kanał 2 i 3 analogicznie do 1).

Ostatnim etapem konfiguracji TIM1 jest ustawienie przerwań. Przechodzimy do zakładki NVIC Settings i zaznaczamy TIM1 Trigger and commutation interrupts. Dlaczego? Ponieważ będziemy chcieli wykorzystać przerwanie od tzw. komutacji. Jest to ciekawa funkcjonalność Timera, bardzo użyteczna przy sterowaniu algorytmem 6-Step i silnikiem BLDC. Powoduje ona, że ustawienia poszczególnych kanałów Timera będą aktywowane dopiero po zakończeniu okresu PWM, dzięki czemu mamy pewność, że wszystkie 6 kanałów (trzy pary komplementarne) będzie startowało dokładnie w tym samym momencie i będą ze sobą zsynchronizowane. Dokładny opis tego trybu działania Timera znajdziemy w dokumentacji Reference Manual na stronie 227. Poniższa grafika przedstawia sposób działania funkcji COM commutation Event.

Na koniec skonfigurujemy jeszcze TIM3. Będziemy za jego pomocą generować sygnał COM. Połączeniu obu Timerów wykonamy z poziomu kodu, dlatego tutaj wybieram tylko TIM3 z kanałem 1 jako Output Compate No Output.

Wartości Prescaler i Period ustawiamy tak, aby uzyskać częstotliwość 10 Hz. Wybieramy także tryb Master/Slave Mode jako Enable i Trigger Event Selection jako Update Event.

Warto sprawdzić także, w jaki sposób połączone są ze sobą TIM1 i TIM3, aby wiedzieć jak później skonfigurować tryb Master->Slave pomiędzy nimi. Możemy to sprawdzić w Reference Manual na stronie 294. Widzimy, że TIM3 jest wejściem przełączającym ITR2 dla TIM1. Informację tę wykorzystamy za chwilę.

Przy tak skonfigurowanych peryferiach możemy wygenerować projekt („Project->Generate Code” lub „Alt+K„) i przejść do napisania kodu programu.

Implementacja algorytmu

Na początku stworzymy dwa pliki do obsługi silnika BLDC: bldc_motor.c (w folderze Core->Src) oraz bldc_motor.h (w folderze Core->Inc), które będą stanowiły naszą bibliotekę.

W pliku bldc_motor.h tworzymy enum do określania kierunku obrotów oraz strukturę bldc_control przechowującą parametry pracy silnika.

struct bldc_control
{
	TIM_HandleTypeDef	*tim_pwm;
	TIM_HandleTypeDef	*tim_com;

	TIM_OC_InitTypeDef sConfigOC;

	uint8_t step_number;
	uint32_t speed_pulse;
	uint8_t dir;
};

W pliku bldc_motor.c dodajemy definicje stałych określających maksymalną prędkość silnika oraz wartość rejestru ARR (Period) mającą wpływ na częstotliwość pracy timera TIM3.

#define ARR_TIM3_VALUE		100
#define BLDC_MOTOR_MAX_SPEED	100-1

Na początku programu powinniśmy zainicjalizować parametry pracy potrzebne do sterowania silnikiem BLDC. W funkcji bldc_motor_init podajemy timery, których będziemy używali (jeden do generowania sygnałów PWM, drugi do sterowania czasem przełączania kroków). Następnie ustawiamy pierwszy krok algorytmu (kroki przyjmują wartości od 1 do 6) oraz inicjalizujemy parametry kanałów timera. Będziemy zmieniać tryb pracy kanałów w trakcie sterowania, dlatego ogólne parametry ustawimy raz w elemencie struktury bldc.sConfigOC, aby nie ustawiać ich za każdym razem.

void bldc_motor_Config_Channel_Init(void)
{
	bldc.sConfigOC.OCMode = TIM_OCMODE_PWM1;
	bldc.sConfigOC.Pulse = 0;
	bldc.sConfigOC.OCPolarity = TIM_OCPOLARITY_HIGH;
	bldc.sConfigOC.OCNPolarity = TIM_OCNPOLARITY_HIGH;
	bldc.sConfigOC.OCFastMode = TIM_OCFAST_DISABLE;
	bldc.sConfigOC.OCIdleState = TIM_OCIDLESTATE_RESET;
	bldc.sConfigOC.OCNIdleState = TIM_OCNIDLESTATE_RESET;
}

Następnie konfigurujemy timer TIM3, aby generował sygnał z częstotliwością 100 Hz. Chciałbym się tutaj wytłumaczyć, ponieważ wartość tę dobrałem doświadczalnie w taki sposób, aby mój silnik przy wypełnieniu 10% się obracał. Sterowanie bez kontrolowania położenia wału silnika BLDC jest mało efektywne i niestosowane w praktyce. W artykule przedstawiam taki sposób jako wstęp do dalszej części, gdzie zaimplementujemy odczyt położenia wału i starowanie krokami na ich podstawie. Przedstawienie wszystkiego w jednym artykule było niemożliwe ze względu na zbyt duża objętość materiału. Wydało mi się sensowniejsze podzielnie go na kilka etapów.

Na koniec pozostaje nam konfiguracja zdarzenia komutacji dla kanałów timera TIM1. Mechanizm ten opisywałem powyżej przy konfiguracji mikrokontrolera. Zdarzenie będzie generowane przez timer TIM3 (podłączony jako kanał ITR2 timera TIM1) w trybie TIM_COMMUTATION_TRGI (możliwe jest też programowe generowanie zdarzenia).

void bldc_motor_init(TIM_HandleTypeDef *_tim_pwm, TIM_HandleTypeDef *_tim_com)
{
	bldc.tim_pwm = _tim_pwm;
	bldc.tim_com = _tim_com;

	bldc.step_number = 1;
	bldc.speed_pulse = 0;
	bldc.dir = CW;

	bldc_motor_Config_Channel_Init();

	__HAL_TIM_SET_AUTORELOAD(bldc.tim_com, ARR_TIM3_VALUE);

	HAL_TIM_Base_Start(bldc.tim_com);
	HAL_TIMEx_ConfigCommutationEvent_IT(bldc.tim_pwm, TIM_TS_ITR2, TIM_COMMUTATION_TRGI);
}

Prędkość silnika i kierunek obrotu będziemy zadawali za pomocą funkcji bldc_motor_set_speed().

void bldc_motor_set_speed(uint32_t speed, direction dir)
{
	if(speed > BLDC_MOTOR_MAX_SPEED)
	{
		bldc.speed_pulse = BLDC_MOTOR_MAX_SPEED;
	}
	else
	{
		bldc.speed_pulse = speed;
	}

	bldc.dir = dir;
}

Za sterowanie i generowanie kolejnych kroków algorytmu odpowiada funkcja bldc_motor_six_step_algorithm(). W zależności od kierunku obrotu, numer kroku jest zwiększany lub zmniejszany. W każdym kroku wywoływana jest funkcja do generowania sygnału PWM na danym kanale z wyjściem komplementarnym. Do generowania stanu wysokiego lub niskiego na kanałach komplementarnych (zgodnie z wykresem przedstawionym w opisie algorytmu) stosuję tryb FORCED_ACTIVE oraz FORCED_INACTIVE dla kanału timera (zgodnie z dokumentacją mikrokontrolera – opis tybu 6-step i komutacji w Reference Manual na stronie 227).

void bldc_motor_six_step_algorithm(void)
{
	switch (bldc.step_number)
	{
		case 1:
		{
			bldc_motor_PWM_Config_Channel(bldc.speed_pulse, TIM_CHANNEL_1);
			bldc_motor_OC_Config_Channel(TIM_OCMODE_FORCED_ACTIVE, TIM_CHANNEL_2);
			bldc_motor_OC_Config_Channel(TIM_OCMODE_FORCED_INACTIVE, TIM_CHANNEL_3);
		}
		break;

		case 2:
		{
			bldc_motor_PWM_Config_Channel(bldc.speed_pulse, TIM_CHANNEL_1);
			bldc_motor_OC_Config_Channel(TIM_OCMODE_FORCED_INACTIVE, TIM_CHANNEL_2);
			bldc_motor_OC_Config_Channel(TIM_OCMODE_FORCED_ACTIVE, TIM_CHANNEL_3);
		}
		break;

		case 3:
		{
			bldc_motor_OC_Config_Channel(TIM_OCMODE_FORCED_INACTIVE, TIM_CHANNEL_1);
			bldc_motor_PWM_Config_Channel(bldc.speed_pulse, TIM_CHANNEL_2);
			bldc_motor_OC_Config_Channel(TIM_OCMODE_FORCED_ACTIVE, TIM_CHANNEL_3);
		}
		break;

		case 4:
		{
			bldc_motor_OC_Config_Channel(TIM_OCMODE_FORCED_ACTIVE, TIM_CHANNEL_1);
			bldc_motor_PWM_Config_Channel(bldc.speed_pulse, TIM_CHANNEL_2);
			bldc_motor_OC_Config_Channel(TIM_OCMODE_FORCED_INACTIVE, TIM_CHANNEL_3);
		}
		break;

		case 5:
		{
			bldc_motor_OC_Config_Channel(TIM_OCMODE_FORCED_ACTIVE, TIM_CHANNEL_1);
			bldc_motor_OC_Config_Channel(TIM_OCMODE_FORCED_INACTIVE, TIM_CHANNEL_2);
			bldc_motor_PWM_Config_Channel(bldc.speed_pulse, TIM_CHANNEL_3);
		}
		break;

		case 6:
		{
			bldc_motor_OC_Config_Channel(TIM_OCMODE_FORCED_INACTIVE, TIM_CHANNEL_1);
			bldc_motor_OC_Config_Channel(TIM_OCMODE_FORCED_ACTIVE, TIM_CHANNEL_2);
			bldc_motor_PWM_Config_Channel(bldc.speed_pulse, TIM_CHANNEL_3);
		}
		break;

	}

	if(CW == bldc.dir)
	{
		bldc.step_number++;

		if(bldc.step_number > 6)
			bldc.step_number = 1;
	}
	else if(CCW == bldc.dir)
	{
		bldc.step_number--;

		if(bldc.step_number < 1)
			bldc.step_number = 6;
	}
}

Funkcje do zmiany trybów pracy z PWM1 na FORCED_ACTIVE i FORCED_INACTIVE wyglądają jak poniżej. W przypadku trybów FORCED_ACTIVE i FORCED_INACTIVE tylko kanał komplementarny jest uruchamiany, ponieważ kanał „zwykły” zawsze jest wówczas wyłączony.

void bldc_motor_PWM_Config_Channel(uint32_t pulse, uint32_t channel)
{
	bldc.sConfigOC.OCMode = TIM_OCMODE_PWM1;
	bldc.sConfigOC.Pulse = pulse;
	HAL_TIM_PWM_ConfigChannel(bldc.tim_pwm, &bldc.sConfigOC, channel);

	HAL_TIM_PWM_Start(bldc.tim_pwm, channel);
	HAL_TIMEx_PWMN_Start(bldc.tim_pwm, channel);
}

void bldc_motor_OC_Config_Channel(uint32_t mode, uint32_t channel)
{
	bldc.sConfigOC.OCMode = mode;
	HAL_TIM_OC_ConfigChannel(bldc.tim_pwm, &bldc.sConfigOC, channel);

	HAL_TIM_OC_Stop(bldc.tim_pwm, channel);
	HAL_TIMEx_OCN_Start(bldc.tim_pwm, channel) ;
}

Algorytm 6-krokowy wywoływany jest w przerwaniu od zdarzenia COM. Obsługę callback-a umieściłem w pliku main.c.

/* USER CODE BEGIN 4 */
void HAL_TIMEx_CommutCallback(TIM_HandleTypeDef *htim)
{
	bldc_motor_six_step_algorithm();
}
/* USER CODE END 4 */

Poza tym w funkcji main() wywołujemy inicjalizację struktury silnika BLDC oraz zadajemy prędkość i kierunek ruchu.

  /* USER CODE BEGIN 2 */
  uint32_t time = HAL_GetTick();
  uint32_t max_time = 3000;
  uint32_t dir = CW;
  uint32_t speed = 10;

  bldc_motor_init(&htim1, &htim3);
  bldc_motor_set_speed(speed, dir);
  /* USER CODE END 2 */

W pętli while(1) dodałem prosty program testowy zmieniający kierunek obrotu silnika co 3 s przy użyciu timera programowego.

if((HAL_GetTick() - time) > max_time)
{
	time = HAL_GetTick();

	if(CW == dir)
		dir = CCW;
	else if(CCW == dir)
		dir = CW;

	bldc_motor_set_speed(speed, dir);
}

Należy pamiętać jeszcze, aby dodać #include „bldc_motor.h” w pliku main.h.

/* Private includes ----------------------------------------------------------*/
/* USER CODE BEGIN Includes */
#include "bldc_motor.h"
/* USER CODE END Includes */

Przed uruchomieniem projektu powinniśmy jeszcze połączyć shield z zestawem Nucleo i podłączyć silnik. Robimy to według poniższego schematu.

Teraz możemy skompilować kod (Project->Build Project) i go uruchomić (Run->Run). Efekt działania został przedstawiony na filmie poniżej.

Podsumowanie

Sterowanie 3-fazowym silnikiem BLDC jest znacznie trudniejszym zadaniem niż w przypadku silników szczotkowych DC. Przedstawiony algorytm 6-krokowy to dopiero początek. Takie sterowanie pozwali nam na obrót silnikiem, jednak będzie ono bardzo nieefektywne. Sensowne sterowanie silnikiem BLDC wymaga bowiem znajomości położenia wału. Można to realizować za pomocą czujników Halla lub enkoderów. Coraz popularniejsza metodą jest także sterowanie tzw. bezczujnikowe, które wykorzystuje informację zwrotną o prądach indukowanych w cewkach w trakcie obrotu. W kolejnym artykule postaramy się ulepszyć algorytm, rozszerzając go o tego typu sterowanie.

Do pobrania

Repozytorium GitHub

Dokumentacja ST AN4013 STM32 cross-series timer overview
Dokumentacja ST UM2124 Getting started with the six-step firmware library for STM32

Komentarz

  1. A ja mam jedną uwagę. W tekście wspominasz, że podane ustawienia zegara zapewniają częstotliwość 50kHz. Mi wychodzi 20kHz- jestem w błędzie?

    1. Rzeczywiście, dzięki za uwagę. Nie pamiętam już czy mierzyłem ten sygnał i robiłem w trakcie jakieś zmiany w ustawieniach, czy źle opisałem w tekście. Już poprawione 🙂

  2. A który jaki silnik bierze najmniej prądu? Szczotkowy, bldc, szeregowy, bocznikowy, I inne.? Ja na przykład silnik najmniejszy jaki mam nie znając mocy mam 5A przy średnim obciążeniu nie wiem ile obrotów a szło z 140V dc. 4Ah… Silnik pralki ten szeregowy szczotkowy 250W 230V pod 17000obr.min. Zatrzymany wal wirnik unieruchomiony daje 8A w trzech próbach napięciowych. I to jest super. Bo ledwie wirnik będzie sie kręcił pod obciążeniem extra i mam pewność że nie ma 10A. Ale można zagrzać wirnik albo stojan albo cały silnik. Moment obrotowy w Nm ma nawet trochę siły. Ale zawiodłem się w obrotach. Bo małe obroty ma pod obciążeniem. I szukam innego silnika co by miał mały prąd i duże obroty i dużo siły.

  3. Nie prawda, to takie utrudnianie na sile, proszę nie pisać takich banialuk , działa to na 3 pwmach bez problemu.

    1. Działać działa, ale czy jest to optymalne rozwiązanie? W przedstawiony przeze mnie sposób steruje się silnikami trójfazowymi BLDC przy pomocy dedykowanego interfejsu STM32, więcej informacji można znaleźć m.in. w dokumentacji ST UM2124 oraz AN4013 (załączyłem pod artkułem w sekcji „Do pobrania”).

      1. Ale czy takie sterowanie jest bezpieczne ? Nie jest powiedzmy sobie szczerze, software może zawieść, sygnał podany na dwa mosfety o rożnej budowie nigdy, i mamy gigantyczny problem z głowy

        1. Software może być po pierwsze dobrze przetestowany, a po drugie można dodać dodatkowe zabezpieczenia. Gdybyśmy podchodzili do oprogramowania w ten sposób, że lepiej go za dużo nie pisać, bo może zawieść, to byśmy wcale programów mogli nie pisać i pozostali przy elektronice analogowej. Nie na tym to polega. Ale trochę zboczyłem z tematu – przedstawiony przeze mnie sposób sterowania uwzględnia tylko podstawowe elementy – chciałem przedstawić swoje doświadczenia (dodam, że hobbystyczne) związane ze sterowaniem silników BLDC. Można to znacznie rozszerzyć, stosuje się algorytmy FOC – to zaawansowany temat. Każdy z kolejnym elementów dokładanych do sposobu sterowania (tak jak Dead Time) ma za zadanie zwiększyć efektywność działania, co nie oznacza, że bez niego nie uruchomimy silnika.

          1. W wielu przykładach budowy falowników w necie jest zaprzeczony sygnał dolnego klucza, w przypadku p i n mosfetów nic nawet nie trzeba zaprzeczać, prostsza budowa tym mniej awarii. Ja się spotkałem z uszkodzonym modułem mocy IGBT w UPSie na statku dużej mocy i to tylko ze względu na załączenie obu sygnałów na raz. Wg mnie to jest wada, w projektowaniu niezawodnych rozwiązań komercyjnych chciał bym tego uniknąć z wszelką cenę. FOC nie jest optymalny de facto, lepszy jest DTC.

  4. Po co sześć sygnałów na bramki? Przecież wystarczą 3, a kolejne 3 są tylko zaprzeczeniem tych poprzednich!

    1. Nie do końca. W momencie generowania sygnału PWM są to sygnały komplementarne, ale w pozostałych krokach już nie. Poza tym nie są to do końca sygnały przeciwne – jak wspomniałem w materiale do prawidłowego sterowania tranzystorami wymagany jest czas martwy (dead time), co zwykłe zaprzeczenie sygnałów nam nie gwarantuje.

Dodaj komentarz

Twój adres e-mail nie zostanie opublikowany. Wymagane pola są oznaczone *