完善资料让更多小伙伴认识你,还能领取20积分哦, 立即完善>
什么是USART?跟USART并列的串行通信标准还有哪些?
采用USART实现不同对象之间通讯时,需要注意什么?USART模块系统框图构成?如何让USART模块工作在不同模式? 如何计算并配置波特率相关的寄存器? |
|
相关推荐
1个回答
|
|
概述
本文是对STM32的USART相关知识扫盲(参考正点原子的教学),文章架构如下:
一、USART概述 1、什么是USART?
3、采用USART实现不同对象之间通讯,需要注意什么?
系统框图 1、简述USART模块系统框图构成
1、如何计算波特率,即正确的分频值呢?
1、简述USART相关寄存器,如下所示。
三、USART初始化代码剖析 本节基于HAL库架构,剖析USART初始化的代码细节,USART初始化的代码如下,代码结构简单易懂,下面开始具体分析: UART_HandleTypeDef UART1_Handler; //声明串口1的配置特性 void uart_init(u32 bound) { //UART 初始化设置 UART1_Handler.Instance=USART1; //表明配置USART1 UART1_Handler.Init.BaudRate=bound; //配置波特率 UART1_Handler.Init.WordLength=UART_WORDLENGTH_8B; //配置字长为8位 UART1_Handler.Init.StopBits=UART_STOPBITS_1; //一位停止位 UART1_Handler.Init.Parity=UART_PARITY_NONE; //无奇偶校验位 UART1_Handler.Init.HwFlowCtl=UART_HWCONTROL_NONE; //无硬件流控制 UART1_Handler.Init.Mode=UART_MODE_TX_RX; //收发模式 HAL_UART_Init(&UART1_Handler); //该函数初始化串口 //...... } 1、使用HAL_UART_Init()函数进行USART相关寄存器的初始化配置,从该函数接收参数、返回参数的角度简述该函数是如何配置串口初始化的。 HAL_StatusTypeDef HAL_UART_Init(UART_HandleTypeDef *huart){} 1 首先分析该函数的返回参数 函数的返回类型为HAL_StatusTypeDef,它是用枚举类型来定义的,具体代码如下,它定义了函数的四种返回状态,可以根据返回值判断函数的执行状态。 typedef enum { HAL_OK = 0x00, HAL_ERROR = 0x01, HAL_BUSY = 0x02, HAL_TIMEOUT = 0x03 } HAL_StatusTypeDef; 然后分析该函数的入口参数,入口参数类型为UART_HandleTypeDef,其结构体成员变量如下所示: 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 */ 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; UART_HandleTypeDef成员变量包括以下几组变量 USART_TypeDef *Instance,指向目标串口的基地址 UART_InitTypeDef Init,是目标串口需要初始化配置的特性 与发送数据相关的三个变量,*pTxBuffPtr、TxXferSize、TxXferCount 与接收数据相关的三个变量,*pRxBuffPtr、 RxXferSize、RxXferCount(这些变量的作用后续使用时,再详细解释) DMA相关设置变量 LOCK相关设置变量 UART的状态标志位 UART错误日志 容易知道: 通过配置Init的成员变量,从而配置好目标串口的具体初始化特性 Instance指针是指向结构体USART_TypeDef,通过给Instance赋值,选择需要配置的串口地址 然后将Init的成员变量赋值给Instance的成员变量,实现串口初始化。 2、需要初始化配置的USART的寄存器的地址是如何定位到的? 指针Instance指向的地址取值为USART1 ~ x,USART1~x的地址是通过外设总线地址加偏移量定义的,然后一步一步映射,通过GPIO那一节的文章我们知道,最终是通过RAM里面的某一个绝对地址作为基地址。 #define USART1 ((USART_TypeDef *) USART1_BASE) #define USART6 ((USART_TypeDef *) USART6_BASE) #define USART1_BASE (APB2PERIPH_BASE + 0x1000) #define USART6_BASE (APB2PERIPH_BASE + 0x1400) 串口地址已经定位好了,再看看Instance指针指向的结构体USART_TypeDef的成员变量,由此我们知道,通过访问Instance的成员变量,定位到相应串口配置寄存器的地址的。 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; 3、使用HAL_UART_Init()函数进行USART相关寄存器的初始化配置,从该函数具体实现功能的角度简述该函数是如何配置串口初始化的。 HAL_UART_Init()函数要实现两个初始化功能,第一个功能是初始化串口相关IO口的复用配置 第二个功能才是初始化串口相关特性,将Init的成员变量赋值给Instance的成员变量来实现。 4、HAL_UART_Init()函数里面,如何初始化串口相关IO口的复用配置的? 稍微研究下就可以发现,HAL_UART_Init()函数通过调用HAL_UART_MspInit()函数,实现了串口相关IO口的复用配置,相关代码如下,GPIO口的相关配置细节不再赘述。 void HAL_UART_MspInit(UART_HandleTypeDef *huart) { //GPIO端口设置 GPIO_InitTypeDef GPIO_Initure; if(huart->Instance==USART1)//配置USART1 { __HAL_RCC_GPIOA_CLK_ENABLE(); //串口1使用到的是PA9和PA10。所以使能GPIOA __HAL_RCC_USART1_CLK_ENABLE(); //然后使能UART1 GPIO_Initure.Pin=GPIO_PIN_9; //PA9 GPIO_Initure.Mode=GPIO_MODE_AF_PP; //GPIO工作在复用模式 GPIO_Initure.Pull=GPIO_PULLUP; //上拉 GPIO_Initure.Speed=GPIO_SPEED_FAST; //高速 GPIO_Initure.Alternate=GPIO_AF7_USART1; //复用成哪一种复用模式? HAL_GPIO_Init(GPIOA,&GPIO_Initure); //将原始寄存器设置成以上特性 GPIO_Initure.Pin=GPIO_PIN_10; //PA10 HAL_GPIO_Init(GPIOA,&GPIO_Initure); //PA10设置成以上特性 #if EN_USART1_RX HAL_NVIC_EnableIRQ(USART1_IRQn); //使能中断 HAL_NVIC_SetPriority(USART1_IRQn,3,3); //设置中断优先级 #endif } } 5、我们已经知道,调用HAL_UART_MspInit()函数来实现IO口的复用配置,进一步研究可以发现,这个函数在定义时,函数类型前面可以加了_Weak关键字,这个关键字有什么用? 官方库文件对HAL_UART_MspInit()如下定义,即在函数里面什么事都不干。 __weak void HAL_UART_MspInit(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_MspInit could be implemented in the user file */ } 上文我们用户自己对HAL_UART_MspInit()重新定义了一番,这样难道不会造成重定义报错么? _Weak关键字定义的函数,允许用户重新定义一个同名函数,最终编译器编译的时候会选择用户定义的函数。如果用户没有定义,那么才执行_Weak关键字定义的函数内容。 这样做有什么好处呢? 开发者可以事先定义好一个流程,后续如果项目需求改了,不用对既定流程做任何修改,只用对流程中的与实现关联的代码细节进行修改即可。 typedef struct { uint32_t BaudRate; /*!< This member configures the UART communication baud rate. The baud rate is computed using the following formula: - IntegerDivider = ((PCLKx) / (8 * (OVR8+1) * (huart->Init.BaudRate))) - FractionalDivider = ((IntegerDivider - ((uint32_t) IntegerDivider)) * 8 * (OVR8+1)) + 0.5 Where OVR8 is the "oversampling by 8 mode" configuration bit in the CR1 register. */ uint32_t WordLength; /*!< Specifies the number of data bits transmitted or received in a frame. This parameter can be a value of @ref UART_Word_Length */ uint32_t StopBits; /*!< Specifies the number of stop bits transmitted. This parameter can be a value of @ref UART_Stop_Bits */ uint32_t Parity; /*!< Specifies the parity mode. This parameter can be a value of @ref UART_Parity @note When parity is enabled, the computed parity is inserted at the MSB position of the transmitted data (9th bit when the word length is set to 9 data bits; 8th bit when the word length is set to 8 data bits). */ uint32_t Mode; /*!< Specifies whether the Receive or Transmit mode is enabled or disabled. This parameter can be a value of @ref UART_Mode */ uint32_t HwFlowCtl; /*!< Specifies whether the hardware flow control mode is enabled or disabled. This parameter can be a value of @ref UART_Hardware_Flow_Control */ uint32_t OverSampling; /*!< Specifies whether the Over sampling 8 is enabled or disabled, to achieve higher speed (up to fPCLK/8). This parameter can be a value of @ref UART_Over_Sampling */ }UART_InitTypeDef; 6、HAL_UART_Init()函数如何初始化串口特性的呢? 容易发现,HAL_UART_Init()通过调用UART_SetConfig()函数,实现了串口特性的初始化。UART_SetConfig()函数配置寄存器的流程如下: 检查参数是否有问题? 将待配置寄存器读出来。 将待配置寄存器的相关位清零。 配置相关位。 将配置好的值写回寄存器 static void UART_SetConfig(UART_HandleTypeDef *huart) { /* Check the parameters */ assert_param(IS_UART_BAUDRATE(huart->Init.BaudRate)); assert_param(IS_UART_STOPBITS(huart->Init.StopBits)); //...... /*-------------------------- USART CR1 Configuration -----------------------*/ tmpreg = huart->Instance->CR1; /* Clear M, PCE, PS, TE and RE bits */ tmpreg &= (uint32_t)~((uint32_t)(USART_CR1_M | USART_CR1_PCE | USART_CR1_PS | USART_CR1_TE | USART_CR1_RE | USART_CR1_OVER8)); /* Configure the UART Word Length, Parity and mode: */ tmpreg |= (uint32_t)huart->Init.WordLength | huart->Init.Parity | huart->Init.Mode | huart->Init.OverSampling; /* Write to USART CR1 */ huart->Instance->CR1 = (uint32_t)tmpreg; } 7、这里会产生一个疑惑,Init的成员变量都是32位的数据类型,但是,是要去配置寄存器中对应的某些位,这是否会有矛盾? typedef struct { uint32_t BaudRate; uint32_t WordLength; uint32_t StopBits; uint32_t Parity; uint32_t Mode; uint32_t HwFlowCtl; uint32_t OverSampling; }UART_InitTypeDef; 不矛盾,Init的成员变量虽然都是32位的数据类型,但是只有特定位才会置数,其余位都是置零的,因此这里将Init的多个成员变量相或,多个成员变量之间相互不影响,共同组成一个总的32位的数据,再赋值给串口相关控制寄存器。 分析至此,我们已经知道串口初始化最重要的两步是怎么进行的: 串口IO口复用配置。 串口相关特性配置。 知道了这两点,我们可以说是已经会用串口了,但远远谈不上会写串口配置代码。HAL_UART_Init()函数里面还涉及大量代码运行逻辑,笔者本文不会进一步深入了,感兴趣的读者自行研究。 但有一点私货想跟读者分享,不要过分追求学究思维中的完美主义,不要对所有细节都企图了解的面面俱到,我们要追求工程师思维,即 先实现产品需求,一上来先做到从无到有,再慢慢从有到好。 但不要止步于从无到有的短暂快乐,从有到好的痛苦研究过程才更有意义 实现从有到好的过程,即对种种细节进行优化的过程,我们也要对各种细节赋予权重,权重高的细节优先研究,权重低的细节适当舍弃。不要有强迫症思想。 |
|
|
|
只有小组成员才能发言,加入小组>>
调试STM32H750的FMC总线读写PSRAM遇到的问题求解?
1645 浏览 1 评论
X-NUCLEO-IHM08M1板文档中输出电流为15Arms,15Arms是怎么得出来的呢?
1564 浏览 1 评论
992 浏览 2 评论
STM32F030F4 HSI时钟温度测试过不去是怎么回事?
691 浏览 2 评论
ST25R3916能否对ISO15693的标签芯片进行分区域写密码?
1610 浏览 2 评论
1871浏览 9评论
STM32仿真器是选择ST-LINK还是选择J-LINK?各有什么优势啊?
656浏览 4评论
STM32F0_TIM2输出pwm2后OLED变暗或者系统重启是怎么回事?
526浏览 3评论
542浏览 3评论
stm32cubemx生成mdk-arm v4项目文件无法打开是什么原因导致的?
515浏览 3评论
小黑屋| 手机版| Archiver| 电子发烧友 ( 湘ICP备2023018690号 )
GMT+8, 2024-11-27 01:25 , Processed in 0.775862 second(s), Total 79, Slave 62 queries .
Powered by 电子发烧友网
© 2015 bbs.elecfans.com
关注我们的微信
下载发烧友APP
电子发烧友观察
版权所有 © 湖南华秋数字科技有限公司
电子发烧友 (电路图) 湘公网安备 43011202000918 号 电信与信息服务业务经营许可证:合字B2-20210191 工商网监 湘ICP备2023018690号