Autor: Henio
Redakcja: Dondu
W tym artykule przedstawię sposób na sterowanie dwoma silnikami krokowymi unipolarnymi poprzez moduł bluetooth i system Android.
Projekt ten powstał (i dalej powstaje) do sterowania laserem. Jest to fajne działko pozwalające w miarę precyzyjnie sterować punktem na niewielkich odległościach.
Sterowanie może nie jest jeszcze doskonałe, ale można je wykorzystać do różnych celów, np. silniki mogą poruszać głowicą po szynach skierowaną w dół itp.
Część mechaniczna wygląda następująco:
Wygląd części mechanicznej działka. |
a w działaniu prezentuje się tak:
Sterowanie silnikami
Do projektu wykorzystałem dwa takie same silniki krokowe unipolarne, które pochodzą z drukarki. Cewki silnika są zasilane napięciem 12V i są sterowane poprzez tranzystory MOSFET z kanałem typu N "BS170".Tranzystory do cewek silnika są podłączone tak:
Podłączenie cewek silnika. |
Jak wyznaczyć uzwojenia takiego silnika można przeczytać w artykule który znajduje się na blogu: Silnik krokowy
Sterowanie silnika odbywa się półkrokowo, poniższa tabela pokazuje sekwencje załączania tranzystorów.
Sekwencje sterowania |
Polega to na tym że każda kolejna sekwencja jest wykonywana co 2ms. Tranzystory są tak włączane i wyłączane że każda z cewek jest zasilana przez 3 pełne sekwencje, z czego kolejna przeciwległa cewka jest już zasilana podczas ostatniej sekwencji wcześniejszej cewki.
Schemat i elementy
Teraz przedstawię schemat całego układu oraz plik Eagle do pobrania. Nie będę zamieszczał piku płytki ponieważ nie jest zbyt dobrze zrobiona, a poza tym każdy musi dopasować wyprowadzenia do silników.Schemat do pobrania: Schemat
Schemat całego układu. |
Jak widać na schemacie wykorzystany uC to ATmega32A, jednak podejrzewam że wszystko włącznie z kodem programu powinno pasować do ATmegi8 ponieważ wykorzystuję tylko interfejs USART, oraz TIMER0, a jak się nie mylę to rejestry są takie same, przynajmniej do USART na 100%. Na schemacie jest też złącze P/S2 Mouse, ale obecnie jest niewykorzystywane więc można sobie odpuścić. Wyświetlacz też nie jest wykorzystywany lecz można go zostawić, gdyż może się przydać do ewentualnej rozbudowy.
Niewykorzystane piny uC są podłączone do goldpinów w celu ewentualnej rozbudowy. Są też wypuszczone piny +5V, +12V oraz masy. Zastosowany moduł bluetooth to HC-05 przystosowany do poziomów napięć +5V. Podłączony jest też kwarc 16 MHz jednak obecnie wykorzystany jest wewnętrzny oscylator 8 MHz.
Wygląd płytki |
Wygląd płytki |
Kod programu
Opis działania programu jest zawarty w komentarzach, mam nadzieję że wszystko będzie czytelne i zrozumiałe. Oczywiście sposób komunikacji USART jest skopiowany z artykułu na blogu: RS-232: Komunikacja ATmega8 z komputerem
main.c
//częstotliwość zegara //#define F_CPU 8000000UL // Częstotliwość F_CPU ustaw w opcjach projektu zgodnie z: // http://mikrokontrolery.blogspot.com/2011/03/fcpu-gcc-gdzie-definiowac.html #include <avr/io.h> #include <util/delay.h> #include <stdlib.h> #include <avr/interrupt.h> //definicja MOS N #define MOS1 PA0 #define MOS2 PA1 #define MOS3 PA2 #define MOS4 PA3 #define MOS5 PA4 #define MOS6 PA5 #define MOS7 PA6 #define MOS8 PA7 #define LASER PB0 //definicja portu pod którym mam podpięty laser //kiedy silniki są w ruchu na pinie jest "0" logiczne //kiedy silniki stoją na pinie jest "1" logiczne #define MOS1_ON PORTA |= (1<<MOS1); #define MOS1_OFF PORTA &= ~(1<<MOS1); #define MOS2_ON PORTA |= (1<<MOS2); #define MOS2_OFF PORTA &= ~(1<<MOS2); #define MOS3_ON PORTA |= (1<<MOS3); #define MOS3_OFF PORTA &= ~(1<<MOS3); #define MOS4_ON PORTA |= (1<<MOS4); #define MOS4_OFF PORTA &= ~(1<<MOS4); #define MOS5_ON PORTA |= (1<<MOS5); #define MOS5_OFF PORTA &= ~(1<<MOS5); #define MOS6_ON PORTA |= (1<<MOS6); #define MOS6_OFF PORTA &= ~(1<<MOS6); #define MOS7_ON PORTA |= (1<<MOS7); #define MOS7_OFF PORTA &= ~(1<<MOS7); #define MOS8_ON PORTA |= (1<<MOS8); #define MOS8_OFF PORTA &= ~(1<<MOS8); #define LASER_ON PORTB &= ~(1<<LASER); #define LASER_OFF PORTB |= (1<<LASER); #define LICZNIK 6 //max 90 (to zależy od silnika, przy moim jest 90) //6 = 2ms //oznaczenia: zmienna kończy się na _v - zmienna sterująca silnikiem pionowym, _h - zmienna sterująca silnikiem poziomym; #define OPOZNIENIE_V 0 //ile przerwań timera silnik ma czekać po każdym kroku silnika #define OPOZNIENIE_H 0 //ustawiając zwalniamy pracę silnika volatile int wait_time_h = 0; //ile już czekał volatile int wait_time_v = 0; volatile int seq_v = 0; //każdy krok silnika składa się z 9 'podkroków', zmienna ta przechowuje informację w którym 'podkroku' silnik się znajduje volatile int seq_h = 0; volatile int max_v = 30; //maksymalna ilość kroków silnika (maksymalne 'obrót' w jedną stronę) volatile int max_h = 100; //ustawiamy tutaj "rozdzielczoć" np. przy wartociach 100x100 silnik będzie krelił po kwadracie, przy różnych silnikach lub przekładniach tutaj dowiadczalnie możemy wyskalować ich pracę do np. panoramy volatile int aktualna_pozycja_v = 0; //aktualna pozycja w jakiej silnik się znajduje volatile int aktualna_pozycja_h = 0; volatile int docelowa_pozycja_v = 0; //docelowa pozycja na jaką silnik ma się przesunąć volatile int docelowa_pozycja_h = 0; char numbers[2][6]; //w tej tablicy przechowywane są odbierane liczby w postaci tekstowej unsigned short number = 0, length[2] = {0, 0}; //number - zmienna określająca która liczba jest aktualnie odbierana, langth - długości odbieranych liczb int vertical = 0, horizontal = 0; //zmienne przechowujące pozycję w postaci // int po przeanalizowaniu odebranego pakietu short msg = 0; //zmienna ta służy do kontroli ilości otrzymanych danych w jednym // pakiecie (w jednym pakiecie przesyłana jest pozycja dla silnika //poziomego i pionowego więc po sparsowaniu pakietu ta zmienna //powinna wynosić 2, jeśli jest inna to pakiet jest ignorowany) //zmienne dot. odbioru danych volatile unsigned char odb_x; //odebrana liczba X volatile unsigned char odb_flaga =0;//flaga informująca main o odebraniu liczby //===================================== //================ LISTA ============== //===================================== //lista przechowująca kolejne punkty na które mają się przesunąć silniki. każdy element listy ma wskaźnik na kolejny element typedef struct { //struktura (nowy typ danych) przechowująca punkt na jaki mają się przesunąć silniki int pion; int poziom; } point; typedef struct { //sktuktura przechowująca punkt na który ma się przesunąć silnik oraz wskaźnik do następnego elementu listy point punkt; struct node *next; } node; node *first = NULL; //wskaźnik na pierwszy element listy node *last = NULL; //wskaźnik na ostatni element listy point *tmp = NULL; //zmienna tymczasowa przechowująca punkt; point *getPoint() //funkcja zwracająca wskaźnik do point, zwraca ona pierwszy punkt z listy { if(first == NULL) { //jeśli lista jest pusta to zwraca NULL return NULL; } else { //jeśli nie to zwraca pierwszy element return &(first->punkt); } } void addPoint(int pion, int poziom) //funkcja dodająca punkt na koniec listy { if(last == NULL) { //jeśli lista jest pusta to first i last == NULL first = malloc(sizeof(node)); //alokacja pamięci o rozmiarze jednego //elementu (node) i przypisanie do wskaźnika //first first->punkt.pion = pion; //przypisanie wartości do punktu first->punkt.poziom = poziom; first->next = NULL; //lista ma tylko jeden element, więc wskaźnik //next == NULL (wtedy wiemy że to jest koniec listy) last = first; //lista ma tylko jeden element, więc first i last wskazuje na ten sam element, dlatego do last przypisujemy first; } else { last->next = malloc(sizeof( node)); //lista nie była pusta więc pod wskaźnik next w ostatnim elemencie przypisujemy wskaźnik do nowo zaalokowanej pamięci dla nowego elementu last = last->next; //teraz ostatnim elementem jest nowo zaalokowany element więc przypisujemy go do wskaźnika last last->punkt.pion = pion; //przypisanie wartości do punktu last->punkt.poziom = poziom; last->next = NULL; //last wskazuje na ostatni element więc wskaźnik //next w tym elemencie musi wskazywać NULL } } void removePoint() //funkcja usuwająca pierwszy element z listy { if(first != NULL) { //jeśli lista nie jest pusta if(first->next != NULL) { //jeśli lista ma więcej niż jeden element node *next = first->next; //do tymczasowego wskaźnika przypisujemy // wskaźnik na następny element free(first); //zwalniamy pamięć pierwszego elementu first = next; //ten element co był następny jest teraz pierwszym elementem więc przypisujemy go do wskaźnika first } else { //jeśli lista ma tylko jeden element free(first); //zwalnia pamięć tego elementu first = NULL; //przypisuje first i last NULL żeby było wiadomo że lista jest pusta last = NULL; } } } //===================================== //===================================== //===================================== 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 9600 //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 //U W A G A !!! //W ATmega8, aby zapisać do rejestru UCSRC należy ustawiać bit URSEL //zobacz także: http://mikrokontrolery.blogspot.com/2011/04/avr-czyhajace-pulapki.html#avr_pulapka_2 UCSRC = (1<<URSEL) | (1<<UCSZ1) | (1<<UCSZ0); //bitów danych: 8 //bity stopu: 1 //parzystość: brak //włącz nadajnik i odbiornik oraz ich przerwania odbiornika //przerwania nadajnika włączamy w funkcji wyslij_wynik() UCSRB = (1<<TXEN) | (1<<RXEN) | (1<<RXCIE); } //-------------------------------------------------------------- ISR(USART_RXC_vect) { //przerwanie generowane po odebraniu bajtu odb_x = UDR; //zapamiętaj odebraną liczbę odb_flaga = 1; //ustaw flagę odbioru liczby dla main() } //-------------------------------------------------------------- //sekwencja impulsów podawana na silnik krokowy //po wywołaniu odpowiedniej funkcji każdy kolejny case będzie się wykonywał co jedno przerwanie timera (w tym przypadku 2ms) //funkcja dla obrotów w lewo void obroty_l(int seq) { switch(seq) { case 0: MOS1_OFF; MOS2_OFF; MOS3_ON; MOS4_OFF; break; case 1: MOS1_OFF; MOS2_ON; MOS3_ON; MOS4_OFF; break; case 2: MOS1_OFF; MOS2_ON; MOS3_OFF; MOS4_OFF; break; case 3: MOS1_ON; MOS2_ON; MOS3_OFF; MOS4_OFF; break; case 4: MOS1_ON; MOS2_OFF; MOS3_OFF; MOS4_OFF; break; case 5: MOS1_ON; MOS2_OFF; MOS3_OFF; MOS4_ON; break; case 6: MOS1_OFF; MOS2_OFF; MOS3_OFF; MOS4_ON; break; case 7: MOS1_OFF; MOS2_OFF; MOS3_ON; MOS4_ON; break; case 8: MOS1_OFF; MOS2_OFF; MOS3_OFF; MOS4_OFF; break; default: MOS1_OFF; MOS2_OFF; MOS3_OFF; MOS4_OFF; break; } } //funkcja dla obrotów w prawo void obroty_p(int seq) { switch(seq) { case 0: MOS1_OFF; MOS2_OFF; MOS3_ON; MOS4_ON; break; case 1: MOS1_OFF; MOS2_OFF; MOS3_OFF; MOS4_ON; break; case 2: MOS1_ON; MOS2_OFF; MOS3_OFF; MOS4_ON; break; case 3: MOS1_ON; MOS2_OFF; MOS3_OFF; MOS4_OFF; break; case 4: MOS1_ON; MOS2_ON; MOS3_OFF; MOS4_OFF; break; case 5: MOS1_OFF; MOS2_ON; MOS3_OFF; MOS4_OFF; break; case 6: MOS1_OFF; MOS2_ON; MOS3_ON; MOS4_OFF; break; case 7: MOS1_OFF; MOS2_OFF; MOS3_ON; MOS4_OFF; break; case 8: MOS1_OFF; MOS2_OFF; MOS3_OFF; MOS4_OFF; break; default: MOS1_OFF; MOS2_OFF; MOS3_OFF; MOS4_OFF; break; } } //funkcja dla obrotów w dół void obroty_d(int seq) { switch(seq) { case 0: MOS5_OFF; MOS6_OFF; MOS7_ON; MOS8_OFF; break; case 1: MOS5_OFF; MOS6_ON; MOS7_ON; MOS8_OFF; break; case 2: MOS5_OFF; MOS6_ON; MOS7_OFF; MOS8_OFF; break; case 3: MOS5_ON; MOS6_ON; MOS7_OFF; MOS8_OFF; break; case 4: MOS5_ON; MOS6_OFF; MOS7_OFF; MOS8_OFF; break; case 5: MOS5_ON; MOS6_OFF; MOS7_OFF; MOS8_ON; break; case 6: MOS5_OFF; MOS6_OFF; MOS7_OFF; MOS8_ON; break; case 7: MOS5_OFF; MOS6_OFF; MOS7_ON; MOS8_ON; break; case 8: MOS5_OFF; MOS6_OFF; MOS7_OFF; MOS8_OFF; break; default: MOS5_OFF; MOS6_OFF; MOS7_OFF; MOS8_OFF; break; } } //funkcja dla obrotów w górę void obroty_g(int seq) { switch(seq) { case 0: MOS5_OFF; MOS6_OFF; MOS7_ON; MOS8_ON; break; case 1: MOS5_OFF; MOS6_OFF; MOS7_OFF; MOS8_ON; break; case 2: MOS5_ON; MOS6_OFF; MOS7_OFF; MOS8_ON; break; case 3: MOS5_ON; MOS6_OFF; MOS7_OFF; MOS8_OFF; break; case 4: MOS5_ON; MOS6_ON; MOS7_OFF; MOS8_OFF; break; case 5: MOS5_OFF; MOS6_ON; MOS7_OFF; MOS8_OFF; break; case 6: MOS5_OFF; MOS6_ON; MOS7_ON; MOS8_OFF; break; case 7: MOS5_OFF; MOS6_OFF; MOS7_ON; MOS8_OFF; break; case 8: MOS5_OFF; MOS6_OFF; MOS7_OFF; MOS8_OFF; break; default: MOS5_OFF; MOS6_OFF; MOS7_OFF; MOS8_OFF; break; } } int parseInt(volatile char *tab, int lendth) //funkcja zmieniająca tekst na zmienną int. *tab wskaźnik na ten tekst, //lendth - długość tego tekstu { int value = 0; //ustawienie początkowej wartości na 0 int multipler = 1; //mnożnik int i; for(i=lendth-1; i>=0; --i) { //lecimy od końca dopóki i nie będzie == 0 if(tab[i] == '-') { //jeśli analizowany znak == '-' //to znaczy że liczba jest ujemna więc //trzeba zmienić znak w value value *= -1; } else { value += (tab[i]-48) * multipler;//jeśli nie to znaczy że jest to jakaś //liczba więc trzeba od tego znaku odjąć 48 (patrz tablica ascii //liczba 0 ma wartość 48, 1 ma wartość 49 więc jak odejmiemy 48 //to będziemy mieć liczbę taką jaką chcemy), trzeba to pomnożyć //przez mnożnik pozycji } multipler *= 10; //zwiększenie mnożnika } return value; } int main(void) { LCD_Initalize(); //inicjalizacja LCD DDRA |= (1<<MOS1) | (1<<MOS2) | (1<<MOS3) | (1<<MOS4) | (1<<MOS5) | (1<<MOS6) | (1<<MOS7) | (1<<MOS8); // wyjscia DDRB |= (1<<LASER); //na początku wszystkie tranzystory wyłaczone MOS1_OFF; MOS2_OFF; MOS3_OFF; MOS4_OFF; MOS5_OFF; MOS6_OFF; MOS7_OFF; MOS8_OFF; LASER_OFF; TIMSK |= (1<<TOIE0); //przerwanie overflow TCCR0 |= (1<<CS01) | (1<<CS00); //prescaler 64 TCNT0 = LICZNIK; //poczatkowa wartosc licznika usart_inicjuj(); //inicjuj moduł USART (RS-232) sei(); //globalne uruchomienie przerwań while(1) { if(odb_flaga) { //jeśli odebrano jakiś znak switch(odb_x) { //sprawdzenie jaki to znak case 'v': //jeśli to v to number = 0; //jest to liczba 0 w tablicy length[number] = 0; //zerowanie długości tej liczby msg++; //zwiększenie msg bo odebraliśmy liczbę v (tzn //zaczynamy odbierać) break; case 'h': //jeśli to h to number = 1; //jest to liczba 1 w tablicy length[number] = 0; //zerowanie długości tej liczby msg++; //zwiększenie msg bo odebraliśmy liczbę h (tzn //zaczynamy odbierać) break; case ':': //jeśli jest to : number = 0; // length[number] = 0; break; case ';': //jeśli jest to ; to znaczy że zakończyliśmy //odbierać pakiet danych if(msg == 2) { //jeśli w pakiecie były obydwie liczby vertical = parseInt(numbers[0], length[0]); //wyciągnięcie pozycji v horizontal = parseInt(numbers[1], length[1]);//wyciągnięcie pozycji h addPoint(vertical, horizontal);//dodanie punktu z tymi pozycjami //do listy } msg = 0; //wyzerowanie msg break; default: //jeśli żaden z tych znaków to znaczy że jest to //pojedyncza liczba numbers[ number ][ length[ number ] ] = odb_x; //przypisanie tej pojedynczej liczby do tablicy // przechowującej całą liczbę length[ number ]++; //zwiększenie długości tej liczby break; } odb_flaga = 0; //zerowanie odbioru znaku } } } void obrot() //funkcja obracająca silnikami { if(wait_time_v >= OPOZNIENIE_V && aktualna_pozycja_v < docelowa_pozycja_v) { //jeśli silnik czekał już odpowiednią ilość // przerwań i powinien się ruszyć do góry to obroty_g(seq_v); //obrót silnika na podany 'podkrok' seq_v++; if(seq_v > 8) { //jeśli silnik obrócił się o cały cykl to aktualna_pozycja_v++; //zwiększenie jego pozycji o 1 seq_v = 0; //wyzerowanie zmiennej 'podkroku' wait_time_v = 0; //wyzerowanie czasu jaki silnik czeka po każdym kroku } } else if(wait_time_v >= OPOZNIENIE_V && aktualna_pozycja_v > docelowa_pozycja_v) { //jeśli silnik czekał już odpowiednią ilość //przerwań i powinien się ruszyć w dół to //tak samo jak wyżej tylko przesunięcie //silnika w dół obroty_d(seq_v); seq_v++; if(seq_v > 8) { aktualna_pozycja_v--; seq_v = 0; wait_time_v = 0; } } else { //jeśli silnik powinien jeszcze czekać lub jest już na pozycji //docelowej to zwiększenie licznika przechowującego czas jaki //silnik czeka wait_time_v++; } if(wait_time_h >= OPOZNIENIE_H && aktualna_pozycja_h < docelowa_pozycja_h) { //to samo co wyżej tylko dla drugiego silnika obroty_p(seq_h); seq_h++; if(seq_h > 8) { aktualna_pozycja_h++; seq_h = 0; wait_time_h = 0; } } else if(wait_time_h >= OPOZNIENIE_H && aktualna_pozycja_h > docelowa_pozycja_h) { obroty_l(seq_h); seq_h++; if(seq_h > 8) { aktualna_pozycja_h--; seq_h = 0; wait_time_h = 0; } } else { wait_time_h++; } if(aktualna_pozycja_v == docelowa_pozycja_v && aktualna_pozycja_h == docelowa_pozycja_h) { //jeśli obydwa silniki osiągnąły swój cel to: tmp = getPoint(); //pobranie następnego punktu z listy na który mają //przesunąć się silniki if(tmp != NULL) { //jeśli w liście był następny punkt LASER_ON; //włączenie lasera //przeskalowanie pozycji na jaki ma się silnik przemieścić docelowa_pozycja_v = (max_v * tmp->pion) / 100.0f; docelowa_pozycja_h = (max_h * tmp->poziom) / 100.0f; seq_v = 0; //wyzerowanie stanu silnika seq_h = 0; removePoint(); //usunięcie pierwszego elementu z listy } else { LASER_OFF; //jeśli w liście nie było żadnego elementu //to wyłączenie lasera } } } //-----------------------------// ISR(TIMER0_OVF_vect) //funkcja przerwania wykonywana co 2 ms { TCNT0 = LICZNIK; //poczatkowa wartosc licznika obrot(); }Do pobrania: main.c (kopia)
Trzeba pamiętać aby ustawić fusebity na oscylator 8 MHz lub dostosować timer i prescaler.
Jak to zrobić oczywiście znajdziemy na blogu:
Aplikacja na Androida
Aplikację w formacie *.apk można pobrać stąd: LaserControllerV2.apk (kopia)Aplikacja wysyła współrzędne ekranu w postaci np. :v20h90;
: - oznacza początek danych (dodany po to gdyż nie wiadomo czemu czasem gubiło literkę v)
v - oznacza punkt w pionie
h - oznacza punk w poziomie
; - oznacza koniec danych
Maksymalna wysyłana dana to: :v100h100;
więc zawsze można napisać program na PC.
W aplikacji lepiej nie trzymać długo palca na ekranie jednocześnie szybko nim poruszając. Po nagromadzeniu przez uC dużo danych lubi się zawiesić i przestaje reagować.
W aplikacji po wciśnięciu przycisku "Czyść" aplikacja wysyła dane: :v0h0;
oraz czyści ekran, co powoduje ustawienie pozycji początkowej.
Działanie
Na filmiku mam ustawione opóźnienie obydwu silników na 50 (czyli po każdej sekwencji czeka 100 ms) dlatego jest takie lekkie skakanie, przy 0 bardzo ładnie i płynnie wszystko chodzi.Podczas testów zauważyłem, że nie chodzi to dokładnie równo, może to być spowodowane tym że mam różne przekładnie lub małymi błędami w programie.
Można prosić o schemat modułu z bluetooth?
OdpowiedzUsuńTutaj dokumentacja modułu:
Usuńhttp://www.rasmicro.com/Bluetooth/EGBT-045MS-046S%20Bluetooth%20Module%20Manual%20rev%201r0.pdf
i jak są rozmieszczone wyprowadzenia:
http://imagizer.imageshack.us/a/img15/7514/4gre.jpg
Zapomniałem sobie że aplikacja automatycznie łączy się z BT na podstawie jego adresu MAC (w tym przypadku z moim BT). Jak powstanie poprawiona wersja z szukaniem urządzeń to wstawię ją tutaj w komentarzu.
OdpowiedzUsuńDaj znać mailowo albo przez formularz kontaktu i dodamy do artykułu.
Usuńok
UsuńWitam, w jakim programie napisana jest aplikacja LaserControllerV2? Mógłbym prosić o kod źródłowy tej apki?
OdpowiedzUsuńWitam. Projekt jest fajny jednak chciałbym go wykorzystać... zbudować podobny układ sterujący dwoma silnikami osobno. Tak by każdy dało się ustawic na konkretną "szybkość" ilość obrotów oraz aby były ograniczniki krancowe bo całość ma sterować "przesunięciem" elementu o żądaną odległość z żądaną prędkością. Tak by nie wyszło po za zakres. Oraz by można wartościami przesunięcia oraz szybkości dowolnie z poziomu programu sterowac oraz aby zadane kilka programów wykonywał po ich uruchomieniu z oprogramowania. Da się tak zmodyfikować aplikację i uklad (dodać ograniczniki krancowe) by można było tak sterować silnikami krokowymi. Pozdrawiam.
OdpowiedzUsuńTen komentarz został usunięty przez autora.
OdpowiedzUsuń