前言

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

交流电压检测需要通过单片机ADC分点采集交流电压周期内多点电压,求均方根后才能求得。STM32F4系列单片机内部有DSP库,所以直接调用DSP库内函数就可以得到交流信号有效值,今天就是对F4系列单片机的交流电压采样进行代码分享。

交流电压检测代码思路

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

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

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

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

//这个请在主函数中调用
Inverter_SPWM_Iint(20000,0);//两路SPWM波,频率为20KHZ,PC6,PA7(N) ,定时20KHZ,重装载值8400

void Inverter_SPWM_Iint(uint32_t pfreq, uint16_t psc)
{
    //初始化GPIO
    GPIO_InitTypeDef GPIO_InitStruct;
    TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStruct;
    //将输出通道2初始化为PWM模式1
    TIM_OCInitTypeDef TIM_OCInitStruct;
    //死区和刹车功能配置
    TIM_BDTRInitTypeDef TIM_BDTRInitStruct;
    NVIC_InitTypeDef NVIC_InitStructure;
    //使能GPIO时钟
    RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOC | RCC_AHB1Periph_GPIOA, ENABLE);

    GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AF;
    GPIO_InitStruct.GPIO_OType = GPIO_OType_PP;
    GPIO_InitStruct.GPIO_Pin = GPIO_Pin_6;
    GPIO_InitStruct.GPIO_PuPd = GPIO_PuPd_NOPULL;
    GPIO_InitStruct.GPIO_Speed = GPIO_Speed_100MHz;
    GPIO_Init(GPIOC, &GPIO_InitStruct);

    GPIO_InitStruct.GPIO_Pin = GPIO_Pin_7;
    GPIO_Init(GPIOA, &GPIO_InitStruct);

    //GPIO复用
    GPIO_PinAFConfig(GPIOC, GPIO_PinSource6, GPIO_AF_TIM8);
    GPIO_PinAFConfig(GPIOA, GPIO_PinSource7, GPIO_AF_TIM8);

    TimerPeriod = (SystemCoreClock / pfreq) - 1; //自动重装载周期值,168M/pfreq

    //初始化定时器时钟
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_TIM8, ENABLE);

    //初始化时具单元
    TIM_DeInit(Inverter_TIMX);

    TIM_TimeBaseInitStruct.TIM_ClockDivision = 0;
    TIM_TimeBaseInitStruct.TIM_CounterMode = TIM_CounterMode_Up;
    TIM_TimeBaseInitStruct.TIM_Period = TimerPeriod;
    TIM_TimeBaseInitStruct.TIM_Prescaler = psc;
    TIM_TimeBaseInit(Inverter_TIMX, &TIM_TimeBaseInitStruct);

    TIM_OCInitStruct.TIM_OCMode = TIM_OCMode_PWM1;
    TIM_OCInitStruct.TIM_OutputState = TIM_OutputState_Enable;
    TIM_OCInitStruct.TIM_OutputNState = TIM_OutputNState_Enable;
    TIM_OCInitStruct.TIM_OCPolarity = TIM_OCPolarity_High;
    TIM_OCInitStruct.TIM_OCNPolarity = TIM_OCNPolarity_High;
    TIM_OCInitStruct.TIM_OCIdleState = TIM_OCIdleState_Set;
    TIM_OCInitStruct.TIM_OCNIdleState = TIM_OCNIdleState_Reset;
    TIM_OCInitStruct.TIM_Pulse = TimerPeriod / 2 - 1;
    TIM_OC1Init(Inverter_TIMX, &TIM_OCInitStruct);
    //使能预装载寄存器
    TIM_OC1PreloadConfig(Inverter_TIMX, TIM_OCPreload_Enable);


    TIM_BDTRInitStruct.TIM_OSSIState = TIM_OSSIState_Disable;
    TIM_BDTRInitStruct.TIM_OSSRState = TIM_OSSRState_Disable;
    TIM_BDTRInitStruct.TIM_LOCKLevel = TIM_LOCKLevel_1;
    TIM_BDTRInitStruct.TIM_DeadTime = 0;
    TIM_BDTRInitStruct.TIM_BreakPolarity = TIM_BreakPolarity_Low;
    TIM_BDTRInitStruct.TIM_Break = TIM_Break_Disable;
    TIM_BDTRInitStruct.TIM_AutomaticOutput = TIM_AutomaticOutput_Enable;
    TIM_BDTRConfig(Inverter_TIMX, &TIM_BDTRInitStruct);

    TIM_ITConfig(Inverter_TIMX, TIM_IT_Update, ENABLE); //允许定时器8更新中断

    NVIC_InitStructure.NVIC_IRQChannel = TIM8_UP_TIM13_IRQn; //TIM8更新中断
    NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0x00; //抢占优先级0
    NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0x01; //子优先级1
    NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
    NVIC_Init(&NVIC_InitStructure);

    //使能自动重装载
    TIM_ARRPreloadConfig(Inverter_TIMX, ENABLE);

    //开启定时器
    TIM_Cmd(Inverter_TIMX, ENABLE);

    //主输出使能
    TIM_CtrlPWMOutputs(Inverter_TIMX, ENABLE);
}

