STM32 串口收发文本数据包

单片机学习!

目录

前言

一、文本数据包格式

二、串口收发文本数据包代码

三、代码解析

 3.1 标志位清除

3.2 数据包接收

四、代码问题改进

总结


前言

        本文介绍了串口收发文本数据包程序设计的思路并详解代码作用。


一、文本数据包格式

        文本数据包的格式的定义如下图所示:可变包长,含包头包尾,其中包头为@,包尾为换行的两个符号\r和\n,中间的载荷字符数量不固定。

b16afb066c18430aa8d89c5115d051e4.png

二、串口收发文本数据包代码

        程序就只写接收的部分,因为发送不像HEX数组一样,方便一个个更改。这里发送就直接在主函数里SendString或者printf就行了。

总代码示例:

#include "stm32f10x.h"                  // Device header
#include <stdio.h>char Serial_RxPacket[100];//接收缓存区
uint8_t Serial_RxFlag;//标志位void Serial_Init(void)
{//第一步开启时钟RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1,ENABLE);//开启USART1的时钟RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);//开启GPIO的时钟//第二步初始化GPIO引脚GPIO_InitTypeDef GPIO_InitStruct;GPIO_InitStruct.GPIO_Mode= GPIO_Mode_AF_PP;//引脚模式GPIO_InitStruct.GPIO_Pin= GPIO_Pin_9;//引脚选择Pin_9GPIO_InitStruct.GPIO_Speed= GPIO_Speed_50MHz;GPIO_Init(GPIOA,&GPIO_InitStruct);//初始化GPIOAGPIO_InitStruct.GPIO_Mode= GPIO_Mode_IPU;//引脚模式GPIO_InitStruct.GPIO_Pin= GPIO_Pin_10;//引脚选择Pin_10GPIO_InitStruct.GPIO_Speed= GPIO_Speed_50MHz;GPIO_Init(GPIOA,&GPIO_InitStruct);//初始化GPIOA//第三步初始化USARTUSART_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);//配置USART1的接收中断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]);}
}//这个函数的返回值是X的Y次方
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');}
}//printf函数重定向到串口
int fputc(int ch,FILE *f)
{Serial_SendByte(ch);return ch;
}//函数实现一个Serial_RxData变量读后自动清除标志位Serial_RxFlag的功能
uint8_t Serial_GetRxFlag(void)
{if(Serial_RxFlag == 1){Serial_RxFlag = 0;return 1;}return 0;
}//中断函数
void USART1_IRQHandler(void)
{static uint8_t RxState = 0;//做状态变量Sstatic uint8_t pRxPacket = 0;//这个静态变量用于指示接收到数据包中哪一个数据了,最开始默认为0//先判断标志位if(USART_GetITStatus(USART1,USART_IT_RXNE)==SET)//如果RXNE确实置1了,就进入if{//首先获取一下RxDatauint8_t RxData = USART_ReceiveData(USART1);if(RxState == 0)//等待包头{if(RxData == '@'){RxState = 1;pRxPacket = 0;}}else if(RxState == 1)//接收数据{if(RxData == '\r'){RxState = 2;}else {Serial_RxPacket[pRxPacket] = RxData;pRxPacket ++;}}else if(RxState == 2)//等待第二个包尾{if(RxData == '\n'){RxState = 0;Serial_RxPacket[pRxPacket] = '\0';Serial_RxFlag = 1;}}USART_ClearITPendingBit(USART1,USART_IT_RXNE); }
}


        串口配置部分和数据发送、接收的代码详解可以看前两篇博文:

STM32 USART串口发送_串口发送代码-CSDN博客icon-default.png?t=O83Ahttps://blog.csdn.net/Echo_cy_/article/details/142794600?spm=1001.2014.3001.5501

STM32 USART串口接收_stm32 uart发送数据-CSDN博客icon-default.png?t=O83Ahttps://blog.csdn.net/Echo_cy_/article/details/143817933?spm=1001.2014.3001.5501本文只分析新设计的接收文本数据包函数。


三、代码解析

         程序最前面为了收发数据包,先定义了一个缓存区的数组和一个标志位。

char Serial_RxPacket[100];
uint8_t Serial_RxFlag;

        接收缓存用于接收字符,数量可以给多一点防止溢出,这要求单条指令最长不超过100个字符。

char Serial_RxPacket[100];

        自定义的标志位,如果收到一个数据包,就置Serial_RxFlag为1:

