Przyrostowy enkoder optyczny

Pozostałe układy mikrokontrolerów, układy peryferyjne i inne, nie mieszczące się w powyższych kategoriach.
Awatar użytkownika
gaweł
Geek
Geek
Posty: 1196
Rejestracja: wtorek 24 sty 2017, 22:05
Lokalizacja: Białystok

Przyrostowy enkoder optyczny

Postautor: gaweł » poniedziałek 26 wrz 2022, 21:30

emeta_00.jpg


Przyrostowy enkoder optyczny

Ostatnio wpadło mi w ręce kilka sztuk dosyć ciekawych elementów, które generalnie służą jako elementy pomiarowe w różnego rodzaju układach automatyki. Jego podstawowa funkcja dotyczy pomiaru kąta obrotu, która przy dodatkowym wyposażeniu może mieć znacząco większą funkcjonalność i zastosowanie. Przyszedł mi do głowy pomysł na alternatywne i nietypowe zastosowanie tych elementów, ale napiszę o tym później. Obecnie skupiam uwagę na poznaniu możliwości i idei działania. Za mich czasów studenckich takich zabawek nie było a później jakoś nie było okazji. Teraz pojawiły się możliwości.
Typowy enkoder zbudowany jest z obrotowej tarczy posiadającej nacięte szczeliny lub przezroczystej tarczy z nadrukowanymi przesłonami. Po jednej stronie tarczy umieszczone są elementy wysyłające wiązkę światła po drugiej zaś elementy światłoczułe. Wiązka światła podczas ruchu tarczy impulsowo trafia na elementy światłoczułe, w których generowane są impulsy. By określić kierunek obrotów, enkoder generuje dwa sygnały przesunięte w fazie o 90 stopni. Analizując przychodzące dane można uzyskać wiele informacji. Kolejność wystąpienia zbocz sygnału pozwala określić kierunek obrotów, ilość wygenerowanych impulsów mówi o kącie obrotu a częstotliwość impulsów o prędkości obrotowej.
emeta_01.png

Przyglądając się powyższemu przebiegowi można zauważyć, że traktując wyjście enkodera jako liczbę dwubitową, to przy każdej zmianie jej wartości zachodzi zawsze zmiana tylko na jednej pozycji.
emeta_02.png

To niesie skojarzenie z kodem Graya, w którym cechą charakterystyczną jest to, że kolejne słowa kodowe różnią się tylko stanem jednego bitu.
emeta_03.png

Kręcąc enkoderem w jedną stronę możliwe są następujące zmiany (ruch na osi czasu do przodu):
  • 00 → 01
  • 01 → 11
  • 11 → 10
  • 10 → 00
emeta_04.png

Ruch obrotowy w przeciwną stronę daje (ruch na osi czasu do tyłu):
  • 01 → 00
  • 11 → 01
  • 10 → 11
  • 00 → 10
To sugeruje algorytm działania. Wystarczy pamiętać poprzedni stan i zaobserwować zmiany jakie zaszły dając nowy stan. Program prezentacyjny jest maksymalnie uproszczony by pokazać ideę działania. Mając opanowaną podstawową wiedzę dotyczącą działania, można później temat rozwijać i realizować różnego rodzaju optymalizacje. Niemniej zanim... to wariant uproszczony.
Pierwszym elementem do rozkminy to przyłączenie samego dekodera. Jest to element, który ma w sobie jakieś elementy półprzewodnikowe, więc zabawy z omomiarką niewiele dadzą. Jednak nie jest tak źle, każdy ma w sobie podstawową wiedzę w jaki sposób działa.
emeta_05.jpg

Mamy podane kolory kabelków i przyporządkowaną im funkcję. Nawet opis się zgadza z rzeczywistością.
emeta_06.jpg

Schemat układu badawczego to:
emeta_07.png

