Elektroda.pl
Elektroda.pl
X

Search our partners

Find the latest content on electronic components. Datasheets.com
Elektroda.pl
Please add exception to AdBlock for elektroda.pl.
If you watch the ads, you support portal and users.

Diagnozer joysticków (np. do Pegasusa) na Atmega 8

phanick 16 Nov 2015 20:31 6345 4
  • Diagnozer joysticków (np. do Pegasusa) na Atmega 8
    Diagnozer joysticków (np. do Pegasusa) na Atmega 8

    Projekt
    Przyszedł wreszcie czas naprawić posiadane przeze mnie joysticki do Pegasusa. Zwykle uszkodzeniu (urwaniu) uległ kabel lub taki niesprawdzony/niesprawny joystick został zakupiony bez znajomości jego przeszłości. Jeśli kable się urwały, to mamy mały problem, bo płytka PCB joysticka składa się z jednego glutowego układu scalonego (rejestr przesuwny + generator do przycisków TURBO) i ciężko stwierdzić, gdzie co podłączyć.

    Diagnozer joysticków (np. do Pegasusa) na Atmega 8

    A podłączanie jak leci aż za którymś razem zadziała może się zakończyć tragicznie:
    - zewrzemy dwa wyjścia,
    - podłączymy odwrotnie VCC i GND.

    Protokół komunikacyjny z joystickiem Pegasusa
    Kontrolery mają czteropozycyjny krzyżak (d-pad), przycisk select, start, A, B, czasem Turbo A, Turbo B. Złącze joysticka składa się z 5 pinów:
    - GND: masa,
    - VCC: zasilanie +5V,
    - STROBE: strob zapisu – konsola generując opadające zbocze rozpoczyna odczytywanie stanu klawisy
    - CLK: sygnał zegarowy – kolejne zbocza narastające generowane przez konsolę powodują wystawienie przez joystick na złącze DATA stanu kolejnych przycisków w kolejności: A,B, SELECT, START, GÓRA, DÓŁ, LEWO, PRAWO
    - DATA: wystawiane przez konsolę stany przycisków (0 – przycisk wciśnięty, 1 – przycisk zwolniony, otwarty kolektor)

    Założenia
    Postanowiłem więc stworzyć mini-diagnozer do joysticków, który w zamyśle ma działać tak:
    1. podłączamy do niego joystick od Pegasusa,
    2. w diagnozerze wybieramy, jaki klawisz joysticka testować,
    2. na joysticku trzymamy wciśnięty wcześniej ustawiony klawisz (np. START),
    3. wciskamy przycisk na diagnozerze,
    4. diagnozer testuje wszystkie możliwe kombinacje połączenia przewodów i:
    - jeśli znajdzie działającą kombinacje i jest ona poprawną – wyświetla komunikat OK,
    - jeśli znajdzie działającą kombinacje, ale nie jest ona poprawna – wyświetla, które połączenia są zamienione, np. G9 – na 9 pinie jest MASA, S5 – na 5 pinie jest sygnał Strobe, c3 – na 3 pinie jest sygnał CLK;
    - jeśli żadna kombinacja jest niepoprawna – wyświetla komunikat: ER (error).

    Wystarczy więc – jeśli kabelki w joysticku się oderwały – przylutować je, jak leci, a następnie podłączyć do diagnozera. Wszystkie sygnały w diagnozerze mają w szeregu rezystor 180 ograniczający prąd, więc podczas testowania nie ma zwarć i joystick nie ulegnie uszkodzeniu. Procesor ze swoich wyjść zasila (VCC+gnd) badany joystick. Diagnozer ma możliwość wysterowania dowolnych z 9 pinów DB9, więc w przyszłości można dodać implementację testowania innych rzeczy (np. myszki COM, klawiatury – z przejściówką, itp).

    Efekt
    Dzięki urządzeniu udało się uratować 4 joysticki, a 2 zostały wykryte jako uszkodzone – pewnie słusznie, bo na teście diod w mierniku jeden z pinów joysticka w ogóle nie ma połączenia do żadnego z pozostałych.
    Diagnozer joysticków (np. do Pegasusa) na Atmega 8

    Ciekawostki
    Chciałem, aby PCB joysticka było jak najmniejsze – stąd na PCB tylko:
    - jeden przycisk do rozpoczęcia testu,
    - wyświetlacz LED 7 segmentowy w minimalistycznym połączeniu – jedynie 2 rezystory ograniczające prąd dla każdej z cyfr, wadą jest oczywiście jaśniejsze świecenie cyfry gdy mniej segmentów jest zapalonych (można to programowo zminimalizować przez PWM)
    - AVR Atmega 8 taktowana wewnętrznym kwarcem 8 MHz
    - całość zasilana z portu programatora ISP
    - bardzo chciałem wszystko zmieścić na jednej warstwie :)

    Diagnozer joysticków (np. do Pegasusa) na Atmega 8
    Diagnozer joysticków (np. do Pegasusa) na Atmega 8
    Diagnozer joysticków (np. do Pegasusa) na Atmega 8
    Diagnozer joysticków (np. do Pegasusa) na Atmega 8
    Diagnozer joysticków (np. do Pegasusa) na Atmega 8

    Diagnozer joysticków (np. do Pegasusa) na Atmega 8 Diagnozer joysticków (np. do Pegasusa) na Atmega 8



    Kłopoty
    Od dłuższego czasu napisałem sobie specjalne makra pod AVR definiujące wszystkie porty, aby np. zamiast pisać:

    Code:

    #define BTN1_DDR  DDRC
    #define BTN1_PORT PORTC
    #define BTN1_PIN  PINC
    #define BTN1_P    PC0

    #define BTN2_DDR  DDRB
    #define BTN2_PORT PORTB
    #define BTN2_PIN  PINB
    #define BTN2_P    PB1

    #define BTN3_DDR  DDRD
    #define BTN3_PORT PORTD
    #define BTN3_PIN  PIND
    #define BTN3_P    PD7



    ...

    cbi(BTN1_DDR,  BTN1_P)  //przycisk BTN1 jako wejście
    sbi(BTN1_PORT, BTN1_P); //włącz rezystor obciągający na BTN1
    cbi(BTN2_DDR,  BTN2_P)  //przycisk BTN2 jako wejście
    sbi(BTN2_PORT, BTN2_P); //włącz rezystor obciągający na BTN2
    cbi(BTN3_DDR,  BTN3_P)  //przycisk BTN3 jako wejście
    sbi(BTN3_PORT, BTN3_P); //włącz rezystor obciągający na BTN3

    while (get_bit(BTN1_PIN, BTN1_P)); //czekaj aż przycisk BTN1 nie zostanie wciśnięty

    móc napisać:

    Code:

    #define BTN1 C0
    #define BTN2 B1
    #define BTN3 D7

    as_input(BTN1, PULLUP_ON);
    as_input(BTN2, PULLUP_ON);
    as_input(BTN3, PULLUP_ON);

    while (!in(BTN1));


    Makra te definiują w stałej np. A0 wszystkie informacje o tym porcie, tj:
    - adres rejestru DDR dla A0 (DDR),
    - adres rejestru PORT dla A0 (PORTA),
    - adres rejestru PIN dla A0 (PINA),
    - numer portu dla A0 (0).
    Każda z tych zmiennych jest 8-bitowa, potrzebujemy więc 32 bity:
    Code:

    #define A0 (PA0 | CAST1(&PORTA) << 8 | CAST1(&PINA) << 16 | CAST1(&DDRA) << 24)


    i teraz, jeśli chcemy się dowiedzieć np. jaki jest rejestr DDR dla końcówki A0, po prostu wyłuskujemy to, np. makrem:
    Code:

    #define DDR(name) \
        (*(volatile uint8_t*)(int)(((name) >> 24) & 0xFF))


    zatem makro as_output wygląda tak:
    Code:

    #define as_output(pinname) \
        sbi(DDR(pinname), P(pinname))


    Ponadto sprytny kompilator C widząc, że wszystko dzieje się na stałych, zoptymalizuje kod tak, że wywołanie tych funkcji ograniczy się do dwóch rozkazów assemblera, np:

    Code:

    00000024:   98A0        CBI       0x14,0         Clear bit in I/O register
    +00000025:   9AA8        SBI       0x15,0         Set bit in I/O register
    18:          as_input(BTN2, PULLUP_ON);

    czyli w zasadzie wyjdzie tak samo, jakbyśmy napisali.

    Code:

    cbi ddrc, 0
    sbi portc, 0


    Dużo czytelniej, a tak samo szybko. Często jednak kusi nas do tego stopnia, że zamiast przekazywać takie końcówki wprost do funkcji, chcielibyśmy stworzyć ich tablicę, np:

    Code:

       const uint32_t btns[] = {BTN1, BTN2, BTN3};
       as_input(btns[0], PULLUP_ON);
       as_input(btns[1], PULLUP_ON);
       as_input(btns[2], PULLUP_ON);

       
    .
    Słowem klucz jest tutaj const – jeśli dodamy je przed deklaracją tablicy, kompilator uzna, że wartości tablicy nie będą się zmieniać, więc będzie mógł od razu stwierdzić, że powyższe wywołanie będzie równoznaczne temu z przykładu wyżej. Często jednak chcemy np. iterować po takiej tablicy lub wręcz zmieniać jej wartość w trakcie programu – wtedy nie możemy dać const. I tutaj pojawia się mój kłopot – kompilator wtedy nie może rozwinąć takiego makra do jednej instrukcji assemblera, lecz musi te wszystkie przesunięcia bitowe zamienić na rozkazy procesora. I tu pojawia się problem – kompilator zamienia to w taki sposób, że już instrukcja ustawienia bitu nie jest tożsama z instrukcją sbi/cbi z Assemblera. Kompilator najpierw odczytuje do rejestru wartość np. zmiennej DDRC, portem ustawia w niej odpowiedni bit, a potem tak zmodyfikowaną wartość z powrotem zapisuje do rejestru DDR. Problem jest jednak taki, że gdy działamy na przerwaniach, to takie ciągi instrukcji mogą być przerwane przerwaniami i w rzeczywistości dostajemy niezłą sieczkę na portach – sam tego doświadczyłem, bo gdy program testował joystick, to wartość na wyświetlaczu siedmiosegmentowym, sterowanym w przerwaniu zmieniała się jak szalona. W obecnej chwili rozwiązałem to tak, że przed każda z instrukcji np. as_input(...), as_output(...), out(.., 0), out(..., 1), in(...) wyłączam przerwania, a po – włączam. Jest to jednak nieekonomiczne, macie jakiś pomysł jak zmusić kompilator, aby kompilował te instrukcje z wykorzystaniem rozkazów sbi/cbi ?

    W obecnej chwili – np. gdy przed deklaracją tablicy wręcz dodam „volatile”, kompilator kompiluje mi to do:
    Code:

    0000002F:   93DF        PUSH      R29            Push register on stack
    +00000030:   93CF        PUSH      R28            Push register on stack
    +00000031:   B7CD        IN        R28,0x3D       In from I/O location
    +00000032:   B7DE        IN        R29,0x3E       In from I/O location
    +00000033:   972C        SBIW      R28,0x0C       Subtract immediate from word
    +00000034:   B60F        IN        R0,0x3F        In from I/O location
    +00000035:   94F8        CLI                      Global Interrupt Disable
    +00000036:   BFDE        OUT       0x3E,R29       Out to I/O location
    +00000037:   BE0F        OUT       0x3F,R0        Out to I/O location
    +00000038:   BFCD        OUT       0x3D,R28       Out to I/O location
    17:          volatile uint32_t btns[] = {BTN1, BTN2, BTN3};
    +00000039:   01DE        MOVW      R26,R28        Copy register pair
    +0000003A:   9611        ADIW      R26,0x01       Add immediate to word
    +0000003B:   E6E0        LDI       R30,0x60       Load immediate
    +0000003C:   E0F0        LDI       R31,0x00       Load immediate
    +0000003D:   E08C        LDI       R24,0x0C       Load immediate
    +0000003E:   9001        LD        R0,Z+          Load indirect and postincrement
    +0000003F:   920D        ST        X+,R0          Store indirect and postincrement
    +00000040:   5081        SUBI      R24,0x01       Subtract immediate
    +00000041:   F7E1        BRNE      PC-0x03        Branch if not equal
    19:             as_input(btns[0], PULLUP_ON);

    Cool? Ranking DIY
    Can you write similar article? Send message to me and you will get SD card 64GB.
    About Author
    phanick
    Level 28  
    Offline 
    phanick wrote 2446 posts with rating 2533, helped 56 times. Live in city Warszawa. Been with us since 2007 year.
  • #2
    Damian_Max
    Level 15  
    Fajna konstrukcja, choć uważam, że z minionej epoki.
    (teraz załóżmy, że cofamy się z 10/18 lat)
    Mógłbyś przejść z takim urządzeniem na bazar używanego sprzętu i wybrać tylko działające joysticki i je sprzedawać xD, lub prowadzić szybką naprawę.

    Bardzo podobają mi się listingi binarnego kodu wraz z opisem, sam to robiłeś?

    W kontekście macie jakiś pomysł.
    1. Myślę że nie do końca rozumiem problem, ale mógłbyś dodać flagę:
    Code:
    volatile BOOL mutex = false;
    , którą ustawiasz przed operacją na tablicy a zerujessz po niej. A sprawdzać ją będziesz w przerwaniu.
    2. W C da się zrobić wstawki assembler-owe, a skoro widzisz jak to wygląda to możesz tego użyć.
    3. Jest też jakaś flaga którą możesz dodatkowo użyć przy kompilacji, która rozwija for-y.

    Podoba mi się że nie kopiujesz kodu tylko używasz makr -- choć zamiast makr polecam funkcje inline.
    Preprocesor rządzi :-).
  • #3
    RomanWorkshop
    Level 12  
    phanick wrote:
    cbi(BTN1_DDR, BTN1_P) //przycisk BTN1 jako wejście
    sbi(BTN1_PORT, BTN1_P); //włącz rezystor obciągający na BTN1


    Hmmm... Rezystor obciągający... Ale, że on niby ten, tego... To są takie rezystory?!
  • #4
    japko1024
    Level 18  
    phanick wrote:
    - AVR Atmega 8 taktowana wewnętrznym kwarcem 8 MHz
    Atmega nie ma wewnętrznego kwarcu, tylko rezonator RC, będący częścią układu scalonego.
  • #5
    Freddy
    Level 43  
    japko1024 wrote:
    , tylko rezonator RC,
    Nie rezonator RC, lecz oscylator RC. " Internal Calibrated RC Oscillator"