Skocz do zawartości

STM32F1 Implementacja buforowanego UARTa


r0land

Pomocna odpowiedź

Witam wszystkich, jakiś czas temu przeniosłem się na procki STM32F1, sporo korzystałem z kursów Forbot więc postaram się także w jakiś sposób odwdzięczyć za czyjąś robotę. Dziś prezentuję moją implementację UARTa wspomaganą buforami FIFO i przerwaniami. Zaletą takiego rozwiązania jest, pod warunkiem nie wysyłania danych więcej niż szybkość transmisji, natychmiastowe wyjście z procedury wysłania bajtu, zaś przy odczycie danych, braku gubienia przychodzących bajtów jeżeli czytamy w zbyt długich odstępach czasu. Temat ten wielokrotnie pojawiał się tutaj w przykładach ale nigdzie nie znalazłem kompleksowego, zadowalającego mnie rozwiązania, prezentuję więc własne, może się komuś przydać albo udoskonalić. Moje rozwiązanie bazuje na HAL, jako implementacji FIFO użyłem kodu z wyguglanej kwerendy"Steve Karg Interrupt Safe Ring Buffer Library", chociaż w moim rozwiązaniu bezpieczeństwo na przerwania nie jest wymagane.

Pomijając trywialne sprawy typu inicjalizacja FIFO, pinów, UARTA itd, zakładamy:

UART_HandleTypeDef     g_uart1; // poprawnie zainicjowany UART:

1) Odbieranie danych, założenia:

FIFO_BUFFER            g_rxFifo; // Bufor odbiorczy
uint8_t                g_rxBuffer; // Pomocniczy bajt do odbierania danych

Inicjalizacjia, konieczne jest zainicjowanie odbierania na przerwaniach, najlepiej zrobić to zaraz przy inicjalizacji UARTa:

HAL_UART_Receive_IT(&g_uart1, &g_rxBuffer, 1);

Inicjalizuje to odbiór danych, 1 bajtu do buforu g_rxBuffer. Funkcja ta wraca od razu, rezultatem będzie zawołanie callbacka HAL_UART_RxCpltCallback który należy przekierować do poniższej funkcji:

void serial1_RxCpltCallback()
{
    FIFO_Put(&g_rxFifo, g_rxBuffer);
    HAL_UART_Receive_IT(&g_uart1, &g_rxBuffer, 1);
}

Funkcja ta wpisuje odebraną wartość do FIFO i natychmiast inicjuje odbieranie kolejnego bajtu. Funkcja do odbierania wygląda następująco, zwraca status: 0 - nie odebrano bajtu, 1 - odebrano bajt, przypisano pod podany wskaźnik. Niestety nie ma tutaj kontroli gdy odbieranie przepełni FIFO i zacznie gubić odbierane bajty, implementacja musi zapewniać w miarę sprawne odbieranie danych. Sprawę wyłączania i włączania przerwań opiszę później.
 

uint8_t serial1_receive(uint8_t* byte)
{
    __disable_irq();

    if( FIFO_Empty(&g_rxFifo))
    {
        __enable_irq();
        return 0;
    }

    *byte = FIFO_Get(&g_rxFifo);
    __enable_irq();

    return 1;
}

2) Wysyłanie danych:

FIFO_BUFFER            g_txFifo;  // Bufor nadawczy
uint8_t                g_txBuffer;  // Pomocniczy
volatile uint8_t    g_txPending = 0; // Flaga informująca o trwającej transmisji bajtu.

Zasada działania, początkowo txPending = 0 co oznacza że nic nie jest wysyłane, w takim przypadku przestawiamy ją na 1 i bajt danych wysyłamy bezpośrednio wołając HAL_UART_Transmit_IT, nie korzystając z fifo. Jednak zawołamy funkcję drugi raz zanim transmisja się zakończy i wywoła przewanie końca transmisji to txPending będzie miało wartość 1 co oznacza, że bajtu wysłać nie można i wpisujemy go do FIFO. Tutaj jeszcze sprawdzamy czy FIFO nie jest pełne, jeśli pełne go czekamy aż się zwolni i to jest jedyna możliwość gdy funkcja może trwać niespodziewania długo, ale dzięki temu jest gwarancja braku dropowania bajtów przy zbyt szybkiej transmisji. Jeśli komuś na tym nie zależy może usunąć pętlę while, pozostawiająć wykonanie FIFO_Put.
 

void serial1_send(uint8_t byte)
{
    __disable_irq();

    if( g_txPending )
    {
        while(!FIFO_Put(&g_txFifo, byte))
        {
            __enable_irq();
            __disable_irq();
        }
    }
    else
    {
        g_txPending = 1;
        g_txBuffer = byte;
        HAL_UART_Transmit_IT(&g_uart1, &g_txBuffer, 1);
    }

    __enable_irq();
}

