打印
[应用相关]

hal库串口dma卡死_STM32CubeMX之串口封装详解

[复制链接]
995|11
手机看帖
扫描二维码
随时随地手机跟帖
跳转到指定楼层
楼主
wowu|  楼主 | 2021-8-4 20:18 | 只看该作者 |只看大图 回帖奖励 |倒序浏览 |阅读模式
一.串口实例
​ 我们都知道,其实单片机最后其实都是对 串口相关的寄存器 进行操作,那么我们想扒一扒它的流程,必然要先知道串口相关的寄存器是哪些,因此查阅 STM32F4xx中文参考手册 ,我们可以在 第711页 找到以下相关寄存器:

  • 状态寄存器


  • 数据寄存器


  • 波特率寄存器


  • 控制寄存器1


  • 控制寄存器2


  • 控制寄存器3


  • 保护时间和预分频器寄存器




使用特权

评论回复
沙发
wowu|  楼主 | 2021-8-4 20:19 | 只看该作者
这些就是操作串口所需要的寄存器,那么我们应该怎么和 HAL库 的东西对应起来,去HAL库中找找,我们就会在 stm32f407xx.h 的头文件中找到以下结构体

/**
  * @brief Universal Synchronous Asynchronous Receiver Transmitter
  */

typedef struct
{
  __IO uint32_t SR;        /*!< USART Status register,                   Address offset: 0x00 */
  __IO uint32_t DR;        /*!< USART Data register,                     Address offset: 0x04 */
  __IO uint32_t BRR;       /*!< USART Baud rate register,                Address offset: 0x08 */
  __IO uint32_t CR1;       /*!< USART Control register 1,                Address offset: 0x0C */
  __IO uint32_t CR2;       /*!< USART Control register 2,                Address offset: 0x10 */
  __IO uint32_t CR3;       /*!< USART Control register 3,                Address offset: 0x14 */
  __IO uint32_t GTPR;      /*!< USART Guard time and prescaler register, Address offset: 0x18 */
} USART_TypeDef;


使用特权

评论回复
板凳
wowu|  楼主 | 2021-8-4 20:19 | 只看该作者
你会发现,寄存器和结构体中的变量名一一对应,那就是它了,我们再深入研究以下,为什么每个声明都是用uint32_t 的类型?我们还可以在 STM32F4xx中文参考手册 找到以下这张图,更直观的解释原因


​ 从上图可以看出,每个寄存器都是之间的地址偏移都是 4个字节,也就是 32 bit ,所以是用 uint32_t ,使每个寄存器占用 4 个字节,符合芯片文档。


使用特权

评论回复
地板
wowu|  楼主 | 2021-8-4 20:20 | 只看该作者
可是不知道你发现了没有,这边都没有说串口在内存的实际地址,有些小伙伴可能也不知道,就算有地址要怎么操作,这里我们稍微复习一下C 语言中的 常量指针 的知识,下面给出一小段代码

*(uint32_t *)0x40011000 = 0xFF;
​ 这段代码的含义,就是将 0x40011000 这个值转为指向一个32 bit 的内存空间的指针,* 取地址符号就是使得0xFF存入该 32 bit的空间内。

​ 因此我们应该要找到我们所使用的 串口1在内存的地址 ,说了这么多遍内存,我们就来看看 STM32 的内存到底长什么样子,我们可以在 对应的芯片手册上找到其内存映射图,当前芯片是 STM32F407,因此我们在芯片手册中会看到


​ 从上图可以看到 STM32F407 的内存映射图,内存被分成了很多块,而串口是属于外设,因此现在只要关心从下往上数第三块内存块就行了,但是目前我们也只能从中看到有 AHB1,AHB2,APB1,APB2,用标准库的小伙伴,应该对这些词很熟悉了,就是经常要对其进行时钟初始化的外设总线。


使用特权

