本笔记基于 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;
}



被折叠的 条评论
为什么被折叠?



