【STM32学习笔记】串口通信协议

本笔记基于 B 站优质 STM32 教学自媒体 UP 主:
江协科技:【STM32入门教程-2023版 细致讲解 中文字幕】https://www.bilibili.com/video/BV1th411z7sn?vd_source=05dc4a341cab233beaacc8227fe5a7fe
铁头山羊:【铁头山羊stm32 入门教程【新版】】https://www.bilibili.com/video/BV11X4y1j7si?vd_source=05dc4a341cab233beaacc8227fe5a7fe

本文仅作为个人学习记录分享与交流,不涉及商业行为
实验代码见 Gitee 地址:https://gitee.com/LightHall/stm32-code

1、串口的基本概念

1.1 串口

串口是一种应用广泛的通讯接口,成本低、易使用、通信线路简单,可以实现两个设备的互相通信(单片机之间、单片机与电脑之间、单片机与各式各样的模块之间)

1.2 串口通信

串口通信(串行通信):串口通信是一种数据传输方式,其中数据以位的形式按顺序逐个传输。

与并行通信相比,串口通信每次只传输一个位,因此需要较少的传输线

1)硬件电路

在这里插入图片描述

2)通信协议

在这里插入图片描述

串口传输的数据以位的形式按顺序逐个传输,按照类型可分为:空闲位、起始位、数据位、校验位、停止位

空闲位: UART 协议规定,当总线处于空闲状态时信号线的状态为 1 即高电平
起始位: 开始进行数据传输时发送方要先发出一个低电平 0 来表示传输字符的开始
数据位: 起始位之后就是要传输的数据,并且低位先传
奇偶校验位: 数据位传送完成后,要进行奇偶校验
停止位: 数据结束标志,可以是 1 位、1.5 位、2 位的高电平

低位先传

3) 简单示例

基本格式
在这里插入图片描述

① 例子1:

通过串口发送十进制数字 27,27对应的二进制为 0001 1011,低位先传,所以在数据位中依次传送 1 1 0 1 1 0 0 0 ,如下图所示:

在这里插入图片描述

② 例子2:

发送字符串 Hello
H e l o 字符的 ASCII 码分别为 0x48、0x65、0x6c、0x6f,分别对应二进制0100 1000、0110 0101、0110 1100、0110 1111,低位先传,所以如下图所示:

在这里插入图片描述

1.3 同步与异步通信

串行通信可进一步分类为:同步通信 & 异步通信

同步传输: 依靠时钟信号来同步数据信号的收发,不需要起始信号和停止信号,收发双方遵循同步的时钟即可
异步传输: 不需要同样的时钟信号(使用各自的时钟信号),依靠起始信号和停止信号同步数据信号的收发,而且需要双方约定好传输速率(如波特率)

1.4 两种常见串口通信协议

通用异步串行接收/发送器 UART(Universal Asynchronous Receiver/Transmitter)

通用同步/异步串行接收/发送器 USART(Universal Synchronous/Asynchronous Receiver/Transmitter)

1.5 串口通信缺点

只能一对一通信

2、串口模块的使用方法

2.1 基本结构与串并转换示意

在这里插入图片描述

2.2 波特率

72MHz的STM32生成115200的波特率示意如下图
在这里插入图片描述

不用算,标准库提供的函数可以自动算,只要填写需要的波特率即可

2.3 相关编程接口

① USARTx 的时钟
// 以USART1为例
RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1,ENABLE);
② USARTx 的参数配置 & USARTx 的初始化

初始化函数以及USARTx的各配置参数如下图:

在这里插入图片描述

假设现在要配置 USART1、波特率为115200、全双工通信、8位数据位、无需校验位、1位停止位

代码示例如下:

// USART结构体及配置
USART_InitTypeDef USART_InitStruct;

USART_InitStruct.USART_BaudRate = 115200;						// 115200波特率
USART_InitStruct.USART_Mode = USART_Mode_Tx | USART_Mode_Rx;	// 双向通信
USART_InitStruct.USART_WordLength = USART_WordLength_8b;		// 8位数据位
USART_InitStruct.USART_Parity = USART_Parity_No;				// 无需校验位
USART_InitStruct.USART_StopBits = USART_StopBits_1;				// 1位停止位

