Zaawansowane sterowanie prędkością silnika – „rampa” trapezoidalna

Tworząc projekt wykorzystujący napęd w postaci silnika prądu stałego, krokowego, czy BLDC w pewnym momencie dochodzimy do wniosku, że podstawowe sterowanie zaimplementowane w naszymi programie, gdzie zadajemy tylko prędkość silnika, jest niewystarczające. Chcielibyśmy, aby nasze urządzenie poruszało się płynnie i nie „szarpało” w momencie zmiany kierunku ruchu, jak również precyzyjnie dojeżdżało do zadanego celu. Właśnie w tym celu stosujemy łagodny start (tzw. soft start), który jest elementem tzw. „rampy”.

W artykule chciałbym przedstawić sposób sterowania z wykorzystaniem rampy w kształcie trapezu. Do realizacji projektu wykorzystam silnik krokowy, sterownik DRV8834 oraz zestaw Nucleo-L476RG. W materiale skupię się na opisie implementacji samej rampy. Dokładny opis sposobu sterowania silnikiem krokowym opisany został w artykule „Silnik krokowy„.

Czym jest rampa trapezoidalna

Rampa to określenie charakterystyki zmian prędkości w czasie. Zakłada, że sterując prędkością silnika zadajemy nie tylko docelową prędkość, ale także parametry określające, jak ta prędkość w czasie ma być osiągana, czyli przyspieszenie i opóźnienie. W przypadku najprostszego sterowania zadajemy prędkość docelową z jaką chcemy, aby silnik się poruszał. Teoretyczna wartość przyspieszenia jest nieskończona, jednak silnik w zależności od obciążenia osiąga docelową prędkość w różnym czasie, na który nie mamy wpływu.

Aby precyzyjnie sterować ruchem silnika i osiąganą przez niego pozycją (czy to zadanego kąta w manipulatorze, czy odległości przebytej przez robota mobilnego) musimy panować nad ruchem obiektu. W tym celu stosuje się właśnie rampę. Rampa uwzględnia różnicę między teoretycznymi zmianami prędkości a rzeczywistym światem fizycznym. Stosowane są różne profile ramp, które znajdują zastosowanie w konkretnych przypadkach. Najpopularniejszym i najczęściej stosowanym rodzajem jest rampa trapezoidalna.

Rampa trapezoidalna zakłada, że zmiany prędkości silnika w momencie przyspieszania i opóźniania są stałe. Mamy tutaj do czynienia ze znanym z lekcji fizyki ruchem jednostajnie przyspieszonym oraz ruchem jednostajnie opóźnionym. Prowadzi on do liniowego wzrostu i spadku prędkości poruszającego się obiektu. Wykres zmian prędkości w czasie dla tego typu rampy przedstawia poniższy wykres.

Najważniejszymi parametrami zadawanymi w przypadku profilu trapezoidalnego jest prędkość docelowa (v_max) oraz przyspieszenie (a od acceleration) i opóźnienie (d od deceleration). Wartości te definiują nam, jak długo będzie trwał proces rozpędzania się silnika oraz jego hamowania. Jak widzimy na wykresie, ruch silnika w przypadku rampy trapezoidalnej możemy podzielić na 4 etapy. Etap 1 to przyspieszanie, którego efektem jest osiągnięcie zadanej prędkości. Następnie silnik porusza się ze stałą prędkością, aż do mementu, w którym powinien zacząć zwalniać, aby zatrzymać się w odpowiednim momencie. Etap 3 to zwalnianie, czyli ruch jednostajnie opóźniony.

Właściwie w tym momencie z czysto teoretycznego punktu widzenia można byłoby zakończyć opis rampy. Jednak praktyka wskazuje, że tak zdefiniowany ruch jest wyidealizowany i nie zawsze prowadzi do osiągnięcia zadanego celu – kąta obrotu czy przejechanej odległości. Przy zbyt małych prędkościach nasz obiekt może nie być w stanie się już się poruszać, a więc zatrzyma się w niewielkiej odległości od celu. A nam w końcu zależy na tym, aby ruch był jak najbardziej precyzyjny. Dlatego stosuje się zabieg widoczny w etapie 4. Polega on na określeniu prędkości minimalnej, z jaką może poruszać się silnik przy danym obciążeniu, i w końcowym etapie spowalniania wykonanie ruchu z tą prędkością minimalną, aż do momentu osiągnięcia zadanego celu. Ze względu na to, że prędkość jest wtedy bardzo mała, możliwe jest natychmiastowe zatrzymanie silnika, co byłoby niemożliwe przy większej prędkości. W ten sposób jesteśmy w stanie uzyskać płynny i precyzyjny ruch.

