STM32 串口

目录

通信协议

通信的目的是将一个设备的数据传送到另一个设备,扩展硬件系统

通信协议是制定通信的规则,通信双方按照协议规则进行数据收发

常见的通信协议有以下几种

半双工是只能一端发送,另一端接收,而全双工是两端可以同时发送和接收,发送接收使用的两根线,互不影响。除此之外还有单工,就是只能单向发送,另一端不能发送。

同步通信是发送方发出数据后,等接收方发回响应以后才发下一个数据包的通讯方式。

异步通信是发送方发出数据后,不等接收方发回响应,接着发送下个数据包的通讯方式。

所以同步是阻塞模式,异步是非阻塞模式。

所以SPI和I2C都是同步通信,因为他们都有时钟线,UART是异步通信方式,他只有两个数据线,发送完数据不会确认你是否接收到。

串口通信概念

串口是一种应用十分广泛的通讯接口,串口成本低、容易使用、通信线路简单,可实现两个设备的互相通信。

单片机的串口可以使单片机与单片机、单片机与电脑、单片机与各式各样的模块互相通信,极大地扩展了单片机的应用范围,增强了单片机系统的硬件实力。

简单双向串口通信有两根通信线(发送端TX和接收端RX)

TX与RX要交叉连接

当只需单向的数据传输时,可以只接一根通信线

当电平标准不一致时,需要加电平转换芯片

因为TX和RX是单端信号,他们的电平是相对GND的,所以这三根线是必须加的。如果通信双方有单独的供电,VCC就不需要单独接在一起,如果一方没有单独供电就需要接在一起。

什么是单端信号:

单端信号是相对于差分信号而言的,单端输入指信号有一个参考端和一个信号端构成,参考端一般为地端。单端信号只使用一根信号线进行传输。由于信号每一点的电压都是相对于地而言的。而在长距离传输时,不同位置地平面的电位可能会有差别,这就导致了信号的误差,即不稳定因素。加之可能会有干扰信号,这就导致了单端信号的抗干扰能力很差。

串口常用的电平标准

电平标准是数据1和数据0的表达方式,是传输线缆中人为规定的电压与数据的对应关系,串口常用的电平标准有如下三种:

TTL电平:+3.3V或+5V表示1,0V表示0

RS232电平:-3 ~ -15V表示1,+3 ~ +15V表示0

RS485电平:两线压差+2 ~ +6V表示1,-2 ~ -6V表示0(差分信号)

类似串口这种低压设备使用的都是TTL电平,如果需要其他的电平就需要加电平转换芯片,但是在软件层面都属于串口,所以在程序上没有什么变化。

串口及时序

波特率:串口通信的速率

起始位:标志一个数据帧的开始,固定为低电平

数据位:数据帧的有效载荷,1为高电平,0为低电平,低位先行

校验位:用于数据验证,根据数据位计算得来

停止位:用于数据帧间隔,固定为高电平

上述规则是串口协议规定的,每个字节都在一个数据帧中。每个帧都有起始位,数据位和停止位。上述图片右边,在数据位中后面还可以加一个奇偶校验位。

串口波特率是串口发送数据的速率,如果接收慢了,就会漏掉某些位,如果接收快了,就会重复接收某些位。所以发送接收双方要约定好速率。

起始位:串口的空闲状态必须是高电平,在传输的时候,必须先发送一个起始位,必须是低电平,打破空闲状态,产生一个下降沿,这个下降沿就是告诉接收设备,这一帧的数据要开始了。

停止位:用于发送完数据后的数据帧间隔,固定为高电平,同时也可以为下一个起始位做准备的。使下一帧数据可以产生下降沿。

校验位:串口使用的是奇偶校验的方法,如果数据出错了可以选择丢弃或者要求重传。可以选择无校验,奇校验和偶校验。无校验就是不进行校验,奇校验就是通过在校验位置1或者0保证数据位的1为奇数个,然后接收方通过验证是否满足奇数个 1 来确定是否传输出错。偶校验同理。但是这种校验方式只能一定程度上避免传输出错。

在STM32中根据数据比特位反转高低电平和接收端都是由USART外设自动完成的,用户不需要管。当然也可以使用定时器模拟这也波形,按照数据帧的要求,给GPIO口置高或者低。模拟接收就是读取RX端的电平,需要注意在接收的时候需要一个外部中断,在起始位的下降沿触发,进入接收状态,并且对齐采样时钟,依次采样八次。