// 初始化USART1
USART_Init(USART1,&USART_InitStruct);
③ Tx 与 Rx 引脚的参数配置 & Tx 与 Rx 引脚的初始化

在这里插入图片描述

串口是通过 STM32 的 I/O 引脚去与外界通信的,所以需要配置 I/O 引脚。

但 I/O 引脚不是任意可选的

如下图,STM32 规定了在未重映射时,PA9、PA10 的默认复用功能为USART1;PA2、PA3 的默认复用功能为USART2。

在这里插入图片描述

如下图,如果重映射,PB6、PB7 的重映射功能为USART1

在这里插入图片描述

此处假设不进行重映射,所以需要开启 GPIOA 时钟,配置 GPIO 参数并初始化。

在这里插入图片描述

问题在于 GPIO 参数如何取值?如下图:

在这里插入图片描述

④ USART总开关

在这里插入图片描述
示例1:

// 以USART1为例
USART_Cmd(USART1,ENABLE);
⑤ 重映射时钟开启与声明

若重映射,需要额外开启 AFIO 时钟,并声明重映射 USARTx:

// 以USART1为例 
RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO,ENABLE);
GPIO_PinRemapConfig(GPIO_Remap_USART1,ENABLE);
⑥ 向发送数据寄存器存入数据

在这里插入图片描述

示例

// 以USART1为例
USART_SendData(USART1,pData[i]);	// uint8_t *pData
⑦ 查询 USARTx 标志位

在这里插入图片描述

在这里插入图片描述

⑧ 从接收数据寄存器读取数据
unit16_t USART_ReceiveData(USART_TypeDef *USARTx);
⑨ fputc函数重载

‌printf函数会调用fputc函数的功能,主要是因为printf函数的输出需要通过fputc函数来实现字符的输出‌
具体来说,当我们在程序中调用printf函数时,实际上是通过标准库中的stdio.h头文件提供的接口
这个接口内部会使用fputc函数来逐个输出字符,从而实现字符串的打印输出‌

重定向fputc函数‌:由于标准库中的fputc函数默认输出到标准输出(通常是控制台),如果需要将输出重定向到其他设备(如一个串口),就需要重定义fputc函数,改变printf的输出目的地‌。

Keil 中必须如下设置:
Project→Options for Target→Target→勾选Use MicroLIB
否则标准库的printf无法正确重定向到串口

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

在这里插入图片描述

3、实验1:发送数据

前提

不重映射,采用 USART1、波特率为115200、全双工通信、8位数据位、无需校验位、1位停止位

步骤

① 开启 USART1 时钟、配置 USART1 并初始化
//开启USART1的时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1,ENABLE);
//配置USART1并初始化
USART_InitTypeDef USART_InitStruct;
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);
② 开启 GPIO 时钟、配置 GPIO 并初始化(配置 TX 和 RX)

根据查表得制未重映射下的 USART1 的 Tx 与 Rx 分别对应 PA9、PA10

且 Tx 和 Rx 选择全双工模式,分别对应 PA9 的复用推挽输出模式、PA10 的输入上拉模式

//PA9作为Tx
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_10MHz;
GPIO_Init(GPIOA,&GPIO_InitStructure);

//PA10作为Rx
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10;
GPIO_Init(GPIOA,&GPIO_InitStructure);
③ 开启 USART1 总开关
//USART总开关
USART_Cmd(USART1,ENABLE);
④ 串口发送多个8位数据
//串口发送数据原理函数
void My_USART_SendBytes(USART_TypeDef *USARTx,uint8_t *pData,uint16_t Size){
	for(uint32_t i=0;i<Size;i++){
		//TXE为0时表明发送数据寄存器不为空;为1时才表明发送数据寄存器为空
		while(USART_GetFlagStatus(USART1,USART_FLAG_TXE)==RESET);
		//向发送数据寄存器存入数据
		USART_SendData(USART1,pData[i]);
	}
	//TC为0时表明移位数据寄存器不为空;为1时才表明移位数据寄存器为空
	while(USART_GetFlagStatus(USART1,USART_FLAG_TC)==RESET);
}
⑤ 业务代码
uint8_t D[] = {49,50,51,52,53};

My_USART_SendBytes(USART1,D,5);

while(1){}
总代码

