STM32F4串口使用心得,分享以抛砖引玉

2019-07-20 23:51发布

本帖最后由 13327286788 于 2016-2-2 20:13 编辑

      本人主要是搞硬件的,对编程并不在行,但因为使用到了STM32F407开发板,有时又不得不写一些程序。最开始写的时候肯定是针对串口,因为它最常用,而且够简单!柿子总是捡软的捏!本人使用MDK5.12,库函数使用STM32Cube_FW_F4_V1.4.0(后续版本无法改动)。由于要用到串口接收诸如$xxxxxx,xxxx,.... 等消息或命令并解析,本人网上一直找程序该如何解析这类消息或命令,但一直没有找到,于是只有自己按着笨方法解析:串口采用中断方式接收,并设置环形接收缓存,缓存的大小根据需要确定。先来看一下STM32Cube_FW_F4_V1.4.0库中被改动过的UART类型结构定义。
typedef struct
{
  USART_TypeDef                 *Instance;        /*!< UART registers base address        */

  UART_InitTypeDef              Init;             /*!< UART communication parameters      */

  uint8_t                       *pTxBuffPtr;      /*!< Pointer to UART Tx transfer Buffer */

  uint16_t                      TxXferSize;       /*!< UART Tx Transfer size              */

  uint16_t                      TxXferCount;      /*!< UART Tx Transfer Counter           */

  uint8_t                       *pRxBuffPtr;      /*!< Pointer to UART Rx transfer Buffer */

  uint16_t                      RxXferSize;       /*!< UART Rx Transfer size              */

  uint16_t                      RxXferCount;      /*!< UART Rx Transfer Counter           */  
        
  uint16_t                      RxIndex;          /* 用户添加: 用于接收时索引         */

  uint8_t                       **pTxQueue;    /* 用户添加:存放发送数据首地址的列队的首地址*/

  uint16_t                     *pTxXferSize;     /* 用户添加:存放发送数据的个数的列队的首地址*/

  uint16_t                     TxQueueCount;     /* 用户添加:发送列队添加计数器          */

  uint16_t                     TxQueueSize;      /* 用户添加:发送列队总大小          */

  uint16_t                     TxQueueIndex;     /* 用户添加: 发送列队已处理索引         */

  uint32_t                     TxSynStart;        /* 用户添加:用于同步列队各变量变化的参数一   */

  uint32_t                     TxSynEnd;         /* 用户添加:用于同步列队各变量变化的参数二     */

  DMA_HandleTypeDef             *hdmatx;          /*!< UART Tx DMA Handle parameters      */

  DMA_HandleTypeDef             *hdmarx;          /*!< UART Rx DMA Handle parameters      */

  HAL_LockTypeDef               Lock;             /*!< Locking object                     */

  __IO HAL_UART_StateTypeDef    State;            /*!< UART communication state           */

  __IO uint32_t                 ErrorCode;        /*!< UART Error code                    */

}UART_HandleTypeDef;
上面类型定义中注释有“用户添加”的为我自己添加的成员。对成员,我推荐只加不减,这样就不会影响其他程序调用。以下是改动过的库函数
static HAL_StatusTypeDef UART_Receive_IT(UART_HandleTypeDef *huart)
{
  uint16_t* tmp;
  uint32_t tmp1 = 0;

  tmp1 = huart->State;
  if((tmp1 == HAL_UART_STATE_BUSY_RX) || (tmp1 == HAL_UART_STATE_BUSY_TX_RX))
  {
    if(huart->Init.WordLength == UART_WORDLENGTH_9B)
    {
      tmp = (uint16_t*) huart->pRxBuffPtr;
      if(huart->Init.Parity == UART_PARITY_NONE)
      {
        *tmp = (uint16_t)(huart->Instance->DR & (uint16_t)0x01FF);
        huart->pRxBuffPtr += 2;
      }
      else
      {
        *tmp = (uint16_t)(huart->Instance->DR & (uint16_t)0x00FF);
        huart->pRxBuffPtr += 1;
      }
    }
    else
    {
      if(huart->Init.Parity == UART_PARITY_NONE)
      {
        *huart->pRxBuffPtr++ = (uint8_t)(huart->Instance->DR & (uint8_t)0x00FF);
      }
      else
      {
        *huart->pRxBuffPtr++ = (uint8_t)(huart->Instance->DR & (uint8_t)0x007F);
      }
    }

    if(--huart->RxXferCount == 0)
    {
//      __HAL_UART_DISABLE_IT(huart, UART_IT_RXNE);

//      /* Check if a transmit process is ongoing or not */
//      if(huart->State == HAL_UART_STATE_BUSY_TX_RX)
//      {
//        huart->State = HAL_UART_STATE_BUSY_TX;
//      }
//      else
//      {
//        /* Disable the UART Parity Error Interrupt */
//        __HAL_UART_DISABLE_IT(huart, UART_IT_PE);

//        /* Disable the UART Error Interrupt: (Frame error, noise error, overrun error) */
//        __HAL_UART_DISABLE_IT(huart, UART_IT_ERR);

//        huart->State = HAL_UART_STATE_READY;
//      }
//      HAL_UART_RxCpltCallback(huart);
        /*以上注释掉的为原库代码,以下两行为用户添加的代码, 修改后,接收缓存为环形缓存。*/
        huart->RxXferCount = huart->RxXferSize;//用户修改,非原库代码
        huart->pRxBuffPtr -= huart->RxXferSize;//用户修改,非原库代码
                        
      return HAL_OK;
    }
    return HAL_OK;
  }
  else
  {
    return HAL_BUSY;
  }
}

