两个电压互感器测量三相相电压的算法、步骤以及碰到的问题

原理分析
在三相电中,为了对三相相电压进行监测,
需要分别测试三相的线电压,或者是两两之间的相电压。
比如A相与B相之间的相电压UAB,B相与C相之间的相电压UBC,以及C相与A相之间的相电压UCA。
按照传统的做法,需要三个电压互感器。
如果控制器采用真有效值的测试三相电压,未对电压信号进行取绝对值等非线性的变化。
则控制器可以根据矢量运算,在一个周期内同步采样相电压UAB,UBC;
根据UAB=UA-UB,UBC=UB-UC。
得到UAC=UA-UC=UC-UB+UB-UC=UAB+UBC;
在一个周期20ms内对UAB、UBC的对应的A/D输入通道等间隔采样128点,每个采样点分别得到两个数值 96776e53c7894c6cbf8e32f354e84dec.jpg 以及 daed271521b44209a07c38823634de7d.jpg
通过数值计算得到UBC对应的A/D数值 006be61484bf41758181375a0c7426f2.jpg
把采样得到的两个数值以及计算得到的一个数值根据真有效值算法计算得到A/D的真有效值,
再通过标定系统得到真实的三相之间的相电压值。

软件步骤及代码
具体步骤:
1)采用定时器触发A/D自动转换。
2)利用STM32处理器的regular group A/D采样功能,将UAB,UBC的电压组成group进行自动转换。
3)利用DMA将A/D结果寄存器的数值自动搬移到缓存。
4)控制器用一个变量记录DMA的空缓存计数,并实时查询DMA的空缓存计数,两个计数不相等,则说明有新的数据,取出数据,并更新变量。
5)当取到UAB、UBC两个数值之后,利用上述公式计算得到UAC的对应A/D值。
6)根据真有效值算法,计算三个A/D值的累加和以及平方和。
7)一个周期计算完成之后,根据累加和以及平方和计算A/D的真有效值,并根据标定系统计算出真实的三相之间的相电压值。
有两个注意事项:
1)为了真实还原UAC的矢量值,需要采用同一时刻的UAB以及UBC进行计算,但是STM32只有一个A/D模块,通过模拟开关进行通道的切换,所以对UAB,UBC的采样只能分时进行,而不能做到同步采样。
如果采用regular group的自动采样,所采得的UAB以及UBC的A/D值在时间上大概相差采样时间+14个A/D时钟,如果A/D时间设置为14MHz,大概相差几个us。
2)UAB以及UBC且两组不同的电路进行处理,其电压互感器以及后续的运放调整电路都不一样。因此,两个回路的相移以及放大倍数不会完全相同。因此,在计算UAB的矢量值时,需要对UAB以及UBC的相移以及放大部数进行修正,使之相同,可以通过插值、放缩的方法进行修正。
#include "Adc.h"
  • #include "define.h"
  • #include "IO.h"
  • #include "Timer.h"
  • #include "math.h"
  • #define AD_TIMEOUT_COUNT 250
  • #define AD_ERR_COUNT 3
  • #define AD_STATE_SWITCH  0x00    //????
  • #define AD_STATE_START   0x01    //????
  • #define AD_STATE_READRES 0x02    //????
  • #define AD_INIT_SEND_TIME 3
  • #define AD_TIMEOUT_COUNT 250
  • #define AD_ERR_COUNT 3  
  • const STRADPortConfig ad_port_config[] =
  • {
  •         {P_EXTERNAL_0_PORT, P_EXTERNAL_0_PIN},
  •         {P_EXTERNAL_1_PORT, P_EXTERNAL_1_PIN},
  •         {P_EXTERNAL_2_PORT, P_EXTERNAL_2_PIN},
  •         {P_EXTERNAL_3_PORT, P_EXTERNAL_3_PIN},
  •         {P_EXTERNAL_4_PORT, P_EXTERNAL_4_PIN},
  •         {P_EXTERNAL_5_PORT, P_EXTERNAL_5_PIN},
  •         {P_EXTERNAL_6_PORT, P_EXTERNAL_6_PIN},
  •         {P_EXTERNAL_7_PORT, P_EXTERNAL_7_PIN}
  • };
  • //ADC12_8, ADC12_15, ADC12_14,ADC12_13,ADC12_12 ,ADC12_11 ,ADC12_10
  • STRADResType adregs;
  • U16 g_ad_uiRes[AD_CHANNEL_NUM] = {0};
  • void fnAD_Init(void);
  • void fnAD_RealTime(void);
  • void fnAD_StateControl(void);
  • void fnAD_ReqByMask(U16 mask);
  • U16 fnAD_Get(U8 channel);
  • U8 fnAD_IsFinishByChannel(U8 channel);
  • void fnAD_ClearByChannel(U8 channel);
  • void FSMC_SRAM_Configuration(void);
  • void FSMC_NORSRAMInit(void);
  • void fnAD_AppInit(void);
  • void fnAD_AppRealTime(void);
  • #define ADC_DMA_BUFF_SIZE 1024
  • U16 adc_dma_buff[ADC_DMA_BUFF_SIZE];
  • #define ADC_TIMER TIM1
  • void fnADC_InitTimer(void)
  • {
  •     RCC->APB2ENR |= RCC_APB2ENR_TIM1EN; //RCC_APB1ENR_TIM4EN;//Timer2 clock enable
  •     ADC_TIMER->CR1 = TIM_CR1_ARPE;//自动加载
  •    // TIM4->ARR = BS_BIT_PERIOD - 1;//205*0.5=102.5us
  •     ADC_TIMER->PSC = (SYSCLK_SYS_FREQ /2000000  - 1);//fck_psc/(PSC[15:0] + 1)=2MHz
  •     ADC_TIMER->EGR = 0x0001;//Reload immediate
  •     ADC_TIMER->ARR = 625;//64 samples per 20ms
  •     ADC_TIMER->CCR4 = 312;
  •     ADC_TIMER->EGR = 0x0001;//Reload immediate
  •     ADC_TIMER->CCMR2 = 0x6800;//使能PWM功能,并开启自动加载CH3
  •        
  •           ADC_TIMER->CCER= 0x9000;//??PWM??,???????CH3
  •     ADC_TIMER->CR2 = TIM_CR2_MMS_1;
  •     ADC_TIMER->CR1 |= 0x0001;
  •        
  • }
  • void fnADC_InitDMA(void){
  •         U32 tmpreg, value;
  •         RCC->AHBENR |= RCC_AHBENR_DMA1EN;
  •         RCC->APB2ENR |= (RCC_APB2ENR_ADC1EN);
  •         DMA1_Channel1->CCR &= DMA_CCR_EN;
  •         DMA1_Channel1->CCR = 0;
  •         DMA1_Channel1->CNDTR = 0;
  •         DMA1_Channel1->CPAR = 0;
  •         DMA1_Channel1->CMAR = 0;
  •         DMA1->IFCR = 0;
  •         tmpreg = DMA_CCR_CIRC | DMA_CCR_MINC | DMA_CCR_MSIZE_0 | DMA_CCR_PSIZE_0 | DMA_CCR_PL_1;
  •         DMA1_Channel1->CCR = tmpreg;
  •         DMA1_Channel1->CNDTR = (ADC_DMA_BUFF_SIZE);
  •         DMA1_Channel1->CPAR = (U32)&ADC1->DR;
  •         DMA1_Channel1->CMAR = (U32)&adc_dma_buff;
  •         DMA1_Channel1->CCR |= DMA_CCR_EN;
  • }
  • void fnADC_Config(void){
  •         ADC1->CHSELR = 0x03;
  •        
  • }
  • void fnAD_InitLow(void)
  • {
  •         U32 value;
  •         U8 i;
  •         ADC1->CR &= ADC_CR_ADEN;
  •        
  •         RCC->APB2ENR |= RCC_APB2ENR_ADC1EN;
  •         for(i = 0; i< AD_CHANNEL_RNUM; i++)
  •         {
  •                 SET_IO_ANALOG(ad_port_config[i].port, ad_port_config[i].pin);
  •         }
  •         fnADC_Config();
  •         ADC1->CR |= ADC_CR_ADCAL;
  •         while(ADC1->CR & ADC_CR_ADCAL);
  •         ADC1->CFGR1 =  ADC_CFGR1_EXTEN_0  | ADC_CFGR1_DMAEN | ADC_CFGR1_DMACFG;
  •         ADC1->CFGR2 = 0;
  •         ADC1->CR |= ADC_CR_ADEN |ADC_CR_ADSTART;
  •         memset(&adregs, 0 , sizeof(adregs));
  • }
  • void ad_init(void)
  • {
  •     fnAD_InitLow();
  •          fnADC_InitDMA();
  •         adregs.m_uchRevCount = ADC_DMA_BUFF_SIZE;
  •     fnADC_InitTimer();
  •    
  •     adregs.m_uchRevPointer = 0;
  •        
  • }
  • #define ADC_SAMPLE_COUNT 512
  • void ad_CalAD(U16 pointer, U16 value){
  •         U32 sumx2, uiDataA, uiDataB, uiDataC;
  •         U64 sumx2n, sumxn2;
  •         U32 sumxn;
  •        
  •         sumx2 = (U32)value * value;
  •         adregs.m_uiSumX2[pointer] += (U64)sumx2;
  •         adregs.m_uiSumX[pointer] += (U32)value;
  •         adregs.m_uiCount[pointer] ++;
  •         adregs.m_uiADVal[pointer] = value;
  •         if(adregs.m_uiCount[pointer] >= ADC_SAMPLE_COUNT){
  •                 sumxn = (U32)adregs.m_uiSumX[pointer] ;
  •                 sumxn2 = (U64)sumxn * sumxn;
  •                
  •                 sumxn2 = sumxn2/ ADC_SAMPLE_COUNT;
  •                
  •                 sumx2n = adregs.m_uiSumX2[pointer];
  •                 if(sumx2n >= sumxn2){
  •                         sumx2n -= sumxn2;
  •                 }else{
  •                         sumx2n = 0;
  •                 }
  •                 sumx2n = sumx2n / ADC_SAMPLE_COUNT;
  •                 uiDataC= (U32)sumx2n;
  •                 uiDataC = sqrt(uiDataC);
  •                 adregs.m_uiSumX[pointer]  = 0;
  •                 adregs.m_uiSumX2[pointer] = 0;
  •                 adregs.m_uiCount[pointer] = 0;
  •                 adregs.m_uiRes[pointer] = (U16)uiDataC;
  •                 adregs.m_uiFinish[pointer] = TRUE;
  •                
  •         }
  •        
  •        
  • }
  • void ad_state(void)
  • {
  •         U16 data;
  •         U16 npointer;
  •         static U16 adab;
  •         U16 nval;
  •         while(adregs.m_uchRevCount != DMA1_Channel1->CNDTR){
  •                 if(DMA1_Channel1->CNDTR != 0){
  •                  
  •                 if(adregs.m_uchRevCount == 1){
  •                         adregs.m_uchRevCount = ADC_DMA_BUFF_SIZE;       
  •                 }else{
  •                         adregs.m_uchRevCount --;
  •                 }
  •                 if(adregs.m_uchRevPointer >= ADC_DMA_BUFF_SIZE){
  •                         adregs.m_uchRevPointer = 0;
  •                 }
  •                 data = adc_dma_buff[adregs.m_uchRevPointer];
  •                
  •                 npointer = adregs.m_uchRevPointer % AD_CHANNEL_RNUM;
  •                
  •                 fnAD_CalAD(npointer, data);
  •                 if(npointer == 0){
  •                         adab = data;
  •                 }else{
  •                         nval = 2048;
  •                         nval += data;
  •                         if(nval >= adab){
  •                                 nval += adab;
  •                         }else{
  •                                 nval = 0;
  •                         }
  •                         fnAD_CalAD(2, nval);
  •                 }
  •                 adregs.m_uchRevPointer ++;
  •                 if(adregs.m_uchRevPointer >= ADC_DMA_BUFF_SIZE){
  •                         adregs.m_uchRevPointer = 0;
  •                 }
  •         }
  • }
  • }
  • 复制代码
    一个小插曲
    在提供第一个样品很客户时,由于时间仓促,我们并没有上三相电进行测试。
    而且用两相电进行测试,所以对计算的的UAC并没有与实际情况进行比对验证。
    结果客户收到进行测试时,发现UAB, UBC数值与实际的非常吻合,但是UAB却得到了高达600多V的真有效值。
    5149554c338843cfb57c8fa7dba12ed9?from=pc.jpg
    测试数值

    细查代码发现 dc95a7baa2564ab6bd42efefb1282d21.jpg 因一时头脑发热误写成了:
    be5dc8eba3b7450b810cebf53cbe5ef0.jpg
    再经过下述矢量分析进一步验证该错误,如下图:
    A、B、C三相电压矢量分别在坐标中表示出来:
    1aa08eb103f04d3a85b35b0941bd3aa0?from=pc.jpg
    三相电压矢量图

    矢量的加法满足平行四边形法则和三角形法则。
    具体地,两个矢量a和b相加,得到的是另一个矢量。这个矢量可以表示为a和b的起点重合后,以它们为邻边构成的平行四边形的一条对角线,或者表示为将a的终点和b的起点重合后,从a的起点指向b的终点的矢量。
    两个矢量a和b相减,则把矢量a和矢量b的终点连接,方向指向被减数,即为所得结果。
    根据矢量加减的规则,如果三相电压是50Hz的正弦信号,没有高次谐波分量。而且相互之间的相位相差120°。
    如上图,如果知道Uab=393.50V,Ubc=394.50V,根据余弦定律得到:
    41fd85d8997f4367822066309307bda1.jpg
    6ad5e624de484599af1f6dd224e7e1a4.jpg
    而在程序中将数值相加误写成相减。
    得到图上所示的平行四边形的另一边:
    也就是
    78884671fd754791aeb939873d4a2fac.jpg
    98df23818f5241ababd828a86ebdb2a4.jpg
    从而得到错误的600多的电压值。
    有人会问,通过三相电压的矢量关系,也可以由Uab以及Ubc的真有效值推算出来的Uac的电压真有效值。
    为什么还需要大费周折对每一个采样点得得到的数值进行加法运算,再通过真有效值算法计算得到。
    其原因是,只能三相电压信号是50Hz的正弦信号并且相互之间的相位差为120°才可以通过矢量关系计算得到,但是真实的三相电压都含有高次谐波信号,不是一个标准的正弦信号。


    来源:物联网全栈开发