STM32ZET6-USART使用

news2024/11/25 17:36:35

一、原理说明

STM32自带通讯接口

 通讯目的

通信方式:

全双工:通信时可以双方同时通信。

半双工:通信时同一时间只能一个设备发送数据,其他设备接收。

单工:只能一个设备发送到另一个设备,例如USART只有TX线。

时钟类型:

异步:约定传输速率,例如波特率相同,约定起始信号(和结束信号、帧头帧尾等进行约束和校验)进行传输。

优点是节省了硬件资源,缺点是在传输过程中停止会传输失败,不能中途暂停,而且难以用软件模拟,只能使用硬件传输。

同步:有一根单独的时钟线,通过时钟信号控制传输时的速率。

优点是在传输过程中可以中断(停止时钟信号即可停止),停止后可以继续传输,并且可以使用软件模拟时钟信号和通信,无硬件资源时可以牺牲引脚来使用。

传输类型:

单端:通过信号和GND的电平差来区分高低电平,需要把通信双方的GND接到一起,否则容易受到干扰。

差分:通过两根线的差分电平来判断高低电平通信,抗干扰信号更强,传输距离更远。

(USB尽量共地)

设备拓扑类型:

点对点:设备1-1通信,可能需要寻址,以确定

点对多:设备1-n通信

多对多:设备n-n通信

串口协议:

CH340可以将串口转换为USB协议,可以直接插在电脑上。

陀螺仪传感器可以测量角速度加速度等姿态参数,可以接串口和I2C。 

蓝牙串口模块,可以和蓝牙设备互联,实现手机遥控单片机的功能。

串口接线图:

电平标准:

串口协议的软件定义:

RB8和TB8是奇偶校验位,可选择是否使用。一般需要校验位,则选择9bit数据,不需要校验位则选择8bit。

波特率:每秒传输码元的个数,可能每个码元包含信息量不止1bit,单位为码元/s,或者为bund。

比特率:每秒传输bit的速率。单位bit/s,或者bps。在二进制调制的情况下一个码元就是一个bit。

奇偶校验:

奇校验表示,包括校验位和数据位,发送的1为奇数个。例如发送0x0F,则发送 111100001

串口波形实例:

起始位为低电平,默认线上高电平,结束位为高电平(方便区分两个连续数据包)。

 STM32的USART外设:

UART和USART类似,但是只支持时钟输出,不支持时钟输入。

同步模式是支持CLK时钟输出、硬件流控制表示传输时可通过从设备反馈 来控制主设备的发送 防止从设备处理慢导致数据丢失。DMA是串口支持DMA数据转运。STM32的串口协议也可支持智能卡、IrDA(红外通信)、LIN(局域网)等设备。

STM32-USART内部原理:

 发送数据寄存器TDR和接收数据寄存器RDR使用同一块存储区域。nRTS高速主机当前是否可以接收数据。nCTS用于接收从机nRTS信号的引脚,判断从机当前是否可以接收。n代表低电平有效。串口使用SCLK可以兼容SPI。若不知道对方波特率,可以通过SCLK计算得到并传出输出。

唤醒单元用来实现串口挂载多设备,可以给串口分配地址。总线上对应发送的地址设备会进行通信。

TXE发送寄存器空。

RXNE接收寄存器非空。

USART波形及配置: 

 一般配置9bit字长,都会使用校验位。若不是用校验位,则第九位为载荷bit。

选择8bit字长最好无校验位,若有校验位,那么有效载荷7bit。每次发送不够1byte。

 停止位高电平,一般用1bit。

USART结构图:

  USART起始位侦测原理:

 波特率分频(分频后还有16倍分频):

 例如USART1波特率9600,那么9600 = 72000000/(16*DIV);DIV = 468.785。放入寄存器为111010100.11

USB转串口模块的内部电路图:

不同数据模式:

二、实例

1、串口发送,OLED接收

接线图:

 引脚定义:

重构printf函数:

printf输出汉字需要增加--no-multibyte-chars配置,防止中文乱码

--no-multibyte-chars

 

接收时需要用相同的编码方式 

 

若utf-8兼容性不好,可更换GB2312编码方式。串口接收汉字,需要收发的两方编码方式相同。

main.c 

#include "stdio.h"
#include "stm32f10x.h"                  // Device header
#include "Delay.h"
#include "OLED.h"
#include "Serial.h"
uint8_t SendDataA[3] = {0x01,0x02,0x03};
char *Char = "Hello Word!!!";
int32_t zNum = 23213;
int main(void){
	OLED_Init();
	Serial_Init();
	OLED_ShowString(1,1,"AD0:0000");
	OLED_ShowString(2,1,"AD1:0000");
	while(1){
		Delay_ms(1000);
		//Serial_SendByte(0x41);//发送十六进制0x41
		//Serial_SendByte('A');//发送字符A,同0x41.逻辑为发送A-底层0x41发送-串口底层接收0x41-可现实字符或十六进制数
		//Serial_SendByte(SendDataA[0]);//发送1byte数据
		//Serial_SendByteArray(&SendDataA[0],3);//发送指定长度字节数组
		//Serial_SendString(Char);发送字符串
		//Serial_SendSignedNum(zNum,4);
		
		//printf打印串口方法1
		//printf("Num = %d\r\n" , 666);// \r\n为字符换行,printf通过重定向打印串口
		
		//printf打印串口方法2
		//不使用重定向格式化字符串来串口打印,所有的串口都有可以用
		//char String[100];
		//sprintf(String,"Num = %d\r\n" , 666);//使用sprintf进行格式化,然后使用串口进行输出
		//Serial_SendString(String);//打印Num = 666\r\n
		
		//printf打印串口方法3
		//Serial_printf("Num = %d\r\n" , 666);
		
		//发送汉字字符串-接收需要和发送时相同的编码方式
		Serial_printf("你好!");
	}
	return 0;
}

