STM32:USART串口收发数据

USART通信

一. USART介绍

USART功能单元,能够实现同步串行通信(USART)和异步串行通信(UART)。同步串行通信在异步串行通信的基础上增加了同步时钟信号USART_CK,用来同步触发收发双方数据的传输。USART可以不使用同步时钟信号,便作为异步通信使用,此时与UART无差别。

二. 功能引脚复用

STM32单片机所有引脚上电复位后的默认功能均不是USART的功能引脚Tx和Rx,因此需要复用STM32单片机的引脚作为USART的功能引脚。

注意: 同一个引脚可以服用成多个片上外设的功能引脚,但是在同一时刻引脚仅能复用成某一个片上外设的功能引脚,否则将出现功能的冲突。

三. 串行通信连接方式


数据发送引脚TxD与对方数据接收引脚RxD需要进行交叉连接。当只需要单向传输时,可以只连接一根线

四. 功能介绍

  1. CPU 发送数据: 当数据从 TDR 转移到移位寄存器时,会产生 TDR 已空事件
    TXE,使状态寄存器(SR)中的TXE 置位,以表示数据已经转移到发送移位寄存器。因此,CPU 可以根据 TXE标志的状态来判断 TDR是否为空,当空时即可将下一个要发送的数据写人TDR等待发送。另外,转移到发送移位寄存器中的数据在发送控制器的控制下一位一位地从 TxD 输出引脚发送出去。当数据全部发送完成后,会触发 TC 事件,使 SR 中的TC置位,以表示数据发送完成。因此,CPU 可以根据TC标志的状态来判断数据是否发送完成,从而可以开始发送下一个数据。
  2. CPU 接收数据: 当一个完整数据从串口的RxD引脚逐位移入到接收移位寄存器后,USART 接收控制器会自动将接收移位寄存器中的数据转移到 RDR 中,同时触发
    RXNE事件,使SR 中的RXNE 置位,以表示接收数据已准备好。因此,CPU 可以根据RXNE 标志的状态来判断接收数据是否已准备好,从而控制何时可以读取 RDR 中的数据,以实现一个数据的接收。
  3. 收发中断请求: 如果期望 USART 利用中断的方式进行数据的收发控制,则需要使能收发中断请求。若开启了 TXEIE、TCIE 和 RXNEIE 的中断使能位,当 TXE、TC 和RXNE置位时,USART 的中断控制器会自动产生中断请求,并通过 USART 的专用中断通道送往 NVIC 触发中断服务,进而可以在中断服务程序中实现数据的收发。
  4. 数据收发格式: USART 异步串行通信是以字节为单位进行数据传输的,即是一个字节一个字节地进行数据传输。通信过程中每次传输1字节数据,需要组装成一个数据帧进行传输。数据帧之间可以间隔传送,也可以连续传送。一个数据帧一般由1个起始位、8或9个数据位、1个奇偶校验位和多个停止位构成,因此在进行申行通信时需要设置具体的数据位和停止位个数,并确定是否使用奇偶校验位,以构成一个特定的数据帧。串行通信时收发双方必须按照相同的帧格式进行数据收发,才能保证收发双方可以正确地对数据帧进行解析,以获取传输的字节数据。
  5. 串行通信波特率: 由于 USART 异步串行通信双方无同步时钟,为保证收发双方能正确地接收和发送每帧数据,收发双方需要按照相同的位速率进行数据移位。位速率即是串行通信中的波特率,收发双方需保持一致的通信波特率,才能正确地进行数据收发。
  • USARTDIV是无符号定点数,fck是外设时钟

注意: CPU的时钟频率越低,则某一特定波特率的误差也越低。

五. USART特性


如图,USART传输数据字长可以设置为8位或者9位(包括奇偶校验位),若选择八位字长,那么数据长度只有7位,没有一个字节;若选择9位字长,出去奇偶校验位刚好一个字节长度,所以一般会选择八位字长无校验位,和九位字长有校验位。

Tx在起始位拉低电平,在停止位拉高电平,表明传输的开始和结束

空闲符号 被视为完全由’1’组成的一个完整的数据帧,后面跟着包含了数据的下一帧的开始位(‘1的位数也包括了停止位的位数)。

断开符号 被视为在一个帧周期内全部收到’0’(包括停止位期间,也是’0’)。在断开帧结束时,发送器再插入1或2个停止位(‘1’)来应答起始位。

六. 发送器

在USART发送期间,在TX引脚上首先移出数据的最低有效位。

USART支持多种停止位的配置: 0.5、 1、 1.5和2个停止位。表示的停止时间的长短

  1. 1个停止位:停止位位数的默认值
  2. 2个停止位:可用于常规USART模式、单线模式以及调制解调器模式。
  3. 0.5个停止位:在智能卡模式下接收数据时使用。
  4. 1.5个停止位:在智能卡模式下发送和接收数据时使用。

