Перейти к содержимому

Измерение действующего значения напряжения, тока, коэффициента мощности однофазной сети

Вычисления действующего значения напряжения, тока, коэффициента мощности будут выполнены по формулам ниже:

Irms=sqrt(i1*i1+i2*i2+...+in*in );
Urms=sqrt(u1*u1+u1*u1+...u1*u1 );  
Pact=(u1*i1+u2*i2+...un*in);
n - кол-во выборок
Ptot=Urms*Irms;
коэффициент мощности = Pact/Ptot.

Схема

Напряжение подключается к выводу АЦП на пину A3, Два тока на A2 и A1, напряжение смещения на A0.

Программа.

#include "stm32f10x.h"
/* подключение библиотеки DSP */
#define ARM_MATH_CM3// перед подключением указываем на каком ядре построен МК
#include "arm_math.h"
/* Подключение библиотек для LCD дисплея */
#include "LCD/HD44780.h"
#include "LCD/bcd.h"
/* Стандартная си библиотека перевода float в ASCII символы */
#include <stdio.h>

/* ---------------------Переменные---------------------*/
int16_t BuffADCVoltage1[165];
int16_t BuffADCCurrent1[165];      //буфер 1
int16_t BuffADCVoltage2[165];
int16_t BuffADCCurrent2[165];      //буфер2
int m;
int i;
int l;
//для формул
int Urms;
int Irms;
int Pact;
int Ptot;
float Pactfloat;
float Ptotfloat;
float cosf1;
//для вывода информации на дисплей
float Urmsfloat;
float Irmsfloat;
//float ;
char buf[5];
/* ---------------------математические функции корня, rms---------------------*/
//математическая функция определения квадратного корня
uint32_t MATH_sqrt(register uint32_t value)
{
    //lint -save -e40 -e10 -e845 -e522
    register uint32_t d = 0, ax = 0;

    // Находим количество битов дополняющих до 32, по сути a + b = 32, где и номер старшего бита
    // Затем из 32 вычитаем значение, получаем номер старшего бита b = 32 - a и кладем в 'а'

    #if (__ARMCC_VERSION >= 6010050)
        __asm volatile (
        "CLZ    %[A], %[VALUE]"           "\n\t"
        "RSB    %[A], %[A], 0x1F"
                : [A]"=&r" (ax)
                : [VALUE]"r" (value), [A]"r" (ax)
        );
    #else
        __asm {
            CLZ    ax, value
            RSB    ax, ax, 0x1F
        };
    #endif

    /* Нахождение квадратного корня методом Ньютона. Основная формула для вычисления:
    *     sqrt = (sqrt' + value / sqrt') / 2
    *  где:
    *     value - значение корень которого находим
    *     sqrt - текущее значение приближения
    *     sqrt' - значение приближения из предыдущего шага
    *  Для ускорения первым приближением выбирается значение равное value сдвинутому на половину
    */
    d = value >> (ax >> 1);    // первое приближение
    ax = (d + value / d) >> 1;
    d = (ax + value / ax) >> 1;
    ax = (d + value / d) >> 1;
    value = (ax + value / ax) >> 1;
    //lint -restore
    return value;
}
int RMS_rashet(int16_t array [], int n)
{
        uint16_t k; 
        int sum=0;
        int resultrms;
        for(k=0;k<n;k++)
        {
        sum+=array[k]*array[k];
        }
        sum=sum/n;
        resultrms=MATH_sqrt(sum);
        return resultrms;
}
int Pact_rashet(int16_t array1 [],int16_t array2 [], int n)
{
        uint16_t k; 
        int sum=0;
        int resultpakt;
        for(k=0;k<n;k++)
        {
        sum+=array1[k]*array2[k];
        }
        resultpakt=sum;
        return resultpakt;
}
/* ---------------------Обработчики Прерываний---------------------*/
//Таймер
void TIM3_IRQHandler (void)
{
          TIM3->SR &= ~TIM_SR_UIF; //
        
        if(m == 0)
            {
                m=1;
                l=i;
                i=0;
                //Urms=sqrt(u1*u1+u1*u1+...u1*u1 ); 
                Urms=RMS_rashet(BuffADCVoltage1,l);
                //Irms=sqrt(i1*i1+i2*i2+...+in*in );
                Irms=RMS_rashet(BuffADCCurrent1,l);
                //Pact=(u1*i1+u2*i2+...un*in);
                Pact=Pact_rashet(BuffADCVoltage1,BuffADCCurrent1, l);
                //Ptot=Urms*Irms;
                Ptot=Urms*Irms*l;
                //коэффициент мощности = Pact/Ptot.
                Pactfloat=Pact;
                Ptotfloat=Ptot;
                cosf1=Pactfloat/Ptotfloat;
                if (cosf1>1)
                {
                    cosf1=1;
                }
                if (Urms<5&&Irms<5)
                {
                    cosf1=1;
                }
            }
            else
        {
                m=0;
                l=i;
                i=0;
                //Urms=sqrt(u1*u1+u1*u1+...u1*u1 ); 
                Urms=RMS_rashet(BuffADCVoltage2,l);
                //Irms=sqrt(i1*i1+i2*i2+...+in*in );
                Irms=RMS_rashet(BuffADCCurrent2,l);
                //Pact=(u1*i1+u2*i2+...un*in);
                Pact=Pact_rashet(BuffADCVoltage2,BuffADCCurrent2, l);
                //Ptot=Urms*Irms;
                Ptot=Urms*Irms*l;
                //коэффициент мощности = Pact/Ptot.
                Pactfloat=Pact;
                Ptotfloat=Ptot;
                cosf1=Pactfloat/Ptotfloat;
                if (cosf1>1)
                {
                    cosf1=1;
                }
                if (Urms<5&&Irms<5)
                {
                    cosf1=0000;
                }
            }
}
//таймер
void TIM2_IRQHandler(void)
{
    //A0--->JDR3
    //A2--->JDR1
    //A3--->JDR2
        TIM2->SR &= ~TIM_SR_UIF; //
        if(m == 0)
        {
            BuffADCVoltage1[i] =ADC1->JDR2-ADC1->JDR3;
            BuffADCCurrent1[i] =ADC1->JDR1-ADC1->JDR3;
            i++;
        }
        else
        {
            BuffADCVoltage2[i] =ADC1->JDR2-ADC1->JDR3;
            BuffADCCurrent2[i] =ADC1->JDR1-ADC1->JDR3;
            i++;
        }
}
/* ---------------------Функции инициализации---------------------*/
//Функция настройки тактирования
void SetSysClockTo72(void)
{
    ErrorStatus HSEStartUpStatus;
 
    /* SYSCLK, HCLK, PCLK2 and PCLK1 configuration -----------------------------*/
    /* Системный RESET RCC (делать не обязательно, но полезно на этапе отладки) */
    RCC_DeInit();
 
    /* Включаем HSE (внешний кварц) */
    RCC_HSEConfig( RCC_HSE_ON);
 
    /* Ждем пока HSE будет готов */
    HSEStartUpStatus = RCC_WaitForHSEStartUp();
 
    /* Если с HSE все в порядке */
    if (HSEStartUpStatus == SUCCESS)
    {
    /* Следующие две команды касаются исключительно работы с FLASH.
    Если вы не собираетесь использовать в своей программе функций работы с Flash,
    FLASH_PrefetchBufferCmd( ) та FLASH_SetLatency( ) можно закомментировать */
 
        /* Включаем Prefetch Buffer */
        FLASH_PrefetchBufferCmd( FLASH_PrefetchBuffer_Enable);
 
        /* FLASH Latency.
    Рекомендовано устанавливать:
        FLASH_Latency_0 - 0 < SYSCLK≤ 24 MHz
        FLASH_Latency_1 - 24 MHz < SYSCLK ≤ 48 MHz
        FLASH_Latency_2 - 48 MHz < SYSCLK ≤ 72 MHz */
        FLASH_SetLatency( FLASH_Latency_2);
 
        /* HCLK = SYSCLK */ /* Смотри на схеме AHB Prescaler. Частота не делится (RCC_SYSCLK_Div1) */
        RCC_HCLKConfig( RCC_SYSCLK_Div1);
 
        /* PCLK2 = HCLK */ /* Смотри на схеме APB2 Prescaler. Частота не делится (RCC_HCLK_Div1)  */
        RCC_PCLK2Config( RCC_HCLK_Div1);
 
        /* PCLK1 = HCLK/2 */ /* Смотри на схеме APB1 Prescaler. Частота делится на 2 (RCC_HCLK_Div2)
        потому что на выходе APB1 должно быть не более 36МГц (смотри схему) */
        RCC_PCLK1Config( RCC_HCLK_Div4);
 
        /* PLLCLK = 8MHz * 9 = 72 MHz */
        /* Указываем PLL от куда брать частоту (RCC_PLLSource_HSE_Div1) и на сколько ее умножать (RCC_PLLMul_9) */
        /* PLL может брать частоту с кварца как есть (RCC_PLLSource_HSE_Div1) или поделенную на 2 (RCC_PLLSource_HSE_Div2). Смотри схему */
        RCC_PLLConfig(((uint32_t)0x00010000), RCC_PLLMul_9);
 
        /* Включаем PLL */
        RCC_PLLCmd( ENABLE);
 
        /* Ждем пока PLL будет готов */
        while (RCC_GetFlagStatus(RCC_FLAG_PLLRDY) == RESET)
        {
        }
 
        /* Переключаем системное тактирование на PLL */
        RCC_SYSCLKConfig( RCC_SYSCLKSource_PLLCLK);
 
        /* Ждем пока переключиться */
        while (RCC_GetSYSCLKSource() != 0x08)
        {
        }
    }
    else
    { /* Проблемы с HSE. Тут можно написать свой код, если надо что-то делать когда микроконтроллер не смог перейти на работу с внешним кварцом */
 
        /* Пока тут заглушка - вечный цикл*/
        while (1)
        {
        }
    }
}
//Функция включения тактирования
void RCC_ENABLE(void)
{
        RCC->APB2ENR |= RCC_APB2ENR_IOPAEN;
        RCC->APB2ENR |= RCC_APB2ENR_IOPCEN;
        RCC->APB1ENR |= RCC_APB1ENR_TIM2EN;
        RCC->APB1ENR |= RCC_APB1ENR_TIM3EN;
        RCC->CFGR    |= RCC_CFGR_ADCPRE_DIV8;      //входной делитель 
        RCC->APB2ENR |= RCC_APB2ENR_ADC1EN;   //подаем тактирование АЦП
}
//Настройка портов
void GPIO_Configuration(void)
{
        //Конфигурирование PORTA.3,PORTA.5 - аналоговый вход
		GPIOA->CRL   &= ~GPIO_CRL_MODE3;       //Очистить биты MODE
		GPIOA->CRL   &= ~GPIO_CRL_CNF3;        //Очистить биты CNF
	
		GPIOA->CRL   &= ~GPIO_CRL_MODE5;       //Очистить биты MODE
		GPIOA->CRL   &= ~GPIO_CRL_CNF5;        //Очистить биты CNF
    
        GPIOC->CRH   &= ~GPIO_CRH_MODE15;      //очистить разряды MODE
        GPIOC->CRH   &= ~GPIO_CRH_CNF15;       //очистить разряды CNF
        GPIOC->CRH   |=  GPIO_CRH_MODE15_1;    //выход, 2MHz
        GPIOC->CRH   &= ~GPIO_CRH_CNF15;       //общего назначения, симетричный  
        
        GPIOC->CRH   &= ~GPIO_CRH_MODE14;      //очистить разряды MODE
        GPIOC->CRH   &= ~GPIO_CRH_CNF14;       //очистить разряды CNF
        GPIOC->CRH   |=  GPIO_CRH_MODE14_1;    //выход, 2MHz
        GPIOC->CRH   &= ~GPIO_CRH_CNF14;       //общего назначения, симетричный  
}
//настройка 
void TIM2_CONFIG(void)
{
		TIM2->PSC   = 36-1 ; 				// 36/36= 1MHz
		TIM2->ARR   = 125 ;				//  1ms с чпстотой 50Hz нужно 1MHz/250=8kHZ
		TIM2->EGR  |= TIM_EGR_UG;           //
        TIM2->DIER |= TIM_DIER_UIE; 		//разрешаем прерывание от таймера    
        TIM2->CR1  |= TIM_CR1_CEN;          // Начать отсчёт!
}
//настройка 
void TIM3_CONFIG(void)
{
		TIM3->PSC   = 360-1 ; 				// 36/360= 100kHz
		TIM3->ARR   = 2000 ;				//  50Hz нужно 100kHz/2000=50HZ
        TIM3->DIER |= TIM_DIER_UIE; 		//разрешаем прерывание от таймера
        TIM3->EGR  |= TIM_EGR_UG;
        TIM3->CR1  |= TIM_CR1_CEN;          // Начать отсчёт!
}
//настройка работы АЦП
void ADC_init(void)
{
        //настройка АЦП происходит в режиме power down mode
        ADC1->CR2 |=  ADC_CR2_ADON;          //включить АЦП
				
        /* ----------включить сканирование, Jauto-----------*/    
        
        ADC1->CR1     =  0;          	    
        ADC1->CR1 |= ADC_CR1_JAUTO|ADC_CR1_SCAN;;             //
            	
        /* ----------- 
        запускающего АЦП, Разрешение запуска по триггеру----------*/
        ADC1->CR2 |= ADC_CR2_CONT|ADC_CR2_JEXTTRIG|ADC_CR2_JEXTSEL;          // 
        
        /* -----------установка времени выборки 239.5 cycles*/ 
        ADC1->SMPR2 |= ADC_SMPR2_SMP3_2|ADC_SMPR2_SMP3_1|ADC_SMPR2_SMP3_0|ADC_SMPR2_SMP5_2|ADC_SMPR2_SMP5_1|ADC_SMPR2_SMP5_0; //установка t выборки для 1 и 2 канала 13,5 цикла        
        
        //Калибровка
        // reset calibration
		ADC1->CR2 |= ADC_CR2_RSTCAL; 		    
		while (ADC1->CR2 & ADC_CR2_RSTCAL) {}
		//start self-calibration
		ADC1->CR2 |= ADC_CR2_CAL;
		while(ADC1->CR2 & ADC_CR2_CAL) {}
		
        
        ADC1->JSQR  &= ~  ADC_JSQR_JSQ1|ADC_JSQR_JSQ2|ADC_JSQR_JSQ3|ADC_JSQR_JSQ4;
        //A0--->JDR3
        //A2--->JDR1
        //A3--->JDR2
        ADC1->JSQR |= 
                    ADC_JSQR_JSQ3_0|ADC_JSQR_JSQ3_1
                    |ADC_JSQR_JSQ2_1
                    |ADC_JSQR_JL_1 ;//
        // External trigger conversion mode for injected channels
        ADC1->CR2 |= ADC_CR2_JSWSTART;      //Разрешаем внешний запуск инжектированной группы}
        
}
//Включение и настройка приоритетов прерываний
void NVIC_IRQ(void)
{
        //Понижаем приоритет прерыванию EXTI2
        NVIC_SetPriority (TIM3_IRQn, 2);
        NVIC_EnableIRQ(TIM3_IRQn); 			//Разрешение TIM2_DAC_IRQn прерывания
        NVIC_EnableIRQ(TIM2_IRQn); 			//Разрешение TIM2_DAC_IRQn прерывания
        //NVIC_EnableIRQ(ADC1_2_IRQn);            //включение прерывания в ядре 
        
}
//******************************для дисплея*********************************
// функция задержки
void delay(uint32_t ms)
{
   volatile uint32_t nCount;
        RCC_ClocksTypeDef RCC_Clocks;
        RCC_GetClocksFreq (&RCC_Clocks);

        nCount=(RCC_Clocks.HCLK_Frequency/10000)*ms;
        for (; nCount!=0; nCount--);
}
int main(void)
{   
        //функция инициализации дисплея
        lcd_init();    
        //функция включения работы контроллера на частоте 72 MHz
        lcd_out("-init-");    
        //функция включения работы контроллера на частоте 72 MHz
        SetSysClockTo72();
        //функция включения тактирования на нужной периферии
        RCC_ENABLE();
        //функция настройки пинов
        //GPIO_Configuration();
        TIM2_CONFIG();
        TIM3_CONFIG();
        ADC_init();
        NVIC_IRQ();
        lcd_clear();
        while (1)
            {
               lcd_set_xy(0, 0);
               lcd_out("V=");
               lcd_set_xy(2, 0);
               Urmsfloat=Urms;
               Urmsfloat=Urmsfloat/13.19;
               sprintf(buf, "%.3f", Urmsfloat);
               lcd_send(buf[0],DATA);
               lcd_send(buf[1],DATA);
               lcd_send(buf[2],DATA);
               lcd_send(buf[3],DATA);
               lcd_out("V");
               lcd_set_xy(8, 0);
               lcd_out("I=");                
               Irmsfloat=Irms;
               Irmsfloat=Irmsfloat/62;
               sprintf(buf, "%.3f", Irmsfloat);
               lcd_send(buf[0],DATA);
               lcd_send(buf[1],DATA);
               lcd_send(buf[2],DATA);
               lcd_send(buf[3],DATA);
                lcd_send(buf[4],DATA);
               lcd_out("A");
               lcd_set_xy(0, 1);
               lcd_out("PFC=");
               sprintf(buf, "%.3f", cosf1);
               lcd_send(buf[0],DATA);
               lcd_send(buf[1],DATA);
               lcd_send(buf[2],DATA);
               lcd_send(buf[3],DATA);
               lcd_send(buf[4],DATA);
               lcd_out("P=");
               lcd_set_xy(10, 1);
               sprintf(buf, "%.3f", Pactfloat);
               lcd_send(buf[0],DATA);
               lcd_send(buf[1],DATA);
               lcd_send(buf[2],DATA);
               lcd_send(buf[3],DATA);
              
               //lcd_out("V=");
               //BCD_4IntLcd(Urms);
               //lcd_set_xy(9, 1);
               //lcd_out("I=");
               //BCD_4IntLcd(Irms);
               //delay(200);
                
            }
}