Serial.h

#ifndef __SERIAL_H
#define __SERIAL_H
#include "stm32f10x.h"                  // Device header
#include "stdio.h"
void Serial_Init(void);
void Serial_SendByte(uint8_t Byte);
void Serial_SendByteArray(uint8_t *ByteArray,uint16_t length);
void Serial_SendString(char *Char);
void Serial_SendSignedNum(int32_t Num,uint16_t length);
void Serial_SendNum(int32_t Num,uint16_t length);
void Serial_printf(char *format,...);
#endif

Serial.c

#include "stm32f10x.h"                  // Device header
#include "math.h"
#include "stdio.h"
#include "stdarg.h"
/**
  * @brief 初始化USART1,通过USART1进行收发
  * @param  
  *     @arg 
  * @param  
  *     @arg 
  * @retval None
  */
void Serial_Init(void){
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1,ENABLE);
	
	GPIO_InitTypeDef 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);
	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);
	
	USART_InitTypeDef USART_InitStructure;
	USART_InitStructure.USART_BaudRate = 9600;
	USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;//硬件流控制
	USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;
	USART_InitStructure.USART_Parity = USART_Parity_No;
	USART_InitStructure.USART_WordLength = USART_WordLength_8b;
	USART_InitStructure.USART_StopBits = USART_StopBits_1;
	USART_Init(USART1,&USART_InitStructure);
	
	USART_Cmd(USART1,ENABLE);
}
/**
  * @brief 串口发送1byte
  * @param  
  *     @arg 
  * @param  
  *     @arg 
  * @retval None
  */
void Serial_SendByte(uint8_t Byte){
	USART_SendData(USART1,Byte);
	while(USART_GetFlagStatus(USART1,USART_FLAG_TXE) == RESET);//发送缓冲不为空则等待
}

/**
  * @brief 发送字节数组
  * @param  数组指针
  *     @arg 
  * @param  长度
  *     @arg 
  * @retval None
  */
void Serial_SendByteArray(uint8_t *ByteArray,uint16_t length){
	for(int i = 0 ; i<length ;  i++){
		Serial_SendByte(ByteArray[i]);
	}
}

/**
  * @brief 发送一个字符串
  * @param  
  *     @arg 
  * @param  
  *     @arg 
  * @retval None
  */
void Serial_SendString(char *String){
	int i = 0;
	for(i=0;String[i]!='\0';i++){
		Serial_SendByte(String[i]);
	}
}

/**
  * @brief 以字符形式发送有符号数字
  * @param  
  *     @arg 
  * @param  
  *     @arg 
  * @retval None
  */
void Serial_SendSignedNum(int32_t Num,uint16_t length){
	if(Num>=0){
		Serial_SendByte(0x2B);//加号
	}else{
		Serial_SendByte(0x2D);//减号
		Num = -Num;
	}
	//Num = abs(Num); abs处理int数据所以不适用
	for(int i=1;i<=length;i++){
		Serial_SendByte((Num/(uint32_t)pow(10,length-i)%10)+'0');
	}
}

/**
  * @brief 以字符形式发送无符号数字
  * @param  
  *     @arg 
  * @param  
  *     @arg 
  * @retval None
  */
void Serial_SendNum(int32_t Num,uint16_t length){
	for(int i=1;i<=length;i++){
		Serial_SendByte((Num/(uint32_t)pow(10,length-i)%10)+'0');
	}
}

/**
  * @brief 重定向fputc函数,到串口1即Serial_SendByte函数,使用micor提供的精简库
  *     @arg fputc是printf函数的底层,printf打印时就是调用fputc单个打印,通过重定向可以在串口打印
  * @param  
  *     @arg 
  * @param  
  *     @arg 
  * @retval None
  */
int fputc(int ch,FILE *f)
{
	Serial_SendByte(ch);
	return ch;
}


/**
  * @brief 重构v sprintf,完成printf打印串口
  *     @arg sprintf只能接收直接写的参数,vsprintf可以接收封装格式参数
  * @param  format:接收格式化字符串
  *     @arg 
  * @param  ...:可变参数列表,用来接收剩余参数
  *     @arg 
  * @retval None
  */
void Serial_printf(char *format,...){
	char String[100];
	va_list arg;//定义一个参数列表变量
	va_start(arg,format);//从format位置开始接收参数列表,放在arg中
	vsprintf(String,format,arg);//将打印参数arg格式化为format格式字符串,最好放入打印字符串变量String
	va_end(arg);//释放参数表
	Serial_SendString(String);//发送格式化后的字符串
}

2、串口发送+接收,串口收到数据后将数据返回

main.c

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

int main(void){
	OLED_Init();
	Serial_Init();
	OLED_ShowString(1,1,"Rx:00");
	while(1){
		Serial1Rx();
		OLED_ShowHexNum(1,4,Serial1_RxData,2);//显示1byte接收的十六进制数
	}
	return 0;
}

Serial.c

#include "stm32f10x.h"                  // Device header
#include "math.h"
#include "stdio.h"
#include "stdarg.h"
uint8_t Serial1_RxData;
uint8_t Serial1_Flag;
/**
  * @brief 初始化USART1,通过USART1进行收发
  * @param  
  *     @arg 
  * @param  
  *     @arg 
  * @retval None
  */