uint8_t Serial_RxFlag;

 3.1 标志位清除

        Serial_GetRxFlag函数实现一个Serial_RxData变量读后自动清除标志位Serial_RxFlag的功能。

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

3.2 数据包接收

        在中断函数USART1_IRQHandler里需要用状态机来执行接收逻辑,接收数据包,然后把载荷数据存在Serial_RxPacket数组里。

        根据状态转移图首先要定义一个标志当前状态的变量S,在中断函数里面定义一个静态变量。

代码示例:

//中断函数接收数据,执行状态机逻辑
void USART1_IRQHandler(void)
{static uint8_t RxState = 0;//当做状态变量Sstatic uint8_t pRxPacket = 0;//这个静态变量用于指示接收到数据包中哪一个数据了,最开始默认为0//先判断标志位if(USART_GetITStatus(USART1,USART_IT_RXNE)==SET)//如果RXNE确实置1了,就进入if{//首先获取一下RxDatauint8_t RxData = USART_ReceiveData(USART1);if(RxState == 0)//等待包头{if(RxData == '@'){RxState = 1;pRxPacket = 0;}}else if(RxState == 1)//接收数据{if(RxData == '\r'){RxState = 2;}else {Serial_RxPacket[pRxPacket] = RxData;pRxPacket ++;}}else if(RxState == 2)//等待第二个包尾{if(RxData == '\n'){RxState = 0;Serial_RxPacket[pRxPacket] = '\0';Serial_RxFlag = 1;}}USART_ClearITPendingBit(USART1,USART_IT_RXNE); }
}

        注意要用else if,如果只用三个并列的if可能会在状态转移的时候出现问题。比如在状态0,需要转移到状态1,就置RxState=1,结果就会造成下面状态1的条件就立马满足了,这样会出现连续两个if都同时成立的情况,就不符合执行逻辑了。所以这里要使用else if,保证每次进状态机代码之后只能选择执行其中一个状态的代码。或者用switch case语句也可以保证只有一个条件满足。写好状态选择的部分,就可以依次写每个状态执行的操作逻辑和状态转移条件了。


重要变量:        

        USART1_IRQHandler中断函数是把数据进行了一次转存,最终还是要扫描查询Serial_RxFlag来接收数据。

        RxState这个静态变量类似于全局变量,函数进入只会初始化一次为0,在函数退出后,数据仍然有效。与全局变量不同的是,静态变量只能在本函数使用。这里就用RxState当做状态变量S,根据状态转换图,三个状态S分别为0、1、2,所以在if语句里根据RxState的不同,需要进入不同的处理程序。

        pRxPacket这个静态变量用于指示接收到数据包中哪一个数据了,最开始默认为0.


        中断函数先判断标志位,如果RXNE确实置1了,就进入if 。

 if(USART_GetITStatus(USART1,USART_IT_RXNE)==SET){...;}

        在if代码框里首先获取一下RxData。

        uint8_t RxData = USART_ReceiveData(USART1);

        接着就是三个状态的条件判断和相应状态下的程序逻辑。

1.等待包头

		if(RxState == 0)//等待包头{if(RxData == '@'){RxState = 1;pRxPacket = 0;}}

        如果收到包头,那就可以转移状态RxState=1;如果没有收到@就不转移状态。

2.接收数据

        因为载荷字符数量并不确定,所以每次接受之前必须先判断是不是包尾\r。

		else if(RxState == 1)//接收数据{if(RxData == '\r'){RxState = 2;}else {Serial_RxPacket[pRxPacket] = RxData;pRxPacket ++;//接收数据后位置编码就自增,指示接收下一个位置的数据。}}

        此代码逻辑是,每进一次接收状态数据就转存一次缓存数据,同时存的位置后移。当收到包尾\r就证明数据收完了,这时就可以转移到下一个状态。同时对pRxPacket清0,为下次接收准备,可以在状态0转移到状态1时提前清一个0.

3.等待第二个包尾

        如果收到第二个包尾\n,那就可以回到最初的状态RxState=0,同时为了表示一个数据包接收到了,可以置一个接收标志位Serial_RxFlag=1;如果没有收到\n就时还没收到包尾,也不做处理,仍然在这个状态等待包尾。

		else if(RxState == 2)//等待第二个包尾{if(RxData == '\n'){RxState = 0;Serial_RxPacket[pRxPacket] = '\0';Serial_RxFlag = 1;}}

        接收到包尾之后还需要给字符数组的最后加一个字符串结束标志位\0,方便后续对字符串进行处理,不然这个字符数组没有结束标志位,就不知道这个字符串有多长了。

