2014-07-21 37 views
16

Próbuję dowiedzieć się, jak korzystać z tego nowego sterownika HAL. Chcę otrzymywać dane za pomocą HAL_UART_Receive_IT(), która konfiguruje urządzenie do uruchamiania funkcji przerwania po otrzymaniu danych.Sterownik UART HAL STM32F4

Problem polega na tym, że musisz określić długość danych do odczytania, zanim wyzwalanie zostanie przerwane. Planuję wysyłać konsole jak polecenia o różnej długości, więc nie mogę mieć stałej długości. Zakładam, że jedynym sposobem, aby to zrobić, byłoby odczytywanie pojedynczych znaków naraz i tworzenie oddzielnego ciągu znaków.

Wygląda na to, że sterownik HAL ma problem, jeśli jeśli ustawisz HAL_UART_Receive_IT() na x liczbę znaków, a następnie spróbujesz wysłać więcej niż x znaków, wystąpi błąd.

Obecnie nie mam pojęcia, czy mam zamiar to zrobić we właściwy sposób, jakieś pomysły?

Odpowiedz

13

Postanowiłem pójść z DMA, aby uzyskać odbiór działa. Używam 1-bajtowego bufora kołowego do obsługi danych, wpisywanych na terminalu szeregowym przetwornika. Oto mój ostateczny kod (tylko część odbierająca, więcej informacji na temat przesyłania na dole).

Niektóre definiuje i zmienne:

#define BAUDRATE    9600 
#define TXPIN     GPIO_PIN_6 
#define RXPIN     GPIO_PIN_7 
#define DATAPORT    GPIOB 
#define UART_PRIORITY   6 
#define UART_RX_SUBPRIORITY 0 
#define MAXCLISTRING   100 // Biggest string the user will type 

uint8_t rxBuffer = '\000'; // where we store that one character that just came in 
uint8_t rxString[MAXCLISTRING]; // where we build our string from characters coming in 
int rxindex = 0; // index for going though rxString 

Konfigurowanie IO:

__GPIOB_CLK_ENABLE(); 
__USART1_CLK_ENABLE(); 
__DMA2_CLK_ENABLE(); 

GPIO_InitTypeDef GPIO_InitStruct; 

GPIO_InitStruct.Pin = TXPIN | RXPIN; 
GPIO_InitStruct.Mode = GPIO_MODE_AF_PP; 
GPIO_InitStruct.Pull = GPIO_NOPULL; 
GPIO_InitStruct.Speed = GPIO_SPEED_LOW; 
GPIO_InitStruct.Alternate = GPIO_AF7_USART1; 
HAL_GPIO_Init(DATAPORT, &GPIO_InitStruct); 

skonfigurować UART:

UART_HandleTypeDef huart1; 
DMA_HandleTypeDef hdma_usart1_rx; 

huart1.Instance = USART1; 
huart1.Init.BaudRate = BAUDRATE; 
huart1.Init.WordLength = UART_WORDLENGTH_8B; 
huart1.Init.StopBits = UART_STOPBITS_1; 
huart1.Init.Parity = UART_PARITY_NONE; 
huart1.Init.Mode = UART_MODE_TX_RX; 
huart1.Init.HwFlowCtl = UART_HWCONTROL_NONE; 
huart1.Init.OverSampling = UART_OVERSAMPLING_16; 
HAL_UART_Init(&huart1); 

Konfigurowanie DMA:

extern DMA_HandleTypeDef hdma_usart1_rx; // assuming this is in a different file 

hdma_usart1_rx.Instance = DMA2_Stream2; 
hdma_usart1_rx.Init.Channel = DMA_CHANNEL_4; 
hdma_usart1_rx.Init.Direction = DMA_PERIPH_TO_MEMORY; 
hdma_usart1_rx.Init.PeriphInc = DMA_PINC_DISABLE; 
hdma_usart1_rx.Init.MemInc = DMA_MINC_DISABLE; 
hdma_usart1_rx.Init.PeriphDataAlignment = DMA_PDATAALIGN_BYTE; 
hdma_usart1_rx.Init.MemDataAlignment = DMA_MDATAALIGN_BYTE; 
hdma_usart1_rx.Init.Mode = DMA_CIRCULAR; 
hdma_usart1_rx.Init.Priority = DMA_PRIORITY_LOW; 
hdma_usart1_rx.Init.FIFOMode = DMA_FIFOMODE_DISABLE; 
HAL_DMA_Init(&hdma_usart1_rx); 

__HAL_LINKDMA(huart, hdmarx, hdma_usart1_rx); 

HAL_NVIC_SetPriority(DMA2_Stream2_IRQn, UART_PRIORITY, UART_RX_SUBPRIORITY); 
HAL_NVIC_EnableIRQ(DMA2_Stream2_IRQn); 

