当前位置: 首页 > article >正文

6.STM32之通信接口《精讲》之USART通信(PC串口与OLED交互)---多字节数据收发(数据包的模式:HEX数据包和文本数据包)

本节将串口收发HEX数据包(发送数据包很简单,主要还是关注一下如何接收数据包)


在这里给大家介绍一下状态机的思想


状态机模式(State Machine Patern)是一种用于描述对象的行为软件设计模式,属于行为型设计模式。在状态机模式中,对象的行为取决于其内部状态,并且在不同的状态下,对象可能会有不同的行为。状态机模式通常涉及定义一组状态以及状态之间的转换规则。

状态机模式结构与实现

状态机模式是一种行为设计模式,用于表示对象的不同状态及其行为变化。它通过将状态、行为和状态之间的转换独立封装,从而使代码更清晰、易扩展。以下是状态机模式的主要组成要素以及实现步骤:


结构组成

  1. 状态(State)
    表示对象所处的特定状态,每个状态定义了在该状态下的行为。

  2. 上下文(Context)
    包含状态机的核心对象,维护当前状态并在状态转换时更新状态。

  3. 转换(Transition)
    描述从一个状态到另一个状态的过程,由外部事件或条件触发。

  4. 动作(Action)
    在状态转换时可能需要执行的操作或行为。


实现步骤

1. 确定状态

识别系统中可能的状态,确保每个状态定义明确。例如,电梯可能有静止上升下降三种状态。

2. 定义状态接口

为所有状态创建一个统一接口或抽象类,定义公共行为。

3. 实现具体状态类

为每个状态实现具体行为,并包含可能的状态转换逻辑。

4. 设计状态转换规则

明确各状态之间的转换条件和触发事件,确保系统运行逻辑一致。

5. 实现上下文类

创建一个上下文类,负责管理当前状态并触发状态的切换。

6. 测试与优化

验证状态转换逻辑,确保所有可能的转换路径正常工作。
 

思路明确了,我们就行实验了!

根据上图,我们发送数据包,是比较容易的,仅需将数据发送出去即可,完全自主可控,但是接受一个数据包就没那抹容易了,这里我们引用到了状态机的思想!如第二张图所示!

接收模式下,我们依然采用的是中断查询模式,也就是说,每来一个字节的数据,我么就会进入中断处理函数对数据进行处理,也就是串口中断函数是逐字节处理接收到的数据的
 

需要注意的一点,一定要分清硬件完成了那些动作,比如,在中断函数中,由硬件完成了接收数据寄存器的自动置位!

#include "stm32f10x.h"                  // Device header
#include <stdio.h>
#include <stdarg.h>

uint8_t Serial_TxPacket[4];
uint8_t Serial_RxPacket[4];
uint8_t Serial_RxFlag;

void Serial_Init(void)
{
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1, ENABLE);
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
	
	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_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;
	USART_InitStructure.USART_WordLength = USART_WordLength_8b;
	USART_Init(USART1, &USART_InitStructure);
	
	USART_ITConfig(USART1, USART_IT_RXNE, ENABLE);
	
	NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
	
	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_Cmd(USART1, ENABLE);
}

void Serial_SendByte(uint8_t Byte)
{
	USART_SendData(USART1, Byte);
	while (USART_GetFlagStatus(USART1, USART_FLAG_TXE) == RESET);
}

void Serial_SendArray(uint8_t *Array, uint16_t Length)
{
	uint16_t i;
	for (i = 0; i < Length; i ++)
	{
		Serial_SendByte(Array[i]);
	}
}

void Serial_SendString(char *String)
{
	uint8_t i;
	for (i = 0; String[i] != '\0'; i ++)
	{
		Serial_SendByte(String[i]);
	}
}

uint32_t Serial_Pow(uint32_t X, uint32_t Y)
{
	uint32_t Result = 1;
	while (Y --)
	{
		Result *= X;
	}
	return Result;
}

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');
	}
}

int fputc(int ch, FILE *f)
{
	Serial_SendByte(ch);
	return ch;
}

void Serial_Printf(char *format, ...)
{
	char String[100];
	va_list arg;
	va_start(arg, format);
	vsprintf(String, format, arg);
	va_end(arg);
	Serial_SendString(String);
}

uint8_t Serial_GetRxFlag(void)
{
	if (Serial_RxFlag == 1)
	{
		Serial_RxFlag = 0;
		return 1;
	}
	return 0;
}


void Serial_SendPacket(void)
{
	Serial_SendByte(0xFF);
	Serial_SendArray(Serial_TxPacket,4);
	Serial_SendByte(0xFE);
}


void USART1_IRQHandler(void)
{
	static uint8_t RxState = 0;
	static uint8_t pRxData = 0;
	
	if (USART_GetITStatus(USART1, USART_IT_RXNE) == SET)
	{
		extern uint16_t RxData;
		RxData = USART_ReceiveData(USART1);
		
		if(RxState == 0)
		{
			if(RxData == 0xFF)
			{
				RxState = 1;
				pRxData = 0;
			}
			
		}
		else if (RxState == 1)
		{
			Serial_RxPacket[pRxData++] = RxData;
			if(pRxData >= 4)
			{
				RxState = 2;
			}
		}
		else if(RxState == 2)
		{
			if(RxData == 0xFE)
			{
				RxState = 0;
				Serial_RxFlag = 1;
			}
		}
//		USART_SendData(USART1,RxData);	
		USART_ClearITPendingBit(USART1, USART_IT_RXNE);
	}
}


















#include "stm32f10x.h"                  // Device header
#include "Delay.h"
#include "OLED.h"
#include "Serial.h"
#include "Key.h"
#include <stdio.h>

uint8_t RxData;
uint16_t KeyNum;


