USART引脚组成

USARTx:
USARTx_Tx 发送
USARTx_Rx 接受
USARTx_CTS Clear To Send
USARTx_RTS Request To Send
USARTx_CK 同步模式的时钟线
硬件流控(CTS,RTS):就是控制串口数据的收发,通过请求发送允许发送的拉高拉低来防止数据的丢失,保证数据收发的完整性
优势:相比于软件流控,硬件流控不占据传输数据位、响应更快高速串口大数据场景下防丢包效果更稳定。
在这里插入图片描述

USART默认引脚与重映射引脚分布

在这里插入图片描述
USART1的默认Tx,Rx引脚位于PA9,PA10
重映射引脚(备用引脚)位于PB6,PB7

串口引脚的IO配置

在这里插入图片描述

串口工作模式

全双工

数据传输双向,两根线互不影响
在这里插入图片描述

半双工

数据传输虽然是双向的,既能从左向右发送数据,也能从右向左发数据,但是不能同时进行
在这里插入图片描述

同步模式

在标准的串口基础上增加一根CK时钟线,负责在两个设备之间传输时钟信号,时钟信号可以把两个设备同步起来。
在这里插入图片描述

硬件流控

在标准串口基础上增加CTS,RTS这两个引脚,也是交叉连接。
在这里插入图片描述
在这里插入图片描述

Rx引脚可以选择输入浮空模式输入上拉模式,但是更推荐使用输入上拉模式,因为Rx引脚负责接收数据的,如果这根线意外断开了,IO悬空,上拉电阻会将它拉到一个高电压(提供一个高电压1),防止IO悬空导致外界的电磁波信号造成干扰
为什么不选输入下拉
根据串口通信格式可知:
串口通信格式
在空闲状态下,发送一个低电压表示起始位,然后跟着的是8~9位是数据位,数据位之后是停止位,停止位后面是空闲位,我们用高电压表示空闲,Rx断开时,上拉电阻提供一个高电压,表示串口处于空闲状态,这样对程序断线造成的影响最小

USART内部标志位

标志位反映了USART模块内部工作状态信息
TXE:发送数据寄存器空。
TDR为空时,TXE=1,否则TXE=0。
TC:发送完成标志。
当TDR为空且移位寄存器为空时,TC=1,否则TC=0。

错误标志位:PE、FE、NE、ORE

PE:Parity Error 奇偶校验错
FE:Frame Error 帧格式错误 接收到的时=是无效数据帧,FE=1,否则FE=0
NE:Noise Error 噪声错 接收的数据中检测到了噪声,NE=1,否则NE=0
ORE:Overrun Error 过载错 由于过载造成了数据丢失,发数据的速度大于收数据的速度,导致部分数据被覆盖,此时ORE由0变为1

串口初始化

1.先初始化串口引脚的IO配置

/*初始化USART1的Tx,Rx引脚*/
//默认 PA9-Tx   复用推挽模式
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);//开GPIOA时钟
GPIO_InitTypeDef GPIO_InitStruct={0};
GPIO_InitStruct.GPIO_Pin=GPIO_Pin_9;
GPIO_InitStruct.GPIO_Mode=GPIO_Mode_AF_PP;
GPIO_InitStruct.GPIO_Speed=GPIO_Speed_10MHz;
GPIO_Init(GPIOA,&GPIO_InitStruct);
//	默认 PA10-Rx   输入上拉模式或者输入浮空模式
GPIO_InitStruct.GPIO_Pin=GPIO_Pin_10;
GPIO_InitStruct.GPIO_Mode=GPIO_Mode_IPU;
GPIO_Init(GPIOA,&GPIO_InitStruct);

使用重映射引脚时:

/*USART1重映射*/
RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO,ENABLE);//我要使能IO的复用功能
GPIO_PinRemapConfig(GPIO_Remap_USART1,ENABLE);//将USART1的IO引脚重映射
//PB6-Tx
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE);
GPIO_InitTypeDef GPIO_InitStruct={0};
GPIO_InitStruct.GPIO_Pin=GPIO_Pin_6;
GPIO_InitStruct.GPIO_Mode=GPIO_Mode_AF_PP;
GPIO_InitStruct.GPIO_Speed=GPIO_Speed_10MHz;
GPIO_Init(GPIOB,&GPIO_InitStruct);
//PB7 Rx
GPIO_InitStruct.GPIO_Pin=GPIO_Pin_7;
GPIO_InitStruct.GPIO_Mode=GPIO_Mode_IPU;
GPIO_Init(GPIOB,&GPIO_InitStruct);

2.初始化USART模块,此处我们使用USART1

RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1,ENABLE);
USART_InitTypeDef USART_InitStruct={0};
USART_InitStruct.USART_BaudRate=115200;
USART_InitStruct.USART_Mode=USART_Mode_Tx|USART_Mode_Rx;
USART_InitStruct.USART_WordLength=USART_WordLength_8b;;
USART_InitStruct.USART_Parity= USART_Parity_No;
USART_InitStruct.USART_StopBits=USART_StopBits_1;
USART_Init(USART1,&USART_InitStruct);

3.使能USART1外设(一定要记得!)

USART_Cmd(USART1,ENABLE);//闭合总开关,使能USART

在这里插入图片描述
串口初始化完成后,调用USART_Cmd编程接口去闭合总开关,使能USART模块,这样才算串口初始化真正完成!
查询USART标志位的值

串口发送数据

USART_GetFlagStatus(串口名称,要查询的标志位)
返回值:SET–>1 RESET–>0

if(USART_GetFlagStatus(USART1,USART_FLAG_TXE)==SET)//判断发送数据寄存器是否为空
{
	//需要执行的操作
}
if(USART_GetFlagStatus(USART1,USART_FLAG_TC)==SET)//判断数据是否发送完成
{
	//需要执行的操作
}

把要发送的数据写入到发送数据寄存器当中:
USART_SendData(串口名称,uint16_t要发送的数据)
为什么这里使用2个字节
根据串口通信格式,若我们采用无校验位,那么数据位有9位,要采用2字节存储

//发送0x01
USART_SendData(USART1,0x01);

使用串口一次性发送多个字节:

	/*作用:使用串口一次性发送多个字节*/
	/*参数:pData-要发送的数据,Size-要发送的字节数*/
	/*pData[i] 每次取出的是一个 uint8_t,也就是一个字节。因为串口一次发一个字节*/
void My_USART_SendBytes(USART_TypeDef *USARTx,uint8_t*pData,uint16_t Size)
{
	for(uint32_t i=0;i<Size;i++)
		{
			//1.等待发送数据寄存器空
			while(USART_GetFlagStatus(USART1,USART_FLAG_TXE)==RESET);
			//2.将要发送的数据写入发送数据寄存器
			USART_SendData(USART1,pData[i]);		
		}
			//3.等待数据发送完成
while(USART_GetFlagStatus(USART1,USART_FLAG_TC)==RESET);
}
uint8_t bytestosend[]={1,2,3,4,5,6};
My_USART_SendBytes(USART1,bytestosend,6);

串口接收数据

RxNE:接收数据寄存器非空
当RDR非空时,RxNE=1,否则RxNE=0
从接收数据寄存器读取数据:
USART_ReceiveData(串口名称)
返回一个无符号16位整型,可以装下9位数据位的情况
读数据前要判断一下接收数据寄存器里有没有数据,有数据才能读出来
Step:
1.等待接收数据寄存器非空
2.接收数据
3.处理数据

#include "stm32f10x.h"
#include "delay.h"
#include "stdio.h"
void My_USART_SendBytes(USART_TypeDef*USARTx,uint8_t*pData,uint16_t Size);
void My_USART_Init(void);
void OnBoardLED_Init(void);
int main(void)
{
	My_USART_Init();
	OnBoardLED_Init();
	uint16_t Data_Recv;
while(1)
{
	//1.等待接收数据寄存器非空
	while(USART_GetFlagStatus(USART1,USART_FLAG_RXNE)==RESET);
	//2.接收数据
	Data_Recv=USART_ReceiveData(USART1);
	//3.处理数据
	if(Data_Recv=='0')
	{
		GPIO_WriteBit(GPIOC,GPIO_Pin_13,Bit_RESET);
	}
	else if(Data_Recv=='1')
	{
		GPIO_WriteBit(GPIOC,GPIO_Pin_13,Bit_SET);
	}
}
}
//@简介:对USART1进行初始化
//				PB6-Tx,PB7-Rx
//				115200,8,1,None,收发双向
void My_USART_Init(void)
{		
		/*USART1重映射*/
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO,ENABLE);
	GPIO_PinRemapConfig(GPIO_Remap_USART1,ENABLE);
	//PB6 Tx
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE);
	GPIO_InitTypeDef GPIO_InitStruct={0};
	GPIO_InitStruct.GPIO_Pin=GPIO_Pin_6;
	GPIO_InitStruct.GPIO_Mode=GPIO_Mode_AF_PP;
	GPIO_InitStruct.GPIO_Speed=GPIO_Speed_10MHz;
	GPIO_Init(GPIOB,&GPIO_InitStruct);
	//PB7 Rx
	GPIO_InitStruct.GPIO_Pin=GPIO_Pin_7;
	GPIO_InitStruct.GPIO_Mode=GPIO_Mode_IPU;
	GPIO_Init(GPIOB,&GPIO_InitStruct);
	/*初始化USART模块*/
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1,ENABLE);
	USART_InitTypeDef USART_InitStruct={0};
	USART_InitStruct.USART_BaudRate=115200;
  USART_InitStruct.USART_Mode=USART_Mode_Tx|USART_Mode_Rx;
  USART_InitStruct.USART_WordLength=USART_WordLength_8b;;
  USART_InitStruct.USART_Parity= USART_Parity_No;
  USART_InitStruct.USART_StopBits=USART_StopBits_1;
  USART_Init(USART1,&USART_InitStruct);
  USART_Cmd(USART1,ENABLE);//闭合总开关,使能USART
}
void OnBoardLED_Init(void)
{
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC,ENABLE);
	GPIO_InitTypeDef GPIO_InitStruct={0};
	GPIO_InitStruct.GPIO_Pin=GPIO_Pin_13;
	GPIO_InitStruct.GPIO_Mode=GPIO_Mode_Out_OD;
	GPIO_InitStruct.GPIO_Speed=GPIO_Speed_2MHz;
	GPIO_Init(GPIOC,&GPIO_InitStruct);
	GPIO_WriteBit(GPIOC,GPIO_Pin_13,Bit_SET);
}