Ze względu na fakt, że napięcie zasilające enkoder jest w zakresie 7-35V, nie jest możliwe bezpośrednie przyłączenie jego do procka. Oglądając przebiegi na wyjściach enkodera przy zasilaniu ze źródła o napięciu 12V, amplituda przebiegu cyfrowego również wynosi 12V, więc jest daleka od standardów TTL. Musi wystąpić element pośredniczący, który doprowadzi do pełnej harmonii. W tej roli występuje analogowy komparator napięcia. Na wejścia odwracające podany jest sygnał z enkodera, na wejściach nieodwracających jest ustalony próg przerzutu dla komparatora. W gruncie rzeczy występuje tu duża tolerancja, gdyż sygnał z enkodera ma przebieg cyfrowy (w sensie jest sygnał – nie ma sygnału). Sygnał ten jest obrabiany w procku i wyświetlany na kilkucyfrowym wyświetlaczu. Jego schemat pokazuje rysunek:
emeta_08.png

Jest to wręcz klasyczne rozwiązanie wyświetlacza, który nie wymaga dodatkowych wyjaśnień. Starzy wyjadacze mają właściwą wiedzę i nie jest żadną tajemnicą jego działanie.
Każda teoria wymaga potwierdzenia w wyniku eksperymentu, toteż zbudowałem odpowiednie środowisko badawcze.
emeta_09.jpg

Na oscylku zaobserwowałem przebiegi (te już na wyjściach komparatora).
emeta_10.png

Można sobie pokręcić, w jedną stronę:
emeta_11.jpg

i w drugą stronę:
emeta_12.jpg
Nie masz wymaganych uprawnień, aby zobaczyć pliki załączone do tego posta.

Prawdziwe słowa nie są przyjemne. Przyjemne słowa nie są prawdziwe.
Lao Tse

Awatar użytkownika
Zegar
User
User
Posty: 246
Rejestracja: wtorek 02 lip 2019, 14:42

Re: Przyrostowy enkoder optyczny

Postautor: Zegar » wtorek 27 wrz 2022, 06:30

gaweł pisze:...Za moich czasów studenckich takich zabawek nie było, a później jakoś nie było okazji. Teraz pojawiły się możliwości...


Czekałem na ten artykuł, a czekając czytałem: https://stefankisielewski.wordpress.com ... mistyczny/
W życiu nie ma przypadków. ;)
"If A = success, then the formula is A = X + Y + Z.
X is work. Y is play. Z is keep your mouth shut."
A. Einstein

Awatar użytkownika
ZbeeGin
User
User
Posty: 483
Rejestracja: sobota 08 lip 2017, 17:16
Lokalizacja: Śląsko-Zagłębiowska Metropolia
Kontaktowanie:

Re: Przyrostowy enkoder optyczny

Postautor: ZbeeGin » wtorek 27 wrz 2022, 12:06

gaweł pisze:To sugeruje algorytm działania. Wystarczy pamiętać poprzedni stan i zaobserwować zmiany jakie zaszły dając nowy stan.

Na przykład (STM8, SPL):

Code: Select all

/**
******************************************************************************
* @file : main.c
* @brief : Main program body
******************************************************************************
* @attention
*
******************************************************************************
*/

/* Private define ------------------------------------------------------------*/
#define ENCODER_A_GPIO GPIOC
#define ENCODER_A_PIN GPIO_PIN_3 /* wejscie enkodera A */
#define ENCODER_B_GPIO GPIOD
#define ENCODER_B_PIN GPIO_PIN_1 /* wejscie enkodera B */

#define INVALID (0) /* przejście nieprawidłowe */
#define LEFT (1) /* dozwolone stany */
#define RIGHT (2)

/* Private const -------------------------------------------------------------*/
/**
* @brief Tabela przejść enkodera, bazowana na kodzie Gray-a
*/
const uint8_t EncoderTurnTable[4][4] = {
/* 0x00, 0x01, 0x10, 0x11 */
/* 0x00 */ { INVALID, INVALID, RIGHT, INVALID },
/* 0x01 */ { INVALID, INVALID, INVALID, LEFT },
/* 0x10 */ { LEFT, INVALID, INVALID, INVALID },
/* 0x11 */ { INVALID, RIGHT, INVALID, INVALID }
};

/* Private variables ---------------------------------------------------------*/
uint8_t EncoderLast = 0x00; /* stany przejsciowe wejsc enkodera */
uint8_t EncoderNow = 0x00;

/* Private user code ---------------------------------------------------------*/
/**
* @brief Funkcja zwracająca informacje o kierunku obrotu
* @param previous : stan poprzedni pinów
* @param actual : stan aktualny pinów
* @retval informacja o kierunku
**/
static uint8_t get_movement(uint8_t previous, uint8_t actual)
{
/* zwróć wartość z tabeli o podanych wpółrzędnych */
return (EncoderTurnTable[previous][actual]);
}