在主函数中:
#define Uart1RxBufferSize   2000   //环形接收缓存大小
#define Uart1TxQueueSize    500   //发送列队总大小
uint8_t Uart1RxBuffer[Uart1RxBufferSize];//环形接收缓存数组
uint8_t *Uart1TxQueue[Uart1TxQueueSize]; //发送列队地址数组(数组中存放的是要发送数据的首地址)
uint16_t Uart1TxferSize[Uart1TxQueueSize];//发送列队尺寸数组(数组中存放的是没一个列队指向的数据大小)

下面是串口初始化:
uart1.Instance = USART1;
uart1.Init.BaudRate = 19200;
uart1.Init.WordLength = UART_WORDLENGTH_8B;
uart1.Init.StopBits = UART_STOPBITS_1;
uart1.Init.Parity = UART_PARITY_NONE;
uart1.Init.HwFlowCtl = UART_HWCONTROL_NONE;
uart1.Init.Mode = UART_MODE_TX_RX;
uart1.State = HAL_UART_STATE_RESET;
uart1.pTxQueue = Uart1TxQueue;
uart1.pTxXferSize = Uart1TxferSize;
uart1.TxQueueSize = Uart1TxQueueSize;
uart1.TxQueueCount = Uart1TxQueueSize;
uart1.TxQueueIndex = 0;
uart1.TxSynStart = 0;
uart1.TxSynEnd = 0;
if (HAL_UART_Init(&uart1) != HAL_OK)
{
      Error_Handler();
}
/*启动串口中断接收*/
if (HAL_UART_Receive_IT(&uart1, (uint8_t *) Uart1RxBuffer, Uart1RxBufferSize) != HAL_OK)
{
    Error_Handler();
}

主循环中:
while(1)
{
     /*在串口接收中断函数中,每接收一个字节uart1.RxXferCount就会减一,因此可以根据
       uart1.RxIndex != uart1.RxXferSize - uart1.RxXferCount来判断是否存在已经接收
       但尚未处理的数据
      */
    if (uart1.RxIndex != uart1.RxXferSize - uart1.RxXferCount) //如果不相等,则说明存在尚未处理的数据
   {
       /*----------------此处添加用户代码起始--------------- */

        //对于$GPGGA等NMEA0183命令,可以根据‘$’、‘,’、‘ '、‘ '来判断和分离地址域和数据域,具体就不多说

        /*----------------此处添加用户代码结束--------------- */

        if (++uart1.RxIndex == uart1.RxXferSize) //每次循环只处理一个数据,如果环形缓存已满,则回到开始的地方,由于像贪吃蛇一样,因此环形缓存要足够大,以确保不会被咬尾巴
        {
                uart1.RxIndex = 0;
        }

   }
  if (uart2.RxIndex != uart2.RxXferSize - uart2.RxXferCount) //如果不相等,则说明存在尚未处理的数据
   {
       /*----------------此处添加用户代码起始--------------- */

        //对于$GPGGA等NMEA0183命令,可以根据‘$’、‘,’、‘ '、‘ '来判断和分离地址域和数据域,具体就不多说

        /*----------------此处添加用户代码结束--------------- */

        if (++uart2.RxIndex == uart2.RxXferSize) //每次循环只处理一个数据,如果环形缓存已满,则回到开始的地方,由于像贪吃蛇一样,因此环形缓存要足够大,以确保不会被咬尾巴
        {
                uart2.RxIndex = 0;
        }
   }
}