STM32中USART

USART(Universal Synchronous/Asynchronous Receiver/Transmitter)通用同步/异步收发器。S就是同步的意思。UART,少一个S的就是异步收发器。但是一般不使用同步功能,所以USART和UART使用起来没什么区别。STM32中USART只是多了个时钟输出而已,只支持时钟输出,不支持时钟输入。所以同步模式更多的是为了兼容别的协议,或者有特殊用途而设计的,并不支持两个USART之间的通信。

USART是STM32内部集成的硬件外设,可根据数据寄存器的一个字节数据自动生成数据帧时序,从TX引脚发送出去,也可自动接收RX引脚的数据帧时序,拼接为一个字节数据,存放在数据寄存器里。

自带波特率发生器,波特率发生器就是用来配置波特率的,其实就是一个分频器,比如APB2总线给72MHz,然后波特率发生器进行分频,得到我们想要的时钟。最高达4.5Mbits/s。

可配置数据位长度(8/9)、停止位长度(0.5/1/1.5/2)。

可选校验位(无校验/奇校验/偶校验)。

支持同步模式、硬件流控制、DMA、智能卡、IrDA、LIN。同步模式就是多了个时钟CLK输出。

硬件流控制:如果A设备向B设备发数据,发送太快了,此时B没准备好接收可能就造成了数据丢失。如果有硬件流控制,在硬件电路上会多出一根线。如果B没有准备好就置高电平,如果准备好了就置低电平。A接收到了B反馈的准备信号,就只会在B准备好的时候才发数据。如果B没有准备好,数据就不会发送出去。STM32是有硬件流控制的,但是一般不用。

STM32F103C8T6 USART资源: USART1、 USART2、 USART3

USART框图

左上角TX和RX就是串口的收发引脚,下面一点SW_RX,IRDA_OUT,IRDA_IN是智能卡和IrDA通信的引脚。

上面灰色框中,发送数据寄存器(TDR)和接收数据寄存器(RDR)共用一个地址,这里和51单片机的SBUF是一样的。在程序上只表现为一个寄存器即数据寄存器DR。但实际硬件中分成了两个寄存器,一个用于发送TDR,另一个用于接收RDR。

然后往下看,下面是两个移位寄存器,一个用于发送,一个用于接收发送。发送移位寄存器 的作用就是把一个字节的数据一位一位的移除去,正好对应串口协议的波形的数据位。这两个寄存器是怎么工作的呢?举个例子,比如你在某时刻给TDR写入了0X55这个数据,在寄存器里就是二进制存储01010101。那么此时硬件检测到你写入数据了,他就会检查当前数据寄存器是不是有数据正在移位,如果没有,这个01010101就会立刻全部移动到发送移位寄存器准备发送。当数据从TDR移动到移位寄存器时,会置一个标志位,叫TXE(TX Empty),发送给寄存器空,我们检查这个标志位,如果置 1 了,我们就可以在TDR写入下一个数据了。注意,当TXE标志位置 1 时,数据其实还没有发送数据,只要数据从TDR转移到发送移位寄存器了,TXE就会置 1 ,我们就可以写入新的数据了,然后发送移位寄存器就会在下面的发送器控制 的驱动下向右移位,然后一位一位的把数据输出到TX引脚。这里向右移位,刚好和串口协议规定的低位先行是一致的。TDR会在移位寄存器移动完成后再次转移TDR的数据。
接收移位寄存器接收器控制 的控制下开始接收数据,是从高位向低位这个方向移动的。当接收完成后会转移到RDR中,此时也会置一个标志位叫RXNE(RX Not Empty)接收数据寄存器非空。检测到RXNE置 1 后就可以把数据读走了。

可以发现在左边就有上面介绍的硬件数据流控。nRTS(Request To Send)是请求发送,是输出脚,即告诉对方当前能不能收。nCTS(Clear To Send)是清除发送,是输入脚。也就是用于接收别人nRTS的信号的。前面加n意思是低电平有效。用法就是将自己的RTS接到对方的CTS,当能接收的时候,RTS就置0,对方CTS接收到后就可以一直发。当处理不过来时,就置 1 ,对方就会停止发送。