//定时器14中断服务函数
void TIM8_UP_TIM13_IRQHandler(void)
{
    if(TIM_GetITStatus(Inverter_TIMX, TIM_IT_Update) == SET) //溢出中断
    {
	      static u32 count=0;
	      if(count++%2)//一个周期内采样200个点
	      {
	      	calculate_rms();			
	      }
//        if((master_swith & (0x01 << 15)) == 0x8000)
//        {
//            global_control();//开始运行系统
//        }
    }
    TIM_ClearITPendingBit(Inverter_TIMX, TIM_IT_Update); //清除中断标志位
}

(2)配置ADC和高速DMA采集代码,一个通道输出,同时配置采样精度(也就是配置采样时间)

adc.c

#include "adc.h"
#include "stm32f4xx_adc.h"
#include "stm32f4xx_dma.h"

#define BufferSize (6*10)//传输六个通道的数据,可自由更改,每个通道的数据传输十次

static u16 DMA_Buf[BufferSize]={0};//设定DMA存储数组
u16 *adc_result=DMA_Buf;//利用这个变量在measure函数里面进行计算和工作
 
static void DMA_Config(void);//DMA的初始化函数
static void GPIO_Config(void);//所用到的IO口的函数
static void ADC1_CH3_CH4_CH14_Config(void);//ADC1通道的整体初始化
static void ADC2_CH5_CH6_CH15_Config(void);//ADC2通道的整体初始化 
  /*
            初始化ADC,实验ADC1和ADC2双重模式,分别配置规则通道
               (同时)             (同时)
            ADC1 CH3 PA3      ADC1 CH4 PA4    ADC1 CH14 PC4
            ADC2 CH5 PA5      ADC2 CH6 PA6    ADC2 CH15 PC5

  */
 void adc_init(void)
 {
    ADC_CommonInitTypeDef ADC_CommonInitStructure;
    RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_DMA2, ENABLE);
    RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA, ENABLE);
    RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOC, ENABLE);
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1, ENABLE);
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC2, ENABLE);
	 
    DMA_Config();//DMA初始化
	 
    GPIO_Config();//初始化IO口,设置为模拟输入模式

	//对于f1而言,这是一个新的CCR寄存器,配置影响所有的ADC,所以要谨慎
    ADC_CommonInitStructure.ADC_Mode = ADC_DualMode_RegSimult;//双重ADC通道模式
    ADC_CommonInitStructure.ADC_Prescaler = ADC_Prescaler_Div4;//时钟分频4,84M/4=21M
    ADC_CommonInitStructure.ADC_DMAAccessMode = ADC_DMAAccessMode_1;//DMA模式1,双重模式下,第一个数据是ADC1的数据,第二个数据是ADC2的数据
    ADC_CommonInitStructure.ADC_TwoSamplingDelay = ADC_TwoSamplingDelay_5Cycles;//转换间隔时间5个采样周期,也就是0.23us
    ADC_CommonInit(&ADC_CommonInitStructure);
	ADC1_CH3_CH4_CH14_Config();//初始化ADC1上面的通道
	ADC2_CH5_CH6_CH15_Config();//初始化ADC2上面的通道
    ADC_MultiModeDMARequestAfterLastTransferCmd(ENABLE);//转换完成以后继续转换另外一个通道的数据,ADC1和ADC2来回转换,总共转换三个来回
    ADC_Cmd(ADC1, ENABLE);//使能ADC1
    ADC_Cmd(ADC2, ENABLE);//使能ADC2
    ADC_SoftwareStartConv(ADC1);//无外部触发,启动软件转换,两者数据传送全部由ADC1传送
 }
 
