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

news2024/11/29 22:38:57

我就不讲理论了,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初始化

/*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. 应用场景
    • 按钮输入:通常用于检测按钮按下事件。按钮未按下时,引脚通过上拉电阻保持高电平;按钮按下时,引脚被拉低。
    • 开关状态检测:用于检测开关的状态,开关断开时引脚为高电平,开关闭合时引脚为低电平。
    • 传感器输入:某些传感器输出可能需要一个上拉电阻来确保信号的稳定性。

③、串口初始化

/*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)来控制数据流。
    • 优点:可靠性高,无需软件干预,适用于高速数据传输。
    • 缺点:需要额外的硬件信号线,配置相对复杂。

总结

  • 使用硬件流控制:适用于高速数据传输、长距离通信、嵌入式系统、实时应用和高可靠性要求的场景。
  • 不使用硬件流控制:可能导致数据丢失、缓冲区溢出、性能下降、软件复杂性增加和实时性降低。

    /*中断输出配置*/
	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、串口通信的相关功能函数

①、串口发送一个字节

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

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 检测的是发送寄存器中的数据是否为空

②、发送各种形式的数据

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

/**
  * 函    数:串口发送一个字符串
  * 参    数: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 函数,从而通过串口输出字符串。
/**
  * 函    数:使用printf需要重定向的底层函数
  * 参    数:保持原始格式即可,无需变动
  * 返 回 值:保持原始格式即可,无需变动
  */
int fputc(int ch, FILE *f)
{
	Serial_SendByte(ch);			//将printf的底层重定向到自己的发送字节函数
	return ch;
}

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

