UART通信—基于江科大源码基础进行的改进和解析

我就不讲理论了,CSDN上大佬属实多,我就只讲代码了,串口的基本理论,大家去看其他大佬写的吧

一、源文件的组成

1、包含的头文件

stm32f10x.h 是STM32F10x系列微控制器的标准外设库(Standard Peripheral Library)的主头文件。这个文件通常包含了对整个STM32F10x系列微控制器的所有硬件外设支持的定义和声明。

下面这个在stm32f10x.h中的文件就是包含了外设的头文件

stm32f10x_conf.h 文件是STM32F10x系列微控制器的标准配置头文件。这个文件通常包含了一些宏定义,用于启用或禁用特定的外设库功能。

2、UART初始化

①、开启时钟

    RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1,ENABLE); //开启串口1对应的时钟
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);	//开启串口对应GPIO口的时钟

在配置时钟的时候,需要用到哪些外设,除了到相关手册中查询外,也可以直接到配置文件中查询。

②、GPIO初始化

cpp 复制代码
/*GPIO初始化*/
	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_50MHz;
	GPIO_Init(GPIOA, &GPIO_InitStructure);
	
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;     //上拉输入
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOA, &GPIO_InitStructure);	

PA9对应着串口1的发送端,因此这里选择模式为复用推免输出

复用推免输出模式详解

  • 复用功能模式 (AF)

在复用功能模式下,GPIO 引脚被配置为支持外设的特定功能,例如 USART、SPI、I2C 等。这些引脚可以连接到多个外设,具体取决于你选择的复用功能。

  • 推挽输出 (PP)

推挽输出是一种常见的输出模式,具有以下特点:

高电平:当输出为高电平时,引脚直接连接到 VDD(电源电压),驱动能力较强。

低电平:当输出为低电平时,引脚直接连接到 GND(地),驱动能力较强。

无上拉/下拉电阻:不需要外部上拉或下拉电阻,因为内部电路已经提供了足够的驱动能力。

  • GPIO_Mode_AF_PP****配置详解

GPIO_Mode_AF_PP 将 GPIO 引脚配置为复用推挽输出模式。这种配置通常用于需要高速和强驱动能力的应用,例如 UART、SPI 和 I2C 的数据传输引脚。

PA10对应着串口1的输入端,因此这里选择 上拉输入 模式

上拉输入模式 (GPIO_Mode_IPU) 详解

  1. 定义
    • 上拉输入模式:在这种模式下,GPIO引脚被配置为输入模式,并且内部有一个上拉电阻将其默认拉到高电平(VDD)。
    • 当外部信号未连接或处于高阻态时,引脚的默认状态是高电平。
    • 当外部信号为低电平时,引脚会被拉低。
  1. 优点:减少噪声干扰
    • 防止浮空:避免了引脚在没有外部信号时处于不确定的状态(浮空)。
    • 减少噪声:上拉电阻有助于减少噪声和干扰,提高信号的稳定性。
    • 简化电路设计:不需要外部上拉电阻,减少了外部元件的数量。
  1. 应用场景
    • 按钮输入:通常用于检测按钮按下事件。按钮未按下时,引脚通过上拉电阻保持高电平;按钮按下时,引脚被拉低。
    • 开关状态检测:用于检测开关的状态,开关断开时引脚为高电平,开关闭合时引脚为低电平。
    • 传感器输入:某些传感器输出可能需要一个上拉电阻来确保信号的稳定性。

③、串口初始化

cpp 复制代码
/*USART初始化*/
	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;	//停止位,选择1位
	USART_InitStructure.USART_WordLength = USART_WordLength_8b;		//字长,选择8位
	USART_Init(USART1, &USART_InitStructure);	
波特率:(传输消息要保证,输入和输出两端的波特率保持一致,要不然可能会出现乱码的现象)

(Baud Rate)是串行通信中的一个重要参数,用于衡量数据传输的速度。它表示每秒钟传输的符号(码元)数量。在数字通信中,这些符号通常代表比特(bit)。

硬件流控制(Hardware Flow Control):

硬件流控制(Hardware Flow Control)在串行通信中用于管理数据流,以防止发送方的数据速率超过接收方的处理能力。使用硬件流控制可以有效避免数据丢失和缓冲区溢出问题。下面详细解释何时需要使用硬件流控制以及不使用时可能产生的影响。