static void DMA_Config(void)
{
  DMA_InitTypeDef DMA_InitStructure;  
  DMA_InitStructure.DMA_Channel = DMA_Channel_0;//数据流0,通道0 
  DMA_InitStructure.DMA_Memory0BaseAddr = (uint32_t)&DMA_Buf;//内存地址,即数据地址
  DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)(ADC1_BASE+0x300+0x8);//外设地址,多重模式通用数据寄存器的地址
  DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralToMemory;//外设到内存模式	
  DMA_InitStructure.DMA_BufferSize = BufferSize; //待传输的数据个数,6通道每个通道10个即为60个
  DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;//外设地址不递增
  DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;//内存地址地址递增
  DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_HalfWord;//外设每个数据的大小为半字即16位
  DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_HalfWord;//内存每个数据的大小为半字即16位
  DMA_InitStructure.DMA_Mode = DMA_Mode_Circular;//DMA开启循环模式,一直在传输数据和接收数据
  DMA_InitStructure.DMA_Priority = DMA_Priority_High;//当前数据流优先级设置为高,因为只有一个通道,所以无所谓
  DMA_InitStructure.DMA_FIFOMode = DMA_FIFOMode_Disable;//使能缓冲模式	
  DMA_InitStructure.DMA_FIFOThreshold = DMA_FIFOThreshold_HalfFull;//缓冲阈值为半字
  DMA_InitStructure.DMA_MemoryBurst = DMA_MemoryBurst_Single;//内存单次触发
  DMA_InitStructure.DMA_PeripheralBurst = DMA_PeripheralBurst_Single;//外设单次触发
  DMA_Init(DMA2_Stream0, &DMA_InitStructure);//初始化DMA2
  DMA_Cmd(DMA2_Stream0, ENABLE);//使能DMA2的通道0
}

static void GPIO_Config(void)
{
  GPIO_InitTypeDef GPIO_InitStructure;
  /* ADC12 Channel 3 -> PA3
     ADC12 Channel 4 -> PA4
     ADC12 Channel 5 -> PA5
	 ADC12 Channel 6 -> PA6
	 ADC12 Channel 14-> PC4
	 ADC12 Channel 15-> PC5
  */
  GPIO_InitStructure.GPIO_Pin = GPIO_Pin_3 | GPIO_Pin_4 | GPIO_Pin_5 | GPIO_Pin_6;
  GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AN;
  GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL ;
  GPIO_Init(GPIOA, &GPIO_InitStructure);	
  GPIO_InitStructure.GPIO_Pin=GPIO_Pin_4 | GPIO_Pin_5;
  GPIO_Init(GPIOC, &GPIO_InitStructure);
}


static void ADC1_CH3_CH4_CH14_Config(void)
{
  ADC_InitTypeDef ADC_InitStructure;

  ADC_InitStructure.ADC_Resolution = ADC_Resolution_12b; //半字,也就是12b
  ADC_InitStructure.ADC_ScanConvMode = ENABLE;// 使能扫描模式
  ADC_InitStructure.ADC_ContinuousConvMode = ENABLE;//循环扫描
  ADC_InitStructure.ADC_ExternalTrigConvEdge = ADC_ExternalTrigConvEdge_None;//不使用外部触发,用软件处罚
  ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_T1_CC1;//不使用的话那么这条语句也就失去了作用
  ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right;//数据右对齐
  ADC_InitStructure.ADC_NbrOfConversion = 3;//三个需要配置的数据通道
  ADC_Init(ADC1, &ADC_InitStructure);

  ADC_RegularChannelConfig(ADC1, ADC_Channel_3, 1, ADC_SampleTime_56Cycles);//1÷21000000*68*60÷2=97.14us
  ADC_RegularChannelConfig(ADC1, ADC_Channel_4, 2, ADC_SampleTime_56Cycles);//特此注明,使用了双重模式,故两个通道可以同时转换数据而不产生冲突
  ADC_RegularChannelConfig(ADC1, ADC_Channel_14, 3, ADC_SampleTime_56Cycles);
}