评论回复
5
wowu|  楼主 | 2021-8-4 20:20 | 只看该作者

但是我们还是没有看到我们想要的 地址,因此我们还是要借助一张时钟树的图,来判断串口1 在哪一条总线之下。






使用特权

评论回复
6
wowu|  楼主 | 2021-8-4 20:21 | 只看该作者

​ 结合时钟树和内存映射图,我们可以看到,串口1的地址在0x40010000 到 0x4001 4BFF ,但是还不是真正我们想要的

​ 最终,你会在 STM32F4xx中文参考手册 中发现一张存储的映射表,串口1的地址范围就写在那里








这个地址范围就是我们要对其进行串口操作的实际地址,但是我们的操作实际就用到了7个寄存器


使用特权

评论回复
7
wowu|  楼主 | 2021-8-4 20:22 | 只看该作者
为了更形象的说明,我们用以下代码,将前面提到的寄存器的实际的地址打印出来:


打印结果如下:

huart1.Instance->SR 40011000
huart1.Instance->DR  40011004
huart1.Instance->BRR 40011008
huart1.Instance->CR1 4001100c
huart1.Instance->CR2 40011010
huart1.Instance->CR3 40011014
huart1.Instance->CTPR 40011018
现在我们就很明白了,串口1的基地址 (40011000)+ 上面所说的地址偏移量 = 每个寄存器的实际地址,


使用特权

评论回复
8
wowu|  楼主 | 2021-8-4 20:22 | 只看该作者
接下来我们再看看,这个基地址是怎么在 HAL 库中用上的。我们从上面的分析中知道,串口1其实是在外设总线上的,并且是 APB2 外设总线上,所以还是在stm32f407.h 的头文件中找,我们可以找到以下宏定义:
  • 外设基地址



  • APB2基地址


  • 串口1基地址

[url=]Removeformat[/url]

  • 串口1用 USART_TypeDef 结构体封装







这样我们就可以算出,串口1的基地址为:


c USART1_BASE = 0x40000000 + 0x00010000 + 0x1000 USART1_BASE = 0x40011000 //正如我们上述文档所看到的一样


将这个值通过 USART_TypeDef 结构体指针的强制转换,就可以用结构体的方式来对寄存器进行存取值了


(升华一下下,其实数组的首地址也可以用结构体指针进行强制转换,只要结果符合你的预期就可以了,比如这个技巧可以用在网络传输的时候)



使用特权

评论回复
9
wowu|  楼主 | 2021-8-4 20:23 | 只看该作者
现在来看看 USART1 用在哪里了,我们可以在 usart.c 找到以下代码


我们可以看到它用在了串口1初始化的函数(MX_USART1_UART_Init )里面了,也终于看到了我们这个标题要说的实例( huart1 )了

接下来我们看看这个实例的结构体( UART_HandleTypeDef )长什么样子

/**
  * @brief  UART handle Structure definition
  */
