【STM32】USART

本文详细介绍了STM32的USART(通用同步/异步收发器)的原理、硬件连接、功能框图、代码编写及中断服务。USART支持与从机(如电脑USB、RS232、RS485设备)的连接,并通过配置波特率、数据寄存器、控制与状态寄存器实现数据传输。文中还提供了中断接收和发送的示例代码,包括发送字节、数组、字符串和16位数。此外,讨论了USART的优缺点及其在工程中的应用。
摘要由CSDN通过智能技术生成


一、USART是什么?

USART全称Universal Synchronous/Asynchronous Receiver/Transmitter,即通用同步异步收发器,是一个全双工通用同步/异步串行收发模块。在嵌入式领域,它还有一个更口语化的称呼,即“串口”。

跟USART相似的还有一个UART,它是在USART基础上裁剪掉了同步通信功能,UART应用更为普遍。

二、硬件基础

USART仅规定了协议层通信标准,物理层硬件可以根据通讯对象灵活选择。

1.USART主机接USART从机

这种情况不用进行任何转换,直接将信号线交叉连接,地线直接连接即可。在这里插入图片描述

2.USART主机接电脑USB

一般的单片机硬件设计都会预留一个USART连接电脑,用于在调试程序时可以将调试信息打印在电脑端的串口调试助手。
但单片机的USART无法直接与PC通讯,需要借助CH340CP2102等芯片实现USB与USART的相互转化。
在这里插入图片描述

3.USART主机接RS232从机

单片机的USART无法直接与232设备通讯,需要借助MAX232等芯片实现232与USART的相互转化。
在这里插入图片描述

4.USART主机接RS485从机

单片机的USART无法直接与485设备通讯,需要借助MAX485等芯片实现485与USART的相互转化。
在这里插入图片描述

三、功能框图

在这里插入图片描述

1.功能引脚

TX:数据发送
RX:数据接收
SW_RX:
nRTS:硬件流控制,略
nCTS:硬件流控制,略
SCLK:仅适用于同步模式,略

2.数据寄存器

数据寄存器USART_DR只有低9位有效,并且第9位是否有效受CR1寄存器的M位控制,详见控制寄存器部分。

USART_DR实际包含两个寄存器,一个用于发送的TDR,一个用于接收的RDR。发送时往USART_DR写入数据会自动存入TDR,读取USART_DR会自动提取RDR的数据。
在这里插入图片描述

3.控制与状态寄存器

该部分寄存器囊括了USART接收发送的功能控制,收发状态监视,中断控制。
在这里插入图片描述
RE:接收使能
TE:发送使能
RXNEIE:接收缓冲区非空中断使能
TCIE:发送完成中断使能
PS:校验位(0:偶校验,1:奇校验)
PCE:校验控制使能
M:字长(0:8位,1:9位)
UE:USART使能
在这里插入图片描述
STOP:停止位
00:1个停止位;
01:0.5个停止位;
10:2个停止位;
11:1.5个停止位;

在这里插入图片描述
DMAT:DMA使能发送
DMAR:DMA使能接收

在这里插入图片描述
PE:校验错误
RXNE:读数据寄存器非空
TC:发送完成
TXE:发送数据寄存器空


TC与TXE的区别:数据从TDR发送到发送移位寄存器之后触发TXE置位,但数据还没有从TX引脚发送出去,只有数据从TX引脚发送出去之后,才算完成一次数据发送,触发TC置位。也就是说,TC相比TXE更进一步。


(1)发送

第一步:一般设置数据位为8位,停止位为1位,无校验位;//M=0,STOP=00,PCE=0;

此时字符帧格式为“起始位+数据帧+停止位”。


注意:奇偶校验只能验错,不能验对,校验准确率在50%以下,故一般设置为无校验。


第二步:使能USART,使能TE,即可启动数据发送,发送顺序为低位在前,高位在后


举例:以发送字符’A’为例,其ASCII码为65,二进制为0100 0001,则发送次序为1000 0010。
在这里插入图片描述


第三步:发送数据过程中需要等待TC标志位1,当其置位时,表示传输完成,如果之前有设置TCIE为1,则在数据传输完成时产生中断。

(2)接收

跟接收相似,使能RE 之后开始接收数据,接收完成后就把数据从移位寄存器移到RDR内,并把RXNE置位,如果之前将RXNEIE置位的话将触发中断。

4.波特比率寄存器

USART发送器和接收器使用相同的波特率,计算公式为:

波特率 = f P L C K 16 × U S A R T D I V 波特率=\frac{fPLCK}{16\times USARTDIV} 波特率=16×USARTDIVfPLCK
其中fPCLK为USART时钟,以F1为例,USART1挂载在APB2总线上,时钟频率为72M,USART2挂载在APB1总线上,时钟频率为36M。