右边还要SCLK,产生同步时钟信号,是配合发送移位寄存器输出的。发送寄存器每移位一次,同步时钟电平就跳变一个周期。,时钟告诉对方,我移出去一位数据了。

中间的唤醒单元是实现串口挂在多设备,串口一般是点对点的通信,点对点只支持两个设备互相通信,是两个设备之间通信。唤醒单元可以用来实现多设备的功能,在这里可以给串口分配一个地址(在上面的USART地址处),当发送指定地址时,此设备唤醒开始工作。当发送别的设备地址时,别的设备就唤醒工作。这也就实现了多设备的串口通信。

下面中断输出控制,中断申请位就是状态寄存器里的各种标志位。状态寄存器这里有两个标志位比较重要。就是上面说的TXE和RXNE。

最下面的就是波特率发生器,波特率发生器其实就是分频器。 APB时钟进行分频,得到发送和接收移位的时钟。时钟输入是fPCLKx,x等于 1 或2,USART1挂载在APB2,所以就是PCLK2的时钟一般是72MHz。其他的USART都挂载在APB1,所以是PCLK1的时钟一般是36MHz。之后,这个时钟进行一个分频除以一个USART Div的分频系数。 USART Div里面就是右边的一个数值,并且分为了整数部分和小数部分,因为有些波特率用72兆除一个整数的可能除不尽会有误差,所以这里分频系数是支持小数点后四位的分频就更加精准了。之后分频完之后还要再除16,得到发送器时钟和接收器时钟通向控制部分。右边如果TE为 1 就是发送使能了,发送部分的波特率就有效。如果RE为 1 就是接收器使能了,接收部分的波特率就有效。

USART基本结构

虽然结构上看着有四个寄存器,但是实际上只有一个DR寄存器可以使用。写DR进行发送,读DR进行接收。

波特率发生器

在STM32串口开始采样的时候将一个时序分为16份对起始位进行检测,避免噪声。

在起始位已经对齐了采样时钟,在采样的时候,会在第8,9,10位进行采样。这样采样是将噪声考虑进去了,如果不全为1或者0,说明产生了噪声,就按照2:1的方式来。此时就会将噪声标志位NE置1。

发送器和接收器的波特率由波特率寄存器BRR里的DIV确定

计算公式:波特率 =

多个16就是上面说的有个16倍的采样时钟。

STM32相关库函数

大部分函数都是见过的,一眼就知道怎么使用

c 复制代码
void USART_DeInit(USART_TypeDef* USARTx);
void USART_Init(USART_TypeDef* USARTx, USART_InitTypeDef* USART_InitStruct);
void USART_StructInit(USART_InitTypeDef* USART_InitStruct);
void USART_ClockInit(USART_TypeDef* USARTx, USART_ClockInitTypeDef* USART_ClockInitStruct);
void USART_ClockStructInit(USART_ClockInitTypeDef* USART_ClockInitStruct);
void USART_SendData(USART_TypeDef* USARTx, uint16_t Data);
uint16_t USART_ReceiveData(USART_TypeDef* USARTx);

USART_ClockInitUSART_ClockStructInit是用来配置同步时钟输出的。
USART_SendData发送数据,USART_ReceiveData接收数据。
USART_ReceiveData,读取发送过来的数据。

配置

c 复制代码
void Serial_Init(void)
{
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1, ENABLE);
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
	
	GPIO_InitTypeDef GPIO_InitStructure;
 	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;//STM32串口1的发送端,使用复用推挽输出
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9;
 	GPIO_Init(GPIOA, &GPIO_InitStructure);
	
	USART_InitTypeDef USART_InitStructure;
	USART_InitStructure.USART_BaudRate = 9600;//波特率
	USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;//是否使用硬件流控制
	USART_InitStructure.USART_Mode = USART_Mode_Tx;//接收还是发送,如果二者都需要就或起来
	USART_InitStructure.USART_Parity = USART_Parity_No;//校验位
	USART_InitStructure.USART_StopBits = USART_StopBits_1;//停止位
	USART_InitStructure.USART_WordLength = USART_WordLength_8b;//数据帧长度
	USART_Init(USART1, &USART_InitStructure);
	
	USART_Cmd(USART1, ENABLE);
}

