logo elektroda
logo elektroda
X
logo elektroda
REKLAMA
REKLAMA
Adblock/uBlockOrigin/AdGuard mogą powodować znikanie niektórych postów z powodu nowej reguły.

[Atgega8][C] Obsługa impulsatora (enkodera) obrotowego

matys4877 19 Sty 2010 15:29 9763 17
REKLAMA
  • #1 7558630
    matys4877
    Poziom 12  
    Kupiłem impulsatory na allegro no name i napisałem program do ich obsługi.
    Problem w tym, że działa w 90% więc da się używać impulsatora ale czasami jest to niewygodne.
    Jeżeli ktoś wie jak poprawić, mój program byłbym wdzięczny za informację.
    Poniżej mój program:

    
    
    
      //--------------------------------------------------------------------//
     //---------------------- impulsator -----------------------------------//
    //--------------------------------------------------------------------//
    
    
    volatile unsigned char enc;
    
    #define encoder_a (!((PINC&_BV(2))>>2))
    #define encoder_b (!((PINC&_BV(1))>>1))
    
    void impulsator(void)
    {
    static unsigned char stara_a,stara_b,tab[3],xd;
    
    
    
    if((stara_a!=encoder_a)||(stara_b!=encoder_b))
    {
    _delay_ms(5);   //redukcja drgań styków
    
    	if((stara_a!=encoder_a)||(stara_b!=encoder_b))
    	{
    
    		if((encoder_a==1)&&(stara_a==0))	//rosnace zbocze
    		{
    		tab[2]=tab[1];
    		tab[1]=1;
    		stara_a=1;	
    		}
    
    
    		if((encoder_a==0)&&(stara_a==1))	//opadajace zbocze
    		{
    		tab[2]=tab[1];
    		tab[1]=3;
    		stara_a=0;
    		}
    	
    
    		if((encoder_b==1)&&(stara_b==0))	//rosnace zbocze
    		{
    		tab[2]=tab[1];
    		tab[1]=5;
    		stara_b=1;	
    		}
    
    
    		if((encoder_b==0)&&(stara_b==1))	//opadajace zbocze
    		{
    		tab[2]=tab[1];
    		tab[1]=2;
    		stara_b=0;
    		}
    	
    	xd=(tab[2]-tab[1]);
    	
    	
    
    	if((xd==252)||(xd==1))
    	{
    	enc++;
    	tab[2]=0;
    	tab[1]=0;
    	}
    
    
    	if((xd==4)||(xd==255))
    	{
    	enc--;
    	tab[2]=0;
    	tab[1]=0;
    	}
    
    
    
    
    	}
    
    
    }
    }
    
  • REKLAMA
  • #2 7559285
    Kabuto15
    Poziom 19  
    Ogólnie ciężko rozkminić Twój kod (przynajmniej dla mnie) - nazwy zmiennych, jakieś dziwne odejmowanie (tab[2]-tab[1]).
    Jeśli chodzi o zrobienie w 100% działającej obsługi enkodera to robi się to tak, że uznaje się jeden z sygnałów jako referencyjny (np. sygnał A). Jako decydujące wybiera się jedno ze zboczy (np. rosnące). Następnie tylko na tym zboczu sprawdza się stan logiczny drugiego kanału. W zależności od poziomu na nim decyduje się o kierunku: CW lub CCW.
    [Atgega8][C] Obsługa impulsatora (enkodera) obrotowego
  • REKLAMA
  • #3 7559842
    matys4877
    Poziom 12  
    u mnie jest tak że sprawdza oba zbocza na jednym i na drugim kanale i wpisuje je do tablicy która przechowuje 2 ostatnie zbocza. Zrobienie kroku cw lub ccw następuje wtedy gdy w tablicy są dwa zbocza rosnące lub malejące a ich kolejność decyduje o kierunku obrotu. Te impulsatory powinny dawać w stanie równowagi 11 lub 00 niestety u mnie w 20% przypadków tak nie jest, bo już w stanie równowagi nieraz jeden kanał przeskakuje do następnego kroku. Żeby się znowu pojawiły np 11 trzeba trochę cofnąć pokrętło. W stanie równowagi mały ruch (dotknięcie pokrętła) nieraz powoduje zmianę stanu na kanale co by chyba generowało fałszywe odczyt zbocza i w efekcie przeskok. Chyba te impulsatory były zbyt tanie. Wiecie gdzie można kupić jakiś w miarę tani i dobry impulsator? Zależy mi na tym by był z przyciskiem. Np ten z avt byłby dobry?
  • REKLAMA
  • #4 7561154
    Pawel1812
    Poziom 26  
    Posiadam oba te enkodery. Oczywiście ten z avt jest nieporównywalnie lepszy. W moim z allegro bywa że po puszczeniu jeden ze styków jest ciągle zwarty...
    Jednak z programu wnioskuje że enkodera nie obsługujesz w przerwaniach co powoduje dziwne zachowanie np. momentami kręcisz w górę a wartość na LCD spada w dół. Tylko przerwania gwarantują reakcję na każde zbocze.
  • #5 7561235
    Limonit
    Poziom 13  
    Ja zrobiłem tak, na przerwaniu. Przerwanie wyzwalane kazdym zboczem, impulsator przykładowo na pinach 3 i 4 portu D.
    
    ISR (INT1_vect)
    {
    _delay_ms(1);
    u08 tmp = PIND;
    if(bit_is_set(tmp,3))
    {
    	if(bit_is_set(tmp,4))
    	tact = LEFT;
    	if(bit_is_clear(tmp,4))
    	tact = RIGHT;
    }
    if(bit_is_clear(tmp,3))
    {
    	if(bit_is_set(tmp,4))
    	tact = RIGHT;
    	if(bit_is_clear(tmp,4))
    	tact = LEFT;
    }
    }
    

    Działa w 100%. Zmienna tact przechowuje ostatni ruch impulsatora. Oczywiście po odczytaniu nalezy ja ustawic na jakąś neutralną wartość. Jest to tak proste, że nie ma własciwie o czym pisac.
  • REKLAMA
  • #6 7561296
    Konto nie istnieje
    Poziom 1  
  • #7 7561355
    Pawel1812
    Poziom 26  
    Zastanawiam się tylko czy użycie funkcji _delay_ms(1); w przerwaniu jest dobrym pomysłem skoro przerwanie powinno być wykonywane jak najkrócej. Funkcję chętnie przetestuję w swoim programie.
  • #8 7561384
    Konto nie istnieje
    Poziom 1  
  • #9 7561615
    Limonit
    Poziom 13  
    Więc ta 1ms jednak chroni. Bez tego odczekania funckja działa bardzo zawodnie. Po wykryciu przerwania wystepują drgania styków i pierwszy test linii która wykryła przerwanie moze dać błędny wynik. (linii 3).

    natomiast zapomniałem dopisać jednej ważnej rzeczy. Na samym końcu funkcji przydaje sie coś w stylu

    Czyli wywalamy przerwanie które pojawi się w czasie tej 1ms jako zakłócenie.
    Odczekanie ogólnie faktycznie wydłuża obsługe przerwania i zmniejsza maksymalna mozliwą szybkość obrotu, ale dobre kilkadziesiąt impulsów na sekundę to wykrywa, co IMHO jest wystarczające.

    Zachęcam do eksperymentów.
  • #10 7561644
    Konto nie istnieje
    Poziom 1  
  • #11 7565984
    m.bartczak
    Poziom 16  
    Ja osobiście użyłem kondensatorów i następującego kodu:

    
    struct Encoder {
    	u08 last; // Last read of encoder
    	u08 current; // Current state of encoder
    	int value;
    };
    
    volatile struct Encoder encoder;
    
    void encoderCheck(void)
    {
    	u08 v = 0;
    	if(ENCODER_PIN0_READ & ENCODER_PIN0)
    	{
    		v = v + 1;
    	}
    	if(ENCODER_PIN1_READ & ENCODER_PIN1)
    	{
    		v = v + 2;
    	}
    	encoder.current = v;
    	switch(encoder.last)
    	{
    		case 0:
    		{
    			if(v==1)
    			{
    				encoder.value--;
    			}
    			if(v==2)
    			{
    				encoder.value++;
    			}
    		}
    		break;
    		case 1:
    		{
    			if(v==3)
    			{
    				encoder.value--;
    			}
    			if(v==0)
    			{
    				encoder.value++;
    			}
    		}
    		case 2:
    		{
    			if(v==0)
    			{
    				encoder.value--;
    			}
    			if(v==3)
    			{
    				encoder.value++;
    			}
    		}
    		break;
    		case 3:
    		{
    			if(v==2)
    			{
    				encoder.value--;
    			}
    			if(v==1)
    			{
    				encoder.value++;
    			}
    		}
    		break;
    	}
    	encoder.last = encoder.current;
    }
    


    Kod uruchamiam na przerwaniu timera (podpięte poprzez bibliotekę procyon)

    
    void printByteToLcd(u08 data)
    {
    	LCD_WriteData(data);
    }
    
    int main(void) {
    	rprintfInit(printByteToLcd);
    	LCD_Initalize();
    	LCD_Clear();
    	timerInit();
    	encoder.last = 0;
    	encoder.current = 0;
    	timer0SetPrescaler(TIMER_CLK_DIV256);
    	timerAttach(TIMER0OVERFLOW_INT, encoderCheck);
    
    	ENCODER_PIN0_DIR &= ~(ENCODER_PIN0);
    	ENCODER_PIN1_DIR &= ~(ENCODER_PIN1);
    	// Enable pull-ups
    	ENCODER_PIN0_PORT |= ENCODER_PIN0;
    	ENCODER_PIN1_PORT |= ENCODER_PIN1;
    
    	while(1)
    	{
    		LCD_GoTo(0,1);
    		rprintfNum(10, 16, FALSE, ' ', encoder.value/3);
    	}
    }
    
  • #12 7573812
    matys4877
    Poziom 12  
    Z ostatniego kodu wywnioskowałem że twój potencjometr w stanach stabilnych daje
    00 10 11 01 itd. Czy mam racje?
  • #13 7585583
    m.bartczak
    Poziom 16  
    Dokładnie, dostaję z niego ciąg wartości 023102310231 itd - zauważ że jest to zgodnie z rysunkiem z postu kol. Kabuto15 dla przypadku Forward, starszy bit to sygnał B, młodszy to sygnał A.
  • #14 7589258
    matys4877
    Poziom 12  
    U mnie jest tak ze pojedyncze kliknięcie to zmiana z 00 na 11 lub z 11 na 00. Więc o kierunku obrotu decyduje który kanał pierwszy zmienił swój stan. Twój kod by za każdym kliknięciem jakby się o 2 przesunął.
  • #15 7591601
    m.bartczak
    Poziom 16  
    Tak z ciekawości - sprawdzałeś pół kliknięcia? ;)

    Wszystko zależy jak producent ustawił klikacza w enkoderze, jeśli na pełne cykle to wystarczy podzielić wartość na 2.
  • #16 7599468
    matys4877
    Poziom 12  
    Udało mi się zrobić ten program tak aby działał w 100%. Ciągle opiera się na detekcji i przechowywaniu 2 ostatnich zbocz. Każde zbocze ma przypisany numer i tylko w momencie gdy zajda 2 rosnące lub 2 malejące zbocza jest krok a od ich kolejności zależy czy w prawo czy w lewo. W moim impulsatorze jest tak że nawet gdy nie kręcę tylko lekko przechylam i jeszcze nie następuje klikniecie to tez jeden kanał zmienia stan a gdy puszcze to wraca do poprzedniej lub nie chociaż ciągle jest w tym samym kroku. Więc program oparty na przerwaniach z jednego kanału by co chwile jakieś złe kroki dawał.
    [Atgega8][C] Obsługa impulsatora (enkodera) obrotowego
    Tutaj prawidłowa detekcja która będzie wykrywała krok tylko w momencie jego nastąpienia.
    [Atgega8][C] Obsługa impulsatora (enkodera) obrotowego
    gotowy program
    
    #define encoder_a ((PINC&_BV(2))>>2)
    #define encoder_b ((PINC&_BV(1))>>1)
    
    volatile unsigned char enc;
    
    void impulsator(void)
    {
    static unsigned char poprzednia_a,poprzednia_b,poprzednia_c,poprzednia_d,tab[3],roznica;
    
    
    
    if((poprzednia_a!=encoder_a)||(poprzednia_b!=encoder_b))	//jezeli zmini się stan na jakimkolwiek wejsciu
    {
    poprzednia_c=encoder_a;			//przechowuja stany kanałow z czasu detekcji zmiany 
    poprzednia_d=encoder_b;
    
    _delay_ms(1);
    
    	if((poprzednia_a!=encoder_a)||(poprzednia_b!=encoder_b))
    	{
    
    		if((poprzednia_c==1)&&(poprzednia_a==0))	//rosnace zbocze na kanale A 
    		{						//numer zbocza 1
    		tab[2]=tab[1];					//przepisanie numeru wczesniejszego zbocza do tab[2]  
    		tab[1]=1;					//wpisanie zbocza ktore nastapilo teraz do tab[1]
    		poprzednia_a=1;					//stan kanalu A do zmiennej poprzednia_a
    		}
    
    
    		if((poprzednia_c==0)&&(poprzednia_a==1))	//opadajace zbocze na kanale A
    		{						//numer zbocza 3
    		tab[2]=tab[1];
    		tab[1]=3;
    		poprzednia_a=0;
    		}
    	
    
    		if((poprzednia_d==1)&&(poprzednia_b==0))	//rosnace zbocze na kanale B
    		{						//numer zbocza 5
    		tab[2]=tab[1];
    		tab[1]=5;
    		poprzednia_b=1;	
    		}
    
    
    		if((poprzednia_d==0)&&(poprzednia_b==1))	//opadajace zbocze na kanale B
    		{						//numer zbocza 2
    		tab[2]=tab[1];
    		tab[1]=2;
    		poprzednia_b=0;
    		}
    
    
    
    
    	
    		roznica=(tab[2]-tab[1]);
    	
    		if((roznica==252)||(roznica==1))	//jezeli 2 rosnace zbocza lub 2 malejace zbocza kierunek w prawo to zwieksz enc
    		{
    		enc++;
    		tab[2]=150;				//wpisz do tablic wartosci które nie zrobia zadnym zboczem detekci obrotu
    		tab[1]=150;			}
    
    
    		if((roznica==4)||(roznica==255))	//to samo tylko kieruniek w lewo
    		{
    		enc--;
    		tab[2]=150;
    		tab[1]=150;
    		}
    
    
    
    
    	}
    
    
    }
    }
    

    funkcje impulsator wystarczy wywoływać w programie co jakiś czas i bez problemu wykrywa wszystkie zmiany nawet jak się dość szybko kręci.
  • #17 7757661
    Karol966
    Poziom 31  
    Witam. Ten kod mi się najbardziej podoba ;)
    
    ISR (INT1_vect) 
    { 
    _delay_ms(1); 
    u08 tmp = PIND; 
    if(bit_is_set(tmp,3)) 
    { 
       if(bit_is_set(tmp,4)) 
           tact = LEFT; 
       esle
           tact = RIGHT; 
    } 
    else
    { 
       if(bit_is_set(tmp,4)) 
           tact = RIGHT; 
       else
           tact = LEFT; 
    } 
    }
    


    Niestety dopiero zaczynam programować AVR'y w C. W Bascomie już je programowałem. Pisać w C potrafię na tyle, by poradzić sobie spokojnie z MCS51.

    Co do tego kodu to mam takie pytanie: Co to jest?:
    u08 tmp = PIND;
    u08 tmp to jakaś zmienna? Jak to się stosuje? jaką bibliotekę należy dołączyć? To jakieś makro? Dodatkowo zmienna tact to jakaś struktura? Najwięcej wyjaśnił by mi kompletny kod programu wraz z tą funkcją obsługi impulsatora( dokładniej obsługą przerwania).Tu też widziałem, że robi się to nieco inaczej wiec nie wiem jak powinno to wyglądać http://avr.elektroda.eu/?q=node/25 Chciał bym napisać obsługę impulsatora dla ATiny13.
  • #18 7760886
    kacper_80
    Poziom 11  
    u08 tmp = PIND;
    u08 tmp to zmienna lokalna typu unsigned char (uint8_t) u08 zapewne zostało wcześniej zdefiniowane i dołączone w pliku z definicjami zmiennych. Do zmiennej tmp zapisywana jest wartość z portu D, a następnie sprawdzany stan jednego bitu w celu określenia kierunku.
REKLAMA