配置步骤:

  1. 通过在USART_CR1寄存器上置位UE位来激活USART
  2. 编程USART_CR1的M位来定义字长
  3. 在USART_CR2中编程停止位的位数
  4. 如果采用多缓冲器通信,配置USART_CR3中的DMA使能位(DMAT)。按多缓冲器通信中的描述配置DMA寄存器。
  5. 利用USART_BRR寄存器选择要求的波特率
  6. 设置USART_CR1中的TE位,发送一个空闲帧作为第一次数据发送。
  7. 把要发送的数据写进USART_DR寄存器(此动作清除TXE位)。在只有一个缓冲器的情况下,对每个待发送的数据重复步骤7。
  8. 在USART_DR寄存器中写入最后一个数据字后,要等待TC=1,它表示最后一个数据帧的传输结束。当需要关闭USART或需要进入停机模式之前,需要确认传输结束,避免破坏最后一次传输

七. 接收器

在USART接收期间,数据的最低有效位首先从RX脚移进。

可设置的停止位:在正常模式时,可以是1或2个,在智能卡模式里可能是0.5或1.5个
配置步骤:

  1. 将USART_CR1寄存器的UE置1来激活USART。
  2. 编程USART_CR1的M位定义字长
  3. 在USART_CR2中编写停止位的个数
  4. 如果需多缓冲器通信,选择USART_CR3中的DMA使能位(DMAR)。按多缓冲器通信所要求的配置DMA寄存器。
  5. 利用波特率寄存器USART_BRR选择希望的波特率
  6. 设置USART_CR1的RE位。激活接收器,使它开始寻找起始位。

八. 代码分析

初始化函数Serial_Init()
void Serial_Init()
{
	GPIO_InitTypeDef GPIO_InitStruct;
	USART_InitTypeDef USART_InitStruct;
	NVIC_InitTypeDef NVIC_InitStruct;
	
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1,ENABLE);
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);

	GPIO_InitStruct.GPIO_Mode=GPIO_Mode_AF_PP;
	GPIO_InitStruct.GPIO_Pin=GPIO_Pin_9;
	GPIO_InitStruct.GPIO_Speed=GPIO_Speed_50MHz;
	GPIO_Init(GPIOA,&GPIO_InitStruct);
	
	GPIO_InitStruct.GPIO_Mode=GPIO_Mode_IPU;
	GPIO_InitStruct.GPIO_Pin=GPIO_Pin_10;
	GPIO_InitStruct.GPIO_Speed=GPIO_Speed_50MHz;
	GPIO_Init(GPIOA,&GPIO_InitStruct);

	USART_InitStruct.USART_BaudRate=9600;
	USART_InitStruct.USART_HardwareFlowControl=USART_HardwareFlowControl_None;
	USART_InitStruct.USART_Mode=USART_Mode_Tx | USART_Mode_Rx;
	USART_InitStruct.USART_Parity=USART_Parity_No;
	USART_InitStruct.USART_StopBits=USART_StopBits_1;
	USART_InitStruct.USART_WordLength=USART_WordLength_8b;
	
	USART_Init(USART1,&USART_InitStruct);
	
	USART_ITConfig(USART1,USART_IT_RXNE,ENABLE);
	
	NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
	
	NVIC_InitStruct.NVIC_IRQChannel=USART1_IRQn;
	NVIC_InitStruct.NVIC_IRQChannelCmd=ENABLE;
	NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority=1;
	NVIC_InitStruct.NVIC_IRQChannelSubPriority=1;
    NVIC_Init(&NVIC_InitStruct);
	
	USART_Cmd(USART1,ENABLE);
}

主体逻辑是: 打开时钟,初始化GPIO,串口和中断优先级寄存器。

初始化GPIO: 其中可以看到调用了两次GPIO_Init()函数,为什么呢?主要是因为串口的发送(GPIOA_Pin_9)和接收(GPIOA_Pin_10)引脚是复用在两个固定的GPIO口,所选择的模式不一样所以需要分别进行初始化。


这里需要注意的是一定要分别初始化两个GPIO口,不然的话,按照顺序执行每一行代码就会只配置接收模式。(这是我找了一晚上粗心犯下的错)。

配置串口:

  1. 参数介绍:
  • USART_BaudRate 比特率
  • USART_HardwareFlowControl 通过配置选择是否需要硬件流控制
  • USART_Mode=USART_Mode_Tx | USART_Mode_Rx 配置串口是接收模式还是发送模式,还是二者同时设置
  • USART_Parity 设置校验位,选择奇校验/偶校验/不需要校验
  • USART_StopBits 设置停止位
  • USART_WordLength 设置字节长度,是九字节(一般会选择校验位)还是八字节(一般不选择校验位)
