20

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

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

HAL 驱动程序似乎有一个问题,如果您设置HAL_UART_Receive_IT()接收x字符数,然后尝试发送多个x字符,则会出现错误。

目前我不知道我是否以正确的方式去做,有什么想法吗?

4

6 回答 6

18

我决定与 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> ");
        }
    }
}

以上就是接收字符和构建显示用户输入内容的字符串(字符数组)的所有代码。如果用户点击退格或删除,数组中的最后一个字符将被覆盖,如果他们点击回车,则该数组被发送到另一个函数并作为命令处理。

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

感谢@Flip 和@Dormen 的建议!

于 2015-02-16T10:22:20.400 回答
8

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

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

这有点复杂,因为虽然 DMA 自己执行循环缓冲,但如果超过缓冲区的末尾,则必须手动实现回环到开头。

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

于 2015-01-13T12:29:41.140 回答
3

STM32 UART 驱动程序有点不稳定。他们开箱即用的唯一方法是如果您知道您将收到的确切字符数。如果您想接收未指定数量的字符,我遇到并尝试了几种解决方案:

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

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

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

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

于 2015-01-26T10:37:10.870 回答
1

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

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

这对我来说似乎很好用,因为我通过 USART 接收命令,其第一个字节告诉我该命令将长多少字节。也许它也可以为你工作!

于 2016-01-22T18:50:34.320 回答
0

在文件“stm32l0xx_it.c”(或根据需要的 l4xx)中使用不同的方法进行修补,例如“void USART2_IRQHandler(void)”。每次接收到一个字符时,都会调用此中断。使用 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 仅接收一个字节总是会取消初始化并且需要再次初始化接收过程,结果证明太慢了。上面的代码只是一个框架;我曾经在状态结构中计算换行符,这使我可以随时从环形缓冲区中读取已完成的行。还应包括检查接收到的字符或其他事件是否导致中断。
编辑:
这种方法被证明可以与 DMA 不支持的 USARTS 一起使用,而是使用它。将 CubeMX 生成器与 HAL 库一起使用时,在循环模式下使用 1 字节的 DMA 更短且更易于实现。

EDIT2:
由于最近的 HAL 库的变化,这不能逐行工作。该原理仍然可以快速有效地工作,但必须适应这些“方言”。对不起,但它是无地板桶一直在改变它。

于 2017-07-10T11:09:55.913 回答
0

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

于 2017-07-20T09:31:49.437 回答