int main(void)
{
	OLED_Init();
	Serial_Init();
	Key_Init();
	OLED_ShowString(1,1,"TxData:");
	OLED_ShowString(3,1,"RxData:");
	
	
	
	Serial_TxPacket[0] = 0x01;
	Serial_TxPacket[1] = 0x02;
	Serial_TxPacket[2] = 0x03;
	Serial_TxPacket[3] = 0x04;
	Serial_SendPacket();
	OLED_ShowHexNum(2,1,Serial_TxPacket[0],2);
	OLED_ShowHexNum(2,4,Serial_TxPacket[1],2);
	OLED_ShowHexNum(2,7,Serial_TxPacket[2],2);
	OLED_ShowHexNum(2,10,Serial_TxPacket[3],2);
	

	

	
//	OLED_ShowHexNum(1,1,
	
	while (1)
	{
		KeyNum = Key_GetNum();	
		if(KeyNum == 1)
		{
			Serial_TxPacket[0] ++;
			Serial_TxPacket[1] ++;
			Serial_TxPacket[2] ++;
			Serial_TxPacket[3] ++;
			Serial_SendPacket();
			OLED_ShowHexNum(2,1,Serial_TxPacket[0],2);
			OLED_ShowHexNum(2,4,Serial_TxPacket[1],2);
			OLED_ShowHexNum(2,7,Serial_TxPacket[2],2);
			OLED_ShowHexNum(2,10,Serial_TxPacket[3],2);
		}
	
		if(Serial_GetRxFlag() == 1)
		{
			OLED_ShowHexNum(4,1,Serial_RxPacket[0],2);
			OLED_ShowHexNum(4,4,Serial_RxPacket[1],2);
			OLED_ShowHexNum(4,7,Serial_RxPacket[2],2);
			OLED_ShowHexNum(4,10,Serial_RxPacket[3],2);
					
		}		
		
		
		
	

			
		
	}
}

ChatGPT 说:

ChatGPT

USART 接收数据时以一个字节为单位触发中断,而不是多个字节,主要是由 USART 硬件设计的工作机制 决定的。以下是详细解释:


1. USART 的工作原理

(1) 数据接收与寄存器设计
  • USART 的核心包括一个接收数据寄存器(DR)和接收缓冲机制:
    • 当接收到一个完整的数据字节(例如 8 位或 9 位)后,数据会存储在寄存器 DR 中。
    • 同时,USART 硬件会自动设置状态寄存器中的 RXNE 标志位(Receive Data Register Not Empty),表示接收寄存器非空,有数据可以读取。
(2) 中断触发机制
  • RXNE 标志位被设置时,USART 会触发接收中断(USART_IT_RXNE)。
  • 每次中断只处理一个字节的数据,因为 RXNE 标志仅表示接收寄存器中有一个新数据字节

2. 为什么不是多个字节触发中断?

(1) 硬件设计限制
  • USART 的接收缓冲区通常只存储一个字节(或有限的 FIFO 深度),每次接收一个字节后会立即触发中断,等待软件处理。
  • 如果接收多个字节才触发一次中断,会带来如下问题:
    • 硬件需要额外的缓冲空间来存储多个字节。
    • 无法确定软件读取数据的时序,容易导致缓冲区溢出或数据丢失。
(2) 实时性要求
  • 通信协议中,很多数据是逐字节解析的,逐字节触发中断能确保数据被快速处理,减少延迟。
  • 多字节触发中断可能会增加处理的复杂性,特别是在数据速率较高的情况下。
(3) 单字节设计更通用
  • 单字节的中断设计可以兼容不同的数据帧格式(如 8 位或 9 位数据)。
  • 对于固定帧长或可变帧长的数据包,软件可以通过状态机或 DMA 实现多字节处理,而无需硬件支持。

3. 如果需要处理多个字节怎么办?

(1) 循环读取
  • 在中断服务程序(ISR)中,每次接收一个字节后,软件可以自行处理数据,或者将数据存入缓冲区,等待完整包到达后再解析。
(2) DMA 模式
  • 如果数据量较大且需要高效传输,可以启用 DMA(Direct Memory Access) 模式。
  • DMA 可以自动将多个字节从 DR 寄存器转移到内存,而不依赖 CPU 的逐字节读取。
  • 中断仅在传输完成或错误时触发,减少中断次数。

4. 优点总结:逐字节触发中断

  • 简单可靠:硬件设计简单,减少实现复杂性。
  • 实时性强:每接收到一个字节,立刻通知 CPU 处理,适合实时通信。
  • 灵活性高:软件可以根据实际需求,自由选择逐字节处理或多字节处理。
  • 适应协议多样性:逐字节接收更容易适配各种通信协议。

示例对比

逐字节中断处理(当前实现方式)
 

c

复制代码

