目录

1、按照数据传送的方向,分为:

2、按照通信方式,分为:

STM32串口通信基础

串口通信过程

UART(USART)框图

串口通信实验

编程要点

代码分析


通信接口背景知识

设备之间的通信方式

一般情况下,设备之间的通信方式可以分成并行通信和串行通信两种。它们的区别是:

串行通信的分类

1、按照数据传送的方向,分为:

  • 单工:数据传输只支持数据在一个方向上传输
  • 半双工:允许数据在两个方向上传输。但是,在某一时刻,只允许数据在一个方向上传输,它实际上是一种切换方向的单工通信,它不需要独立的接收端和发送端,两者可以合并一起使用一个端口。
  • 全双工:允许数据同时在两个方向上传输。因此,全双工通信是两个单工通信方式的结合,需要独立的接收端和发送端

2、按照通信方式,分为:

  • 同步通信:带时钟同步信号传输,比如SPI,I2C通信接口
  • 异步通信:不带时钟同步信号,比如UART(通用异步收发器),单总线

在同步通信中,收发设备上方会用一根信号线传输信号,在时钟信号的驱动下双方进行协调,同步数据。例如,通信中通常双方会统一规定在时钟信号的上升沿或者下降沿对数据线进行采样,

在异步通信中不使用时钟信号进行数据同步,他们直接在数据信号中穿插一些用于同步的信号位,或者将主题数据进行打包,以数据帧的格式传输数据。通信中还需要双方约定好数据的传输速率(也就是波特率)等,以便更好地同步。常用的波特率有9600、115200等

在同步通信中,数据信号所传输的内容绝大部分是有效数据,而异步通信中则会包含数据帧的各种标识符,所以同步通信效率高,但是同步通信双方的时钟允许误差小,稍稍始终出错就可能导致数据错乱,异步通信双方的始终允许误差大。

常见的串行通信接口

STM32串口通信基础

STM32的串口通信接口有两种,分别是:UART(通用异步收发器)、USART(通用同步异步收发器)。而对于大容量的STM32F10x芯片,分别有3个USART和2个UART

UART引脚连接方法:

  • RXD:数据输入引脚,数据接收
  • TXD:数据发送引脚,数据发送 

 对于两个芯片之间的连接,两个芯片GND共地,同时TXD和RXD交叉连接。这里的交叉连接的意思是,芯片1的RXD连接芯片2的TXD,芯片2的RXD连接芯片1的TXD。这样,两个芯片之间就可以进行TTL电平通信了。

 若是芯片与PC机(或者上位机)相连,除了共地之外,就不能这样交叉连接了。尽管PC机和芯片都有TXD和RXD引脚,但是通常PC机通常使用的是RS-232接口(通常为DB9封装),因此不能直接交叉连接。RS-232接口是9针(或引脚),通常是TXD和RXD经过电平转换得到的。故想要使得芯片与PC机的RS232接口直接通信,需要也将芯片的输入与输出端口电平传换成RS232类型,在交叉连接。

经过电平转换之后,芯片串口和RS232的电平标准是不一样的:

  • 单片机的电平标准(TTL电平):+5V表示1,0V表示0
  • RS232的电平标准:+15/+13V表示0,-15/-13表示1

RS232通信协议标准串口的设备间通信结构如下:

所以单片机串口与PC机串口通信就应该遵循下面的连接方式:在单片机串口与上位机给出的RS232口之间,通过电平转换电路(如下图中的Max232芯片)实现TTL电平与RS232电平之间的转换。

 具体要了解RS232串口的,可以直接看链接RS232串口简介

STM32的UART特点:

  • 全双工异步通信
  • 分数波特率发生器系统,提供精确的波特率。发送和接收共用的可编程波特率,最高可达4.5Mbits/S
  • 可编程的数据字长度(8位或者9位)
  • 可配置的停止位(支持1位或者2两位或者0.5或者1.5位)
  • 单独的发送器和接收器使能位
  • 检测标志:1、接收缓冲器 2、发送缓冲器 3、传输结束标志
  • 多个带标志的中断源,触发中断
  • 其他:校验控制,四个错误检测标志