函数USART_Init(USART1,&USART_InitStruct)
void USART_Init(USART_TypeDef* USARTx, USART_InitTypeDef* USART_InitStruct)
{
  uint32_t tmpreg = 0x00, apbclock = 0x00;
  uint32_t integerdivider = 0x00;
  uint32_t fractionaldivider = 0x00;
  uint32_t usartxbase = 0;
  RCC_ClocksTypeDef RCC_ClocksStatus;
  /* Check the parameters */
  assert_param(IS_USART_ALL_PERIPH(USARTx));
  assert_param(IS_USART_BAUDRATE(USART_InitStruct->USART_BaudRate));  
  assert_param(IS_USART_WORD_LENGTH(USART_InitStruct->USART_WordLength));
  assert_param(IS_USART_STOPBITS(USART_InitStruct->USART_StopBits));
  assert_param(IS_USART_PARITY(USART_InitStruct->USART_Parity));
  assert_param(IS_USART_MODE(USART_InitStruct->USART_Mode));
  assert_param(IS_USART_HARDWARE_FLOW_CONTROL(USART_InitStruct->USART_HardwareFlowControl));
  /* The hardware flow control is available only for USART1, USART2 and USART3 */
  if (USART_InitStruct->USART_HardwareFlowControl != USART_HardwareFlowControl_None)
  {
    assert_param(IS_USART_123_PERIPH(USARTx));
  }

  usartxbase = (uint32_t)USARTx;

/*---------------------------- USART CR2 Configuration -----------------------*/
  tmpreg = USARTx->CR2;
  tmpreg &= CR2_STOP_CLEAR_Mask;
  tmpreg |= (uint32_t)USART_InitStruct->USART_StopBits;
  
  /* Write to USART CR2 */
  USARTx->CR2 = (uint16_t)tmpreg;

/*---------------------------- USART CR1 Configuration -----------------------*/
  tmpreg = USARTx->CR1;
  tmpreg &= CR1_CLEAR_Mask;
  tmpreg |= (uint32_t)USART_InitStruct->USART_WordLength | USART_InitStruct->USART_Parity |
            USART_InitStruct->USART_Mode;
  /* Write to USART CR1 */
  USARTx->CR1 = (uint16_t)tmpreg;

/*---------------------------- USART CR3 Configuration -----------------------*/  
  tmpreg = USARTx->CR3;
  tmpreg &= CR3_CLEAR_Mask;
  tmpreg |= USART_InitStruct->USART_HardwareFlowControl;
  USARTx->CR3 = (uint16_t)tmpreg;

/*---------------------------- USART BRR Configuration -----------------------*/
  /* Configure the USART Baud Rate -------------------------------------------*/
  RCC_GetClocksFreq(&RCC_ClocksStatus);
  if (usartxbase == USART1_BASE)
  {
    apbclock = RCC_ClocksStatus.PCLK2_Frequency;
  }
  else
  {
    apbclock = RCC_ClocksStatus.PCLK1_Frequency;
  }
  
  /* Determine the integer part */
  if ((USARTx->CR1 & CR1_OVER8_Set) != 0)
  {
    integerdivider = ((25 * apbclock) / (2 * (USART_InitStruct->USART_BaudRate)));    
  }
  else
  
    integerdivider = ((25 * apbclock) / (4 * (USART_InitStruct->USART_BaudRate)));    
  }
  tmpreg = (integerdivider / 100) << 4;

  fractionaldivider = integerdivider - (100 * (tmpreg >> 4));

  /* Implement the fractional part in the register */
  if ((USARTx->CR1 & CR1_OVER8_Set) != 0)
  {
    tmpreg |= ((((fractionaldivider * 8) + 50) / 100)) & ((uint8_t)0x07);
  }
  else 
  {
    tmpreg |= ((((fractionaldivider * 16) + 50) / 100)) & ((uint8_t)0x0F);
  }
  
  /* Write to USART BRR */
  USARTx->BRR = (uint16_t)tmpreg;
}

USART CR1/CR2/CR3 都是串口的控制寄存器,软件通过将对应需要配置参数(硬件流控制,校验位,停止位,字节长度,收发模式)对应的每一位清零之后再配置对应的参数。具体的对应关系参考手册。

  RCC_GetClocksFreq(&RCC_ClocksStatus);
  if (usartxbase == USART1_BASE)
  {
    apbclock = RCC_ClocksStatus.PCLK2_Frequency;
  }
  else
  {
    apbclock = RCC_ClocksStatus.PCLK1_Frequency;
  }

RCC_GetClocksFreq(&RCC_ClocksStatus);这个函数主要是用来配置时钟的频率,又因为只有USART1挂载在APB2总线上的,所有这样写

之后的代码就是通过一系列的计算算出最后需要放在BBR寄存器中的波特率值。


Tx/Rx波特率是我们自己配置的,通过变量USART_BaudRate。需要计算的是USARTDIV。fck会根据挂载总线的不同输出不同的频率,USART1使用PCLK2(最高72MHz),其它USART使用PCLK1(最高36MHz)。


以fck=36MHz,波特率=9600为例,算出的USARTDIV=234.375,整数部分为234,小数部分0.375*16=6(最后结果0xEA6)
注: CPU的时钟频率越低,则某一特定波特率的误差也越低。可以达到的波特率上限可以由这组数据得到。

代码可以简化如下:

BRR=(PCLK/(8*Baud))<<3=PCLK/Baud,结果公式一模一样,然后将整数部分左移1位就行了。完整代码如下:

uint16_t Div = PCLK/BAUD; //也可写成(PCLK + BAUD/2)/BAUD

uint16_t DIV_Mantissa = (Div & ~0x7)<<1;

uint16_t DIV_Fraction = Div & 0x07;

BRR = DIV_Mantissa | DIV_Fraction;
  • 20
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值