2020电赛E题--非线性失真器程序设计--01--算法仿真与STM32FFT数据验证(附工程源码+gitee链接)

本文涉及的产品
网络型负载均衡 NLB,每月750个小时 15LCU
应用型负载均衡 ALB,每月750个小时 15LCU
传统型负载均衡 CLB,每月750个小时 15LCU
简介: 2020电赛E题--非线性失真器程序设计--01--算法仿真与STM32FFT数据验证(附工程源码+gitee链接)

写在前面


20电赛整体感觉难度比之前小,本次程序设计上也没有太多的难点。功能指标全部完成,程序实现了测量每种失真的情况下的THD的近似值。并且进行了程序拓展,实现了全自动的测量,以及显示测量波形的波形图,频谱图。根据题目要求,我们可以看出这次程序设计要用到FFT算法。

我们的程序设计有两个版本,一个版本是通过定时器进行采样得到特定采样率下的数据并保存在数组里,然后进行傅里叶变换,另外一种就是通过定时器产生PWM波生成ADC的采样时钟,直接通过DMA保存数据然后进行傅里叶变换。

在理论计算下,

所以本文主要介绍我们实现的FFT的功能测试验证的程序。

题目


image.png

比赛指标要求


image.png

为什么需要FFT?


任何连续测量的时域信号都可以表示为不同频率的正弦波信号的无限叠加。以累加的方式来计算该信号中不同信号的频率、振幅和相位。所以本次测量就必须要使用FFT算法。

原理部分就引用下别人的帖子大家自行查看:

超详细易懂FFT(快速傅里叶变换)及代码实现

知识科普:THD


总谐波失真表明功放工作时,由于电路不可避免的振荡或其他谐振产生的二次,三次谐波与实际输入信号叠加,在输出端输出的信号就不单纯是与输入信号完全相同的成分,而是包括了谐波成分的信号,这些多余出来的谐波成分与实际输入信号的对比,用百分比来表示就称为总谐波失真。一般来说,总谐波失真在500赫兹附近最小,所以大部分功放表明总谐波失真是用500赫兹信号做测试,但有些更严格的厂家也提供20-20000赫兹范围内的总谐波失真数据。总谐波失真在1%以下,一般耳朵分辨不出来,超过10%就可以明显听出失真的成分。这个总谐波失真的数值越小,音色就更加纯净。一般产品的总谐波失真都小于1%@500Hz,但这个数值越小,表明产品的品质越高。

所以在进行测试前我们就要先有个概念
对于信号源输出的1k的正弦信号,总谐波失真的近似值越小,表示程序更精准,基本在1.0%以内。
对于信号源输出的1k的方波信号,总谐波失真的近似值大约是0.3887(前5次谐波计算的近似值)

失真度测试仪测量的结果:


正弦波


image.png

方波


image.png

这里解释下为啥方波测量出来的是44.26%,这里先给出方波的傅里叶变换式子

image.png

因为对于近似值来说方波取前五次傅里叶变换的值就是0.3887

image.png

计算到前7次时候

image.png

MTLAB仿真测试


所以根据已有的知识,进行下MATLAB仿真测试

clf;fs=10240; %采样频率
Ndata=1024; %数据长度
N=1024; %FFT的数据长度
n=0:Ndata-1;
t=n/fs;   %数据对应的时间序列
x=0.5*sin(2*pi*1000*t)+1;   %时间域信号
%subplot(2,2,4),plot(t,x);
subplot(2,2,2),plot(t,x,'.--');
y=fft(x,N);   %信号的Fourier变换
mag=abs(y);    %求取振幅
f=(0:N-1)*fs/N; %真实频率
subplot(2,2,1),plot(f(1:N/2),mag(1:N/2)*2/N); %绘出Nyquist频率之前的振幅
xlabel('频率/Hz');ylabel('振幅');
title('Ndata=10240 Nfft=1024');grid on;

image.png

这里仿真显示的频谱图和我们的代码模拟给出的输入信号是相同的所以大致可以按照这个傅里叶变换的标准进行编写代码。之所以这里画出采样的波形图是因为后面我们程序要画波形图,所以这里就测试了下。理想波形的总谐波失真计算没有意义所以就不进行计算。

STM32测试程序:


FFT.C


这里的FFT也是找到的别人写好的程序,所以就不做详细讲解了(能力有限)

