Вычисления действующего значения напряжения, тока, коэффициента мощности будут выполнены по формулам ниже:
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






































