电源-手把手教你用STM32F4做交流电压采样
前言电源方向资料免费开源到QQ群280730348,欢迎进群交流沟通。博客地址edadong.com,博文同步发布在知乎、bilibili,其中bilibili主要以视频为主。建议去B站听详细视频解说。
交流电压检测需要通过单片机ADC分点采集交流电压周期内多点电压,求均方根后才能求得。STM32F4系列单片机内部有DSP库,所以直接调用DSP库内函数就可以得到交流信号有效值,今天就是对F4系列单片机的交流电压采样进行代码分享。
交流电压检测代码思路交流电压存在周期,我们需要设定如下思路,才能够还原交流电压信号的有效值:
确认PWM驱动波频率,如20KHz驱动情况下,采集交流信号的频率必须是这个频率的1/整数倍。通常我们在定时器中断中完成这个功能。
确认采样点数,一个周期内采样点数越高,得到的有效值就更稳定可靠。比如一个周期内采集200个点做均方根,但这个过程会出现一个问题,采样点数过高,系统运行过于缓慢,会影响到波形的产生,拉宽波形频率,并且在最终均方根运算的时候占用过多的时间。
确认ADC采样路数,采样时间,计算单次采样所需要的时间,避免时间不够无法获得最新的电压数据。
确认基准电压大小,减去对应基准电压值。
确认交流电压缩放倍数,用于还原对应真实交流电压大小。
采集到足够多的数据后,执行均方根算法。
以上就是采集交流电压的代码思路,那我们开始介绍每一段代码
(1)确认PWM驱动频率和采样点数,开启采集交流信号的对应频率中断,此处我们采取同频定时器,中断内部计数的方式。由于我们采样点数确认为200,交流正弦波输出为50Hz,交流信号频率为20KHz,所以一个完整周期会执行400次中断。每进入2次中断执行一次交流单点电压采集。
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101//这个请在主函数中调用Inverter_SPWM_Iint(20000,0);//两路SPWM波,频率为20KHZ,PC6,PA7(N) ,定时20KHZ,重装载值8400void 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
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129#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
1234567891011121314151617#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)定义基准电压和交流电压缩放倍数
12345678910111213141516//采集电量参数//输出交流电压抬,PA4float AC_Vout_vref=1.54650f;float AC_Vout_ratio=50.4557f;//输出交流电流,PC4float AC_Iout_vref=1.51600f;float AC_Iout_ratio=3.3333f;//输入交流电压,PC5float AC_Vin_verf=1.52400f;float AC_Vin_ratio=51.2035f;//输入直流电压,PA2float DC_Vin_verf=0.0000f;float DC_Vin_ratio=33.0655f;//boost输入电流,PA3float DC_Iin_verf=0.0000f;float DC_Iin_ratio=1.2280f;
(4)定义采集函数
measure.c
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143#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 measure.h 12345678910111213141516171819202122232425262728293031323334353637383940414243#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的分享