Измерение напряжения.

U изм. на вольтметреU изм. на проект. устр-ве
11,349 В11,2 В
23,807 В23,7 В
34,367 В34,1 В
46,275 В46,0 В
59,99 В59,9 В
77,18 В76,9 В
96,64 В96,3 В

Измеренное напряжение на STM32 и вольтметре YOKOGAWA отличаются друг от друга в диапазоне от 0 до 100 Вольт не более в +0,5 вольт. Возможно если изменить коэффциент в программе ошибка снизится.

Диапазон измерения вольтметра от 0 до 100 вольт. Трансформатор измерительный 1:10 делитель напряжения после трансформатора равен примерно 10, фиксатор уровня на ОУ примерно равен 1.65 вольт. Максимальное напряжение АЦП 3.3 вольта, минимальное 0 вольт, отрицательные напряжения АЦП у STM32 не понимает. Соответственно если U вх =100 В, действующее значение а амплитуда 140 Вольт, на выходе трансформатора получаем 14 В, после делителя 1,4 вольт, что почти равно не считая 0,25 вольт крайнему диапазону АЦП.

Измерение тока.

I изм. на вольтметре I изм. на проект. устр-ве
0 А0,048 А
0,105 А0,113 А
0,205 А0,210 А
0,308 А0,306 А
0,397 А0,387 А
0,485 А0,484 А
0,614 А0,613 А
0,735 А0,726 А
0,840 А0,823 А
1,011 А1,000А

