目录

SPI

要求:

简介

通信特性:

移位示意图:

SPI时序基本单元

SPI时序

程序:

初始化

引脚配置

协议层

I2C

简介

​编辑物理层特性:

数据传输格式

硬件

I2C时序基本单元

I2C初始化

引脚配置

起始停止

发送一个字节

接收一个字节

发送,接收应答

I2C时序

USART

基本概念

硬件电路

关键配置

电平标准:

基本结构:

时序:

常见问题:

三种方式

轮询

初始化

轮询发送

轮询接收

轮询发送一个字节

中断:

初始化

文本接收数据包


SPI

要求:

掌握SPI的基本原理,信号线的作用,通信过程,4种模式

独立查阅并且理解任意SPI从设备的数据手册,提取其通信协议要求(命令,地址,格式,时序)

正确配置主设备的SPI外设(模式,速率,位序),编写软件实现与目标从设备的通信

简介

主设备控制时钟,从设备响应指令(一主多从,有几个从机就有几个SS片选信号)

SPI不支持多主机

核心信号线:SCK/CLK 主->从 同步时钟(数据位传输的节拍器)

                     MOSI/DI  主->从 主设备输出数据线

                     MISO/DO 从->主 从设备输出数据线

                     SS/CS     主->从 片选信号,低电平激活从机,多从机时,每个设备独立1根。

看连接的设备是从机还是主机,当是从机时DI就是MOSI

当多个从机时,当从机的片选信号SS为1高电平时,MISO置为高阻态,防止多个从机同时驱动 MISO 总线,避免总线冲突。

通信特性:

全双工(MOSI与MISO同时传输数据),同步串行

移位示意图:

  1. 当主机通过 波特率发生器 产生 SCK 时钟,每一个 SCK 脉冲驱动一次移位:
    • 主机移位寄存器:最高位(图中最左侧 1)通过 MOSI 发送给从机;同时,从机移位寄存器的最高位(图中最左侧 0)通过 MISO 回传给主机。
    • 下一个 SCK 脉冲:主机和从机的移位寄存器同时 右移一位,继续传输下一位数据……
  2. 一次完整传输
    当 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时,第一个边沿时都移出数据,第二个边沿时,都移入数据,依次循环  

  1. 模式 0(CPOL = 0,CPHA = 0):空闲时 SCK 为低电平,在 SCK 的上升沿采样数据,下降沿改变数据。
  2. 模式 1(CPOL = 0,CPHA = 1):空闲时 SCK 为低电平,在 SCK 的下降沿采样数据,上升沿改变数据。
  3. 模式 2(CPOL = 1,CPHA = 0):空闲时 SCK 为高电平,在 SCK 的下降沿采样数据,上升沿改变数据。
  4. 模式 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 次,逐位处理):
    1. 发送位MySPI_W_MOSI(ByteSend & (0x80 >> i))
      • 通过掩码从ByteSend中提取第(7-i)位(高位先行,符合文档中 “数据字节以最高有效位优先移位” 的规则),写入 MOSI 线。
    2. 时钟上升沿MySPI_W_SCK(1)
      • 拉高 SCK,W25Q64 在上升沿采样 MOSI 线的信号,接收主机发送的位(模式 0 的特性)。
    3. 接收位if (MySPI_R_MISO() == 1){ByteReceive |= (0x80 >> i);}
      • 读取 MISO 线的信号(W25Q64 发送的位),按高位先行规则存入ByteReceive的对应位。
    4. 时钟下降沿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)先发送。

通信流程通常包括:

  1. 起始信号(Start Condition)
  2. 从设备地址(7 位或 10 位)+ 读写位
  3. 应答位(ACK/NACK)
  4. 数据传输
  5. 停止信号(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

基本概念

  1. 异步串行通信,两根线(RX TX)
  2. 波特率:双方必须一致
  3. 数据格式:起始位(1位低电平)+数据位+可选校验位+停止位(1-2位高电平)低位先行

 校验位:

奇校验:数据位中 1 的个数 + 校验位 = 奇数;

偶校验:数据位中 1 的个数 + 校验位 = 偶数。

CRC(循环冗余校验)

硬件电路

关键配置

  1. 波特率计算:USARTDIV=f_ck/(16*baudrate)
  2. 常用模式:轮询,中断,DMA
  3. 中断类型:TX完成,RX收到数据,传输错误

常用模式

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

    电平标准:

    基本结构:

    时序:

    常见问题:

    1. 如何解决数据溢出? 及时读取数据或使用DMA
    2. 如何实现不定长接收?      IDLE中断+DMA
    3. 如何提高传输效率?  使用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,即可知道是否有新的完整数据包。

    Logo

    openvela 操作系统专为 AIoT 领域量身定制,以轻量化、标准兼容、安全性和高度可扩展性为核心特点。openvela 以其卓越的技术优势,已成为众多物联网设备和 AI 硬件的技术首选,涵盖了智能手表、运动手环、智能音箱、耳机、智能家居设备以及机器人等多个领域。

    更多推荐