STM32 HAL中使用中断接收USART数据

8
我有些困难在USART上接收数据。我的目标是能够通过USART接收任意长度的命令(只限于最大长度)。我使用中断程序来检查每个接收到的字符,但是我仍然无法实现我的目标。该程序每次接收新字符时都会被调用,但是HAL_UART_Receive_IT(&huart1,rx_data,buff_size_rx)不会实时更新,因此当我检查rx_data[pointer]时无法看到接收的字符,但几秒钟后它会在rx_data缓冲区中出现。
目前我所拥有的:
int pointer =0;

...

void USART1_IRQHandler(void)
{
  /* USER CODE BEGIN USART1_IRQn 0 */
    if ( USART1->ISR & UART_IT_TXE) {

    }

    if ( USART1->ISR & UART_IT_RXNE) {
        HAL_UART_Receive_IT(&huart1,rx_data,buff_size_rx);
        if(rx_data[pointer]=='\0') {
              pointer=0;
              readCommand(rx_data);
              clearBuffer(rx_data,buff_size_rx);
        } else {
          pointer++;
          if(pointer>=buff_size_rx) {
              pointer=0;
          }
        }
    }
    /* USER CODE END USART1_IRQn 0 */
    HAL_UART_IRQHandler(&huart1);
    /* USER CODE BEGIN USART1_IRQn 1 */



  /* USER CODE END USART1_IRQn 1 */
}

你说的复制缓冲区实现是什么意思?我想在HAL中实现这个,但我还没有找到解决方案。DMA会很好,但我需要为每个命令指定固定长度或使用超时,而这在STM32F303K8T6的HAL中不受支持。 - HansPeterLoft
消息只有大约16个字符。如果我只使用HAL_UART_Receive_IT,它将把字符写入rx_data缓冲区,但我永远不知道是否收到了空字符,并且它的行为类似于环形缓冲区,仅适用于固定长度。 - HansPeterLoft
1
请看我用于接收任意GPS数据的代码此处。它使用了HAL,但是跳过了中断。如果我再次做这件事,我会放弃HAL并选择纯CMSIS。 - Paulo Soares
IMO 对于16字节的DMA不值得。 - 0___________
@Paulo Soares - 裸寄存器,CMSIS 只是一堆定义和一些内联函数。 - 0___________
显示剩余3条评论
5个回答

7

HAL_UART_Receive_IT() 不应该从中断处理程序中这样调用,而是通过中断启动接收固定数量的字节。

一个可能的解决方法是在 /* USER CODE BEGIN USART1_IRQn 1 */ 部分中,在HAL_UART_IRQHandler() 完成后检查输入缓冲区。当处理完一个命令时,您可以将处理结构中的 pRxBuffPtrRxXferCount 重置为其原始值,以重新从缓冲区的开头开始。

另一种可怕的可能解决方案是使用缓冲区大小为1调用HAL_UART_Receive_IT(),并设置一个HAL_UART_RxCpltCallback()处理程序,在每次检查接收到的字节时调用HAL_UART_Receive_IT()

当然,您也可以像PeterJ和其他人(总是)建议的那样不使用HAL。

  • 您已经实现了引脚和中断设置,请先不要更改它们。
  • 根据参考手册计算UART->BRR的值,或从hal中复制相关代码。
  • 设置UART->CR1=USART_CR1_RE|USART_CR1_TE|USART_CR1_UE|USART_CR1_RXNEIE;,现在您正在接收中断。
  • 在中断函数中,将UART->SR读入一个临时变量,并检查它。
  • 当有等待接收的字节时,读取UART->DR,否则进行错误处理(稍后)。
  • 当上述内容起作用时,摆脱其余的HAL调用。

中断响应和处理时间在嵌入式应用程序中通常是关键的,而HAL只会浪费很多时间。


