Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

STM32F4 UART HAL Driver

Tags:

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 function when data is received.

Problem is that you have to specify the length of data to read before the interrupt triggers. I plan on sending console like commands of varying length so can't have a fixed length. I assume the only way to do this would be to read single characters at a time and build up a separate string.

The HAL driver seems to have a problem where if you set the HAL_UART_Receive_IT() to receive x number of characters, and then try to send more than x characters, there will be an error.

Currently I have no idea if I'm going about it the right way, any ideas?

like image 372
HammerFet Avatar asked Jul 21 '14 22:07

HammerFet


People also ask

What is STM32 Hal driver?

The STM32 HAL-LL drivers, an abstraction layer offering a set of APIs ensuring maximized portability across the STM32 portfolio. The BSP drivers of each evaluation, demonstration or nucleo board provided for this STM32 series.

What is the difference between Hal and LL?

The HAL offers high-level and feature-oriented APIs, with a high-portability level. They hide the MCU and peripheral complexity to end-user. The LL offers low-level APIs at registers level, with better optimization but less portability. They require deep knowledge of the MCU and peripherals specifications.

What is STM32 Hal?

The STM32 Hardware Abstraction Layer (HAL) provides a simple, generic multi-instance set of APIs (application programming interfaces) to interact with the upper layers like the user application, libraries and stacks.


2 Answers

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!

like image 155
HammerFet Avatar answered Sep 21 '22 17:09

HammerFet


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.

like image 23
Flip Avatar answered Sep 20 '22 17:09

Flip