何时需要使用硬件流控制
  1. 高速数据传输
    • 当数据传输速率非常高时,接收方可能无法及时处理所有接收到的数据,导致缓冲区溢出。硬件流控制可以通过CTS/RTS信号线动态调整数据流,确保接收方能够处理所有数据。
  1. 长距离通信
    • 在长距离通信中,信号传输延迟较大,可能会导致接收方来不及处理数据。硬件流控制可以更好地管理数据流,确保数据的可靠传输。
  1. 嵌入式系统
    • 在嵌入式系统中,处理器资源有限,处理能力可能不足。硬件流控制可以减轻处理器负担,提高系统的稳定性和可靠性。
  1. 实时应用
    • 对于需要实时处理数据的应用,如工业自动化、医疗设备等,硬件流控制可以确保数据的及时处理,避免因数据丢失而导致的系统故障。
  1. 高可靠性要求
    • 对于对数据完整性有高要求的应用,如金融交易、航空航天等,硬件流控制可以提供更高的数据传输可靠性。
不使用硬件流控制的影响
  1. 数据丢失
    • 如果接收方的缓冲区已满而发送方继续发送数据,可能会导致数据丢失。特别是在高速数据传输或处理器处理能力不足的情况下,数据丢失的风险更高。
  1. 缓冲区溢出
    • 接收方的缓冲区可能会溢出,导致数据被覆盖或系统崩溃。这不仅会导致数据丢失,还可能影响系统的稳定性。
  1. 性能下降
    • 为了防止数据丢失,发送方可能需要频繁地检查接收方的状态,这会增加软件开销,降低整体性能。
  1. 复杂性增加
    • 如果不使用硬件流控制,需要通过软件实现流量控制机制,如XON/XOFF协议。这会增加软件的复杂性,并且不如硬件流控制可靠。
  1. 实时性降低
    • 在实时应用中,数据的及时处理非常重要。如果数据丢失或处理延迟,可能会导致系统响应时间延长,影响实时性能。
软件流控制 vs 硬件流控制
  • 软件流控制(如XON/XOFF):
    • 通过特定的字符(通常是ASCII码中的XON (0x11) 和 XOFF (0x13))来控制数据流。
    • 优点:不需要额外的硬件信号线。
    • 缺点:增加了软件开销,不如硬件流控制可靠,容易受到数据干扰。
  • 硬件流控制(如CTS/RTS):
    • 通过专用的硬件信号线(CTS和RTS)来控制数据流。
    • 优点:可靠性高,无需软件干预,适用于高速数据传输。
    • 缺点:需要额外的硬件信号线,配置相对复杂。

总结

  • 使用硬件流控制:适用于高速数据传输、长距离通信、嵌入式系统、实时应用和高可靠性要求的场景。
  • 不使用硬件流控制:可能导致数据丢失、缓冲区溢出、性能下降、软件复杂性增加和实时性降低。
cpp 复制代码
    /*中断输出配置*/
	USART_ITConfig(USART1, USART_IT_RXNE, ENABLE);					//开启串口接收数据的中断;中断模式、接收数据寄存器非空中断。
	
	/*NVIC中断分组*/
	NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);					//配置NVIC为分组2
	
	/*NVIC配置*/
	NVIC_InitTypeDef NVIC_InitStructure;							//定义结构体变量
	NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn;				//选择配置NVIC的USART1线
	NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;					//指定NVIC线路使能
	NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;		//指定NVIC线路的抢占优先级为1
	NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;				//指定NVIC线路的响应优先级为1
	NVIC_Init(&NVIC_InitStructure);									//将结构体变量交给NVIC_Init,配置NVIC外设
	
	/*USART使能*/
	USART_Cmd(USART1, ENABLE);	

串口中断配置

  • USART_IT_RXNE:接收数据寄存器非空 (Receive Data Register Not Empty) 中断。
  • 数据寄存器中不是空的就启动中断

NVIC中断配置

选择分组二,抢占优先级可以有4个值,响应优先级也可以有4个,是一个比较中和的分组

中断通道,选择串口1的中断通道

抢占优先级和响应优先级

超市购物结账

抢占:霸道,我只要比你的抢占优先级高,我来了,不管你是正在排队准备付款还是正在被结账员扫描物品,你都得靠边站,我付完款了才轮的到你。

响应:基于抢占优先级相同的情况下

响应,有响才有应,一个正在结账的人结账完成,就是对后面所有的人的一个响,那么后面接下来谁先来应呢,就得看谁的响应优先级高了,响应优先级高的时候是不管先来后到的,可以插队,但是不可以打断正在执行过程中的中断。

