STM32 Uart 接收不定长数据

      前面讲了Uart三种不同的方式接收数据,请参照《STM32 Uart及其配置》《STM32 Uart中断接收》《STM32 Uart DMA方式接收数据》,但是,它们都需要指定数据的长度,但实际应用中,会出现不定长度的数据,比如,某些模块的@命令,那么,如何接收不定长度的数据呢?今天,我们就来扒一扒STM32 Uart 变长数据的接收。

      问题来了,变长数据包,我们如何确定数据包的长度?

      带着问题思考,我们可以得出以下几种思路:

      1. 制定严格的通信协议,带有一串特殊字符的数据包头、长度、校验、包尾等,比如,一开始收到:0xAA-0xBB-0xCC-0xDD-0xEE,表示数据包头,接下来两个字节是数据包长度,接下来是数据包内容,接下来是校验值,接下来是结尾。

      2. 使用定时器,判定在指定时间内没有收到数据,就算一个数据包结束。可以在收到一个数据后,开启定时器,定时器的超时时间设定为1~2个数据之间的时间,若在此期间有数据,则判定为数据包未结束,重载定时器,若此期间无数据,则判定为数据包结束,关闭定时器。

      这篇章里不讨论1和2,因为STM32有更方便的处理方式,STM32能够检测空闲。

      看RM0033,空闲,可以理解为,下一个起始位之前的全部1,也就是Rx线上高电平。

      会不会和数据0xFF冲突呢?不会,因为数据0xFF有起始位,空闲没有。

      看下来这个手画的图,中间那一段高电平,就是空闲。

      

      当空闲被检测到时,如果IDLEIE位设置,就会产生一个IDLE中断。

      我们捕获这个中断,并处理它就行了。

      在空闲中断产生之前,我们收到的数据,怎么处理呢?先收着,存进一个Buffer里面!

      如前面讲的《STM32 Uart中断接收》《STM32 Uart DMA方式接收数据》,接收数据,也可以用中断和DMA两种不同的方式。我们把两种方式都写进代码里面,用两个不同的宏定义区分开来。

      我们就写一个,收到数据,往串口发送出收到的数据长度,以及完整的数据吧。

      理论讲完,就开始实践吧!

      参照《STM32 Uart DMA方式接收数据》,建立工程,生成代码。

   在 usart.c 里面写这样一段代码,#define RCV_RXNEIDLE_PROCESS 表示用RX中断方式接收数据,#define RCV_DMAIDLE_PROCESS 表示用DMA方式接收数据。这段代码主要用于选择接收数据的方式。同一时间只能使用一种方式,#error这里做了个限制,不允许同时打开这两个宏定义,否则会编译出错。

// select oneof two method
//#define RCV_RXNEIDLE_PROCESS
#define RCV_DMAIDLE_PROCESS

#if defined(RCV_DMAIDLE_PROCESS)&&defined(RCV_RXNEIDLE_PROCESS)
    #error "Don't Allow #define Two Micro at the same time, Check RCV_RXNEIDLE_PROCESS && RCV_DMAIDLE_PROCESS"
#endif

      接收数据,就定义一个数组来存放数据,就申请1024个字节的数组吧;

      数据要变长,就定义一个变量来表示接收到的数据长度;

      最后,再定义一个指针,用来指向存放数据的数组。

      在 usart.c 里面,这一断代码定义了所需要的数据。

uint8_t uart4Rx[UART4_BUF_MAX];      // 存放接收到的数据
uint8_t *pUart4Rx;    // 指向存认数据的数组
uint16_t uart4RxLength;     // 接收到数据的长度

      整个事件的流程就是,接收一段数据后,产生IDLE中断,IDLE中断服务例程里处理数据。

      那我们就看一下stm32f2xx_it.c里面UART4_IRQHandler()中断服务例程里面,对Idle中断是怎么处理的?

      看一下 HAL_UART_IRQHandler() 这个函数,它对Idle中断没有处理!那怎么办呢?自己写一段咯。

      在HAL_UART_IRQHandler() 函数加上这一段。

/* UART in mode Idle -------------------------------------------------*/
if(((isrflags & USART_SR_IDLE) != RESET) && ((cr1its & USART_CR1_IDLEIE) != RESET))
{
    HAL_UART_IdleCpltCallback(huart);      
    return;
}  

      这个回调函数HAL_UART_IdleCpltCallback(),仿照着在stm32f2xx_hal_uart.c里面加一个回调函数。

      后面,我们在 usart.c 里面重写它。

/**
  * @brief  Idle callbacks.
  * @param  huart pointer to a UART_HandleTypeDef structure that contains
  *                the configuration information for the specified UART module.
  * @retval None
  */