接下来说串口发送。串口发送时间比较长,如果不采用中断或DMA方式发送,总觉得不爽,因为程序在那里干等着!所以我推荐采用中断或DMA,当然有DMA最好是采用DMA。STM32Cube_FW_F4_V1.4.0中串口采用DMA发送数据的函数为HAL_StatusTypeDef HAL_UART_Transmit_DMA(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size),但是我们如果直接使用这个函数还是不太方便,比如我有多个数据块要连续发送,如果直接使用上面的函数,你发下一个数据块的时候必须等到前面一个数据块发送完才能进行,这又得等待,因此我就在UART_HandleTypeDef 类型中添加了
uint8_t                       **pTxQueue;    /* 用户添加:存放发送数据首地址的列队的首地址*/

uint16_t                      *pTxXferSize;     /* 用户添加:存放发送数据的个数的列队的首地址*/

uint16_t                       TxQueueCount;     /* 用户添加:发送列队添加计数器          */

uint16_t                       TxQueueSize;      /* 用户添加:发送列队总大小          */

uint16_t                       TxQueueIndex;     /* 用户添加: 发送列队已处理索引         */

uint32_t                       TxSynStart;        /* 用户添加:用于同步列队各变量变化的参数 一   */

uint32_t                       TxSynEnd;         /* 用户添加:用于同步列队各变量变化的参数二     */
等成员,用于构建发送列队。定义函数
void UART_Queue_Transmit_DMA(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size)
{
    uint8_t *pTx;
    uint16_t size;

        huart->TxSynStart++;//开始同步

        /*以下几条代码执行时可能会被中断,从而出现有些参数变化了,有些参数尚未变化,特别是针对当前串口使用的DMA
         * 发送完成中断,由于中断函数中会使用到这些参数,如果参数变化没有同步,则会出现错误。因此使用huart->TxSynStart
         * 和huart->TxSynEnd两个参数进行同步。在DMA发送完成中断中判断这里个参数是否相等来决定是否启动下一次发送,
         * 如果相等则启动下一次发送,否则不启动下一次发送,等中断退出到这里时,由本程序启动下一次发送。
         * */
        *huart->pTxQueue++ = pData;
        *huart->pTxXferSize++ = Size;
        if (--huart->TxQueueCount == 0)
        {
                huart->TxQueueCount = huart->TxQueueSize;
                huart->pTxQueue -= huart->TxQueueSize;
                huart->pTxXferSize -= huart->TxQueueSize;
        }

        huart->TxSynEnd++;//结束同步

        /*注意:当程序执行在这里之前,可能会执行中断程序中会根据huart->TxQueueIndex !=
         * huart->TxQueueSize - huart->TxQueueCount成立与否调用HAL_UART_Transmit_DMA函数,
         * 中断完成后退出中断,回到这里,此时UART已经处于就绪状态,接下来的判断会成立,但理应
         * 在本程序中执行的HAL_UART_Transmit_DMA函数在中断函数中已经执行,所以不能再次调用
         * HAL_UART_Transmit_DMA函数。中断是否发生,可以判断huart->TxQueueIndex !=
         * huart->TxQueueSize - huart->TxQueueCount的成立与否来确定,若没有发生中断,则继续
         * 调用HAL_UART_Transmit_DMA函数。
         */
        if (huart->State != HAL_UART_STATE_BUSY_TX
                        && huart->State != HAL_UART_STATE_BUSY_TX_RX)
        {
                if (huart->TxQueueIndex != huart->TxQueueSize - huart->TxQueueCount)
                {
                        pTx = (uint8_t *) *(huart->pTxQueue + huart->TxQueueCount
                                        - huart->TxQueueSize + huart->TxQueueIndex);
                        size = *(huart->pTxXferSize + huart->TxQueueCount
                                        - huart->TxQueueSize + huart->TxQueueIndex);

                        /*以下第一个判断必须位于HAL_UART_Transmit_DMA函数调用前*/
                        if (++huart->TxQueueIndex == huart->TxQueueSize)
                        {
                                huart->TxQueueIndex = 0;
                        }

                        HAL_UART_Transmit_DMA(huart, pTx, size);
                }
        }
}

