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

简介: 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

相关实践学习
部署高可用架构
本场景主要介绍如何使用云服务器ECS、负载均衡SLB、云数据库RDS和数据传输服务产品来部署多可用区高可用架构。
负载均衡入门与产品使用指南
负载均衡(Server Load Balancer)是对多台云服务器进行流量分发的负载均衡服务,可以通过流量分发扩展应用系统对外的服务能力,通过消除单点故障提升应用系统的可用性。 本课程主要介绍负载均衡的相关技术以及阿里云负载均衡产品的使用方法。
目录
相关文章
|
2天前
|
算法 数据安全/隐私保护 计算机视觉
基于二维CS-SCHT变换和LABS方法的水印嵌入和提取算法matlab仿真
该内容包括一个算法的运行展示和详细步骤,使用了MATLAB2022a。算法涉及水印嵌入和提取,利用LAB色彩空间可能用于隐藏水印。水印通过二维CS-SCHT变换、低频系数处理和特定解码策略来提取。代码段展示了水印置乱、图像处理(如噪声、旋转、剪切等攻击)以及水印的逆置乱和提取过程。最后,计算并保存了比特率,用于评估水印的稳健性。
|
22天前
|
机器学习/深度学习 人工智能 算法
基于DCT和扩频的音频水印嵌入提取算法matlab仿真
本文介绍了结合DCT和扩频技术的音频水印算法,用于在不降低音质的情况下嵌入版权信息。在matlab2022a中实现,算法利用DCT进行频域处理,通过扩频增强水印的隐蔽性和抗攻击性。核心程序展示了水印的嵌入与提取过程,包括DCT变换、水印扩频及反变换步骤。该方法有效且专业,未来研究将侧重于提高实用性和安全性。
|
3天前
|
存储 算法 数据可视化
基于harris角点和RANSAC算法的图像拼接matlab仿真
本文介绍了使用MATLAB2022a进行图像拼接的流程,涉及Harris角点检测和RANSAC算法。Harris角点检测寻找图像中局部曲率变化显著的点,RANSAC则用于排除噪声和异常点,找到最佳匹配。核心程序包括自定义的Harris角点计算函数,RANSAC参数设置,以及匹配点的可视化和仿射变换矩阵计算,最终生成全景图像。
|
3天前
|
算法 Serverless
m基于遗传优化的LDPC码NMS译码算法最优归一化参数计算和误码率matlab仿真
MATLAB 2022a仿真实现了遗传优化的归一化最小和(NMS)译码算法,应用于低密度奇偶校验(LDPC)码。结果显示了遗传优化的迭代过程和误码率对比。遗传算法通过选择、交叉和变异操作寻找最佳归一化因子,以提升NMS译码性能。核心程序包括迭代优化、目标函数计算及性能绘图。最终,展示了SNR与误码率的关系,并保存了关键数据。
12 1
|
4天前
|
机器学习/深度学习 算法 数据安全/隐私保护
基于有序抖动块截断编码的水印嵌入和提取算法matlab仿真
这是一个关于数字图像水印嵌入的算法介绍。使用MATLAB2022a,该算法基于DOTC,结合抖动和量化误差隐藏,确保水印的鲁棒性和隐蔽性。图像被分为N*N块,根据水印信号进行二值化处理,通过调整重建电平的奇偶性嵌入水印。水印提取是嵌入过程的逆操作,通过重建电平恢复隐藏的水印比特。提供的代码片段展示了从块处理、水印嵌入到噪声攻击模拟及水印提取的过程,还包括PSNR和NC的计算,用于评估水印在不同噪声水平下的性能。
|
9天前
|
存储 算法
m基于LDPC编译码的matlab误码率仿真,对比SP,MS,NMS以及OMS四种译码算法
MATLAB 2022a仿真实现了LDPC译码算法比较,包括Sum-Product (SP),Min-Sum (MS),Normalized Min-Sum (NMS)和Offset Min-Sum (OMS)。四种算法在不同通信场景有各自优势:SP最准确但计算复杂度高;MS计算复杂度最低但性能略逊;NMS通过归一化提升低SNR性能;OMS引入偏置优化高SNR表现。适用于资源有限或高性能需求的场景。提供的MATLAB代码用于仿真并绘制不同SNR下的误码率曲线。
148 3
|
12天前
|
算法 数据安全/隐私保护 计算机视觉
基于DCT变换的彩色图像双重水印嵌入和提取算法matlab仿真
**算法摘要:** - 图形展示:展示灰度与彩色图像水印应用,主辅水印嵌入。 - 软件环境:MATLAB 2022a。 - 算法原理:双重水印,转换至YCbCr/YIQ,仅影响亮度;图像分割为M×N块,DCT变换后嵌入水印。 - 流程概览:两步水印嵌入,每步对应不同图示表示。 - 核心代码未提供。
|
15天前
|
算法 数据安全/隐私保护 数据格式
基于混沌序列的图像加解密算法matlab仿真,并输出加解密之后的直方图
该内容是一个关于混沌系统理论及其在图像加解密算法中的应用摘要。介绍了使用matlab2022a运行的算法,重点阐述了混沌系统的特性,如确定性、非线性、初值敏感性等,并以Logistic映射为例展示混沌序列生成。图像加解密流程包括预处理、混沌序列生成、数据混淆和扩散,以及密钥管理。提供了部分核心程序,涉及混沌序列用于图像像素的混淆和扩散过程,通过位操作实现加密。
|
18天前
|
设计模式 算法 Java
[设计模式Java实现附plantuml源码~行为型]定义算法的框架——模板方法模式
[设计模式Java实现附plantuml源码~行为型]定义算法的框架——模板方法模式
|
23天前
|
机器学习/深度学习 算法
m基于深度学习的QPSK调制解调系统频偏估计和补偿算法matlab仿真
MATLAB 2022a中展示了基于深度学习的QPSK调制解调系统频偏估计和补偿算法仿真结果。该算法运用神经网络模型实时估计并补偿无线通信中的频率偏移。QPSK调制将二进制信息映射到四个相位状态,解调通常采用相干解调。深度学习算法通过预处理、网络结构设计、损失函数选择和优化算法实现频偏估计。核心程序生成不同SNR下的信号,比较了有无频偏补偿的误码率,显示了补偿效果。
12 1