Elektroda.pl
Elektroda.pl
X
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 6495 4
Renex
  • 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ć:

    
    #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ć:

    
    #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:
    
    #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:
    
    #define DDR(name) \
        (*(volatile uint8_t*)(int)(((name) >> 24) & 0xFF))
    


    zatem makro as_output wygląda tak:
    
    #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:

    
    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.

    
    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:

    
    	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:
    
    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
    About Author
    phanick
    Level 28  
    Offline 
    Has specialization in: energetyk najwyższych napięć
    phanick wrote 2659 posts with rating 2614, helped 61 times. Live in city Warszawa. Been with us since 2007 year.
  • Renex
  • #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ę:
    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 :-).
  • Renex
  • #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"