Callback HAL_UART_TxCpltCallback należy przekierować do poniższej funkcji która działa w następujący sposób, jeśli fifo nie jest puste to pobiera z niego kolejny bajt i ponownie inicjuje transmisję na przewaniach, pozostając w stanie trwającej transmisji (txPending jest i pozostaje 1), natomiast jeśli fifo jest puste to oznacza to że nie ma już nic do transmisji i przestawiamy stan na txPending = 0.

void serial1_TxCpltCallback()
{
    if( !FIFO_Empty(&g_txFifo))
    {
        g_txBuffer = FIFO_Get(&g_txFifo);
        HAL_UART_Transmit_IT(&g_uart1, &g_txBuffer, 1);
    }
    else
    {
        g_txPending = 0;
    }
}

3) Słowo o włączaniu/wyłączaniu przerwań i innych niuansach, zauważmy że przy odbiorze danych kod HAL (funkcja HAL_UART_Receive_IT) jest wołana tylko z przerwania, natomiast przy wysyłaniu (funkcja) HAL_UART_Transmit_IT z przerwania bądź z programu głównego. Pisząc tą implementację tymczasowo odbieranie miałem w przedstawionej tutaj wersji zaś nadawanie jako klasyczne, blokujące HAL_UART_Transmit z programu głównego. I niestety pojawia się problem, gdy przerwanie HAL_UART_RxCpltCallback  pojawi się w czasie trwającego, blokującego HAL_UART_Transmit gdyż HAL zaznacza ten obiekt jako locked i cokolwiek będziemy chcieli zrobić w czasie przerwania to nam się nie uda, dostaniemy status HAL_BUSY z HAL_UART_Receive_IT i transmisja stanie. Żaden z tutoriali poruszających transmisję UART na przerwaniach nie podaje tego niuansu a jest to akurat niezwykle istotne dla prawidłowego działania w dłuższym czasie. Rozwiązanie które przedstawiłem jest bezpieczne pod tym względem, każda funkcja kliencka wyłącza tymczasowo przerwania aby zabezpieczyć się przed tym problemem.

4) Ulepszenia. "Ręczne" fifo można przerobić na DMA, wysyłanie wielu bajtów można usprawnić pobierając z FIFO więcej niż jeden bajt, czego z pewnością prędzej czy później dokonam.

Pozdrawiam,
Remigiusz

Edytowano przez Treker
Poprawiłem formatowanie.
  • Lubię! 1
Link do komentarza
Share on other sites

@r0land, witam na forum 😉 Widzę, że to Twoje pierwsze kroki na Forbocie, oto najważniejsze informacje na start:

  • Chcesz przywitać się z innymi członkami naszej społeczności? Skorzystaj z tematu powitania użytkowników.
  • Opis najciekawszych funkcji, które ułatwiają korzystanie z forum znajdziesz w temacie instrukcja korzystania z forum - co warto wiedzieć?
  • Poszczególne posty możesz oceniać (pozytywnie i negatywnie) za pomocą reakcji - ikona serca w prawym dolnym rogu każdej wiadomości.

Dnia 22.11.2018 o 20:37, r0land napisał:

Witam wszystkich, jakiś czas temu przeniosłem się na procki STM32F1, sporo korzystałem z kursów Forbot więc postaram się także w jakiś sposób odwdzięczyć za czyjąś robotę. Dziś prezentuję moją implementację UARTa wspomaganą buforami FIFO i przerwaniami.

Nie sprawdzałem Twojego programu, bo na forum mamy wielu lepszych ode mnie specjalistów od STMów, którzy na pewno wytknęliby wszystkie błędy. Pisze, aby podziękować za to, że zechciałeś podzielić się swoim rozwiązaniem - oby więcej takich osób 😉

Link do komentarza
Share on other sites

Dołącz do dyskusji, napisz odpowiedź!

Jeśli masz już konto to zaloguj się teraz, aby opublikować wiadomość jako Ty. Możesz też napisać teraz i zarejestrować się później.
Uwaga: wgrywanie zdjęć i załączników dostępne jest po zalogowaniu!

Anonim
Dołącz do dyskusji! Kliknij i zacznij pisać...

×   Wklejony jako tekst z formatowaniem.   Przywróć formatowanie

  Dozwolonych jest tylko 75 emoji.

×   Twój link będzie automatycznie osadzony.   Wyświetlać jako link

×   Twoja poprzednia zawartość została przywrócona.   Wyczyść edytor

×   Nie możesz wkleić zdjęć bezpośrednio. Prześlij lub wstaw obrazy z adresu URL.

×
×
  • Utwórz nowe...

Ważne informacje

Ta strona używa ciasteczek (cookies), dzięki którym może działać lepiej. Więcej na ten temat znajdziesz w Polityce Prywatności.