当前位置:主页 > 查看内容

关于 STM32F103 串口异步通信原理及实现固件库详解

发布时间:2021-06-15 00:00| 位朋友查看

简介:STM32f103串口通信详解 原理分析 首先我们从串口通信的物理层和协议层来分别分析。 物理层 对于串口通信的物理层的标准变化有很多种在这儿我主要是讲解 RS-232 标准。 这儿是以 RS-232 标准的常见设备通信结构图 由图可以看到两个信号的 DB9 接口通过串口信……

STM32f103串口通信详解

原理分析

首先,我们从串口通信的物理层和协议层来分别分析。

  1. 物理层
    对于串口通信的物理层的标准变化有很多种,在这儿,我主要是讲解 RS-232 标准。
    这儿是以 RS-232 标准的常见设备通信结构图:
    在这里插入图片描述
    由图可以看到,两个信号的 DB9 接口,通过串口信号线连接在一起,信号线是使用的 RS-232 标准传输数据信号。但是 RS-232 标准信号不能直接被控制器识别使用,所以我们需要使用一个电平转换芯片转换成 TTL 标准信号,才能实现通信。
    为什么需要转换呢? RS-232 与 TTL 有什么区别呢?
    在这里插入图片描述
    上图是两种标准电平信号的区别。
    在这里插入图片描述
    上图是两种电平标准表示同一个信号的对比图。
    对于 DB9 这种串口线我就不过多介绍了,我们只需要了解到下图的连线方式就可以了。
    在这里插入图片描述
    这儿要记住, RXD 与 TXD是交叉连接的。
  2. 协议层

串口通信的数据包又发送设备通过自身的 TXD 接口传输到接收设备的 RXD 接口,在串口通讯的协议层中,数据结构又下图所示,起始位,数据位,校验位,停止位。
在这里插入图片描述
波特率
由于异步通信之中是没有时钟信号的,所以需要在两个设备之间约定好波特率(及每个码元的长度)。
通讯的起始和停止信号
串口通讯的一个数据包从起始信号开始,到停止信号结束,起始信号由一个逻辑 0 表示,停止信号可以由 0.5 , 1 , 1.5 , 或者 2 来表示( 2 个位),只要两台设备约定一致就行。
有效数据
起始位之后就是有效数据(主体数据),一般 5 , 6 , 7 , 8 位长。
数据校验
由于数据通信容易受到外部干扰而导致数据偏差,可以通过加入校验位来解决,一般来说,有奇偶校验, 01 校验,或者无校验。
奇校验要求有效数据加上校验位中 1 的个数位奇数,而偶校验就要求为偶数, 0 校验校验位总位为 0 , 1 校验校验位总为 1。

现在开始介绍 STM32 的 USART 原理

在这里插入图片描述
接下来我们来具体分析这张图:

功能引脚
在这里插入图片描述
在这里插入图片描述

从这儿我们可以看到,在 STM32 中,它有这么一些功能引脚:
TX :发送数据的引脚
RX:接收数据的引脚
SW_RX:数据接收引脚,只用于单线和智能卡模式,属于内部引脚,没有具体的外部引脚
nRTS:请求以发送, n 代表低电平有效。如果使能这个引脚,当准备接收数据时就会变成低电平,当接收寄存器已经满了,就会置高。
nCTS:清除以发送,低电平有效,若使能,当发送数据之前会检测一下,若是低电平,则可以发送数据,若是高电平,则在当前数据发送完后停止发送。
SCLK:发送器时钟输出引脚,仅适用于同步模式
因为每个芯片对应的 USART 引脚不一样,所以需要去芯片资料自行查阅。

数据寄存器
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

在 STM32 中 USART 的数据寄存器只有低九位有效用来存放接收和发送的数据,并且第九位数据是否有效取决于 USART 控制寄存器决定。
在这里插入图片描述
发送和接收的数据都是放在数据寄存器中,所以数据寄存器实际上包含了两个寄存器,一个是用于发送的可写寄存器 TDR ,一个是用于接收的可读 RDR,当进行读写操作时,数据都是放在这个数据寄存器当中。
TDR 和 RDR 都是介于系统总线和移位寄存器之间。串行通信是一个位一个位传输的, 发送时把 TDR 内容转移到发送移位寄存器,然后把移位寄存器数据每一位发送出去,接收 时把接收到的每一位顺序保存在接收移位寄存器内然后才转移到 RDR。