假设我们有以下四个中断,配置如下:

|--------|-----------|----------|
| 中断 | 抢占优先级 | 子优先级 |
| IRQ1 | 1 | 0 |
| IRQ2 | 1 | 1 |
| IRQ3 | 2 | 0 |
| IRQ4 | 2 | 1 |

  • IRQ1 和 IRQ2
    • 抢占优先级相同(都是1),但子优先级不同。
    • IRQ2 的子优先级更高,所以在同一抢占优先级组内,IRQ2 会先于 IRQ1 被处理。
  • IRQ3 和 IRQ4
    • 抢占优先级相同(都是2),但子优先级不同。
    • IRQ4 的子优先级更高,所以在同一抢占优先级组内,IRQ4 会先于 IRQ3 被处理。
  • IRQ1/IRQ2 和 IRQ3/IRQ4
    • IRQ3 和 IRQ4 的抢占优先级(2)高于 IRQ1 和 IRQ2 的抢占优先级(1)。
    • 因此,如果 IRQ3 或 IRQ4 发生时,IRQ1 或 IRQ2 正在执行,IRQ3 或 IRQ4 会立即抢占 IRQ1 或 IRQ2。

④使能串口1

/*USART使能*/
	USART_Cmd(USART1, ENABLE);	

3、串口通信的相关功能函数

①、串口发送一个字节

cpp 复制代码
/**
  * 函    数:串口发送一个字节
  * 参    数:Byte 要发送的一个字节
  * 返 回 值:无
  */
void Serial_SendByte(uint8_t Byte)
{
	USART_SendData(USART1, Byte);		//将字节数据写入数据寄存器,写入后USART自动生成时序波形
	while (USART_GetFlagStatus(USART1, USART_FLAG_TXE) == RESET);	//等待发送完成
	/*下次写入数据寄存器会自动清除发送完成标志位,故此循环后,无需清除标志位*/
}
cpp 复制代码
void USART_SendData(USART_TypeDef* USARTx, uint16_t Data)
{
  /* Check the parameters */
  assert_param(IS_USART_ALL_PERIPH(USARTx));
  assert_param(IS_USART_DATA(Data)); 
    
  /* Transmit Data */
  USARTx->DR = (Data & (uint16_t)0x01FF);
}

assert_param:

是一个宏,通常用于在嵌入式系统编程中进行参数检查。它的主要目的是确保传入函数的参数是有效的,如果参数无效,则触发断言失败,从而帮助开发者发现和修复错误。

因此以下这两行分别是验证 串口号 传输数据 是否有效。

这里是写寄存器,主要是将9位或者8位数据保留(最终保留几位数据根据配置所定)

保留的原理:符号&的作用是按位与,全真则真,一假则假

举例

0x01FF转化为二进制为 0000 0001 1111 1111

如果传输的数据的二进制是 0000 0000 0000 1111

那么两个数据按位与后,还是传输数据的 0000 0000 0000 1111

因为计算机种就是以二进制传递信息的,所以最终是以二进制的数据形式被存储在了DR寄存器中

USART 数据寄存器 DR 通常只能处理 8 位或 9 位的数据,以上面这种方法也是为了将高于9位的数据清零,前面都是0了,不管传过来的数据是1还是0,最终经过按位与后都为0了。

USART(通用同步异步收发传输器)的SR寄存器(状态寄存器)是一个非常重要的寄存器,它用于指示USART外设的各种状态。通过读取SR寄存器,可以获取当前USART的状态信息,如数据是否准备好发送、是否接收到数据、是否有错误发生等。

这是一个检测当前寄存器的状态

USART_FLAG_TXE 检测的是发送寄存器中的数据是否为空

②、发送各种形式的数据

以下是几种数据格式的发送函数,都是围绕着发送字节函数来的,借助可以检测特殊符号的循环完成整个的发送,但是其中内在和发送字节是相同的

cpp 复制代码
/**
  * 函    数:串口发送一个字符串
  * 参    数:String 要发送字符串的首地址
  * 返 回 值:无
  */
void Serial_SendString(char *String)
{
	uint8_t i;
	for (i = 0; String[i] != '\0'; i ++)//遍历字符数组(字符串),遇到字符串结束标志位后停止
	{
		Serial_SendByte(String[i]);		//依次调用Serial_SendByte发送每个字节数据
	}
}

