通信协议总结
目录
SPI
要求:
掌握SPI的基本原理,信号线的作用,通信过程,4种模式
独立查阅并且理解任意SPI从设备的数据手册,提取其通信协议要求(命令,地址,格式,时序)
正确配置主设备的SPI外设(模式,速率,位序),编写软件实现与目标从设备的通信
简介

主设备控制时钟,从设备响应指令(一主多从,有几个从机就有几个SS片选信号)
SPI不支持多主机
核心信号线:SCK/CLK 主->从 同步时钟(数据位传输的节拍器)
MOSI/DI 主->从 主设备输出数据线
MISO/DO 从->主 从设备输出数据线
SS/CS 主->从 片选信号,低电平激活从机,多从机时,每个设备独立1根。
看连接的设备是从机还是主机,当是从机时DI就是MOSI
当多个从机时,当从机的片选信号SS为1高电平时,MISO置为高阻态,防止多个从机同时驱动 MISO 总线,避免总线冲突。
通信特性:
全双工(MOSI与MISO同时传输数据),同步串行
移位示意图:


- 当主机通过 波特率发生器 产生 SCK 时钟,每一个 SCK 脉冲驱动一次移位:
- 主机移位寄存器:最高位(图中最左侧 1)通过 MOSI 发送给从机;同时,从机移位寄存器的最高位(图中最左侧 0)通过 MISO 回传给主机。
- 下一个 SCK 脉冲:主机和从机的移位寄存器同时 右移一位,继续传输下一位数据……
- 一次完整传输:
当 8 位数据全部移位完成后,主机移位寄存器将包含从机返回的数据(01010101),从机移位寄存器将包含主机发送的数据(10101010)。SPI 是 “全双工同步通信”,收发操作在同一时钟周期内同时完成。
SPI时序基本单元
1.起始终止

当SS片选信号选中时,SS将一直置低电平
2.交换一个字节(4钟模式)
时钟模式(CPOL&CPHA)
时钟极性(CPOL,Clock Polarity)和时钟相位(CPHA,Clock Phase)

当模式0时,当SS置低电平时,MOSI和MISO要移出数据,因为SCK第一个边沿要移入数据,又因为移入数据前,必须得移出数据,所以选择当SS置低电平时,移出数据。第二个边沿再移出数据,第三个边沿再移入数据
当模式1时,第一个边沿时都移出数据,第二个边沿时,都移入数据,依次循环

- 模式 0(CPOL = 0,CPHA = 0):空闲时 SCK 为低电平,在 SCK 的上升沿采样数据,下降沿改变数据。
- 模式 1(CPOL = 0,CPHA = 1):空闲时 SCK 为低电平,在 SCK 的下降沿采样数据,上升沿改变数据。
- 模式 2(CPOL = 1,CPHA = 0):空闲时 SCK 为高电平,在 SCK 的下降沿采样数据,上升沿改变数据。
- 模式 3(CPOL = 1,CPHA = 1):空闲时 SCK 为高电平,在 SCK 的上升沿采样数据,下降沿改变数据。
SPI的基础就是发送一个字节
SPI时序


因为MOSI和MISO是全双工,必须一收一发
因此,当从机无需返回有效数据时,必须填充 “无关数据”(如全 1 的 0xFF),本质是为了满足 SPI 全双工的硬件机制,避免 MISO 线处于不确定的高阻态(可能引入噪声或干扰总线)。


程序:
初始化