串口通信过程

 STM32中UART参数

串口通信的数据包由发送设备通过自身的TXD接口传输到接收设备的RXD接口,通信双方的数据包格式要规约一致才能正常收发数据。STM32中串口异步通信需要定义的参数:起始位,数据位(8位或者9位),奇偶校验位,停止位,波特率设置

UART串口通信的数据包以帧为单位,常用的帧结构为:1位起始位+8位数据位+1位奇偶校验位(可选)+1位停止位。如下图所示:

 奇偶校验位分为奇校验和偶校验两种,是一种简单的数据误码校验方法。奇校验是指每帧数据中,包括数据位和奇偶校验位的全部9个位中1的个数必须为奇数;偶校验是指每帧数据中,包括数据位和奇偶校验位的全部9个位中1的个数必须为偶数。

校验方法除了奇校验(odd)、偶校验(even)之外,还可以有:0 校验(space)、1 校验(mark)以及无校验(noparity)。 0/1校验:不管有效数据中的内容是什么,校验位总为0或者1。

UART(USART)框图

这个框图分成上中下三个部分。

框图的上部分,数据从RX进入到接收移位寄存器,后进入到接收数据寄存器,最终供CPU或者DMA来进行读取;数据从CPU或者DMA传递过来,进入发送数据寄存器,后进入发送移位寄存器,最终通过TX发送出去。

然而,UART的发送和接收都需要波特率来进行控制,波特率是怎样进行控制的呢?

这就到了框图的下部分,在接受移位寄存器,发送移位寄存器都还有一个进入的箭头,分别连接到接收器控制,发送器控制。而这两者连接的又是接收器时钟、发送器时钟。也就是说,异步通信尽管没有时钟同步信号,但是在串口内部,是提供了时钟信号来进行控制的。而接受器时钟和发送器时钟是由什么控制的呢?

可以看到,接收器时钟和发送器时钟又被连接到同一个控制单元,也就是说它们共用一个波特率发生器。同时也可以看到接收器时钟(发生器时钟)的计算方法、USARTDIV的计算方法。

这里需要知道一个知识点:

  • USART1的时钟:PCLK2(高速)
  • USART2、USART3、UART4、UART5的时钟:PCLK1(低速)

串口通信实验

实现串口发送数据到上位机以及上位机发送数据到串口后接收到数据,并立马发送回上位机,以及通过发送的信息,控制RGB灯。

首先讲解的是USART的接发通信实验

编程要点

  1. 使能RX和TX引脚GPIO时钟和USART时钟
  2. 初始化GPIO,并将GPIO复用到USART上
  3. 配置USART参数
  4. 配置中断控制器并使能USART接收中断
  5. 使能USART
  6. 在USART接收中断服务函数实现数据接收和发送

代码分析

GPIO和USART宏定义

/*串口1*/
#define DEBUG_USARTx				USART1
#define USART_CLK					RCC_APB2Periph_USART1
#define USART_APBx_Cmd				RCC_APB2PeriphClockCmd
#define USART_BoundRate				115200
 
/*串口1引脚定义*/
#define USART_GPIO_CLK				RCC_APB2Periph_GPIOA
#define USART_GPIO_APBx_Cmd		    RCC_APB2PeriphClockCmd
#define USART_GPIO_TX_PORT		    GPIOA
#define USART_GPIO_TX_PIN			GPIO_Pin_9
#define USART_GPIO_RX_PORT		    GPIOA
#define USART_GPIO_RX_PIN			GPIO_Pin_10
 
/*串口1中断*/		
#define USART_IRQ					USART1_IRQn
#define USART_IRQ_Handler			USART1_IRQHandler


使用宏定义方便程序的移植和升级。开发板中的CH340G的收发引脚默认通过跳帽连接到USART1,如果想使用其他串口,也可以通过硬件改变和定义其他串口的宏定义来实现其他串口的作用。

嵌套向量中断控制器

