写在前面
从前我也是个白嫖怪,现在我想要当个被白嫖的人。如果有帮助,希望能留言个下次一定(老b站了hhhh)工程开源说明:分享在电赛期间的每个版本的程序,希望能对大家有帮助,本文档大致进行说明下代码的讲解,懒得下载的人可以通过文章中的代码自行调试。
gitee链接
不习惯github,还是换成国内的平台了。
代码功能描述
(备注:有些部分是直接把正点原子的例程的东西贴过来改写的,所以大家在正点的板子上可能更方便操作,我们使用的板子是自己做的板子,所以屏幕显示可能直接使用正点原子的板子无法正常显示,这个只需要在正点的LCD的例程下面进行修改即可,别的按键功能只需要自行修改管脚即可),波形的幅值大小是根据我们的模拟部分进行拟合的,所以显示的是放大器输出的大约的幅值大小。
使用网版FFT,定时器3采集版本
我们为了保证进行FFT之后的精准程度(分度值),在权衡下,选择了10240HZ为最后的采样频率,采样点数为1024,这样我们得到的频域的数据的分度值是10hz,所以也就能保证我们比较精准取到1k,2k,3k,4k,5k频率下的频域的幅值信息。
大致功能说明:
- 使用PA1进行ADC的采样。
- 采样定时器3进行定时采集(控制采样时钟)。
- 只进行一次采集测试,复位后进行下次采集。
- 绘制频谱和波形图。
版本效果展示
使用FFT库函数,定时器输出PWM波进行采集版本
我们为了保证进行FFT之后的精准程度(分度值),在权衡下,选择了10240HZ为最后的采样频率,采样点数为1024,这样我们得到的频域的数据的分度值是10hz,所以也就能保证我们比较精准取到1k,2k,3k,4k,5k频率下的频域的幅值信息。
只进行讲解说明最终版本的功能:
- 使用PC1进行ADC的采样。(和上个版本略有不同)
- 通过继电器切换控制不同失真状态
- 可以全自动循环采集各种失真情况下的THD。全自动模式下,采集5次求均值进行保存显示
- 可以实现单种状态循环测量
- 可以通过按键切换不同的失真状态(控制继电器)
- 可以显示频谱和波形数据
最终实现效果展示
这里是单独测试程序功能,没有接继电器,全自动模式测试显示的失真状态的汉字可能对不上,随机给了波进行测的,见谅。后面三种波形测试放到了单次循环的模式下。
测试三种1k的信号的理论值
- 正弦:1%以内
- 方波:大约38.87%
- 三角波:大约11.81%
测量1k方波
测试1k正弦波
测试1k三角波
定时器3采集版本代码:
这里的FFT算法同前文,不进行展示。由于c文件比较多,对没有参数声明,只是函数声明的h文件不再进行展示。lcd显示屏的代码不再展示,自行修改显示到自己的平台下。
main.c
/* Includes ------------------------------------------------------------------*/ #include "usart.h" #include "fft.h" #include <math.h> #include "led.h" #include "delay.h" #include "key.h" #include "sys.h" #include "lcd.h" #include "usart.h" #include "adc.h" #include "timer.h" #include <stdio.h> /* Private typedef -----------------------------------------------------------*/ /* Private define ------------------------------------------------------------*/ /* Private macro -------------------------------------------------------------*/ #define N 1024 //采样点数 #define Fs 10240 //采样频率 #define F 10 //分辨率 /* Private variables ---------------------------------------------------------*/ /* Private function prototypes -----------------------------------------------*/ /* Private functions ---------------------------------------------------------*/ extern float data[1024]; extern int end; /*屏幕设计频谱*/ int x11=20; int y11=120,y12=220; //竖 int x21=20,x22=280;//横 int y21=120; /*屏幕设计波形*/ int X1=20,X2=160; int Y1=1,Y2=101; int XX1=20,YY1=1; //FFT测试数据集 输入数组 complex FFT_256PointIn[N]; //FFT测试数据集 输出数组 float FFT_256PointOut[N/2]; float Mag[N/2]; u8 temp1[20]; //填入数组 测试 //void InitBufInArray() //{ // unsigned short i; // for(i=0; i<N; i++) // { // FFT_256PointIn[i].real = 1500 * sin(2*PI * i * 2000.0 / Fs) // +10 * sin(2*PI * i * 1000.0 / Fs) // +4000 * sin(2*PI * i * 4000.0 / Fs); // FFT_256PointIn[i].imag = 0; // } //} void InitBufInArray() { unsigned short i; for(i=0; i<N; i++) { FFT_256PointIn[i].real = data[i]*3.30/4096;// FFT_256PointIn[i].imag = 0; //测试 // printf("%d ",i); // printf("%lf \n",FFT_256PointIn[i].real); } } /****************************************************************** 函数名称:GetPowerMag() 函数功能:计算各次谐波幅值 参数说明: 备 注:先将FFT_256PointIn分解成实部(X)和虚部(Y), 然后计算幅值:(sqrt(X*X+Y*Y)*2/N 然后计算相位:atan2(Y/X) 作 者:土耳其冰激凌 *******************************************************************/ void GetPowerMag() { unsigned short i; float X,Y,P; c_abs(FFT_256PointIn,FFT_256PointOut,N/2); for(i=0; i<N/2; i++) { X = FFT_256PointIn[i].real/N; //计算实部 Y = FFT_256PointIn[i].imag/N; //计算虚部 if(i==0) Mag[i] = FFT_256PointOut[i]/N; //计算幅值 else Mag[i] = FFT_256PointOut[i]*2/N; P = atan2(Y,X)*180/PI; //计算相位 printf("%d ",i); printf("%d ",F*i); printf("%f \r\n",Mag[i]); } } /** *计算欧总谐振失真函数 **/ void GetTHD() { unsigned short i=20;//控制字符平移 float Uo1,Uo2,Uo3,Uo4,Uo5; double THD,thd_fz=0,thd_fm=0; Uo1=Mag[100]; Uo2=Mag[200]; Uo3=Mag[300]; Uo4=Mag[400]; Uo5=Mag[500]; thd_fm=Uo1; thd_fz=Uo2*Uo2 + Uo3*Uo3 + Uo4*Uo4 + Uo5*Uo5; thd_fz=sqrt(thd_fz); THD=thd_fz/thd_fm*100; sprintf((unsigned char *)temp1,"Uo1:%5lfV",Uo1); LCD_ShowString(180,0+i,200,16,16,temp1); sprintf((unsigned char *)temp1,"Uo2:%5lfV",Uo2); LCD_ShowString(180,15+i,200,16,16,temp1); sprintf((unsigned char *)temp1,"Uo3:%5lfV",Uo3); LCD_ShowString(180,30+i,200,16,16,temp1); sprintf((unsigned char *)temp1,"Uo4:%5lfV",Uo4); LCD_ShowString(180,45+i,200,16,16,temp1); sprintf((unsigned char *)temp1,"Uo5:%5lfV",Uo5); LCD_ShowString(180,60+i,200,16,16,temp1); sprintf((unsigned char *)temp1,"THD:%5lf%%",THD); LCD_ShowString(180,75+i,200,16,16,temp1); //测试 // printf("%lf ",Uo1); // printf("%lf ",Uo2); // printf("%lf ",Uo3); // printf("%lf ",Uo4); // printf("%lf ",Uo5); } /************* 画波形图 * */ void Getsignchart() { int i; int con=8,x_con=4; for(i=0;i<35;i++){ if(i==0){ LCD_DrawPoint(20+i*x_con+1,101-data[i+4]*3.30/1024*con); } else { LCD_DrawPoint(20+i*x_con+1,101-data[i+4]*3.30/1024*con); LCD_DrawLine(20+(i-1)*x_con+1, 101-data[i-1+4]*3.30/1024*con, 20+i*x_con+1, 101-data[i+4]*3.30/1024*con); } } int main(void) { int i,t; delay_init();//延时函数初始化 SystemInit();//系统时钟初始化 USART_Configuration();//串口1初始化 Adc_Init(); //ADC初始化 TIM3_Int_Init(780*3+2,2);//1952*2 //TIM3_Int_Init(780,8);//1952*2 //TIM3_Int_Init(98,71);//1952*2 LCD_Init(); //printf("这是一个FFT 测试实验\r\n"); POINT_COLOR=GRAY;//设置字体为红色 LCD_Display_Dir(1); LCD_ShowNum(10,220,0,1,16); //画图频谱区域 for(i=0;i<8;i++){ LCD_DrawLine(XX1, Y1, XX1, Y2); XX1=XX1+20; } for(i=0;i<6;i++){ LCD_DrawLine(X1, YY1, X2, YY1); YY1=YY1+20; } //设计频谱参数 LCD_ShowString(285,200,32,16,16,"fre/"); LCD_ShowString(290,215,24,16,16,"Hz"); LCD_ShowString(0,120,32,16,16,"|A"); LCD_ShowString(1,135,32,16,16,"/V"); LCD_ShowString(65,221,24,16,16,"1k"); LCD_ShowString(110,221,24,16,16,"2k"); LCD_ShowString(158,221,24,16,16,"3k"); LCD_ShowString(208,221,24,16,16,"4k"); LCD_ShowString(265,221,24,16,16,"5k"); for(i=0;i<14;i++){ LCD_DrawLine(x11, y11, x11, y12); x11=x11+20; } for(i=0;i<6;i++){ LCD_DrawLine(x21, y21, x22, y21); y21=y21+20; } // printf("点数 频率 幅值 实部 虚部\n"); // GetPowerMag(); while(1) { if(end==1) //接受一次 { InitBufInArray(); fft(N,FFT_256PointIn); POINT_COLOR=RED;//设置字体为红色 //printf("点数 频率 幅值 实部 虚部\n"); GetPowerMag(); end=0; GetTHD(); Getsignchart(); for(i=0;i<256;i++){ if(i==0){ if(Mag[0]*50==0) LCD_DrawPoint(20+i+1,220); } else { LCD_DrawPoint(20+i+1,220-Mag[i*2]*25); LCD_DrawLine(20+i-1+1, 220-Mag[(i-1)*2]*25, 20+i+1, 220-Mag[i*2]*25); } } } } }
adc.c
#include "adc.h" #include "delay.h" //初始化ADC void Adc_Init(void) { ADC_InitTypeDef ADC_InitStructure; GPIO_InitTypeDef GPIO_InitStructure; RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA |RCC_APB2Periph_ADC1 , ENABLE ); //使能ADC1通道时钟 RCC_ADCCLKConfig(RCC_PCLK2_Div6); //设置ADC分频因子6 72M/6=12,ADC最大时间不能超过14M //PA1 作为模拟通道输入引脚 GPIO_InitStructure.GPIO_Pin = GPIO_Pin_1; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN; //模拟输入引脚 GPIO_Init(GPIOA, &GPIO_InitStructure); ADC_DeInit(ADC1); //复位ADC1,将外设 ADC1 的全部寄存器重设为缺省值 ADC_InitStructure.ADC_Mode = ADC_Mode_Independent; //ADC工作模式:ADC1和ADC2工作在独立模式 ADC_InitStructure.ADC_ScanConvMode = DISABLE; //模数转换工作在单通道模式 ADC_InitStructure.ADC_ContinuousConvMode = ENABLE; //模数转换工作在单次转换模式 ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None; //转换由软件而不是外部触发启动 ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right; //ADC数据右对齐 ADC_InitStructure.ADC_NbrOfChannel = 1; //顺序进行规则转换的ADC通道的数目 ADC_Init(ADC1, &ADC_InitStructure); //根据ADC_InitStruct中指定的参数初始化外设ADCx的寄存器 ADC_RegularChannelConfig(ADC1, ADC_Channel_1, 1, ADC_SampleTime_71Cycles5 ); //ADC1,ADC通道,采样时间为239.5周期 ADC_Cmd(ADC1, ENABLE); //使能指定的ADC1 ADC_ResetCalibration(ADC1); //使能复位校准 while(ADC_GetResetCalibrationStatus(ADC1)); //等待复位校准结束 ADC_StartCalibration(ADC1); //开启AD校准 while(ADC_GetCalibrationStatus(ADC1)); //等待校准结束 ADC_SoftwareStartConvCmd(ADC1, ENABLE); //使能指定的ADC1的软件转换启动功能 }