自定义的USART协议源码阅读
预备知识:
0x0d(回车)0x0a(换行)
整个数据流过程中,这两个一定是一起出现的,如果不是,则说明这串数据出错了,需要重置所有数据
预定义部分
#define USART_REC_LEN 200
#define EN_USART1_RX 1
extern u8 USART_RX_BUF[USART_REC_LEN];
extern u16 USART_RX_STA;
u8 USART_RX_BUF[USART_REC_LEN];
u16 USART_RX_STA=0;
//USART_RX_STA 取第15位(&0x8000),判断是不是接收到了0x0a,如果是强行设置(|=0x8000)
//USART_RX_STA 取第14位(&0x4000),判断是不是接收到了0x0d,如果是强行设置(|=0x4000)
//USART_RX_STA 第0-13位,数据有效位
框架定义
void USART1_IRQHandler(void)
{
u8 Res; //要存到USART_RX_BUF中的数据
#if SYSTEM_SUPPORT_OS
OSIntEnter();
#endif
//USARTx:USART 外设的指针。这里 USARTx 可以是你使用的具体 USART 外设,例如 USART1、USART2 等
//USART_IT:指定要检查的中断源。通常,STM32 的 USART 中断是通过枚举值来表示的,
//例如:USART_IT_RXNE:接收数据寄存器非空中断(即接收寄存器有新数据可读取)。USART_IT_TXE:发送数据寄存器空中断(即发送寄存器空,可以写入数据)。
//该函数返回一个 FlagStatus 类型的值,它是一个宏定义,表示当前中断状态:SET:表示指定的中断已触发。RESET:表示指定的中断未触发。
if(USART_GetITStatus(USART1, USART_IT_RXNE) != RESET)
{
//自定义部分
}
}
#if SYSTEM_SUPPORT_OS
OSIntExit();
#endif
}
自定义部分
用两个数据表示接收结束(0x0d,0x0a)
Res =USART_ReceiveData(USART1);
if((USART_RX_STA&0x8000)==0) //标志位的第15位是不是被设置为1,如果是,结束接收
{
//标志位的第14位是不是被设置为1,如果是,倒数第二个数据(0x0d)已经接收到了,标志位重置过了
if(USART_RX_STA&0x4000)
{
//倒数第二个数据(0x0d)已经接收到了,Res也已经接收了下一个数据,
//判断最后一个是不是0x0a,!=不是,说明整个数据出错了
if(Res!=0x0a) USART_RX_STA=0;
//是,说明整个数据没有问题,USART_RX_STA第15位标志位设置为1,下次入口判断为否,数据接收完成
else USART_RX_STA|=0x8000;
}
else //连倒数第二个数据0x0d都没到,还在中间传输过程中
{
//0x0d接收到,表示已经到倒数第一个数据了,设置第14位标志位,下次接收到数据判断是不是最后一位(0x0a)即可
if(Res==0x0d) USART_RX_STA|=0x4000;
//还在中间数据的记录中
else
{
USART_RX_BUF[USART_RX_STA&0X3FFF]=Res ;//把数据存在USART_RX_BUF的第USART_RX_STA位上
USART_RX_STA++;
if(USART_RX_STA>(USART_REC_LEN-1))USART_RX_STA=0; //数据超过长度,并且还没有到结束判断位(0x0d,0x0a),不合理的数据,舍弃重新从0开始接收
}
}
}
//其中USART_ReceiveData函数
uint16_t USART_ReceiveData(USART_TypeDef* USARTx)
{
/* Check the parameters */
assert_param(IS_USART_ALL_PERIPH(USARTx));
/* Receive Data */
return (uint16_t)(USARTx->DR & (uint16_t)0x01FF);
}
typedef struct
{
//SR(Status Register,状态寄存器):用于指示外设的当前状态,包括是否有数据可读、发送缓冲区是否空闲、是否发生错误等。
//常见标志位:TXE(发送缓冲区空)、RXNE(接收缓冲区非空)、TC(发送完成)等。
//RESERVED0:这是一个占位符,用于保持寄存器地址的对齐,
__IO uint16_t SR;
uint16_t RESERVED0;
//用于收发数据。写入此寄存器的数据会被发送,读取此寄存器可获取接收到的数据。
__IO uint16_t DR;
uint16_t RESERVED1;
__IO uint16_t BRR;
uint16_t RESERVED2;
__IO uint16_t CR1;
uint16_t RESERVED3;
__IO uint16_t CR2;
uint16_t RESERVED4;
__IO uint16_t CR3;
uint16_t RESERVED5;
__IO uint16_t GTPR;
uint16_t RESERVED6;
} USART_TypeDef;
SR寄存器中的TXE(Transmit Data Register Empty,发送数据寄存器空)标志并不表示接收数据完成,而是用于指示发送数据寄存器是否为空。
TXE = 1:发送数据寄存器空闲,可以写入新的数据进行发送。这表示上一次的数据已经从发送寄存器转移到发送移位寄存器,硬件准备好接受新的数据。
TXE = 0:发送数据寄存器仍然忙碌,不能写入新数据。需要等待该标志位被硬件置为1后再写数据。
判断接收数据是否完成,通常查看SR寄存器中的RXNE(Receive Data Register Not Empty,接收数据寄存器非空)标志位:
RXNE = 1:接收数据寄存器中有数据可以读取。说明接收完成,可以从数据寄存器(DR)中读取数据。RXNE = 0:接收数据寄存器为空,当前没有可读取的数据。
//自带的函数也是用SR标志位判断的
USART_GetFlagStatus(USART1, USART_FLAG_TC)
typedef enum {RESET = 0, SET = !RESET} FlagStatus, ITStatus;
FlagStatus USART_GetFlagStatus(USART_TypeDef* USARTx, uint16_t USART_FLAG)
{
FlagStatus bitstatus = RESET;
/* Check the parameters */
assert_param(IS_USART_ALL_PERIPH(USARTx));
assert_param(IS_USART_FLAG(USART_FLAG));
/* The CTS flag is not available for UART4 and UART5 */
if (USART_FLAG == USART_FLAG_CTS)
{
assert_param(IS_USART_123_PERIPH(USARTx));
}
if ((USARTx->SR & USART_FLAG) != (uint16_t)RESET)
{
bitstatus = SET;
}
else
{
bitstatus = RESET;
}
return bitstatus;
}
引脚等初始化设置
接口需要传入想要的波特率
void uart_init(u32 bound){
GPIO_InitTypeDef GPIO_InitStructure;
USART_InitTypeDef USART_InitStructure;
//用于管理和控制中断的硬件模块。NVIC 允许微控制器处理外部或内部的中断事件,并能够灵活地控制中断的优先级、响应和屏蔽等特性。
NVIC_InitTypeDef NVIC_InitStructure;
//用于控制 APB2 总线外设时钟 的函数
//原型:void RCC_APB2PeriphClockCmd(uint32_t RCC_APB2Periph, FunctionalState NewState);
//RCC_APB2Periph: 该参数指定要启用或禁用的 APB2 外设时钟。它是一个按位 OR(或运算)组合的掩码,表示不同外设的时钟。NewState: 该参数指定是否启用或禁用外设时钟。
//RCC_APB2Periph_USART1|RCC_APB2Periph_GPIOA 这段代码会同时启用 USART1 和 GPIOA 的时钟。
//使能时钟后,外设能够正常工作。例如启用定时器时钟后,定时器可以正常计数;启用 USART 时钟后,串口通信可以正常进行。
RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1|RCC_APB2Periph_GPIOA, ENABLE);
//USART1_TX GPIOA.9
//GPIO_Pin_9对应HAL(硬件抽象层)库中,表示 GPIO 引脚 9
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
//它使得 GPIO 引脚能够作为外部设备(如 USART、SPI、I2C 等)的 复用功能引脚,并且具有 推挽输出 的特性。
//表示 复用推挽输出模式(Alternate Function Push-Pull)
//Push-Pull 是一种输出模式,意味着引脚可以输出 高电平(Vcc)或 低电平(GND)。
//在推挽模式下,GPIO 引脚有两个驱动源(一个源驱动高电平,一个源驱动低电平),能够提供更强的驱动能力,推挽模式可以输出更稳定的电平。
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_Init(GPIOA, &GPIO_InitStructure);
//USART1_RX GPIOA.10
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10;
//指示该引脚为 浮空输入,即该引脚没有内部上拉电阻或下拉电阻的连接,处于“浮空”状态,电平状态由外部电路决定。
//引脚的电平状态完全依赖于外部电路的驱动。因此,浮空输入模式通常适用于那些由外部设备驱动的输入引脚,或者输入信号本身已经通过外部电路完成了电平拉升或拉低。
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
GPIO_Init(GPIOA, &GPIO_InitStructure);
//Usart1 NVIC
//USART1_IRQn 是一个 STM32 中的中断请求通道(IRQ channel),它代表 USART1 的中断请求
NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn;
//优先级设置抢占优先级和子优先级的设置
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=3 ;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 3;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);
//USART
USART_InitStructure.USART_BaudRate = bound;
USART_InitStructure.USART_WordLength = USART_WordLength_8b;
//停止位主要用来标记数据帧的结束,并为接收端提供时间来处理接收到的数据。根据协议的不同,停止位的数量可以为 1、1.5 或 2 个。
USART_InitStructure.USART_StopBits = USART_StopBits_1;
//配置中设置奇偶校验(Parity)选项
USART_InitStructure.USART_Parity = USART_Parity_No;、
//配置 硬件流控制 的选项
USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;
//配置中设置 串口通信模式 的一部分。它表示 USART 外设同时启用 接收(Rx) 和 发送(Tx) 模式,即该串口将能够接收和发送数据
USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;
USART_Init(USART1, &USART_InitStructure);
USART_ITConfig(USART1, USART_IT_RXNE, ENABLE);
USART_Cmd(USART1, ENABLE);
}
复用功能通常包括:
USART1_TX:USART1 传输线
USART1_RX:USART1 接收线
SPI1_SCK:SPI 时钟线
SPI1_MISO:SPI 主输入从输出线
I2C1_SCL:I2C 时钟线
I2C1_SDA:I2C 数据线
引脚可以配置为不同的输入模式
typedef enum
{
//名称: 模拟输入模式 (Analog Input)用途: 用于 ADC(模数转换器)等模拟功能。说明: 该模式下引脚连接到内部模拟电路,不受数字输入或输出逻辑控制。
GPIO_Mode_AIN = 0x0,
//名称: 浮空输入模式 (Input Floating)用途: 用作数字输入,但没有内部上下拉电阻。说明: 输入引脚未连接到任何固定的逻辑电平,易受外部噪声影响。适用场景: 外部电路已经提供了明确的电平(如按键有外部上下拉电阻)
GPIO_Mode_IN_FLOATING = 0x04,
//名称: 下拉输入模式 (Input Pull-Down)用途: 用作数字输入,内部下拉电阻将引脚默认拉低。说明: 防止浮空状态,未接信号时输入电平为低。适用场景: 信号线需要默认低电平(如按钮未按下时默认低电平)
GPIO_Mode_IPD = 0x28,
//名称: 上拉输入模式 (Input Pull-Up)用途: 用作数字输入,内部上拉电阻将引脚默认拉高。说明: 防止浮空状态,未接信号时输入电平为高。适用场景: 信号线需要默认高电平(如按钮未按下时默认高电平)
GPIO_Mode_IPU = 0x48,
//名称: 开漏输出模式 (Output Open-Drain)用途: GPIO 引脚作为开漏输出,可用于实现线与(逻辑与)或连接外部上拉电阻。说明:输出低电平时,电流通过 GPIO 引脚流向 GND。输出高电平时,GPIO 引脚处于高阻态,电平由外部上拉电阻决定。
//适用场景: I²C 总线通信等需要共享总线的场景。
GPIO_Mode_Out_OD = 0x14,
//名称: 推挽输出模式 (Output Push-Pull)用途: GPIO 引脚作为标准数字输出。说明:输出低电平时,GPIO 引脚直接连接到 GND。输出高电平时,GPIO 引脚直接连接到 VDD。
//适用场景: 控制 LED、驱动简单的数字逻辑电路等。
GPIO_Mode_Out_PP = 0x10,
//名称: 复用功能开漏输出 (Alternate Function Open-Drain)用途: 用于外设功能(如 I²C 或其他需要开漏特性的外设)。说明:与 GPIO_Mode_Out_OD 类似,但用于外设功能。可实现多设备连接的总线控制。
//适用场景: I²C 等需要复用开漏功能的场景。
GPIO_Mode_AF_OD = 0x1C,
//名称: 复用功能推挽输出 (Alternate Function Push-Pull)用途: 用于外设功能(如 USART、SPI 等需要推挽特性的外设)。说明:与 GPIO_Mode_Out_PP 类似,但用于外设功能。输出特性由外设控制。
//适用场景: 串口通信(USART)、SPI 通信等需要高速推挽输出的场景。
GPIO_Mode_AF_PP = 0x18
}GPIOMode_TypeDef;
上拉输入
下拉输入
浮空输入
STM32 的中断优先级是基于 4 位的系统,
优先级范围从 0 到 15。
抢占优先级和子优先级的位数是由系统的优先级分组方式决定的。
STM32 的中断优先级通常是通过 NVIC_InitTypeDef 结构中的两个字段设置的:
抢占优先级:决定了一个中断能否打断另一个中断。
子优先级:如果多个中断具有相同的抢占优先级,则由子优先级来决定执行的先后顺序。
抢占优先级,其值越小,优先级越高。例如,0 是最高的优先级,15 是最低的优先级。
硬件流控制?
硬件流控制是串行通信中的一种机制,用于管理数据流,以避免接收方因为接收缓冲区满而丢失数据。
它通过使用额外的控制信号(如 RTS 和 CTS)来动态控制数据传输的速率。
RTS(Request To Send):由发送方发出,用于告诉接收方准备好接收数据。
CTS(Clear To Send):由接收方发出,用于告诉发送方它可以发送数据。
当启用硬件流控制时,接收方可以使用 CTS 信号告诉发送方它的接收缓冲区已满,需要暂停发送数据。发送方可以使用 RTS 信号来请求接收方准备好接收数据。
SYSTEM_SUPPORT_OS 宏定义,
通常用于在嵌入式系统中决定是否启用操作系统支持。
它作为条件编译的标志,用于根据是否启用操作系统(OS)来选择性地编译代码。
在嵌入式系统中,许多项目需要区分 是否使用操作系统。
有时系统会运行 裸机(bare-metal) 应用程序,也就是不依赖于操作系统;
裸机环境(Bare-metal environment)是指不依赖操作系统的嵌入式编程环境。即程序直接与硬件交互,没有操作系统(如 Linux、FreeRTOS 或其他实时操作系统)来管理资源、调度任务和提供服务。在裸机编程中,程序员负责所有的硬件初始化、任务调度、内存管理和中断处理等。
而在其他情况下,可能会使用如 FreeRTOS、CMSIS-RTOS 或 RTX 这样的实时操作系统(RTOS)。
为了管理这些差异,开发人员通常使用 SYSTEM_SUPPORT_OS 这样的宏来切换不同的编译逻辑。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 【自荐】一款简洁、开源的在线白板工具 Drawnix
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY
· Docker 太简单,K8s 太复杂?w7panel 让容器管理更轻松!