typedef struct __UART_HandleTypeDef
{
  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              */
  __IO uint16_t                 TxXferCount;      /*!< UART Tx Transfer Counter           */
  uint8_t                       *pRxBuffPtr;      /*!< Pointer to UART Rx transfer Buffer */
  uint16_t                      RxXferSize;       /*!< UART Rx Transfer size              */
  __IO uint16_t                 RxXferCount;      /*!< UART Rx Transfer Counter           */
  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    gState;           
  __IO HAL_UART_StateTypeDef    RxState;         
  __IO uint32_t                 ErrorCode;        /*!< UART Error code                    */

#if (USE_HAL_UART_REGISTER_CALLBACKS == 1)
  void (* TxHalfCpltCallback)(struct __UART_HandleTypeDef *huart);        /*!< UART Tx Half Complete Callback        */
  void (* TxCpltCallback)(struct __UART_HandleTypeDef *huart);            /*!< UART Tx Complete Callback             */
  void (* RxHalfCpltCallback)(struct __UART_HandleTypeDef *huart);        /*!< UART Rx Half Complete Callback        */
  void (* RxCpltCallback)(struct __UART_HandleTypeDef *huart); /*!< UART Rx Complete Callback */
  void (* ErrorCallback)(struct __UART_HandleTypeDef *huart);  /*!< UART Error Callback  */
void (* AbortCpltCallback)(struct __UART_HandleTypeDef *huart); /*!< UART Abort CompleCallback      */
  void (* AbortTransmitCpltCallback)(struct __UART_HandleTypeDef *huart); /*!< UART Abort Transmit Complete Callback */
  void (* AbortReceiveCpltCallback)(struct __UART_HandleTypeDef *huart);  /*!< UART Abort Receive Complete Callback  */
  void (* WakeupCallback)(struct __UART_HandleTypeDef *huart);  /*!< UART Wakeup Callback */
  void (* MspInitCallback)(struct __UART_HandleTypeDef *huart); /*!< UART Msp Init callback*/
  void (* MspDeInitCallback)(struct __UART_HandleTypeDef *huart); /*!< UART Msp DeInitcallback*/
#endif  /* USE_HAL_UART_REGISTER_CALLBACKS */

} UART_HandleTypeDef;

​ 我们可以看到在这个结构体里看到,它需要串口基地址,串口初始化所需要的值,以及数据接收和发送所需要的指针,和当前状态标志位。


使用特权

评论回复
10
wowu|  楼主 | 2021-8-4 20:24 | 只看该作者
二. 串口实例初始化

​ 我们可以在串口初始化的函数(MX_USART1_UART_Init )中,找到我们自己在STM32CubeMX 的图形界面中为串口配置的值,如下

huart1.Init.BaudRate = 115200;
  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 中的 UART_SetConfig 函数中,如下代码所示

/**
  * @brief  Configures the UART peripheral.
  * @param  huart  Pointer to a UART_HandleTypeDef structure that contains
  *                the configuration information for the specified UART module.
  * @retval None
  */
