Skocz do zawartości

r0land

Użytkownicy
  • Zawartość

    2
  • Rejestracja

  • Ostatnio

Reputacja

3 Neutralna

O r0land

  • Ranga
    1/10
  1. Witam, nazywam się Remigiusz, 38 lat, zawodowo programista systemów audio/video, hobbystycznie zajmuję się mikrokontrolerami dla różnych swoich zastosowań związanych ze sterowaniem, modelarstwem, lotniarstwem czy też domem. Na Forbot trafiłem przy okazji zapoznawania się z procesorami STM32 i tutaj znalazłem naprawdę sporo fajnych materiałów w kursach które pozwoliły mi pokonać wiele problemów, że tak powiem "startowych".
  2. 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
×
×
  • Utwórz nowe...