MiniSTM32F103实现家庭普通电路中的电流谐波检测

前言:本人大二小白一个,通过一些小项目来充实下自己,不断学习,希望能与大家多多交流~

接下来主要分为代码设计和实际采集操作过程两部分来讲讲

代码设计

程序流程图

在这里插入图片描述
配置MiniSTM32的TIM2、ADC和DMA,利用TIM2产生矩形波,在上升沿触发ADC采集,可通过控制矩形波的周期来控制采样频率fs,然后由DMA把采集到的数据送入内存,共采集256个点。

配置TIM2、ADC和DMA

个人认为用库函数配置板子上的外设比较简单,把是什么、怎么用这两方面搞清楚就行了,可参考正点原子的教学视频。
代码如下:

#include "adc.h"

volatile uint16_t ADC_ConvertedValue[256];			//ADC采样的数据
u16 DMA1_MEM_LEN;
extern complex x[N];
#define ADC1_DR_Address    ((u32)0x4001244C)		//ADC1的地址

//TIM2配置,arr为重加载值,psc为预分频系数
void TIM2_Init(u16 arr,u16 psc)
{
    TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
    TIM_OCInitTypeDef TIM_OCInitStructure;

    RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2|RCC_APB2Periph_GPIOA, ENABLE); 		//时钟使能


    //定时器TIM2初始化
    TIM_TimeBaseStructure.TIM_Period = arr; 		//设置在下一个更新事件装入活动的自动重装载寄存器周期的值
    TIM_TimeBaseStructure.TIM_Prescaler =psc; 			//设置用来作为TIMx时钟频率除数的预分频值
    TIM_TimeBaseStructure.TIM_ClockDivision = 0; 		//设置时钟分割:TDTS = Tck_tim
    TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up; 		//TIM向上计数模式
    TIM_TimeBaseInit(TIM2, &TIM_TimeBaseStructure);			//根据指定的参数初始化TIMx的时间基数单位

    TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1;		//选择定时器模式:TIM脉冲宽度调制模式1
    TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;		//比较输出使能
    TIM_OCInitStructure.TIM_Pulse = 10;
    TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_Low;		//输出极性:TIM输出比较极性低
    TIM_OC2Init(TIM2, & TIM_OCInitStructure);		//初始化外设TIM2_CH2

    TIM_Cmd(TIM2, ENABLE); 			//使能TIM2
}

//DMA1配置
void DMA1_Config()
{
    DMA_InitTypeDef DMA_InitStructure;
    RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1,ENABLE);	  			//使能ADC1通道时钟

	DMA1_MEM_LEN = 256;
    //DMA1初始化
    DMA_DeInit(DMA1_Channel1);
    DMA_InitStructure.DMA_PeripheralBaseAddr = ADC1_DR_Address;				//ADC1地址
    DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t)&ADC_ConvertedValue; 		//内存地址
    DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC; 				//方向(从外设到内存)
    DMA_InitStructure.DMA_BufferSize = 256; 						//传输的数据量
    DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable; 		//外设地址固定
    DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable; 			//内存地址固定
    DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_HalfWord ; //外设数据单位
    DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_HalfWord ;    //内存数据单位
    DMA_InitStructure.DMA_Mode = DMA_Mode_Circular  ; 		//DMA模式:循环传输
    DMA_InitStructure.DMA_Priority = DMA_Priority_High ; 		//优先级:高
    DMA_InitStructure.DMA_M2M = DMA_M2M_Disable;   		//禁止内存到内存的传输
    DMA_Init(DMA1_Channel1, &DMA_InitStructure);  //配置DMA1
}

void DMA1_Enable()
{
	 DMA_Cmd(DMA1_Channel1,DISABLE);
	 DMA_SetCurrDataCounter(DMA1_Channel1,DMA1_MEM_LEN);    
    DMA_Cmd(DMA1_Channel1,ENABLE);
}




