STM32F4 UART HAL Driver

后端 未结 6 1011
北荒
北荒 2021-01-30 04:49

I\'m trying to figure out how to use this new HAL driver. I want to receive data using the HAL_UART_Receive_IT() which sets up the device to run an interrupt functi

相关标签:
6条回答
  • 2021-01-30 05:05

    Receiving data while the Data Register (DR) is full will result in an overrun error. The problem is that the function UART_Receive_IT(UART_HandleTypeDef*) will stop reading the DR register once it has received enough data. Any new data will cause the overrun error.

    What I did was to rather use a circular DMA receive structure. You can then use currentPosInBuffer - uart->hdmarx->Instance->NDTR to determine how much data was received that you haven't processed yet.

    It is a little bit more complicated because while the DMA does the circular buffering itself, you have to manually implement the loopback to the beginning if you go past the end of the buffer.

    I have also found a glitch where the controller says it has transferred the data (i.e. NDTR has decreased) but the data is not yet in the buffer. It may be some DMA/bus access contention issue, but it is annoying.

    0 讨论(0)
  • 2021-01-30 05:05

    I had to face the same problem in my project. What I did is start reading 1 byte with HAL_USART_Receive_IT() right after the peripheral initialization.

    Then I wrote a callback on transfer complete which puts the byte in a buffer, sets a flag if command is complete and then calls HAL_USART_Receive_IT() again for another byte.

    It seems to work nice for me since I receive commands trough the USART whose first byte tells me how many bytes more the command is going to be long. Maybe it could work for you too!

    0 讨论(0)
  • 2021-01-30 05:06

    The STM32 UART drivers are a bit wonky. The only way they work out of the box is if you know the exact number of characters you are going to receive. If you want to receive an unspecified number of characters there are a couple of solutions that I have come across and tried:

    1. Set the amount of characters to receive to 1 and build a separate string. This works but has problems when receiving data very fast, because every time the driver reads the rxBuffer it dissables the interrupt, so some characters can be lost.

    2. Set the amount of characters to receive to the largest possible message size and implement a timeout, after which the whole message is read.

    3. Write your own UART_Receive_IT function, which writes directly into a circular buffer. This is more work, but it is what I found works best in the end. You do have to change some of the hal drivers though, so the code is less portable.

    Another way is to use DMA like @Flip suggested.

    0 讨论(0)
  • 2021-01-30 05:10

    Usually i wrote my own UART circular buffer implementation. As said before, STM32 HAL library's UART interrupt functions are little bit strange. You can write your own circular buffer with just 2 array and pointers using UART interrupt flags.

    0 讨论(0)
  • 2021-01-30 05:16

    Have a different approach patching e.g. "void USART2_IRQHandler(void)" in the file "stm32l0xx_it.c" (or l4xx as needed). Every time a character is received this interrupt is called. There is space to insert user code which keeps unchanged when updating with CubeMX code generator. Patch:

    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 */
    }
    

    I supply a small character buffer and start the receive IT function. Up to 115200 Baud it never consumed more than 1 Byte leaving the rest of the buffer unused.

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

    When receiving a byte I capture it and put it to my own ring-buffer and set the character-pointer and -counter back:

    // 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
        }
      }
    }
    

    This method proved to be rather fast. Receiving only one byte using IT or DMA always de-initializes and needs initializing the receiving process again which turned out to be too slow. The code above is only a frame; I used to count newline characters here in a status structure which allows me any time to read completed lines from the ring-buffer. Also a check if a received character or some other event caused the interrupt should be included.
    EDIT:
    This method proved to work fine with USARTS which are not supported by DMA and use IT instead. Using DMA with 1 byte in circular mode is shorter and easier to implement when using CubeMX generator with HAL library.

    EDIT2:
    Due to changes in more recent HAL Libraries this does not work line by line. The principle still works fast and fine but has to be adapted to these 'dialects'. Sorry, but it is floor-less barrel to change it all-time.

    0 讨论(0)
  • 2021-01-30 05:20

    I decided to go with DMA to get the receive working. I'm using a 1 byte circular buffer to handle data as it is typed on the transmitter's serial terminal. Here's my final code (only the receive part, more info on transmit at the bottom).

    Some defines and variables:

    #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
    

    Set up 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);
    

    Set up the 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);
    

    Set up 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);
    

    Set up DMA interrupt:

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

    Start DMA:

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

    DMA receive 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> ");
            }
        }
    }
    

    So that's pretty much all the code to receive characters and build a string (char array) that shows what the user has entered. If the user hits backspace or del, the last character in the array is overwritten and if they hit enter, that array is sent to another function and processed as a command.

    To see how the command parsing and transmit code works, see my project Here

    Thanks to @Flip and @Dormen for their suggestions!

    0 讨论(0)
提交回复
热议问题