//------------------------
//	串口实验1:发送数据
//------------------------

#include "stm32f10x.h"

//串口发送数据原理函数
void My_USART_SendBytes(USART_TypeDef *USARTx,uint8_t *pData,uint16_t Size);

int main(void)
{
	//开启USART1的时钟
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1,ENABLE);
	//初始化USART1
	USART_InitTypeDef USART_InitStruct;
	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);
	
//开启GPIO充当主机的Tx和Rx
	//让PA9当作Tx
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
	GPIO_InitTypeDef GPIO_InitStructure;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_10MHz;
	GPIO_Init(GPIOA,&GPIO_InitStructure);
	
	//让PA10充当Rx
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10;
	GPIO_Init(GPIOA,&GPIO_InitStructure);
	
	//USART总开关
	USART_Cmd(USART1,ENABLE);
	
	uint8_t D[] = {49,50,51,52,53};
	
	My_USART_SendBytes(USART1,D,5);
	
	while(1)
	{
		
	}
}

//串口发送数据原理函数
void My_USART_SendBytes(USART_TypeDef *USARTx,uint8_t *pData,uint16_t Size){
	for(uint32_t i=0;i<Size;i++){
		//TXE为0时表明发送数据寄存器不为空;为1时才表明发送数据寄存器为空
		while(USART_GetFlagStatus(USART1,USART_FLAG_TXE)==RESET);
		//向发送数据寄存器存入数据
		USART_SendData(USART1,pData[i]);
	}
	//TC为0时表明移位数据寄存器不为空;为1时才表明移位数据寄存器为空
	while(USART_GetFlagStatus(USART1,USART_FLAG_TC)==RESET);
}

4、 实验2:发送格式化数据

前提

不重映射,采用 USART1、波特率为115200、全双工通信、8位数据位、无需校验位、1位停止位

步骤

① 开启 USART1 时钟、配置 USART1 并初始化
//开启USART1的时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1,ENABLE);
//配置USART1并初始化
USART_InitTypeDef USART_InitStruct;
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);
② 开启 GPIO 时钟、配置 GPIO 并初始化

根据查表得制未重映射下的 USART1 的 Tx 与 Rx 分别对应 PA9、PA10

且 Tx 和 Rx 选择全双工模式,分别对应 PA9 的复用推挽输出模式、PA10 的输入上拉模式

//PA9作为Tx
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_10MHz;
GPIO_Init(GPIOA,&GPIO_InitStructure);

//PA10作为Rx
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10;
GPIO_Init(GPIOA,&GPIO_InitStructure);
③ 开启 USART1 总开关
//USART总开关
USART_Cmd(USART1,ENABLE);
④ 重载 fputc函数
//重定向fputc函数
int fputc(int ch,FILE *f){
	while(USART_GetFlagStatus(USART1,USART_FLAG_TXE)==RESET);
	USART_SendData(USART1,(uint8_t)ch);
	while(USART_GetFlagStatus(USART1,USART_FLAG_TC)==RESET);
	return ch;
}
⑤ 业务代码
uint8_t D[] = {49,50,51,52,53};
for(int i=0;i<5;i++)
	printf("%c ",D[i]);