void Serial_Init(void){
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1,ENABLE);
	
	GPIO_InitTypeDef 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);
	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);
	
	USART_InitTypeDef USART_InitStructure;
	USART_InitStructure.USART_BaudRate = 9600;
	USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;//硬件流控制
	USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;
	USART_InitStructure.USART_Parity = USART_Parity_No;
	USART_InitStructure.USART_WordLength = USART_WordLength_8b;
	USART_InitStructure.USART_StopBits = USART_StopBits_1;
	USART_Init(USART1,&USART_InitStructure);
	
	USART_ITConfig(USART1,USART_IT_RXNE,ENABLE);
	
	NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
	
	//NVIC配置-中断优先级和中断对应通道使能 stm32f10x.h
	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);
}
/**
  * @brief 串口发送1byte
  * @param  
  *     @arg 
  * @param  
  *     @arg 
  * @retval None
  */
void Serial_SendByte(uint8_t Byte){
	USART_SendData(USART1,Byte);
	while(USART_GetFlagStatus(USART1,USART_FLAG_TXE) == RESET);//发送缓冲不为空则等待
}

/**
  * @brief USART1中断函数,接收的数据返回
  * @param  
  *     @arg 
  * @param  
  *     @arg 
  * @retval None	
  *		@arg 中断函数名在startup_stm32f10x_md.s中
  */
void USART1_IRQHandler(void){
	if(USART_GetITStatus(USART1,USART_IT_RXNE) == SET){
		Serial1_RxData = USART_ReceiveData(USART1);
		Serial1_Flag = 1;
		USART_ClearITPendingBit(USART1,USART_IT_RXNE);
	}
}

/**
  * @brief 配合USART1_IRQHandler串口1中断,中断后处理行为
  * @param  
  *     @arg 
  * @param  
  *     @arg 
  * @retval None
  */
void Serial1Rx(void){
	if(Serial1_Flag){
		Serial_SendByte(Serial1_RxData);
		Serial1_Flag = 0;
	}
}




/**
  * @brief 发送字节数组
  * @param  数组指针
  *     @arg 
  * @param  长度
  *     @arg 
  * @retval None
  */
void Serial_SendByteArray(uint8_t *ByteArray,uint16_t length){
	for(int i = 0 ; i<length ;  i++){
		Serial_SendByte(ByteArray[i]);
	}
}

/**
  * @brief 发送一个字符串
  * @param  
  *     @arg 
  * @param  
  *     @arg 
  * @retval None
  */
void Serial_SendString(char *String){
	int i = 0;
	for(i=0;String[i]!='\0';i++){
		Serial_SendByte(String[i]);
	}
}

/**
  * @brief 以字符形式发送有符号数字
  * @param  
  *     @arg 
  * @param  
  *     @arg 
  * @retval None
  */
void Serial_SendSignedNum(int32_t Num,uint16_t length){
	if(Num>=0){
		Serial_SendByte(0x2B);//加号
	}else{
		Serial_SendByte(0x2D);//减号
		Num = -Num;
	}
	//Num = abs(Num); abs处理int数据所以不适用
	for(int i=1;i<=length;i++){
		Serial_SendByte((Num/(uint32_t)pow(10,length-i)%10)+'0');
	}
}

/**
  * @brief 以字符形式发送无符号数字
  * @param  
  *     @arg 
  * @param  
  *     @arg 
  * @retval None
  */
void Serial_SendNum(int32_t Num,uint16_t length){
	for(int i=1;i<=length;i++){
		Serial_SendByte((Num/(uint32_t)pow(10,length-i)%10)+'0');
	}
}

/**
  * @brief 重定向fputc函数,到串口1即Serial_SendByte函数,使用micor提供的精简库
  *     @arg fputc是printf函数的底层,printf打印时就是调用fputc单个打印,通过重定向可以在串口打印
  * @param  
  *     @arg 
  * @param  
  *     @arg 
  * @retval None
  */
int fputc(int ch,FILE *f)
{
	Serial_SendByte(ch);
	return ch;
}


/**
  * @brief 重构v sprintf,完成printf打印串口
  *     @arg sprintf只能接收直接写的参数,vsprintf可以接收封装格式参数
  * @param  format:接收格式化字符串
  *     @arg 
  * @param  ...:可变参数列表,用来接收剩余参数
  *     @arg 
  * @retval None
  */
void Serial_printf(char *format,...){
	char String[100];
	va_list arg;//定义一个参数列表变量
	va_start(arg,format);//从format位置开始接收参数列表,放在arg中
	vsprintf(String,format,arg);//将打印参数arg格式化为format格式字符串,最好放入打印字符串变量String
	va_end(arg);//释放参数表
	Serial_SendString(String);//发送格式化后的字符串
}


/**
  * @brief 串口1接收,查询方式,接收到的数据返回
  * @param  
  *     @arg 
  * @param  
  *     @arg 
  * @retval None
  */
void Serial_Get(void){
	if(USART_GetFlagStatus(USART1,USART_FLAG_RXNE) == SET){
		uint8_t Get_Data = USART_ReceiveData(USART1);
		Serial_SendByte(Get_Data);
	};
}



Serial.h

#ifndef __SERIAL_H
#define __SERIAL_H
#include "stm32f10x.h"                  // Device header
#include "stdio.h"
extern uint8_t Serial1_Flag;
extern uint8_t Serial1_RxData;
void Serial_Init(void);
void Serial_SendByte(uint8_t Byte);
void Serial_SendByteArray(uint8_t *ByteArray,uint16_t length);
void Serial_SendString(char *Char);
void Serial_SendSignedNum(int32_t Num,uint16_t length);
void Serial_SendNum(int32_t Num,uint16_t length);
void Serial_printf(char *format,...);
void Serial_Get(void);
void Serial1Rx(void);
#endif

3、数据包收发,收发Hex数据包

程序功能和逻辑:

1、按钮按下,对应STM32发送Hex数据包,OLED显示发送Hex数据。每次按按钮数据包递增。

2、串口发送Hex数据包,对应OLED显示接收的Hex数据(当接收到正确的数据包会进行处理,把接受的数据包也发送,接受的错误的数据包不会发送,但OLED也会显示。)。

