STM32F4 UART HAL驱动程序

时间:2014-07-21 22:26:07

标签: stm32 stm32f4discovery hal

我正在试图弄清楚如何使用这个新的HAL驱动程序。我想使用HAL_UART_Receive_IT()接收数据,设置设备在接收数据时运行中断功能。

问题是您必须在中断触发之前指定要读取的数据长度。我计划像不同长度的命令一样发送控制台,因此不能有固定的长度。我假设唯一的方法是一次读取单个字符并构建一个单独的字符串。

HAL驱动程序似乎有问题,如果您将HAL_UART_Receive_IT()设置为接收x个字符数,然后尝试发送超过x个字符,那么将会有一个问题错误。

目前我不知道我是否会采用正确的方式,任何想法?

6 个答案:

答案 0 :(得分:15)

我决定使用DMA来让接收工作。我正在使用一个1字节的循环缓冲区来处理数据,因为它是在变送器的串行终端上输入的。这是我的最终代码(只有接收部分,底部传输的更多信息)。

一些定义和变量:

#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

设置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);

设置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);

设置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);

设置DMA中断:

extern DMA_HandleTypeDef hdma_usart1_rx;

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

启动DMA:

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

DMA接收回调:

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

所以这几乎是所有接收字符的代码并构建一个显示用户输入内容的字符串(char数组)。如果用户命中backspace或del,则会覆盖数组中的最后一个字符,如果它们按Enter键,则该数组将被发送到另一个函数并作为命令处理。

要查看命令解析和传输代码的工作原理,请参阅我的项目Here

感谢@Flip和@Dormen的建议!

答案 1 :(得分:6)

数据寄存器(DR)已满时接收数据将导致溢出错误。问题是函数UART_Receive_IT(UART_HandleTypeDef*)一旦收到足够的数据就会停止读取DR寄存器。任何新数据都会导致溢出错误。

我所做的是宁愿使用循环DMA接收结构。然后,您可以使用currentPosInBuffer - uart->hdmarx->Instance->NDTR来确定尚未处理的数据量。

稍微复杂一点,因为当DMA执行循环缓冲时,如果超过缓冲区的末尾,则必须手动将环回实现到开头。

我还发现了一个故障,控制器说它已经传输了数据(即NDTR已经减少了),但数据还没有在缓冲区中。它可能是一些DMA /总线访问争用问题,但它很烦人。

答案 2 :(得分:3)

STM32 UART驱动程序有点不稳定。他们开箱即用的唯一方法是,如果您知道要接收的确切字符数。如果你想收到一个数目不详的字符,我会遇到一些解决方案:

  1. 将要接收的字符数设置为1并构建单独的字符串。这很有效但在接收数据的速度非常快时会出现问题,因为每次驱动程序读取rxBuffer都会使中断失败,因此某些字符可能会丢失。

  2. 将要接收的字符数设置为最大可能的消息大小并实现超时,然后读取整个消息。

  3. 编写自己的UART_Receive_IT函数,该函数直接写入循环缓冲区。这是更多的工作,但这是我发现最好的工作。您必须更改一些hal驱动程序,因此代码的可移植性较差。

  4. 另一种方法是使用像@Flip建议的DMA。

答案 3 :(得分:0)

我的项目中不得不面对同样的问题。 我所做的是在外设初始化后立即用HAL_USART_Receive_IT()读取1个字节。

然后我写了一个关于传输完成的回调函数,它将字节放在缓冲区中,如果命令完成则设置一个标志,然后再次调用HAL_USART_Receive_IT()另一个字节。

它似乎对我很好,因为我通过USART接收命令,其第一个字节告诉我命令多长几个字节。 也许它也适合你!

答案 4 :(得分:0)

采用不同的方法修补,例如&#34;无效USART2_IRQHandler(无效)&#34;在文件&#34; stm32l0xx_it.c&#34; (或根据需要l4xx)。每次收到一个字符时,都会调用此中断。插入用户代码的空间在使用CubeMX代码生成器进行更新时保持不变。补丁:

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

我提供一个小字符缓冲区并启动接收IT功能。高达115200波特,它从不消耗超过1字节,剩余的缓冲区未使用。

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

当接收到一个字节时,我捕获它并将它放到我自己的环形缓冲区并设置字符指针和-counter:

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

这种方法证明相当快。使用IT或DMA仅接收一个字节总是取消初始化并需要再次初始化接收过程,结果证明速度太慢。上面的代码只是一个框架;我曾经在状态结构中计算换行符,这使我可以随时从环形缓冲区中读取完成的行。还应包括检查收到的字符或其他事件是否导致中断 编辑:
事实证明,这种方法可以与USARTS一起使用,而DMA不支持它,而是使用IT。 使用带有HAL库的CubeMX生成器时,在循环模式下使用带有1个字节的DMA更短且更容易实现。

答案 5 :(得分:0)

通常我编写了自己的UART循环缓冲区实现。如前所述,STM32 HAL库的UART中断函数有点奇怪。 您可以使用UART中断标志编写自己的循环缓冲区,只有2个数组和指针。