2020电赛E题--非线性失真器程序设计-02-使用STM32实现THD高精度测量采集(附整个工程gitee链接)(一)

简介: 2020电赛E题--非线性失真器程序设计-02-使用STM32实现THD高精度测量采集(附整个工程gitee链接)

写在前面


从前我也是个白嫖怪,现在我想要当个被白嫖的人。如果有帮助,希望能留言个下次一定(老b站了hhhh)工程开源说明:分享在电赛期间的每个版本的程序,希望能对大家有帮助,本文档大致进行说明下代码的讲解,懒得下载的人可以通过文章中的代码自行调试。

gitee链接


不习惯github,还是换成国内的平台了。

快嫖我,快嫖我

image.png

代码功能描述


(备注:有些部分是直接把正点原子的例程的东西贴过来改写的,所以大家在正点的板子上可能更方便操作,我们使用的板子是自己做的板子,所以屏幕显示可能直接使用正点原子的板子无法正常显示,这个只需要在正点的LCD的例程下面进行修改即可,别的按键功能只需要自行修改管脚即可),波形的幅值大小是根据我们的模拟部分进行拟合的,所以显示的是放大器输出的大约的幅值大小。

使用网版FFT,定时器3采集版本


我们为了保证进行FFT之后的精准程度(分度值),在权衡下,选择了10240HZ为最后的采样频率,采样点数为1024,这样我们得到的频域的数据的分度值是10hz,所以也就能保证我们比较精准取到1k,2k,3k,4k,5k频率下的频域的幅值信息。

大致功能说明:

  • 使用PA1进行ADC的采样。
  • 采样定时器3进行定时采集(控制采样时钟)。
  • 只进行一次采集测试,复位后进行下次采集。
  • 绘制频谱和波形图。

版本效果展示


image.png

使用FFT库函数,定时器输出PWM波进行采集版本


我们为了保证进行FFT之后的精准程度(分度值),在权衡下,选择了10240HZ为最后的采样频率,采样点数为1024,这样我们得到的频域的数据的分度值是10hz,所以也就能保证我们比较精准取到1k,2k,3k,4k,5k频率下的频域的幅值信息。

只进行讲解说明最终版本的功能:

  • 使用PC1进行ADC的采样。(和上个版本略有不同)
  • 通过继电器切换控制不同失真状态
  • 可以全自动循环采集各种失真情况下的THD。全自动模式下,采集5次求均值进行保存显示
  • 可以实现单种状态循环测量
  • 可以通过按键切换不同的失真状态(控制继电器)
  • 可以显示频谱和波形数据

最终实现效果展示


这里是单独测试程序功能,没有接继电器,全自动模式测试显示的失真状态的汉字可能对不上,随机给了波进行测的,见谅。后面三种波形测试放到了单次循环的模式下。

image.png

测试三种1k的信号的理论值


  • 正弦:1%以内
  • 方波:大约38.87%
  • 三角波:大约11.81%

测量1k方波


image.png

测试1k正弦波


image.png

测试1k三角波


image.png

定时器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的软件转换启动功能
}
目录
相关文章
|
7月前
|
数据采集 编解码 算法
STM32采集正弦幅值的研究与实践
STM32采集正弦幅值的研究与实践
801 0
|
7月前
|
C++ 芯片 编译器
STM32F103标准外设库—— 新建工程与库函数(四)
STM32F103标准外设库—— 新建工程与库函数(四)
150 0
STM32F103标准外设库—— 新建工程与库函数(四)
|
5月前
|
传感器 编解码 IDE
STM32CubeMX ADC采集光照和电压
STM32CubeMX ADC采集光照和电压
243 3
|
6月前
|
传感器 数据采集 人工智能
【STM32+k210项目】基于AI技术智能语音台灯的设计(完整工程资料源码)
【STM32+k210项目】基于AI技术智能语音台灯的设计(完整工程资料源码)
277 1
|
5月前
|
传感器 编解码 API
【STM32开发入门】温湿度监测系统实战:SPI LCD显示、HAL库应用、GPIO配置、UART中断接收、ADC采集与串口通信全解析
SPI(Serial Peripheral Interface)是一种同步串行通信接口,常用于微控制器与外围设备间的数据传输。SPI LCD是指使用SPI接口与微控制器通信的液晶显示屏。这类LCD通常具有较少的引脚(通常4个:MISO、MOSI、SCK和SS),因此在引脚资源有限的系统中非常有用。通过SPI协议,微控制器可以向LCD发送命令和数据,控制显示内容和模式。
198 0
|
7月前
|
中间件 编译器 调度
STM32cubemx对FreeRTOS的适配(工程模板配置)
STM32cubemx对FreeRTOS的适配(工程模板配置)
408 0
|
6月前
|
存储 算法 测试技术
【STM32项目】基于Stm32c8t6-镭射激光打印机的设计(完整工程资料源码)(二)
【STM32项目】基于Stm32c8t6-镭射激光打印机的设计(完整工程资料源码)(二)
196 0
|
6月前
|
存储 搜索推荐 安全
【STM32项目】基于Stm32c8t6-镭射激光打印机的设计(完整工程资料源码)(一)
【STM32项目】基于Stm32c8t6-镭射激光打印机的设计(完整工程资料源码)(一)
155 0
STM32 Keil工程中使用abs函数报警告 warning: #223-D: function "abs" declared implicitly
STM32 Keil工程中使用abs函数报警告 warning: #223-D: function "abs" declared implicitly
943 0
|
6月前
使用STM32F103标准库实现定时器控制LED点亮和关闭
通过这篇博客,我们学习了如何使用STM32F103标准库,通过定时器来控制LED的点亮和关闭。我们配置了定时器中断,并在中断处理函数中实现了LED状态的切换。这是一个基础且实用的例子,适合初学者了解STM32定时器和中断的使用。 希望这篇博客对你有所帮助。如果有任何问题或建议,欢迎在评论区留言。
470 2