/**
  * 函    数:次方函数(内部使用)
  * 返 回 值:返回值等于X的Y次方
  */
uint32_t Serial_Pow(uint32_t X, uint32_t Y)
{
	uint32_t Result = 1;	//设置结果初值为1
	while (Y --)			//执行Y次
	{
		Result *= X;		//将X累乘到结果
	}
	return Result;
}

/**
  * 函    数:串口发送数字
  * 参    数:Number 要发送的数字,范围:0~4294967295
  * 参    数:Length 要发送数字的长度,范围:0~10
  * 返 回 值:无
  */
void Serial_SendNumber(uint32_t Number, uint8_t Length)
{
	uint8_t i;
	for (i = 0; i < Length; i ++)		//根据数字长度遍历数字的每一位
	{
		Serial_SendByte(Number / Serial_Pow(10, Length - i - 1) % 10 + '0');	//依次调用Serial_SendByte发送每位数字
	}
}

③、重定向函数

  1. Serial_SendByte****函数
    • 这是一个假设的串口发送函数,用于通过串口发送一个字节的数据。
    • 具体实现取决于你的硬件平台和串口驱动程序。
  1. 重写 fputc****函数
    • fputc 是标准I/O库中的一个函数,用于将一个字符写入指定的文件流。
    • 通过重写 fputc,你可以改变其默认行为,使其将字符发送到串口而不是标准输出。
    • 代码中,Serial_SendByte(ch) 被调用来发送字符 ch 到串口。
    • return ch; 确保 fputc 返回正确的字符值,以便 printf 可以继续正常工作。
  1. 使用 printf****输出字符串
    • main 函数中,printf("Hello, World!\n"); 会被调用。
    • 由于 fputc 已被重写,printf 会通过 fputc 将每个字符发送到 Serial_SendByte 函数,从而通过串口输出字符串。
cpp 复制代码
/**
  * 函    数:使用printf需要重定向的底层函数
  * 参    数:保持原始格式即可,无需变动
  * 返 回 值:保持原始格式即可,无需变动
  */
int fputc(int ch, FILE *f)
{
	Serial_SendByte(ch);			//将printf的底层重定向到自己的发送字节函数
	return ch;
}

Printf函数的封装,本质上是将数据转化为字符串后,以字符串的形式发出

cpp 复制代码
/**
  * 函    数:自己封装的prinf函数
  * 参    数:format 格式化字符串
  * 参    数:... 可变的参数列表
  * 返 回 值:无
  */
void Serial_Printf(char *format, ...)
{
	char String[100];				//定义字符数组
	va_list arg;					//定义可变参数列表数据类型的变量arg
	va_start(arg, format);			//从format开始,接收参数列表到arg变量
	vsprintf(String, format, arg);	//使用vsprintf打印格式化字符串和参数列表到字符数组中
	va_end(arg);					//结束变量arg
	Serial_SendString(String);		//串口发送字符数组(字符串)
}

接收中断函数

这里我使用的环形缓冲区,可以用来存储更多的字节

cpp 复制代码
/**
  * 函    数:USART1中断函数
  * 参    数:无
  * 返 回 值:无
  * 注意事项:此函数为中断函数,无需调用,中断触发后自动执行
  *           函数名为预留的指定名称,可以从启动文件复制
  *           请确保函数名正确,不能有任何差异,否则中断函数将不能进入
  */
void USART1_IRQHandler(void)
{
	// 检查是否是接收中断
    if (USART_GetITStatus(USART1, USART_IT_RXNE) != RESET) {
        // 从USART数据寄存器读取一个字节的数据
        uint8_t data = USART_ReceiveData(USART1);

        // 将接收到的数据放入环形缓冲区
        Rx_Data[WriteIndex] = data;

        // 更新写索引
        WriteIndex = (WriteIndex + 1) % Data_size;

        // 设置接收标志位
        Rx_Flag = 1;
		
		USART_ClearITPendingBit(USART1, USART_IT_RXNE);	
    }
	
}

二、源码

USART.c

cpp 复制代码
#include <stm32f10x.h>		//包含头文件
#include <stdarg.h>
#include "uart.h"
#include "stdio.h"

#define Data_size 100			//给数据接收缓存足够的空间

uint8_t Rx_Data[Data_size];     //
uint16_t WriteIndex = 0;
uint16_t ReadIndex = 0;
uint8_t Rx_Flag;