谢谢,我会先尝试解决方法。但是如何重置pRxBuffPtr和RxXferCount?我的意思是指针需要一个地址吗? - HansPeterLoft
@berendi 在HAL IRQ处理程序中被调用。你看过这个处理程序是如何实现的吗?该函数执行从U(S)ART的物理读取,并将数据放入接收缓冲区。无论如何,这是一个可怕的实现。 - 0___________
@PeterJ_01:在哪个HAL版本中?我检查了一些存储在我的驱动器上的版本,但是HAL_UART_Receive_IT()从未在HAL_UART_IRQHandler()或HAL库中的任何其他地方被调用。实际读取USART->DR寄存器的操作发生在UART_Receive_IT()(没有HAL_前缀)中,该函数被声明为静态函数,无法从用户程序中访问。 - followed Monica to Codidact
@HansPeterLoft:把它们设置为你的接收缓冲区的起始位置(即指针)和长度。 - followed Monica to Codidact

6

普通的HAL库对于连续接收或长度不同的命令是无用的。

如果您已经安装了完整的HAL软件包,可以查看低级接口的示例。

Projects\STM32F411RE-Nucleo\Examples_LL\USART\USART_Communication_Rx_IT_Continuous

主要是将你的USART设置为连续接收:

    void Configure_USART(void) {    
        /* (1) Enable GPIO clock and configures the USART pins *********************/

        /* Enable the peripheral clock of GPIO Port */
        USARTx_GPIO_CLK_ENABLE();

        /* Configure Tx Pin as : Alternate function, High Speed, Push pull, Pull up */
        LL_GPIO_SetPinMode(USARTx_TX_GPIO_PORT, USARTx_TX_PIN, LL_GPIO_MODE_ALTERNATE);
        USARTx_SET_TX_GPIO_AF();
        LL_GPIO_SetPinSpeed(USARTx_TX_GPIO_PORT, USARTx_TX_PIN, LL_GPIO_SPEED_FREQ_HIGH);
        LL_GPIO_SetPinOutputType(USARTx_TX_GPIO_PORT, USARTx_TX_PIN, LL_GPIO_OUTPUT_PUSHPULL);
        LL_GPIO_SetPinPull(USARTx_TX_GPIO_PORT, USARTx_TX_PIN, LL_GPIO_PULL_UP);

        /* Configure Rx Pin as : Alternate function, High Speed, Push pull, Pull up */
        LL_GPIO_SetPinMode(USARTx_RX_GPIO_PORT, USARTx_RX_PIN, LL_GPIO_MODE_ALTERNATE);
        USARTx_SET_RX_GPIO_AF();
        LL_GPIO_SetPinSpeed(USARTx_RX_GPIO_PORT, USARTx_RX_PIN, LL_GPIO_SPEED_FREQ_HIGH);
        LL_GPIO_SetPinOutputType(USARTx_RX_GPIO_PORT, USARTx_RX_PIN, LL_GPIO_OUTPUT_PUSHPULL);
        LL_GPIO_SetPinPull(USARTx_RX_GPIO_PORT, USARTx_RX_PIN, LL_GPIO_PULL_UP);

        /* (2) NVIC Configuration for USART interrupts */
        /*  - Set priority for USARTx_IRQn */
        /*  - Enable USARTx_IRQn */
        NVIC_SetPriority(USARTx_IRQn, 0);  
        NVIC_EnableIRQ(USARTx_IRQn);

        /* (3) Enable USART peripheral clock and clock source ***********************/
        USARTx_CLK_ENABLE();

        /* (4) Configure USART functional parameters ********************************/
        /* TX/RX direction */
        LL_USART_SetTransferDirection(USARTx_INSTANCE, LL_USART_DIRECTION_TX_RX);

        /* 8 data bit, 1 start bit, 1 stop bit, no parity */
        LL_USART_ConfigCharacter(USARTx_INSTANCE, LL_USART_DATAWIDTH_8B, LL_USART_PARITY_NONE, LL_USART_STOPBITS_1);

        /* No Hardware Flow control */
        /* Reset value is LL_USART_HWCONTROL_NONE */
        // LL_USART_SetHWFlowCtrl(USARTx_INSTANCE, LL_USART_HWCONTROL_NONE);

        /* Oversampling by 16 */
        /* Reset value is LL_USART_OVERSAMPLING_16 */
        // LL_USART_SetOverSampling(USARTx_INSTANCE, LL_USART_OVERSAMPLING_16);

        /* Set Baudrate to 115200 using APB frequency set to 100000000/APB_Div Hz */
        /* Frequency available for USART peripheral can also be calculated through LL RCC macro */
        /* Ex :
            Periphclk = LL_RCC_GetUSARTClockFreq(Instance); or 
            LL_RCC_GetUARTClockFreq(Instance); depending on USART/UART instance
  
            In this example, Peripheral Clock is expected to be equal to 
            100000000/APB_Div Hz => equal to SystemCoreClock/APB_Div
        */
        LL_USART_SetBaudRate(USARTx_INSTANCE, SystemCoreClock/APB_Div, LL_USART_OVERSAMPLING_16, 115200); 

        /* (5) Enable USART *********************************************************/
        LL_USART_Enable(USARTx_INSTANCE);
    }