#include "math.h"
#include "fft.h"
//精度0.0001弧度
//复数的交换 
void conjugate_complex(int n,complex in[],complex out[])
{
  int i = 0;
  for(i=0;i<n;i++)
  {
    out[i].imag = -in[i].imag;
    out[i].real = in[i].real;
  } 
}
//求所有复数的模
void c_abs(complex f[],float out[],int n)
{
  int i = 0;
  float t;
  for(i=0;i<n;i++)
  {
    t = f[i].real * f[i].real + f[i].imag * f[i].imag;
    out[i] = sqrt(t);
  } 
}
//求复数的和 
void c_plus(complex a,complex b,complex *c)
{
  c->real = a.real + b.real;
  c->imag = a.imag + b.imag;
}
//求复数的差  
void c_sub(complex a,complex b,complex *c)
{
  c->real = a.real - b.real;
  c->imag = a.imag - b.imag;  
}
//求复数的积
void c_mul(complex a,complex b,complex *c)
{
  c->real = a.real * b.real - a.imag * b.imag;
  c->imag = a.real * b.imag + a.imag * b.real;  
}
//求复数的商 
void c_div(complex a,complex b,complex *c)
{
  c->real = (a.real * b.real + a.imag * b.imag)/(b.real * b.real +b.imag * b.imag);
  c->imag = (a.imag * b.real - a.real * b.imag)/(b.real * b.real +b.imag * b.imag);
}
#define SWAP(a,b)  tempr=(a);(a)=(b);(b)=tempr
void Wn_i(int n,int i,complex *Wn,char flag)
{
  Wn->real = cos(2*PI*i/n);
  if(flag == 1)
  Wn->imag = -sin(2*PI*i/n);
  else if(flag == 0)
  Wn->imag = -sin(2*PI*i/n);
}
//傅里叶变化
void fft(int N,complex f[])
{
  complex t,wn;//中间变量
  int i,j,k,m,n,l,r,M;
  int la,lb,lc;
  /*----计算分解的级数M=log2(N)----*/
  for(i=N,M=1;(i=i/2)!=1;M++); 
  /*----按照倒位序重新排列原信号----*/
  for(i=1,j=N/2;i<=N-2;i++)
  {
    if(i<j)
    {
      t=f[j];
      f[j]=f[i];
      f[i]=t;
    }
    k=N/2;
    while(k<=j)
    {
      j=j-k;
      k=k/2;
    }
    j=j+k;
  }
  /*----FFT算法----*/
  for(m=1;m<=M;m++)
  {
    la=pow(2,m); //la=2^m代表第m级每个分组所含节点数   
    lb=la/2;    //lb代表第m级每个分组所含碟形单元数
                 //同时它也表示每个碟形单元上下节点之间的距离
    /*----碟形运算----*/
    for(l=1;l<=lb;l++)
    {
      r=(l-1)*pow(2,M-m); 
      for(n=l-1;n<N-1;n=n+la) //遍历每个分组,分组总数为N/la
      {
        lc=n+lb;  //n,lc分别代表一个碟形单元的上、下节点编号     
        Wn_i(N,r,&wn,1);//wn=Wnr
        c_mul(f[lc],wn,&t);//t = f[lc] * wn复数运算
        c_sub(f[n],t,&(f[lc]));//f[lc] = f[n] - f[lc] * Wnr
        c_plus(f[n],t,&(f[n]));//f[n] = f[n] + f[lc] * Wnr
      }
    }
  }
}
//傅里叶逆变换
void ifft(int N,complex f[])
{
  int i=0;
  conjugate_complex(N,f,f);
  fft(N,f);
  conjugate_complex(N,f,f);
  for(i=0;i<N;i++)
  {
    f[i].imag = (f[i].imag)/N;
    f[i].real = (f[i].real)/N;
  }
}
struct compx EE(struct compx b1,struct compx b2)
{
  struct compx b3;
  b3.real = b1.real*b2.real-b1.imag*b2.imag;
  b3.imag = b1.real*b2.imag+b1.imag*b2.real;
  return (b3);
}
void FFT(struct compx *xin,int N)
{
int f,m,LH,nm,i,k,j,L;
double p , ps ;
int le,B,ip;
float pi;
struct compx w,t;
LH=N/2; 
f=N;
for(m=1;(f=f/2)!=1;m++){;}  /*2^m=N*/
{
for(L=m;L>=1;L--)    /*这里和时域的也有差别*/
{ 
le=pow(2,L);
B=le/2; /*每一级碟形运算间隔的点数*/
pi=3.14159;
 for(j=0;j<=B-1;j++)
  {
   p=pow(2,m-L)*j;
   ps=2*pi/N*p;
   w.real=cos(ps);
   w.imag=-sin(ps);
   for(i=j;i<=N-1;i=i+le)
     {
      ip=i+B;  
      t=xin[i];
      xin[i].real=xin[i].real+xin[ip].real;
      xin[i].imag=xin[i].imag+xin[ip].imag;  
      xin[ip].real=xin[ip].real-t.real;
      xin[ip].imag=xin[ip].imag-t.imag;     
      xin[ip]=EE(xin[ip],w);
     }
  }
}
}
/*变址运算*/
nm=N-2;   
j=N/2;
for(i=1;i<=nm;i++)
{
if(i<j){t=xin[j];xin[j]=xin[i];xin[i]=t;}
k=LH;
while(j>=k){j=j-k;k=k/2;}
j=j+k;
}
}