Konfigurowanie DMA przerwań:

extern DMA_HandleTypeDef hdma_usart1_rx; 

void DMA2_Stream2_IRQHandler(void) 
{ 
    HAL_NVIC_ClearPendingIRQ(DMA2_Stream2_IRQn); 
    HAL_DMA_IRQHandler(&hdma_usart1_rx); 
} 

startu DMA:

__HAL_UART_FLUSH_DRREGISTER(&huart1); 
HAL_UART_Receive_DMA(&huart1, &rxBuffer, 1); 

DMA otrzymać callback:

void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart) 
{ 
    __HAL_UART_FLUSH_DRREGISTER(&huart1); // Clear the buffer to prevent overrun 

    int i = 0; 

    print(&rxBuffer); // Echo the character that caused this callback so the user can see what they are typing 

    if (rxBuffer == 8 || rxBuffer == 127) // If Backspace or del 
    { 
     print(" \b"); // "\b space \b" clears the terminal character. Remember we just echoced a \b so don't need another one here, just space and \b 
     rxindex--; 
     if (rxindex < 0) rxindex = 0; 
    } 

    else if (rxBuffer == '\n' || rxBuffer == '\r') // If Enter 
    { 
     executeSerialCommand(rxString); 
     rxString[rxindex] = 0; 
     rxindex = 0; 
     for (i = 0; i < MAXCLISTRING; i++) rxString[i] = 0; // Clear the string buffer 
    } 

    else 
    { 
     rxString[rxindex] = rxBuffer; // Add that character to the string 
     rxindex++; 
     if (rxindex > MAXCLISTRING) // User typing too much, we can't have commands that big 
     { 
      rxindex = 0; 
      for (i = 0; i < MAXCLISTRING; i++) rxString[i] = 0; // Clear the string buffer 
      print("\r\nConsole> "); 
     } 
    } 
} 

Więc to dość dużo cały kod do odbierania znaków i zbudować ciąg (char array), która pokazuje, co użytkownik wprowadził. Jeśli użytkownik trafi backspace lub del, ostatni znak w tablicy zostanie nadpisany i jeśli trafią enter, tablica ta zostanie wysłana do innej funkcji i przetworzona jako polecenie.

Aby zobaczyć jak parsowanie polecenia i przekazywać prace kodu, patrz mój projekt Here

Dzięki @Flip i @Dormen na ich sugestie!

6

Odbieranie danych, gdy rejestr danych (DR) jest pełny, spowoduje błąd przekroczenia. Problem polega na tym, że funkcja UART_Receive_IT(UART_HandleTypeDef*) przestanie czytać rejestr DR po otrzymaniu wystarczającej ilości danych. Wszelkie nowe dane spowodują błąd przekroczenia.

To, co zrobiłem, to raczej użyć okrągłej struktury DMA. Następnie można użyć numeru currentPosInBuffer - uart->hdmarx->Instance->NDTR, aby określić, ile otrzymanych danych nie zostało jeszcze przetworzonych.

Jest to nieco bardziej skomplikowane, ponieważ podczas gdy DMA wykonuje sam cykliczne buforowanie, musisz ręcznie zaimplementować sprzężenie zwrotne do początku, jeśli przekroczysz koniec bufora.

Znalazłem błąd, gdy kontroler mówi, że przesłał dane (tj. Zmniejszyło się NDTR), ale dane nie są jeszcze w buforze. Może to być problem z rywalizacją DMA/magistralą, ale jest to denerwujące.

+0

Błąd prawdopodobnie wynika z buforowania danych w procesorze. Powinien ustawić bufor na niezbuforowany w MPU lub użyć instrukcji płukania pamięci podręcznej przed odczytaniem bufora. – Flip

3

Sterowniki UART STM32 są nieco nieporęczne. Jedynym sposobem, w jaki pracują po wyjęciu z pudełka, jest znajomość dokładnej liczby znaków, które otrzymasz. Jeśli chcesz otrzymywać bliżej nieokreśloną liczbę znaków istnieje kilka rozwiązań, które mam natknąć i próbowałem:

  1. Ustaw ilość znaków, aby otrzymać 1 i zbudować osobny ciąg. Działa to, ale ma problemy z szybkim odbiorem danych, ponieważ za każdym razem, gdy sterownik odczytuje rxBuffer, rozłącza przerwanie, więc niektóre znaki mogą zostać utracone.

  2. Ustaw liczbę znaków do odebrania według największego możliwego rozmiaru wiadomości i wprowadź limit czasu, po którym zostanie odczytana cała wiadomość.

  3. Napisz własną funkcję UART_Receive_IT, która zapisuje bezpośrednio do okrągłego bufora. To więcej pracy, ale to, co znalazłem, działa najlepiej na końcu. Musisz jednak zmienić niektóre sterowniki hal, więc kod jest mniej przenośny.

