前言

电源方向资料免费开源到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的分享