void UART_Init(void)
{
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1,ENABLE); //开启串口1对应的时钟
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);	//开启串口对应GPIO口的时钟
	
	/*GPIO初始化*/
	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_50MHz;
	GPIO_Init(GPIOA, &GPIO_InitStructure);
	
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOA, &GPIO_InitStructure);	
	
	
	/*USART初始化*/
	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;	//停止位,选择1位
	USART_InitStructure.USART_WordLength = USART_WordLength_8b;		//字长,选择8位
	USART_Init(USART1, &USART_InitStructure);	

	/*中断输出配置*/
	USART_ITConfig(USART1, USART_IT_RXNE, ENABLE);			//开启串口接收数据的中断
	
	/*NVIC中断分组*/
	NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);			//配置NVIC为分组2
	
	/*NVIC配置*/
	NVIC_InitTypeDef NVIC_InitStructure;					//定义结构体变量
	NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn;		//选择配置NVIC的USART1线
	NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;			//指定NVIC线路使能
	NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;		//指定NVIC线路的抢占优先级为1
	NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;		//指定NVIC线路的响应优先级为1
	NVIC_Init(&NVIC_InitStructure);							//将结构体变量交给NVIC_Init,配置NVIC外设
	
	/*USART使能*/
	USART_Cmd(USART1, ENABLE);	
	
	
}

/**
  * 函    数:串口发送一个字节
  * 参    数:Byte 要发送的一个字节
  * 返 回 值:无
  */
void Serial_SendByte(uint8_t Byte)
{
	USART_SendData(USART1, Byte);		//将字节数据写入数据寄存器,写入后USART自动生成时序波形
	while (USART_GetFlagStatus(USART1, USART_FLAG_TXE) == RESET);	//等待发送完成
	/*下次写入数据寄存器会自动清除发送完成标志位,故此循环后,无需清除标志位*/
}

/**
  * 函    数:串口发送一个数组
  * 参    数:Array 要发送数组的首地址
  * 参    数:Length 要发送数组的长度
  * 返 回 值:无
  */
void Serial_SendArray(uint8_t *Array, uint16_t Length)
{
	uint16_t i;
	for (i = 0; i < Length; i ++)		//遍历数组
	{
		Serial_SendByte(Array[i]);		//依次调用Serial_SendByte发送每个字节数据
	}
}

/**
  * 函    数:串口发送一个字符串
  * 参    数:String 要发送字符串的首地址
  * 返 回 值:无
  */
void Serial_SendString(char *String)
{
	uint8_t i;
	for (i = 0; String[i] != '\0'; i ++)//遍历字符数组(字符串),遇到字符串结束标志位后停止
	{
		Serial_SendByte(String[i]);		//依次调用Serial_SendByte发送每个字节数据
	}
}

/**
  * 函    数:次方函数(内部使用)
  * 返 回 值:返回值等于X的Y次方
  */
uint32_t Serial_Pow(uint32_t X, uint32_t Y)
{
	uint32_t Result = 1;	//设置结果初值为1
	while (Y --)			//执行Y次
	{
		Result *= X;		//将X累乘到结果
	}
	return Result;
}

/**
  * 函    数:串口发送数字
  * 参    数:Number 要发送的数字,范围:0~4294967295
  * 参    数:Length 要发送数字的长度,范围:0~10
  * 返 回 值:无
  */
void Serial_SendNumber(uint32_t Number, uint8_t Length)
{
	uint8_t i;
	for (i = 0; i < Length; i ++)		//根据数字长度遍历数字的每一位
	{
		Serial_SendByte(Number / Serial_Pow(10, Length - i - 1) % 10 + '0');	//依次调用Serial_SendByte发送每位数字
	}
}

/**
  * 函    数:使用printf需要重定向的底层函数
  * 参    数:保持原始格式即可,无需变动
  * 返 回 值:保持原始格式即可,无需变动
  */
int fputc(int ch, FILE *f)
{
	Serial_SendByte(ch);			//将printf的底层重定向到自己的发送字节函数
	return ch;
}

/**
  * 函    数:自己封装的prinf函数
  * 参    数:format 格式化字符串
  * 参    数:... 可变的参数列表
  * 返 回 值:无
  */