FFT.H


#ifndef __FFT_H__
#define __FFT_H__
typedef struct complex //复数类型
{
  float real;   //实部
  float imag;   //虚部
}complex;
struct compx
{
  double real;
  double imag;
};
#define PI 3.1415926535897932384626433832795028841971
///
void conjugate_complex(int n,complex in[],complex out[]);
void c_plus(complex a,complex b,complex *c);//复数加
void c_mul(complex a,complex b,complex *c) ;//复数乘
void c_sub(complex a,complex b,complex *c); //复数减法
void c_div(complex a,complex b,complex *c); //复数除法
void fft(int N,complex f[]);//傅立叶变换 输出也存在数组f中
void ifft(int N,complex f[]); // 傅里叶逆变换
void c_abs(complex f[],float out[],int n);//复数数组取模
 void FFT(struct compx *xin,int N);
#endif

main.c


程序是根据网上的程序更改参考的,用的是别人自己写的FFT,把两个人写的放到了一起,大家可以根据需要自己选择如何调用,后面会更新使用官方库版本的FFT的代码版本。串口部分就使用串口1就行,如果是正点的板子程序改写是默认打开了串口1的。

/* Includes ------------------------------------------------------------------*/
#include "main.h"
#include "usart.h"
#include "fft.h"
#include <math.h>
/* Private typedef -----------------------------------------------------------*/
/* Private define ------------------------------------------------------------*/
/* Private macro -------------------------------------------------------------*/
#define  N    1024          //采样点数
#define  Fs   10240        //采样频率
#define  F    10          //分辨率
/* Private variables ---------------------------------------------------------*/
/* Private function prototypes -----------------------------------------------*/
/* Private functions ---------------------------------------------------------*/
//FFT测试数据集 输入数组
complex  FFT_256PointIn[N];
//FFT测试数据集 输出数组
float   FFT_256PointOut[N/2];                   
//填入数组      
double result[N];
struct compx s[N];
void InitBufInArray()
{
 unsigned short i;
 for(i=0; i<N; i++)    
  {
       FFT_256PointIn[i].real  = 1 * sin(2*PI * i * 1000.0 / Fs) 
                                 +1;
       FFT_256PointIn[i].imag = 0;
    } 
}
/******************************************************************
函数名称:GetPowerMag()
函数功能:计算各次谐波幅值
参数说明:
备  注:先将FFT_256PointIn分解成实部(X)和虚部(Y),
         然后计算幅值:(sqrt(X*X+Y*Y)*2/N
         然后计算相位:atan2(Y/X)
*******************************************************************/
void GetPowerMag()
{
    unsigned short i;
    float  X,Y,P,Mag;
    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;    //计算虚部
        Mag = FFT_256PointOut[i]*2/N;    //计算幅值
        P = atan2(Y,X)*180/PI;           //计算相位
        printf("%d      ",i);
        printf("%d      ",F*i); 
        printf("%f      \r\n",Mag);
//        printf("%f      ",P);
//        printf("%f      ",X);
//        printf("%f      \r\n",Y);     
    }
}
void dsp_g2_test()
{
  u16 i=0;
  for(i=0;i<N;i++)
  {
    s[i].real = 32000 * sin(PI*2*i*(50.0f/Fs));
    s[i].real+= 16000 * sin(PI*2*i*(550.0f/Fs));
    s[i].real+= 9000 * sin(PI*2*i*(1150.0f/Fs));
    s[i].real+= 6000 * sin(PI*2*i*(2100.0f/Fs));
    s[i].real+= 4000 * sin(PI*2*i*(5000.0f/Fs));
    s[i].imag=0;
  }
  FFT(s,N);
  for(i=0;i<N/2;i++)
  {
    if(i==0)
      result[i] = sqrt(s[i].real * s[i].real + s[i].imag * s[i].imag)/N;
    else
      result[i] = sqrt(s[i].real * s[i].real + s[i].imag * s[i].imag)*2/N;
    printf("%d      ",i);
    printf("%d      ",10*i);
    printf("%f      \r\n",result[i]);
    //if(result[i] > 10)
    //printf("%4d,%4d,%ld\n",i,(u16)((double)i*Fs/NPT),(u32)result[i]);
  }
}
/**
  * @brief  串口打印输出
  * @param  None
  * @retval None
  */