Hex数据包格式

可变长数据包可能导致:数据和包尾重合,导致误判。

对于这种情况, 可对数据进行限幅,然后包头包尾在限幅之外。或者尽量使用定长数据包,然后尽量增加包头包尾的数量,并尽量让数据不能呈现包头包尾的状态。

对于Hex数据包最好选择定长带包头包尾,可以避免接收错误。

如果载荷不会和包头包尾重复,那么选择可变包长。

Hex通信协议为:

发送:

按下按键STM32发送0xFF 0x01 0x02 0x03 0x04 0xFE,以0xFF为包头,0xFE为包尾,中间4byte为数据,每次发送数据字节递增。

接收:

通过串口助手发送0xFF 0xXX 0xXX 0xXX 0xXX 0xFE,共6byte,其中以0xFF为包头,0xFE为包尾,中间4byte为数据。STM32接收后通过OLED显示数据字节。

Hex数据包接收:

main.c

#include "stdio.h"
#include "stm32f10x.h"                  // Device header
#include "Delay.h"
#include "OLED.h"
#include "Serial.h"
#include "Button.h"
int main(void){
	OLED_Init();
	Serial_Init();
	Button1_Init();
	OLED_ShowString(1,1,"Tx:");
	OLED_ShowString(3,1,"Rx:");
	while(1){
		Serial1Rx();
		GetButton_Send();
		OLED_ShowHexNum(4,1,Serial1_RxPacket[0],2);
		OLED_ShowHexNum(4,4,Serial1_RxPacket[1],2);
		OLED_ShowHexNum(4,7,Serial1_RxPacket[2],2);
		OLED_ShowHexNum(4,10,Serial1_RxPacket[3],2);
		OLED_ShowHexNum(2,1,Serial1_TxPacket[0],2);
		OLED_ShowHexNum(2,4,Serial1_TxPacket[1],2);
		OLED_ShowHexNum(2,7,Serial1_TxPacket[2],2);
		OLED_ShowHexNum(2,10,Serial1_TxPacket[3],2);
	}
	return 0;
}

Serial.c

#include "stm32f10x.h"                  // Device header
#include "math.h"
#include "stdio.h"
#include "stdarg.h"
uint8_t Serial1_RxData;
uint8_t Serial1_RxFlag;//接收数据包标志位
uint8_t Serial1_RxPacket[4];//接收数据包
uint8_t Serial1_TxPacket[4] = {0x01,0x02,0x03,0x04};//发送数据包
/**
  * @brief 初始化USART1,通过USART1进行收发
  * @param  
  *     @arg 
  * @param  
  *     @arg 
  * @retval None
  */
void Serial_Init(void){
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1,ENABLE);
	
	GPIO_InitTypeDef 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);
	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);
	
	USART_InitTypeDef USART_InitStructure;
	USART_InitStructure.USART_BaudRate = 9600;
	USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;//硬件流控制
	USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;
	USART_InitStructure.USART_Parity = USART_Parity_No;
	USART_InitStructure.USART_WordLength = USART_WordLength_8b;
	USART_InitStructure.USART_StopBits = USART_StopBits_1;
	USART_Init(USART1,&USART_InitStructure);
	
	USART_ITConfig(USART1,USART_IT_RXNE,ENABLE);
	
	NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
	
	//NVIC配置-中断优先级和中断对应通道使能 stm32f10x.h
	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);
}
/**
  * @brief 串口发送1byte
  * @param  
  *     @arg 
  * @param  
  *     @arg 
  * @retval None
  */
void Serial_SendByte(uint8_t Byte){
	USART_SendData(USART1,Byte);
	while(USART_GetFlagStatus(USART1,USART_FLAG_TXE) == RESET);//发送缓冲不为空则等待
}

/**
  * @brief 发送字节数组
  * @param  数组指针
  *     @arg 
  * @param  长度
  *     @arg 
  * @retval None
  */
void Serial_SendByteArray(uint8_t *ByteArray,uint16_t length){
	for(int i = 0 ; i<length ;  i++){
		Serial_SendByte(ByteArray[i]);
	}
}

/**
  * @brief 根据数据包发送数据数组
  * @param  
  *     @arg 
  * @param  
  *     @arg 
  * @retval None
  */
void Serial1Tx_HexPacket(void){
	Serial_SendByte(0xFF);//发送包头
	Serial_SendByteArray(Serial1_TxPacket,4);
	Serial_SendByte(0xFE);//发送包尾
}

/**
  * @brief 配合USART1_IRQHandler串口1接收中断,对接收到的数据进行处理
  * @param  
  *     @arg 
  * @param  
  *     @arg 
  * @retval None
  */
void Serial1Rx(void){
	if(Serial1_RxFlag){
		for(int i=0;i<4;i++){
			Serial1_TxPacket[i] = Serial1_RxPacket[i];
		}
		Serial1Tx_HexPacket();
		Serial1_RxFlag = 0;
	}
}





/**
  * @brief 配合USART1_IRQHandler串口1接收中断,接收到的Hex数据包进行判断,
  * @param  
  *     @arg 
  * @param  
  *     @arg 
  * @retval None
  */