SS默认高电平
将 SPI 的 SS 引脚(片选) 置为高电平,释放从机(如 W25Q64)
SCK默认低电平
将 SPI 的 SCK 引脚(时钟) 置为低电平,让时钟线进入空闲状态
void MySPI_Init(void)
{
/*开启时钟*/
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); //开启GPIOA的时钟
/*GPIO初始化*/
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_4 | GPIO_Pin_5 | GPIO_Pin_7;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure); //将PA4、PA5和PA7引脚初始化为推挽输出
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure); //将PA6引脚初始化为上拉输入
/*设置默认电平*/
MySPI_W_SS(1); //SS默认高电平
MySPI_W_SCK(0); //SCK默认低电平
}
引脚配置
void MySPI_W_SS(uint8_t BitValue)
{
GPIO_WriteBit(GPIOA, GPIO_Pin_4, (BitAction)BitValue); //根据BitValue,设置SS引脚的电平
}
void MySPI_W_SCK(uint8_t BitValue)
{
GPIO_WriteBit(GPIOA, GPIO_Pin_5, (BitAction)BitValue); //根据BitValue,设置SCK引脚的电平
}
void MySPI_W_MOSI(uint8_t BitValue)
{
GPIO_WriteBit(GPIOA, GPIO_Pin_7, (BitAction)BitValue); //根据BitValue,设置MOSI引脚的电平,BitValue要实现非0即1的特性
}
uint8_t MySPI_R_MISO(void)
{
return GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_6); //读取MISO电平并返回
}
协议层
MySPI_Start:启动 SPI 通信
- 功能:通过拉低 SS 引脚(
MySPI_W_SS(0)),选中 W25Q64 从机,进入 SPI 通信状态。
MySPI_Stop:终止 SPI 通信
- 功能:通过拉高 SS 引脚(
MySPI_W_SS(1)),释放 W25Q64 从机,结束当前 SPI 通信。
MySPI_SwapByte:SPI 模式 0 下的字节交换(核心)
- 功能:按 SPI 模式 0 的时序,完成 1 字节数据的发送与接收(全双工),适配 W25Q64 的位传输规则。
- 时序细节(循环 8 次,逐位处理):
- 发送位:
MySPI_W_MOSI(ByteSend & (0x80 >> i))- 通过掩码从
ByteSend中提取第(7-i)位(高位先行,符合文档中 “数据字节以最高有效位优先移位” 的规则),写入 MOSI 线。
- 通过掩码从
- 时钟上升沿:
MySPI_W_SCK(1)- 拉高 SCK,W25Q64 在上升沿采样 MOSI 线的信号,接收主机发送的位(模式 0 的特性)。
- 接收位:
if (MySPI_R_MISO() == 1){ByteReceive |= (0x80 >> i);}- 读取 MISO 线的信号(W25Q64 发送的位),按高位先行规则存入
ByteReceive的对应位。
- 读取 MISO 线的信号(W25Q64 发送的位),按高位先行规则存入
- 时钟下降沿:
MySPI_W_SCK(0)- 拉低 SCK,W25Q64 在下降沿准备下一位数据(模式 0 的空闲状态)。
- 发送位:
开始
void MySPI_Start(void)
{
MySPI_W_SS(0); //拉低SS,开始时序
}
停止
void MySPI_Stop(void)
{
MySPI_W_SS(1); //拉高SS,终止时序
}
交换一个字节
uint8_t MySPI_SwapByte(uint8_t ByteSend)
{
uint8_t i, ByteReceive = 0x00; //定义接收的数据,并赋初值0x00,此处必须赋初值0x00,后面会用到
for (i = 0; i < 8; i ++) //循环8次,依次交换每一位数据
{
MySPI_W_MOSI(ByteSend & (0x80 >> i)); //使用掩码的方式取出ByteSend的指定一位数据并写入到MOSI线
MySPI_W_SCK(1); //拉高SCK,上升沿移出数据
if (MySPI_R_MISO() == 1){ByteReceive |= (0x80 >> i);} //读取MISO数据,并存储到Byte变量
//当MISO为1时,置变量指定位为1,当MISO为0时,不做处理,指定位为默认的初值0
MySPI_W_SCK(0); //拉低SCK,下降沿移入数据
}
return ByteReceive; //返回接收到的一个字节数据
}
I2C
简介

物理层特性:
I2C 协议仅需要两根线即可实现通信:
SDA(Serial Data Line):串行数据线
SCL(Serial Clock Line):串行时钟线
这两根线都需要通过上拉电阻连接到电源,因此当总线上的所有设备都不驱动总线时,总线会保持高电平状态。这种设计使得多个设备可以共享同一总线,实现多主多从的通信架构。
数据传输格式
I2C 通信以字节(8 位)为基本单位进行数据传输,每个字节后面会跟随一个应答位(ACK/NACK)。数据传输时,最高位(MSB)先发送。
通信流程通常包括:
- 起始信号(Start Condition)
- 从设备地址(7 位或 10 位)+ 读写位
- 应答位(ACK/NACK)
- 数据传输
- 停止信号(Stop Condition)
硬件

当SDA和SCL为高电平时,从机接收不到总线驱动,“总线未被驱动”
开漏输出的高电平状态,本质是 “总线未被任何设备驱动”,此时从机因检测不到有效信号而处于 “等待状态”,不会被误触发。这正是 I2C 协议将高电平定义为空闲状态的核心原因,与开漏输出的硬件特性完美适配。
I2C时序基本单元
I2C初始化