Serial_RxPacket[pRxPacket] = '\0';

        这里调用USART_ClearITPendingBit函数,直接清除一下标志位:

   USART_ClearITPendingBit(USART1,USART_IT_RXNE);

        以上中断接收和变量的封装就完成了!

四、代码问题改进

        当然这样还是存在一个问题,如果连续发送数据包,程序处理不及时,可能导致数据包错位。一般文本数据包是独立的,不存在连续,错位的话问题就比较大。所以程序可以稍作修改来解决这个问题,等每次处理完成之后,再开始接收下一个数据包。

        可以利用设计的获取标志位Serial_GetRxFlag函数,程序逻辑不使用原来读取Flag之后立刻清除的策略。在中断函数里,等待包头的时候再加一个条件。

			if(RxData == '@' && Serial_RxFlag == 0)

如果收到包头,并且Serial_RxFlag=0时,才执行接收,若Serial_RxFlag不等于0,就是发太快了,还没处理完呢,就跳过这个数据包。

        之前读取标志位之后立刻清零的函数Serial_GetRxFlag先删除。

        把Serial_RxFlag也申明为外部可调用,暂时不封装了。

extern uint8_t Serial_RxFlag;

        主函数里当Serial_RxFlag标志位为1时,就代表接收到数据包了,可以执行相应的操作。等操作完成之后,再把Serial_RxFlag标志位清0,在中断函数USART1_IRQHandler里只有Serial_RxFlag标志位为0了,才会继续接收下一个数据包。这样写数据和读数据就是严格分开的,不会同时进行,就可以避免数据包错位的现象了。但是这样发送数据包的频率就不能太快了,太快会丢弃部分数据包。

总代码示例:

Serial.c

#include "stm32f10x.h"                  // Device header
#include <stdio.h>char Serial_RxPacket[100];//接收缓存用于接收字符
uint8_t Serial_RxFlag;//自定义的标志位,如果收到一个数据包,就置Serial_RxFlagvoid Serial_Init(void)
{//第一步开启时钟RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1,ENABLE);//开启USART1的时钟RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);//开启GPIO的时钟//第二步初始化GPIO引脚GPIO_InitTypeDef GPIO_InitStruct;GPIO_InitStruct.GPIO_Mode= GPIO_Mode_AF_PP;//引脚模式GPIO_InitStruct.GPIO_Pin= GPIO_Pin_9;//引脚选择Pin_9GPIO_InitStruct.GPIO_Speed= GPIO_Speed_50MHz;GPIO_Init(GPIOA,&GPIO_InitStruct);//初始化GPIOAGPIO_InitStruct.GPIO_Mode= GPIO_Mode_IPU;//引脚模式GPIO_InitStruct.GPIO_Pin= GPIO_Pin_10;//引脚选择Pin_10GPIO_InitStruct.GPIO_Speed= GPIO_Speed_50MHz;GPIO_Init(GPIOA,&GPIO_InitStruct);//初始化GPIOA//第三步初始化USARTUSART_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);//配置USART1的接收中断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]);/}
}//这个函数的返回值是X的Y次方
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');}
}//printf函数重定向到串口
int fputc(int ch,FILE *f)
{Serial_SendByte(ch);return ch;
}//函数实现一个Serial_RxData变量读后自动清除标志位Serial_RxFlag的功能
//uint8_t Serial_GetRxFlag(void)
//{
//	if(Serial_RxFlag == 1)
//	{
//		Serial_RxFlag = 0;
//		return 1;
//	}
//	return 0;
//}//中断函数
void USART1_IRQHandler(void)
{static uint8_t RxState = 0;static uint8_t pRxPacket = 0;//判断标志位if(USART_GetITStatus(USART1,USART_IT_RXNE)==SET){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'){RxState = 2;}else {Serial_RxPacket[pRxPacket] = RxData;pRxPacket ++;}}else if(RxState == 2)//等待第二个包尾{if(RxData == '\n'){RxState = 0;Serial_RxPacket[pRxPacket] = '\0';Serial_RxFlag = 1;}}USART_ClearITPendingBit(USART1,USART_IT_RXNE); }
}

 

Serial.h