void Serial1Rx_HexPacket(void){
	static uint8_t RxState = 0;//接收状态机
	static uint8_t RxDataFlag = 0;//接收数据下标
	/*
		0/1 :包头
		1 :数据
		2 :包尾
	*/
	Serial1_RxData = USART_ReceiveData(USART1);//接收数据
	if(RxState == 0){//等待接收包头
		if(Serial1_RxData == 0xFF){//如果获取包头
			RxState = 1;
			RxDataFlag = 0;//在每次接收数据前清0,更稳定
		}
	}else if(RxState == 1){//等待数据
		Serial1_RxPacket[RxDataFlag] = Serial1_RxData;
		RxDataFlag++;
		if(RxDataFlag>=4){
			RxState = 2;
		}
	}else if(RxState == 2){//等待包尾
		if(Serial1_RxData == 0xFE){//等待包尾
			RxState = 0;
			Serial1_RxFlag = 1;//标志接收到了正确的数据包标志位
		}else{
			RxState = 0;//如果接收的不是包尾,则丢弃数据包
		}
	}
	
}

/**
  * @brief USART1中断函数,接收的数据返回
  * @param  
  *     @arg 
  * @param  
  *     @arg 
  * @retval None	
  *		@arg 中断函数名在startup_stm32f10x_md.s中
  */
void USART1_IRQHandler(void){
	if(USART_GetITStatus(USART1,USART_IT_RXNE) == SET){
		Serial1Rx_HexPacket();//开始接收数据
		USART_ClearITPendingBit(USART1,USART_IT_RXNE);
	}
}









Serial.h

#ifndef __SERIAL_H
#define __SERIAL_H
#include "stm32f10x.h"                  // Device header
#include "stdio.h"
extern uint8_t Serial1_RxData;
extern uint8_t Serial1_RxFlag;
extern uint8_t Serial1_RxPacket[];
extern uint8_t Serial1_TxPacket[];
void Serial_Init(void);
void Serial_SendByte(uint8_t Byte);
void Serial_SendByteArray(uint8_t *ByteArray,uint16_t length);
void Serial1Rx(void);
void Serial1Rx_HexPacket(void);
void Serial1Tx_HexPacket(void);
#endif

Button.h

#ifndef __BUTTON_H
#define __BUTTON_H
#include "stm32f10x.h"                  // Device header

void Button1_Init(void);
void GetButton_Send(void);
#endif

Button.c

#include "stm32f10x.h"                  // Device header
#include "Delay.h"
#include "Serial.h"
/**
  * @brief 初始化引脚B1地开信号接收Button,A1推挽输出控制LED
  * @param  
  *     @arg 
  * @param  
  *     @arg 
  * @retval None
  */
void Button1_Init(void){
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE);
	
	GPIO_InitTypeDef  GPIO_InitStructure;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_1;
	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_1;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOB,&GPIO_InitStructure);
	
	GPIO_SetBits(GPIOA,GPIO_Pin_1);//高电平
}

/**
  * @brief 
  * @param  
  *     @arg 
  * @param  
  *     @arg 
  * @retval None
  */
void GetButton_Send(void){
	if(GPIO_ReadInputDataBit(GPIOB,GPIO_Pin_1) == RESET){
		Delay_ms(20);
		while(GPIO_ReadInputDataBit(GPIOB,GPIO_Pin_1) == RESET);
		Delay_ms(20);
		Serial1_TxPacket[0]++;
		Serial1_TxPacket[1]++;
		Serial1_TxPacket[2]++;
		Serial1_TxPacket[3]++;
		Serial1Tx_HexPacket();
	}
}

4、数据包收发,收发文本数据包

程序功能逻辑:

1、按钮按下,灯开关,对应STM32发送开关灯文本数据包,OLED显示发送的文本数据

2、串口发送开关灯Hex数据包,对应开关灯,对应OLED显示接收的Hex数据,STM32发送开关灯文本数据包包反馈

文本数据包格式:

使用文本数据包,中间数据基本上都是字母,不会和包头包尾重复。可使用不定长数据包。

通常用换行做包尾,这样在打印时就是一行一行显示,相对Hex数据包解析效率低。 

文本数据包通信协议为:

发送:

按下按键STM32交替发送@ LED_ON\r\n和@LED_OFF\r\n,对应LED开关,以@为包头,\r\n为包尾(换行),LED_ON和LED_OFF为数据。并在OLED显示

接收:

通过串口助手发送@ LED_ON\r\n和@LED_OFF\r\n,对应LED开关,其中以@为包头,\r\n为包尾(换行),LED_ON和LED_OFF为数据。并通过OLED显示数据字节。当STM32接收到错误指令时报故。

文本数据包接收:

main.c

#include "stdio.h"
#include "stm32f10x.h"                  // Device header
#include "Delay.h"
#include "OLED.h"
#include "Serial.h"
#include "Button.h"
int main(void){
	OLED_Init();
	Serial_Init();
	Button1_Init();
	
	OLED_ShowString(1,1,"Tx:");
	OLED_ShowString(2,1,"@LED_OFF");
	OLED_ShowString(3,1,"Rx:");
	OLED_ShowString(4,1,"@LED_OFF");
	
	while(1){
		GetButton_LED();
		Serial1TextRx();
	}
	return 0;
}

代码讲解

Serial.c

#include "stm32f10x.h"                  // Device header
#include "math.h"
#include "stdio.h"
#include "stdarg.h"
#include "OLED.h"
#include "Button.h"
#include "String.h"
uint8_t Serial1_RxData;//接收数据字节
uint8_t Serial1_RxFlag;//接收完整数据包标志位
char Serial1_RxTextPacket[100];//接收文本数据包
/**
  * @brief 初始化USART1,通过USART1进行收发
  * @param  
  *     @arg 
  * @param  
  *     @arg 
  * @retval None
  */