/* Main function -------------------------------------------------------------*/
/**
* @brief Petla glowna programu
* @param None
* @retval None
**/
int main(void) {
uint8_t movement;

MCU_Config(); /* konfiguracja procesora */

/* zrównaj stany poczatkowe A, B */
/* --------------------------------------------------------- */
EncoderNow = EncoderLast;

while(1) {

/* dekoduj ruch enkodera */
/* --------------------- */
EncoderNow = (GPIO_ReadInputPin(ENCODER_A_GPIO, ENCODER_A_PIN) == RESET) ? 0x01 : 0x00;
EncoderNow |= (GPIO_ReadInputPin(ENCODER_B_GPIO, ENCODER_B_PIN) == RESET) ? 0x02 : 0x00;
movement = get_movement(EncoderLast, EncoderNow);

if (movement != INVALID) { /* weź zrób coś */ };

EncoderLast = EncoderNow; /* zrownaj stany by wykryc nastepna zmiane */
}
}

Awatar użytkownika
Zegar
User
User
Posty: 246
Rejestracja: wtorek 02 lip 2019, 14:42

Re: Przyrostowy enkoder optyczny

Postautor: Zegar » wtorek 27 wrz 2022, 13:36

Czy ta tablica nie powinna wyglądać tak:

Kod: Zaznacz cały

const uint8_t EncoderTurnTable[4][4] = {
  /*              0x00,    0x01,    0x10,    0x11 */
  /* 0x00 */ { STOP   , LEFT   ,   RIGHT, INVALID },
  /* 0x01 */ { RIGHT  , STOP   , INVALID,    LEFT },
  /* 0x10 */ {    LEFT, INVALID, STOP   , RIGHT   },
  /* 0x11 */ { INVALID,   RIGHT, LEFT   , STOP    }
};
?

Czy źle coś zrozumiałem...
"If A = success, then the formula is A = X + Y + Z.
X is work. Y is play. Z is keep your mouth shut."
A. Einstein

Awatar użytkownika
ZbeeGin
User
User
Posty: 483
Rejestracja: sobota 08 lip 2017, 17:16
Lokalizacja: Śląsko-Zagłębiowska Metropolia
Kontaktowanie:

Re: Przyrostowy enkoder optyczny

Postautor: ZbeeGin » wtorek 27 wrz 2022, 13:45

Zawartość tablicy trzeba dopasować do enkodera - dużą rolę odgrywa tu gdzie jest detencja i czy w ogóle enkoder ją posiada.
Mój przykład pracuje w urządzeniu z enkoderem 15imp na obrót i 30 pozycjami stabilnymi. Gdybym ją wypełnił tak jak Twoja to ruch o jeden przeskok wygenerowałby dwa razy PRAWO lub LEWO.

Awatar użytkownika
gaweł
Geek
Geek
Posty: 1196
Rejestracja: wtorek 24 sty 2017, 22:05
Lokalizacja: Białystok

Re: Przyrostowy enkoder optyczny

Postautor: gaweł » wtorek 27 wrz 2022, 14:21

Zegar pisze:W życiu nie ma przypadków. ;)

Bardzo ciekawy wniosek :idea:

Prawdziwe słowa nie są przyjemne. Przyjemne słowa nie są prawdziwe.
Lao Tse

Awatar użytkownika
gaweł
Geek
Geek
Posty: 1196
Rejestracja: wtorek 24 sty 2017, 22:05
Lokalizacja: Białystok

Re: Przyrostowy enkoder optyczny

Postautor: gaweł » wtorek 27 wrz 2022, 22:21

Każdy ma jakieś osiągnięcia i dorobek. Mój wariant oprogramowania jest następujący (w zakresie interesujących fragmentów):

Kod: Zaznacz cały

