Sterowanie 3-fazowym silnikiem BLDC – pomiar BEMF

Silniki BLDC do optymalnej pracy wymagają sterowania na podstawie położenia wirnika. Pozwala to na osiągnięcie maksymalnego momentu obrotowego. Istnieje kilka metod na określenie położenia wału – w tym celu można wykorzystać enkodery lub czujniki Halla. Często do sterowania silnikiem BLDC wykorzystywany jest także pomiar BEMF (Back ElectroMotive Force). Metoda ta jest o tyle wygodna, że nie wymaga montażu dodatkowych czujników na silniku, a wykorzystuje pomiar napięcia indukowanego w niezasilanym uzwojeniu. W artykule postaram się przybliżyć bezczujnikową metodę sterowania i przedstawić przykładowy program implementujący sterowanie BLDC z wykorzystaniem BEMF.

Artykuł jest kontynuacją materiałów dotyczących sterowania silnikami BLDC. Przed przeczytaniem poniższego materiału zachęcam do zapoznania się z informacjami przedstawionymi w artykule „Sterowanie 3-fazowym silnikiem BLDC – algorytm 6-step”. W projekcie wykorzystam sterownik STSPIN230 (shield X-NUCLEO-IHM11M1) oraz zestawu Nucleo-F401RE.

Czym jest Back-EMF

BEMF (Back-EMF, Back ElectroMotive Force) to siła przeciw elektromotoryczna, która powoduje przepływ prądu w obwodzie elektrycznym. W przypadku silników BLDC mamy do czynienia z pojawieniem się napięcia w uzwojeniu silnika np. w momencie, gdy obracamy wirnikiem za pomocą zewnętrznej siły np. ręką.

Zjawisko wytwarzania się napięcia na niezasilanych uzwojeniach silnika wykorzystano do opracowania metody sterowania bez użycia czujników. Sygnał BEMF może przyjąć formę sinusoidalną lub trapezoidalną. Kształt sinusoidy ma sygnał w przypadku silników PMSM (Permanent Magnet Synchronous Motor). Dla silników BLDC sygnał Back-EMF ma kształt trapezoidalny (poniższy wykres) [źródło: STMicroelectronics UM2124].

Algorytm sterowania metodą bezczujnikową

Algorytm sterowania z wykorzystaniem pomiaru BEMF bazuje na zjawisku indukowania się napięcia w uzwojeniach silnika. W zasilanych fazach nie możemy wykryć indukowanego napięcia, ponieważ są one zwierane przez obwód zasilania. Jednak zgodnie z algorytmem 6-krokowym, zawsze jedna z faz nie jest zasilana i na podstawie napięcia, jakie się tam pojawia, jesteśmy w stanie określić położenie wirnika.

Zero Crossing

Celem algorytmu sterowania metodą bezczujnikową jest znalezienie kluczowego momentu, w którym sygnał BEMF przechodzi przez punkt zero. Moment ten jest często nazywany z języka angielskiego jako Zero Crossing Event, a punkt przejścia indukowanego napięcia przez zero jako Zero Crossing Point.

Ze względu na to, że do sterowania uzwojeniami używamy sygnału PWM, napięcie indukowane w uzwojeniach również przyjmuje formę podobnie wyglądającego wykresu, gdzie raz napięcie skacze do wyższego poziomu, a raz do niższego. Istnieją dwie możliwości wykrycia punktu ZC:

  • poprzez pomiar sygnału przez ADC w momencie występowania stanu wysokiego na wyjściu PWM. Wówczas punkt Zero Crossing występuje, gdy mierzony sygnał osiąga połowę wartości napięcia zasilania szyny ADC (czyli połowę zakresu pomiaru ADC).
  • poprzez pomiar sygnału przez ADC w momencie występowania stanu niskiego na wyjściu PWM. Wówczas punkt Zero Crossing występuje, gdy mierzony sygnał osiąga 0 V.

Najlepszym momentem na wykonanie pomiaru jest centralny punkt sygnału PWM, który wypada w połowie okresu. Do tego celu wykorzystamy tryb wyrównania do środka timera dostępny w STM32.

Wiedza o tym, w którym momencie sygnał BEMF przechodzi przez punkt ZC, pozwala nam na przełączenie sygnałów PWM zgodnie z kolejnym krokiem algorytmu 6-step. Należy jednak zauważyć, że moment przejścia przez ZC nie jest jednocześnie punktem wykonania kolejnego kroku – zdarzenia te są przesunięte o 30° względem siebie, co musimy uwzględnić w implementowanym algorytmie.