总线的 空闲状态 必须是 SDA 和 SCL 同时为高电平。
void MyI2C_Init(void)
{
/*开启时钟*/
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE); //开启GPIOA的时钟
/*GPIO初始化*/
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_OD;//开漏输出
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10 | GPIO_Pin_11;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOB, &GPIO_InitStructure); //将PA1和PA2引脚初始化为推挽输出
/*设置GPIO初始化后的默认电平*/
GPIO_SetBits(GPIOB, GPIO_Pin_10 | GPIO_Pin_11); //设置PA1和PA2引脚为高电平
}
引脚配置
MyI2C_W_SCL:控制 SCL 时钟线电平
- 功能:通过
GPIO_WriteBit函数,将GPIOB 的 Pin10(定义为 SCL 时钟线)设置为指定电平(BitValue为 0 时输出低电平,为 1 时输出高电平)。 - 关键:
- SCL 是 I2C 的时钟线,主机通过控制其高低电平跳变(时钟脉冲)同步数据传输节奏。
Delay_us(10)是为了满足 I2C 协议的时序要求(确保从机有足够时间识别电平变化),避免因电平切换过快导致数据传输错误。
MyI2C_W_SDA:控制 SDA 数据线电平
- 功能:通过
GPIO_WriteBit函数,将GPIOB 的 Pin11(定义为 SDA 数据线)设置为指定电平(BitValue为 0 时输出低电平,为 1 时输出高电平)。 - 关键:
- SDA 是 I2C 的数据线,用于传输实际数据(地址、命令、数据字节等)。
- 同样需要
Delay_us(10)保证电平稳定,符合 I2C 时序中对数据建立时间的要求。
MyI2C_R_SDA:读取 SDA 数据线电平
- 功能:通过
GPIO_ReadInputDataBit函数,读取GPIOB 的 Pin11(SDA 数据线)的当前电平,返回 0(低电平)或 1(高电平)。 - 关键:
- 用于接收从机返回的数据(如从机的应答信号 ACK、数据字节等)。
- 延时确保在电平稳定后再读取,避免读取到跳变过程中的不稳定信号。
void MyI2C_W_SCL(uint8_t BitValue)
{
GPIO_WriteBit(GPIOB,GPIO_Pin_10,(BitAction)BitValue);
Delay_us(10);
}
void MyI2C_W_SDA(uint8_t BitValue)
{
GPIO_WriteBit(GPIOB,GPIO_Pin_11,(BitAction)BitValue);
Delay_us(10);
}
uint8_t MyI2C_R_SDA(void)
{
uint8_t BitValue;
BitValue=GPIO_ReadInputDataBit(GPIOB,GPIO_Pin_11);
Delay_us(10);
return BitValue;
}
起始停止

起始信号的核心是 “当 SCL 为高电平时,SDA 出现下降沿(从高到低的跳变)”。要产生这个下降沿,必须先让 SDA 处于高电平状态,再在 SCL 保持高电平期间将 SDA 拉低。
停止信号(Stop):当 SCL 为高电平时,SDA 由低电平变为高电平(上升沿),表示通信结束。
// 起始信号:SCL高时,SDA产生下降沿
void MyI2C_Start(void)
{
MyI2C_W_SDA(1); // SDA 先置高(准备下降沿)
MyI2C_W_SCL(1); // SCL 置高(满足时序条件)
MyI2C_W_SDA(0); // SDA 置低(产生下降沿,触发起始)
MyI2C_W_SCL(0); // SCL 置低(进入传输等待状态)
}
// 停止信号:SCL高时,SDA产生上升沿
void MyI2C_Stop(void)
{
MyI2C_W_SDA(0); // SDA 先置低(准备上升沿)
MyI2C_W_SCL(1); // SCL 置高(满足时序条件)
MyI2C_W_SDA(1); // SDA 置高(产生上升沿,触发停止)
MyI2C_W_SCL(0); // SCL 置低(释放总线,可选但规范)
}
发送一个字节

从机只能被动读取
该函数通过循环逐位发送 8 位数据:先通过位运算将当前位输出到 SDA 线,再拉高 SCL 让从机读取,最后拉低 SCL 准备下一位,按 I2C 时序完成 1 字节传输。
void MyI2C_SendByte(uint8_t Byte)
{
uint8_t i;
for(i=0;i<8;i++)
{//传递8位数据,这样写方便
MyI2C_W_SDA(Byte & (0x80>>i));
MyI2C_W_SCL(1);
MyI2C_W_SCL(0);
}
}
接收一个字节

整个过程遵循 I2C 时序:SCL 高电平时读取 SDA 数据,低电平时准备下一位,确保数据按位(高位优先)正确接收。
uint8_t MyI2C_ReceiveByte(void)
{
uint8_t i,Byte=0x00;
MyI2C_W_SDA(1);//先释放
for(i=0;i<8;i++)
{
MyI2C_W_SCL(1);
if(MyI2C_R_SDA()==1) {Byte |=(0x80 >>i);}
MyI2C_W_SCL(0);
}
return Byte;
}
发送,接收应答

