STM32上实现FFT算法精准测量正弦波信号的幅值、频率和相位差(标准库)

news2024/12/23 7:05:43

在研究声音、电力或任何形式的波形时,我们常常需要穿过表面看本质。FFT(快速傅里叶变换)就是这样一种强大的工具,它能够揭示隐藏在复杂信号背后的频率成分。本文将带你走进FFT的世界,了解它是如何将时域信号转化为频域信号,如何使用STM32F407微控制器和FFT来分析正弦信号的幅值、频率和相位差。

一、FFT介绍

FFT(快速傅里叶变换)是一种将信号从时域(随时间变化的信号)转换为频域(不同频率成分的信号)的算法。做一个比喻:信号=食材,FFT=刀,频域=切好的食材。时域信号:想象你拿到了一块复杂的食材,比如一只未处理的鱼,里面有骨头、肉、皮等。这就是时域信号,它包含所有部分,但你还不能清楚地看到每个成分。FFT:这就像一把精准的,它能快速地把鱼切成不同的部分:肉、骨头、皮等。FFT就像这把刀,把复杂的信号拆分为不同的频率成分,让我们能够看到信号中隐藏的细节。频域信号:处理后的结果就像分好类的食材——你清楚地看到了鱼的骨头、肉和皮,知道每个部分的大小(幅值)和具体成分(频率)。这样你就可以根据需要进一步处理这些“分好类的食材”。FFT像一把高效的刀,能快速、精准地将复杂的信号“解剖”成简单、清晰的频率部分。其优势在于:

1.实现频域分析:FFT将时域信号转换为频域,使我们能够识别信号中的频率成分。对于复杂信号,时域分析可能难以识别其频率特征,而频域分析(如滤波、频谱分析、数据压缩)则能直观地展现信号的频率内容。

2.提高效率:直接计算离散傅里叶变换(DFT)运算复杂度较高,而FFT通过优化算法显著提升计算效率,适用于单片机中实时数据处理。

FFT广泛应用于音频处理、通信系统、图像处理、医学成像、地震数据分析等领域。在电力信号处理中,FFT可帮助清晰识别信号中的基波和谐波成分,协助检测电网中的频率偏移和谐波干扰问题。

二、时域分析 vs 频域分析


时域:时域是我们通常看到的信号,比如正弦波,随时间波动。用电压表测量交流电压时,指针上下波动就是时域信号的表现。时域分析只能告诉我们信号的瞬时变化,无法揭示信号的频率组成。
  
频域:频域是一种数学上的表示方式,用于分析信号的频率成分。在频域中,任何复杂的时域信号都可以分解为正弦波的叠加,因为正弦波是频域中唯一存在的基本波形。

时域分析:这是你在电压表或示波器上看到的信号波形,它显示信号如何随时间变化。时域分析的主要缺点是无法直接告诉你信号中包含哪些频率成分。

频域分析:通过FFT,我们可以将这些时域信号分解为不同的频率成分,就像把一首歌分解成各个音符。可以帮助我们找到信号中的主要频率,如50Hz的基波和其他高频谐波。

三、FFT的实部和虚部

正弦信号可以用以下数学表达式表示: x(t)=A⋅sin(ωt+θ) 其中:

  • A 是振幅,表示信号的最大值。
  • ω 是角频率,单位为弧度/秒(ω=2πf,其中 f 为频率)。
  • θ是相位角,表示信号的初始偏移。
    x(t)=A⋅sin(ωt+θ) 
    本身是一个实数函数,因为它在任何时间点 t 的值都是实数。这个表达式没有直接包含虚数部分,所以可以说它的“虚部”是0。

FFT 变换后,得到的是复数Re+j⋅Im ,包括实部和虚部:

  • 实部 (Re):与余弦分量相关,决定信号的振幅。
  • 虚部 (Im):与正弦分量相关,影响信号的相位。 

计算幅值和相位:

  • 幅值:表示信号在某频率下的强度,通过以下公式计算:幅值 = sqrt(Re^2 + Im^2)

  • 相位:表示信号相对于参考信号的偏移,通过以下公式计算: 相位 = atan2(Im, Re)