#ifndef __SERIAL_H
#define __SERIAL_H#include <stdio.h>extern char Serial_RxPacket[];
extern uint8_t Serial_RxFlag;void Serial_Init(void);
void Serial_SendByte(uint8_t Byte);
void Serial_SendArray(uint8_t *Array,uint16_t Length);
void Serial_SendString(char *String);
void Serial_SendNumber(uint32_t Number,uint8_t Length);#endif


总结

        以上就是今天要讲的内容,本文仅仅简单介绍了串口收发文本数据包程序设计的思路并详解了一些设计代码的细节,最后对于代码可能产生的问题做了改进。

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

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

相关文章

centos下安装ffmpeg

如果你在CentOS 7.9下执行 sudo yum install ffmpeg 时遇到问题&#xff0c;可能是因为默认的yum仓库中没有FFmpeg或者其版本太旧。你可以通过添加第三方仓库如Nginx、Remi或EPEL来安装更新版本的FFmpeg。以下是具体的步骤&#xff1a; 添加并启用必要的仓库 安装EPEL仓库&…

预言机调研

预言机 1. 概述 预言机主要承担两个工作&#xff0c;一是验证信息可靠性&#xff0c;二是传递信息。 如果没有预言机&#xff0c;区块链的信息来源将仅限于其内部数据&#xff0c;其广泛使用的潜力和可能性将会大大降低。 区块链预言机是区块链与外部世界之间的桥梁。它们使区…

【1211更新】腾讯混元Hunyuan3D-1文/图生3D模型云端镜像一键运行

目录 项目介绍 显存占用 11月21 新增纹理烘焙模块Dust3R 烘焙相关参数&#xff1a; AutoDL云端镜像 启动说明 标准模型下载 项目介绍 https://github.com/Tencent/Hunyuan3D-1 腾讯混元 3D 生成模型,支持文本和图像条件生成(对于文生3D&#xff0c;支持中/英双语生成)…

【前端】HTML标签汇总

目录 展示用户信息的标签 1.文本标签 span 2.标题标签 h1~h6 3.竖着布局的标签 div 4.段落标签 p 5.超链接标签 a 5.1跳转至网上的资源 5.2锚点 6.列表标签 6.1有序列表 ol 6.2无序列表 ul 7.图片标签 img 7.1相对路径 7.1.1兄弟关系 7.1.2叔侄关系 7.1.3表兄弟…

基于python的一个简单的压力测试(DDoS)脚本

DDoS测试脚本 声明&#xff1a;本文所涉及代码仅供学习使用&#xff0c;任何人利用此造成的一切后果与本人无关 源码 import requests import threading# 目标URL target_url "http://47.121.xxx.xxx/"# 发送请求的函数 def send_request():while True:try:respo…

深入探究 Scikit-learn 机器学习库

一、数据处理与准备 &#xff08;一&#xff09;数据加载 内置数据集&#xff1a;Sklearn 自带一些经典数据集&#xff0c;如鸢尾花数据集&#xff08;load_iris&#xff09;、波士顿房价数据集&#xff08;load_boston&#xff09;等。这些数据集方便初学者快速上手实践&…

今天你学C++了吗?——C++中的类与对象(日期类的实现)——实践与知识的碰撞❤

♥♥♥~~~~~~欢迎光临知星小度博客空间~~~~~~♥♥♥ ♥♥♥零星地变得优秀~也能拼凑出星河~♥♥♥ ♥♥♥我们一起努力成为更好的自己~♥♥♥ ♥♥♥如果这一篇博客对你有帮助~别忘了点赞分享哦~♥♥♥ ♥♥♥如果有什么问题可以评论区留言或者私信我哦~♥♥♥ ✨✨✨✨✨✨ 个…

负载均衡和tomcat

一、负载均衡 1.相关概念 nginx的反向代理<-->负载均衡 负载均衡 将四层或者是七层的请求分配到多台后端的服务器上&#xff0c;从而分担整个业务的负载。提高系统的稳定性&#xff0c;也可以提供高可用&#xff08;备灾&#xff0c;其中的一台后端服务器如果发生故障…

【蓝桥杯每日一题】推导部分和——带权并查集

推导部分和 2024-12-11 蓝桥杯每日一题 推导部分和 带权并查集 题目大意 对于一个长度为 ( N ) 的整数数列 ( A 1 , A 2 , ⋯ , A N A_1, A_2, \cdots, A_N A1​,A2​,⋯,AN​ )&#xff0c;小蓝想知道下标 ( l ) 到 ( r ) 的部分和 ∑ i l r A i A l A l 1 ⋯ A r \su…