void Serial_Init(void){
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1,ENABLE);
	
	GPIO_InitTypeDef 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);
	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);
	
	USART_InitTypeDef USART_InitStructure;
	USART_InitStructure.USART_BaudRate = 9600;
	USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;//硬件流控制
	USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;
	USART_InitStructure.USART_Parity = USART_Parity_No;
	USART_InitStructure.USART_WordLength = USART_WordLength_8b;
	USART_InitStructure.USART_StopBits = USART_StopBits_1;
	USART_Init(USART1,&USART_InitStructure);
	
	USART_ITConfig(USART1,USART_IT_RXNE,ENABLE);
	
	NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
	
	//NVIC配置-中断优先级和中断对应通道使能 stm32f10x.h
	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);
}
/**
  * @brief 串口发送1byte
  * @param  
  *     @arg 
  * @param  
  *     @arg 
  * @retval None
  */
void Serial_SendByte(uint8_t Byte){
	USART_SendData(USART1,Byte);
	while(USART_GetFlagStatus(USART1,USART_FLAG_TXE) == RESET);//发送缓冲不为空则等待
}



/**
  * @brief 发送一个字符串
  * @param  
  *     @arg 
  * @param  
  *     @arg 
  * @retval None
  */
void Serial_SendString(char *String){
	int i = 0;
	for(i=0;String[i]!='\0';i++){
		Serial_SendByte(String[i]);
	}
}

/**
  * @brief 根据数据包发送文本数据数组
  * @param  
  *     @arg 
  * @param  
  *     @arg 
  * @retval None
  */
void Serial1Tx_TextPacket(void){
	Serial_SendByte('@');//发送包头
	if(GPIO_ReadInputDataBit(GPIOA,GPIO_Pin_1)==RESET){
		Serial_SendString("LED_ON");
	}else{
		Serial_SendString("LED_OFF");
	}
	Serial_SendByte('\r');//发送包尾
	Serial_SendByte('\n');//发送包尾
	
}


/**
  * @brief 配合USART1_IRQHandler串口1接收中断,对接收到的文本数据进行处理
  * @param  
  *     @arg 
  * @param  
  *     @arg 
  * @retval None
  */
void Serial1TextRx(void){
	if(Serial1_RxFlag){
		OLED_ShowString(4,1,"                ");
		OLED_ShowString(4,1,Serial1_RxTextPacket);
		if(strcmp(Serial1_RxTextPacket,"LED_OFF") == 0){
			LED_OFF(GPIOA,GPIO_Pin_1);
		}else if(strcmp(Serial1_RxTextPacket,"LED_ON") == 0){
			LED_ON(GPIOA,GPIO_Pin_1);
			
		}else{
			OLED_ShowString(2,1,"Rx_Error");
			Serial_SendString("Rx_Error\r\n");
		}
		Serial1_RxFlag = 0;
	}
}

/**
  * @brief 配合USART1_IRQHandler串口1接收中断,接收到的Text数据包进行判断,
  * @param  
  *     @arg 
  * @param  
  *     @arg 
  * @retval None
  */
void Serial1Rx_TextPacket(void){
	static uint8_t RxState = 0;//接收状态机
	static uint8_t RxDataFlag = 0;//接收数据下标
	/*
		0/1 :包头
		1 :数据
		2 :包尾
	*/
	Serial1_RxData = USART_ReceiveData(USART1);//接收数据
	if(RxState == 0){//等待接收包头
		if(Serial1_RxData == '@'){//如果获取包头
			RxState = 1;
			RxDataFlag = 0;//在每次接收数据前清0,更稳定
		}
	}else if(RxState == 1){//等待数据
		if(Serial1_RxData != '\r'){
			Serial1_RxTextPacket[RxDataFlag] = Serial1_RxData;
			RxDataFlag++;
		}else{
			RxState = 2;
		}		
	}else if(RxState == 2){//等待包尾
		if(Serial1_RxData == '\n'){//等待包尾
			RxState = 0;
			Serial1_RxTextPacket[RxDataFlag] = '\0';
			Serial1_RxFlag = 1;//标志接收到了正确的数据包标志位
		}else{
			RxState = 0;//如果接收的不是包尾,则丢弃数据包
		}
	}
}

/**
  * @brief USART1中断函数,接收的数据返回
  * @param  
  *     @arg 
  * @param  
  *     @arg 
  * @retval None	
  *		@arg 中断函数名在startup_stm32f10x_md.s中
  */
void USART1_IRQHandler(void){
	if(USART_GetITStatus(USART1,USART_IT_RXNE) == SET){
		//Serial1Rx_HexPacket();//开始接收数据
		Serial1Rx_TextPacket();
		USART_ClearITPendingBit(USART1,USART_IT_RXNE);
	}
}

代码讲解:

 Serial1TextRx未放在中断内,减少消耗软件资源(减少中断时间占用)

Serial.h

#ifndef __SERIAL_H
#define __SERIAL_H
#include "stm32f10x.h"                  // Device header
#include "stdio.h"
extern uint8_t Serial1_RxData;
extern uint8_t Serial1_RxFlag;
extern char Serial1_RxTextPacket[];
void Serial_Init(void);
void Serial_SendByte(uint8_t Byte);
void Serial_SendString(char *Char)

void Serial1TextRx(void);
void Serial1Tx_TextPacket(void);
void Serial1Rx_TextPacket(void);
#endif

Button.c

#include "stm32f10x.h"                  // Device header
#include "Delay.h"
#include "Serial.h"
#include "OLED.h"
/**
  * @brief 初始化引脚B1地开信号接收Button,A1推挽输出控制LED
  * @param  
  *     @arg 
  * @param  
  *     @arg 
  * @retval None
  */
void Button1_Init(void){
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE);
	
	GPIO_InitTypeDef  GPIO_InitStructure;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_1;
	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_1;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOB,&GPIO_InitStructure);
	
	GPIO_SetBits(GPIOA,GPIO_Pin_1);//高电平
}
/**
  * @brief LED关闭,OLED显示@LED_OFF,并且串口发送@LED_OFF\r\n
  * @param  
  *     @arg 
  * @param  
  *     @arg 
  * @retval None
  */