DMA发送完成反馈函数
void HAL_UART_TxCpltCallback(UART_HandleTypeDef *huart)
{
   /* NOTE: This function Should not be modified, when the callback is needed,
            the HAL_UART_TxCpltCallback could be implemented in the user file
    */
        uint16_t tmp;
        uint8_t *pTx;
        uint16_t size;

        if (huart->State == HAL_UART_STATE_RESET)
        {
                huart->State == HAL_UART_STATE_READY;
                HAL_UART_Init(huart);
                tmp = huart->RxXferCount;
                HAL_UART_Receive_IT(huart, (uint8_t *) huart->pRxBuffPtr,
                                huart->RxXferSize);
                huart->RxXferCount = tmp;
        }
        else
        {
                if (huart->TxSynStart == huart->TxSynEnd)  //相等则启动下一发送
                {
                        if (huart->TxQueueIndex != huart->TxQueueSize - huart->TxQueueCount)
                        {
                                pTx = (uint8_t *) *(huart->pTxQueue + huart->TxQueueCount
                                                - huart->TxQueueSize + huart->TxQueueIndex);
                                size = *(huart->pTxXferSize + huart->TxQueueCount
                                                - huart->TxQueueSize + huart->TxQueueIndex);

                                /*以下第一个判断必须位于HAL_UART_Transmit_DMA函数调用前*/
                                if (++huart->TxQueueIndex == huart->TxQueueSize)
                                {
                                        huart->TxQueueIndex = 0;
                                }

                                HAL_UART_Transmit_DMA(huart, pTx, size);
                        }
                        else
                        {

                        }
                }
        }
}

主循环中:
while(1)
{
         if (HAL_GetTick() - Timer2 > 1000))//每秒执行一次
        {
            Timer2 = HAL_GetTick();
            /*一秒钟连续发送200次 ”1234567890“和"abcdefghijklmnopQrstuvwxyz",一共7200字节,并且是多个串口同时进行,表示毫无压力*/
            for (j = 0; j < 200; j++)
           {
                UART_Queue_Transmit_DMA(&uart1, (uint8_t *) "1234567890", COUNTOF("1234567890") - 1);
                UART_Queue_Transmit_DMA(&uart1, (uint8_t *) "abcdefghijklmnopQrstuvwxyz",COUNTOF("abcdefghijklmnopQrstuvwxyz") - 1);
                UART_Queue_Transmit_DMA(&uart4, (uint8_t *) "1234567890",COUNTOF("1234567890") - 1);
                UART_Queue_Transmit_DMA(&uart4,(uint8_t *) "abcdefghijklmnopQrstuvwxyz",COUNTOF("abcdefghijklmnopQrstuvwxyz") - 1);
                UART_Queue_Transmit_DMA(&uart5, (uint8_t *) "1234567890",COUNTOF("1234567890") - 1);
                UART_Queue_Transmit_DMA(&uart5,(uint8_t *) "abcdefghijklmnopQrstuvwxyz",COUNTOF("abcdefghijklmnopQrstuvwxyz") - 1);
          }
     }
}
以上就是我对串口使用的一点心得,拿出来分享,希望能抛砖引玉。


友情提示: 此问题已得到解决,问题已经关闭,关闭后问题禁止继续编辑,回答。