在这里插入图片描述

USARTDIV由BRR寄存器定义,低4位定义其小数部分,高12位定义其整数部分。


例子:如果要波特率为115200,则由:
115200 = 72000000 16 × U S A R T D I V 115200=\frac{72000000}{16\times USARTDIV} 115200=16×USARTDIV72000000
得到USARTDIV=39.0625,DIV_Fraction=0.0625*16=0x01,DIV_Mantissa=39=0x17,则BRR应设置为0x171。

有时候不能保证经过配置后的实际波特率与目标波特率完全相等,但允许10%以内的误差,要知道USART是支持异步通信的,收发双方波特率不用完全一致。



波特率与比特率的区别:比特率容易理解,即bit/s,波特率表示每秒传输多少个码元,常见的通讯中一个码元表示两种状态(0或1),要知道一个比特也表示两种状态(0或1),所以大部分情况下比特率与波特率从数值上是划等号的(单位不一样)。

假设某种通讯中一个码元表示4种状态(ABCD),物理上可以用0V、2V、4V、6V表示。此时必须用两个二进制才能表示此种通讯下的一个状态(00表示A,01表示B,10表示C,11表示D),也就是一个码元对应两个二进制位,码元数是二进制数的一半,故波特率是比特率的一半。

波特率 = 比特率 / 每符号含的比特数 波特率=比特率/每符号含的比特数 波特率=比特率/每符号含的比特数

比特率仅是二进制下的概念,波特率是所有进制下的概念,比特率是波特率的子集。



关于不同波特率对应的传输速度
常见波特率从2400到460800不等,2400即

2400 / 8 / 1024 = 0.29 k b / s 2400/8/1024=0.29kb/s 2400/8/1024=0.29kb/s
115200即

115200 / 8 / 1024 = 14.06 k b / s 115200/8/1024=14.06kb/s 115200/8/1024=14.06kb/s
460800即

460800 / 8 / 1024 = 56.25 k b / s 460800/8/1024=56.25kb/s 460800/8/1024=56.25kb/s

USART对波特率其实没有上限,需要考虑到实际通信双方的参数要求,F1的波特率最高支持4.5Mb/s


5.保护时间和预分频寄存器

智能卡模式或红外模式下需要配置GTPR,此处省略。

在这里插入图片描述

四、代码编写

下面的代码都是基于“USART主机接电脑USB”硬件基础编写的。

1.中断接收

功能:主机可向PC发送字节、数组、字符串、16位数据;主机通过中断方式接收数据。
首先是硬件初始化:

void USART_Config(void)
{
	GPIO_InitTypeDef GPIO_InitStructure;
	USART_InitTypeDef USART_InitStructure;

	// 打开串口用到的GPIO的时钟
	DEBUG_USART_GPIO_APBxClkCmd(DEBUG_USART_GPIO_CLK, ENABLE);
	
	// 打开串口外设的时钟
	DEBUG_USART_APBxClkCmd(DEBUG_USART_CLK, ENABLE);

	// 将USART Tx的GPIO配置为推挽复用模式
	GPIO_InitStructure.GPIO_Pin = DEBUG_USART_TX_GPIO_PIN;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(DEBUG_USART_TX_GPIO_PORT, &GPIO_InitStructure);

  // 将USART Rx的GPIO配置为浮空输入模式
	GPIO_InitStructure.GPIO_Pin = DEBUG_USART_RX_GPIO_PIN;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
	GPIO_Init(DEBUG_USART_RX_GPIO_PORT, &GPIO_InitStructure);
	
	// 配置串口的工作参数
	// 配置波特率(BRR寄存器)
	USART_InitStructure.USART_BaudRate = DEBUG_USART_BAUDRATE;
	// 配置数据字长(CR1寄存器M位)
	USART_InitStructure.USART_WordLength = USART_WordLength_8b;
	// 配置停止位(CR2寄存器STOP位)
	USART_InitStructure.USART_StopBits = USART_StopBits_1;
	// 配置校验位(CR1寄存器PCE位)
	USART_InitStructure.USART_Parity = USART_Parity_No ;
	// 配置硬件流控制
	USART_InitStructure.USART_HardwareFlowControl = 
	USART_HardwareFlowControl_None;
	// 配置工作模式,收发一起(CR1寄存器TE/RE位)
	USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;
	// 完成串口的初始化配置
	USART_Init(DEBUG_USARTx, &USART_InitStructure);
	
	// 串口中断优先级配置
	NVIC_Configuration();
	
	// 使能串口接收中断(CR1寄存器RXNEIE位)
	USART_ITConfig(DEBUG_USARTx, USART_IT_RXNE, ENABLE);	
	
	// 使能串口(CR1寄存器UE位)
	USART_Cmd(DEBUG_USARTx, ENABLE);	    
}

