Elektroda.pl
Elektroda.pl
X
Proszę, dodaj wyjątek www.elektroda.pl do Adblock.
Dzięki temu, że oglądasz reklamy, wspierasz portal i użytkowników.

[STM32F1][C] - Efektywna komunikacja komendami AT

Co_pat 08 Maj 2014 21:09 1659 13
  • #1 08 Maj 2014 21:09
    Co_pat
    Poziom 15  

    Witam!

    Eclipse Kepler
    Sourcery CodeBench Lite 2011.09-69
    OpenOCD 0.6.1
    STM32F103ZET6

    Od pewnego czasu bawię się modułem GSM. Komunikacja z modułem standardowo komendami AT.
    Chciałbym usprawnić komunikację z modułem ponieważ moim zdaniem teraz robię to bardzo nieefektywnie:
    - wpisanie do bufora nadawczego komendy i uruchomienie wysyłania z wykorzystaniem przerwań
    - sprawdzenie czy zmieniła się wartość licznika odebranych bajtów i sprawdzenie czy w buforze znajduje się oczekiwany ciąg znaków.
    W każdym obiegu pętli sprawdzam wszystkie odebrane znaki czy znajduje się w nich np. OK\n\r
    Sprawdzanie wszystkich elementów tablicy za każdym razem nie jest optymalne tym bardziej, że dane mogą przychodzić podczas sprawdzania bufora.
    Myślałem, aby w przerwaniu sprawdzać kolejne odebrane bajty i jak odbiorę po kolei OK\n\r lub ERROR\n\r ( te dwie komendy moduł wysyła zawsze) to ustawić flagę i dopiero sprawdzić bufor.
    Tylko czy to nie za dużo na przerwanie?
    Może ma ktoś inny pomysł?

    0 13
  • #2 08 Maj 2014 21:59
    gaskoin
    Poziom 38  

    Zapisz sobie tablice z oczekiwanymi komendami. Np:

    Kod: c
    Zaloguj się, aby zobaczyć kod


    i w przerwaniu, jeśli napotkasz na znak \n to jedziesz od końca i sprawdzasz czy pokrywa się to z którąś komendą. Pętla for będzie musiała dla COMMAND_OK sprawdzić tylko 4 elementy bufora. Nie jest to za wiele na przerwanie.

    0
  • #3 08 Maj 2014 22:06
    Freddie Chopin
    Specjalista - Mikrokontrolery

    EDIT: post nie do końca aktualny, bo gaskoin zdążył go poprawić gdy ja pisałem swój (;

    Ja tak lekko poza tematem...

    gaskoin napisał:
    char* COMMAND_ERROR = "ERROR\r\n";
    char* COMMAND_OK = "OK\r\n"

    "Const correctness" mówi, że wskaźniki powinny być co najmniej typu "const char *" (w zasadzie przedstawione definicje są "deprecated" w C++, w C niestety działają), a najlepiej w ogóle tak:

    const char * const COMMAND_ERROR = "ERROR\r\n";

    4\/3!!

    0
  • #4 09 Maj 2014 14:43
    Co_pat
    Poziom 15  

    Dzięki za odpowiedzi!

    gaskoin wydaje mi się, że sprawdzanie bufora w przerwaniu po odebraniu znaku nie będzie zbyt optymalne bo komunikacja wygląda tak:
    Zapytanie:
    AT\r\n
    Odpowiedź:
    AT\r\n // Z włączonym echem
    OK\r\n
    Więc w trakcie odbierania znak \n pojawia się klika razy przez co będę blokował przerwanie podczas odbierania danych. Teoretycznie echo może być wyłączone, ale teraz jest nieocenione podczas uruchamiania urządzenia.

    Może lepszym rozwiązaniem będzie sprawdzanie czy kolejno odebrane znaki składają się na OK lub ERROR?

    Zrobiłem coś takiego:

    Kod: c
    Zaloguj się, aby zobaczyć kod

    Sprawdzone działa, ale czy jest optymalne?Co o tym myślicie?
    Będę wdzięczny za każdą uwagę niekoniecznie dotyczącą samego odbioru komend AT.

    0
  • #5 09 Maj 2014 15:28
    gaskoin
    Poziom 38  

    To nie ma aż tak wielkiego narzutu. Wystarczy pamiętać początek dla znaku po \r\n i mieć aktualny wskaźnik gdzie się znajdujemy. Jakoś musisz sprawdzić czy to jest OK\r\n. Nie możesz oczekiwać, że coś procesor wykona tylko wtedy kiedy przyjdzie OK\r\n i nigdy więcej bo to niemożliwe :)

    Można to od razu parsować do jakiegoś bufora handlerów do komend i mieć coś na wzór polimorfizmu z języków obiektowych. Pytanie też co chcesz potem z tym zrobić.

    Co do wydajności - jeśli nie ma problemów z wydajnością to nie ma sensu też się nad nią zbytnio rozwodzić. Donald Knuth powiedział "Premature optimization is the root of all evil". Co do kodu - mimo, że nie ma go wiele, to jest bardzo nieczytelny. Ja nie potrafię skumać co się dzieje w tych kilku linijkach (komentarze wcale nie są na to jakimkolwiek rozwiązaniem).

    0
  • #7 09 Maj 2014 15:55
    Co_pat
    Poziom 15  

    Generalnie zawsze czekam na OK lub ERROR ponieważ moduł zawsze odpowiada w ten sposób, oczywiście wszystko z określonym timeout-em.
    Czas odpowiedzi modułu to min 70ms na prostą komendę więc w tym czasie "robię" pozostałe rzeczy. Sprawdzanie flagi i timeout-u znacznie przyspieszy mi działanie całości w porównaniu do przeszukiwania bufora odbiorczego za każdym razem.

    gaskoin napisał:
    Co do kodu - mimo, że nie ma go wiele, to jest bardzo nieczytelny. Ja nie potrafię skumać co się dzieje w tych kilku linijkach.

    Który fragment masz na myśli?

    GrzegorzKostka
    Pola tej struktury są modyfikowane w przerwaniu więc moim zdaniem volatile jest konieczne.

    0
  • #8 09 Maj 2014 16:55
    Freddie Chopin
    Specjalista - Mikrokontrolery

    Co_pat napisał:
    Pola tej struktury są modyfikowane w przerwaniu więc moim zdaniem volatile jest konieczne.

    Volatile jest konieczne tylko i wyłącznie jeśli pola struktury modyfikowane są (w nieuporządkowany sposób) w przerwaniu _i_ w kodzie głównym. Jeśli TYLKO w przerwaniu to nie ma takiej potrzeby. Jeśli tylko część struktury jest "współdzielona", to cała nie musi być volatile, wystarczy atrybut przy poszczególnych elementach (tych które są współdzielone).

    4\/3!!

    0
  • #9 09 Maj 2014 19:22
    Tom RealTime
    Poziom 10  

    Musisz pamiętać iż komunikacja z modułem komendami AT jest asynchroniczna. Znaczy to że po wysłaniu polecenia AT możesz, ale nie musisz dostać pełną odpowiedź OK lub ERROR. Zależy od użytego modułu. Simcom, Telit etc...
    Cześć odpowiedzi przychodzi spontanicznie i rozpoczyna się na ogół od znaku sterującego + (plus'a). np. +MIPRUDP: .....
    Zachodzą również zależności czasowe, które należy zachować. tj. po wysłaniu jednego polecenia AT... nie może pójść inne. do czasu odpowiedzi. Odpowidz może być po 10 ms lub 500 ms.
    Otwieranie portów również w wybranych modułach wymaga, wysłania polecenia i oczekiwania na odpowiedź OK, a następnie spontanicznej np. w Telit +MIPOPEN.
    Musisz też mieć na uwadze że każdy z modułów ma błędy które nie będą usuwane. Tylko POLSKIE firmy z firmware usuwają błędy.
    Obsługa powinna być zrobiona na wątkach jeśli stosujesz system wbudowany. Widzę STM32F103 więc nie sądzę abyś uruchomił linux'a lub uclinuxa. Możesz próbować skorzystać z koordynatora zadań i zestawić uart z mcu przez DMA.
    Dane mogą być wyczytywane i analizowane polingiem.
    Poling sprowadza się do analizy deskryptorów dma co jakiś kwant czasu. Są dane analizujesz....

    0
  • #10 09 Maj 2014 20:35
    Co_pat
    Poziom 15  

    Tom RealTime
    Akurat mam Telit-a GL865. Komunikacja wygląda tylko na zasadzie zapytanie-odpowiedź, jedyną sytuacją jest połączenie przychodzące i ewentualnie dane GPRS które przychodzą asynchronicznie.
    Program piszę bez żadnego OS-a.
    Z mojego punktu widzenia wykorzystanie DMA może być problematyczne ponieważ nie mam stałej komend wysyłanych do modułu, zazwyczaj nie wiem jakiej długości ramkę odbiorę więc oczekiwanie na koniec komendy zakończonej przez np.OK\r\n wydaje mi się najsensowniejsze.
    Dlatego chcę się skupić na efektywnym odbieraniu tych komend.

    Dopisałem co miałem na myśli. Co o tym myślicie?

    Kod: c
    Zaloguj się, aby zobaczyć kod

    0
  • #11 09 Maj 2014 21:06
    Tom RealTime
    Poziom 10  

    Przejście na DMA uprości Ci komunikację i program. masz bufor cykliczny. DMA używasz po to aby za Ciebie wpisywał lub wyczytywał dane z rejestru UART.
    Po co robić to ręcznie. Lepiej skupić się na analizie danych.
    Zakładam że odbiór masz w przerwaniu. Zrób prosty bufor cykliczny o wielkości 256 bytes. GSM_communication.counter_RX - będzie bajtowy. Drugi wskaźnik (np. GSM_communication.counter_AX) będzie gonił GSM_communication.counter_RX++
    dorobić jeszcze na liczniku timeout....

    void irq_u3(void) {
    uint8_t data = (uint8_t)(USART3->DR & 0x1FF); // Odebrany znak od modułu
    GSM_communication.buffer_RX[GSM_communication.counter_RX++] = data; // Zapisanie znaku do bufora odbiorczego
    }

    int wait_answer_telit(void) {
    #define OKCRLN 0x4F4B0D0A //OK\r\n
    #define RRCRLN 0x52520D0A // RR\r\n (fragment ERROR\r\n)
    unsigned int result = 0;
    unsigned char byte;
    for (;;;) {
    while (GSM_communication.counter_RX != GSM_communication.counter_AX) {
    byte = GSM_communication.buffer_RX[GSM_communication.counter_AX++];
    result <<= 8;
    result |= byte;
    if (result == OKCRLN) return 1;
    else if (result == RRCRLN) return -1;
    }
    }
    }

    Jest to przykład. Rozwiązanie na pętli czekającej na znak jest karygodne, jednak dla przykładu może tak być. W chwili gdy wskazniki są powinien przejść do innego tasku, a ten zostawić do czasu nadejscia kolejnego znaku.

    0
  • #13 09 Maj 2014 21:34
    Tom RealTime
    Poziom 10  

    Protothreads - to jest namiastka systemu operacyjnego. Dokładnie chodzi o przełączanie stosu, czyli aby to zastosować trzeba mieć napisaną obsługę koordynatora zadań. Jeżeli ktoś radzi sobie z problematyką koordynatora zadań, problemu by nie opisywał na forum.
    Najłatwiejsze rozwiązanie problemu to poling i obsługa uarta'a (nadawanie i odbiór) przez dma lub przerwanie.
    Program działa:
    1. wyslij ramke AT
    2. czekaj na OK lub ERROR lub +
    3. Jezeli + odbierz do konca komunikat i przeanalizuj
    4. czekaj na nowe dane do wysylki lub gdy odebrano dane (poczatek od +) analizuj ramke
    5. jezeli nie ma nowych danych lub je zaanalizowano, a nowe dane czekaj do wysylki do pkt. 1

    0
  • #14 10 Maj 2014 13:01
    Co_pat
    Poziom 15  

    GrzegorzKostka dzięki za zwrócenie uwagi na protothreads, aktualnie mój program składa się z kliku FSM zbudowanych w oparciu o switch/case i flag synchronizujących ich działanie więc przejście na protothreads może być dobrym pomysłem.

    0