int main ( void )
{
  uint8_t CurrentState ;
  uint8_t Increment ;
  uint8_t Decrement ;
  /*-------------------------------------------------------------------------*/
  SoftwareInit ( ) ;
  HardwareInit ( ) ;
  EnvirInit ( ) ;
  LastEncoderState = GetEncoderState ( ) ;
  sei ( ) ;
  for ( ; ; )
  {
    CurrentState = GetEncoderState ( ) ;
    if ( CurrentState != LastEncoderState )
    {
      Increment = 0 ;
      Decrement = 0 ;
      switch ( LastEncoderState )
      {
        case 0 :
          if ( CurrentState == 1 )
            Increment = 1 ;
          if ( CurrentState == 2 )
            Decrement = 1 ;
          break ;
        case 1 :
          if ( CurrentState == 3 )
            Increment = 1 ;
          if ( CurrentState == 0 )
            Decrement = 1 ;
          break ;
        case 2 :
          if ( CurrentState == 0 )
            Increment = 1 ;
          if ( CurrentState == 3 )
            Decrement = 1 ;
          break ;
        case 3 :
          if ( CurrentState == 2 )
            Increment = 1 ;
          if ( CurrentState == 1 )
            Decrement = 1 ;
          break ;
      } /* switch */ ;
      LastEncoderState = CurrentState ;
      if ( Increment )
        PulseCounter += 1 ;
      if ( Decrement )
        PulseCounter -= 1 ;
      IntToStr ( PulseCounter ) ;
    } /* if */ ;
  } /* for */ ;
  return ( 0 ) ;
} /* main */

Istotna funkcja na styku programu i przyłącza enkodera to:

Kod: Zaznacz cały

static uint8_t GetEncoderState ( void )
{
  uint8_t PortData ;
  uint8_t State ;
  /*-------------------------------------------------------------------------*/
  PortData = EncoderPort ;
  State = 0 ;
  if ( PortData & ( 1 << EncoderPinA ) )
    State = 1 ;
  if ( PortData & ( 1 << EncoderPinB ) )
    State += 2 ;
  return ( State ) ;
} /* GetEncoderState */

Oczywiście należy pamiętać, że jest to wersja badawczo-rozwojowo-poznawcza. Jej zadaniem jest zrozumienie istoty zjawiska. Jednak jest już do czego „przyłożyć edytor” i poeksperymentować pod kątem określonych zastosowań i optymalizacji, gdyż rozwiązanie z ciągłym pollingiem stanu enkodera ma małą przydatność praktyczną. Można przykładowo zastosować przerwania typu Pin Change Interrupt, które oferuje ten procek (jest to powód, dla którego został zastosowany taki: metodą drobnych kroczków i kolejnych udoskonaleń można uzyskać rozwiązanie o dużych walorach praktycznych).
Zastosowane enkodery mają dużą precyzję, dają:
  • 50 PPR
    emeta_21.jpg
  • 200 PPR
    emeta_22.jpg
  • 1250 PPR.
    emeta_23.jpg
Nie zauważyłem, by enkoder przeskakiwał prawo ↔ lewo, chociaż uzyskanie obrotu o 1 dla tego ostatniego to jest całkiem spore wyzwanie: trzeba mieć doskonałe ręce.
Dla tropicieli:
emeta.7z
Nie masz wymaganych uprawnień, aby zobaczyć pliki załączone do tego posta.
Ostatnio zmieniony poniedziałek 24 paź 2022, 17:32 przez gaweł, łącznie zmieniany 1 raz.

Prawdziwe słowa nie są przyjemne. Przyjemne słowa nie są prawdziwe.
Lao Tse

Awatar użytkownika
gaweł
Geek
Geek
Posty: 1196
Rejestracja: wtorek 24 sty 2017, 22:05
Lokalizacja: Białystok

Re: Przyrostowy enkoder optyczny

Postautor: gaweł » środa 05 paź 2022, 18:09

Czas na wyższy level

Rozwiązanie z pollingową obsługą enkodera ma istotne walory jedynie „dydaktyczne”. Prosty algorytm obsługi pozwala w miarę bezboleśnie ogarnąć wszystko. Jednak z praktycznego punktu widzenia nie bardzo nadaje się do poważniejszych zastosowań, gdyż nie chodzi o to, by „cała para poszła w gwizdek”.
Zastosowany mikrokontroler ma dosyć ciekawe możliwości sprowadzające się do generowania przerwania w wyniku zaistnienia jakiejkolwiek zmiany na odpowiednich pinach. Przy takim podejściu program zostaje zwolniony z konieczności ciągłego sprawdzania stanu linii danych z enkodera i może zając się istotnymi działaniami. Środowisko pracy jest identyczne jak przy obsłudze pollingowej. Zmianie ulega jedynie sposób interpretacji nadchodzących informacji.
emeta_31.jpg