Czasami zdarza się jednak tak, że parametry ruchu (kąt obrotu, prędkość oraz przyspieszenie i opóźnienie) zadane są w taki sposób, że silnik nie jest w stanie osiągnąć docelowej prędkości maksymalnej tak, aby zachować wartości przyspieszenia i opóźnienia. Wówczas mamy do czynienia z niepełną rampą trapezoidalną, w której etap 2, czyli ruch ze stałą prędkością jest pominięty.

W takim przypadku niezbędne jest wykonanie właściwych obliczeń pozwalających na ustalenie momentu, w którym silnik powinien zacząć zwalniać, oraz prędkości maksymalnej, jaką powinien osiągnąć.

Konfiguracja mikrokontrolera

Znając podstawy działania rampy trapezoidalnej możemy przejść do konfiguracji wyjść i wejść mikrokontrolera. Przedstawię tylko najważniejsze elementy konfiguracji. Dokładny opis wraz z zasadą działania sterownika DRV8834 opisany został w artykule „Silnik krokowy„, do którego odsyłam. Sterowanie silnikiem krokowym zaimplementujemy dla mikrokontrolera STM32L476RG na zestawie Nucleo-L476RG w środowisku STM32CubeIDE.

Tworzymy zatem nowy projekt wybierając „File->New->STM32 Project”. Przechodzimy przez wstępną konfigurację projektu i zabieramy się za konfigurację wyjść mikrokontrolera. Ja wygenerowałem projekt z domyślną konfiguracją dla płytki Nucleo, dlatego część pinów mam już skonfigurowane. Do sterowania silnikiem będziemy potrzebowali jednego sygnału PWM oraz jednego wyjścia GPIO. Na pin STEP sterownika DRV8834 musimy podać impulsy – wykorzystamy sygnał PWM o wypełnieniu 50%. Jako wyjście PWM posłuży nam pin PB10 (TIMER 2, kanał 3). W oknie konfiguracji układów peryferyjnych po lewej stronie wybieramy zatem zakładkę „Timers->TIM2” i w polu „Channel 3” wybieramy „PWM Generation CH3„. Poza tym potrzebujemy jednego pinu do wyboru kierunku obrotu – wykorzystamy pin PA8. W przypadku wyjść GPIO, konfigurujemy je klikając lewym przyciskiem myszy na pin i wybierając opcję „GPIO_Output„. Możemy też przypisać etykietę DIR dla PA8 i STEP dla PB10 (klikając prawym przyciskiem na pin).

Teraz przejdziemy do konfiguracji samego układu Timer-a. Po wybraniu trybu pracy kanału 3 jako PWM, otworzy nam się okno Configuration, gdzie mamy dostępną konfiguracje rejestrów licznika przedstawioną w przystępny sposób. Wartości Prescalera, Counter Period oraz Pulse zostały dobrane tymczasowo, ponieważ będziemy je modyfikowali w trakcie programu w taki sposób, aby osiągnąć zadaną prędkość obrotu. Ważne, aby wartość Pulse stanowiła 50% wartości Counter Period (potrzebujemy wygenerować sygnał prostokątny). Poza tym chcemy aby nasz licznik zliczał w górę (Counter Mode -> Up), wykorzystamy tryb 1 PWMbez trybu Fast oraz z polaryzacją High. Pełna konfiguracja Timer-a 2 oraz kanału 3 wygląda w następujący sposób.

Silnik krokowy jest znakomitym rozwiązaniem w przypadku, gdy potrzebujemy wykonać obrót o zadany kąt. Aby to zrealizować, będziemy potrzebowali zliczać impulsy generowane przez Timer 2 Channel 3 i zatrzymać sygnał PWM, gdy licznik osiągnie żądaną wartość. Zaimplementujemy metodę programową z wykorzystaniem przerwań od timera 2 po wygenerowaniu każdego impulsu i zliczaniu ich poprzez inkrementację zmiennej.