void Serial_SentByte(uint8_t Byte)
{
	USART_SendData(USART1, Byte);
	while(USART_GetFlagStatus(USART1, USART_FLAG_TXE) == RESET);
}

使用printf

使用printf前需要打开工程选项,勾选MicroLIB

MicroLIB是Keil为嵌入式平台优化的一个精简库。

还需要对printf进行重定向,将printf函数打印的东西输出到串口。因为printf函数默认打印到屏幕,但是单片机没有屏幕。

需要包含stdio.h,然后重定向fputc函数。

c 复制代码
int fputc(int ch, FILE* f)
{
	Serial_SentByte(ch);
	return ch;
}

重定向fputc跟printf有什么关系呢?

fputc是printf函数的底层,printf函数打印的时候就是不断的调用fputc函数一个个打印的。所以重定向一下fputc函数就会打印到串口了。

c 复制代码
#include "Config.h"
#include <stdio.h>
void Serial_Init(void)
{
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1, ENABLE);
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
	
	GPIO_InitTypeDef GPIO_InitStructure;
 	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;//STM32串口1的发送端,使用复用推挽输出
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9;
 	GPIO_Init(GPIOA, &GPIO_InitStructure);
	
	USART_InitTypeDef USART_InitStructure;
	USART_InitStructure.USART_BaudRate = 9600;//波特率
	USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;//是否使用硬件流控制
	USART_InitStructure.USART_Mode = USART_Mode_Tx;//接收还是发送,如果二者都需要就或起来
	USART_InitStructure.USART_Parity = USART_Parity_No;//校验位
	USART_InitStructure.USART_StopBits = USART_StopBits_1;//停止位
	USART_InitStructure.USART_WordLength = USART_WordLength_8b;//数据帧长度
	USART_Init(USART1, &USART_InitStructure);
	
	USART_Cmd(USART1, ENABLE);
}

void Serial_SentByte(uint8_t Byte)
{
	USART_SendData(USART1, Byte);
	while(USART_GetFlagStatus(USART1, USART_FLAG_TXE) == RESET);
}

int fputc(int ch, FILE* f)
{
	Serial_SentByte(ch);
	return ch;
}
c 复制代码
int main(void)
{
	OLED_Init();
	Serial_Init();
	//Serial_SentByte(0x41);
	printf("Num = %d\r\n", 666);
	char string[100];
	sprintf(string, "Num = %d\r\n", 666);
	//然后可以使用其他串口在打印
	while(1)
	{
		
	}
}

重定向后,相当于将printf里面的字符串一个一个提取出来打印。因为我们使用的是Serial_SentByte重定向的,所以打印时会转为ASCII码,但是以字符接收就又会转换回字符。

接收

接收需要在初始化一下另一个引脚,并且可以选择查询或者中断接收。

如果选择中断就需要再配置一下中断,如果选择查询方式就一直查询RXNE标志位。

串口初始化代码为

c 复制代码
void Serial_Init(void)
{
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1, ENABLE);
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
	
	GPIO_InitTypeDef GPIO_InitStructure;
 	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;//STM32串口1的发送端,使用复用推挽输出
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9;
 	GPIO_Init(GPIOA, &GPIO_InitStructure);
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;//STM32串口1的接收端,使用上拉输入
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10;
 	GPIO_Init(GPIOA, &GPIO_InitStructure);
	
	USART_InitTypeDef USART_InitStructure;
	USART_InitStructure.USART_BaudRate = 9600;//波特率
	USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;//是否使用硬件流控制
	USART_InitStructure.USART_Mode = USART_Mode_Tx | USART_Mode_Rx;//接收还是发送,如果二者都需要就或起来
	USART_InitStructure.USART_Parity = USART_Parity_No;//校验位
	USART_InitStructure.USART_StopBits = USART_StopBits_1;//停止位
	USART_InitStructure.USART_WordLength = USART_WordLength_8b;//数据帧长度
	USART_Init(USART1, &USART_InitStructure);
	
	USART_Cmd(USART1, ENABLE);
}

main.c:

c 复制代码
int main(void)
{
	OLED_Init();
	Serial_Init();
	while(1)
	{
		if(USART_GetFlagStatus(USART1, USART_FLAG_RXNE) == SET)
		{
			uint8_t data = USART_ReceiveData(USART1);
			OLED_ShowHexNum(1, 1, data, 2);
		}
	}
}