uint8_t LED_OFF(GPIO_TypeDef* GPIOx,uint16_t GPIO_Pin){
	GPIO_WriteBit(GPIOx,GPIO_Pin,Bit_SET);//关闭LED
	OLED_ShowString(2,1,"@LED_OFF        ");
	Serial1Tx_TextPacket();//串口发送
	return 1;
}

/**
  * @brief LED打开,OLED显示@LED_ON,并且串口发送@LED_ON
  * @param  
  *     @arg 
  * @param  
  *     @arg 
  * @retval None
  */
uint8_t LED_ON(GPIO_TypeDef* GPIOx,uint16_t GPIO_Pin){
	GPIO_WriteBit(GPIOx,GPIO_Pin,Bit_RESET);//打开LED
	OLED_ShowString(2,1,"@LED_ON         ");
	Serial1Tx_TextPacket();//串口发送
	return 0;
}

/**
  * @brief 获取B1的Button是否按下,按下则控制LED反转
  * @param  
  *     @arg 
  * @param  
  *     @arg 
  * @retval None
  */
void GetButton_LED(void){
	if(GPIO_ReadInputDataBit(GPIOB,GPIO_Pin_1) == RESET){
		Delay_ms(20);
		while(GPIO_ReadInputDataBit(GPIOB,GPIO_Pin_1) == RESET);
		Delay_ms(20);
		if(GPIO_ReadInputDataBit(GPIOA,GPIO_Pin_1) == RESET){
			LED_OFF(GPIOA,GPIO_Pin_1);
		}else{
			LED_ON(GPIOA,GPIO_Pin_1);
		}
	}
}

 代码讲解:

 

Button.h

#ifndef __BUTTON_H
#define __BUTTON_H
#include "stm32f10x.h"                  // Device header

void Button1_Init(void);
uint8_t LED_OFF(GPIO_TypeDef* GPIOx,uint16_t GPIO_Pin);
uint8_t LED_ON(GPIO_TypeDef* GPIOx,uint16_t GPIO_Pin);
void GetButton_LED(void);
#endif

程序逻辑:

按下按钮:GetButton_LED -> GetButton_LED -> LED_ON/LED_OFF -> GPIO_WriteBit/OLED_ShowString/Serial1Tx_TextPacket

发送控制指令:USART1_IRQHandler->Serial1Rx_TextPacket->Serial1TextRx->LED_ON/LED_OFF -> GPIO_WriteBit/OLED_ShowString/Serial1Tx_TextPacket

反思

本节程序模块之间的耦合性太高,应该针对不同的模块进行不同的功能封装,然后统一调用即可。不应该在一个模块程序中调用另一个模块程序。

例如:串口模块中尽量只写串口函数,若需要串口相关返回和处理,增加标志位返回函数或者全局变量即可。LED控制和按钮采集,应该只写LED控制和按钮采集。在主函数中,应该进行采集控制和通信函数的调用。每个模块库应该保持整洁和低耦性,方便理解和使用。

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

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

相关文章

电话语音机器人,是由哪些功能构成?

电话语音机器人是自动电话销售、筛选意向客户的&#xff0c;只要录入好行业话术&#xff0c;导入要拨打的手机号&#xff0c;机器人就可以上岗工作了。 电话语音机器人组成部分&#xff1a; 1、语音识别器&#xff0c;主要作用&#xff1a;识别客户讲话内容&#xff0c;从而做…

理解 WordPress | 第二篇:结构化分析

WordPress 专题致力于从 0 到 1 搞懂、用熟这种可视化建站工具。 第一阶段主要是理解。 第二阶段开始实践个人博客、企业官网、独立站的建设。 如果感兴趣&#xff0c;点个关注吧&#xff0c;防止迷路。 WordPress 的内容和功能结构可以按照层级来划分&#xff0c;这种层次化的…

vue3项目history模式部署404处理,使用 historyApiFallback 中间件支持单页面应用路由

vue3项目history模式部署404处理&#xff0c;使用 historyApiFallback 中间件支持单页面应用路由 在现代的 web 开发中&#xff0c;单页面应用&#xff08;SPA&#xff09;变得越来越流行。这类应用通常依赖于客户端路由来提供流畅的用户体验&#xff0c;但在服务器端&#xf…

计算机毕业设计Hadoop+PySpark深度学习游戏推荐系统 游戏可视化 游戏数据分析 游戏爬虫 Scrapy 机器学习 人工智能 大数据毕设

温馨提示&#xff1a;文末有 CSDN 平台官方提供的学长联系方式的名片&#xff01; 温馨提示&#xff1a;文末有 CSDN 平台官方提供的学长联系方式的名片&#xff01; 温馨提示&#xff1a;文末有 CSDN 平台官方提供的学长联系方式的名片&#xff01; 作者简介&#xff1a;Java领…

PHP电商供应链ERP管理系统小程序源码

&#x1f680;电商供应链大揭秘&#xff01;ERP管理系统如何重塑你的商业版图✨ &#x1f50d; 什么是电商供应链ERP管理系统&#xff1f; 电商供应链ERP管理系统是一款基于FastAdminThinkPHP开发的系统。该系统可满足电商企业管理自身进销存&#xff0c;帮助中小型电商企业管…

全参微调与LoRA的区别,及7种LoRA变种方法解析

随着LLM的发展和应用&#xff0c;在LLM的预训练模型基础上做微调&#xff0c;使其适用于自己的业务场景的研究越来越多。与全参数SFT相比LoRA是在冻结LLM本身参数的基础上&#xff0c;在旁路增加两个可学习的矩阵&#xff0c;用于训练和学习&#xff0c;最后推理是LLM输出和可学…

ubuntu工具 -- ubuntu服务器临时没有网络,急需联网下载东西怎么办? 使用手机提供网络