Poza konfiguracją Timera 2 przedstawionej powyżej, potrzebujemy jeszcze włączyć przerwania, które będą generowane po wystąpieniu każdego impulsu. Wchodzimy do zakładki „NVIC Settings” i zaznaczamy „TIM2 global interrupt„. Priorytet tutaj nie będzie miał znaczenia, bo jest to nieskomplikowany projekt, ale jeśli używasz przerwań w większym projekcie, pamiętaj aby właściwie dobrać priorytety.

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

Implementacja

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

W pliku stepper.h dodamy definicje stałych określających konfigurację naszego silnika krokowego oraz sterownika.

#define STEPPER_MOTOR_TASK_FREQUENCY	        100
#define STEPPER_MOTOR_TASK_PERIOD		(1000/STEPPER_MOTOR_TASK_FREQUENCY)

#define STEPPER_MOTOR_MAX_FREQ_HZ		(MICRO_STEP * 1000)
#define STEPPER_MOTOR_MIN_FREQ_HZ		1

#define STEPPER_MOTOR_MAX_SPEED_USTEP_PER_SEC		STEPPER_MOTOR_MAX_FREQ_HZ
#define STEPPER_MOTOR_MIN_SPEED_USTEP_PER_SEC		(STEPPER_MOTOR_MAX_SPEED_USTEP_PER_SEC / 100)

#define STEP_PER_REVOLUTION			200
#define MICRO_STEP				32

#define CONSTANT_SPEED				0
#define LINEAR_SPEED				1

Prędkością silnika będziemy sterować zmieniając częstotliwość pracy timera 2. Założyłem, że zakres częstotliwości będzie w przedziale od 1 Hz do 1 kHz (dobrałem tę wartość doświadczalnie przy pełnym kroku dla mojego silnika – przy wyższych wartościach piszczał i nie był już w stanie się ruszyć). W programie zaimplementujemy dwa rodzaje sterowania: ze stałą prędkością (CONSTANT_SPEED), czyli bez użycia rampy oraz z zastosowaniem rampy trapezoidalnej, czyli LINEAR_SPEED.

W porównaniu do sterowania silnikiem krokowym opisanego w artykule „Silnik krokowy„, rozszerzyłem strukturę silnika o kilka elementów. Przede wszystkim dodałem podstrukturę speed_profile_s, gdzie przechowuję typ rampy, docelową prędkość, przyspieszenie i opóźnienie. Dodatkowo stworzyłem podstrukturę ramp_s, w której umieszczone są wyniki obliczeń mikrokroków potrzebnych do osiągnięcia maksymalnej prędkości i potrzebnych do wychamowania. Poza tym dodałem zmienne przechowujące aktualną prędkość oraz flagę określającą, czy prędkość maksymalna została osiągnięta.

struct speed_profile_s
{
	profile_mode mode;
	uint32_t target_speed;
	uint32_t accel;
	uint32_t decel;
};

struct ramp_s
{
	uint32_t usteps_to_full_speed;
	uint32_t usteps_to_stop;
};

struct stepper_s
{
	struct htim_s timer;

	struct speed_profile_s profile;
	struct ramp_s ramp;

	stepper_mode mode;
	volatile uint32_t ustep_counter;
	uint32_t usteps_to_count;
	uint32_t actual_speed;

	uint32_t target_speed_achieved;
};

Funkcje w pliku stepper.c również zostały zmodyfikowane. Dodałem funkcję stepper_set_ramp_profile(), która odpowiada za zapisanie danych o parametrach rampy.

void stepper_set_ramp_profile(struct stepper_s *_stepper, profile_mode _mode, uint32_t _target_speed, uint32_t _accel, uint32_t _decel)
{
	_stepper->profile.mode = _mode;
	_stepper->profile.target_speed = _target_speed * MICRO_STEP;
	_stepper->profile.accel = _accel * MICRO_STEP;
	_stepper->profile.decel = _decel * MICRO_STEP;
}

Następnie zmodyfikowałem funkcje stepper_set_continous() oraz stepper_set_angle(), gdzie zamiast zadania prędkości silnika wywoływana jest funkcja stepper_ramp_init() inicjalizująca rampę.

void stepper_set_continous(struct stepper_s *_stepper, direction _dir)
{
	_stepper->mode = continous;

	stepper_set_direction(_stepper, _dir);
	stepper_ramp_init(_stepper);

	HAL_TIM_PWM_Start_IT(_stepper->timer.htim, _stepper->timer.channel);
}