中断方式:

需要加入代码来开启中断:

c 复制代码
USART_ITConfig(USART1, USART_IT_RXNE, ENABLE);

然后配置NVIC

c 复制代码
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);

NVIC_InitTypeDef NVIC_InitStructure;
NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;
NVIC_Init(&NVIC_InitStructure);

手册上写道,当读取USART_DR(即数据寄存器,即执行了USART_ReceiveData)后,RXNE会自动置0,所以我们读取数据后就不需要再进行清零了。

还需要写中断函数:

整体配置代码就是:

c 复制代码
#include "Config.h"
uint8_t data;
uint8_t rx_flag;
void Serial_Init(void)
{
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1, ENABLE);
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
	
	GPIO_InitTypeDef GPIO_InitStructure;
 	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;//STM32串口1的发送端,使用复用推挽输出
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9;
 	GPIO_Init(GPIOA, &GPIO_InitStructure);
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;//STM32串口1的接收端,使用上拉输入
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10;
 	GPIO_Init(GPIOA, &GPIO_InitStructure);
	
	USART_InitTypeDef USART_InitStructure;
	USART_InitStructure.USART_BaudRate = 9600;//波特率
	USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;//是否使用硬件流控制
	USART_InitStructure.USART_Mode = USART_Mode_Tx | USART_Mode_Rx;//接收还是发送,如果二者都需要就或起来
	USART_InitStructure.USART_Parity = USART_Parity_No;//校验位
	USART_InitStructure.USART_StopBits = USART_StopBits_1;//停止位
	USART_InitStructure.USART_WordLength = USART_WordLength_8b;//数据帧长度
	USART_Init(USART1, &USART_InitStructure);
	
	USART_ITConfig(USART1, USART_IT_RXNE, ENABLE);
	
	NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
	
	NVIC_InitTypeDef NVIC_InitStructure;
	NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn;
	NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
	NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;
	NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;
	NVIC_Init(&NVIC_InitStructure);
	
	USART_Cmd(USART1, ENABLE);
}

void Serial_SentByte(uint8_t Byte)
{
	USART_SendData(USART1, Byte);
	while(USART_GetFlagStatus(USART1, USART_FLAG_TXE) == RESET);
}

int fputc(int ch, FILE* f)
{
	Serial_SentByte(ch);
	return ch;
}

uint8_t Get_RX_Flag()
{
	if(rx_flag == 1)
	{
		rx_flag = 0;
		return 1;
	}
	else
	{
		return 0;
	}
}

uint8_t Get_USART1_Data()
{
	return data;
}

void USART1_IRQHandler(void)
{
	if(USART_GetITStatus(USART1, USART_IT_RXNE) == SET)
	{
		data = USART_ReceiveData(USART1);
		rx_flag = 1;
	}
}
相关推荐
智商偏低4 小时前
单片机之helloworld
单片机·嵌入式硬件
青牛科技-Allen5 小时前
GC3910S:一款高性能双通道直流电机驱动芯片
stm32·单片机·嵌入式硬件·机器人·医疗器械·水泵、
森焱森7 小时前
无人机三轴稳定控制(2)____根据目标俯仰角,实现俯仰稳定化控制,计算出升降舵输出
c语言·单片机·算法·架构·无人机
白鱼不小白7 小时前
stm32 USART串口协议与外设(程序)——江协教程踩坑经验分享
stm32·单片机·嵌入式硬件
S,D8 小时前
MCU引脚的漏电流、灌电流、拉电流区别是什么
驱动开发·stm32·单片机·嵌入式硬件·mcu·物联网·硬件工程
芯岭技术11 小时前
PY32F002A单片机 低成本控制器解决方案,提供多种封装
单片机·嵌入式硬件
youmdt11 小时前
Arduino IDE ESP8266连接0.96寸SSD1306 IIC单色屏显示北京时间
单片机·嵌入式硬件
嘿·嘘12 小时前
第七章 STM32内部FLASH读写
stm32·单片机·嵌入式硬件
Meraki.Zhang12 小时前
【STM32实践篇】:I2C驱动编写
stm32·单片机·iic·驱动·i2c
几个几个n14 小时前
STM32-第二节-GPIO输入(按键,传感器)
单片机·嵌入式硬件