为什么TXD/RXD要配置为推挽复用和浮空输入模式?

答:数据手册规定,具体可看【STM32】GPIO末尾


其中的串口中断优先级配置函数:

static void NVIC_Configuration(void)
{
  NVIC_InitTypeDef NVIC_InitStructure;
  
  /* 嵌套向量中断控制器组选择 */
  NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
  
  /* 配置USART为中断源 */
  NVIC_InitStructure.NVIC_IRQChannel = DEBUG_USART_IRQ;
  /* 抢断优先级*/
  NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;
  /* 子优先级 */
  NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;
  /* 使能中断 */
  NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
  /* 初始化配置NVIC */
  NVIC_Init(&NVIC_InitStructure);
}

到此初始化已经完成,可以开始发送数据。

(1)发送一个字节

void Usart_SendByte( USART_TypeDef * pUSARTx, uint8_t ch)
{
	/* 发送一个字节数据到USART */
	USART_SendData(pUSARTx,ch);
		
	/* 等待发送数据寄存器为空,置位后即退出循环 */
	/* 但TXE置位并不代表数据已从TXD引脚发送完毕,只是说明发送寄存器空了,可以填装下一个数据了 */
	while (USART_GetFlagStatus(pUSARTx, USART_FLAG_TXE) == RESET);	
}

USART_SendData是标准库函数,其定义如下:

void USART_SendData(USART_TypeDef* USARTx, uint16_t Data)
{
  /* Check the parameters */
  assert_param(IS_USART_ALL_PERIPH(USARTx));
  assert_param(IS_USART_DATA(Data)); 
    
  /* Transmit Data */
   /* 可以看出它是往DR寄存器直接装填数据*/
   /* DR寄存器是8位的,个人觉得形参写uint8_t更直接,可能是为了跟其他函数保持参数格式一致吧 */
  USARTx->DR = (Data & (uint16_t)0x01FF);
}

(2)发送8位的数组

void Usart_SendArray( USART_TypeDef * pUSARTx, uint8_t *array, uint16_t num)
{
  uint8_t i;
	
	for(i=0; i<num; i++)
  {
	    /* 发送一个字节数据到USART */
	    /* 调用上一个函数 */
	    Usart_SendByte(pUSARTx,array[i]);	
  
  }
	/* 等待发送完成 */
	while(USART_GetFlagStatus(pUSARTx,USART_FLAG_TC)==RESET);
}

(3)发送字符串

void Usart_SendString( USART_TypeDef * pUSARTx, char *str)
{
	unsigned int k=0;
  do 
  {
      Usart_SendByte( pUSARTx, *(str + k) );
      k++;
  } while(*(str + k)!='\0');
  
  /* 等待发送完成 */
  while(USART_GetFlagStatus(pUSARTx,USART_FLAG_TC)==RESET)
  {}
}

(4)发送一个16位数

void Usart_SendHalfWord( USART_TypeDef * pUSARTx, uint16_t ch)
{
	uint8_t temp_h, temp_l;
	
	/* 取出高八位 */
	temp_h = (ch&0XFF00)>>8;
	/* 取出低八位 */
	temp_l = ch&0XFF;
	
	/* 发送高八位 */
	USART_SendData(pUSARTx,temp_h);	
	while (USART_GetFlagStatus(pUSARTx, USART_FLAG_TXE) == RESET);
	
	/* 发送低八位 */
	USART_SendData(pUSARTx,temp_l);	
	while (USART_GetFlagStatus(pUSARTx, USART_FLAG_TXE) == RESET);	
}

(5)串口中断服务函数

void DEBUG_USART_IRQHandler(void)
{
  uint8_t ucTemp;
	if(USART_GetITStatus(DEBUG_USARTx,USART_IT_RXNE)!=RESET)
	{		
		ucTemp = USART_ReceiveData(DEBUG_USARTx);
    USART_SendData(DEBUG_USARTx,ucTemp);    
	}	 
}

2.DMA

此部分留到总结完DMA之后再补充

3.printf重定向

我们想直接使用c语言里面的printf、scanf等函数,就需要对其进行重定向。

由于printf 最终是调用 fputc 输出数据,而 fputc 是一个 weak(弱引用)函数,覆写即可重定向 printf。scanf也是如此。

///重定向c库函数printf到串口,重定向后可使用printf函数
int fputc(int ch, FILE *f)
{
		/* 发送一个字节数据到串口 */
		USART_SendData(DEBUG_USARTx, (uint8_t) ch);
		
		/* 等待发送完毕 */
		while (USART_GetFlagStatus(DEBUG_USARTx, USART_FLAG_TXE) == RESET);		
	
		return (ch);
}