void stepper_set_angle(struct stepper_s *_stepper, direction _dir, uint32_t _angle)
{
	_stepper->mode = angle;

	_stepper->ustep_counter = 0;
	_stepper->usteps_to_count = _angle * (STEP_PER_REVOLUTION * MICRO_STEP) / 360;

	if(0 == _stepper->usteps_to_count)
	{
		stepper_imediate_stop(_stepper);
		return;
	}

	stepper_set_direction(_stepper, _dir);
	stepper_ramp_init(_stepper);

	HAL_TIM_PWM_Start_IT(_stepper->timer.htim, _stepper->timer.channel);
}

Stworzyłem również funkcję stepper_stop_with_profile() która wywołuje zatrzymanie silnika zgodnie z ustawieniami rampy. Przydatna będzie w przypadku, gdy chcemy zatrzymać silnik pracujący w trybie obrotu ciągłego.

void stepper_stop_with_profile(struct stepper_s *_stepper)
{
	_stepper->mode = stop;
	_stepper->ustep_counter = 0;
}

Funkcja stepper_ramp_init() odpowiada za wykonanie wstępnych obliczeń. W przypadku trybu ze stałą prędkością, zadaje tylko prędkość silnika. Dla ruchu z zastosowaniem rampy, funkcja wywołuje podfunkcję stepper_ramp_init_linear_mode(), która wykonuje obliczenia mikrokroków potrzebnych do osiągnięcia zadanej prędkości i hamowania zgodnie z zadanym przyspieszeniem i opóźnieniem. W przypadku obrotu o zadany kąt, funkcja analizuje również, czy silnik będzie w stanie osiągnąć zadaną prędkość i jeśli będzie to niemożliwe (zbyt krótki ruch), wyznaczy punkt, w którym silnik będzie musiał zacząć zwalniać.

void stepper_ramp_init(struct stepper_s *_stepper)
{
	switch (_stepper->profile.mode)
	{
		case CONSTANT_SPEED:
		{
			stepper_set_speed(_stepper, _stepper->profile.target_speed);
			break;
		}
		case LINEAR_SPEED:
		{
			stepper_ramp_init_linear_mode(_stepper);
			break;
		}
		default:
			break;
	}
}

void stepper_ramp_init_linear_mode(struct stepper_s *_stepper)
{
	stepper_set_speed(_stepper, _stepper->profile.accel/STEPPER_MOTOR_TASK_FREQUENCY);

	if(continous == _stepper->mode)
	{
		_stepper->ramp.usteps_to_full_speed = (_stepper->profile.target_speed * _stepper->profile.target_speed) / (2 *_stepper->profile.accel);
		_stepper->ramp.usteps_to_stop = (_stepper->profile.target_speed * _stepper->profile.target_speed) / (2 *_stepper->profile.decel);
	}
	else if(angle == _stepper->mode)
	{
		_stepper->ramp.usteps_to_full_speed = (_stepper->profile.target_speed * _stepper->profile.target_speed) / (2 *_stepper->profile.accel);
		_stepper->ramp.usteps_to_stop = (_stepper->profile.target_speed * _stepper->profile.target_speed) / (2 *_stepper->profile.decel);

		if(_stepper->usteps_to_count < (_stepper->ramp.usteps_to_full_speed + _stepper->ramp.usteps_to_stop))
		{
			_stepper->ramp.usteps_to_full_speed = (_stepper->usteps_to_count * _stepper->profile.decel) / (_stepper->profile.accel + _stepper->profile.decel);
			_stepper->ramp.usteps_to_stop = _stepper->usteps_to_count - _stepper->ramp.usteps_to_full_speed;
		}
	}
}

Za prawidłową pracę silnika i generowanie rampy odpowiada funkcja stepper_ramp_process(). Wywoływana będzie ona w pętli głównej. Jak możemy zauważyć, będzie się wykonywana co jakiś odcinek czasu zdefiniowany przez stałą STEPPER_MOTOR_TASK_PERIOD (w moim przypadku co 10 ms) z wykorzystaniem timera programowego.