控制器
在这里插入图片描述
当 USART_CR1 寄存器发送使能置 1 ,也就是 TE 置 1 时,启动数据发送(此时可以往数据寄存器里面写入数据。),发送移位寄存器的 数据会在 TX 引脚输出,当 RE 置 1 ,使能 USART 接收, RX 口开始搜索起始位,当确定到起始位后,将接收到的数据放到数据寄存器里,并把 RXNE 位置 1 。如果是同步模式, SCLK 也会输出时钟信号。
在这里插入图片描述
停止位时间长短可以通过 USART 控制寄存器2控制。
在这里插入图片描述
在这里插入图片描述

小数波特率的生成
在这里插入图片描述
USART 的接收好和发送使用同一个波特率,计算公式为:
在这里插入图片描述
f(CK) 为 USART 时钟,USARTDIV是一个存放波特率的寄存器
在这里插入图片描述

校验控制
在 STM32F103 系列的 USART 还支持奇偶校验位,当使用奇偶校验时,数据位加上校验位一共 9 位,此时的 CR1 寄存器 M 位需要置 1 ,设置为 9 位数据位。

中断控制
当然,对于串口通信中,有多个串口中断请求事件,分别代表了各种状态下的情况。
所有 USART 的控制都在下列寄存器里。

状态寄存器 SR

在这里插入图片描述
在这里插入图片描述

数据寄存器 DR

在这里插入图片描述
在这里插入图片描述

波特比例寄存器 BRR

在这里插入图片描述

控制寄存器 CR1

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

控制寄存器 CR2

在这里插入图片描述
在这里插入图片描述

控制寄存器 CR3

在这里插入图片描述
在这里插入图片描述

保护时间和预分频寄存器 GTPR

在这里插入图片描述
在这里插入图片描述
此上,就是所有关于 STM32F103 的 USART 的所有寄存器,接下来来实操固件库函数吧!

代码

#define DEBUG_USART_IRQ USART1_IRQn	//宏定义中断源,方便移植,增强可读性。
#define DEBUG_USART_TX_GPIO_PORT GPIOA	//宏定义TX的GPIO。
#define DEBUG_USART_TX_GPIO_PIN GPIO_Pin_9	//宏定义TX口的引脚。
#define DEBUG_USART_RX_GPIO_PORT GPIOA	//宏定义RX的GPIO。
#define DEBUG_USART_RX_GPIO_PIN GPIO_Pin_10	//宏定义RX口的引脚。
#define DEBUG_USART_BAUDRATE 115200	//宏定义波特率。
#define  DEBUG_USARTx USART1	//宏定义串口。

static void NVIC_Configuration(void)	//这儿是USART的中断配置,static代表限制了此函数只能在本文件之中使用。
{
  NVIC_InitTypeDef NVIC_InitStructure;	//定义一个NVIC的结构体。
  
  NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);	//配置优先级分组。
  NVIC_InitStructure.NVIC_IRQChannel = DEBUG_USART_IRQ;	//配置USART为中断源
  NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;	//选择中断主优先级。
  NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;	//配置子优先级。
  NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;	//使能中断。
  NVIC_Init(&NVIC_InitStructure);	//初始化NVIC结构体。
}

void USART_Config(void)
{
	GPIO_InitTypeDef GPIO_InitStructure;	//定义一个GPIO结构体。
	USART_InitTypeDef USART_InitStructure;	//定义一个USART结构体。

	DEBUG_USART_GPIO_APBxClkCmd(DEBUG_USART_GPIO_CLK, ENABLE);	// 打开串口GPIO的时钟。
	DEBUG_USART_APBxClkCmd(DEBUG_USART_CLK, ENABLE);	// 打开USART的时钟。
	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);	//初始化发送引脚。
	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);	//初始化接收引脚。
	USART_InitStructure.USART_BaudRate = DEBUG_USART_BAUDRATE;	//配置波特率。
	USART_InitStructure.USART_WordLength = USART_WordLength_8b;	//配置数据字长。
	USART_InitStructure.USART_StopBits = USART_StopBits_1;	//配置停止位。
	USART_InitStructure.USART_Parity = USART_Parity_No ;	//配置校验位。
	USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;	//关闭硬件流控制。
	USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;	//配置工作模式为接收和发送。
	USART_Init(DEBUG_USARTx, &USART_InitStructure);	//初始化USART结构体。
	NVIC_Configuration();	//串口中断优先级配置。
	USART_ITConfig(DEBUG_USARTx, USART_IT_RXNE, ENABLE);	//使能串口接收中断。
	USART_Cmd(DEBUG_USARTx, ENABLE);	//使能串口。	    
}