Диапазон измерения амперметра. Трансформатор тока имеет диапазон 1 к 1000. Трансформатор тока нагружен на резистор 50 Ом, значит при протекании тока 1 А на первичной обмотке трансформатора на вторичной обмотке будет 1 : 1000, 1 mA, который в свою очередь будет создавать падение напряжение 50 mV.

На осцилографе осцилограммы приходящие на АЦП. Постоянная составляющая отфильтрована. Красный график соответсвтвует току, серый напряжению. Красный имеет сильные шумы. Cдвига фаз создаваемого измерительными трансформаторами или нет или его не видно из-за шумов. Серый 50 вольт действующее, амплитудное 50*1,4=70 В. 70/10/10=0,7 В. что примерно и видно на сером графике.

Измерение коэффициента мощности.

Перед измерением увеличил кол-во выборок до 158 за период.

Проверить достоверность измерения коэффициента мощности достаточно сложно, так как нет калиброванной емкости или индуктивности. Для проверки подавал сигналы напрямую на выходы АЦП с помощью функционального генератора, поэтому погрешности создаваемые измерительными трансформаторами и схемой не учтены.

индуктивная нагрузка, ток отстаёт от напряжения
емкостная нагрузка, ток опережает напряжение

CH1-A2(ток),
CH2-A3(напряжение)

Больше одного косинус конечно не может быть.

arccos(0.99733): 4.188°
arccos(0.9853): 9.836°
arccos(0.9662): 14.939°
arccos(0.708): 44.928°
arccos(-0.00044): 90.025°
arccos(-0.1773): 100.213°

Измерение активно-индуктивной нагрузки на стенде.

коэффициент мощности должен быть по расчётам 0,9635

При измерении по факту он колеблется около 0,97, 0,96

Добавить комментарий