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.

Atmega8 - Makra do rejestrów - jak wymusić atomowość, niepodzielność

phanick 16 Nov 2015 20:44 1302 2
  • #1
    phanick
    Level 28  
    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 uprości 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 deklaracja 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 chcemu 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ść spowrotem 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);
  • Helpful post
    #2
    ASMnauka_
    Level 15  
    Witam
    phanick wrote:
    macie jakiś pomysł jak zmusić kompilator aby kompilował te instrukcje z wykorzystaniem rozkazów sbi/cbi ?

    Niestety rozkazy CBI i SBI działają tylko na stałych.
    Ja również muszę robić to na zmiennych.