问题 ubuntu服务器配置经常遇到临时需要网络下载文件需求, 通过有线连接又来不及 解决方法 使用手机usb为ubuntu服务器提供网络 先在ubuntu上运行 ifconfig 查看当前的网络接口, 一会看看多了哪个网口 1. 手机端操作 先使用usb数据线将手机连接到服务器上 打开手机的usb共享…

一文快速预览经典深度学习模型(一)——CNN、RNN、LSTM、Transformer、ViT

Hi&#xff0c;大家好&#xff0c;我是半亩花海。本文主要简要并通俗地介绍了几种经典的深度学习模型&#xff0c;如CNN、RNN、LSTM、Transformer、ViT&#xff08;Vision Transformer&#xff09;等&#xff0c;便于大家初探深度学习的相关知识&#xff0c;并更好地理解深度学…

【D3.js in Action 3 精译_038】4.2 D3 折线图的绘制方法及曲线插值处理

当前内容所在位置&#xff08;可进入专栏查看其他译好的章节内容&#xff09; 第一部分 D3.js 基础知识 第一章 D3.js 简介&#xff08;已完结&#xff09; 1.1 何为 D3.js&#xff1f;1.2 D3 生态系统——入门须知1.3 数据可视化最佳实践&#xff08;上&#xff09;1.3 数据可…

数据结构(8.7_2)——败者树

多路平衡归并带来的问题 什么是败者树 败者树的构造 败者树的使用 败者树在多路平衡归并中的应用 败者树的实现思路 总结

Web Broker(Web服务应用程序)入门教程(1)

1、介绍 Web Broker 组件&#xff08;位于工具面板的“Internet”选项卡中&#xff09;可以帮助您创建与特定统一资源标识符&#xff08;URI&#xff09;相关联的事件处理程序。当处理完成后&#xff0c;您可以通过编程方式构建 HTML 或 XML 文档&#xff0c;并将它们传输给客…

Redis高级篇之缓存一致性详细教程

文章目录 0 前言1.缓存双写一致性的理解1.1 缓存按照操作来分 2. 数据库和缓存一致性的几种更新策略2.1 可以停机的情况2.2 我们讨论4种更新策略2.3 解决方案 总结 0 前言 缓存一致性问题在工作中绝对没办法回避的问题&#xff0c;比如&#xff1a;在实际开发过程中&#xff0c…

Vue2进阶之Vue3高级用法

Vue3高级用法 响应式Vue2&#xff1a;Object.definePropertyObject.definePropertythis.$set设置响应式 Vue3&#xff1a;Proxy composition APIVue2 option API和Vue3 compositionAPIreactive和shallowReactivereadonly效果toRefs效果 生命周期main.jsindex.htmlLifeCycle.vue…

Unity3D学习FPS游戏(10)子弹攻击敌人掉血(碰撞检测)

前言&#xff1a;前面最然创造出带有血条的敌人&#xff0c;但子弹打中敌人并没有效果。所以本篇将实现子弹攻击敌人&#xff0c;并让敌人掉血。 子弹攻击敌人掉血 整体思路目标补充知识-碰撞检测 准备工作刚体和碰撞器添加添加刚体后子弹代码优化补充知识-标签系统Tag添加 碰…

AMD显卡低负载看视频掉驱动(chrome edge浏览器) 高负载玩游戏却稳定 解决方法——关闭MPO

问题 折磨的开始是天下苦黄狗久矣&#xff0c;为了不再被讨乞丐的显存恶心&#xff0c;一怒之下购入了AMD显卡&#xff08;20GB显存确实爽 头一天就跑了3dmark验机&#xff0c;完美通过&#xff0c;玩游戏也没毛病 但是呢这厮是一点不省心&#xff0c;玩游戏没问题&#xff0c…

服装品牌零售业态融合中的创新发展:以开源 AI 智能名片 S2B2C 商城小程序为视角

摘要&#xff1a;本文以服装品牌零售业态融合为背景&#xff0c;探讨信息流优化和资金流创新的重要作用&#xff0c;并结合开源 AI 智能名片 S2B2C 商城小程序&#xff0c;分析其如何进一步推动服装品牌在零售领域的发展&#xff0c;提高运营效率和用户体验&#xff0c;实现商业…

【数据库】elasticsearch

1、架构 es会为每个索引创建一定数量的主分片和副本分片。 分片&#xff08;Shard&#xff09;&#xff1a; 将索引数据分割成多个部分&#xff0c;每个部分都是一个独立的索引。 主要目的是实现数据的分布式存储和并行处理&#xff0c;从而提高系统的扩展性和性能。 在创建索…

语言模型的评测

语言模型的评测 内在评测 在内在评测中&#xff0c;测试文本通常由与预训练中所用的文本独立同分布的文本构成&#xff0c;不依赖于具体任务。最为常用的内部评测指标是困惑度&#xff08;Perplexity&#xff09; 困惑度是衡量语言模型对测试文本预测能力的一个指标&#xf…

Golang | Leetcode Golang题解之第535题TinyURL的加密与解密

题目&#xff1a; 题解&#xff1a; import "math/rand"type Codec map[int]stringfunc Constructor() Codec {return Codec{} }func (c Codec) encode(longUrl string) string {for {key : rand.Int()if c[key] "" {c[key] longUrlreturn "http:/…

德国卡赫携丰硕成果七赴进博会

第七届中国国际进口博览会于11月5日在国家会展中心&#xff08;上海&#xff09;正式拉开帷幕。作为全球最大的清洁设备和清洁解决方案提供商&#xff0c;“全勤生”德国卡赫连续七年参展&#xff0c;并携集团旗下子品牌HAWK霍克一同亮相技术装备展区3号馆&#xff0c;更带来多…