Procedura startu

Implementując algorytm sterowania z wykorzystaniem BEMF musimy wziąć pod uwagę bardzo ważną kwestię związaną ze zjawiskiem indukowania się napięcia w niezasilanym uzwojeniu. Zauważyć należy, że w momencie, kiedy silnik się nie porusza, przetwornik ADC nie otrzyma żadnego użytecznego sygnału, a co za tym idzie, w momencie startu nie znamy położenia wirnika. Aby otrzymać użyteczny sygnał BEMF, silnik musi obracać się z pewną prędkością. Dlatego niezbędna jest tutaj procedura startu. Algorytm rozruchu silnika składa się z dwóch podstawowych elementów.

Pierwszy etapem jest tzw. wyrównanie pozycji (alignment). Polega on na tym, że przez określony czas podajemy na uzwojenia silnika sygnały konkretnego kroku i czekamy, aż silnik spozycjonuje się. Dzięki takiemu zabiegowi wiemy jaki kolejny krok musimy podać, aby silnik obrócił się w określonym kierunku. Pominięcie tego kroku sprawi, że wysterujemy silnik przypadkowym krokiem algorytmu 6-step, a w rezultacie może spowodować, że silnik zacznie obracać się chwilowo w innym kierunku, a w skrajnym przypadku uniemożliwi rozruch.

Drugim etapem jest wygenerowanie rampy do rozruchu. W przypadku silnika BLCD polega to na stopniowym zwiększaniu czasu pomiędzy kolejnymi przełączeniami kroków, dzięki czemu silnik będzie przyspieszał do momentu, aż uzyskamy prędkość, przy której sygnał BEMF będzie miał odpowiedni kształt i pozwoli na przełączanie kroków na jego podstawie.

Opóźnienie demagnetyzacji

Jeszcze jednym istotnym elementem, który chciałbym poruszyć implementując algorytm sterowania bezczujnikowego, jest opóźnienie demagnetyzacji. Jest to czas, jaki powinniśmy odczekać po przełączeniu kroku, aby odczytywać napięcie BEMF. Opóźnienie to jest wymagane, ponieważ w momencie wyłączenia fazy silnika tranzystor będzie przewodził jeszcze prąd przez jakiś czas, przez co pomiar będzie zakłócony. Czas demagnetyzacji będzie zależny od prędkości obrotowej silnika i jest procentem czasu kroku.

Konfiguracja mikrokontrolera

Znając podstawy działania algorytmu sterowania 3-fazowego silnika BLDC z wykorzystaniem metody bezczujnikowej możemy przejść do konfiguracji wyjść i wejść mikrokontrolera. Zaimplementujemy go dla układu STM32F401RE na zestawie Nucleo-F401RE w środowisku STM32CubeIDE. Dokładny opis konfiguracji w przypadku sygnałów PWM i timerów opisany został w artykule „Sterowanie 3-fazowym silnikiem BLDC – algorytm 6-step”. Poniżej zamieszczam tylko skrótowy opis, a skupię się na nowych elementach wymaganych do pomiaru napięć BEMF.

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.

Następnie konfigurujemy Timer-y. 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.

Teraz przechodzimy do konfiguracji ustawień TIM1. 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. Tryb Center Aligned Mode 1 polega na tym, że timer liczy najpierw do góry i załącza stan wysoki, gdy osiągnie wartość rejestru CCR (Pulse). Następnie licznik liczy do dołu i na wyjściu ustawia stan niski, gdy ponownie osiągnie wartość CCR (Pulse). W ten sposób sygnał PWM zostanie wygenerowany z wyrównaniem do środka, a my możemy na koniec zliczania w dół wykonać pomiar BEMF, czyli na środku stanu niskiego PWM.

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

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 oraz TIM1 update interrupt. Przerwania od komutacji użyjemy, aby wszystkie 6 kanałów (trzy pary komplementarne) startowały dokładnie w tym samym momencie. Natomiast przerwanie od przepełnienia TIM1 zastosujemy do wykonania pomiaru napięć BEMF.

Na koniec skonfigurujemy jeszcze TIM3. Będziemy za jego pomocą generować sygnał COM. Połączeniu obu Timerów wykonamy z poziomu kodu. Wartości Prescaler i Period ustawiamy tak, aby uzyskać częstotliwość umożliwiająca pracę naszego silnika zarówno dla małych, jak i większych prędkości (dobrałem je na podstawie pomiarów pracy silnika). 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ę.