通过 FFT 的实部和虚部,我们可以准确地获取信号的幅值和相位,从而深入理解信号的频率特性,包括基波和谐波的影响。

  • 基波:信号的主要频率成分。例如,电网的基波通常是50Hz或60Hz,它代表了信号的基本频率。

  • 谐波:基波频率的整数倍,例如基波的2倍、3倍频率等。谐波会影响电力设备的正常运行,可能导致设备过热或损坏。

四、如何用STM32进行FFT计算?

让我们一步步看看整个计算流程。

1. 信号采集
首先,我们需要使用STM32的ADC模块来采集模拟信号,比如三相交流电。ADC将模拟信号(如电压或电流)转换为数字信号,供后续处理。

采样数量:FFT的计算通常需要2的整数次幂的采样点数(如1024、2048)。采样点数越多,频率分辨率越高。
  
采样频率:采样频率必须至少是信号频率的两倍(奈奎斯特定理)。例如,分析50Hz的信号时,采样频率应至少为100Hz,但通常使用更高的采样频率,比如10kHz,以保证计算精度。下面的代码是1000Hz的信号的,选用的 采样频率是100000Hz。

2. FFT变换
采集到的时域数据通过FFT算法进行处理,转换为频域信息。ARM-DSP库中有现成的FFT函数,可以简化计算过程。

3. 运算结果:幅值、频率和相位差

幅值:信号的振幅大小,表示每个频率成分的强度。基波的幅值代表主要的电压或电流值。
  
频率:FFT能帮助我们识别信号中的不同频率成分,如电网中的50Hz基波及其他谐波。

相位差:对于三相信号,FFT可以帮助我们分析不同相之间的相位差,揭示它们之间的时间延迟。

4. 处理流程概述

整个处理流程如下:

  • 信号采集:定时器触发ADC采样交流电信号。
  • 采样与ADC转换:STM32的ADC将模拟信号转为数字信号。
  • DMA传输:使用DMA自动传输采样数据到内存。
  • FFT计算:利用FFT将时域数据转换为频域数据。
  • 结果提取:从FFT结果中提取幅值、频率和相位差信息。

五、程序实现

硬件:正点原子探索者 V3 STM32F407 开发板,下面是核心代码,代码中使用了ARM提供的数学库(arm_math.h)来实现FFT算法,以及STM32的标准库库来配置定时器、ADC和DMA。通过这些配置,系统能够高效地采集和处理模拟信号,分析其频谱特性。完整代码请在资源下载。

#include "tim_adc_dma_fft.h"
#include "usart.h"
#include "arm_math.h"   
#include "delay.h" 

/*通过ADC采集模拟信号,然后使用DMA将采集的数据传输到内存中,接着通过FFT算法分析信号的频谱,最后通过串口输出信号的基波和谐波的频率、幅值和相位差等信息*/
#define sampledot  4096
#define FFT_LENGTH		4096		//4096点FFT
#define fft_arr 10     // 用于计算FFT采样频率的系数             
#define fft_psc 84       // 用于计算FFT采样频率的系数            

const u32  fft_sample_freq=84000000/(fft_arr*fft_psc);  // 计算FFT采样频率

float fft_inputbuf[FFT_LENGTH*2];	 // FFT输入数组,用于存放复数
float fft_outputbuf[FFT_LENGTH];	 // FFT输出数组,存放幅值
arm_cfft_radix4_instance_f32 scfft;   // FFT实例结构体

u32 sampledata[sampledot]={0};//用于存放ADC采样数据的数组,高16位保存adc2 pa5, 低16位保存adc1 pa6

float phase_difference=0; // 用于存放相位差的变量
float freamp[50];//用于存放各次谐波频率和幅值的数组

