czwartek, 31 marca 2016

Czy w moim AVR jest miejsce na czasownik?

W jakim stanie się znajduje?

Program napisany na mikrokontrolerze wykonuje się w pętli nieskończonej. Podczas jednego przebiegu pętli możemy wyróżnić różne stany, w jakich znajduje się nasza aplikacja. Myśląc o kodziku jako zbiorze stanów, będzie nam łatwiej zarządzać i rozwijać to, co nam głowa do Atmel Studio przyniesie. Weźmy na warsztat jeden z ostatnich kawałków kodu...

czasownik «część mowy obejmująca wyrazy oznaczające czynność lub stan; też: wyraz należący do tej części mowy»  - SJP

Logika biznesowa - Red!

Skomplikujmy nieco ten przykład. Załóżmy, że pewne urządzenie podpięte do naszej Atmegi za pośrednictwem USART powinno zmieniać stan naszych diod. Wiemy jak skonfigurować komunikację. Skąd będziemy wiedzieć, kiedy ma się zmienić stan? Ustalmy protokół: 'A' będzie załączać diodę przy pinie PB0, 'a' - wyłączać. Analogicznie dla diody podpiętej do pinu PB1 'B' ją załączy, a 'b' wyłączy.

Najprostszy pomysł - Green!

Jak mogłaby wyglądać obsługa przerwania USART0_RXC_vect? Na przykład tak:
ISR(USART0_RXC_vect)
{
    char temp;

    temp = UDR0;

    if (temp == 'a')
    {
        PORTB |= (1 << PB0);
    }
    else if (temp == 'A')
    {
        PORTB &= ~(1 << PB0);     
    }
    else if (temp == 'b')
    {
        PORTB |= (1 << PB1);
    }
    else if (temp == 'B')
    {
        PORTB &= ~(1 << PB1);    
    }
}


Działa? Wyśmienicie! Wygląda? Byle jak. Poza tym możemy tu wpaść w pułapkę 'rozwleczenia' przerwania. Jego czas wykonania może stać się niebezpiecznie długie i negatywnie wpłynąć na główny kod programu. Mało tego - traci nam się informacja, co właściwie oznaczały te literki? Pomijam już fakt, że można dać tu switcha.
Zastanówmy się, co powinno wydarzyć się w obsłudze przerwania, na jakiej informacji nam zależy?
Chcemy się dowiedzieć, jaką informację przekazało nam urządzenie podłączone przez USART. Czy pozostałą część kodu interesuje znak, jaki się tu pojawił? Nie. Ważna jest akcja, jaka powinna zostać wykonana po otrzymaniu konkretnego znaku. Przetłumaczmy ją zatem na coś bardziej zrozumiałego.

Refactor!


ISR(USART0_RXC_vect)
{
    char messsage;
    
    messsage = UDR0;
    
    switch (messsage)
    {
        case TurnLed0Off:
            state = Led0Off;
            break;
        case TurnLed0On:
            state = Led0On;
            break;
        case TurnLed1Off:
            state = Led1Off;
            break;
        case TurnLed1On:
            state = Led1On;
            break;
        default:
            state = Idle;
            break;
    }
}

Co się stało?

Nazwaliśmy to, co właściwie wpada do naszego systemu, a oprócz tego wprowadziliśmy zmienną state, do której przypisujemy jakieś wartości. Określają one, w jakim stanie ma się znaleźć mikrokontroler - a dokładniej, jaką akcję powinien wykonać w momencie otrzymania konkretnej wiadomości. Skąd wziąć te wartości? Użyjmy enuma! I umieśćmy gdzieś na początku kodu.

typedef enum 
{ 
    Idle = 0, 
    Led0On, 
    Led0Off, 
    Led1On, 
    Led1Off 
} States;

typedef enum 
{ 
    TurnLed0On = 'A', 
    TurnLed0Off = 'a', 
    TurnLed1On = 'B', 
    TurnLed1Off = 'b' 
} Messages;

Jeszcze tylko zmienna state widoczna w obsłudze przerwania...

volatile States state;

...którą na dzień dobry ustawimy w stan Idle

state = Idle;

I obsługa poszczególnych stanów w kodzie programu:

while (1)
{
    switch (state)
    {
        case Idle:               
            break;
        case Led0Off:
            PORTB |= (1 << PB0);
            state = Idle;
            break;
        case Led0On:
            PORTB &= ~(1 << PB0);
            state = Idle;
            break;
        case Led1Off:
            PORTB |= (1 << PB0);
            state = Idle;
            break;
        case Led1On:
            PORTB &= ~(1 << PB0);
            state = Idle;
            break;
        default:
            break;       
    }    
}

W ten sposób otrzymaliśmy działający kodzik gotowy na implementację kolejnych bardziej lub mniej zawiłych zasad i reguł. Zwróć uwagę, że obsługa danego stanu kończy się przejściem w stan bezczynności (Idle).

Rozpatrzyliśmy dość trywialny przypadek oparty o diody i 4 komendy. Czy to podejście można zastosować do implementacji 'rozmowy' z jakimś modułem zewnętrznym? WiFi? GSM?


Brak komentarzy:

Prześlij komentarz