__weak void HAL_UART_IdleCpltCallback(UART_HandleTypeDef *huart)
{
  /* Prevent unused argument(s) compilation warning */
  UNUSED(huart);
  /* NOTE: This function Should not be modified, when the callback is needed,
           the HAL_UART_TxCpltCallback could be implemented in the user file
   */
}

 

      整个HAL_UART_IRQHandler()函数,是这样的。

void HAL_UART_IRQHandler(UART_HandleTypeDef *huart)
{
   uint32_t isrflags   = READ_REG(huart->Instance->SR);
   uint32_t cr1its     = READ_REG(huart->Instance->CR1);
   uint32_t cr3its     = READ_REG(huart->Instance->CR3);
   uint32_t errorflags = 0x00U;
   uint32_t dmarequest = 0x00U;

  /* If no error occurs */
  errorflags = (isrflags & (uint32_t)(USART_SR_PE | USART_SR_FE | USART_SR_ORE | USART_SR_NE));
  if(errorflags == RESET)
  {
    /* UART in mode Receiver -------------------------------------------------*/
    if(((isrflags & USART_SR_RXNE) != RESET) && ((cr1its & USART_CR1_RXNEIE) != RESET))
    {
      UART_Receive_IT(huart);
      return;
    }
  }  

  /* If some errors occur */
  if((errorflags != RESET) && (((cr3its & USART_CR3_EIE) != RESET) || ((cr1its & (USART_CR1_RXNEIE | USART_CR1_PEIE)) != RESET)))
  {
    /* UART parity error interrupt occurred ----------------------------------*/
    if(((isrflags & USART_SR_PE) != RESET) && ((cr1its & USART_CR1_PEIE) != RESET))
    {
      huart->ErrorCode |= HAL_UART_ERROR_PE;
    }
    
    /* UART noise error interrupt occurred -----------------------------------*/
    if(((isrflags & USART_SR_NE) != RESET) && ((cr3its & USART_CR3_EIE) != RESET))
    {
      huart->ErrorCode |= HAL_UART_ERROR_NE;
    }
    
    /* UART frame error interrupt occurred -----------------------------------*/
    if(((isrflags & USART_SR_FE) != RESET) && ((cr3its & USART_CR3_EIE) != RESET))
    {
      huart->ErrorCode |= HAL_UART_ERROR_FE;
    }
    
    /* UART Over-Run interrupt occurred --------------------------------------*/
    if(((isrflags & USART_SR_ORE) != RESET) && ((cr3its & USART_CR3_EIE) != RESET))
    {
      huart->ErrorCode |= HAL_UART_ERROR_ORE;
    }

    /* Call UART Error Call back function if need be --------------------------*/    
    if(huart->ErrorCode != HAL_UART_ERROR_NONE)
    {
      /* UART in mode Receiver -----------------------------------------------*/
      if(((isrflags & USART_SR_RXNE) != RESET) && ((cr1its & USART_CR1_RXNEIE) != RESET))
      {
        UART_Receive_IT(huart);
      }

      /* If Overrun error occurs, or if any error occurs in DMA mode reception,
         consider error as blocking */
      dmarequest = HAL_IS_BIT_SET(huart->Instance->CR3, USART_CR3_DMAR);
      if(((huart->ErrorCode & HAL_UART_ERROR_ORE) != RESET) || dmarequest)
      {
        /* Blocking error : transfer is aborted
           Set the UART state ready to be able to start again the process,
           Disable Rx Interrupts, and disable Rx DMA request, if ongoing */
        UART_EndRxTransfer(huart);
        
        /* Disable the UART DMA Rx request if enabled */
        if(HAL_IS_BIT_SET(huart->Instance->CR3, USART_CR3_DMAR))
        {
          CLEAR_BIT(huart->Instance->CR3, USART_CR3_DMAR);
          
          /* Abort the UART DMA Rx channel */
          if(huart->hdmarx != NULL)
          {
            /* Set the UART DMA Abort callback :
               will lead to call HAL_UART_ErrorCallback() at end of DMA abort procedure */
            huart->hdmarx->XferAbortCallback = UART_DMAAbortOnError;
            if(HAL_DMA_Abort_IT(huart->hdmarx) != HAL_OK)
            {
              /* Call Directly XferAbortCallback function in case of error */
              huart->hdmarx->XferAbortCallback(huart->hdmarx);
            }
          }
          else
          {
            /* Call user error callback */
            HAL_UART_ErrorCallback(huart);
          }
        }
        else
        {
          /* Call user error callback */
          HAL_UART_ErrorCallback(huart);
        }
      }
      else
      {
        /* Non Blocking error : transfer could go on.
           Error is notified to user through user error callback */
        HAL_UART_ErrorCallback(huart);
        huart->ErrorCode = HAL_UART_ERROR_NONE;
      }
    }
    return;
  } /* End if some error occurs */

// 这里,就是对Idle中断的处理啊啊啊啊啊!!!
     /* UART in mode Idle -------------------------------------------------*/
    if(((isrflags & USART_SR_IDLE) != RESET) && ((cr1its & USART_CR1_IDLEIE) != RESET))
    {
        HAL_UART_IdleCpltCallback(huart);      
      return;
    }  

  /* UART in mode Transmitter ------------------------------------------------*/
  if(((isrflags & USART_SR_TXE) != RESET) && ((cr1its & USART_CR1_TXEIE) != RESET))
  {
    UART_Transmit_IT(huart);
    return;
  }
  
  /* UART in mode Transmitter end --------------------------------------------*/
  if(((isrflags & USART_SR_TC) != RESET) && ((cr1its & USART_CR1_TCIE) != RESET))
  {
    UART_EndTransmit_IT(huart);
    return;
  }
}