// 定时器3初始化函数
void Tim3_Init(u16 arr,u16 psc)
{
	  TIM_TimeBaseInitTypeDef   TIM_TimeBaseInitstruct;          
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3,ENABLE);          
  
	TIM_TimeBaseInitstruct.TIM_Period=arr;   
    TIM_TimeBaseInitstruct.TIM_Prescaler=psc;
	TIM_TimeBaseInitstruct.TIM_CounterMode=TIM_CounterMode_Up;
	TIM_TimeBaseInitstruct.TIM_ClockDivision=TIM_CKD_DIV1;
	TIM_TimeBaseInit(TIM3,&TIM_TimeBaseInitstruct);
	
	//TIM_ITConfig(TIM3,TIM_IT_Update,ENABLE);     
	TIM_SelectOutputTrigger(TIM3, TIM_TRGOSource_Update);	
	TIM_Cmd(TIM3,DISABLE);
}
// ADC初始化函数
void Adc_Init()
{
	GPIO_InitTypeDef  GPIO_InitStructure;
	ADC_CommonInitTypeDef ADC_CommonInitStructure;
	ADC_InitTypeDef       ADC_InitStructure;
	
	
	RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA, ENABLE);
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1, ENABLE);
  	RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC2, ENABLE);
	
	 GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6|GPIO_Pin_5;  //adc 1和2 的通道
	 GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AN;
	 GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL;
	 GPIO_Init(GPIOA, &GPIO_InitStructure);
	
	RCC_APB2PeriphResetCmd(RCC_APB2Periph_ADC1,ENABLE);	
	RCC_APB2PeriphResetCmd(RCC_APB2Periph_ADC1,DISABLE);
	RCC_APB2PeriphResetCmd(RCC_APB2Periph_ADC2,ENABLE);	
	RCC_APB2PeriphResetCmd(RCC_APB2Periph_ADC2,DISABLE); //重置
	
	ADC_CommonInitStructure.ADC_Mode = ADC_DualMode_InjecSimult;
    ADC_CommonInitStructure.ADC_TwoSamplingDelay = ADC_TwoSamplingDelay_5Cycles;
    ADC_CommonInitStructure.ADC_DMAAccessMode = ADC_DMAAccessMode_2;
    ADC_CommonInitStructure.ADC_Prescaler = ADC_Prescaler_Div2; 
    ADC_CommonInit(&ADC_CommonInitStructure);
	
	ADC_InitStructure.ADC_Resolution = ADC_Resolution_12b;
    ADC_InitStructure.ADC_ScanConvMode = DISABLE;	
    ADC_InitStructure.ADC_ContinuousConvMode = DISABLE;
    ADC_InitStructure.ADC_ExternalTrigConvEdge = ADC_ExternalTrigConvEdge_Rising;  
    ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right;	
    ADC_InitStructure.ADC_NbrOfConversion =1;  //通道数
	ADC_InitStructure.ADC_ExternalTrigConv=ADC_ExternalTrigConv_T3_TRGO;
    ADC_Init(ADC1, &ADC_InitStructure);
    ADC_Init(ADC2, &ADC_InitStructure);
    ADC_RegularChannelConfig(ADC2, ADC_Channel_5, 1, ADC_SampleTime_3Cycles);
	ADC_RegularChannelConfig(ADC1, ADC_Channel_6, 1, ADC_SampleTime_3Cycles);
		
   ADC_MultiModeDMARequestAfterLastTransferCmd(ENABLE); //多路转化完后触发dma
   
	ADC_DMACmd(ADC1, ENABLE); 
	
	ADC_Cmd(ADC1, ENABLE);
    ADC_Cmd(ADC2, ENABLE);
}


// DMA初始化函数,用于ADC数据的采集
void Dma_ADC_Init()
{
	
	
	DMA_InitTypeDef  DMA_InitStructure;
	NVIC_InitTypeDef NVIC_InitStructure;	
	RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_DMA2, ENABLE);
	

	DMA_DeInit(DMA2_Stream0);
	
	DMA_InitStructure.DMA_BufferSize= sampledot;
	DMA_InitStructure.DMA_Channel=DMA_Channel_0; 
	DMA_InitStructure.DMA_DIR=DMA_DIR_PeripheralToMemory;	
	DMA_InitStructure.DMA_FIFOMode = DMA_FIFOMode_Enable;         
    DMA_InitStructure.DMA_FIFOThreshold = DMA_FIFOThreshold_HalfFull;	
	
	DMA_InitStructure.DMA_Memory0BaseAddr= (uint32_t)&sampledata ;//要存入的值
	
	DMA_InitStructure.DMA_MemoryBurst=DMA_MemoryBurst_Single;
	DMA_InitStructure.DMA_MemoryDataSize= DMA_MemoryDataSize_Word;
	DMA_InitStructure.DMA_MemoryInc=DMA_MemoryInc_Enable;		
	DMA_InitStructure.DMA_Mode=DMA_Mode_Circular;
	
		
	DMA_InitStructure.DMA_PeripheralBaseAddr=(uint32_t)0x40012308; //adc地址
	DMA_InitStructure.DMA_PeripheralBurst=DMA_PeripheralBurst_Single;
	DMA_InitStructure.DMA_PeripheralDataSize=DMA_PeripheralDataSize_Word;
	DMA_InitStructure.DMA_PeripheralInc=DMA_PeripheralInc_Disable;
	DMA_InitStructure.DMA_Priority=DMA_Priority_High;
	

 
  DMA_Init(DMA2_Stream0, &DMA_InitStructure);
  DMA_ITConfig(DMA2_Stream0, DMA_IT_TC, ENABLE);
  DMA_Cmd(DMA2_Stream0, ENABLE);
	 
  NVIC_InitStructure.NVIC_IRQChannel = DMA2_Stream0_IRQn;  //DMA2_Stream0中断
  NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=0;  //抢占优先级1
  NVIC_InitStructure.NVIC_IRQChannelSubPriority =0;        //子优先级1
  NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;            //IRQ通道使能
  NVIC_Init(&NVIC_InitStructure);
}