void Adc_Init()
{
 
    ADC_InitTypeDef ADC_InitStructure;
    GPIO_InitTypeDef GPIO_InitStructure;
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA|RCC_APB2Periph_ADC1, ENABLE);	  //使能GPIOA时钟

    //PA6 作为模拟通道输入引脚
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_1;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN;
    GPIO_Init(GPIOA, &GPIO_InitStructure);

    ADC_DeInit(ADC1);  //复位ADC1,将外设 ADC1 的全部寄存器重设为缺省值
    //ADC1初始化
    ADC_InitStructure.ADC_Mode = ADC_Mode_Independent; 			//独立ADC模式
    ADC_InitStructure.ADC_ScanConvMode = DISABLE;  			//关闭扫描方式
    ADC_InitStructure.ADC_ContinuousConvMode = DISABLE;			//关闭连续转换模式
    ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_T2_CC2;   	//使用外部触发模式
    ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right; 			//采集数据右对齐
    ADC_InitStructure.ADC_NbrOfChannel = 1; 			//要转换的通道数目
    ADC_Init(ADC1, &ADC_InitStructure);

    RCC_ADCCLKConfig(RCC_PCLK2_Div6);				//配置ADC时钟,为PCLK2的6分频,即12MHz,(239.5+12.5)/12Mhz=21us

    ADC_Cmd(ADC1,ENABLE);

    ADC_ResetCalibration(ADC1);				//复位校准寄存器
    while(ADC_GetResetCalibrationStatus(ADC1));				//等待校准寄存器复位完成

    ADC_StartCalibration(ADC1);				//ADC校准
    while(ADC_GetCalibrationStatus(ADC1));				//等待校准完成

    ADC_RegularChannelConfig(ADC1, ADC_Channel_1, 1, ADC_SampleTime_239Cycles5);		//配置ADC1通道6为239.5个采样周期 ,采样周期为21us
	 ADC_ExternalTrigConvCmd(ADC1, ENABLE);		//设置外部昂一触发模式使能
}

FFT算法

FFT是最重要,也是最难懂的。由于论述篇幅太长,这里就不说了,请看另一篇文章:对于FFT和DFT的理解
这里给出实现FFT的代码:

#include "sys.h"
#include "fft.h"

complex x[N],W[N];//分别为输入序列,变换核


void RaderReverse()
{
	u16 i,j,k;
	//第一个和最后一个数位置不变,故不处理
	for(i=1,j=N/2; i<N-1; ++i) 
	{
		//原始坐标小于变换坐标才交换,防止重复
		if(i<j)
		{
			complex temp = x[j];
			x[j] = x[i];
			x[i] = temp;
		}
		k = N/2; // 用于比较最高位
		while(k <= j)
		{ // 位判断为1
			j = j-k;// 该位变为0
			k = k/2;// 用于比较下一高位
		}
		j = j+k;// 判断为0的位变为1
	}
}
void fft()
{
	int i=0,j=0,k=0,l=0;
	complex up,down,product;
	RaderReverse();

	//w是蝶形系数
	for(int i=0;i<N;++i) 
	{
		W[i].real = cos((2*M_PI*i)/N);
		W[i].img = -sin((2*M_PI*i)/N);	
	}

	for(i=0; i<log(N)/log(2); ++i)  /*log(n)/log(2) 级蝶形运算 stage */  
	{
		l = 1<<i;
		for(j=0;j<N;j+= 2*l) /*一组蝶形运算 group,每组group的蝶形因子乘数不同*/ 
		{
			for(k=0;k<l;++k) /*一个蝶形运算 每个group内的蝶形运算的蝶形因子乘数成规律变化*/  
			{
				//product = x[j+k+l]*W[n*k/2/l]; //一次乘法
				mul(x[j+k+l], W[N*k/2/l], &product);
//				up   = x[j+k] + product; //一次加法,得蝶形变换上半部分输出
//				down = x[j+k] - product;//一次减法,得蝶形变换下半部分输出
				add(x[j+k], product, &up);
				sub(x[j+k], product, &down);
				x[j+k]   = up;//不占用新的空间,将结果输出储存在输入的位置
				x[j+k+l] = down;
			}
		}
	}
}