发送应答:主机给从机发送应答,告诉从机是不是要接着发。如果从机发送一个数据后,得到主机的应答,那么从机接着发送。如果没有得到应答,就停止发送数据
接收应答:发送一个字节后,紧跟着调用接收应答,用来判断从机有没有接收到数据。如果从机收到,那么主机释放SDA时,从机立刻把SDA拉下来,然后在SCL高电平期间,主机读取应答位,如果应答位为,那么从机确实收到。(主机发送一个字节后,就问有没有人收到,我要放手了,如果有人收到,你就把SDA拽下来,主机高电平读取数据,确实有人收到了)
MyI2C_SendAck(uint8_t AckByte):发送应答信号:每传输 1 字节数据后,接收方必须发送应答信号。若发送 “非应答”(1),通常表示数据传输结束或接收失败。
MyI2C_ReceiveAck(void):接收应答信号AckByte为 0 时表示接收成功,为 1 时表示接收失败或需终止传输。
void MyI2C_SendAck(uint8_t AckByte)
{
MyI2C_W_SDA(AckByte);
MyI2C_W_SCL(1);
MyI2C_W_SCL(0);
}
uint8_t MyI2C_ReceiveAck(void)
{
uint8_t i,AckByte=0x00;
MyI2C_W_SDA(1);//先释放
MyI2C_W_SCL(1);
AckByte=MyI2C_R_SDA();
MyI2C_W_SCL(0);
return AckByte;
}
I2C时序



USART
USART1 APB1
USART2 USART3 APB2
基本概念
- 异步串行通信,两根线(RX TX)
- 波特率:双方必须一致
- 数据格式:起始位(1位低电平)+数据位+可选校验位+停止位(1-2位高电平)低位先行

校验位:
奇校验:数据位中 1 的个数 + 校验位 = 奇数;
偶校验:数据位中 1 的个数 + 校验位 = 偶数。
CRC(循环冗余校验)

硬件电路

关键配置
- 波特率计算:USARTDIV=f_ck/(16*baudrate)
- 常用模式:轮询,中断,DMA
- 中断类型:TX完成,RX收到数据,传输错误
常用模式
- 轮询模式:通过不断查询发送 / 接收状态寄存器(如 TXE 发送空、RXNE 接收非空)判断操作是否完成,实现简单但占用 CPU 资源,适合数据量小、实时性要求低的场景。
- 中断模式:当发送完成、接收数据或发生错误时,硬件触发中断,CPU 仅在中断发生时处理数据,节省 CPU 资源,适合中等数据量和实时性要求较高的场景。
- DMA 模式:通过 DMA(直接存储器访问)控制器自动完成数据在内存与 UART 外设间的传输,无需 CPU 干预,适合大数据量传输(如文件、传感器数据流),效率最高。
轮询:CPU占用率100% 最高波特率 115200 实时性 差 实现简单 低速调试
中断:CPU占用率~30% 最高波特率 1Mbps 实时性 中 实现中等 中速设备
DMA:CPU占用率<5% 最高波特率 10Mbps 实时性 优 实现复杂 高数据流
电平标准:

基本结构:

时序:

常见问题:
- 如何解决数据溢出? 及时读取数据或使用DMA
- 如何实现不定长接收? IDLE中断+DMA
- 如何提高传输效率? 使用DMA减少CPU占用
三种方式
轮询
初始化
void Serial_Init(void)
{
/*开启时钟*/
RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1, ENABLE); //开启USART1的时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); //开启GPIOA的时钟
/*GPIO初始化*/
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;//将PA9引脚初始化为复用推挽输出
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_Init,配置USART1
/*USART使能*/
USART_Cmd(USART1, ENABLE); //使能USART1,串口开始运行
}
轮询发送
//轮询发送
void Serial_send(uint8_t *data,uint32_t len)
{
for(uint32_t i=0;i<len;i++)
{
//等待发送缓冲区为空
while(USART_GetFlagStatus(USART1,USART_FLAG_TXE)==RESET);
//发送一个字节
USART_SendData(USART1,data[i]);
}
//等待最后一个字节发送完成(TC完成标志)
while(USART_GetFlagStatus(USART1,USART_FLAG_TC)==RESET);
}
关键标志:USART_FLAG_TXE(Transmit Data Register Empty,发送数据寄存器空)。
当该标志为 RESET(0)时,表示 UART 的发送数据寄存器中还有未发送的数据,此时不能写入新数据。
当该标志为 SET(1)时,表示发送数据寄存器已空,硬件已将其中的数据转移到移位寄存器进行发送,此时可以写入下一个字节。
等待机制:while 循环会一直阻塞,直到 TXE 标志变为 SET,确保上一个字节已被硬件取走,避免新数据覆盖未发送的数据。
轮询接收
//轮询接收
void Serial_receive(uint8_t *data,uint32_t len)
{
for(uint32_t i=0;i<len;i++)
{
//等待接收缓冲区为空
while(USART_GetFlagStatus(USART1,USART_FLAG_RXNE)==RESET);
data[i]=USART_ReceiveData(USART1);
}
}
轮询模式的核心是 **“软件查询硬件状态 → 硬件通过标志位反馈状态 → 软件根据状态执行操作”** 的循环:
- 发送:查询 TXE → 硬件准备好 → 写入数据 → 重复直到完成 → 确认 TC;
- 接收:查询 RXNE → 硬件收到数据 → 读取数据 → 重复直到完成。
轮询发送一个字节