Mając skonfigurowane timery, możemy przejść do konfiguracji przetwornika ADC. Wykorzystamy kanały 7, 8 oraz 13, ponieważ na shieldzie X-NUCLEO-IHM11M1 sygnały BEMF podłączone są pod piny PA7 (BEMF_W), PB0 (BEMF_V) oraz PC3 (BEMF_U).

Będziemy wykonywali pomiar na trzech kanałach. Jak wcześniej wspomniałem, pomiar będzie wywoływany programowo w momencie wystąpienia przerwania od TIM1.

W celu poprawnego skonfigurowania przetwornika, wybieramy Prescaler PCLK2 divided by 4, rozdzielczość 12-bitów oraz wyrównanie danych do prawej. Ponieważ mamy trzy pomiary, wykorzystamy tryb skanowania. Pomiar ADC będziemy chcieli mieć wywoływany programowo, dlatego tryb ciągły nie będzie nam potrzebny. Analogicznie nie potrzebujemy też trybu Discontinuous. Włączamy natomiast DMA Continuous Request Request i End Of Conversion Mode jako zakończenie serii konwersji.

W ustawieniach trybu regularnej konwersji, wybieramy liczbę konwersji jako 3 i Regular Conversion launched by software. W ustawieniach pomiaru wybieramy kolejno dla rank 1 kanał 13, rank 2 kanał 8 i rank 3 kanał 7, aby miec po kolei pomiary z uzwojenia U, V i W. Jako czas konwersji ustawiamy 28 cykli, co zapewni nam wystarczającą dokładność. Pełna konfiguracja widoczna jest poniżej.

Żeby wykorzystać przesyłanie danych za pomocą DMA, w zakładce DMA Settings wybieramy ADC1 i DMA2 Stream 0. W ustawieniach pozostaje tryb Circular, z inkrementacją adresów pamięci oraz długość danych Word.

Warto upewnić się również, czy włączone są przerwania od DMA, które wykorzystamy do wykonywania obliczeń po pomiarze ADC.

Widok wyjść mikrokontrolera w konfiguratorze będzie się przedstawiał jak na poniższym obrazku.

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

Implementacja

Implementację kontynuujemy, uzupełniając kod przygotowany w artykule „Sterowanie 3-fazowym silnikiem BLDC – algorytm 6-step”. W pliku bldc_motor.h dodajemy enum ułatwiający operowanie etapami procedury startu.

typedef enum
{
	ALIGNMENT = 0,
	START = 1,
	STARTED = 2
}bldc_status;

Uzupełniamy również strukturę o regulator PID, flagi oraz tablicę do pomiarów ADC. Zasadę działania regulatora PID opisywałem w artykule „Sterowanie prędkością – regulator PID”.

struct bldc_control
{
	TIM_HandleTypeDef	*tim_pwm;
	TIM_HandleTypeDef	*tim_com;

	TIM_OC_InitTypeDef sConfigOC;

	volatile uint8_t step_number;
	volatile uint8_t actual_step_number;
	uint32_t speed_pulse;
	volatile uint8_t dir;
	volatile uint32_t flaga_next_step;

	uint32_t bemf_adc_data[3];
	volatile uint32_t state;

	pid_str pid_controller;
};

W pliku bldc_motor.c dodajemy define-y z progiem wykrywania przejścia przez 0 oraz tablicę w czasami używanymi w procedurze startu.

#define BEMF_THRESHOLD_UP		200
#define BEMF_THRESHOLD_DOWN		200

#define PROCESS_MAX_TIME		1

uint32_t table_of_ms_time_to_count_in_process[2] = {200, 3000};
uint32_t time_counter = 0;
uint32_t time_to_count = 0;

W funkcji init przypisujemy początkowy stan jako ALIGNMENT oraz uruchamiamy przerwania od TIM1, które posłużą nam do wywoływania pomiaru ADC.

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 = 6;
	bldc.speed_pulse = 0;
	bldc.dir = CW;
	bldc.flaga_next_step = 0;

	bldc.state = ALIGNMENT;
	time_to_count = table_of_ms_time_to_count_in_process[ALIGNMENT];

	pid_init(&bldc.pid_controller, 0.05, 0, 0.01, 10);

	bldc_motor_Config_Channel_Init();

	__HAL_TIM_SET_AUTORELOAD(bldc.tim_com, ARR_TIM3_VALUE);

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