//寻找基波、高次谐波对应的点
double Amplitude[6], Phase[6];//基波幅值, 相位
volatile u16 j, m, k;//已知基波频率,采样频率,采样总点数
double I2,I1;
void Calculate_Wave()
{
	
	volatile double Max_Amplitude = 0.0;
	volatile double t =0.0;
	
//	m = j*256/4339+1;//计算基波频率对应的点
//	m = 0;
	//计算直流分量
	Amplitude[0] = (sqrt(x[0].real*x[0].real+x[0].img*x[0].img))/256;//基波对应的点
	Phase[0] = 180*atan2(x[0].img, x[0].real)/M_PI;
	
	for(k=1; k<52; k++)
	{
		t = sqrt(x[k].real*x[k].real +x[k].img*x[k].img)/128;
		if(t > Max_Amplitude)
		{
			Max_Amplitude = t;
			j = k;
		}
	}
	
	if(j != 0)
	{
	for(k=1; k<6; k++)
	{
	 m = k*j;//计算频率j对应的点,第一个点为基波频率
	 Amplitude[k] = (sqrt(x[m].real*x[m].real+x[m].img*x[m].img))/128;//高次谐波对应的点
	 Phase[k] = 180*atan2(x[m].img, x[m].real)/M_PI;
	}
    } 
	I2 = (Amplitude[0] + Amplitude[1]*0.7071)/R;
	I1 = I2 * 100;
}



void add(complex a,complex b,complex *c)  //复数加法计算
{
	c->real=a.real+b.real;
	c->img=a.img+b.img;
}


void mul(complex a,complex b,complex *c)  //复数乘法计算
{
	c->real=a.real*b.real - a.img*b.img;
	c->img=a.real*b.img + a.img*b.real;
}


void sub(complex a,complex b,complex *c)  //复数减法计算
{
	c->real=a.real-b.real;
	c->img=a.img-b.img;
}

处理FFT点

把时域信号变换到频域,最重要的三个量是频率、幅值和相位,如何根据FFT的结果算得这三个量?

例如某点n所表示的频率为:Fn=(n-1)*Fs/N,n是第n个点,Fs是采样频率,N是采集点数,Fs/N又称为频率分辨率,如果采样频率Fs为256Hz,采样点数为256点,则可以分辨到1Hz。
FFT后某点n是复数(a+bi的形式)
幅值:每一个点对应一个频率,幅值指的是在该频率下信号分量的幅值。假设原始信号的峰值为A,那么FFT 结果的每个点(除了第一个点直流分量之外)的模值就是A的N/2倍。而第一个点就是直流分量,它的模值就是原直流分量幅值的N倍。
相位:Pn=atan2(b,a),也就是arctan,反正切求得相角。
这里需要说明的是: FFT后得到的相位实际上是cos()的相位,因为这个FFT过程,根据惯例设定cos(x)的初始相位为0,sin(x)的初始相位为-90°。

寻找直流分量、基波和谐波

直流分量频率为0,FFT后对应的是第一个频率点,直接计算即可。
如何寻找基波?结合频谱图特性来看,基波的幅值是最大的,所以在这里采用的方法是寻找幅值最大所对应的频率点,该频率点对应的频率即是基波频率,由于已知市电的频率为50Hz,所以这里为了节省计算量只在前52个点里面找幅值最大的点。
已知基波,那么谐波频率是基波频率的整数倍,可直接算出相关的频率点。

采集过程

采集方式

由于测试对象是家庭普通电路,需要用到开口式电流互感器,非接触式,直接夹在单根火线或者零线上。
在这里插入图片描述
互感器二次侧电阻越小越好,因为这里ADC+电阻的组合其实相当于一个电流表,互感器处于工作状态时,二次侧接近短路。
ADC所读取的值就是采集点值,FFT后通过流压关系(欧姆定律)和变比关系即可推出一次侧原电路的电流值。

碰到的问题

1.这里需要注意,一般的家用电器(至少我家的是)火线零线都是合并在一条线里面的,用一根粗电线来包含单独的火线零线,电流互感器直接测这跟粗电线是测不到数据的,因为互感器(变压器)的原理是利用电流激发的磁场效应,如果互感器同时测火线零线,电流激发的磁场相互抵消,互感器不工作。
2.电流互感器的电流变比不要过大,比如我用的是1000/5A的,变比200,用来测只有最大只有几十安的电路不合适,因为二次侧的电流太小了,影响ADC采集。为了增大二次侧电流,我采用的方法是增加一次侧缠绕匝数,从原本的单根火线穿入改成两根火线穿入(还是同一根火线),匝数比从原来的1:200变成了2:200,电流变比降低到了100,这样二次侧的电流就增大了。

扫描二维码关注公众号,回复: 10332847 查看本文章
发布了2 篇原创文章 · 获赞 3 · 访问量 89

猜你喜欢

转载自blog.csdn.net/ddddddddddda/article/details/105186067