Innym sposobem jest użycie DMA, jak sugerował @Flip.

+0

Inny pomysł: użyj "protokołu", gdy pierwszy raz otrzymasz 1 bajt, zawierający rozmiar następnej ilości danych. 'Odczekaj 1 bajt -> odbierać wartość "5", odczekać 5 bajtów -> otrzymasz 5 bajtów, odczekać 1 bajt -> odbierać wartość "28", odczekać 28 bajtów -> otrzymasz 28 bajtów, ..., Poczekaj na 1 bajt -> odbierz wartość "0", END ' – ofaurax

+0

@ofaurax tak, ale działa to tylko wtedy, gdy masz kontrolę nad obydwoma końcami komunikacji. –

0

Musiałem zmierzyć się z tym samym problemem w moim projekcie. Po uruchomieniu urządzenia peryferyjnego zacząłem od odczytu 1 bajta z HAL_USART_Receive_IT().

Następnie napisałem wywołanie zwrotne po zakończeniu przesyłania, które umieszcza bajt w buforze, ustawia flagę, jeśli polecenie jest kompletne, a następnie ponownie wywołuje HAL_USART_Receive_IT() dla innego bajtu.

Wydaje mi się, że działa mi dobrze, ponieważ otrzymuję polecenia przez USART, którego pierwszy bajt mówi mi, ile bajtów więcej, to polecenie będzie długie. Może to może działać również dla Ciebie!

0

Mieć inne podejście do łatania, np. "vART USART2_IRQHandler (void)" w pliku "stm32l0xx_it.c" (lub l4xx w razie potrzeby). Za każdym razem, gdy zostaje odebrany znak, wywoływane jest to przerwanie. Jest miejsce na wstawienie kodu użytkownika, który pozostaje niezmieniony podczas aktualizacji za pomocą generatora kodu CubeMX. Łatka:

void USART2_IRQHandler(void) 
{ 
    /* USER CODE BEGIN USART2_IRQn 0 */ 

    /* USER CODE END USART2_IRQn 0 */ 
    HAL_UART_IRQHandler(&huart2); 
    /* USER CODE BEGIN USART2_IRQn 1 */ 
    usart_irqHandler_callback(&huart2); // patch: call to my function 
    /* USER CODE END USART2_IRQn 1 */ 
} 

Dostarczam mały bufor znaków i uruchamiam funkcję IT otrzymywania. Do 115200 Baud nigdy nie zużyło więcej niż 1 bajtu, pozostawiając resztę bufora nieużywaną.

st = HAL_UART_Receive_IT(&huart2, (uint8_t*)rx2BufIT, RX_BUF_IT_SIZE); 

Po otrzymaniu bajt I uchwycić go i umieścić go na moim własnym buforze pierścieniowym i zestaw znaków-pointer i liczniku powrotem:

// placed in my own source-code module: 
void usart_irqHandler_callback(UART_HandleTypeDef* huart) { 
    HAL_UART_StateTypeDef st; 
    uint8_t c; 
    if(huart->Instance==USART2) { 
    if(huart->RxXferCount >= RX_BUF_IT_SIZE) { 
     rx2rb.err = 2;   // error: IT buffer overflow 
    } 
    else { 
     huart->pRxBuffPtr--;  // point back to just received char 
     c = (uint8_t) *huart->pRxBuffPtr; // newly received char 
     ringbuf_in(&rx2rb, c); // put c in rx ring-buffer 
     huart2.RxXferCount++; // increment xfer-counter avoids end of rx 
    } 
    } 
} 

Metoda ta okazała się dość szybko. Odbieranie tylko jednego bajtu za pomocą IT lub DMA zawsze deinicjalizuje i wymaga ponownego zainicjowania procesu odbierania, który okazał się zbyt wolny.Powyższy kod jest tylko ramką; Użyłem tutaj policzenia znaków nowej linii w strukturze statusu, która pozwala mi w każdej chwili przeczytać wypełnione linie z bufora pierścieniowego. Sprawdź także, czy odebrany znak lub jakieś inne zdarzenie spowodowało przerwanie.
EDIT:
Metoda ta okazała się działać dobrze z USARTS, które nie są obsługiwane przez DMA i używać go zamiast. Używanie DMA z 1 bajtem w trybie kołowym jest krótsze i łatwiejsze do wdrożenia przy użyciu generatora CubeMX z biblioteką HAL.

0

Zazwyczaj pisałam realizację własnego bufora okrągły UART. Jak wspomniano wcześniej, funkcje przerwań UART biblioteki STM32 HAL są trochę dziwne. Możesz napisać swój własny bufor cykliczny z tylko 2 tablicami i wskaźnikami używając flag przerwania UART.