// 数据初始化函数,用于初始化ADC、DMA、串口和定时器
void Data_Init()
{
	u32 idex;
	float temp;	
	Adc_Init();
	Dma_ADC_Init();
	uart_init(115200);
	arm_cfft_radix4_init_f32(&scfft,FFT_LENGTH,0,1);//初始化scfft结构体,设定FFT相关参数     //FFT_LENGTH 4096
	Tim3_Init(fft_arr-1,fft_psc-1);
}


// DMA中断服务函数,用于处理ADC数据采集完成后的操作
void DMA2_Stream0_IRQHandler(void)  
{
	u32 idex;	//用于将采集到的数据赋值给fft_inputbuf[2*idex]的计数	
  float bias_voltage2,HZ2,amplitude2,phase2,bias_voltage1,HZ1,amplitude1,phase1;


	u8 temp[40];
	int i;
	u16   freamplen; // freamp长度的一半
	
	
	if(DMA_GetITStatus(DMA2_Stream0, DMA_IT_TCIF0))  //判断DMA传输完成中断  
    {
		
		TIM_Cmd(TIM3,DISABLE);//关闭时钟,进行计算			
		//adc2 pa5
		for(idex=0;idex<sampledot;idex++) //高16位fft,adc2 fft1 //sampledot==4096
		{			
			fft_inputbuf[2*idex]=(u16)(sampledata[idex]>>16)*(3.3/4096);    //生成输入信号实部
			fft_inputbuf[2*idex+1]=0;//虚部全部为0
		}
		arm_cfft_radix4_f32(&scfft,fft_inputbuf);  //fft运算
		arm_cmplx_mag_f32(fft_inputbuf,fft_outputbuf,FFT_LENGTH);	//把运算结果复数求模得幅值	
		freamplen=fft_getpeak(fft_inputbuf,fft_outputbuf+1,freamp,FFT_LENGTH/2,10,5,0.2);//寻找基波和谐波	
		
		bias_voltage2=fft_outputbuf[0]/FFT_LENGTH;//直流 
		HZ2=freamp[0];//频率
		amplitude2=freamp[1];//幅度
		phase2=freamp[2];//相位
		freamp[0]=0;freamp[1]=0;freamp[2]=0;

		//adc1 pa6
		for(idex=0;idex<sampledot;idex++) //低16位fft ,adc1 fft2
		{
			 fft_inputbuf[2*idex]=(u16)(sampledata[idex])*(3.3/4096);    //生成输入信号实部
			 fft_inputbuf[2*idex+1]=0;//虚部全部为0	,
			
		}	
		arm_cfft_radix4_f32(&scfft,fft_inputbuf);  //fft运算
		arm_cmplx_mag_f32(fft_inputbuf,fft_outputbuf,FFT_LENGTH);	//把运算结果复数求模得幅值
		freamplen=fft_getpeak(fft_inputbuf,fft_outputbuf+1,freamp,FFT_LENGTH/2,10,5,0.2); //寻找基波和谐波	
		
		bias_voltage1=fft_outputbuf[0]/FFT_LENGTH;//偏置电压      
		HZ1=freamp[0];//频率
		amplitude1=freamp[1];//幅度
		phase1=freamp[2];//相位
		freamp[0]=0;freamp[1]=0;freamp[2]=0;
		
		phase_difference=phase2-phase1;
	  if(phase_difference>180) phase_difference=phase_difference-180;
	  if(phase_difference<-180) phase_difference=phase_difference+180;
		
		printf("\r\n");    //fft采样频率
		printf("fft_sample_freq:%d\r\n",fft_sample_freq);    //fft采样频率 		
		printf("bias_voltage1:%.2f\r\n",bias_voltage1); //偏置电压 
		printf("bias_voltage2:%.2f\r\n",bias_voltage2); //偏置电压

		printf("HZ1:%.2f\r\n",HZ1);   //频率
		printf("HZ2:%.2f\r\n",HZ2);//频率
		
		printf("amplitude1:%.2f\r\n",amplitude1); //幅值 
		printf("amplitude2:%.2f\r\n",amplitude2);//幅值  
		
		printf("phase_difference:%.2f\r\n",phase_difference);//相位差        
		DMA_ClearITPendingBit(DMA2_Stream0, DMA_IT_TCIF0);
		
	}	
}


