前言

电源方向资料免费开源到QQ群280730348,欢迎进群交流沟通。博客地址edadong.com,博文同步发布在知乎、bilibili,其中bilibili主要以视频为主。建议去B站听详细视频解说。

交流电压检测需要通过单片机ADC分点采集交流电压周期内多点电压,求均方根后才能求得。STM32F4系列单片机内部有DSP库,所以直接调用DSP库内函数就可以得到交流信号有效值。但是STM32F1并没有,所以只能自己写函数去实现。虽然均方根的原理很简单,但是更重要的是我们将代码移植到STM32F1上面的时候,会出现计算力不足的情况,导致波形出现问题。因此为了解决这个问题,我们需要对采样和最终的均方根做一点点小的优化,以避免这个问题的产生。

交流电压检测代码思路

交流电压存在周期,我们需要设定如下思路,才能够还原交流电压信号的有效值:

  1. 确认PWM驱动波频率,如24KHz驱动情况下,采集交流信号的频率必须是这个频率的1/整数倍。通常我们在定时器中断中完成这个功能。
  2. 确认采样点数,一个周期内采样点数越高,得到的有效值就更稳定可靠。比如一个周期内采集200个点做均方根,但这个过程会出现一个问题,采样点数过高,系统运行过于缓慢,会影响到波形的产生,拉宽波形频率,并且在最终均方根运算的时候占用过多的时间。
  3. 确认ADC采样路数,采样时间,计算单次采样所需要的时间,避免时间不够无法获得最新的电压数据。
  4. 确认基准电压大小,在内部转换为对应ADC采集值,降低浮点数运算量,提升运算时间。
  5. 确认交流电压缩放倍数,用于还原对应真实交流电压大小。
  6. 采集到足够多的数据后,执行均方根算法,放在另外一个定时器中断中执行,优先级要低于做采集和PID的定时器,避免被打断。

以上就是采集交流电压的代码思路,那我们开始介绍每一段代码

(1)确认PWM驱动频率和采样点数,开启采集交流信号的对应频率中断,此处我们采取同频定时器,中断内部计数的方式。由于我们采样点数确认为100,交流正弦波输出为60Hz,交流信号频率为24KHz,所以一个完整周期会执行400次中断。每进入4次中断执行一次交流单点电压采集。

//这个请在主函数中调用
TIM3_Int_Init(3000-1,1-1);//24KHZ

//这个是定时器三定时器中断和内部采集函数
void TIM3_Int_Init(u16 arr,u16 psc)
{	
	NVIC_InitTypeDef NVIC_InitStructure;
	TIM_TimeBaseInitTypeDef  TIM_TimeBaseStructure;

	RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE);//TIM3时钟使能    
	
	//定时器TIM7初始化
	TIM_TimeBaseStructure.TIM_Period = arr; //设置在下一个更新事件装入活动的自动重装载寄存器周期的值	
	TIM_TimeBaseStructure.TIM_Prescaler =psc; //设置用来作为TIMx时钟频率除数的预分频值
	TIM_TimeBaseStructure.TIM_ClockDivision = TIM_CKD_DIV1; //设置时钟分割:TDTS = Tck_tim
	TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;  //TIM向上计数模式
	TIM_TimeBaseInit(TIM3, &TIM_TimeBaseStructure); //根据指定的参数初始化TIMx的时间基数单位
 
	TIM_ITConfig(TIM3,TIM_IT_Update,ENABLE ); //使能指定的TIM3中断,允许更新中断
	
	TIM_Cmd(TIM3,ENABLE);//开启定时器3
	
	NVIC_InitStructure.NVIC_IRQChannel = TIM3_IRQn;
	NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=0 ;//抢占优先级0
	NVIC_InitStructure.NVIC_IRQChannelSubPriority = 3;		//子优先级3
	NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;			//IRQ通道使能
	NVIC_Init(&NVIC_InitStructure);	//根据指定的参数初始化VIC寄存器
}

void TIM3_IRQHandler(void)
{ 	
	static u8 ca_count=0;
	if (TIM_GetITStatus(TIM3, TIM_IT_Update) != RESET)//是更新中断
	{	
		ca_count++;
		if(ca_count>=4)
		{
		  if(jisuan_status==0)calculate_rms();	
			ca_count=0;
		}
		TIM_ClearITPendingBit(TIM3, TIM_IT_Update  );  //清除TIM3更新中断标志    
	}	    
}

(2)配置ADC和高速DMA采集代码,一个通道输出,同时配置采样精度(也就是配置采样时间),这里选用PA0进行采样。

adc.c