Facebook如何避免因IP变动而封号?实用指南

随着Facebook在个人社交与商业推广中的广泛应用&#xff0c;越来越多的用户面临因“IP变动”而被封号的问题。尤其是跨境电商、广告运营者和多账号管理用户&#xff0c;这种情况可能严重影响正常使用和业务发展。那么&#xff0c;如何避免因IP变动导致的封号问题&#xff1f;本…

8.1 日志管理

本文深入解析 MySQL 中的关键日志类型&#xff0c;包括错误日志、二进制日志、查询日志和慢查询日志&#xff0c;帮助运维人员高效定位问题、分析性能并管理数据库。 1. 错误日志 1.1 概述 错误日志记录 MySQL 服务器运行过程中的重要信息&#xff0c;例如启动、关闭、错误及…

Docker 学习总结(84)—— Docker 常用运维命令

版本与信息查询 docker --version:查看安装的Docker版本。 docker info:获取Docker系统的详细配置信息。 镜像管理 docker images:列出本地所有镜像。 docker search IMAGE_NAME:搜索Docker Hub上的镜像。 docker pull IMAGE_NAME[:TAG]:从仓库下载指定镜像。 docker rmi …

【C++】判断能否被 3, 5, 7 整除问题解析与优化

博客主页&#xff1a; [小ᶻ☡꙳ᵃⁱᵍᶜ꙳] 本文专栏: C 文章目录 &#x1f4af;前言&#x1f4af;题目描述&#x1f4af;老师代码实现与分析老师代码逻辑分析优点缺点 &#x1f4af;学生代码实现与分析学生代码逻辑分析优点缺点 &#x1f4af;改进与优化优化代码实现优化…

[计算机网络]IP地址推行的“书同文,车同轨”

硬件地址无法直接转换的故事 在很久很久以前&#xff0c;网络世界就像一个庞大的帝国&#xff0c;各个村落&#xff08;网络&#xff09;都有自己的语言&#xff08;硬件地址&#xff09;。每个村落都有自己的规则和习惯&#xff0c;村里的每户人家&#xff08;设备&#xff0…

Maven pom文件分析

文章目录 project子元素分类项目基础信息- parent- modelVersion- groupId- artifactId- name- version- packaging- description- url- inceptionYear 组织与许可信息- organization- licenses- developers- contributors- mailingLists 自定义属性信息- properties 模块配置-…

修改浏览器地址栏参数

Vue 修改当前页面地址栏参数 function updateUrlParameter(param: string, value: string) {const url new URL(window.location.href); // 获取当前页面的 URL// 解析哈希部分const hash url.hash ? url.hash.slice(1) : "";const [path, queryString] hash.sp…

深度优先搜索(DFS)与回溯法:从全排列到子集问题的决策树与剪枝优化

文章目录 前言&#x1f384;一、全排列✨核心思路✨实现步骤✨代码✨时间和空间复杂度&#x1f381;1. 时间复杂度&#x1f381;2. 空间复杂度 &#x1f384;二、子集✨解法一&#xff1a;逐位置决策法&#x1f381;步骤分析&#x1f381;运行示例&#x1f381;代码 ✨解法二&a…

加密算法之单向散列算法

加密算法 背景&#xff1a; 现有的序列号加密算法大都是软件开发者自行设计的&#xff0c;大部分相当简单。有些算法&#xff0c;其作者虽 然下了很大的工夫&#xff0c;却往往达不到希望达到的效果。其实&#xff0c;有很多成熟的算法可以使用&#xff0c;特别是密 码学中一些…

【FAQ】HarmonyOS SDK 闭源开放能力 —Push Kit(8)

1.问题描述&#xff1a; 在AGC中&#xff0c;推送服务的消息回执新建成功后&#xff0c;有一个有效期 1&#xff0c;这个有效期是什么意思&#xff0c;过期后&#xff0c;会影响什么呢&#xff1f; 2&#xff0c;这个有效期是否可以修改成一直不过期&#xff1f; 解决方案&…

egg初始搭建

前言 egg.js 是由阿里开源的面向企业级开发的 Node.js 服务端框架&#xff0c;它的底层是由 Koa2 搭建。 Github&#xff1a;https://github.com/eggjs/egg&#xff0c;目前 14.8K Star&#xff0c;egg 很适合做中台。 安装 首先&#xff0c;你要 确保 Node 已经配置环境变量…