/**
  * 函    数:自己封装的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);		//串口发送字符数组(字符串)
}

接收中断函数

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

/**
  * 函    数: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

#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

#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

#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);			//串口将收到的数据回传回去,用于测试
			
		}
		
		}
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2188967.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

C语言基础(7)之操作符(1)(详解)

目录 1. 各种操作符介绍 1.1 操作符汇总表 2. 移位操作符 2.1 移位操作符知识拓展 —— 原码、反码、补码 2.2 移位操作符讲解 2.2.1 右移操作符 ( >> ) 2.2.2 左移操作符 ( << ) 3. 位操作符 3.1 & (按位与) 3.2 | (按位或) 3.3 ^ (按位异或) 3.4…

【AI学习】Mamba学习(二):线性注意力

上一篇《Mamba学习&#xff08;一&#xff09;&#xff1a;总体架构》提到&#xff0c;Transformer 模型的主要缺点是&#xff1a;自注意力机制的计算量会随着上下文长度的增加呈平方级增长。所以&#xff0c;许多次二次时间架构&#xff08;指一个函数或算法的增长速度小于二次…

C++ 多态:重塑编程效率与灵活性

目录 多态的概念 多态的定义及实现 多态的构成条件 虚函数 虚函数的重写 虚函数重写的两个例外&#xff1a; 1. 协变(基类与派生类虚函数返回值类型不同) 2. 析构函数的重写(基类与派生类析构函数的名字不同&#xff09; 析构函数要不要定义成虚函数&#xff1f;&…

绝对值得收藏!分享7款ai写作论文免费一键生成网站

在当前的学术研究和写作过程中&#xff0c;AI写作工具已经成为了许多研究者和学生的重要助手。这些工具不仅能够提高写作效率&#xff0c;还能帮助生成高质量的论文内容。以下是七款免费的AI写作论文生成器&#xff0c;其中特别推荐千笔-AIPassPaper。 1.千笔-AIPassPaper 千…

信号处理: Block Pending Handler 与 SIGKILL/SIGSTOP 实验

1. 信号处理机制的 “三张表” kill -l &#xff1a;前 31 个信号为系统标准信号。 block pending handler 三张表保存在每个进程的进程控制块 —— pcb 中&#xff0c;它们分别对应了某一信号的阻塞状态、待处理状态以及处理方式。 block &#xff1a;通过 sigset_t 类型实现&…

YOLO11改进 | 检测头 | 融合渐进特征金字塔的检测头【AFPN3】

秋招面试专栏推荐 &#xff1a;深度学习算法工程师面试问题总结【百面算法工程师】——点击即可跳转 &#x1f4a1;&#x1f4a1;&#x1f4a1;本专栏所有程序均经过测试&#xff0c;可成功执行&#x1f4a1;&#x1f4a1;&#x1f4a1; 本文介绍了一个渐进特征金字塔网络&…

关于 S7 - 1200 通过存储卡进行程序更新

西门子S7-1200系列PLC可以通过存储卡进行程序的更新&#xff0c;固件版本的升级以及程序数据的存储多项功能。本例进行程序更新的操作。 存储卡的订货号以及存储容量 一&#xff1b;如何插入存储卡 在CPU断电下&#xff0c;将CPU上挡板向下掀开&#xff0c;可以看到右上角有一…

ai写作论文会被检测吗?分享市面上7款自动写论文网站

近年来&#xff0c;随着人工智能技术的飞速发展&#xff0c;AI写作工具在学术界引起了广泛关注。然而&#xff0c;这些工具的使用也引发了关于学术诚信和检测机制的讨论。根据多所高校的声明&#xff0c;为了应对AI代写论文的现象&#xff0c;许多高校已经开始引入论文检测工具…

Python入门:深入了解__init__.py 文件(如何实现动态导入子模块)

文章目录 📖 介绍 📖🏡 演示环境 🏡📒 文章内容 📒📝 `__init__.py` 的作用示例:📝 如何编写 `__init__.py`1. 空的 `__init__.py`2. 导入子模块3. 初始化代码4. 动态导入子模块📝 编写 `__init__.py` 的技巧和注意事项⚓️ 相关链接 ⚓️📖 介绍 📖 在…

01:(寄存器开发)点亮一个LED灯

寄存器开发 1、单片机的简介1.1、什么是单片机1.2、F1系列内核和芯片的系统架构1.3、存储器映像1.4、什么是寄存器 2、寄存器开发模板工程3、使用寄存器点亮一个LED4、代码改进15、代码改进2 本教程使用的是STM32F103C8T6最小系统板&#xff0c;教程来源B站up“嵌入式那些事”。…

前缀和(6)_和可被k整除的子数组_蓝桥杯

个人主页&#xff1a;C忠实粉丝 欢迎 点赞&#x1f44d; 收藏✨ 留言✉ 加关注&#x1f493;本文由 C忠实粉丝 原创 前缀和(6)_和可被k整除的子数组 收录于专栏【经典算法练习】 本专栏旨在分享学习算法的一点学习笔记&#xff0c;欢迎大家在评论区交流讨论&#x1f48c; 目录 …

kubeadm部署k8s

1.1 安装Docker [rootk8s-all ~]# wget -O /etc/yum.repos.d/docker-ce.repo https://mirrors.huaweicloud.com/docker-ce/linux/centos/docker-ce.repo [rootk8s-all ~]# sed -i sdownload.docker.commirrors.huaweicloud.com/docker-ce /etc/yum.repos.d/docker-ce.repo [ro…

基于Keras的U-Net模型在图像分割与计数中的应用

关于深度实战社区 我们是一个深度学习领域的独立工作室。团队成员有&#xff1a;中科大硕士、纽约大学硕士、浙江大学硕士、华东理工博士等&#xff0c;曾在腾讯、百度、德勤等担任算法工程师/产品经理。全网20多万粉丝&#xff0c;拥有2篇国家级人工智能发明专利。 社区特色&a…

Yocto - 使用Yocto开发嵌入式Linux系统_07 构建使用的临时文件夹

Detailing the Temporary Build Directory 在本章中&#xff0c;我们将尝试了解映像生成后临时构建目录的内容&#xff0c;并了解 BitBake 如何在烘焙过程中使用它。此外&#xff0c;我们还将了解这些目录中的某些内容如何在出现问题时作为有价值的信息来源来帮助我们。 In thi…

前缀和——从LeetCode题海中总结常见套路

目录 前缀和定义 截断前缀和DP&#xff1a;LeetCode53.最大子序和 经典左右指针&#xff1a;LeetCode209.长度最小的子数组 暴力求解&#xff1a;超时 优雅的双指针写法一&#xff1a; 优雅的双指针写法二&#xff1a; LeetCode.1588.所有奇数长度子数组的和 手速题&am…

springboot系列--web相关知识探索三

一、前言 web相关知识探索二中研究了请求是如何映射到具体接口&#xff08;方法&#xff09;中的&#xff0c;本次文章主要研究请求中所带的参数是如何映射到接口参数中的&#xff0c;也即请求参数如何与接口参数绑定。主要有四种、分别是注解方式、Servlet API方式、复杂参数、…

[大语言模型-算法优化] 微调技术-LoRA算法原理及优化应用详解

[大语言模型-算法优化] 微调技术-LoRA算法原理及优化应用详解 前言: 古人云: 得卧龙者&#xff0c;得天下。 然在当今大语言模型流行的时代&#xff0c;同样有一句普世之言: 会微调技术者&#xff0c;得私域大模型部署之道&#xff01; 在众多微调技术中&#xff0c;LoRA (…

单细胞scDist细胞扰动差异分析学习

scDist通过分析不同状态下细胞的距离来找到差异最大的细胞亚群(见下图的A)&#xff0c;然后再分析每一个细胞亚群的PCA通过线性的混合模型并结合最终的系数去预估不同干预方式下细胞群之间的距离。 Augur是通过对每一个细胞进行AUC评分并排序最终找到扰动最佳的细胞群&#xf…

等额本金和等额本息是什么意思?

等额本金和等额本息是两种常见的贷款还款方式&#xff0c;它们各自有着不同的特点和适用场景。下面我将用通俗易懂的语言来解释这两种还款方式&#xff1a; 等额本金 定义&#xff1a;等额本金指的是在贷款期限内&#xff0c;每月偿还相同数额的本金&#xff0c;而利息则随着剩…

FPGA远程烧录bit流

FPGA远程烧录bit流 Vivado支持远程编译并下载bit流到本地xilinx开发板。具体操作就是在连接JTAG的远程电脑上安装hw_server.exe。比如硬件板在实验室或者是其他地方&#xff0c;开发代码与工程在本地计算机&#xff0c;如何将bit流烧录到实验室或者远程开发板&#xff1f; vi…