void NVIC_Config(void)
{
	/*定义NVIC初始化结构体*/
	NVIC_InitTypeDef NVIC_InitStruct;
	/*设置优先级分组*/
	NVIC_PriorityGroupConfig(NVIC_PriorityGroup_1);
	/*设置中断源*/
	NVIC_InitStruct.NVIC_IRQChannel	=	USART_IRQ;
	/*中断使能*/
	NVIC_InitStruct.NVIC_IRQChannelCmd	=	ENABLE;
	/*设置中断抢占优先级*/
	NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority	=	1;
	/*设置中断子优先级*/
	NVIC_InitStruct.NVIC_IRQChannelSubPriority	=	1;
	/*结合初始化函数,设置相关的寄存器*/
	NVIC_Init(&NVIC_InitStruct);
}


USART初始化配置

void USART1_Config(void)
{
	/*定义串口1的初始化结构体*/
	USART_InitTypeDef USART_InitStruct;
	GPIO_InitTypeDef	GPIO_InitStruct;
	
	/*打开串口外设时钟*/
	USART_APBx_Cmd(USART_CLK,ENABLE);
	
	/*打开GPIO外设时钟*/
	USART_GPIO_APBx_Cmd(USART_GPIO_CLK,ENABLE);
	
	/*将USART的TX配置为复用推挽输出*/
	GPIO_InitStruct.GPIO_Mode		= 	GPIO_Mode_AF_OD;
	GPIO_InitStruct.GPIO_Pin		=	USART_GPIO_TX_PIN;
	GPIO_InitStruct.GPIO_Speed	    = 	GPIO_Speed_50MHz;
	GPIO_Init(USART_GPIO_TX_PORT,&GPIO_InitStruct);
	
	/*将USART的RX配置为浮空输入*/
	GPIO_InitStruct.GPIO_Mode		=		GPIO_Mode_IN_FLOATING;
	GPIO_InitStruct.GPIO_Pin		=		USART_GPIO_RX_PIN;
	GPIO_Init(USART_GPIO_RX_PORT,&GPIO_InitStruct);
	
	/*配置USART的参数设置*/
	
	/*USART的波特率设置为115200*/
	USART_InitStruct.USART_BaudRate	=	USART_BoundRate;
	/*配置USART的模式为TX和RX*/
	USART_InitStruct.USART_Mode		=	USART_Mode_Tx | USART_Mode_Rx;
	/*配置USART为1位停止位*/
	USART_InitStruct.USART_StopBits	=	USART_StopBits_1;
	/*配置USART的字长为8位字长*/
	USART_InitStruct.USART_WordLength	=	USART_WordLength_8b;
	/*配置USART的校验为无校验*/
	USART_InitStruct.USART_Parity		=	USART_Parity_No;
	/*配置USART的硬件控制流为None*/
	USART_InitStruct.USART_HardwareFlowControl	=	USART_HardwareFlowControl_None;
	/*结合USART的初始化函数,设置USART的寄存器*/
	USART_Init(DEBUG_USARTx,&USART_InitStruct);
	
	
	/*配置USART的中断优先级*/
	NVIC_Config();
	
	/*使能串口发送中断*/
	USART_ITConfig(DEBUG_USARTx, USART_IT_TXE,ENABLE);
	USART_ITConfig(DEBUG_USARTx, USART_IT_RXNE,ENABLE);
 
		/*使能串口*/
	USART_Cmd(DEBUG_USARTx,ENABLE);
 
}


使用GPIO_InitTypeDef和USART_InitTypeDef构体定义一个GPIO初始化变量以及一个USART初始化变量,这两个结构体这里不再赘述。

字符发送

/*设置单字节发送函数*/
void USART_SendByte(USART_TypeDef* pUSARTx,uint8_t Data)
{
	USART_SendData(pUSARTx, Data);
	/*等待发送完成*/
  while((USART_GetFlagStatus(pUSARTx, USART_FLAG_TXE)) == RESET);
 
}
 
