本文共 16149 字,大约阅读时间需要 53 分钟。
上一篇写了 STM32CubeMX 的串口的使用,而这篇来扒一扒,它是怎么进行封装的。其实在标准库中也类似如下过程。
我们都知道,其实单片机最后其实都是对 串口相关的寄存器 进行操作,那么我们想扒一扒它的流程,必然要先知道串口相关的寄存器是哪些,因此查阅 STM32F4xx中文参考手册 ,我们可以在 第711页 找到以下相关寄存器:
控制寄存器1
控制寄存器2
控制寄存器3
这些就是操作串口所需要的寄存器,那么我们应该怎么和 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中文参考手册 找到以下这张图,更直观的解释原因
从上图可以看出,每个寄存器都是之间的地址偏移都是 4个字节,也就是 32 bit ,所以是用 uint32_t
,使每个寄存器占用 4 个字节,符合芯片文档。
可是不知道你发现了没有,这边都没有说串口在内存的实际地址,有些小伙伴可能也不知道,就算有地址要怎么操作,这里我们稍微复习一下C 语言中的 常量指针 的知识,下面给出一小段代码
*(uint32_t *)0x40011000 = 0xFF;
这段代码的含义,就是将 0x40011000
这个值转为指向一个32 bit 的内存空间的指针,* 取地址符号就是使得0xFF存入该 32 bit的空间内。
因此我们应该要找到我们所使用的 串口1在内存的地址 ,说了这么多遍内存,我们就来看看 STM32 的内存到底长什么样子,我们可以在 对应的芯片手册上找到其内存映射图,当前芯片是 STM32F407,因此我们在芯片手册中会看到
从上图可以看到 STM32F407 的内存映射图,内存被分成了很多块,而串口是属于外设,因此现在只要关心从下往上数第三块内存块就行了,但是目前我们也只能从中看到有 AHB1,AHB2,APB1,APB2
,用标准库的小伙伴,应该对这些词很熟悉了,就是经常要对其进行时钟初始化的外设总线。
但是我们还是没有看到我们想要的 地址,因此我们还是要借助一张时钟树的图,来判断串口1 在哪一条总线之下。
结合时钟树和内存映射图,我们可以看到,串口1的地址在0x40010000
到 0x4001 4BFF
,但是还不是真正我们想要的
最终,你会在 STM32F4xx中文参考手册 中发现一张存储的映射表,串口1的地址范围就写在那里
这个地址范围就是我们要对其进行串口操作的实际地址,但是我们的操作实际就用到了7个寄存器
为了更形象的说明,我们用以下代码,将前面提到的寄存器的实际的地址打印出来:
打印结果如下:
huart1.Instance->SR 40011000huart1.Instance->DR 40011004huart1.Instance->BRR 40011008huart1.Instance->CR1 4001100chuart1.Instance->CR2 40011010huart1.Instance->CR3 40011014huart1.Instance->CTPR 40011018
现在我们就很明白了,串口1的基地址 (40011000
)+ 上面所说的地址偏移量 = 每个寄存器的实际地址,
接下来我们再看看,这个基地址是怎么在 HAL 库中用上的。我们从上面的分析中知道,串口1其实是在外设总线上的,并且是 APB2
外设总线上,所以还是在stm32f407.h
的头文件中找,我们可以找到以下宏定义:
串口1用 USART_TypeDef
结构体封装
这样我们就可以算出,串口1的基地址为:
USART1_BASE = 0x40000000 + 0x00010000 + 0x1000USART1_BASE = 0x40011000 //正如我们上述文档所看到的一样
将这个值通过 USART_TypeDef
结构体指针的强制转换,就可以用结构体的方式来对寄存器进行存取值了
(升华一下下,其实数组的首地址也可以用结构体指针进行强制转换,只要结果符合你的预期就可以了,比如这个技巧可以用在网络传输的时候)
现在来看看 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;
我们可以看到在这个结构体里看到,它需要串口基地址,串口初始化所需要的值,以及数据接收和发送所需要的指针,和当前状态标志位。
我们可以在串口初始化的函数(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
)就是发送失败了,返回超时。
if (UART_WaitOnFlagUntilTimeout(huart, UART_FLAG_TXE, RESET, tickstart, Timeout) != HAL_OK) { return HAL_TIMEOUT; }//查询 UART_FLAG_TXE 标志位
如果没有超时,就通过下面代码进行发送,pData 指针 指向我们想要发送的数据的首地址
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
)就是接收失败了,返回超时。
if (UART_WaitOnFlagUntilTimeout(huart, UART_FLAG_RXNE, RESET, tickstart, Timeout) != HAL_OK) { return HAL_TIMEOUT; }//查询 UART_FLAG_RXNE 标志位
如果没有超时,就通过下面代码进行数据接收,pData 指针 指向我们想要缓存数据的首地址(一般为数组首地址),将 DR 中的数据读取出来。
*pData++ = (uint8_t)(huart->Instance->DR & (uint8_t)0x00FF);
总结:
该函数就是一直在查询是否是接收状态,如果可以接收就接收,不能接收,就等到Timeout
结束,返回超时,值得注意的是 这里如果没有接收到足够的数据也会返回超时,比如你要接收10个,如果只接收到了 9个 它也会返回超时,可以通过计算获取到底接收到几个。
当接收超时时,计算接收到的个数 = huart1.RxXferSize - huart1.RxXferCount - 1
全文总结:
后续还会继续分享串口的其他基础知识和使用,感兴趣的小伙伴记得关注我!
-----------------------------------------------结束-------------------------------------------------------- 文章有价值,请各位看官点个赞,关注我或者点右边打个赏吧!转载地址:http://numvn.baihongyu.com/