【问题标题】:STM32 HAL USART receive by interruptSTM32 HAL USART 通过中断接收
【发布时间】:2018-02-22 05:47:16
【问题描述】:

我无法通过 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_UART_Receive_IT 然后调用 HAL irq 处理程序,它会再次调用它。做裸寄存器或 HAL,而不是两者兼而有之。无论如何,对于像 uart 这样的简单外围设备的 HAL?
  • 复制缓冲区实现是什么意思?我想在 HAL 中实现这一点,但我真的没有找到解决方案。 DMA 会很好,但我需要为每个命令设置一个固定长度或使用超时,而 HAL 不支持 STM32F303K8T6。
  • 哈尔做到了。如果你使用 hal,你不应该对自助餐做任何事情。我会自己实现它,但它是你的代码,没有这个愚蠢的库。你的消息有多长?如果做空,则没有 DMA 的意义。
  • 这些消息只有大约 16 个字符。如果我只使用 HAL_UART_Receive_IT,它会将字符写入 rx_data 缓冲区,但我再也看不到我是否收到了空字符,它就像一个环形缓冲区,只适用于固定长度。
  • 看看我用来接收任意 GPS 数据的代码here。它使用 HAL,但缩短了中断。如果我再做一次,我会抛弃 HAL 并使用普通的 CMSIS。

标签: stm32 hal


【解决方案1】:

HAL_UART_Receive_IT() 并不是要以这种方式从中断处理程序中调用,而是要开始通过中断接收固定字节数。

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

另一个horrible可能的解决方法是调用HAL_UART_Receive_IT(),缓冲区大小为1,并设置一个HAL_UART_RxCpltCallback()处理程序,每次检查接收到的字节,然后再次调用HAL_UART_Receive_IT()必要时。

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

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

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

【讨论】:

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

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

如果您安装了完整的 HAL 包,您可以查看 LowLevel 接口的示例。

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 IT 处理程序应如下所示

    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,如果你想在每次接收时都被调用,你需要重新启用它。

    长版可以在我的帖子here...

    【讨论】:

      【解决方案4】:

      您可以使用 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;
          }
      }
      

      【讨论】:

        【解决方案5】:

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

        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. 从USARTx_IRQHandler in stm32f4xx_it.c 中整理出空闲线路事件:
        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. 在您的 PC 中打开一个串口客户端,设置为 115200 波特,8N1。
        2. 将断点设置为uart2_idleHandler()。
        3. 发送“Hello world”。
        4. 当断点命中时,您可以通过p/c *my_uart_buffer@20 检查缓冲区。

        示例项目

        Here 是在 STM32F407 探索板上运行的完整示例。

        【讨论】:

          猜你喜欢
          • 2012-09-14
          • 2014-08-14
          • 1970-01-01
          • 2019-08-10
          • 2018-01-09
          • 2021-05-07
          • 2022-01-10
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多