static void UART_SetConfig(UART_HandleTypeDef *huart)
{
  uint32_t tmpreg;
  uint32_t pclk;

  /* Check the parameters */
  assert_param(IS_UART_BAUDRATE(huart->Init.BaudRate));
  assert_param(IS_UART_STOPBITS(huart->Init.StopBits));
  assert_param(IS_UART_PARITY(huart->Init.Parity));
  assert_param(IS_UART_MODE(huart->Init.Mode));

  /*-------------------------- USART CR2 Configuration -----------------------*/
  /* Configure the UART Stop Bits: Set STOP[13:12] bits
     according to huart->Init.StopBits value */
  MODIFY_REG(huart->Instance->CR2, USART_CR2_STOP, huart->Init.StopBits);

  /*-------------------------- USART CR1 Configuration -----------------------*/
  /* Configure the UART Word Length, Parity and mode:
     Set the M bits according to huart->Init.WordLength value
     Set PCE and PS bits according to huart->Init.Parity value
     Set TE and RE bits according to huart->Init.Mode value
     Set OVER8 bit according to huart->Init.OverSampling value */

  tmpreg = (uint32_t)huart->Init.WordLength | huart->Init.Parity | huart->Init.Mode | huart->Init.OverSampling;
  MODIFY_REG(huart->Instance->CR1,
             (uint32_t)(USART_CR1_M | USART_CR1_PCE | USART_CR1_PS | USART_CR1_TE | USART_CR1_RE | USART_CR1_OVER8),
             tmpreg);

  /*-------------------------- USART CR3 Configuration -----------------------*/
  /* Configure the UART HFC: Set CTSE and RTSE bits according to huart->Init.HwFlowCtl value */
  MODIFY_REG(huart->Instance->CR3, (USART_CR3_RTSE | USART_CR3_CTSE), huart->Init.HwFlowCtl);

  /* Check the Over Sampling */
  if (huart->Init.OverSampling == UART_OVERSAMPLING_8)
  {
    /*-------------------------- USART BRR Configuration ---------------------*/
#if defined(USART6) && defined(UART9) && defined(UART10)
    if ((huart->Instance == USART1) || (huart->Instance == USART6) || (huart->Instance == UART9) || (huart->Instance == UART10))
    {
      pclk = HAL_RCC_GetPCLK2Freq();
      huart->Instance->BRR = UART_BRR_SAMPLING8(pclk, huart->Init.BaudRate);
    }
#elif defined(USART6)
    if ((huart->Instance == USART1) || (huart->Instance == USART6))
    {
      pclk = HAL_RCC_GetPCLK2Freq();
      huart->Instance->BRR = UART_BRR_SAMPLING8(pclk, huart->Init.BaudRate);
    }
#else
    if (huart->Instance == USART1)
    {
      pclk = HAL_RCC_GetPCLK2Freq();
      huart->Instance->BRR = UART_BRR_SAMPLING8(pclk, huart->Init.BaudRate);
    }
#endif /* USART6 */
    else
    {
      pclk = HAL_RCC_GetPCLK1Freq();
      huart->Instance->BRR = UART_BRR_SAMPLING8(pclk, huart->Init.BaudRate);
    }
  }
  else
  {
    /*-------------------------- USART BRR Configuration ---------------------*/
#if defined(USART6) && defined(UART9) && defined(UART10)
    if ((huart->Instance == USART1) || (huart->Instance == USART6) || (huart->Instance == UART9) || (huart->Instance == UART10))
    {
      pclk = HAL_RCC_GetPCLK2Freq();
      huart->Instance->BRR = UART_BRR_SAMPLING16(pclk, huart->Init.BaudRate);
    }
#elif defined(USART6)
    if ((huart->Instance == USART1) || (huart->Instance == USART6))
    {
      pclk = HAL_RCC_GetPCLK2Freq();
      huart->Instance->BRR = UART_BRR_SAMPLING16(pclk, huart->Init.BaudRate);
    }
#else
    if (huart->Instance == USART1)
    {
      pclk = HAL_RCC_GetPCLK2Freq();
      huart->Instance->BRR = UART_BRR_SAMPLING16(pclk, huart->Init.BaudRate);
    }
#endif /* USART6 */
    else
    {
      pclk = HAL_RCC_GetPCLK1Freq();
      huart->Instance->BRR = UART_BRR_SAMPLING16(pclk, huart->Init.BaudRate);
    }
  }
}

/**
  * @}
  */
它真正操作到我们所说那些串口相关的寄存器(CR1, CR2, CR3, BRR)的配置,这里没看到 (SR,DR),因为它在数据的发送和接收时用到了。


使用特权

评论回复
11
wowu|  楼主 | 2021-8-4 20:24 | 只看该作者
三. 串口数据的发送(阻塞模式)
接下来就是对数据发送进行说明,先放出函数

/**
  * @brief  Sends an amount of data in blocking mode.
  * @NOTE   When UART parity is not enabled (PCE = 0), and Word Length is configured to 9 bits (M1-M0 = 01),
  *         the sent data is handled as a set of u16. In this case, Size must indicate the number
  *         of u16 provided through pData.
  * @param  huart Pointer to a UART_HandleTypeDef structure that contains
  *               the configuration information for the specified UART module.
  * @param  pData Pointer to data buffer (u8 or u16 data elements).
  * @param  Size  Amount of data elements (u8 or u16) to be sent
  * @param  Timeout Timeout duration
  * @retval HAL status
  */