#include "adc.h"
#define ADC1_DR_Address   ((u32)0x40012400+0x4c)
__IO uint16_t ADC_ConvertedValue[1];//全局变量//AD采样存放空间
void Init_adc(void)//初始化ADC
{	
	ADC_InitTypeDef ADC_InitStructure;	
	DMA_InitTypeDef DMA_InitStructure; 	
	GPIO_InitTypeDef GPIO_InitStructure; 	
	//---------------------充电AD初始化--------------------		   
	RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE);//启动DMA时钟  		
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1, ENABLE);//启动ADC1时钟	 		
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);//采样脚设置		
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0;	
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN;	
	GPIO_Init(GPIOA, &GPIO_InitStructure); 	
	
	DMA_DeInit(DMA1_Channel1);//DMA1通道1配置				
	DMA_InitStructure.DMA_PeripheralBaseAddr = ADC1_DR_Address;//外设地址		
	DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t)ADC_ConvertedValue;//内存地址		
	DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC;//dma传输方向单向			
	DMA_InitStructure.DMA_BufferSize = 1;//设置DMA在传输时缓冲区的长度		
	DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;//设置DMA的外设递增模式,一个外设			
	DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;//设置DMA的内存递增模式			
	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的优先级别		
	DMA_InitStructure.DMA_M2M = DMA_M2M_Disable;//设置DMA的2个memory中的变量互相访问	
	DMA_Init(DMA1_Channel1, &DMA_InitStructure);				
	DMA_Cmd(DMA1_Channel1, ENABLE);//使能通道1 			
	ADC_InitStructure.ADC_Mode = ADC_Mode_Independent;//独立工作模式			
	ADC_InitStructure.ADC_ScanConvMode = ENABLE;//扫描方式	
	ADC_InitStructure.ADC_ContinuousConvMode = ENABLE;//连续转换		
	ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None;//外部触发禁止			
	ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right;//数据右对齐			
	ADC_InitStructure.ADC_NbrOfChannel = 1;//用于转换的通道数	
	ADC_Init(ADC1, &ADC_InitStructure);				
	ADC_RegularChannelConfig(ADC1, ADC_Channel_0 , 1, ADC_SampleTime_7Cycles5);//规则模式通道配置				
	ADC_DMACmd(ADC1, ENABLE);//使能ADC1的DMA				
	ADC_Cmd(ADC1, ENABLE);//使能ADC1			  	
	ADC_ResetCalibration(ADC1);//使能ADC1复位校准寄存器			
	while(ADC_GetResetCalibrationStatus(ADC1));//检查校准寄存器是否复位完毕				
	ADC_StartCalibration(ADC1);//开始校准			
	while(ADC_GetCalibrationStatus(ADC1));//检测是否校准完毕	 			
	ADC_SoftwareStartConvCmd(ADC1, ENABLE);//开启ADC1的软件转换
} 

adc.h

#ifndef __ADC_H
#define	__ADC_H
#include "stm32f10x.h"

void Init_adc(void);
#endif /* __ADC_H */

(3)定义基准电压和交流电压缩放倍数

u16 AC_V1_vref=1856;
float AC_V1_ratio=34.000f;

(4)定义采集函数

measure.c

#include "measure.h"
#include "adc.h"
#include "main.h"
#include "math.h"
/*
ADC1 CH0 PA0 采集交流电压输入
ADC1 CH1 PA1 采集交流电压输入
*/
extern __IO uint16_t ADC_ConvertedValue[1];
float ADC_ConvertedValueLocal; 
#define SAMPLE_LEN 100    //采样的点数,经计算得知

rms_type AC_rms={0};//存放rms_type的五个变量
u16 AC_sample_mat[SAMPLE_LEN]={0};//用来暂时保存各点采样值并最终将结果送入有效值中

void calculate_rms(void) //计算采样函数
{
	static u32 count=0;
	u32 temp=count %SAMPLE_LEN;
	count++;
	AC_sample_mat[temp]= get_acv1_in(); //PA0
	if(SAMPLE_LEN-1==temp)//如果检测到检测周期内的最后一个采样点
	{
		 count=0;
		 jisuan_status=1;
//     AC_rms.V1_rms=measure_rms(SAMPLE_LEN)/4095*3.3*AC_V1_ratio;//采集输入电压的均方根
	}
}
////采集输入多路交流电压
//float get_acv1_in(void)
//{
//	float temp=0;
//	u32 sum=0;
//	int i;
//	for(i=0;i<ROWS;i++)
//	{
//		sum+=adc_result[i*COLS +0];//采集第一个通道,也就是第一个数据
//	}
//	temp =(double)sum/ROWS/4095*3.3;//将总值变成平均值
//	return (temp-AC_V1_vref)*AC_V1_ratio;//返回直流电压大小,倍数乘以20.07倍(可测试得出)
//	
//}

//采集输入直流电压
u16 get_acv1_in(void)
{
	u16 temp=0;
	temp = ADC_ConvertedValue[0];
	if(temp>=AC_V1_vref)return (temp-AC_V1_vref);//返回直流电压大小,倍数乘以20.07倍(可测试得出)	
	else return (AC_V1_vref-temp);//返回直流电压大小,倍数乘以20.07倍(可测试得出)	
}


//float measure_rms(uint8_t i,uint8_t size)//均方根算法
//{
//	float sum=0,result;
//  int j;
//  for(j=0;j<i;j++)
//	{
//		sum+=(float)(AC_sample_mat[size][j]*AC_sample_mat[size][j]);
//	}
//	sum=(double)(sum/i);
//	result=(float)(1.0*sqrt(sum));
//	return result;
//	
//}

float measure_rms(uint8_t i)//均方根算法
{
	float sum=0,result;
  int j;
  for(j=0;j<i;j++)
	{
		sum+=(AC_sample_mat[j]*AC_sample_mat[j]);
	}
	sum=sum/i;
	result=sqrt(sum);
	return result;
	
}

measure.h

#ifndef __MEASURE_H
#define __MEASURE_H
#include "stm32f10x.h"

typedef struct _AC_RMS  //定义有效值结构体
{
	float V1_rms;   //输入交流电压有效值  
}rms_type;

extern rms_type AC_rms;
void calculate_rms(void); 
u16 get_acv1_in(void);   
float measure_rms(uint8_t i);//均方根算法,i代表我们采样的点数,size代表我们要采样的数据行


#endif

总结

代码资料如下

通过网盘分享的文件:post8资料合集 链接: https://pan.baidu.com/s/1mh0XajzdhBUUIZntvJKvLg 提取码: enx6 —来自百度网盘超级会员v6的分享