void stepper_ramp_process(struct stepper_s *_stepper)
{
	static uint32_t time_ms = 0;

	if(0 == time_ms)
		time_ms = HAL_GetTick();

	if((HAL_GetTick() - time_ms) < STEPPER_MOTOR_TASK_PERIOD)
		return;

	time_ms = HAL_GetTick();

	if(idle == _stepper->mode)
		return;

	switch (_stepper->profile.mode)
	{
		case CONSTANT_SPEED:
		{
			break;
		}
		case LINEAR_SPEED:
		{
			stepper_ramp_process_linear_mode(_stepper);
			break;
		}
		default:
			break;

	}
}

Jeżeli mamy skonfigurowany ruch silnika z wykorzystaniem rampy trapezoidalnej (LINEAR_SPEED), wywoływana jest podfunkcja stepper_ramp_process_linear_mode(). W przypadku obrotu ciągłego, funkcja sprawdza, czy silnik jest na etapie przyspieszenia. Jeżeli tak, to zwiększa prędkość o odpowiednią wartość zgodnie z przyspieszeniem (przyspieszenie podawane jest przez użytkownika w krokach na sekundę, dlatego tutaj musi zostać podzielone przez wartość częstotliwości pracy zadania). W przypadku wywołania stopu, funkcja odpowiednio zmniejsza prędkość, aż do zatrzymania.

Dla obrotu o zadany kąt funkcja analogicznie zwiększa prędkość do momentu osiągnięcia obliczonej wartości mikrokroków, następnie silnik porusza się z zadaną prędkością maksymalną, a na koniec przechodzi do ruchu opóźnionego. W przypadku hamowania algorytm sprawdza, czy prędkość nie spada poniżej wartości minimalnej STEPPER_MOTOR_MIN_SPEED_USTEP_PER_SEC i ustawią ją na prędkość minimalną, co pozwala zrealizować etap 4 rampy.

Warto zauważyć, że w przypadku ruchu ciągłego dodałem dodatkowo zabezpieczenie w postaci flagi osiągnięcia maksymalnej prędkości (target_speed_achieved). Pozwala to uniknąć sytuacji, gdy zmienna zliczająca wykonane mikrokroki przepełni się i silnik mógłby znów zacząć przyspieszać.

void stepper_ramp_process_linear_mode(struct stepper_s *_stepper)
{
	uint32_t speed = 0;

	if(continous == _stepper->mode)
	{
		if(_stepper->ustep_counter < _stepper->ramp.usteps_to_full_speed && _stepper->target_speed_achieved != 1)
		{
			speed = _stepper->actual_speed + (_stepper->profile.accel / STEPPER_MOTOR_TASK_FREQUENCY);
		}
		else
		{
			_stepper->target_speed_achieved = 1;
			speed = _stepper->profile.target_speed;
		}

		stepper_set_speed(_stepper, speed);
	}
	else if(angle == _stepper->mode)
	{
		if(_stepper->ustep_counter < _stepper->ramp.usteps_to_full_speed)
		{
			speed = _stepper->actual_speed + (_stepper->profile.accel / STEPPER_MOTOR_TASK_FREQUENCY);
		}
		else if((_stepper->usteps_to_count - _stepper->ustep_counter) < _stepper->ramp.usteps_to_stop)
		{
			speed = _stepper->actual_speed - (_stepper->profile.decel / STEPPER_MOTOR_TASK_FREQUENCY);
			if(speed < STEPPER_MOTOR_MIN_SPEED_USTEP_PER_SEC)
			{
				speed = STEPPER_MOTOR_MIN_SPEED_USTEP_PER_SEC;
			}
		}
		else
		{
			speed = _stepper->profile.target_speed;
		}

		stepper_set_speed(_stepper, speed);
	}
	else if(stop == _stepper->mode)
	{
		if(_stepper->ustep_counter < _stepper->ramp.usteps_to_stop)
		{
			speed = _stepper->actual_speed - (_stepper->profile.decel / STEPPER_MOTOR_TASK_FREQUENCY);

			if(speed < STEPPER_MOTOR_MIN_SPEED_USTEP_PER_SEC)
			{
				speed = 0;
			}

			stepper_set_speed(_stepper, speed);
		}
		else
		{
			stepper_imediate_stop(_stepper);
		}
	}
}

Za zliczanie wykonanych mikrokroków oraz zatrzymanie silnika w przypadku ruchu o zadany kat, gdy wymagane mikrokroki zostaną osiągnięte, odpowiada kod obsługi przerwania umieszczony w callback-u w pliku main.c.