/**
  * @brief  ADC2 regular channels 5, 6, 15 configuration
  * @param  None
  * @retval None
  */
static void ADC2_CH5_CH6_CH15_Config(void)
{
  ADC_InitTypeDef ADC_InitStructure;

  ADC_InitStructure.ADC_Resolution = ADC_Resolution_12b;
  ADC_InitStructure.ADC_ScanConvMode = ENABLE;
  ADC_InitStructure.ADC_ContinuousConvMode = ENABLE;
  ADC_InitStructure.ADC_ExternalTrigConvEdge = ADC_ExternalTrigConvEdge_None;
  ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_T1_CC1;
  ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right;
  ADC_InitStructure.ADC_NbrOfConversion = 3;
  ADC_Init(ADC2, &ADC_InitStructure);

  ADC_RegularChannelConfig(ADC2, ADC_Channel_5, 1, ADC_SampleTime_56Cycles);
  ADC_RegularChannelConfig(ADC2, ADC_Channel_6, 2, ADC_SampleTime_56Cycles);
	ADC_RegularChannelConfig(ADC2, ADC_Channel_15, 3, ADC_SampleTime_56Cycles);
}

adc.h

#ifndef ADC_H
#define ADC_H

#include "stm32f4xx.h"

 /**
  * @brief  初始化ADC,实验ADC1和ADC2双重模式,分别配置规则通道
            ADC1 CH3 PA3      ADC1 CH4 PA4
            ADC2 CH5 PA5      ADC2 CH6 PA6
  * @param  None
  * @retval None
  */
 void adc_init(void);
 
 extern u16 *adc_result;

#endif

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

//采集电量参数
//输出交流电压抬,PA4
float AC_Vout_vref=1.54650f;
float AC_Vout_ratio=50.4557f;
//输出交流电流,PC4
float AC_Iout_vref=1.51600f;
float AC_Iout_ratio=3.3333f;
//输入交流电压,PC5
float AC_Vin_verf=1.52400f;
float AC_Vin_ratio=51.2035f;
//输入直流电压,PA2
float DC_Vin_verf=0.0000f;
float DC_Vin_ratio=33.0655f;
//boost输入电流,PA3
float DC_Iin_verf=0.0000f;
float DC_Iin_ratio=1.2280f;

(4)定义采集函数

measure.c

#include "measure.h"
#include "adc.h"
#include "main.h"
#include "math.h"
#include "arm_math.h"
/*
ADC1 CH2 PA3 采集boost直流电压
ADC2 CH3 PA5 采集boost直流电流
ADC1 CH4 PA4 采集inverter交流输出电压
ADC2 CH6 PA6 采集inverter交流输出电流
ADC1 CH14 PC4 采集整流输入前的交流输入电压 
ADC1 CH15 PC5 
*/

#define COLS 6     //6个通道数,在单数组中用来计算
#define ROWS 10    //每个通道10个数据处理,在单数组中用来计算
#define SAMPLE_LEN 200    //采样的点数,经计算得知

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

void calculate_rms(void) //计算采样函数
{
	static u32 count=0;
	u32 temp=count %SAMPLE_LEN;
	count++;
	AC_sample_mat[0][temp]= get_dcv_in(); //PA3
	AC_sample_mat[1][temp]= get_dci_in(); //PA5
	AC_sample_mat[2][temp]= get_acv_out();  //PA4
	AC_sample_mat[3][temp]= get_aci_out(); //PA6
	AC_sample_mat[4][temp]= get_acv_in();//PC4
	AC_sample_mat[5][temp]= get_aci_in(); //PC5
	if(SAMPLE_LEN-1==temp)//如果检测到检测周期内的最后一个采样点
	{
		count=0;
    arm_rms_f32(AC_sample_mat[0], SAMPLE_LEN, &AC_rms.VDC_rms);
    arm_rms_f32(AC_sample_mat[1], SAMPLE_LEN, &AC_rms.IDC_rms);
    arm_rms_f32(AC_sample_mat[2], SAMPLE_LEN, &AC_rms.Vout_rms);
    arm_rms_f32(AC_sample_mat[3], SAMPLE_LEN, &AC_rms.Iout_rms);
    arm_rms_f32(AC_sample_mat[4], SAMPLE_LEN, &AC_rms.Vin_rms);
		arm_rms_f32(AC_sample_mat[5], SAMPLE_LEN, &AC_rms.Iin_rms);
	}
}
//采集输入直流电压
float get_dcv_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*DC_Vin_ratio);//返回直流电压大小,倍数乘以20.07倍(可测试得出)
	
}

