stm32f407 6个串口dma_STM32CubeMX之串口封装详解(长文解析)

概述

​ 上一篇写了 STM32CubeMX 的串口的使用,而这篇来扒一扒,它是怎么进行封装的。

可以学到的知识

  1. STM32CubeMX 是怎么用结构体封装串口
  2. 如何用结构体直接访问寄存器
  3. stm32外设时钟是怎么看的
  4. stm32的内存长什么样子
  5. 常量指针如何使用
  6. ...

一.串口实例

​ 我们都知道,其实单片机最后其实都是对 串口相关的寄存器 进行操作,那么我们想扒一扒它的流程,必然要先知道串口相关的寄存器是哪些,因此查阅 STM32F4xx中文参考手册 ,我们可以在 第711页 找到以下相关寄存器:

  • 状态寄存器

653bab705d4e3afa6e2a21f0dc4e3f4d.png
  • 数据寄存器

f0675b817b6e8ebaa4f2626615a82f54.png
  • 波特率寄存器

979dfa32a19638edfc903bc9313fddd3.png
  • 控制寄存器1

13318df0709f0fad74a0281d67486b43.png

  • 控制寄存器2

825a89fe2718cdbd48adc61688eed07b.png

  • 控制寄存器3

3d5a4a4b08eb46e8dcd42b4f1bac2373.png
  • 保护时间和预分频器寄存器

6cb51bc5d90400c0e3e58d0e90ba7237.png

​ 这些就是操作串口所需要的寄存器,那么我们应该怎么和 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;

你会发现,寄存器和结构体中的变量名一一对应,那就是它了,我们再深入研究以下,为什么每个声明都是用uint32_t 的类型?我们还可以在 STM32F4xx中文参考手册 找到以下这张图,更直观的解释原因

bbb1d1b76a7436542404506a4bea1730.png

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

​ 可是不知道你发现了没有,这边都没有说串口在内存的实际地址,有些小伙伴可能也不知道,就算有地址要怎么操作,这里我们稍微复习一下C 语言中的 常量指针 的知识,下面给出一小段代码

*(uint32_t *)0x40011000 = 0xFF;

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

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

046d45aa543cb39bfd0f8a9cc480dfa2.png

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

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

ddc70f63723005eb8671f528858c78db.png

9b0eeac73371e211ef3db07d91064440.png

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

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

4ed3b21dad8133eb8b17d95c6830dd73.png

a09e4a11358a53b3dfe74b4adc60fc25.png

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

​ 为了更形象的说明,我们用以下代码,将前面提到的寄存器的实际的地址打印出来:

970d6aa85e2dafedb62613ca1f0bfcb0.png

打印结果如下:

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)+ 上面所说的地址偏移量 = 每个寄存器的实际地址,

​ 接下来我们再看看,这个基地址是怎么在 HAL 库中用上的。我们从上面的分析中知道,串口1其实是在外设总线上的,并且是 APB2 外设总线上,所以还是在stm32f407.h 的头文件中找,我们可以找到以下宏定义:

  • 外设基地址

411d106131bf12dd6315310bc277ff01.png
  • APB2基地址

5854686b15cf33da7df38d3356652000.png
  • 串口1基地址

f371171d30b44350e0c1712304a97e6b.png
  • 串口1用 USART_TypeDef 结构体封装

b6a0a2c3bb87df00fa3b23eaed456d58.png

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

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

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

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

现在来看看 USART1 用在哪里了,我们可以在 usart.c 找到以下代码

b6a0a2c3bb87df00fa3b23eaed456d58.png

我们可以看到它用在了串口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;

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

### 二. 串口实例初始化

​ 我们可以在串口初始化的函数(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),因为它在数据的发送和接收时用到了。

三. 串口数据的发送(阻塞模式)

接下来就是对数据发送进行说明,先放出函数

/**
  * @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 结束,返回超时

四. 串口数据接收

接下来就是对数据接收进行说明,再放出函数

/**
  * @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

后续我还会继续分享STM32CuBeMX的相关操作,以及对库函数源码进行剖析,相信你会学到更多知识。

文章有帮助到你,点赞,收藏,关注我吧!

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值