void Serial_Printf(char *format, ...)
{
	char String[100];				//定义字符数组
	va_list arg;					//定义可变参数列表数据类型的变量arg
	va_start(arg, format);			//从format开始,接收参数列表到arg变量
	vsprintf(String, format, arg);	//使用vsprintf打印格式化字符串和参数列表到字符数组中
	va_end(arg);					//结束变量arg
	Serial_SendString(String);		//串口发送字符数组(字符串)
}

/**
  * 函    数:获取串口接收的数据
  * 参    数:无
  * 返 回 值:接收的数据,范围:0~255
  */
uint8_t Serial_GetRxData(void)
{
	return *Rx_Data;			//返回接收的数据变量
}
/**
  * 函    数:获取串口接收标志位
  * 参    数:无
  * 返 回 值:串口接收标志位,范围:0~1,接收到数据后,标志位置1,读取后标志位自动清零
  */
uint8_t Serial_GetRxFlag(void)
{
	if (Rx_Flag == 1)			//如果标志位为1
	{
		Rx_Flag = 0;
		return 1;					//则返回1,并自动清零标志位
	}
	return 0;						//如果标志位为0,则返回0
}



/**
  * 函    数:USART1中断函数
  * 参    数:无
  * 返 回 值:无
  * 注意事项:此函数为中断函数,无需调用,中断触发后自动执行
  *           函数名为预留的指定名称,可以从启动文件复制
  *           请确保函数名正确,不能有任何差异,否则中断函数将不能进入
  */
void USART1_IRQHandler(void)
{
	// 检查是否是接收中断
    if (USART_GetITStatus(USART1, USART_IT_RXNE) != RESET) {
        // 从USART数据寄存器读取一个字节的数据
        uint8_t data = USART_ReceiveData(USART1);

        // 将接收到的数据放入环形缓冲区
        Rx_Data[WriteIndex] = data;

        // 更新写索引
        WriteIndex = (WriteIndex + 1) % Data_size;

        // 设置接收标志位
        Rx_Flag = 1;
		
		USART_ClearITPendingBit(USART1, USART_IT_RXNE);	
    }
	
}
	

UART.h

cpp 复制代码
#ifndef __UART_H
#define __UART_H



void UART_Init(void);
void Serial_SendByte(uint8_t Byte);
void Serial_SendArray(uint8_t *Array, uint16_t Length);
void Serial_SendString(char *String);
void Serial_SendNumber(uint32_t Number, uint8_t Length);
void Serial_Printf(char *format, ...);
void Serial_ProcessRxData(void);

uint8_t Serial_GetRxFlag(void);
uint8_t Serial_GetRxData(void);


#endif

main.c

cpp 复制代码
#include "stm32f10x.h"                  // Device header
#include "uart.h"

uint8_t RxData;

int main(void)
{		
		UART_Init();
		while (1)
		{
		if (Serial_GetRxFlag() == 1)			//检查串口接收数据的标志位
		{
			RxData = Serial_GetRxData();		//获取串口接收的数据
//			Serial_Printf("%x\n",RxData);
			Serial_SendByte(RxData);			//串口将收到的数据回传回去,用于测试
			
		}
		
		}
}
相关推荐
SZPU领跑4 分钟前
第十二届蓝桥杯嵌入式省赛程序设计题解析(基于HAL库)(第一套)
stm32·单片机·算法·职场和发展·蓝桥杯
逢生博客13 分钟前
Rust 语言开发 ESP32C3 并在 Wokwi 电子模拟器上运行(esp-hal 非标准库、LCD1602、I2C)
开发语言·后端·嵌入式硬件·rust
Tlog嵌入式2 小时前
蓝桥杯【物联网】零基础到国奖之路:十六. 扩展模块之矩阵按键
arm开发·stm32·单片机·mcu·物联网·蓝桥杯·iot
黄小美3214 小时前
STM32(五)GPIO输入硬件电路及C语言知识复习
stm32·单片机·嵌入式硬件
py.鸽鸽4 小时前
STM32
stm32·单片机·嵌入式硬件
朴人4 小时前
【从零开始实现stm32无刷电机FOC】【实践】【7.1/7 硬件设计】
stm32·单片机·嵌入式硬件
国科安芯4 小时前
IAR全面支持国科环宇AS32X系列RISC-V车规MCU
人工智能·单片机·mcu·risc-v
qq_294481315 小时前
nrf 24l01使用方法
stm32·嵌入式硬件·物联网
Ω-QAQ-Ω6 小时前
资源《Arduino 扩展板4-单游戏摇杆》说明。
单片机·arduino 扩展板