串口格式化打印

串口打印时间

while(1)
{
	uint32_t currentTick=GetTick();
	uint32_t ms=currentTick%1000;
	currentTick=currentTick/1000;
	uint32_t s=currentTick%60;
	currentTick=currentTick/60;
	uint32_t min=currentTick%60;
	currentTick=currentTick/60;
	uint32_t hour=currentTick;
	printf("%02u:%02u:%2u.%03u\r\n",hour,min,s,ms);
	Delay(100);
}

串口打印字符串

int fputc(int ch,FILE*f)
{
	//1.等待TDR为空
	while(USART_GetFlagStatus(USART1,USART_FLAG_TXE)==RESET);
	//2.发送
	USART_SendData(USART1,(uint8_t)ch);
	return ch;
}
printf("Hello,world\r\n");

串口测试

发送一个字母

My_USART_SendByte(USART1,0x5a);

串口调试助手上显示:
在这里插入图片描述

发送一串数字

uint8_t bytestosend[]={1,2,3,4,5,6};
My_USART_SendBytes(USART1,bytestosend,6);

串口调试助手显示:
在这里插入图片描述

发送一个字符

My_USART_SendChar(USART1,'L');

串口调试助手显示:
在这里插入图片描述

发送一个字符串

My_USART_SendString(USART1,"L3zuibangla!");

串口调试助手显示:
在这里插入图片描述

打印字符名

const char *strName="miumiu";
My_USART_Printf(USART1,"Hi,%s!",strName);

串口调试助手显示:
在这里插入图片描述

串口发送字符控制LED

发送字符‘0’,LED熄灭,发送字符‘1’,LED亮起。

RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC,ENABLE);
GPIO_InitTypeDef GPIO_InitStruct={0};
GPIO_InitStruct.GPIO_Pin=GPIO_Pin_13;
GPIO_InitStruct.GPIO_Mode=GPIO_Mode_Out_OD;
GPIO_InitStruct.GPIO_Speed=GPIO_Speed_2MHz;
GPIO_Init(GPIOC,&GPIO_InitStruct);
char ch;	
while(1)
{
	ch=	My_USART_ReceiveByte(USART1);
	if(ch=='0')
  {
    GPIO_WriteBit(GPIOC,GPIO_Pin_13,Bit_SET);//熄灭
  }
  else if(ch=='1')
 {
   GPIO_WriteBit(GPIOC,GPIO_Pin_13,Bit_RESET);//亮起
 }

现象:
发送0:
在这里插入图片描述板载LED熄灭
在这里插入图片描述
发送1:
在这里插入图片描述
板载LED亮起
在这里插入图片描述

串口返回接收到的字符串

while(1)//放到循环里,可以一直被执行,不然只能执行一次
{
char buffer[100];
if(My_USART_ReceiveLine(USART1,buffer,100,LINE_SEPERATOR_CRLF,-1)==0)// 阻塞/非阻塞读取一行串口数据(以回车换行CRLF结尾)
{
	My_USART_SendString(USART1,buffer);// 收到完整一行,立刻把收到的字符串原路发回
}		
}

串口调试助手显示:
在这里插入图片描述
发送123\r\n,串口接收到并返回123。

Logo

智能硬件社区聚焦AI智能硬件技术生态,汇聚嵌入式AI、物联网硬件开发者,打造交流分享平台,同步全国赛事资讯、开展 OPC 核心人才招募,助力技术落地与开发者成长。

更多推荐