// 获取FFT峰值
int fft_getpeak(float *inputx,float *input,float *output,u16 inlen,u8 x,u8 N,float y) //  intlen 输入数组长度,x寻找长度
{                                                                           
	int i,i2;
	u32 idex;  //不同于上一个函数中的,因为他们在不同的函数中被定义
	float datas;
	float sum;
	int outlen=0;
	for(i=0;i<inlen-x;i+=x)
	{
		arm_max_f32(input+i,x,&datas,&idex);   
		if( (input[i+idex]>=input[i+idex+1])&&(input[i+idex]>=input[i+idex-1])&&( (2*datas)/FFT_LENGTH )>y)   
		   {
			   sum=0;   
			   for(i2=i+idex-N;i2<i+idex+N;i2++)   
			   {
				   sum+=input[i2];          
			   }        
			   if(1.5*sum/(2*N)<datas)       
			   {                                                                                             
				     output[3*outlen+2] = atan2(inputx[2*(i+idex+1)+1],inputx[2*(i+idex+1)])*180/3.1415926f;	//计算相位		   
				     output[3*outlen+1] = 1.0*(2*datas)/FFT_LENGTH;   //计算幅度
					   output[3*outlen] = 1.0*fft_sample_freq*(i+idex+1)/FFT_LENGTH;//计算频率
					   outlen++;				   
			   }                                                                                               
               else continue;			   
		   }
			
		else continue;
		
	}
	return outlen;
	
	
}

  1. 定时器初始化 (Tim3_Init): 配置定时器3,用于控制ADC的采样频率。

  2. ADC初始化 (Adc_Init): 配置两个ADC(ADC1和ADC2),用于采集模拟信号。ADC1和ADC2分别连接到不同的通道,采集不同的模拟信号。

  3. DMA初始化 (Dma_ADC_Init): 配置DMA,用于将ADC采集的数据直接传输到内存中,减少CPU的负担。

  4. 数据初始化 (Data_Init): 调用上述初始化函数,完成系统的基本配置。

  5. FFT峰值获取函数 (fft_getpeak): 该函数用于在FFT结果中寻找峰值,这些峰值代表了信号中的基波(主要频率成分)。下面是核心中的核心。

    		arm_cfft_radix4_f32(&scfft,fft_inputbuf);  //fft运算
    		arm_cmplx_mag_f32(fft_inputbuf,fft_outputbuf,FFT_LENGTH);	//把运算结果复数求模得幅值
    		freamplen=fft_getpeak(fft_inputbuf,fft_outputbuf+1,freamp,FFT_LENGTH/2,10,5,0.2); //寻找基波和谐波

  6. DMA中断服务函数 (DMA2_Stream0_IRQHandler): 当DMA传输完成时,该函数会被调用。它负责执行FFT算法,计算信号的偏置电压、频率、幅度、相位的相关信息并用串口1打印。 
     

六、现象

1.信号发生器输入信号源。
信号1:1000Hz的信号的,幅度1V,偏置电压0.5V,相位0°;

信号2:1000Hz的信号的,幅度1.2V,偏置电压0.6V,相位50°;

2.串口收到测量结果与输入信号源的实际接近。

七、总结

FFT(快速傅里叶变换)是一种强大的技术,它允许我们将信号从时域转换到频域,从而深入分析其频率成分。这种转换揭示了信号隐藏的频率特性,为我们提供了一个全新的视角来观察和理解信号的行为。