Algorytm 6-step działa w ten sposób, że przerwanie od komutacji oznacza wywołanie poprzednio ustawionych parametrów timera. Aby ułatwić analizę pomiarów BEMF wprowadziłem dodatkowe pole w strukturze, gdzie będzie przechowywana informacja o aktualnie wykonywanym kroku. Dodatkowo w funkcji bldc_motor_six_step_algorithm() numer kroku będziemy zwiększać dopiero, gdy wykonany etap wyrównania (Alignment).

if(ALIGNMENT != bldc.state)
{
	bldc.actual_step_number = bldc.step_number;

	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;
	}

	bldc.flaga_next_step = 1;
}

Procedura startu realizowana jest w funkcji bldc_motor_process(). Wywoływana jest w pętli while(1) w funkcji main(). Co 1 ms (stała PROCESS_MAX_TIME) wywoływana jest instrukcja switch z wykonaniem odpowiedniej funkcji dla danego etapu. Etapy procedury startu przełączane są zgodnie z danymi w tablicy table_of_ms_time_to_count_in_process[].

void bldc_motor_process(void)
{
	static uint32_t time = 0;

	if(0 == time)
		time = HAL_GetTick();

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

		time_counter += PROCESS_MAX_TIME;

		if(time_counter >= time_to_count)
		{
			time_counter = 0;
			if(STARTED != bldc.state)
			{
				bldc.state++;
				time_to_count = table_of_ms_time_to_count_in_process[bldc.state];
			}
		}

		switch (bldc.state)
		{
			case ALIGNMENT:
			{
				bldc_motor_alignment();
				break;
			}

			case START:
			{
				bldc_motor_start_pid_calculate();
				break;
			}

			case STARTED:
			{
				break;
			}
			default:
				break;
		}
	}
}

W etapie wyrównania, na silniku wymuszane jest ustawienie się w pozycji z kroku 6 – funkcja bldc_motor_alignment().

void bldc_motor_alignment(void)
{
	bldc.step_number = 6;
	__HAL_TIM_SetAutoreload(bldc.tim_com, ARR_TIM3_VALUE);
}

W etapie startowania (rozpędzania się silnika) stopniowo zwiększana jest prędkość, aby uzyskać rodzaj rampy. Do tego celu wykorzystałem regulator PI wywoływany w fukncji bldc_motor_start_pid_calculate(). Wstępnym celem, jaki chcę osiągnąć w przypadku mojego silnika jest wartość 1000 w rejestrze ARR timera przełączającego kroki TIM3.

void bldc_motor_start_pid_calculate(void)
{
	int32_t diff;
	uint32_t measure = __HAL_TIM_GetAutoreload(bldc.tim_com);

	diff = pid_calculate(&bldc.pid_controller, ARR_TIM3_INIT_VALUE, measure);
	__HAL_TIM_SetAutoreload(bldc.tim_com, measure + diff);
}

Po przejściu rozruchu, silnik zaczyna pracować w trybie pomiaru BEMF i regulowania wartości rejestru ARR (szybkości przełączania kroków) na podstawie pomiaru wystąpienia punktu ZC. Pomiar ADC wykonywany jest od przerwania TIM1 w przypadku zliczania w dół (czyli przy stanie niskim PWM).

void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
	if(htim->Instance == TIM1)
	{
		if(SET == __HAL_TIM_IS_TIM_COUNTING_DOWN(&htim1))
		{
			bldc_motor_bemf_start_adc_conversion();
		}
	}
}

void bldc_motor_bemf_start_adc_conversion(void)
{
	HAL_ADC_Start_DMA(&hadc1, bldc.bemf_adc_data, BEMF_NBR_OF_CONVERSION);
}

Następnie w przerwaniu od zakończeniu sekwencji pomiarów ADC wywoływana jest funkcja do szukania ZC.

void HAL_ADC_ConvCpltCallback(ADC_HandleTypeDef* hadc)
{
	bldc_motor_bemf_calculation();
}