int main(void)
{
  int i,t;
  SystemInit();//系统时钟初始化
  USART_Configuration();//串口1初始化
  printf("这是一个FFT 测试实验\r\n");  
  InitBufInArray(); 
  fft(N,FFT_256PointIn);
  printf("点数   频率  幅值   实部  虚部\n"); 
  //GetPowerMag();
  dsp_g2_test();
  while(1)
  {
    //检查接收数据完成标志位是否置位 
    if(USART_GetFlagStatus(USART1, USART_IT_RXNE) != RESET)
    {
    //将接收到的数据发送出去,对USART_DR的读操作可以将USART_IT_RXNE清零。
    printf("%c",USART_ReceiveData(USART1));
    }
  }
}
/*********************************END OF FILE**********************************/

串口的截图结果是正确的。

image.png

免费开源,大家参考,不通过csdn骗积分了,如果有点收获希望能给个关注和点赞。

工程链接

image.png

相关实践学习
小试牛刀,一键部署电商商城
SAE 仅需一键,极速部署一个微服务电商商城,体验 Serverless 带给您的全托管体验,一起来部署吧!
负载均衡入门与产品使用指南
负载均衡(Server Load Balancer)是对多台云服务器进行流量分发的负载均衡服务,可以通过流量分发扩展应用系统对外的服务能力,通过消除单点故障提升应用系统的可用性。 本课程主要介绍负载均衡的相关技术以及阿里云负载均衡产品的使用方法。
目录
打赏
0
0
0
0
35
分享
相关文章
基于GA遗传算法的拱桥静载试验车辆最优布载matlab仿真
本程序基于遗传算法(GA)实现拱桥静载试验车辆最优布载的MATLAB仿真,旨在自动化确定车辆位置以满足加载效率要求(0.95≤ηq≤1.05),目标是使ηq尽量接近1,同时减少车辆数量和布载耗时。程序在MATLAB 2022A版本下运行,展示了工况1至工况3的测试结果。通过优化模型,综合考虑车辆重量、位置、类型及车道占用等因素,确保桥梁关键部位承受最大荷载,从而有效评估桥梁性能。核心代码实现了迭代优化过程,并输出最优布载方案及相关参数。
基于MobileNet深度学习网络的活体人脸识别检测算法matlab仿真
本内容主要介绍一种基于MobileNet深度学习网络的活体人脸识别检测技术及MQAM调制类型识别方法。完整程序运行效果无水印,需使用Matlab2022a版本。核心代码包含详细中文注释与操作视频。理论概述中提到,传统人脸识别易受非活体攻击影响,而MobileNet通过轻量化的深度可分离卷积结构,在保证准确性的同时提升检测效率。活体人脸与非活体在纹理和光照上存在显著差异,MobileNet可有效提取人脸高级特征,为无线通信领域提供先进的调制类型识别方案。
基于模糊神经网络的金融序列预测算法matlab仿真
本程序为基于模糊神经网络的金融序列预测算法MATLAB仿真,适用于非线性、不确定性金融数据预测。通过MAD、RSI、KD等指标实现序列预测与收益分析,运行环境为MATLAB2022A,完整程序无水印。算法结合模糊逻辑与神经网络技术,包含输入层、模糊化层、规则层等结构,可有效处理金融市场中的复杂关系,助力投资者制定交易策略。
基于BBO生物地理优化的三维路径规划算法MATLAB仿真
本程序基于BBO生物地理优化算法,实现三维空间路径规划的MATLAB仿真(测试版本:MATLAB2022A)。通过起点与终点坐标输入,算法可生成避障最优路径,并输出优化收敛曲线。BBO算法将路径视为栖息地,利用迁移和变异操作迭代寻优。适应度函数综合路径长度与障碍物距离,确保路径最短且安全。程序运行结果完整、无水印,适用于科研与教学场景。
基于sift变换的农田杂草匹配定位算法matlab仿真
本项目基于SIFT算法实现农田杂草精准识别与定位,运行环境为Matlab2022a。完整程序无水印,提供详细中文注释及操作视频。核心步骤包括尺度空间极值检测、关键点定位、方向分配和特征描述符生成。该算法通过特征匹配实现杂草定位,适用于现代农业中的自动化防控。
基于NSGAII的的柔性作业调度优化算法MATLAB仿真,仿真输出甘特图
本程序基于NSGA-II算法实现柔性作业调度优化,适用于多目标优化场景(如最小化完工时间、延期、机器负载及能耗)。核心代码完成任务分配与甘特图绘制,支持MATLAB 2022A运行。算法通过初始化种群、遗传操作和选择策略迭代优化调度方案,最终输出包含完工时间、延期、机器负载和能耗等关键指标的可视化结果,为制造业生产计划提供科学依据。
基于入侵野草算法的KNN分类优化matlab仿真
本程序基于入侵野草算法(IWO)优化KNN分类器,通过模拟自然界中野草的扩散与竞争过程,寻找最优特征组合和超参数。核心步骤包括初始化、繁殖、变异和选择,以提升KNN分类效果。程序在MATLAB2022A上运行,展示了优化后的分类性能。该方法适用于高维数据和复杂分类任务,显著提高了分类准确性。
JS数组操作方法全景图,全网最全构建完整知识网络!js数组操作方法全集(实现筛选转换、随机排序洗牌算法、复杂数据处理统计等情景详解,附大量源码和易错点解析)
这些方法提供了对数组的全面操作,包括搜索、遍历、转换和聚合等。通过分为原地操作方法、非原地操作方法和其他方法便于您理解和记忆,并熟悉他们各自的使用方法与使用范围。详细的案例与进阶使用,方便您理解数组操作的底层原理。链式调用的几个案例,让您玩转数组操作。 只有锻炼思维才能可持续地解决问题,只有思维才是真正值得学习和分享的核心要素。如果这篇博客能给您带来一点帮助,麻烦您点个赞支持一下,还可以收藏起来以备不时之需,有疑问和错误欢迎在评论区指出~
基于WOA鲸鱼优化的CNN-LSTM-SAM网络时间序列回归预测算法matlab仿真
本内容介绍了一种基于CNN-LSTM-SAM网络与鲸鱼优化算法(WOA)的时间序列预测方法。算法运行于Matlab2022a,完整程序无水印并附带中文注释及操作视频。核心流程包括数据归一化、种群初始化、适应度计算及参数更新,最终输出最优网络参数完成预测。CNN层提取局部特征,LSTM层捕捉长期依赖关系,自注意力机制聚焦全局特性,全连接层整合特征输出结果,适用于复杂非线性时间序列预测任务。
基于yolov2和googlenet网络的疲劳驾驶检测算法matlab仿真
本内容展示了基于深度学习的疲劳驾驶检测算法,包括算法运行效果预览(无水印)、Matlab 2022a 软件版本说明、部分核心程序(完整版含中文注释与操作视频)。理论部分详细阐述了疲劳检测原理,通过对比疲劳与正常状态下的特征差异,结合深度学习模型提取驾驶员面部特征变化。具体流程包括数据收集、预处理、模型训练与评估,使用数学公式描述损失函数和推理过程。课题基于 YOLOv2 和 GoogleNet,先用 YOLOv2 定位驾驶员面部区域,再由 GoogleNet 分析特征判断疲劳状态,提供高准确率与鲁棒性的检测方法。