//采集输入直流电流
float get_dci_in(void)
{
	float temp=0;
	u32 sum=0;
	int i;
	for(i=0;i<ROWS;i++)
	{
		sum+=adc_result[i*COLS +1];//采集第二个通道,也就是第二个数据
	}
	temp =(double)sum/ROWS/4095*3.3;//将总值变成平均值
	return (temp*DC_Iin_ratio);//返回直流电流大小
	
}
//采集输出交流电压
float get_acv_out(void)
{
	float temp=0;
	u32 sum=0;
	int i;
	for(i=0;i<ROWS;i++)
	{
		sum+=adc_result[i*COLS +2];//采集第三个通道,也就是第三个数据
	}
	temp =(double)sum/ROWS/4095*3.3;//将总值变成平均值
	return (temp-AC_Vout_vref)*AC_Vout_ratio;//返回交流输出电压大小
	
}
//采集输出交流电流
float get_aci_out(void)
{
	float temp=0;
	u32 sum=0;
	int i;
	for(i=0;i<ROWS;i++)
	{
		sum+=adc_result[i*COLS +3];//采集第四个通道,也就是第四个数据
	}
	temp =(double)sum/ROWS/4095*3.3;//将总值变成平均值
	return (temp-AC_Iout_vref)*AC_Iout_ratio;//返回交流输出电流大小
	
}
//采集输入交流电压
float get_acv_in(void)
{
	float temp=0;
	u32 sum=0;
	int i;
	for(i=0;i<ROWS;i++)
	{
		sum+=adc_result[i*COLS +4];//采集第五个通道,也就是第一个数据
	}
	temp =(double)sum/ROWS/4095*3.3;//将总值变成平均值
	return (temp-AC_Vin_verf)*AC_Vin_ratio;//返回直流电压大小,倍数乘以20.07倍(可测试得出)
	
}

float get_aci_in(void)
{
	float temp=0;
	u32 sum=0;
	int i;
	for(i=0;i<ROWS;i++)
	{
		sum+=adc_result[i*COLS +5];//采集第五个通道,也就是第一个数据
	}
	temp =(double)sum/ROWS/4095*3.3;//将总值变成平均值
	return (temp-AC_Vin_verf)*AC_Vin_ratio;//返回直流电压大小,倍数乘以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;
	
}

measure.h

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

typedef struct _AC_RMS  //定义有效值结构体
{
	float Vin_rms;   //输入交流电压有效值  
	float Vout_rms;  //输出交流电压有效值
	float Iout_rms;  //输入交流电流有效值
	float VDC_rms;   //输出直流电压有效值
	float IDC_rms;   //输出直流电流有效值
	float Iin_rms;
}rms_type;

extern rms_type AC_rms;
void calculate_rms(void); //总检测数据函数
float get_acv_in(void);    //获得输入交流电压
float get_dcv_in(void);    //获得输入直流电压
float get_acv_out(void);   //获得输出交流电压
float get_dci_in(void);    //获得输入直流电流
float get_aci_out(void);   //获得输出交流电流
float get_aci_in(void);   //获得输出交流电流
float measure_rms(uint8_t i,uint8_t size);//均方根算法,i代表我们采样的点数,size代表我们要采样的数据行


#endif
#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

总结

代码资料如下

post9资料合集

链接: https://pan.baidu.com/s/12rAj4YzHQUONdo3RPmqAUA

提取码: c2em —来自百度网盘超级会员v6的分享