Analizę pomiaru BEMF i wykrywanie Zero-crossing będziemy realizowali w funkcji bldc_motor_bemf_calculation(). Na początku umieściłem warunek gwarantujący, że po wykryciu ZC nie będzie już analizowany pomiar BEMF aż do przejścia do kolejnego kroku. Następnie wykonujemy oczekiwanie na demagnetyzację. Gdy pomiary są gotowe do odczytu, w zależności od wykonywanego kroku i kierunku analizowana jest wartość napięcia BEMF i wykrywany moment wystąpienia punktu ZC.

void bldc_motor_bemf_calculation(void)
{
	if(bldc.flaga_next_step == 0)
		return;

	//demagnetization
	if(__HAL_TIM_GetCounter(bldc.tim_com) < (__HAL_TIM_GetAutoreload(bldc.tim_com)/10))
		return;

	switch (bldc.actual_step_number)
	{
		case 1:
		{
			if(CW == bldc.dir)
			{
				if(bldc.bemf_adc_data[BEMF_W_PHASE] < BEMF_THRESHOLD_DOWN)
				{
					bldc_motor_ARR_calculate();
				}
			}
			else if(CCW == bldc.dir)
			{
				if(bldc.bemf_adc_data[BEMF_W_PHASE] > BEMF_THRESHOLD_UP)
				{
					bldc_motor_ARR_calculate();
				}
			}
		}
		break;

		case 2:
		{
			if(CW == bldc.dir)
			{
				if(bldc.bemf_adc_data[BEMF_V_PHASE] > BEMF_THRESHOLD_UP)
				{
					bldc_motor_ARR_calculate();
				}
			}
			else if(CCW == bldc.dir)
			{
				if(bldc.bemf_adc_data[BEMF_V_PHASE] < BEMF_THRESHOLD_DOWN)
				{
					bldc_motor_ARR_calculate();
				}
			}
		}
		break;

		case 3:
		{
			if(CW == bldc.dir)
			{
				if(bldc.bemf_adc_data[BEMF_U_PHASE] < BEMF_THRESHOLD_DOWN)
				{
					bldc_motor_ARR_calculate();
				}
			}
			else if(CCW == bldc.dir)
			{
				if(bldc.bemf_adc_data[BEMF_U_PHASE] > BEMF_THRESHOLD_UP)
				{
					bldc_motor_ARR_calculate();
				}
			}
		}
		break;

		case 4:
		{
			if(CW == bldc.dir)
			{
				if(bldc.bemf_adc_data[BEMF_W_PHASE] > BEMF_THRESHOLD_UP)
				{
					bldc_motor_ARR_calculate();
				}
			}
			else if(CCW == bldc.dir)
			{
				if(bldc.bemf_adc_data[BEMF_W_PHASE] < BEMF_THRESHOLD_DOWN)
				{
					bldc_motor_ARR_calculate();
				}
			}
		}
		break;

		case 5:
		{
			if(CW == bldc.dir)
			{
				if(bldc.bemf_adc_data[BEMF_V_PHASE] < BEMF_THRESHOLD_DOWN)
				{
					bldc_motor_ARR_calculate();
				}
			}
			else if(CCW == bldc.dir)
			{
				if(bldc.bemf_adc_data[BEMF_V_PHASE] > BEMF_THRESHOLD_UP)
				{
					bldc_motor_ARR_calculate();
				}
			}
		}
		break;

		case 6:
		{
			if(CW == bldc.dir)
			{
				if(bldc.bemf_adc_data[BEMF_U_PHASE] > BEMF_THRESHOLD_UP)
				{
					bldc_motor_ARR_calculate();
				}
			}
			else if(CCW == bldc.dir)
			{
				if(bldc.bemf_adc_data[BEMF_U_PHASE] < BEMF_THRESHOLD_DOWN)
				{
					bldc_motor_ARR_calculate();
				}
			}
		}
		break;
	}
}

Gdy punkt ZC zostanie wykryty, wywoływana jest funkcja bldc_motor_ARR_calculate(). Pobierana jest wartość licznika określająca czas od ostatniego przełączenia kroku do ZC oraz aktualna wartość rejestru ARR. Nowa wartość rejestru ARR obliczana jest z uwzględnieniem przesunięcia o 30° punktu ZC względem przełączenia na kolejny krok. Do ustawienia nowej wartości ARR dla TIM3 wykorzystuję dalej regulator PI, dzięki któremu zmiany szybkości przełączania są płynne, co zapobiega przypadkowym zatrzymaniom silnika, gdy wartości są zmieniane zbyt gwałtownie (silnik BLDC jest na takie zmiany dość wrażliwy).

