2018电赛A题记录

2019-04-13 12:52发布

今年的电赛有很多的遗憾,因为很多原因,没有自己参加比赛,但是在比赛的最后,和几位参赛的伙伴们一起思考了下电赛的A题。 我主要是思考程序部分,其实从第四问可以看到,这道题的意图十分明显,就是让我们对整个电路做一个频谱分析,也就是电赛这么多年,省赛的难度又绕回去了,要求做了一个低频的频谱分析仪,不过说实话,精度确实是一个难点,最后我们在实现幅度显示的时候用了分段函数校准还是会有比较大的偏差,所以现在先这样写出来,日后再看其他队伍的解决方法。   做频谱分析,主要手段是采集信号,然后对采集到的信号做傅里叶变换变换。关于stm32f429的fft变换,首先是DSP库的使用教程,这个是可以参考正点原子的f429的配套教学文档配置。然后是具体代码的编写,对于具体代码,个人觉得网上所列写的代码和正点原子的代码还不够精简,一个比较好的参考是在stm32中文网上下载的一个stm32f3系列的参考例程,里面主要用到的函数为: arm_cfft_f32(&arm_cfft_sR_f32_len1024, testInput_f32_10khz, ifftFlag, doBitReverse); /*信号的预处理,方便后面计算幅值*/ /* Process the data through the Complex Magnitude Module for calculating the magnitude at each bin */ /*计算幅度(模值)*/ arm_cmplx_mag_f32(testInput_f32_10khz, testOutput, fftSize); 本人在使用fft的时候其实非常快,只要将ADC采集的数据存放在一个采集电压的数组中,然后利用下面的对应关系: for(i=0; i<1024; i++) { testInput_f32_10khz[i*2+1] = 0; testInput_f32_10khz[i*2] = AM_50_ADC_Data[i]; } 将采集的信号转换为可以处理的形式,这里数组的奇数部分代表的是虚部,偶数部分代表的是实部。前两个函数中ifftFlag 用于指定是傅里叶变换(0)还是反傅里叶变换(1), bitReverseFlag 用于设置是否按位取反,为后面计算这里一般取一。然后后面的代码在这段例程的基础上做修改即可。参考例程可以在这里下载 但是实际的难点并不在这里,实际的难点是做完了fft之后的数据处理。因为fft是官方的库,利用官方现成的东西的确简单而又轻松,真正考验基本功的反而是理论知识。 关于理论分析,我将再在另外一篇博客中讲解,这篇文章主要讲我的调试过程。 因为我们的比赛要求的信号的频率为50hz到1000hz内,而我的采样点数有限,1046个(当然可以更大,但是没有意义),必然导致如果采样率过高,那么对于较低频率的信号,采到的数据的频谱基波的点数会十分靠前,并且甚至在1046个数据点中采集不到完整信号。所以就要求修改adc的采样频率。我们选择最高频率的6倍,即采样率为6Khz。 比较理想的情况是用定时器定时触发adc来实现采样率的精准确定,但是因为我比较懒,所以就没有这样。我采用的是软件触发的方式,也就是在程序中利用程序的运行触发。 在此之前,需要解决一个ADC自身的采样时间的问题。对于ADC本身,如果选择连续模式的话(之所以会分析连续模式,因为程序一开始包括前面的例程都是ADC连续模式),那么采样率就是采样时间加上采样间隔时间的倒数,其中,采样时间和ADC时钟和转换周期有关,ADC 的转换时间可以由以下公式计算:
                                                                          Tcovn=采样时间+12 个周期 采样时间在初始化的时间里就能选定为几个周期。 下面给出一段ADC的初始化代码: ADC_CommonInitStructure.ADC_Mode = ADC_Mode_Independent; // 时钟为fpclk x分频 ADC_CommonInitStructure.ADC_Prescaler = ADC_Prescaler_Div8; //八分频 // 禁止DMA直接访问模式 ADC_CommonInitStructure.ADC_DMAAccessMode = ADC_DMAAccessMode_Disabled; // 采样时间间隔 ADC_CommonInitStructure.ADC_TwoSamplingDelay = ADC_TwoSamplingDelay_20Cycles; ADC_CommonInit(&ADC_CommonInitStructure); // -------------------ADC Init 结构体 参数 初始化-------------------------- ADC_StructInit(&ADC_InitStructure); // ADC 分辨率 ADC_InitStructure.ADC_Resolution = ADC_Resolution_12b; // 禁止扫描模式,多通道采集才需要 ADC_InitStructure.ADC_ScanConvMode = DISABLE; // 连续转换 ADC_InitStructure.ADC_ContinuousConvMode =DISABLE; //禁止外部边沿触发 ADC_InitStructure.ADC_ExternalTrigConvEdge = ADC_ExternalTrigConvEdge_None; //外部触发通道,本例子使用软件触发,此值随便赋值即可 ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_T1_CC1; //数据右对齐 ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right; //转换通道 1个 ADC_InitStructure.ADC_NbrOfConversion = 1; ADC_Init(RHEOSTAT_ADC, &ADC_InitStructure); //--------------------------------------------------------------------------- // 配置 ADC 通道转换顺序为1,第一个转换,采样时间为480个时钟周期 ADC_RegularChannelConfig(RHEOSTAT_ADC, RHEOSTAT_ADC_CHANNEL, 1, ADC_SampleTime_480Cycles);//480个ADC周期 // 使能DMA请求 after last transfer (Single-ADC mode) ADC_DMARequestAfterLastTransferCmd(RHEOSTAT_ADC, ENABLE); // 使能ADC DMA ADC_DMACmd(RHEOSTAT_ADC, ENABLE); // 使能ADC ADC_Cmd(RHEOSTAT_ADC, ENABLE); //开始adc转换,软件触发 ADC_SoftwareStartConv(RHEOSTAT_ADC); 几个比较重要的关于采样率的,首先是ADC时钟的选择,是高速时钟二的分频,然后是连续模式下的采样间隔,最后还有转换的周期。转换周期越大越准。这里我们选取了最大。不过按这样算,采样率也远大于了我们所需要的6khz。而我又懒得去用定时器,所以一个可行的方法是自己强行delay来实现采样率的降低,这里我们同时使用了DMA,DMA的代码比较简单,最后可以见我上传的代码,所以可以列写控制采样率的程序如下所示: while(! DMA_GetFlagStatus(DMA2_Stream0,DMA_FLAG_TCIF0))//等待数据流传输结束 { while(!ADC_GetFlagStatus(ADC1, ADC_FLAG_STRT));//只有在ADC没有工作的时候才能让ADC工作 Delay(3000); ADC_SoftwareStartConv(ADC1); }; 这里我们在每次触发ADC工作的时候都用了delay来延时从而降低了采样率。   其实最难的部分还是在于我做了fft变换之后,我需要从频谱中找到和真实信号的对应关系,这个就需要不断的画图。不过,我们首先要做的一点是要将基波分量和谐波分量找出来,其实这个比较简单,找基波分量,实质上是找最后处理得到的数据的最大值,而找谐波分量,其对应的点数是最大值的整数倍,为了保证谐波分量准确性,我也在旁边点找了下最值,基本通过这种方式,信号的极值算是找出来了。 具体的代码如下所示: for(i=1;i<512;i++) { if(PeiXingYuanOutput[i]>Base_Frequence_Amplitude) { Base_Frequence_Amplitude=PeiXingYuanOutput[i]; Base_Frequence=i; } }//找出基波频率和对应的幅值,然后可以根据这个基波来求出谐波 for(i=2;i<20;i++) { if(Base_Frequence*i<512) { for(t=0;t<5;t++) { if(PeiXingYuanOutput[i*Base_Frequence-2+t]>Amplitude[i]) { Amplitude[i]=PeiXingYuanOutput[i*Base_Frequence-2+t]; Frequence[i]=(i*Base_Frequence-2+t)*Frequence_X; } } FanWei=i; } } //找出可能的谐波分量值 这里的范围,是限定谐波分量可以不用取太大的值,因为题中要求测量1000Hz之内的信号。 然后是最后做的fft变换的个频率的幅值和真实值之间有一个关系,而这个关系又是跟频率有关的一个多项式,剩下的问题就是做多次实际测量找出关系然后拟合了。拟合的代码见上传文件即可。 电赛代码在这里