中断:
初始化
void Serial_Init(void)
{
/*开启时钟*/
RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1, ENABLE); //开启USART1的时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); //开启GPIOA的时钟
/*GPIO初始化*/
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;//将PA9引脚初始化为复用推挽输出
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_Init,配置USART1
USART_ITConfig(USART1,USART_IT_RXNE,ENABLE);//开启串口接收数据的中断
// USART_ITConfig(USART1,USART_IT_TXE,ENABLE);//开启串口发送数据的中断
NVIC_InitTypeDef NVIC_InitStructure;
NVIC_InitStructure.NVIC_IRQChannel=USART1_IRQn;
NVIC_InitStructure.NVIC_IRQChannelCmd=ENABLE;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=1;
NVIC_InitStructure.NVIC_IRQChannelSubPriority=1;
NVIC_Init(&NVIC_InitStructure);
/*USART使能*/
USART_Cmd(USART1, ENABLE); //使能USART1,串口开始运行
}
这样的配置支持基本的串口通信,且通过中断中断可以高效处理接收数据。
文本接收数据包

//中断(处理中断接收)
/*
状态 0:等待包头(起始标志)
状态 1:接收有效数据,同时检测第一个包尾
状态 2:检测第二个包尾,确认数据包结束
*/
void USART1_IRQHandler(void)
{
static uint8_t RxState = 0; //定义表示当前状态机状态的静态变量
static uint8_t pRxPacket = 0; //定义表示当前接收数据位置的静态变量
if(USART_GetITStatus(USART1,USART_IT_RXNE)!=RESET)
{
//读取数据
uint8_t Rxdata=USART_ReceiveData(USART1);
//判断状态机的状态
if(RxState==0)
{
//数据包报头
if(Rxdata=='@' && Serial_RxFlag==0)
{
RxState=1;//下一状态,接收数据包
pRxPacket = 0; // 重置缓冲区索引(准备存新数据)
}
}
else if(RxState==1)
{
if(Rxdata=='\r') // 收到第一个包尾 "\r"
{
RxState=2;//进入状态2,确定包结束
}
else
{
// 增加缓冲区边界判断,避免越界(重要补充)
if(pRxPacket < 99) // 预留1位给结束符'\0'
{
Serial_RxPacket[pRxPacket] = Rxdata;
pRxPacket++;
}
else
{
// 缓冲区满时复位,避免数据溢出
RxState = 0;
pRxPacket = 0;
}
}
}
else if(RxState==2)
{
if(Rxdata=='\n') // 收到第二个包尾"\n"
{
RxState=0;
Serial_RxPacket[pRxPacket] = '\0'; // 加字符串结束符(方便处理字符串)
Serial_RxFlag = 1; // 置位完成标志,通知主程序"有新包"
}
else // 未收到正确的第二个包尾,视为无效数据,复位状态机
{
RxState = 0;
pRxPacket = 0;
}
}
USART_ClearITPendingBit(USART1,USART_IT_RXNE);//清除中断标志
}
通过 “状态机” 机制,能准确识别符合 @xxx\r\n 格式的数据包,过滤无效数据,适合串口通信中可靠接收命令或数据。主程序只需检查 Serial_RxFlag,即可知道是否有新的完整数据包。
openvela 操作系统专为 AIoT 领域量身定制,以轻量化、标准兼容、安全性和高度可扩展性为核心特点。openvela 以其卓越的技术优势,已成为众多物联网设备和 AI 硬件的技术首选,涵盖了智能手表、运动手环、智能音箱、耳机、智能家居设备以及机器人等多个领域。
更多推荐
所有评论(0)