HAL_StatusTypeDef HAL_UART_Transmit(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size, uint32_t Timeout)
{
  uint16_t *tmp;
  uint32_t tickstart = 0U;

  /* Check that a Tx process is not already ongoing */
  if (huart->gState == HAL_UART_STATE_READY)
  {
    if ((pData == NULL) || (Size == 0U))
    {
      return  HAL_ERROR;
    }

    /* Process Locked */
    __HAL_LOCK(huart);

    huart->ErrorCode = HAL_UART_ERROR_NONE;
    huart->gState = HAL_UART_STATE_BUSY_TX;

    /* Init tickstart for timeout managment */
    tickstart = HAL_GetTick();

    huart->TxXferSize = Size;
    huart->TxXferCount = Size;

    /* Process Unlocked */
    __HAL_UNLOCK(huart);

    while (huart->TxXferCount > 0U)
    {
      huart->TxXferCount--;
      if (huart->Init.WordLength == UART_WORDLENGTH_9B)
      {
        if (UART_WaitOnFlagUntilTimeout(huart, UART_FLAG_TXE, RESET, tickstart, Timeout) != HAL_OK)
        {
          return HAL_TIMEOUT;
        }
        tmp = (uint16_t *) pData;
        huart->Instance->DR = (*tmp & (uint16_t)0x01FF);
        if (huart->Init.Parity == UART_PARITY_NONE)
        {
          pData += 2U;
        }
        else
        {
          pData += 1U;
        }
      }
      else
      {
        if (UART_WaitOnFlagUntilTimeout(huart, UART_FLAG_TXE, RESET, tickstart, Timeout) != HAL_OK)
        {
          return HAL_TIMEOUT;
        }
        huart->Instance->DR = (*pData++ & (uint8_t)0xFF);
      }
    }

    if (UART_WaitOnFlagUntilTimeout(huart, UART_FLAG_TC, RESET, tickstart, Timeout) != HAL_OK)
    {
      return HAL_TIMEOUT;
    }

    /* At end of Tx process, restore huart->gState to Ready */
    huart->gState = HAL_UART_STATE_READY;

    return HAL_OK;
  }
  else
  {
    return HAL_BUSY;
  }
}


  • 进来该函数 检查当前串口的状态,若处于准备状态,将串口状态设置为 huart->gState = HAL_UART_STATE_BUSY_TX; 这个其实是为了对串口做保护,防止同时使用串口,导致数据发送错误
  • huart->TxXferCount = Size这变量记录当前还需要 发送的数据个数,在 while 中不断进行判断,如果需要发送数据就通过下面函数查询 SR 寄存器中的状态,超过指定时间(Timeout )就是发送失败了,返回超时。

c if (UART_WaitOnFlagUntilTimeout(huart, UART_FLAG_TXE, RESET, tickstart, Timeout) != HAL_OK) { return HAL_TIMEOUT; } //查询 UART_FLAG_TXE 标志位

如果没有超时,就通过下面代码进行发送,pData 指针 指向我们想要发送的数据的首地址
c huart->Instance->DR = (*pData++ & (uint8_t)0xFF);

总结:

​ 该函数就是一直在查询是否是发送状态,如果可以发送就发送,不能发送,就等到Timeout 结束,返回超时


使用特权

评论回复
12
wowu|  楼主 | 2021-8-4 20:25 | 只看该作者
四. 串口数据接收
接下来就是对数据接收进行说明,再放出函数

/**
  * @brief  Receives an amount of data in blocking mode.
  * @note   When UART parity is not enabled (PCE = 0), and Word Length is configured to 9 bits (M1-M0 = 01),
  *         the received data is handled as a set of u16. In this case, Size must indicate the number
  *         of u16 available through pData.
  * @param  huart Pointer to a UART_HandleTypeDef structure that contains
  *               the configuration information for the specified UART module.
  * @param  pData Pointer to data buffer (u8 or u16 data elements).
  * @param  Size  Amount of data elements (u8 or u16) to be received.
  * @param  Timeout Timeout duration
  * @retval HAL status
  */