USART中断处理程序应该如下:

    void USARTx_IRQHandler(void)
    {
      /* Check RXNE flag value in SR register */
      if(LL_USART_IsActiveFlag_RXNE(USARTx_INSTANCE) && LL_USART_IsEnabledIT_RXNE(USARTx_INSTANCE))
      {
        /* RXNE flag will be cleared by reading of DR register (done in call) */
        /* Call function in charge of handling Character reception */
        USART_CharReception_Callback();
      }
      else
      {
        /* Call Error function */
        Error_Callback();
      }
    }

最后要设置的是回调函数。
void USART_CharReception_Callback(void);

您可以将字节放入缓冲区并在主循环中处理,或者放在任何您想要的地方。


3
自从今天我遇到这个问题后,就找不到一个好的解决方案,因此我想提供一个非常简单的解决方案,使用大部分HAL但避免所描述的问题...
我的方法的简短版本是:
void HAL_UART_MspInit(UART_HandleTypeDef* uartHandle)的最后一个用户代码部分(如果使用多个USART实例,则为相应的实例),使用以下内容启用IRQ:
__HAL_UART_ENABLE_IT(&huartx, UART_IT_RXNE);

然后将所需的代码放入您的中断函数中:

void USART3_IRQHandler(void)  {    
    /* USER CODE BEGIN USART3_IRQn 0 */
    CallMyCodeHere();
    return;  // To avoid calling the default HAL handler at all 
             // (in case you want to save the time)
    /* USER CODE END USART3_IRQn 0 */
    HAL_UART_IRQHandler(&huart3);  // This is the CubeMX generated HAL handler
    /* USER CODE BEGIN USART3_IRQn 1 */
    /* USER CODE END USART3_IRQn 1 */ 
}

不要在任何地方使用HAL_UART_Receive_IT,它会禁用IRQ,如果你想在每次接收时被调用,你需要重新启用它。
长篇版本可以在我的帖子这里中找到...

3

以下是通过中断接收数据和检测空闲线的完整示例:

  1. main.c中启用接收中断和空闲线检测:
  /* USER CODE BEGIN USART2_Init 2 */
  __HAL_UART_ENABLE_IT(&huart2, UART_IT_RXNE);  // enable receive intterupts 
  __HAL_UART_ENABLE_IT(&huart2, UART_IT_IDLE);  // enable idle line detection 
  /* USER CODE END USART2_Init 2 */
  1. stm32f4xx_it.c中的USARTx_IRQHandler函数内,整理出闲置行事件:
void USART2_IRQHandler(void)
{
  /* USER CODE BEGIN USART2_IRQn 0 */
  if (__HAL_UART_GET_FLAG(&huart2, UART_FLAG_IDLE)) { 
    __HAL_UART_CLEAR_IDLEFLAG(&huart2); // taken from https://electronics.stackexchange.com/questions/471272/setting-up-stm32-timer-for-uart-idle-detection#comment1353999_480556
    uart2_idleHandler();
  } else {
    uart2_handler();
  }
  return;
  /* USER CODE END USART2_IRQn 0 */
  HAL_UART_IRQHandler(&huart2);
  /* USER CODE BEGIN USART2_IRQn 1 */
  /* USER CODE END USART2_IRQn 1 */
}

测试:

  1. 创建以下处理程序:
char my_uart_buffer[256];
int my_uart_buffer_index = 0;

void uart2_handler(void){
  char buff; 
  HAL_UART_Receive (&huart2, (uint8_t *)&buff, 1, 400);
  my_uart_buffer[my_uart_buffer_index++] = buff;
}

void uart2_idleHandler(){
  my_uart_buffer_index = 0; 
}
  1. 在您的电脑上打开串口客户端,并设置为115200波特率,8N1。
  2. 将断点设置为uart2_idleHandler()
  3. 发送“Hello world”。
  4. 当断点命中时,您可以通过p/c *my_uart_buffer@20检查您的缓冲区。

示例项目

这里是在STM32F407 Discovery板上运行的完整示例。


1

你可以使用HAL让它正常工作!虽然它可能不像其他实现那样优雅,但是它是可行的。

你需要创建一个错误处理函数,然后调用HAL_UART_RCV_IT的函数必须在发生溢出错误时刷新UART RX。

此外,我使用两个缓冲区。当中断正在填充一个缓冲区时,主循环正在清空另一个缓冲区。

这是它如何对我有效地工作:

typedef enum
{
  UARTREADY = 0,
    UARTBUSY  = 1,
    UARTDATA  = 2,
    UARTERROR = 3
} enumUartStatus;

while(1){
    if(UARTREADY == isUsart3RxReady()){

        Usart3RxBuffer((char *)&SOMRxBytesBuffer[use_buffer_index], RCV_BUFFER_BANK_SIZE);   // receive bytes in the raw buffer

        if(use_buffer_index == RCV_BUFFER_BANK1_INDEX){         
            use_buffer_index = RCV_BUFFER_BANK2_INDEX;
            rxb1_stats++;
        }else{  
            use_buffer_index = RCV_BUFFER_BANK1_INDEX;
            rxb2_stats++;
        }

    }
}

void HAL_UART_ErrorCallback(UART_HandleTypeDef *huart)
{
    if(huart == NULL){
        return;
    }

    if(huart->Instance == USART3){

        if(HAL_UART_GetError(huart) == HAL_UART_ERROR_FE){
            Usart3Ready = UARTREADY;
        }else{      
            Usart3Ready = UARTERROR;
        }
    }
}

void Usart3RxBuffer(char *buffer, unsigned short rxbuffersize){
  /* Reset transmission flag */

    if(Usart3Ready != UARTREADY)
    {
        return;
    }

    if(HAL_UART_GetState(&huart3) == HAL_UART_STATE_READY){

        /*##-3- Put UART peripheral in reception process ###########################*/
        if (HAL_UART_Receive_IT(&huart3, (uint8_t *)buffer, rxbuffersize) != HAL_OK)
        {
            // TODO: Error_Handler();
            DEBUG_TRACE(DEBUG_MSK_MAIN, "UART3 error starting receiver!\r\n");
        }else{

            // An interrupt HAL_UART_ErrorCallback hit right here !!!!
            // There is an overrun error happening here so we have to retry
            // this is because we are using the Receive_IT in a continous communication and there is no handshake or flow control
            if(Usart3Ready != UARTERROR){
                /* Busy waiting to receive bytes */
                Usart3Ready = UARTBUSY;
            }
        }
    }

    if(Usart3Ready == UARTERROR){
        HAL_UART_AbortReceive_IT(&huart3);
        Usart3Ready = UARTREADY;
    }
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接