如《STM32 Uart中断接收》所写,要响应RX中断接收,必须使能RX中断;

如《STM32 Uart DMA方式接收数据》所写,要响应DMA接收,必须使能DMA;

要响应IDLE中断,必须使能 IDLE 中断。

看这个USR_UartInit()函数:

void USR_UartInit(void)
{
// 这里是RX中断处理
#ifdef RCV_RXNEIDLE_PROCESS
    pUart4Rx = uart4Rx;      // 指针指向数组
    uart4RxLength = 0;      // 长度初始为0
    HAL_UART_Receive_IT(&huart4, pUart4Rx, 1);    // 使能Rx中断,并设置长度为1,数据置于uart4Rx中,准备接收第一个数据
#endi

// 这里是DMA中断处理;
#ifdef RCV_DMAIDLE_PROCESS
    uart4RxLength = 0;     // 长度初始为0
    HAL_UART_Receive_DMA(&huart4, uart4Rx, UART4_BUF_MAX);    // 使能DMA,最长为1024字节,数据置于uart4Rx中,准备接收一串数据
#endif
    
    __HAL_UART_ENABLE_IT(&huart4, UART_IT_IDLE);       // 使能 IDLE中断
}

      这个函数在main()里面,初始化串口和DMA之后调用。

  MX_DMA_Init();
  MX_UART4_Init();
  /* USER CODE BEGIN 2 */
    USR_UartInit();
  /* USER CODE END 2 */

      在《STM32 Uart中断接收》里面讲了,响应RX中断,最终会到HAL_UART_RxCpltCallback里面处理;

      在《STM32 Uart DMA方式接收数据》里面讲了,响应DMA中断,最终也会到HAL_UART_RxCpltCallback里面处理。

      RX中断接收:来一次中断,指针要后移,指向下一个数据,上一个数据已经接收,数据长度自加,然后再次使能Rx中断,准备接收下一个数据。

      DMA接收:再次使能DMA,准备接收下一串数据。

      DMA产生中断的条件,1.接收满UART_BUF_MAX,2.设置DMA Disable。

void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
#ifdef RCV_RXNEIDLE_PROCESS
    pUart4Rx++;    // 指针指向下一个数据,上一个数据接收了,才会产生这次中断
    uart4RxLength++;   // 接收到上一个数据,长度要+1
    HAL_UART_Receive_IT(&huart4, pUart4Rx, 1);    // 继续使能RX中断,准备接收。
#endif

#ifdef RCV_DMAIDLE_PROCESS
    HAL_UART_Receive_DMA(&huart4, uart4Rx, UART4_BUF_MAX);   // DMA产生Complete中断条件:1.接收满UART_BUF_MAX,2.设置DMA Disable。再次使能DMA
#endif
}

 

      接收完一串数据,接下来,轮到 Idle 中断出手了。

      Idle中断处理,会调用回调函数HAL_UART_IdleCpltCallback(),我们就实现它。