以上,就是串口的所有配置了,此时串口已经打开,我们需要怎么来使用呢?可以直接使用固件库里面的函数。

void Usart_SendByte( USART_TypeDef * pUSARTx, uint8_t ch)	//发送一个字节的数据
{
	USART_SendData(pUSARTx, ch);	//发送一个字节数据到USART的数据寄存器,进行发送。
	while (USART_GetFlagStatus(pUSARTx, USART_FLAG_TXE) == RESET);	//等待发送数据寄存器为空,数据发送出去后才退出发送函数。
}

void Usart_SendArray( USART_TypeDef * pUSARTx, uint8_t *array, uint16_t num)	//发送一个八位数据的数组。
{
  uint8_t i;	//定义一个数来记录数组个数。
  for(i=0; i<num; i++)	//i记录发送数据组数,当数组全部发送完成,退出循环。
  {
  	Usart_SendByte(pUSARTx, array[i]);	//每次发送一个字节数据到USART数据寄存器。
  }
  while(USART_GetFlagStatus(pUSARTx, USART_FLAG_TC)==RESET);	//等待发送完成后退出函数。
}

void Usart_SendString( USART_TypeDef * pUSARTx, char *str)	//发送字符串。
{
	unsigned int k=0;	//定义一个数,记录字符串长度。
	do 
	{
		Usart_SendByte(pUSARTx, *(str + k));	//字符是有ASCII表表达,所以每一个字符都是一个八位的数据。
     	k++;	//记录数据已发送长度。
  	} while(*(str + k)!='\0');	//判断下一个字符是否为'\0'。
 	while(USART_GetFlagStatus(pUSARTx,USART_FLAG_TC)==RESET);	//等待发送完成。
}

void Usart_SendHalfWord( USART_TypeDef * pUSARTx, uint16_t ch)	//发送16位数据。
{
	uint8_t temp_h, temp_l;	//将16位数据拆分为两个8位数据。
	
	temp_h = (ch&0XFF00)>>8;	//取出高八位。
	temp_l = ch&0XFF;	//取出低八位。
	USART_SendData(pUSARTx,temp_h);	//发送高八位。
	while (USART_GetFlagStatus(pUSARTx, USART_FLAG_TXE) == RESET);	//等待高8位发送完成。
	USART_SendData(pUSARTx,temp_l);	//发送低八位。
	while (USART_GetFlagStatus(pUSARTx, USART_FLAG_TXE) == RESET);	//等待低8位发送完成。
}

以上就是串口的发送函数了,读取数据就要去中断里面读取,中断函数全部在 stm32f10x_it.c 文件里。

#define  DEBUG_USART_IRQHandler USART1_IRQHandler	//宏定义串口中断函数名字。

void DEBUG_USART_IRQHandler(void)
{
  uint8_t ucTemp;	//定义一个8位数据来保存数据。
  
  if(USART_GetITStatus(DEBUG_USARTx, USART_IT_RXNE)!=RESET)	//确认数据寄存器不为空,读取数据。
  {		
  	ucTemp = USART_ReceiveData(DEBUG_USARTx);	//读取数据,可以使用全局变量来保存。
  }	 
}

以上,就是关于串口的发送和读取了,不过,对于写习惯了 C 语言的人来说,更加习惯用 C 语言里面的输入输出吧,那串口可以使用这个来进行发送和接收数据吗?当然可以!只需要将函数重定向就可以了。

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

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

		return (int)USART_ReceiveData(DEBUG_USARTx);
}

是不是很简单,相信大家都已经学会了 STM32 的串口使用了吧。
谢谢大家的阅读!!!

;原文链接:https://blog.csdn.net/weixin_53624282/article/details/115563629
本站部分内容转载于网络,版权归原作者所有,转载之目的在于传播更多优秀技术内容,如有侵权请联系QQ/微信:153890879删除,谢谢!

推荐图文


随机推荐