Program tworzy możliwość generacji przerwania w wyniku jakiejkolwiek zmiany stanu na pinach PB0 (jako PCINT8) i PB1 (jako PCINT9). Wykrycie zmiany na tych wejściach generuje przerwanie określane jako PCINT1_vect. By program reagował na przerwania należy odpowiednio skonfigurować rejestry PCICR oraz PCMSK1. Przerwanie od zegara/licznika 0 jest używane do obsługi wyświetlacza.

Kod: Zaznacz cały

static void HardwareInit ( void )
{
  /*-------------------------------------------------------------------------*/
  TIMSK0 = ( 1<< TOIE0 ) ;
  TCCR0A = 0 ;
  TCCR0B = ( 1 << CS00 ) | ( 1 << CS01 ) ;
  TCCR2A = 0 ;
  PCICR = ( 1 << PCIE1 ) ;
  PCMSK1 = ( 1 << PCINT8 ) | ( 1 << PCINT9 ) ;
} /* HardwareInit */

Obsługę przerwania pokazuje poniższy kawałek kodu:

Kod: Zaznacz cały

SIGNAL ( PCINT1_vect )
{
  uint8_t CurrentState ;
  uint8_t Increment ;
  uint8_t Decrement ;
  /*-------------------------------------------------------------------------*/
  CurrentState = GetEncoderState ( ) ;
  if ( CurrentState != LastEncoderState )
  {
    Increment = 0 ;
    Decrement = 0 ;
    switch ( LastEncoderState )
    {
      case 0 :
        if ( CurrentState == 1 )
          Increment = 1 ;
        if ( CurrentState == 2 )
          Decrement = 1 ;
        break ;
      case 1 :
        if ( CurrentState == 3 )
          Increment = 1 ;
        if ( CurrentState == 0 )
          Decrement = 1 ;
        break ;
      case 2 :
        if ( CurrentState == 0 )
          Increment = 1 ;
        if ( CurrentState == 3 )
          Decrement = 1 ;
        break ;
      case 3 :
        if ( CurrentState == 2 )
          Increment = 1 ;
        if ( CurrentState == 1 )
          Decrement = 1 ;
        break ;
    } /* switch */ ;
    LastEncoderState = CurrentState ;
    if ( Increment )
    {
      PulseCounter += 1 ;
      EncoderFlag = 1 ;
    } /* if */ ;
    if ( Decrement )
    {
      PulseCounter -= 1 ;
      EncoderFlag = 1 ;
    } /* if */ ;
  } /* if */ ;
} /* PCINT1_vect */

W wyniku obsługi przerwania zachodzi zmiana stanu licznika i zostaje ustawiona flaga, że licznik impulsów został zmieniony i obowiązuje nowa wartość.
Nowa wizja obsługi przerwań sprowadza się obecnie do sprawdzenia, czy została ustawiona flaga zmian i ewentualnie nowa wartość zostaje przesłana na wyświetlacz.
emeta_32.jpg

Postać programu prezentacyjnego to:

Kod: Zaznacz cały

int main ( void )
{
  /*-------------------------------------------------------------------------*/
  SoftwareInit ( ) ;
  HardwareInit ( ) ;
  EnvirInit ( ) ;
  LastEncoderState = GetEncoderState ( ) ;
  sei ( ) ;
  for ( ; ; )
  {
    if ( EncoderFlag )
    {
      EncoderFlag = 0 ;
      IntToStr ( PulseCounter ) ;
    } /* if */ ;
  } /* for */ ;
  return ( 0 ) ;
} /* main */

Można powiedzieć, że teraz enkoder „sam się obsługuje” w sposób niewidzialny w algorytmie.


Dla tropicieli:
emeta.7z
Nie masz wymaganych uprawnień, aby zobaczyć pliki załączone do tego posta.

Prawdziwe słowa nie są przyjemne. Przyjemne słowa nie są prawdziwe.
Lao Tse

Awatar użytkownika
gaweł
Geek
Geek
Posty: 1196
Rejestracja: wtorek 24 sty 2017, 22:05
Lokalizacja: Białystok