void USART1_IRQHandler(void) { if (USART_GetITStatus(USART1, USART_IT_RXNE) == SET) { uint16_t RxData = USART_ReceiveData(USART1); // 读取一个字节 ProcessData(RxData); // 逐字节处理数据 USART_ClearITPendingBit(USART1, USART_IT_RXNE); // 清除中断标志 } }

  • 每次接收到一个字节,触发中断,实时处理。
DMA 多字节接收
 

c

复制代码

void USART1_DMA_IRQHandler(void) { if (DMA_GetITStatus(DMA1_Stream5, DMA_IT_TCIF5)) // 检查传输完成中断 { uint8_t buffer[64]; // 假设缓冲区大小为 64 字节 DMA_ReadData(buffer); // 一次性读取整个数据包 ProcessPacket(buffer); // 处理完整数据包 DMA_ClearITPendingBit(DMA1_Stream5, DMA_IT_TCIF5); // 清除中断标志 } }

  • 一次性处理多个字节,减少中断频率,但增加初始配置复杂性。

总结

USART 逐字节触发中断(RXNE)的设计:

  • 基于硬件缓冲能力和实时性需求,确保每个字节都能被及时处理。
  • 提供通用性和灵活性,适用于多种通信协议。
  • 如果需要高效传输大量数据,可以使用 DMA 模式或专用硬件 FIFO,但这些是更高级的扩展功能,单字节触发中断仍是最基础、可靠的实现方式。

4o

您说:

接收模式下,我们依然采用的是中断查询模式,也就是说,每来一个字节的数据,我么就会进入中断处理函数对数据进行处理,也就是串口中断函数是逐字节处理接收到的数据的 润色一下

ChatGPT 说:

ChatGPT

在接收模式下,我们采用的是中断查询模式。也就是说,每当接收到一个字节的数据时,就会触发中断,进入中断处理函数对该字节的数据进行处理。因此,串口中断函数会以逐字节的方式对接收到的数据进行处理。

4o

您说:

根据上文我说的对吗接收模式下,我们依然采用的是中断查询模式,也就是说,每来一个字节的数据,我么就会进入中断处理函数对数据进行处理,也就是串口中断函数是逐字节处理接收到的数据的

ChatGPT 说:

ChatGPT

是的,你的理解是正确的。

在接收模式下,采用中断查询模式时,每当串口接收到一个字节的数据,USART 硬件会设置接收标志位(RXNE),并触发中断。在中断处理函数中,对该字节的数据进行读取和处理。因此,串口中断函数确实是逐字节地处理接收到的数据的。这种机制保证了每个接收到的数据字节都能被及时处理,避免丢失数据。

4o

您说:

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); }

ChatGPT 说:

ChatGPT

这段代码是一个函数,用于通过 USART 外设发送数据。它的作用是将一个 8 位或 9 位的数据单元写入 USART 数据寄存器(DR),从而通过串口将数据发送出去。


代码详细解析

1. 函数功能
 

c

复制代码

void USART_SendData(USART_TypeDef* USARTx, uint16_t Data)

  • USARTx: 指定 USART 外设实例(如 USART1USART2 等),决定数据将通过哪个串口发送。
  • Data: 要发送的数据,类型为 uint16_t,可以是 8 位(标准数据模式)或 9 位(扩展数据模式)。
2. 参数检查
 

c

复制代码

assert_param(IS_USART_ALL_PERIPH(USARTx)); assert_param(IS_USART_DATA(Data));

  • IS_USART_ALL_PERIPH(USARTx):
    • 检查传入的 USARTx 参数是否是有效的 USART 外设实例。
  • IS_USART_DATA(Data):
    • 检查传入的 Data 是否在有效的范围内。
    • 如果是 8 位数据模式,Data 应在 0x000xFF 范围内。
    • 如果是 9 位数据模式,Data 应在 0x000x1FF 范围内。
3. 发送数据
 

c

复制代码

USARTx->DR = (Data & (uint16_t)0x01FF);

  • Data & 0x01FF:
    • 使用掩码 0x01FF 将数据限制在低 9 位范围内,确保高位无效数据不会影响发送。
    • 即使只使用 8 位数据模式,这种操作也不会有副作用,保证兼容性。
  • USARTx->DR:
    • 将数据写入 USART 的数据寄存器(DR)。
    • 硬件会自动将该数据发送到 USART 的发送缓冲区,再通过 TX 引脚发送出去。

函数特点和作用

  1. 逐个数据单元发送

    • 每次调用函数只发送一个 8 位或 9 位的数据单元。
  2. 适配 8 位或 9 位数据格式

    • 通过掩码处理,支持 8 位和 9 位两种数据格式,增强了通用性。
  3. 硬件自动发送

    • 写入 DR 后,USART 硬件会自动完成数据传输,程序无需额外操作。
  4. 简化上层调用

    • 调用者只需提供一个数据,函数内部完成发送寄存器的写入和有效性检查,简化了使用过程。

示例使用

发送 8 位数据
 

c

复制代码

USART_SendData(USART1, 0x41); // 发送 ASCII 字符 'A'(0x41)

发送 9 位数据
 

c

复制代码

USART_SendData(USART1, 0x1C1); // 发送 9 位数据,二进制 111000001

结合中断或查询模式

该函数通常结合以下两种方式使用:

  1. 中断模式
    • 当发送缓冲区空闲时(TXE 标志位被设置),调用该函数发送下一个数据。
  2. 查询模式
    • 主动轮询 TXE 标志位,确保发送缓冲区空闲后调用该函数发送数据。

整体流程

  1. 上层程序调用 USART_SendData
  2. 函数完成参数检查,确保外设和数据范围有效。
  3. 将数据写入 USARTx->DR
  4. 硬件 USART 自动完成数据发送流程。

小结

  • 该函数是 USART 数据发送的核心接口,通过简单的操作封装,屏蔽了底层寄存器操作细节。
  • 使用掩码 0x01FF 保证兼容 8 位和 9 位模式。
  • 它只负责将数据送入发送寄存器,而数据是否完全发送完毕由硬件和状态寄存器负责通知。

4o

您说:

if (USART_GetITStatus(USART1, USART_IT_RXNE) == SET)

ChatGPT 说:

ChatGPT

这行代码的作用是检查 USART1 的接收中断状态,具体来看:


1. 代码解析

函数:USART_GetITStatus(USART1, USART_IT_RXNE)
  • USART1
    • 指定需要检查状态的 USART 外设(这里是 USART1)。
  • USART_IT_RXNE
    • 表示接收数据寄存器非空中断(Receive Data Register Not Empty)。这个中断标志位表明接收寄存器中有新数据需要读取。
  • 返回值
    • 如果对应的中断标志位被设置,函数返回 SET
    • 如果未设置,返回 RESET

比较语句:== SET
  • 检查是否接收到数据:
    • 如果标志位被设置(SET),说明 USART1 接收了一个完整的数据字节,可以读取数据。
    • 如果标志位未设置(RESET),说明没有新数据到来。

2. 具体功能

 

c

复制代码

if (USART_GetITStatus(USART1, USART_IT_RXNE) == SET)

  • 这行代码用来判断是否接收到一个新的数据字节。
  • 当接收到新数据时,RXNE 标志位会被硬件设置,触发接收中断,程序进入接收数据处理逻辑。

3. USART_IT_RXNE 的意义

  • USART_IT_RXNE 对应接收缓冲区非空(RXNE)标志位。
    • 当一个字节被接收到并存储在 DR(数据寄存器)中时,RXNE 标志位会被硬件置位。
    • 程序需要及时读取 DR 中的数据,否则可能导致缓冲区溢出,丢失后续数据。

4. 适用场景

  • 中断模式
    • 通过检查 USART_IT_RXNE,确保进入中断时确实有新数据需要处理。
  • 轮询模式
    • 在主程序中循环检查 USART_IT_RXNE,只有在接收到数据时才执行读取操作。

5. 使用流程

中断处理函数示例
 

c

复制代码

void USART1_IRQHandler(void) { if (USART_GetITStatus(USART1, USART_IT_RXNE) == SET) // 判断是否接收到数据 { uint16_t data = USART_ReceiveData(USART1); // 读取接收到的数据 ProcessData(data); // 对数据进行处理 USART_ClearITPendingBit(USART1, USART_IT_RXNE); // 清除中断标志位 } }


6. 为何要这样检查?

  1. 确保中断触发的原因明确

    • 中断可能由多种标志位触发,例如 TXE(发送缓冲区空)或 RXNE(接收数据寄存器非空)。
    • 通过检查特定标志位,确保处理逻辑与触发原因一致。
  2. 避免误操作

    • 如果不检查标志位直接处理,可能导致错误读取无效数据或执行不必要的操作。

7. 总结

这段代码的核心作用是通过检查 RXNE 标志位,判断 USART 是否接收到了新数据,从而决定是否执行接收数据的处理逻辑。这是 USART 通信中断处理中必不可少的一步,用于保障数据接收的准确性和实时性。

4o

您说:

Cannot set breakpoint(s). -HW breakpoint: all breakpoints in use. -SW breakpoint: cannot write to memory.

ChatGPT 说:

ChatGPT

出现这种问题的原因是调试器无法正确设置断点,可能涉及硬件断点限制、软件断点无法写入内存等多种情况。以下是详细分析及解决方案:


可能的原因

  1. 硬件断点数量限制

    • 一些微控制器的调试器只支持有限数量的硬件断点(通常为 4 或 6 个)。
    • 如果当前已经设置的硬件断点超过了硬件支持的数量,就会导致无法再添加新的断点。
  2. 尝试使用软件断点失败

    • 软件断点依赖于将特定指令替换为中断指令来实现,因此需要调试器对目标内存的写权限。
    • 如果断点所在的内存区域是只读的(如 Flash 区域或只读寄存器),软件断点无法写入,导致错误。
  3. 调试器设置问题

    • 调试器配置可能未正确启用断点功能,或相关调试接口(如 SWD 或 JTAG)未正确连接。
  4. 内存保护机制

    • 某些微控制器可能启用了内存保护机制(如 MPU 或 Flash 写保护),阻止调试器修改代码存储区域,从而导致无法设置断点。
  5. 调试器或 IDE 软件问题

    • 调试器驱动或 IDE 的某些版本可能存在兼容性问题,导致断点功能异常。

解决方法

1. 检查硬件断点数量
  • 查看芯片或调试器手册,确认支持的最大硬件断点数。
  • 如果硬件断点已用尽:
    • 删除一些不必要的断点。
    • 尝试使用软件断点(适用于可写内存区域)。
2. 调整断点位置
  • 确保断点设置在 可写的内存区域,如 RAM 中的函数或数据段。
  • 避免在只读区域(如 Flash、ROM 或寄存器区域)设置软件断点。
3. 优化调试器设置
  • 检查 IDE 的调试设置,确保调试模式与芯片实际配置匹配(如 SWD 或 JTAG 模式)。
  • 在 Keil、IAR、STM32CubeIDE 等 IDE 中,可以尝试以下操作:
    • 清除所有断点,重新设置关键位置的断点。
    • 重置调试会话(终止调试后重新启动)。
    • 在调试器设置中,明确选择硬件或软件断点。
4. 禁用内存保护
  • 如果已启用 Flash 写保护或 MPU 配置,临时禁用相关保护设置以允许软件断点写入。
5. 升级调试工具
  • 确保使用的是调试器和 IDE 的最新版本,以避免已知的兼容性问题。
6. 使用日志调试替代
  • 如果无法添加断点,可以尝试通过串口打印日志或 LED 闪烁等方式记录代码执行情况,辅助调试问题。

示例:STM32CubeIDE 硬件断点调整

  1. 打开调试设置:

    • 右键项目 → Debug Configurations → Debugger → Breakpoints。
  2. 检查断点数量:

    • 查看硬件断点和软件断点的使用情况。
  3. 清除所有断点:

    • 点击清除按钮,然后仅添加关键位置的断点。
  4. 切换到 SWD 模式:

    • 确保调试接口为 SWD,并正确连接。

小结

这种问题通常是硬件断点数量超出限制或断点位置不可写导致的。通过合理分配硬件断点、优化断点位置、调整调试设置可以有效解决。如果问题仍然存在,可能需要检查调试工具的兼容性或通过其他方式辅助调试程序。

4o

您说:

中断函数 void USART1_IRQHandler(void) { static uint8_t RxState = 0; static uint8_t pRxData = 0; if (USART_GetITStatus(USART1, USART_IT_RXNE) == SET) { extern uint16_t RxData; RxData = USART_ReceiveData(USART1); if(RxState == 0) { if(RxData == 0xFF) { RxState = 1; pRxData = 0; } } else if (RxState == 1) { Serial_RxPacket[pRxData++] = RxData; if(pRxData >= 4) { RxState = 2; } } else if(RxState == 2) { if(RxData == 0xFE) { RxState = 0; Serial_RxFlag = 1; } } // USART_SendData(USART1,RxData); USART_ClearITPendingBit(USART1, USART_IT_RXNE); } }

ChatGPT 说:

ChatGPT

这段代码是一个 USART 接收中断处理函数 USART1_IRQHandler,其目的是在接收到数据时,按特定的协议解析数据包。让我们逐步解析这段代码的功能。


代码解析

函数头
 

c

复制代码

void USART1_IRQHandler(void)

  • 这是 USART1 的中断服务函数(IRQ Handler),每当 USART1 接收到数据或发生指定中断时,该函数会被自动调用。

静态变量
 

c

复制代码

static uint8_t RxState = 0; static uint8_t pRxData = 0;

  • RxState:跟踪接收的状态。RxState 的不同值表示数据接收过程的不同阶段。
    • 0:初始状态,等待数据包头。
    • 1:正在接收数据包内容。
    • 2:等待数据包的尾部。
  • **pRxData

4o mini

您说:

中断函数 void USART1_IRQHandler(void) { static uint8_t RxState = 0; static uint8_t pRxData = 0; if (USART_GetITStatus(USART1, USART_IT_RXNE) == SET) { extern uint16_t RxData; RxData = USART_ReceiveData(USART1); if(RxState == 0) { if(RxData == 0xFF) { RxState = 1; pRxData = 0; } } else if (RxState == 1) { Serial_RxPacket[pRxData++] = RxData; if(pRxData >= 4) { RxState = 2; } } else if(RxState == 2) { if(RxData == 0xFE) { RxState = 0; Serial_RxFlag = 1; } } // USART_SendData(USART1,RxData); USART_ClearITPendingBit(USART1, USART_IT_RXNE); } } 主函数 if(Serial_GetRxFlag() == 1) { OLED_ShowHexNum(4,1,Serial_RxPacket[0],2); OLED_ShowHexNum(4,4,Serial_RxPacket[1],2); OLED_ShowHexNum(4,7,Serial_RxPacket[2],2); OLED_ShowHexNum(4,10,Serial_RxPacket[3],2); } 这个函数是如何进行的,数据是如何变化的,是如何触发中断的,中断里的处理逻辑是如何进行的,最后如何显示的,

ChatGPT 说:

ChatGPT

这段代码展示了一个基于 USART1 接收数据的中断处理机制,并通过 OLED 显示接收到的数据。我们将分步骤分析数据的流动、触发中断的过程、接收逻辑以及数据如何显示。


主函数部分

 

c

复制代码

if(Serial_GetRxFlag() == 1) { OLED_ShowHexNum(4,1,Serial_RxPacket[0],2); OLED_ShowHexNum(4,4,Serial_RxPacket[1],2); OLED_ShowHexNum(4,7,Serial_RxPacket[2],2); OLED_ShowHexNum(4,10,Serial_RxPacket[3],2); }

  • Serial_GetRxFlag():这是一个函数(假设它返回 Serial_RxFlag 的值),检查接收标志是否为 1,表示数据包接收完毕并可以进行处理。
  • OLED_ShowHexNum():在 OLED 显示屏上显示数据包中的四个字节。每次调用会显示一个字节的数据,参数 4, 1, 4, 4 等是显示位置,2 是显示的字符宽度(每个字节以 2 位十六进制格式显示)。

中断处理函数

 

c

复制代码

void USART1_IRQHandler(void) { static uint8_t RxState = 0; static uint8_t pRxData = 0; if (USART_GetITStatus(USART1, USART_IT_RXNE) == SET) { extern uint16_t RxData; RxData = USART_ReceiveData(USART1); if (RxState == 0) { if (RxData == 0xFF) { RxState = 1; pRxData = 0; } } else if (RxState == 1) { Serial_RxPacket[pRxData++] = RxData; if (pRxData >= 4) { RxState = 2; } } else if (RxState == 2) { if (RxData == 0xFE) { RxState = 0; Serial_RxFlag = 1; } } USART_ClearITPendingBit(USART1, USART_IT_RXNE); } }

触发中断的过程

  1. USART 接收到数据

    • 当 USART1 接收到数据时,接收数据寄存器(DR)会被填充,并触发 USART_IT_RXNE 中断。
    • 进入中断服务函数 USART1_IRQHandler
  2. 检查接收中断标志

    • 使用 USART_GetITStatus(USART1, USART_IT_RXNE) 检查接收数据寄存器是否非空(RXNE)。如果设置了这个标志,说明有新的数据字节可读取。
  3. 读取接收到的数据

    • RxData = USART_ReceiveData(USART1) 从数据寄存器读取接收到的数据字节并存储到 RxData 变量中。

中断函数的逻辑

状态机解析数据
  1. 状态 RxState == 0:等待数据包的起始标志。

    • 如果接收到的数据是 0xFF,表示数据包的开始。
    • 更新状态 RxState = 1,准备接收数据内容。pRxData = 0 重新初始化数据包字节索引。
  2. 状态 RxState == 1:接收数据内容。

    • 将接收到的数据字节 RxData 存储到 Serial_RxPacket[pRxData++] 中。
    • 如果接收到 4 个字节(pRxData >= 4),表示数据包接收完成,更新状态 RxState = 2,等待数据包的结束标志。
  3. 状态 RxState == 2:等待数据包结束标志。

    • 如果接收到的数据是 0xFE,表示数据包的结束标志。
    • 更新状态 RxState = 0,恢复为初始状态。
    • 设置 Serial_RxFlag = 1,表示数据包已经接收完整,主程序可以处理这个数据包。
清除中断标志
  • USART_ClearITPendingBit(USART1, USART_IT_RXNE):清除接收中断标志位,准备接收下一个字节。

数据流动和显示

  1. 数据流动过程

    • USART 接收到的数据会依次进入中断函数进行处理。
    • 当接收到 0xFF,状态机进入接收数据模式,将接收到的数据存储到 Serial_RxPacket 数组中。
    • 一旦接收到 4 个数据字节,状态机切换到等待结束标志状态。
    • 当接收到 0xFE,标志着数据包接收完成,设置 Serial_RxFlag = 1
  2. 主函数如何显示数据

    • 主程序通过检查 Serial_RxFlag 来判断数据是否接收完整。
    • 一旦接收到完整的数据包,Serial_RxFlag 被置为 1,主程序通过 OLED_ShowHexNum() 函数将接收到的数据逐个显示在 OLED 屏幕上。

总结

  1. 数据接收流程

    • 数据包的接收是通过 USART 中断触发的,每接收到一个字节数据,中断函数就会被调用进行处理。
    • 采用状态机方式解析数据包,依次检测数据包的起始标志 0xFF、数据内容(最多 4 字节)和结束标志 0xFE
  2. 主程序的工作

    • 主程序通过检查 Serial_RxFlag 判断数据包是否接收完毕。
    • 如果数据包接收完成,则将 Serial_RxPacket 数组中的数据通过 OLED 屏幕显示。

这种方式使用中断和状态机来逐字节接收数据并解析,确保数据传输的可靠性和及时性,同时通过 OLED 显示实时反馈接收到的内容。

4o mini

您说:

USART 接收到数据: 当 USART1 接收到数据时,接收数据寄存器(DR)会被填充,并触发 USART_IT_RXNE 中断。 进入中断服务函数 USART1_IRQHandler。 详细解释一下这个中断为啥会被触发,触发条件是什么,一共触发了多少次,这个中断函数执行了多少次,每次中进行哪些操作,中断函数每次处理的数据是1个字节还是多个字节或者是多少位,

ChatGPT 说:

ChatGPT

详细解释:USART 接收到数据中断触发的过程

触发条件:

当 USART 接收到一个字节的数据时,会触发 USART_IT_RXNE 中断。具体的触发条件和过程如下:

  1. 接收数据寄存器 (DR) 填充

    • 在 USART 工作时,它会接收串行数据流。当接收到一个字节的数据时,硬件会将该字节存入 USART 的接收数据寄存器(DR)。
  2. 中断条件:

    • 一旦接收数据寄存器(DR)中的数据被填充完一个字节,且接收缓冲区不为空,USART 会自动设置 RXNE (Receive Data Register Not Empty) 中断标志。这表示接收到的新字节数据已经准备好供读取。
    • 如果启用了接收中断 USART_IT_RXNE,则会触发一个中断,并调用相应的中断处理函数 USART1_IRQHandler
  3. 中断的触发:

    • 每次接收到一个字节数据时,都会触发一次 USART_IT_RXNE 中断,进入 USART1_IRQHandler 函数。
    • 该中断会被触发 一次,每次触发都是接收到一个字节的数据。

中断函数执行时的步骤

 

c

复制代码

void USART1_IRQHandler(void) { static uint8_t RxState = 0; static uint8_t pRxData = 0; if (USART_GetITStatus(USART1, USART_IT_RXNE) == SET) { extern uint16_t RxData; RxData = USART_ReceiveData(USART1); // 读取接收到的数据 if (RxState == 0) { if (RxData == 0xFF) { RxState = 1; // 开始接收数据包 pRxData = 0; // 重置数据包索引 } } else if (RxState == 1) { Serial_RxPacket[pRxData++] = RxData; // 存储接收到的数据字节 if (pRxData >= 4) // 接收到 4 个字节 { RxState = 2; // 数据包接收完毕,进入尾部判断 } } else if (RxState == 2) { if (RxData == 0xFE) // 检查数据包的结束标志 { RxState = 0; // 恢复初始状态 Serial_RxFlag = 1; // 设置接收标志,通知主程序 } } USART_ClearITPendingBit(USART1, USART_IT_RXNE); // 清除中断标志 } }

中断函数的执行过程

每次 USART 接收到一个字节的数据,USART1_IRQHandler 会执行以下步骤:

  1. 检查中断标志

    • USART_GetITStatus(USART1, USART_IT_RXNE) == SET:检查 RXNE 中断标志,确保接收数据寄存器中有新数据。
  2. 读取接收到的数据

    • RxData = USART_ReceiveData(USART1):读取接收到的数据字节并存储到 RxData 中。这里是 16 位的 RxData 变量,它从 USARTx->DR 中提取接收到的字节。
  3. 状态机处理数据

    • RxState == 0:等待数据包的开始标志。
      • 如果接收到 0xFF,表示数据包开始,进入状态 1。
    • RxState == 1:接收数据包内容。
      • 接收到的数据字节会存储到 Serial_RxPacket[pRxData++] 中,直到接收 4 个字节为止。
    • RxState == 2:等待数据包的结束标志。
      • 如果接收到 0xFE,表示数据包的结束标志,完成数据包接收,并将 Serial_RxFlag 置为 1,通知主程序数据接收完成。
  4. 清除中断标志

    • USART_ClearITPendingBit(USART1, USART_IT_RXNE):清除接收中断标志,准备接收下一个字节。

每次中断处理的数据

  • 每次中断服务函数的执行会处理 一个字节 的数据。
  • 为什么是一个字节
    • USART_ReceiveData(USART1) 函数返回的是 16 位(uint16_t)的值,但该函数从接收数据寄存器(DR)中读取的数据实际上只包含一个字节(即 8 位)。所以,RxData 实际上存储的是一个字节数据。
    • 数据传输是逐字节的,每次中断的触发仅会处理接收到的 1 字节数据。

中断触发次数

  • 每次接收到 1 字节数据时都会触发一次中断。因此,如果我们收到 4 字节数据,则中断会被触发 4 次
  • 每次触发时,中断函数都会被执行一次,接收并处理 1 字节数据。

总结:

  1. 触发条件:每次接收到 1 字节数据时,RXNE 中断标志被设置,触发中断。
  2. 中断处理:每次中断都会读取 1 字节数据并进行状态机处理。状态机根据数据的起始标志、数据内容和结束标志来管理数据接收。
  3. 数据处理:每次中断处理 1 字节数据,直到数据包接收完整(4 个字节),然后通过设置 Serial_RxFlag 通知主程序数据包已完成。

中断触发与执行次数示例:

假设接收数据为:0xFF, 0x01, 0x02, 0x03, 0xFE

  1. 中断 1:接收到 0xFF,状态切换到 1,准备接收数据。
  2. 中断 2:接收到 0x01,存储到 Serial_RxPacket[0]
  3. 中断 3:接收到 0x02,存储到 Serial_RxPacket[1]
  4. 中断 4:接收到 0x03,存储到 Serial_RxPacket[2],达到 4 个字节,状态切换到 2。
  5. 中断 5:接收到 0xFE,状态恢复为 0,Serial_RxFlag 设置为 1,通知主程序。

每次中断都处理一个字节数据,并根据状态机的逻辑判断是否接收到完整的数据包。

4o mini

您说:

USART_GetITStatus(USART1, USART_IT_RXNE) == SET这个函数不是在中断函数里面吗?如果没进入中断,这个函数还会执行吗,还怎么触发中断,中断服务函数不是在触发之后才显示的吗?

ChatGPT 说:

ChatGPT

你提的问题非常好,涉及到了中断触发的本质以及中断处理函数的执行逻辑。我们来一步步详细解答你的疑问。


1. 为什么 USART_GetITStatus(USART1, USART_IT_RXNE) 会在中断服务函数里?

  • USART_GetITStatus 是一个 查询函数,用于检查 USART 的指定中断标志是否被置位。
  • 在中断服务函数(ISR)中,通常需要检查这个标志,确认是哪种中断事件触发了当前的中断(例如 RXNETXE 等),以便处理相应的事件。

2. 如果没有进入中断,这个函数还会执行吗?

不会。

  • 中断服务函数(USART1_IRQHandler)是由硬件在中断触发时调用的代码。如果没有触发中断,CPU 不会进入中断服务函数,自然也就不会执行 USART_GetITStatus

中断触发流程:

  1. 硬件(USART 外设)检测到接收事件,设置 RXNE 标志位。
  2. 如果 USART 的接收中断(USART_IT_RXNE)被启用,且 NVIC 的中断优先级允许,CPU 会跳转执行 USART1_IRQHandler
  3. CPU 进入中断服务函数后,才会执行 USART_GetITStatus,检查 RXNE 标志是否为 SET

总结
如果没有触发中断,CPU 根本不会跳转到中断服务函数(USART1_IRQHandler),所以 USART_GetITStatus 不会被调用。


3. 中断是如何触发的?

中断的触发是由硬件条件决定的,具体到 USART 的 RXNE 中断,以下条件必须满足:

  1. 接收数据寄存器(DR)非空:当 USART 接收到 1 字节数据并存储到 DR 时,会自动设置 RXNE 标志位。
  2. RXNE 中断被使能:必须通过调用函数(或直接操作寄存器)显式启用 USART_IT_RXNE 中断。一般通过 USART_ITConfig 函数实现,例如:
     

    c

    复制代码

    USART_ITConfig(USART1, USART_IT_RXNE, ENABLE);

  3. 全局中断被使能:CPU 必须允许中断(一般通过 __enable_irq() 或类似指令启用全局中断)。
  4. 中断优先级允许:NVIC 必须设置适当的中断优先级,确保当前中断能够被响应。

只有满足上述条件,硬件才会触发中断并执行中断服务函数。


4. 中断服务函数是怎么被执行的?

  • 当 USART 硬件触发中断时,CPU 暂停当前的主程序,保存上下文并跳转到中断向量表中指定的中断服务函数(例如 USART1_IRQHandler)。
  • 中断服务函数会根据硬件的状态标志(如 RXNE 标志位)判断事件类型,并执行相应处理。

因此,中断服务函数只会在中断触发后才被执行


5. 为什么需要在中断函数中检查 USART_GetITStatus

虽然中断是由 USART_IT_RXNE 触发的,但实际应用中,一个外设可能有多个中断源(例如发送完成中断、接收完成中断等)。
通过检查中断标志位(如 USART_GetITStatus),可以明确是哪一种事件导致了中断触发,确保对症处理。

例如:

  • 如果是接收事件触发中断,处理接收到的数据;
  • 如果是发送完成事件触发中断,准备发送下一个字节数据。

6. 中断处理函数执行了多少次?

每次接收到一个字节,都会触发一次 RXNE 中断。因此,中断处理函数的执行次数与接收字节数相等

假设接收 4 字节数据(例如 0xFF, 0x01, 0x02, 0xFE),中断服务函数会执行 4 次,每次处理一个字节。


7. 中断函数每次处理的数据是 1 字节还是多个字节?

中断函数每次处理 1 字节 数据。

这是因为:

  • USART 的接收缓冲区是逐字节接收的,每次接收一个字节后,RXNE 标志被置位。
  • 中断触发机制是基于 RXNE,所以每次中断只会对应 1 个字节的数据。

8. 总结:

  • USART_GetITStatus 只会在中断函数内执行,因为中断函数是中断触发后由硬件调用的。
  • 中断函数执行的前提是硬件事件满足中断触发条件,例如接收到 1 字节数据并设置 RXNE 标志位。
  • 每次中断函数执行仅处理 1 字节数据,函数执行次数与接收字节数相等。

4o

您说:

根据上文,假如我通过串口助手发送一个FF 11 22 33 44 FE的数据到单片机的RX上,根据上述代码,我的执行过程,

ChatGPT 说:

ChatGPT

根据你的代码逻辑和发送的数据 FF 11 22 33 44 FE,以下是完整的执行过程:


前提条件

  1. 串口助手通过串口发送数据到单片机的 RX 引脚。
  2. USART 硬件模块接收数据,满足触发中断的条件:
    • 每接收到 1 字节数据时,RXNE 标志位被置位。
    • 中断配置已经完成(USART_IT_RXNE 已经启用,全局中断已开启,NVIC 优先级设置正确)。
  3. 串口中断服务函数(USART1_IRQHandler)每次触发时处理 1 字节数据。

发送数据 FF 11 22 33 44 FE 的执行过程

第一字节: 0xFF
  1. 串口助手发送 0xFF,USART 接收完成,将其存入接收寄存器 DR
  2. RXNE 标志位被置位,触发中断。
  3. CPU 跳转到 USART1_IRQHandler
    • USART_ReceiveData(USART1) 读取接收数据寄存器,将 0xFF 存入 RxData
    • 当前 RxState0,代码检查数据是否为 0xFF
    • 条件成立,进入下一个状态:RxState = 1
    • 重置 pRxData0,准备存储接收数据。

第二字节: 0x11
  1. 串口助手发送 0x11,USART 接收完成,将其存入接收寄存器 DR
  2. RXNE 标志位被置位,触发中断。
  3. CPU 跳转到 USART1_IRQHandler
    • USART_ReceiveData(USART1) 读取接收数据寄存器,将 0x11 存入 RxData
    • 当前 RxState1,将 0x11 存入 Serial_RxPacket[0]
    • 自增 pRxData1,等待下一字节数据。

第三字节: 0x22
  1. 串口助手发送 0x22,USART 接收完成,将其存入接收寄存器 DR
  2. RXNE 标志位被置位,触发中断。
  3. CPU 跳转到 USART1_IRQHandler
    • USART_ReceiveData(USART1) 读取接收数据寄存器,将 0x22 存入 RxData
    • 当前 RxState1,将 0x22 存入 Serial_RxPacket[1]
    • 自增 pRxData2,等待下一字节数据。

第四字节: 0x33
  1. 串口助手发送 0x33,USART 接收完成,将其存入接收寄存器 DR.
  2. RXNE 标志位被置位,触发中断。
  3. CPU 跳转到 USART1_IRQHandler
    • USART_ReceiveData(USART1) 读取接收数据寄存器,将 0x33 存入 RxData
    • 当前 RxState1,将 0x33 存入 Serial_RxPacket[2]
    • 自增 pRxData3,等待下一字节数据。

第五字节: 0x44
  1. 串口助手发送 0x44,USART 接收完成,将其存入接收寄存器 DR
  2. RXNE 标志位被置位,触发中断。
  3. CPU 跳转到 USART1_IRQHandler
    • USART_ReceiveData(USART1) 读取接收数据寄存器,将 0x44 存入 RxData
    • 当前 RxState1,将 0x44 存入 Serial_RxPacket[3]
    • 自增 pRxData4,满足数据包长度条件,设置 RxState = 2

第六字节: 0xFE
  1. 串口助手发送 0xFE,USART 接收完成,将其存入接收寄存器 DR
  2. RXNE 标志位被置位,触发中断。
  3. CPU 跳转到 USART1_IRQHandler
    • USART_ReceiveData(USART1) 读取接收数据寄存器,将 0xFE 存入 RxData
    • 当前 RxState2,代码检查数据是否为 0xFE
    • 条件成立,重置 RxState = 0,表示完整数据包接收完成。
    • 设置标志 Serial_RxFlag = 1,通知主函数数据已就绪。

主函数处理

  1. 主函数中检测到 Serial_RxFlag == 1,进入数据处理流程:
     

    c

    复制代码

    if (Serial_GetRxFlag() == 1) { OLED_ShowHexNum(4, 1, Serial_RxPacket[0], 2); // 显示 0x11 OLED_ShowHexNum(4, 4, Serial_RxPacket[1], 2); // 显示 0x22 OLED_ShowHexNum(4, 7, Serial_RxPacket[2], 2); // 显示 0x33 OLED_ShowHexNum(4, 10, Serial_RxPacket[3], 2); // 显示 0x44 }

  2. OLED 显示的数据:
     

    复制代码

    第1列:11 第2列:22 第3列:33 第4列:44


总结:中断函数执行过程

  • 触发条件:每接收 1 字节,触发一次中断。
  • 执行次数:共触发 6 次中断,对应接收的 6 个字节。
  • 每次处理的数据:1 字节。
  • 逻辑变化
    1. 第一字节 0xFF 检测帧头,进入接收数据状态。
    2. 第二到第五字节存入接收缓冲区。
    3. 第六字节 0xFE 检测帧尾,标志数据包接收完成。
  • 主函数响应:处理接收的数据包,更新 OLED 显示。

这套流程体现了逐字节中断接收的完整逻辑,可靠地完成了数据包的接收与处理。

4o

您说:

串口助手发送 0xFF,USART 接收完成,将其存入接收寄存器 DR。 RXNE 标志位被置位,触发中断。这部分是不是硬件完成的

ChatGPT 说:

ChatGPT

是的,这部分确实是由硬件完成的。以下是硬件工作流程的详细说明:


硬件完成的部分

  1. 数据接收
    当串口助手发送 0xFF,数据通过串口线传输到单片机的 RX 引脚。
    USART 硬件模块在波特率匹配的情况下,将收到的信号解析为数字数据(0xFF)。

  2. 存入数据寄存器
    解析后的字节数据会自动存入 USART 的 接收数据寄存器USARTx->DR)。这是 USART 硬件的内置功能,软件不需要干预。

  3. 标志位置位
    数据寄存器 DR 中有新数据时,硬件会自动将 RXNE(Receive Not Empty)标志位 置位,表示接收数据寄存器不为空。这是由硬件自动处理的,不需要软件操作。

  4. 中断触发
    如果对应的中断使能位(USART_IT_RXNE)已经被设置,RXNE 标志置位时,硬件会自动触发中断请求。

    • CPU 检测到中断请求后,立即跳转到对应的中断服务函数 USART1_IRQHandler

软件需要处理的部分

从中断触发后,才是软件逻辑的介入:

  1. 进入中断服务函数 USART1_IRQHandler
  2. 检查 RXNE 标志位是否置位:
     

    c

    复制代码

    if (USART_GetITStatus(USART1, USART_IT_RXNE) == SET)

    • 如果标志位已置位,表明确实有数据到来。
  3. 读取数据寄存器:
     

    c

    复制代码

    RxData = USART_ReceiveData(USART1);

    • 通过 USART_ReceiveData 读取接收数据寄存器 DR 的内容。此时硬件会自动清除 RXNE 标志位。
  4. 根据状态机逻辑处理数据。

总结

  • 硬件完成部分
    1. 接收数据并存入 DR
    2. 自动置位 RXNE 标志位。
    3. 根据中断使能,自动触发中断。
  • 软件处理部分
    1. 中断触发后,由中断服务函数完成数据读取。
    2. 处理逻辑基于读取的数据执行相应操作。

因此,硬件的自动化机制保证了数据接收的实时性,而软件负责实现更复杂的逻辑。

下一节。文本数据包处理!!!!


http://www.kler.cn/a/409459.html

相关文章:

  • 12-表的约束
  • 机器学习周志华学习笔记-第5章<神经网络>
  • jupyter notebook的 markdown相关技巧
  • Java---反射机制
  • 【C】错误的变量定义导致sprintf()‌输出错误
  • Android mk/bp构建工具介绍
  • 五天SpringCloud计划——DAY1之mybatis-plus的使用
  • SpringBoot3中的性能提升介绍
  • Linux上安装单机版ElasticSearch6.8.1
  • 设计模式之创建模式篇
  • 签到送金币项目
  • 集合Queue、Deque、LinkedList、ArrayDeque、PriorityQueue详解
  • FastAPI和SQLModel结合的优点
  • 拉格朗日乘子(Lagrange Multiplier)是数学分析中用于解决带有约束条件的优化问题的一种重要方法,特别是SVM
  • selenium grid 远程webdriver添加上网代理
  • 7-401 平均值
  • Git 提交的相对引用
  • 【Linux】安装cuda
  • 实验7 JavaScript编程基础7.1密码验证
  • go-web项目通用脚手架
  • 每天100w次登录请求,8G内存该如何设置JVM参数?
  • 论文解析:EdgeToll:基于区块链的异构公共共享收费系统(2019,IEEE INFOCOM 会议);layer2 应对:频繁小额交易,无交易费
  • 数据库-MySQL-Dynamic-Datasource源码解析
  • 鸿蒙征文|鸿蒙心路旅程:始于杭研所集训营,升华于横店
  • 网络安全 - SQL Injection
  • 【Python】数据抓取失败解析