while(1){}
总代码
//开启USART1的时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1,ENABLE);
//配置USART1并初始化
USART_InitTypeDef USART_InitStruct;
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);
```c

//-------------------------------
//	串口实验2:发送格式化字符串
//-------------------------------

#include "stm32f10x.h"
#include "stdio.h"

//重定向fputc函数
int fputc(int ch,FILE *f);

int main(void)
{
	//开启USART1的时钟
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1,ENABLE);
	//初始化USART1
	USART_InitTypeDef USART_InitStruct;
	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);
	
//开启GPIO充当主机的Tx和Rx
	//让PA9当作Tx
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
	GPIO_InitTypeDef GPIO_InitStructure;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_10MHz;
	GPIO_Init(GPIOA,&GPIO_InitStructure);
	
	//让PA10充当Rx
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10;
	GPIO_Init(GPIOA,&GPIO_InitStructure);
	
//USART总开关
	USART_Cmd(USART1,ENABLE);
	
	uint8_t D[] = {49,50,51,52,53};
	for(int i=0;i<5;i++)
		printf("%c ",D[i]);
	
	while(1){}
}

//重定向fputc函数
int fputc(int ch,FILE *f){
	while(USART_GetFlagStatus(USART1,USART_FLAG_TXE)==RESET);
	USART_SendData(USART1,(uint8_t)ch);
	while(USART_GetFlagStatus(USART1,USART_FLAG_TC)==RESET);
	return ch;
}

5、实验3:接收数据

前提

不重映射,采用 USART1、波特率为115200、全双工通信、8位数据位、无需校验位、1位停止位

步骤

① 开启 USART1 时钟、配置 USART1 并初始化
//开启USART1的时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1,ENABLE);
//配置USART1并初始化
USART_InitTypeDef USART_InitStruct;
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);
② 开启 GPIO 时钟、配置 GPIO 并初始化

根据查表得制未重映射下的 USART1 的 Tx 与 Rx 分别对应 PA9、PA10

且 Tx 和 Rx 选择全双工模式,分别对应 PA9 的复用推挽输出模式、PA10 的输入上拉模式

//PA9作为Tx
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_10MHz;
GPIO_Init(GPIOA,&GPIO_InitStructure);

//PA10作为Rx
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10;
GPIO_Init(GPIOA,&GPIO_InitStructure);
③ 开启 USART1 总开关
//USART总开关
USART_Cmd(USART1,ENABLE);
④ 板载LED PC13 开启时钟、配置参数、初始化
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC,ENABLE);
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_OD;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_13;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_2MHz;
GPIO_Init(GPIOC,&GPIO_InitStructure);
GPIO_WriteBit(GPIOC,GPIO_Pin_13,Bit_SET);
⑤ 串口接收数据原理函数
uint8_t My_USART_ReceiveBytes(USART_TypeDef *USARTx){
	while(USART_GetFlagStatus(USART1,USART_FLAG_RXNE)==Bit_RESET);
	return USART_ReceiveData(USART1);	
}
⑥ 业务代码
while(1){
	uint8_t byteRcvd = My_USART_ReceiveBytes(USART1);
	if(byteRcvd == '0')
		GPIO_WriteBit(GPIOC,GPIO_Pin_13,Bit_RESET);
	else if(byteRcvd == '1')
		GPIO_WriteBit(GPIOC,GPIO_Pin_13,Bit_SET);
}
总代码

//------------------------
//	串口实验3:接收数据
//------------------------

#include "stm32f10x.h"

//串口接收数据原理函数
uint8_t My_USART_ReceiveBytes(USART_TypeDef *USARTx);

int main(void)
{
	//开启USART1的时钟
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1,ENABLE);
	//初始化USART1
	USART_InitTypeDef USART_InitStruct;
	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);
	
//开启GPIO充当主机的Tx和Rx
	//让PA9当作Tx
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
	GPIO_InitTypeDef GPIO_InitStructure;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_10MHz;
	GPIO_Init(GPIOA,&GPIO_InitStructure);
	
	//让PA10充当Rx
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10;
	GPIO_Init(GPIOA,&GPIO_InitStructure);
	
//开启板载LED PC13
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC,ENABLE);
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_OD;
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_13;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_2MHz;
	GPIO_Init(GPIOC,&GPIO_InitStructure);
	GPIO_WriteBit(GPIOC,GPIO_Pin_13,Bit_SET);
	//USART总开关
	USART_Cmd(USART1,ENABLE);
	
	while(1){
		uint8_t byteRcvd = My_USART_ReceiveBytes(USART1);
		if(byteRcvd == '0')
			GPIO_WriteBit(GPIOC,GPIO_Pin_13,Bit_RESET);
		else if(byteRcvd == '1')
			GPIO_WriteBit(GPIOC,GPIO_Pin_13,Bit_SET);
	}
}

//串口发送数据原理函数
uint8_t My_USART_ReceiveBytes(USART_TypeDef *USARTx){
	while(USART_GetFlagStatus(USART1,USART_FLAG_RXNE)==Bit_RESET);
	return USART_ReceiveData(USART1);	
}

6、封装 USART 功能

以下函数可以直接用

my_usart.h

/**
  ******************************************************************************
  * @file    my_usart.h
  * @author  铁头山羊
  * @version V 1.0.0
  * @date    2024年9月1日
  * @brief   串口头文件
  ******************************************************************************
  */
	
#ifndef _MY_USART_H_
#define _MY_USART_H_

#include "stm32f10x.h"

#define LINE_SEPERATOR_CR   0x00 // 回车 \r
#define LINE_SEPERATOR_LF   0x01 // 换行 \n
#define LINE_SEPERATOR_CRLF 0x02 // 回车+换行 \r\n

void My_USART_SendByte(USART_TypeDef *USARTx, const uint8_t Data);
void My_USART_SendBytes(USART_TypeDef *USARTx, const uint8_t *pData, uint16_t Size);
void My_USART_SendChar(USART_TypeDef *USARTx, const char C);
void My_USART_SendString(USART_TypeDef *USARTx, const char *Str);
void My_USART_Printf(USART_TypeDef *USARTx, const char *Format, ...);

uint8_t My_USART_ReceiveByte(USART_TypeDef *USARTx);
uint16_t My_USART_ReceiveBytes(USART_TypeDef *USARTx, uint8_t *pDataOut, uint16_t Size, int Timeout);
int My_USART_ReceiveLine(USART_TypeDef *USARTx, char *pStrOut, uint16_t MaxLength, uint16_t LineSeperator, int Timeout);

#endif


my_usart.c

#include "my_usart.h"
#include <stdio.h>
#include <string.h>
#include <stdarg.h>
#include "delay.h"

static USART_TypeDef *usart_for_printf = 0; // 用于printf的USART名称

//
// @简介:使用串口发送一个字节的数据
// 
// @参数 USARTx:串口名称,如USART1, USART2, USART3 ...
// @参数 Data  : 要发送的数据
//
void My_USART_SendByte(USART_TypeDef *USARTx, const uint8_t Data)
{
	My_USART_SendBytes(USARTx, &Data, 1);
}

//
// @简介:使用串口发送多个字节的数据
// 
// @参数 USARTx:串口名称,如USART1, USART2, USART3 ...
// @参数 pData : 要发送的数据(数组)
// @参数 Size  :要发送数据的数量,单位是字节
//
__weak void My_USART_SendBytes(USART_TypeDef *USARTx, const uint8_t *pData, uint16_t Size)
{
	if(Size == 0) return;
	
	for(uint16_t i=0; i < Size; i++)
	{
		while(USART_GetFlagStatus(USARTx, USART_FLAG_TXE) == RESET);
		
		USART_SendData(USARTx, pData[i]);
	}
	
	while(USART_GetFlagStatus(USARTx, USART_FLAG_TC) == RESET);
}

//
// @简介:通过串口发送一个字符
// 
// @参数 USARTx:串口名称,如USART1, USART2, USART3 ...
// @参数 C     :要发送的字符
//
void My_USART_SendChar(USART_TypeDef *USARTx, const char C)
{
	My_USART_SendBytes(USARTx, (const uint8_t *)&C, 1);
}

//
// @简介:通过串口发送字符串
// 
// @参数 USARTx:串口名称,如USART1, USART2, USART3 ...
// @参数 Str   :要发送的字符串
//
void My_USART_SendString(USART_TypeDef *USARTx, const char *Str)
{
	My_USART_SendBytes(USARTx, (const uint8_t *)Str, strlen(Str));
}

//
// @简介:通过串口格式化打印字符串
// 
// @参数 USARTx:串口名称,如USART1, USART2, USART3 ...
// @参数 Format:字符串的格式
// @参数 ...   :可变参数
//
void My_USART_Printf(USART_TypeDef *USARTx, const char *Format, ...)
{
	usart_for_printf = USARTx;
	
	va_list args;
	va_start(args, Format);
	vprintf(Format, args);
	va_end(args);
}


//
// @简介:通过串口读取一字节的数据
// 
// @参数 USARTx  :串口名称,如USART1, USART2, USART3 ...
// 
// @返回值:读取到的字节
//
uint8_t My_USART_ReceiveByte(USART_TypeDef *USARTx)
{
	while(USART_GetFlagStatus(USARTx, USART_FLAG_RXNE) == RESET);
	
	return USART_ReceiveData(USARTx);
}

//
// @简介:通过串口读取多个字节的数据
// 
// @参数 USARTx  :串口名称,如USART1, USART2, USART3 ...
// @参数 pDataOut:输出参数,读取到的数据将输出到此数组当中
// @参数 Size    :需要读取的字节数量
// @参数 Timeout :超时时间,单位是毫秒,负数表示无限长。如果超时时间内没有读取完成则返回。
// 
// @返回值:实际读取到的数据数量
//
__weak uint16_t My_USART_ReceiveBytes(USART_TypeDef *USARTx, uint8_t *pDataOut, uint16_t Size, int Timeout)
{
	uint32_t expireTime;
	
	Delay_Init();
	
	if(Timeout >= 0)
	{
		expireTime = GetTick() + Timeout; // 计算过期时间,过期时间 = 当前时间+Timeout
	}
	
	uint16_t i = 0;
	
	do
	{
		if(USART_GetFlagStatus(USARTx, USART_FLAG_RXNE) == SET)
		{
			pDataOut[i++] = USART_ReceiveData(USARTx);
			
			if(i==Size) break;
		}
	}
	while(Timeout < 0 || GetTick() < expireTime); // 判断是否超时
	
	return i;
}


//
// @简介:通过串口读取一行字符串
// 
// @参数 USARTx       :串口名称,如USART1, USART2, USART3 ...
// @参数 pStrOut      :输出参数,读取到的数据将输出到此数组当中
// @参数 MaxLength    :字符串的最大长度
// @参数 LineSeperator:行分隔符 LINE_SEPERATOR_CR   - 回车 \r
//                               LINE_SEPERATOR_LF   - 换行 \n
//                               LINE_SEPERATOR_CRLF - 回车+换行 \r\n
// @参数 Timeout      :超时时间,单位是毫秒,负数表示无限长。如果超时时间内没有读取完成则返回
// 
// @返回值:0 - 成功读到一行字符串
//         -1 - 超时(Timeout内未读到一行完整的字符串)
//         -2 - 超过字符串的最大长度(字符串的最大长度用MaxLength参数设置)
//
int My_USART_ReceiveLine(USART_TypeDef *USARTx, char *pStrOut, uint16_t MaxLength, uint16_t LineSeperator, int Timeout)
{
	// 如果最大长度都不足以装下行分隔符
	// 就直接返回失败
	if(MaxLength < 2 || ((LineSeperator == LINE_SEPERATOR_CRLF) && (MaxLength < 1)))
	{
		return -2;
	}
	
	int ret = -1;
	uint32_t expireTime;
	
	Delay_Init(); // 要用到单片机当前时间,所以初始化延迟函数
	
	if(Timeout >= 0)
	{
		expireTime = GetTick() + Timeout; // 计算过期时间,过期时间 = 当前时间+Timeout
	}
	
	uint16_t i = 0;
	
	do
	{
		if(USART_GetFlagStatus(USARTx, USART_FLAG_RXNE) == SET)
		{
			char c = (char)USART_ReceiveData(USARTx);
			pStrOut[i++] = c;
			
			if(LineSeperator == LINE_SEPERATOR_CR && c == '\r') // \r
			{
				ret = 0;
				break;
			}
			else if(LineSeperator == LINE_SEPERATOR_LF && c == '\n') // \n
			{
				ret = 0;
				break;
			}
			else if(i >= 2 && pStrOut[i-2] == '\r' && c == '\n') // \r\n
			{
				ret = 0;
				break;
			}
			
			if(i == MaxLength) // 超过最大长度
			{
				ret = -2;
				break;
			}
		}
	}
	while(Timeout < 0 || GetTick() < expireTime); // 判断是否超时
	
	// 在字符串末尾增加'\0'
	if(i == MaxLength)
	{
		pStrOut[i-1] = '\0';
	}
	else
	{
		pStrOut[i] = '\0';
	}
	
	return ret;
}

//
// @简介:此函数为对fputc的重写,以实现串口格式化打印功能
//
int fputc(int ch, FILE *f)
{
	// #1. 等待TXE
	while(USART_GetFlagStatus(usart_for_printf, USART_FLAG_TXE) == RESET);
	// #2. 将数据写入TDR
	USART_SendData(usart_for_printf, (uint8_t)ch);
	
	return ch;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值