Re: Przyrostowy enkoder optyczny

Postautor: gaweł » niedziela 30 paź 2022, 00:53

MY_000.JPG


Enkoder od komputerowej myszki

Moją koncepcją było zastosowanie enkodera do stworzenia możliwości wyboru określonej opcji w wieloelementowego menu. Załóżmy, że jakiś program oferuje kilkadziesiąt opcji do wyboru mając jednowierszowy wyświetlacz. Można w takim systemie przewidzieć dwa przyciski o funkcjonalności: daj następny, daj poprzedni. W takiej technologii przejście przez całe menu to uciążliwe zajęcie. Powstał pomysł, by do tego celu użyć właśnie enkodera. Szybki obrót i... zrobiliśmy duży skok w opcjach. Mały obrót, to doprecyzowanie wyboru. Do tego potrzebny byłby jeszcze przycisk: enter oraz escape.
Niby fajna koncepcja, ale to trochę duże rozwiązanie i potrzebne są dodatkowe przyciski. Tu moją uwagę przykuła komputerowa myszka: taka ze scrollem. Kręcąc kółkiem jest identyczna funkcjonalność i dodatkowo można „kliknąć” samym kółkiem. To jest ta sama idea tylko trochę ulepszona.
Tak się złożyło, że w szufladzie walało się kilka różnych myszaków. Po rozebraniu okazało się, że występują dwa gatunki tych gryzoni: z kółkiem stykowym oraz z rozwiązaniem optycznym. No więc do celów badawczych na początek pozyskałem sam enkoder w wersji stykowej.
Ten cóś ma trzy nóżki, więc wstępnie założyłem, że środkowy będzie wspólnym, który jest odpowiednio komutowany na dwa pozostałe. Jakoś tak założyłem, że idea rozwiązania musi być taka sama. Środowisko badawcze nie jest skomplikowane.
MY_001.png

Kręcąc kółkiem, diody zapalają się oraz gasną. Z wyniku obserwacji nasunął się mi wniosek, że tej enkoder działa deczko inaczej od optycznego: zapala jedną diodę, drugą diodę (nie gasząc poprzedniej) i gasi obie. Już chciałem się pogodzić, że ten działa inaczej, gdy pojawił się pomysł by zamienić środkową nóżkę ze skrają. To okazało się strzałem w dziesiątkę: enkoder zaczął działać zgodnie z oczekiwaniem.
Eksperyment, gdzie wspólnym był styk po przeciwnej stronie również dawał przekłamane wyniki, toteż wspólnym jest ten na poniższej ilustracji.
MY_002.JPG

W wyniku zabawy są następujące rezultaty:
MY_003.JPG

MY_004.JPG

MY_005.JPG

MY_006.JPG

Teraz pozostaje dopasować soft do nowego elementu. Wiadomo, że elementy stykowe dają dzwonienie w chwili przełączenia, więc w oprogramowaniu należy wyeliminować to niekorzystne zjawisko. Można rozważyć jakąś sprzętową blokadę dając rozwiązanie typu RC i stosując bramkę z wejściem Schmitta. Też jest to jakieś rozwiązanie.
Nie masz wymaganych uprawnień, aby zobaczyć pliki załączone do tego posta.

Prawdziwe słowa nie są przyjemne. Przyjemne słowa nie są prawdziwe.
Lao Tse

Awatar użytkownika
gaweł
Geek
Geek
Posty: 1196
Rejestracja: wtorek 24 sty 2017, 22:05
Lokalizacja: Białystok

Re: Przyrostowy enkoder optyczny

Postautor: gaweł » poniedziałek 31 paź 2022, 23:18

"Rozpoznanie walką" okazało się prawdziwe. Całkiem przypadkiem natrafiłem na dokumentację takiego enkodera, gdzie widać, że "wspólny pin" wcale nie jest na środku (jak mi się początkowo wydawało).
EC10E.pdf
Nie masz wymaganych uprawnień, aby zobaczyć pliki załączone do tego posta.

Prawdziwe słowa nie są przyjemne. Przyjemne słowa nie są prawdziwe.
Lao Tse


Wróć do „Inne mikroklocki, również peryferyjne”

Kto jest online

Użytkownicy przeglądający to forum: Obecnie na forum nie ma żadnego zarejestrowanego użytkownika i 1 gość