通过上述介绍,我们探讨了如何使用STM32微控制器执行FFT计算,以提取信号的幅值、频率和相位。
希望这些内容能够为大家提供有价值的参考和指导。在实际应用中,理解和运用FFT的原理和技巧,将有助于我们更有效地处理和分析各种复杂的信号。

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

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

相关文章

【Android】【Compose】实现列表数据添加

序言 在使用列表的时候&#xff0c;以前是使用 Layout 布局里面添加Recyclerview进行列表的显示&#xff0c;但是在Compose里面&#xff0c;是没有这个Recyclerview使用的&#xff0c;那Compose怎么实现列表数据呢&#xff1f; 使用 【LazyColumn】 首先创建一个Compose项目…

java,深拷贝和浅拷贝

在 Java 中&#xff0c;深拷贝&#xff08;Deep Copy&#xff09;和浅拷贝&#xff08;Shallow Copy&#xff09;是对象拷贝的两种方式&#xff0c;主要区别在于它们如何处理对象的内部引用。 目录 一、浅拷贝&#xff08;Shallow Copy&#xff09; 实现方式 二、深拷贝&…

国际商城系统怎么弄 跨境电商商城怎样上线

国际商城系统一般涉及多个关键步骤。首先&#xff0c;需要选择合适的平台或开发工具&#xff0c;如商淘云国际电商系统或自定义开发。其次&#xff0c;系统应支持多语言、多币种以及国际支付网关&#xff0c;以满足全球客户的需求。第三&#xff0c;确保系统具有强大的物流和配…

推荐5款AI论文大纲生成器,一键极速生成!

在当今学术研究和写作领域&#xff0c;AI论文大纲生成器的出现极大地提高了写作效率和质量。以下是五款功能强大且全面的AI论文大纲生成器推荐&#xff1a; 一、千笔-AIPassPaper 千笔-AIPassPaper是一款基于深度学习和自然语言处理技术的AI写作助手&#xff0c;旨在帮助用户…

新160个crackme - 058-CZG-crackme1

运行分析 按下OK键后&#xff0c;程序退出 PE分析 C程序&#xff0c;32位&#xff0c;无壳 静态分析&动态调试 ida函数栏发现winMain(x,x,x,x)&#xff0c;即打开窗口&#xff0c;双击函数跟进 继续跟进 双击DialogFunc函数&#xff0c;这个是窗口逻辑 继续跟进sub_401090函…

数据结构----栈和队列

&#xff08;一&#xff09;栈 1.栈的概念及结构 栈&#xff1a;一种特殊的线性表&#xff0c;其只允许在固定的一端进行插入和删除元素操作。进行数据插入和删除操作的一端 称为栈顶&#xff0c;另一端称为栈底。栈中的数据元素遵守后进先出LIFO&#xff08;Last In First …

GFS 分布式文件系统 GlusterFS

一、GlusterFS概述 1.1、GlusterFS简介 GlusterFS 是一个开源的分布式文件系统。由存储服务器、客户端以及NFS/Samba 存储网关&#xff08;可选&#xff0c;根据需要选择使用&#xff09;组成。 包括其去中心化&#xff08;无元数据服务器&#xff09;的特性&#xff0c;这有…

【苍穹外卖】总结

1 pom 依赖 1.1 MyBatis Spring 用于简化 MyBatis 与 Spring Boot 的集成&#xff0c;提供了对 MyBatis 框架的自动配置支持&#xff0c;简化了数据访问层的开发 1.2 Lombok Lombok 是一个 Java 库&#xff0c;能够通过注解自动生成常见的代码&#xff08;如 getter、setter、…

双亲委派机制知识点

类加载器 双亲委派模型 为什么采用双亲委派模型 打破双亲委派机制的场景 Tomcat 打破双亲委派机制:目的是可以加载不同版本的jar包 实现类隔离&#xff1a;在Tomcat中&#xff0c;每个Web应用使用独立的类加载器加载类文件&#xff0c;这样做的好处在于&#xff0c;当在同一T…

C++二叉搜索树(二叉树进阶)