uint8_t RxLenHi, RxLenlo;    // 长度高位,低位
void HAL_UART_IdleCpltCallback(UART_HandleTypeDef *huart)
{
        __HAL_UART_CLEAR_IDLEFLAG(huart);

// RX
#ifdef RCV_RXNEIDLE_PROCESS
    RxLenHi = (uint8_t)(uart4RxLength>>8);
    RxLenlo =  (uint8_t)(uart4RxLength);           
    HAL_UART_Transmit(huart, &RxLenHi, 1, 1000);
    HAL_UART_Transmit(huart, &RxLenlo, 1, 1000);
    HAL_UART_Transmit(huart, uart4Rx, uart4RxLength, 1000);
    pUart4Rx = uart4Rx;       // 接收完一条命令,指针指向数组头,重新接收
    uart4RxLength = 0;         // 长度设置为0
#endif

// DMA
#ifdef RCV_DMAIDLE_PROCESS
    uart4RxLength = UART4_BUF_MAX-__HAL_DMA_GET_COUNTER(&hdma_uart4_rx);     // 获取长度
    RxLenHi = (uint8_t)(uart4RxLength>>8);
    RxLenlo =  (uint8_t)(uart4RxLength);
    HAL_UART_Transmit(huart, &RxLenHi, 1, 1000);
    HAL_UART_Transmit(huart, &RxLenlo, 1, 1000);
    HAL_UART_Transmit(huart, uart4Rx, uart4RxLength, 1000);
    __HAL_DMA_DISABLE(&hdma_uart4_rx);       // DMA计数重载,会产生DMA完成中断,
                                             // 参考RM0033-9.3.13,当DMA关闭时,会产生DMA完成中断
                                            // 参考RM0033-DMA_SxNDTR,当DMA关闭时,重载计数器
#endif
}

 

      现在,捋一下整个过程:

      RX变长数据接收:

        1. 初始化时使能RX中断,时刻准备着接收数据。

        2. 第一个数据接收,触发RX中断,执行HAL_UART_RxCpltCallback(),函数里,接收的数据指向数组下一个存储空间,接收的长度+1,使能RX中断,准备下一数据接收。

      下一个数据接收,触发RX中断,执行HAL_UART_RxCpltCallback(),函数里,接收的数据指向数组下一个存储空间,接收的长度+1,使能RX中断,准备下一数据接收。

    …… ……

      3. 整条命令接收完成,触发空闲中断,执行行HAL_UART_IdleCpltCallback(),函数里,处理接收到的数据,指针指向uart4Rx[]的第一个数据。

      DMA变长数据接收:

       1. 初始化时使能DMA中断,时刻准备着接收数据。

       2. 有数据,触发DMA事件,有数据,触发DMA事件,有数据,触发DMA事件... ... ...这个过程是全自动的,不需要我们参与,DMA自动把数据接收了,存进uart4Rx[]里。

    3. 整条命令接收完成,触发空闲中断,执行行HAL_UART_IdleCpltCallback(),函数里,处理接收到的数据, __HAL_DMA_DISABLE()关闭DMA,关闭DMA会引起DMA计数重载,且触发DMA Complete 中断,中断服务例程会执行HAL_UART_IdleCpltCallback(),函数里,使能DMA,准备接收下一条命令。


      重新编译,烧录,运行便可;

      返回的第1个字节,长度高位 0x00,返回的第2个字节,长度低位 0x0B,0x000B = 11,我们发的,正好11字节;

      后面紧跟着 11 22 33 44 55 66 77 88 ff ff 99,就是发送区发的数据。

      STM32收到的数据,是这样处理的。

    HAL_UART_Transmit(huart, &RxLenHi, 1, 1000);      // 发送长度高位
    HAL_UART_Transmit(huart, &RxLenlo, 1, 1000);      //  发送长度低位
    HAL_UART_Transmit(huart, uart4Rx, uart4RxLength, 1000);    // 发送接收到的数据

        等等!这样有没有感觉超麻烦?有没有一种简单的方法,像C语言的printf,直接在串口调试助手输出字符串呢?

       请看下一篇《STM32 Uart 实现printf函数》。

    

     整个工程及代码呢,请上百度网盘上下载:

     链接:https://pan.baidu.com/s/19usUcgZPX8cCRTKt_NPcfg

     密码:07on

     文件夹:\Stm32CubeMx\Code\UartRx_Any.rar

 

上一篇:《STM32 Uart DMA方式接收数据

下一篇:《STM32 Uart 实现printf函数

回目录:《前言

      

   

 

 

STM32CubeMX中,实现串口接收不定数据的方法可以通过使用串口中断函数来实现。首先,需要定义全局变量来存储接收到的数据,如引用所示,其中date数组用于存储接收到的数据,temp变量用于暂存每次接收到的字符,i变量用于记录接收到的字符数量。 然后,在串口中断函数中,可以通过判断接收到的字符是否为换行符和归位符来判断一条完整的数据是否接收完成,如引用所示。当接收到的字符满足条件时,可以将接收到的数据通过串口发送出去,并对相关变量进行复位,以便接收下一条数据。 需要注意的是,通过串口中断函数接收不定数据时,需要使用HAL_UART_Receive_IT函数来开启串口中断接收模式,并在中断函数中进行数据的处理和判断。同时,也要确保在主函数中初始化相关串口和中断的配置。 综上所述,通过使用串口中断函数和相关全局变量来实现STM32CubeMX串口不定数据接收。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* *2* *3* [stm32cubemx 串口接收不定数据](https://blog.csdn.net/m0_52521883/article/details/114559071)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 100%"] [ .reference_list ]
评论 19
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值