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

本文涉及的产品
传统型负载均衡 CLB,每月750个小时 15LCU
网络型负载均衡 NLB,每月750个小时 15LCU
应用型负载均衡 ALB,每月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

相关实践学习
SLB负载均衡实践
本场景通过使用阿里云负载均衡 SLB 以及对负载均衡 SLB 后端服务器 ECS 的权重进行修改,快速解决服务器响应速度慢的问题
负载均衡入门与产品使用指南
负载均衡(Server Load Balancer)是对多台云服务器进行流量分发的负载均衡服务,可以通过流量分发扩展应用系统对外的服务能力,通过消除单点故障提升应用系统的可用性。 本课程主要介绍负载均衡的相关技术以及阿里云负载均衡产品的使用方法。
目录
相关文章
|
27天前
|
算法 安全 数据安全/隐私保护
基于game-based算法的动态频谱访问matlab仿真
本算法展示了在认知无线电网络中,通过游戏理论优化动态频谱访问,提高频谱利用率和物理层安全性。程序运行效果包括负载因子、传输功率、信噪比对用户效用和保密率的影响分析。软件版本:Matlab 2022a。完整代码包含详细中文注释和操作视频。
|
12天前
|
算法 数据挖掘 数据安全/隐私保护
基于FCM模糊聚类算法的图像分割matlab仿真
本项目展示了基于模糊C均值(FCM)算法的图像分割技术。算法运行效果良好,无水印。使用MATLAB 2022a开发,提供完整代码及中文注释,附带操作步骤视频。FCM算法通过隶属度矩阵和聚类中心矩阵实现图像分割,适用于灰度和彩色图像,广泛应用于医学影像、遥感图像等领域。
|
13天前
|
算法 调度
基于遗传模拟退火混合优化算法的车间作业最优调度matlab仿真,输出甘特图
车间作业调度问题(JSSP)通过遗传算法(GA)和模拟退火算法(SA)优化多个作业在并行工作中心上的加工顺序和时间,以最小化总完成时间和机器闲置时间。MATLAB2022a版本运行测试,展示了有效性和可行性。核心程序采用作业列表表示法,结合遗传操作和模拟退火过程,提高算法性能。
|
14天前
|
存储 算法 决策智能
基于免疫算法的TSP问题求解matlab仿真
旅行商问题(TSP)是一个经典的组合优化问题,目标是寻找经过每个城市恰好一次并返回起点的最短回路。本文介绍了一种基于免疫算法(IA)的解决方案,该算法模拟生物免疫系统的运作机制,通过克隆选择、变异和免疫记忆等步骤,有效解决了TSP问题。程序使用MATLAB 2022a版本运行,展示了良好的优化效果。
|
13天前
|
机器学习/深度学习 算法 芯片
基于GSP工具箱的NILM算法matlab仿真
基于GSP工具箱的NILM算法Matlab仿真,利用图信号处理技术解析家庭或建筑内各电器的独立功耗。GSPBox通过图的节点、边和权重矩阵表示电气系统,实现对未知数据的有效分类。系统使用MATLAB2022a版本,通过滤波或分解技术从全局能耗信号中提取子设备的功耗信息。
|
13天前
|
机器学习/深度学习 算法 5G
基于MIMO系统的SDR-AltMin混合预编码算法matlab性能仿真
基于MIMO系统的SDR-AltMin混合预编码算法通过结合半定松弛和交替最小化技术,优化大规模MIMO系统的预编码矩阵,提高信号质量。Matlab 2022a仿真结果显示,该算法能有效提升系统性能并降低计算复杂度。核心程序包括预编码和接收矩阵的设计,以及不同信噪比下的性能评估。
32 3
|
24天前
|
人工智能 算法 数据安全/隐私保护
基于遗传优化的SVD水印嵌入提取算法matlab仿真
该算法基于遗传优化的SVD水印嵌入与提取技术,通过遗传算法优化水印嵌入参数,提高水印的鲁棒性和隐蔽性。在MATLAB2022a环境下测试,展示了优化前后的性能对比及不同干扰下的水印提取效果。核心程序实现了SVD分解、遗传算法流程及其参数优化,有效提升了水印技术的应用价值。
|
25天前
|
机器学习/深度学习 算法 数据安全/隐私保护
基于贝叶斯优化CNN-LSTM网络的数据分类识别算法matlab仿真
本项目展示了基于贝叶斯优化(BO)的CNN-LSTM网络在数据分类中的应用。通过MATLAB 2022a实现,优化前后效果对比明显。核心代码附带中文注释和操作视频,涵盖BO、CNN、LSTM理论,特别是BO优化CNN-LSTM网络的batchsize和学习率,显著提升模型性能。
|
30天前
|
存储
基于遗传算法的智能天线最佳阵列因子计算matlab仿真
本课题探讨基于遗传算法优化智能天线阵列因子,以提升无线通信系统性能,包括信号质量、干扰抑制及定位精度。通过MATLAB2022a实现的核心程序,展示了遗传算法在寻找最优阵列因子上的应用,显著改善了天线接收功率。
|
7天前
|
算法 C# 索引
C#线性查找算法
C#线性查找算法!