/*设置双字节发送函数*/
void USART_SendTwoByte(USART_TypeDef* pUSARTx,uint16_t Data)
{
	uint8_t temp_h,temp_l;
	temp_h = (Data&0xff00)>>8;
	temp_l = (Data&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);
 
}
 
/*设置多字节数组发送*/
void USART_SendArray(USART_TypeDef* pUSARTx,uint8_t *array,uint8_t num)
{
	uint8_t i;
	for(i=0;i<=num;i++)
	{
	USART_SendByte(pUSARTx, *(array++));
	}
	/*等待发送完成*/
  while((USART_GetFlagStatus(pUSARTx, USART_FLAG_TC)) == RESET);
 
 
}
 
/*设置字符串发送*/
void USART_SendString(USART_TypeDef* pUSARTx,char *str)
{
	for(;*str!='\0';str++)
	{
		USART_SendByte(pUSARTx, *str);
	}
  while((USART_GetFlagStatus(pUSARTx, USART_FLAG_TC)) == RESET);
 
}


USART中断服务函数

void USART_IRQ_Handler(void)
{
	uint8_t temp;
	if( (USART_GetITStatus(DEBUG_USARTx, USART_IT_TXE) != RESET) )
	{
		LED_B(ON);
		delay(0XFFFFFF);
		LED_B(OFF);
		USART_ITConfig(DEBUG_USARTx,USART_IT_TXE,DISABLE);
	}
	if( (USART_GetITStatus(DEBUG_USARTx, USART_IT_RXNE) != RESET) )
	{
		temp	=	USART_ReceiveData(DEBUG_USARTx);
		USART_SendData(DEBUG_USARTx, temp);
		
	}
 
}


主函数

int main(void)
{
	uint8_t a[10] = {65,66,67,68,69,70,71,72,73,74};
	GPIO_LED_Config();
	USART1_Config();
	USART_SendByte(DEBUG_USARTx,0x63);
  USART_SendTwoByte(DEBUG_USARTx,0x4344);
	USART_SendArray(DEBUG_USARTx,a,10);
  USART_SendString(DEBUG_USARTx, "\n欢迎来到学习STM32的世界\n");
	printf("串口测试输出\n");
	while(1)
	{
	
	
	}	
	
	
}


USART1指令控制RGB彩灯实验

编程要点

  1. 初始化配置RGB彩色灯GPIO
  2. 使能RX和TX引脚GPIO时钟和USART时钟
  3. 初始化GPIO,并将GPIO复用到USART上
  4. 配置USART参数
  5. 使能USART
  6. 获取指令输入,根据指令控制RGB彩色灯

与上一个实验不同,这里我们不再使用接收中断,而是直接靠查询标志位的方式来实现接收。

上面的宏定义以及USART的配置不在赘述。

重定向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函数*/
int fgetc(FILE *f)
{
	/*等待串口输入数据*/
	while(USART_GetFlagStatus(DEBUG_USARTx,USART_FLAG_RXNE) == RESET);
	
	return (int)USART_ReceiveData(DEBUG_USARTx);
 
}

 

主函数

int main(void)
{
//	uint8_t a[10] = {65,66,67,68,69,70,71,72,73,74};
	uint8_t ch;
	extern uint8_t temp;
	ch = temp;
	GPIO_LED_Config();
	USART1_Config();
//	USART_SendByte(DEBUG_USARTx,0x63);
//  USART_SendTwoByte(DEBUG_USARTx,0x4344);
//	USART_SendArray(DEBUG_USARTx,a,10);
//  USART_SendString(DEBUG_USARTx, "\n欢迎来到学习STM32的世界\n");
	printf("串口测试输出\n");
	printf("temp=%d\n",temp);
	while(1)
	{
		//ch = scanf("%d",&temp);
		//printf("ch=%c\n",ch);
		//printf("ch=%d\n",ch);
 
		switch(temp)
		{
			case 0x1:LED_R(ON);
				break;
			case 0x2:LED_B(ON);
			break;
			case 0x3:LED_G(ON);
			break;
			default:LED_ALLOFF;
			break;
		
		}
	
	}	
	
	
}