HAL_StatusTypeDef HAL_UART_Receive(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size, uint32_t Timeout)
{
  uint16_t *tmp;
  uint32_t tickstart = 0U;

  /* Check that a Rx process is not already ongoing */
  if (huart->RxState == HAL_UART_STATE_READY)
  {
    if ((pData == NULL) || (Size == 0U))
    {
      return  HAL_ERROR;
    }

    /* Process Locked */
    __HAL_LOCK(huart);

    huart->ErrorCode = HAL_UART_ERROR_NONE;
    huart->RxState = HAL_UART_STATE_BUSY_RX;

    /* Init tickstart for timeout managment */
    tickstart = HAL_GetTick();

    huart->RxXferSize = Size;
    huart->RxXferCount = Size;

    /* Process Unlocked */
    __HAL_UNLOCK(huart);

    /* Check the remain data to be received */
    while (huart->RxXferCount > 0U)
    {
      huart->RxXferCount--;
      if (huart->Init.WordLength == UART_WORDLENGTH_9B)
      {
        if (UART_WaitOnFlagUntilTimeout(huart, UART_FLAG_RXNE, RESET, tickstart, Timeout) != HAL_OK)
        {
          return HAL_TIMEOUT;
        }
        tmp = (uint16_t *) pData;
        if (huart->Init.Parity == UART_PARITY_NONE)
        {
          *tmp = (uint16_t)(huart->Instance->DR & (uint16_t)0x01FF);
          pData += 2U;
        }
        else
        {
          *tmp = (uint16_t)(huart->Instance->DR & (uint16_t)0x00FF);
          pData += 1U;
        }

      }
      else
      {
        if (UART_WaitOnFlagUntilTimeout(huart, UART_FLAG_RXNE, RESET, tickstart, Timeout) != HAL_OK)
        {
          return HAL_TIMEOUT;
        }
        if (huart->Init.Parity == UART_PARITY_NONE)
        {
          *pData++ = (uint8_t)(huart->Instance->DR & (uint8_t)0x00FF);
        }
        else
        {
          *pData++ = (uint8_t)(huart->Instance->DR & (uint8_t)0x007F);
        }

      }
    }

    /* At end of Rx process, restore huart->RxState to Ready */
    huart->RxState = HAL_UART_STATE_READY;

    return HAL_OK;
  }
  else
  {
    return HAL_BUSY;
  }
}

  • 进来该函数 检查当前串口的状态,若处于准备状态,将串口状态设置为 huart->gState = HAL_UART_STATE_BUSY_RX; 这也是防止同时使用串口,导致数据接收错误
  • huart->TxXferCount = Size这变量记录当前还需要 接收的数据个数,在 while 中不断进行判断,如果需要接收数据就通过下面函数查询 SR 寄存器中的状态,超过指定时间(Timeout )就是接收失败了,返回超时。

c if (UART_WaitOnFlagUntilTimeout(huart, UART_FLAG_RXNE, RESET, tickstart, Timeout) != HAL_OK) { return HAL_TIMEOUT; } //查询 UART_FLAG_RXNE 标志位

  • 如果没有超时,就通过下面代码进行数据接收,pData 指针 指向我们想要缓存数据的首地址(一般为数组首地址),将 DR 中的数据读取出来。

c *pData++ = (uint8_t)(huart->Instance->DR & (uint8_t)0x00FF);

总结:

​ 该函数就是一直在查询是否是接收状态,如果可以接收就接收,不能接收,就等到Timeout 结束,返回超时,值得注意的是 这里如果没有接收到足够的数据也会返回超时,比如你要接收10个,如果只接收到了 9个 它也会返回超时,可以通过计算获取到底接收到几个。

当接收超时时,计算接收到的个数 = huart1.RxXferSize - huart1.RxXferCount - 1


使用特权

评论回复
发新帖 我要提问
您需要登录后才可以回帖 登录 | 注册

本版积分规则

91

主题

4082

帖子

1

粉丝