个人主页&#xff1a;C忠实粉丝 欢迎 点赞&#x1f44d; 收藏✨ 留言✉ 加关注&#x1f493;本文由 C忠实粉丝 原创 C二叉搜索树(二叉树进阶) 收录于专栏 [C进阶学习] 本专栏旨在分享学习C的一点学习笔记&#xff0c;欢迎大家在评论区交流讨论&#x1f48c; 目录 1. 二叉搜索树…

Java重修笔记 第五十七天 坦克大战(七)多线程基础 - 编程练习

1. 线程之间的协调控制&#xff08;通知方式&#xff09; public class Homework04 {public static void main(String[] args) {// 在 main 方法中启动两个线程// 第一个线程内循环打印 1 到 100 以内的整数// 直到第二个线程从键盘读取到 "Q" 指令后结束第一个线程…

Porcupine - 语音关键词唤醒引擎

文章目录 一、关于 Porcupine特点用例尝试一下 语言支持性能 二、Demo1、Python Demo2、iOS DemoBackgroundService DemoForegroundApp Demo 3、网页 Demo3.1 Vanilla JavaScript 和 HTML3.2 Vue Demos 三、SDK - Python 一、关于 Porcupine Porcupine 是一个高度准确和轻量级…

LC并联电路在正弦稳态下的传递函数推导(LC并联谐振选频电路)

LC并联电路在正弦稳态下的传递函数推导&#xff08;LC并联谐振选频电路&#xff09; 本文通过 1.解微分方程、2.阻抗模型两种方法推导 LC 并联选频电路在正弦稳态条件下的传递函数&#xff0c;并通过仿真验证不同频率时 vo(t) 与 vi(t) 的幅值相角的关系。 电路介绍 已知条件…

Axure RP实战:打造高效图形旋转验证码

Axure RP实战&#xff1a;打造高效图形旋转验证码 在数字产品设计的海洋中&#xff0c;验证码环节往往是用户交互体验的细微之处&#xff0c;却承载着验证用户身份的重要任务。 传统的文本验证码虽然简单直接&#xff0c;但随着用户需求的提高和设计趋势的发展&#xff0c;它…

vue2的diff算法

Vue2 的虚拟 DOM diff 算法是一种高效的算法&#xff0c;用于比较新旧两个虚拟 DOM 树&#xff0c;找出差异并更新到真实 DOM 上。这个算法的核心在于尽量减少不必要的 DOM 操作&#xff0c;提高性能。 虚拟dom&#xff1a;把DOM数据化&#xff0c;先通过不断地操作数据&#…

如何在手机端跑大模型?

最近新入手了一台 arm 开发板&#xff0c;内置安装了 Android 13 系统。 昨天把网络问题给解决了&#xff1a;安卓连接 WIFI 但无法上网&#xff1f;盘点踩过的那些坑 今日分享&#xff0c;继续带大家实操&#xff1a;如何把大模型&#xff08;LLM&#xff09;部署到移动端&a…

文章资讯职场话题网站源码整站资源自带2000+数据

介绍&#xff1a; 数据有点多&#xff0c;数据资源包比较大&#xff0c;压缩后还有250m左右。值钱的是数据&#xff0c;网站上传后直接可用&#xff0c;爽飞了 环境&#xff1a;NGINX1.18 mysql5.6 php7.2 代码下载

JUC学习笔记(三)

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 八、共享模型之工具--JUC8.1 AQS 原理1. 概述2 实现不可重入锁自定义同步器自定义锁 3.心得起源目标设计1) state 设计2&#xff09;阻塞恢复设计3&#xff09;队列…

学习笔记 韩顺平 零基础30天学会Java(2024.9.16)

P563 自定义泛型方法 当调用方法时&#xff0c;要传入参数&#xff0c;因为当传入参数时&#xff0c;编译器就可以确定泛型代表的类型 泛型方法和方法使用了泛型是不一样的 泛型方法可以使用类声明的泛型&#xff0c;也可以使用自己的泛型 P564 泛型方法练习 P565 泛型的继承和…

Python编码系列—Python适配器模式:无缝集成的桥梁

&#x1f31f;&#x1f31f; 欢迎来到我的技术小筑&#xff0c;一个专为技术探索者打造的交流空间。在这里&#xff0c;我们不仅分享代码的智慧&#xff0c;还探讨技术的深度与广度。无论您是资深开发者还是技术新手&#xff0c;这里都有一片属于您的天空。让我们在知识的海洋中…