前言
电源方向资料免费开源到QQ群280730348,欢迎进群交流沟通。博客地址edadong.com,博文同步发布在知乎、bilibili,其中bilibili主要以视频为主。建议去B站听详细视频解说。
交流电压检测需要通过单片机ADC分点采集交流电压周期内多点电压,求均方根后才能求得。STM32F4系列单片机内部有DSP库,所以直接调用DSP库内函数就可以得到交流信号有效值。但是STM32F1并没有,所以只能自己写函数去实现。虽然均方根的原理很简单,但是更重要的是我们将代码移植到STM32F1上面的时候,会出现计算力不足的情况,导致波形出现问题。因此为了解决这个问题,我们需要对采样和最终的均方根做一点点小的优化,以避免这个问题的产生。
交流电压检测代码思路
交流电压存在周期,我们需要设定如下思路,才能够还原交流电压信号的有效值:
- 确认PWM驱动波频率,如24KHz驱动情况下,采集交流信号的频率必须是这个频率的1/整数倍。通常我们在定时器中断中完成这个功能。
- 确认采样点数,一个周期内采样点数越高,得到的有效值就更稳定可靠。比如一个周期内采集200个点做均方根,但这个过程会出现一个问题,采样点数过高,系统运行过于缓慢,会影响到波形的产生,拉宽波形频率,并且在最终均方根运算的时候占用过多的时间。
- 确认ADC采样路数,采样时间,计算单次采样所需要的时间,避免时间不够无法获得最新的电压数据。
- 确认基准电压大小,在内部转换为对应ADC采集值,降低浮点数运算量,提升运算时间。
- 确认交流电压缩放倍数,用于还原对应真实交流电压大小。
- 采集到足够多的数据后,执行均方根算法,放在另外一个定时器中断中执行,优先级要低于做采集和PID的定时器,避免被打断。
以上就是采集交流电压的代码思路,那我们开始介绍每一段代码
(1)确认PWM驱动频率和采样点数,开启采集交流信号的对应频率中断,此处我们采取同频定时器,中断内部计数的方式。由于我们采样点数确认为100,交流正弦波输出为60Hz,交流信号频率为24KHz,所以一个完整周期会执行400次中断。每进入4次中断执行一次交流单点电压采集。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43
| TIM3_Int_Init(3000-1,1-1);
void TIM3_Int_Init(u16 arr,u16 psc) { NVIC_InitTypeDef NVIC_InitStructure; TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE); TIM_TimeBaseStructure.TIM_Period = arr; TIM_TimeBaseStructure.TIM_Prescaler =psc; TIM_TimeBaseStructure.TIM_ClockDivision = TIM_CKD_DIV1; TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up; TIM_TimeBaseInit(TIM3, &TIM_TimeBaseStructure); TIM_ITConfig(TIM3,TIM_IT_Update,ENABLE ); TIM_Cmd(TIM3,ENABLE); NVIC_InitStructure.NVIC_IRQChannel = TIM3_IRQn; NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=0 ; NVIC_InitStructure.NVIC_IRQChannelSubPriority = 3; NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; NVIC_Init(&NVIC_InitStructure); }
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 ); } }
|
(2)配置ADC和高速DMA采集代码,一个通道输出,同时配置采样精度(也就是配置采样时间),这里选用PA0进行采样。
adc.c
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46
| #include "adc.h" #define ADC1_DR_Address ((u32)0x40012400+0x4c) __IO uint16_t ADC_ConvertedValue[1]; void Init_adc(void) { ADC_InitTypeDef ADC_InitStructure; DMA_InitTypeDef DMA_InitStructure; GPIO_InitTypeDef GPIO_InitStructure; RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE); RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1, ENABLE); 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); DMA_InitStructure.DMA_PeripheralBaseAddr = ADC1_DR_Address; DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t)ADC_ConvertedValue; DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC; DMA_InitStructure.DMA_BufferSize = 1; 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_InitStructure.DMA_Priority = DMA_Priority_High; DMA_InitStructure.DMA_M2M = DMA_M2M_Disable; DMA_Init(DMA1_Channel1, &DMA_InitStructure); DMA_Cmd(DMA1_Channel1, ENABLE); 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); ADC_Cmd(ADC1, ENABLE); ADC_ResetCalibration(ADC1); while(ADC_GetResetCalibrationStatus(ADC1)); ADC_StartCalibration(ADC1); while(ADC_GetCalibrationStatus(ADC1)); ADC_SoftwareStartConvCmd(ADC1, ENABLE); }
|
adc.h
1 2 3 4 5 6
| #ifndef __ADC_H #define __ADC_H #include "stm32f10x.h"
void Init_adc(void); #endif
|
(3)定义基准电压和交流电压缩放倍数
1 2
| u16 AC_V1_vref=1856; float AC_V1_ratio=34.000f;
|
(4)定义采集函数
measure.c
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80
| #include "measure.h" #include "adc.h" #include "main.h" #include "math.h"
extern __IO uint16_t ADC_ConvertedValue[1]; float ADC_ConvertedValueLocal; #define SAMPLE_LEN 100
rms_type AC_rms={0}; 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(); if(SAMPLE_LEN-1==temp) { count=0; jisuan_status=1;
} }
u16 get_acv1_in(void) { u16 temp=0; temp = ADC_ConvertedValue[0]; if(temp>=AC_V1_vref)return (temp-AC_V1_vref); else return (AC_V1_vref-temp); }
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
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| #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);
#endif
|
总结
代码资料如下
通过网盘分享的文件:post8资料合集
链接: https://pan.baidu.com/s/1mh0XajzdhBUUIZntvJKvLg 提取码: enx6
–来自百度网盘超级会员v6的分享