/* USER CODE BEGIN 4 */
void HAL_TIM_PWM_PulseFinishedCallback(TIM_HandleTypeDef *htim)
{
	if(htim->Instance == stepper.timer.htim->Instance)
	{
		stepper.ustep_counter++;

		if(angle == stepper.mode)
		{
			if(stepper.ustep_counter >= stepper.usteps_to_count)
			{
				stepper_set_speed(&stepper, 0);
			}
		}
	}
}
/* USER CODE END 4 */

Poza tym w pliku main.c wywoływane są funkcje konfigurujące pracę silnika oraz funkcja procesowa ruchu w pętli nieskończonej. Funkcję hamowania z zadanym opóźnieniem wywołuję po naciśnięciu przycisku dostępnego na płytce Nucleo.

/* USER CODE BEGIN 2 */
  direction dir = CW;
  
  stepper_init(&stepper, &htim2, TIM_CHANNEL_3);
  stepper_set_ramp_profile(&stepper, LINEAR_SPEED, 500, 500, 200);
  stepper_set_angle(&stepper, dir, 2*360);
  //stepper_set_continous(&stepper, dir);

  /* USER CODE END 2 */

  /* Infinite loop */
  /* USER CODE BEGIN WHILE */
  while (1)
  {
	  stepper_ramp_process(&stepper);

	  if(HAL_GPIO_ReadPin(B1_GPIO_Port, B1_Pin) == 0)
		  stepper_stop_with_profile(&stepper);
    /* USER CODE END WHILE */

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

Pełny kod programu dostępny jest na moim repozytorium GitHub (sekcja „Do pobrania” na końcu artykułu).

Sprawdzenie działania programu

Przed uruchomieniem projektu powinniśmy połączyć sterownik z zestawem Nucleo i podłączyć silnik. Robimy to według poniższego schematu (mikrokroki ustawione na 1/32).

Teraz możemy skompilować kod (Project->Build Project) i go uruchomić (Run->Run).

Dla sprawdzenia działania napisanego programu warto byłoby podejrzeć, jak zmienia się prędkość w trakcie działa programu. Do tego celu znakomitym narzędziem jest STM32CubeMonitor. Po uruchomieniu programu otrzymujemy ekran startowy. Tworzymy schemat przepływu danych analogicznie to przedstawionego poniżej. Do prezentacji danych wybieramy obiekt „chart”, czyli wykres liniowy.

Następnie konfigurujemy zmienne, jakie chcemy wyświetlić. Klikamy dwa razy w pole „myVariables” i ustawiamy plik „Executable” klikając podając ścieżkę i plik „*.elf”. Wybieramy zmienne stepper.actual_speed oraz stepper.ustep_counter. Następnie w polu „my_Probe_Out” i „myProbe_In” ustawiamy nasz programator. Zatwierdzamy wszystko przyciskiem „Deploy” i otwieramy klikając w „Dashboard”.

Po wgraniu programu na płytkę Nucleo, możemy uruchomić akwizycję danych (należy pamiętać, że STM32CubeIDE i STM32CubeMonitor korzystając z tego samego programatora i nie mogą robić tego jednocześnie). Na poniższych wykresach widoczny jest wykres prędkości zadanej silnika oraz wykonane mikrokroki dla przykładowych parametrów ruchu.

Efekt działania programu widoczny jest na poniższym filmie.

Podsumowanie

Wiele projektów wykorzystujących napędy w postaci silników DC, krokowych czy BLDC wymagają dokładnego sterowania prędkością w zależności od pozycji. Do tego celu stosuje się różne zabiegi, ale jednym z najpopularniejszych jest implementacja tzw. „rampy”. W większości przypadków głównym założeniem jest osiągnięcie precyzji ruchu, ale także płynności dającej fajny efekt wizualny np. przy poruszaniu się robota mobilnego. Dodatkowo, stosując rampę, jesteśmy w stanie ograniczyć gwałtowne ruchy obiektu np. przy zmianie kierunku, a przy tym ograniczyć pobór prądu. Mam nadzieję, że przedstawiony artykuł w prosty sposób wyjaśnia zasadę działania rampy oraz to, w jaki sposób można ją zaimplementować na STM32.

Do pobrania

Repozytorium GitHub

Dodaj komentarz

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