///重定向c库函数scanf到串口,重写向后可使用scanf、getchar等函数
int fgetc(FILE *f)
{
		/* 等待串口输入数据 */
		while (USART_GetFlagStatus(DEBUG_USARTx, USART_FLAG_RXNE) == RESET);

		return (int)USART_ReceiveData(DEBUG_USARTx);
}

注意需要勾选微库:
在这里插入图片描述
这样,只要加上#include<stdio.h>,就可以用printf打印数据了。

4.标准库函数解释

void USART_DeInit(USART_TypeDef* USARTx);//将USART寄存器重置为默认值 ,即恢复出厂设置
void USART_Init(USART_TypeDef* USARTx, USART_InitTypeDef* USART_InitStruct);//将USART_InitStruct里的参数写入寄存器
void USART_StructInit(USART_InitTypeDef* USART_InitStruct);//给USART_InitStruct填充默认值
void USART_ClockInit(USART_TypeDef* USARTx, USART_ClockInitTypeDef* USART_ClockInitStruct);//同步通讯时需要用到
void USART_ClockStructInit(USART_ClockInitTypeDef* USART_ClockInitStruct);//给USART_ClockInitStruct填充默认值
void USART_Cmd(USART_TypeDef* USARTx, FunctionalState NewState);//使能USART
void USART_ITConfig(USART_TypeDef* USARTx, uint16_t USART_IT, FunctionalState NewState);//中断使能
void USART_DMACmd(USART_TypeDef* USARTx, uint16_t USART_DMAReq, FunctionalState NewState);//DMA使能
void USART_SetAddress(USART_TypeDef* USARTx, uint8_t USART_Address);//USART节点地址设置
void USART_WakeUpConfig(USART_TypeDef* USARTx, uint16_t USART_WakeUp);//USART唤醒方式
void USART_ReceiverWakeUpCmd(USART_TypeDef* USARTx, FunctionalState NewState);//静默模式使能
void USART_LINBreakDetectLengthConfig(USART_TypeDef* USARTx, uint16_t USART_LINBreakDetectLength);
void USART_LINCmd(USART_TypeDef* USARTx, FunctionalState NewState);
void USART_SendData(USART_TypeDef* USARTx, uint16_t Data);//发送数据
uint16_t USART_ReceiveData(USART_TypeDef* USARTx);//接收数据
void USART_SendBreak(USART_TypeDef* USARTx);//发送断开字符
void USART_SetGuardTime(USART_TypeDef* USARTx, uint8_t USART_GuardTime);
void USART_SetPrescaler(USART_TypeDef* USARTx, uint8_t USART_Prescaler);
void USART_SmartCardCmd(USART_TypeDef* USARTx, FunctionalState NewState);
void USART_SmartCardNACKCmd(USART_TypeDef* USARTx, FunctionalState NewState);
void USART_HalfDuplexCmd(USART_TypeDef* USARTx, FunctionalState NewState);
void USART_OverSampling8Cmd(USART_TypeDef* USARTx, FunctionalState NewState);
void USART_OneBitMethodCmd(USART_TypeDef* USARTx, FunctionalState NewState);
void USART_IrDAConfig(USART_TypeDef* USARTx, uint16_t USART_IrDAMode);
void USART_IrDACmd(USART_TypeDef* USARTx, FunctionalState NewState);
FlagStatus USART_GetFlagStatus(USART_TypeDef* USARTx, uint16_t USART_FLAG);//获取状态
void USART_ClearFlag(USART_TypeDef* USARTx, uint16_t USART_FLAG);//清除状态
ITStatus USART_GetITStatus(USART_TypeDef* USARTx, uint16_t USART_IT);//检查指定的USART中断是否发生。
void USART_ClearITPendingBit(USART_TypeDef* USARTx, uint16_t USART_IT);

总结

USART作为STM32除了GPIO外最重要的外设,在几乎每个工程中都有应用。
usart优点
(1)只要两根数据线就可以实现通讯(简单)
(2)由于只用于两个设备间一对一通讯,不需要寻址
usart缺点
(1)速度慢(一般应用都在Kb级别)
(2)仅适合于两个设备之间的通讯
(3)数据帧大小有限

参考文献

1.《STM32库开发实战指南》
2.超简单的一种通信,2分钟搞懂,串口通讯的工作原理!https://www.bilibili.com/video/BV1y34y147s5?spm_id_from=333.337.search-card.all.click&vd_source=53d27d53fc6ff276be6e93437afd3e0e
3.https://www.rfwireless-world.com/Terminology/Advantages-and-Disadvantages-of-UART.html

  • 6
    点赞
  • 58
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值