void bldc_motor_ARR_calculate(void)
{
	uint32_t new_ARR_Value;
	uint32_t Bemf_Zero_Cross_Time;
	uint32_t old_ARR_Value = ARR_TIM3_VALUE;
	int32_t diff;

	Bemf_Zero_Cross_Time = __HAL_TIM_GetCounter(bldc.tim_com);
	old_ARR_Value = __HAL_TIM_GetAutoreload(bldc.tim_com);

	new_ARR_Value = Bemf_Zero_Cross_Time + old_ARR_Value/2;

	if(STARTED == bldc.state)
	{
		diff = pid_calculate(&bldc.pid_controller, new_ARR_Value, old_ARR_Value);
		__HAL_TIM_SetAutoreload(bldc.tim_com, old_ARR_Value + diff);
	}

	bldc.flaga_next_step = 0;
}

W funkcji main() wywołujemy tylko inicjalizację i ustawienie prędkości oraz kierunku. W pętli while(1) dodajemy funkcję bldc_motor_process() sterującą procedurą startu.

/* USER CODE BEGIN 2 */
    uint32_t dir = CW;
    uint32_t speed = 10;

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

  /* Infinite loop */
  /* USER CODE BEGIN WHILE */
  while (1)
  {
	  bldc_motor_process();
    /* USER CODE END WHILE */

    /* USER CODE BEGIN 3 */
  }
  /* USER CODE END 3 */

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.

Warto zaznaczyć, co daje tak na prawdę sterowanie z pomiarem BEMF w porównaniu do samego algorytmu 6-step. Sterowanie zgodnie z położeniem wału silnika sprawia, że przede wszystkim osiągamy znacznie większy moment obrotowy. Dodatkowo to, co możemy łatwo zmierzyć jest pobór prądu. W moim przypadku przy wypełnieniu PWM na poziomie 10% pobór prądu przed przejście na trym STARTED silnik pobiera prawie 3 razy więcej prądu, niż przy sterowaniu z wykrywaniem ZC. Ale najważniejsze jest to, że możemy sterować prędkością poprzez zmianę wypełnienia PWM, a nie poprzez przypadkowy dobór prędkości przełączania kroków.

Podsumowanie

Sterowanie silnikiem BLDC w porównaniu do szczotkowych silników DC to proces dość skomplikowany. Wymaga implementacji dedykowanego algorytmu oraz sterowania na podstawie położenia wału, aby uzyskać optymalny moment obrotowy. Algorytm bezczujnikowy (w porównaniu do czujników Halla czy enkoderów) wymaga bardziej rozbudowanego oprogramowania i zastosowania dodatkowych układów peryferyjnych, jednak silniki są dzięki temu tańsze i bardziej niezawodne w eksploatacji. Mam nadzieję, że powyższy artykuł przybliżył Ci sposób sterowania metodą bezczujnikową.

Do pobrania

Repozytorium GitHub

  1. Z tego co tutaj piszesz, to TIM1 ustawiłeś w Mode 1 czyli jak licznik liczy do góry, to do puki nie osiągnie wartości CCR wyjście jest ustawione w stan wysoki. Po osiągnięciu CCR wyjście przełączane jest w stan niski. Potem licznik osiąga ARR i zaczyna liczyć w dół. Gdy zrówna się z CCR znowu przełączane jest w stan wysoki. Mam rację? Jeśli tak, to do opisu który zamieściłeś w tekście i w efekcie chciałeś osiągnąć bardziej pasuje Mode2. Tak się tylko mądrze, bo nie mogę swojej teorii na razie przetestować. Co sądzisz?

  2. MEGA kurs!!!!
    Czekam abyś zrobił jeszcze sterowanie silnika bldc oparte na jakimś enkoderze ABZ, UVW czy też AS_5600 z możliwością szybkiego hamowania silnikiem i rozkręcania go do 1kRPM.
    Pozdrawiam Krzysiek

  3. Mogę wiedzieć z czego korzystałeś przy tworzeniu tego materiału? Źródła itd.? Z góry dzięki 🙂

    1. Głównie opierałem się na materiałach od ST m.in. UM2124,UM2771 oraz bibliotece MC_6Step_Lib. Ciekawa seria artykułów pojawiła się też jakiś czas temu na łamach EP – o BEMF jest w części drugiej pt. „Silniki BLDC (2). Określanie położenia wirnika”.

Dodaj komentarz

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