硬件部分
电源
电源使用隔离降压模块,电源内部自带滤波电容,DC12V 转 DC±5V.
电压采样
电路
电压量程250V,采样到的最大值约为250V的1.414倍,即353.5V。在此电压下,将输入电流控制在2mA左右。当电阻为180kΩ时,电流约为1.96mA,功率0.69W。故选择180kΩ电阻,百分之一精度,功率1W。
电压互感器型号SPT721B,2mA/2mA,输入输出都是电流量。例如,在某一刻输入值为300V,经过180kΩ的限流电阻,输入电流为1.667mA,在互感器的副边也会输出1.667mA的电流,输出相当于一个电流源。在输出端并上一个电阻,电流信号即转化为电压信号。
U6A为电压跟随器,电压跟随器的输入阻抗极大,输出阻抗约等于零,将前级的输入信号和后级的信号放大隔离开来,起缓冲作用。
U2A为反相放大器,反相端输入信号幅值最大为2mA × 47Ω = 94mV,放大倍数为10,输出信号幅值为940mV。同相端为信号抬升,改变电位器,使基准电压为1.25V(单片机ADC基准电压的一半),这样输出电压就可以控制在(1250-940,1250+940),即(310,2190)(单位mV)这个区间里面,将采样的信号送至单片机ADC0通道采样。
电流采样
电路
电流互感器型号CT254A,5A/2.5mA,和电压互感器类似,输入输出也是电流源。在缠绕匝数为1匝时,输入4A电流,在互感器的副边也会输出2mA的电流,若将输入缠绕匝数改为2匝,输出电流就会为4mA。为了解决这个问题,也就是不管匝数怎么变化,送入ADC采样的值都不变,在这里引入了一个数字电位器X9C104.
原理如下图所示,电流信号经过47Ω电阻转化为电压信号接入数字电位器左端(RH),其右端(RL)接地,中心抽头(RW)为输出,接入电压跟随器。当缠绕匝数为2匝时,控制数字电位器抽头在中心位置,此时送入电压跟随器的电压就是原来的二分之一。同理,当缠绕匝数为3匝时,控制数字电位器抽头在距离右端33%的位置,此时送入电压跟随器的电压就是原来的三分之一。这样就能实现匝数比可变。
电压跟随、反相放大部分与电压互感器那里的类似,不过多赘述。
LCD显示
LCD屏幕驱动芯片为ST7789,分辨率320×240.
其他模块与单片机的连接也在这张图上。
软件部分
时钟
外部高速时钟(HFXTCLK)为一颗板载的48MHz晶振
外部低速时钟(LFXTCLK)为一颗板载的32.768kHz晶振
void SystemClock_Init(void)
{
// 启用晶振对应的引脚
GPIO_setAsPeripheralModuleFunctionInputPin(GPIO_PORT_PJ, GPIO_PIN0,
GPIO_PRIMARY_MODULE_FUNCTION);
GPIO_setAsPeripheralModuleFunctionOutputPin(GPIO_PORT_PJ, GPIO_PIN1,
GPIO_PRIMARY_MODULE_FUNCTION);
GPIO_setAsPeripheralModuleFunctionInputPin(GPIO_PORT_PJ, GPIO_PIN3,
GPIO_PRIMARY_MODULE_FUNCTION);
GPIO_setAsPeripheralModuleFunctionOutputPin(GPIO_PORT_PJ, GPIO_PIN2,
GPIO_PRIMARY_MODULE_FUNCTION);
// 设置时钟频率
CS_setExternalClockSourceFrequency(32768, 24000000);
// 设置内核电压 bank延迟
PCM_setCoreVoltageLevel(PCM_VCORE1);
FlashCtl_setWaitState(FLASH_BANK0, 1);
FlashCtl_setWaitState(FLASH_BANK1, 1);
CS_startHFXT(false);
// 连接到外部时钟
CS_initClockSignal(CS_MCLK, CS_HFXTCLK_SELECT, CS_CLOCK_DIVIDER_2);
CS_initClockSignal(CS_ACLK, CS_LFXTCLK_SELECT, CS_CLOCK_DIVIDER_1);
CS_initClockSignal(CS_HSMCLK, CS_HFXTCLK_SELECT, CS_CLOCK_DIVIDER_2);
CS_initClockSignal(CS_SMCLK, CS_HFXTCLK_SELECT, CS_CLOCK_DIVIDER_4);
// 开FPU
FPU_enableModule();
FPU_enableLazyStacking();
}
主函数
#include "msp.h"
#include "driverlib.h"
#include "string.h"
#include "stdlib.h"
#include "math.h"
#include "arm_math.h"
#include "arm_const_structs.h"
#include "clock.h"
#include "st7789.h"
#include "LCDAPI.h"
#include "X9C104P.h"
// 系统延时
#define CPU_F ((double)24000000)
#define delay_us(x) __delay_cycles((long long)(CPU_F*(double)x/1000000.0))
#define delay_ms(x) __delay_cycles((long long)(CPU_F*(double)x/1000.0))
// 测量点数
#define N 1024
#define TIME_SHIFT 10 // 偏移量(多测10个点)
// 定时器计数值
#define TIMER_PERIOD 16
// **********参数修改**********
#define VOLTAGE_PORPORTION (1000 * 0.1358 * sqrt(8) * 1.03) * 1.00662251656f *0.9829f * 1.0009f
#define CURRENT_PORPORTION (1000 * 0.0068 * 0.96) * 1.0123f * 0.9889f
#define EVEN_HARMONICS_SCALAR 0.1
#define VOLTAGE_BIAS 0.3965f +0.1711f
#define CURRENT_BIAS -0.0086f -0.0169f
// ********** 需校准 **********
// 谐波次数
#define HARMONICS_NUM 20
// 谐波步进(离散谱步进)
#define HARMONICS_STEP 25
//平均值取样次数
#define AVERAGE_PEROID_MAX 4
uint8_t average_datasets = 0;
float ADC1_Buff[N + TIME_SHIFT];
float ADC2_Buff[N + TIME_SHIFT];
float fft_ADC2[N * 2];
#define FFT_ADC2_ABS(k) (sqrtf(fft_ADC2[k*2]*fft_ADC2[k*2] + fft_ADC2[k*2+1]*fft_ADC2[k*2+1]))
uint16_t cnt1 = 0;
uint16_t cnt2 = 0;
float Vrms;
float Arms;
// 电压/电流参数
float Arms_average;
float Vrms_average;
// 功率参数
float P;
float P_average;
float S;
float PF;
// 校准值
float Calibration_ADC1;
float Calibration_ADC2;
// ADC、定时器标志位
unsigned char ADC_Conversion_flag = 0;
unsigned char Timer_flag = 0;
// 电流谐波
float A_n[HARMONICS_NUM] = { 0 };
// 0: 左谱线幅值 1: 中间谱线幅值 2: 右谱线幅值
float K_ms[3] = { 0 };
// 百分制
float THD = 0;
float delta = 0;
// 线圈匝数
#define COIL_MAX 10
uint8_t coil_turns = 1;
void main(void)
{
WDT_A->CTL = WDT_A_CTL_PW | WDT_A_CTL_HOLD; // stop watchdog timer
// 指示灯(测试用)
GPIO_setAsOutputPin(GPIO_PORT_P1, GPIO_PIN0);
// 时钟初始化
SystemClock_Init();
// 数字电位器
GPIO_setAsOutputPin(GPIO_PORT_P3, GPIO_PIN3 | GPIO_PIN2);
// LCD 屏幕引脚开启
GPIO_setAsOutputPin(GPIO_PORT_P1, GPIO_PIN6 + GPIO_PIN7);
GPIO_setAsOutputPin(GPIO_PORT_P2,
GPIO_PIN4 + GPIO_PIN5 + GPIO_PIN6 + GPIO_PIN7);
// 用户按键
GPIO_setAsInputPinWithPullUpResistor(GPIO_PORT_P1, GPIO_PIN1 | GPIO_PIN4);
// LCD初始化
Lcd_Reset();
Lcd_Init();
Lcd_Clear(WHITE);
// **********LCD固定字符显示 开始**********
LCD_ShowChar(10, 0, 'V', 0);
LCD_ShowChar(20, 0, 'r', 0);
LCD_ShowChar(30, 0, 'm', 0);
LCD_ShowChar(40, 0, 's', 0);
LCD_ShowChar(10, 20, 'I', 0);
LCD_ShowChar(20, 20, 'r', 0);
LCD_ShowChar(30, 20, 'm', 0);
LCD_ShowChar(40, 20, 's', 0);
LCD_ShowChar(10, 40, 'P', 0);
LCD_ShowChar(10, 60, 'P', 0);
LCD_ShowChar(20, 60, 'F', 0);
LCD_ShowChar(10, 80, 'T', 0);
LCD_ShowChar(20, 80, 'H', 0);
LCD_ShowChar(30, 80, 'D', 0);
int i = 0;
unsigned char j = 0;
for (i = 100; i <= 220; i += 15)
{
LCD_ShowChar(10, i, '1' + j, 0);
j++;
}
j = 0;
LCD_ShowChar(10, 235, '1', 0);
LCD_ShowChar(15, 235, '0', 0);
LCD_ShowChar(150, 0, 'N', 0);
LCD_Showdecimal(180, 0, coil_turns, 2, 0, 16);
// **********LCD固定字符显示 结束**********
// 设置基准电压
REF_A_setReferenceVoltage(REF_A_VREF2_5V);
REF_A_enableReferenceVoltage();
// REF_A_enableReferenceVoltageOutput();
// 使能ADC
MAP_ADC14_enableModule();
// 设置ADC时钟
MAP_ADC14_initModule(ADC_CLOCKSOURCE_MCLK, ADC_PREDIVIDER_1, ADC_DIVIDER_4,
ADC_NOROUTE);
// 开启对应的ADC输入引脚
MAP_GPIO_setAsPeripheralModuleFunctionInputPin(
GPIO_PORT_P5, GPIO_PIN5 | GPIO_PIN2, GPIO_TERTIARY_MODULE_FUNCTION);
// 连续转换
ADC14_configureMultiSequenceMode(ADC_MEM0, ADC_MEM3, false);
// 参考电压,寄存器设置
MAP_ADC14_configureConversionMemory(ADC_MEM0,
ADC_VREFPOS_INTBUF_VREFNEG_VSS,
ADC_INPUT_A0,
ADC_NONDIFFERENTIAL_INPUTS);
MAP_ADC14_configureConversionMemory(ADC_MEM3,
ADC_VREFPOS_INTBUF_VREFNEG_VSS,
ADC_INPUT_A3,
ADC_NONDIFFERENTIAL_INPUTS);
//配置采样定时器
MAP_ADC14_enableSampleTimer(ADC_MANUAL_ITERATION);
//使能转换
MAP_ADC14_enableConversion();
// 手动触发
MAP_ADC14_toggleConversionTrigger();
// 定时器配置
const Timer_A_UpModeConfig upConfig = {
TIMER_A_CLOCKSOURCE_ACLK,
TIMER_A_CLOCKSOURCE_DIVIDER_1,
TIMER_PERIOD,
TIMER_A_TAIE_INTERRUPT_DISABLE,
TIMER_A_CCIE_CCR0_INTERRUPT_ENABLE,
TIMER_A_DO_CLEAR };
//配置定时器TIMER_A0
Timer_A_configureUpMode(TIMER_A3_BASE, &upConfig);
//开中断 使能定时器
//Interrupt_enableSleepOnIsrExit();//退出中断进入低功耗模式 没必要
Interrupt_enableInterrupt(INT_TA3_0);
Timer_A_startCounter(TIMER_A3_BASE, TIMER_A_UP_MODE);
// //开中断控制器
Interrupt_enableMaster();
delay_ms(1000);
while (1)
{
// MAP_PCM_gotoLPM0(); //进入低功耗模式LPM0
if (!GPIO_getInputPinValue(GPIO_PORT_P1, GPIO_PIN1))
{
delay_ms(10);
if (!GPIO_getInputPinValue(GPIO_PORT_P1, GPIO_PIN1))
{
while (!GPIO_getInputPinValue(GPIO_PORT_P1, GPIO_PIN1))
;
Arms_average = 0;
Vrms_average = 0;
P_average = 0;
average_datasets = 0;
GPIO_setOutputHighOnPin(GPIO_PORT_P1, GPIO_PIN0);
if (coil_turns < COIL_MAX)
coil_turns++;
LCD_Showdecimal(180, 0, coil_turns, 2, 0, 16);
continue;
}
}
if (!GPIO_getInputPinValue(GPIO_PORT_P1, GPIO_PIN4))
{
delay_ms(10);
if (!GPIO_getInputPinValue(GPIO_PORT_P1, GPIO_PIN4))
{
while (!GPIO_getInputPinValue(GPIO_PORT_P1, GPIO_PIN4))
;
Arms_average = 0;
Vrms_average = 0;
P_average = 0;
average_datasets = 0;
GPIO_setOutputLowOnPin(GPIO_PORT_P1, GPIO_PIN0);
if (coil_turns > 1)
coil_turns--;
LCD_Showdecimal(180, 0, coil_turns, 2, 0, 16);
continue;
}
}
if (!Timer_flag)
{
Timer_flag = 1;
ADC_Conversion_flag = 0;
set_resistor_1K_value(100 / coil_turns);
Timer_A_startCounter(TIMER_A3_BASE, TIMER_A_UP_MODE);
}
if (ADC_Conversion_flag)
{
uint16_t i = 0;
ADC_Conversion_flag = 0;
arm_mean_f32(ADC1_Buff + TIME_SHIFT, N, &Calibration_ADC1);
for (i = 0; i < N; i++)
{
ADC1_Buff[i + TIME_SHIFT] -= Calibration_ADC1;
ADC1_Buff[i + TIME_SHIFT] *= VOLTAGE_PORPORTION;
ADC1_Buff[i + TIME_SHIFT] += VOLTAGE_BIAS;
}
arm_rms_f32(ADC1_Buff + TIME_SHIFT, N, &Vrms);
arm_mean_f32(ADC2_Buff + TIME_SHIFT, N, &Calibration_ADC2);
for (i = 0; i < N; i++)
{
ADC2_Buff[i + TIME_SHIFT] -= Calibration_ADC2;
ADC2_Buff[i + TIME_SHIFT] *= CURRENT_PORPORTION;
ADC2_Buff[i + TIME_SHIFT] += CURRENT_BIAS;
}
// arm_rms_f32(ADC2_Buff, N, &Arms);
for (i = 0; i < N; i++)
{
Arms += ((ADC2_Buff[i + TIME_SHIFT])
* (ADC2_Buff[i + TIME_SHIFT]));
}
arm_rms_f32(ADC2_Buff + TIME_SHIFT, N, &Arms);
// Arms /= (N - 10);
// Arms = sqrt(Arms);
// Arms *= CURRENT_PORPORTION;
// Arms -= 0.0099;
arm_dot_prod_f32(ADC1_Buff + TIME_SHIFT, ADC2_Buff + TIME_SHIFT, N,
&P);
P /= N;
if (average_datasets < AVERAGE_PEROID_MAX)
{
Arms_average = (Arms + average_datasets * Arms_average)
/ (average_datasets + 1);
Vrms_average = (Vrms + average_datasets * Vrms_average)
/ (average_datasets + 1);
P_average = (P + average_datasets * P_average)
/ (average_datasets + 1);
average_datasets++;
}
else
{
Arms_average = (Arms + (AVERAGE_PEROID_MAX - 1) * Arms_average)
/ AVERAGE_PEROID_MAX;
Vrms_average = (Vrms + (AVERAGE_PEROID_MAX - 1) * Vrms_average)
/ AVERAGE_PEROID_MAX;
P_average = (P + (AVERAGE_PEROID_MAX - 1) * P_average)
/ AVERAGE_PEROID_MAX;
}
S = Arms_average * Vrms_average;
PF = P_average / S;
memset(fft_ADC2, 0, sizeof(float[N * 2]));
for (i = 0; i < N; i++)
{
fft_ADC2[i * 2] = ADC2_Buff[i + TIME_SHIFT];
fft_ADC2[i * 2 + 1] = 0;
}
arm_cfft_f32(&arm_cfft_sR_f32_len1024, fft_ADC2, 0, 1);
// FFT幅度变换
for (i = 0; i < N; i++)
{
fft_ADC2[i * 2] /= (i == 0) ? N : (N / 2);
fft_ADC2[i * 2 + 1] /= (i == 0) ? N : (N / 2);
}
// FFT幅度修正
for (i = 1; i <= HARMONICS_NUM; i++)
{
K_ms[0] = FFT_ADC2_ABS(i*HARMONICS_STEP-1);
K_ms[1] = FFT_ADC2_ABS(i*HARMONICS_STEP);
K_ms[2] = FFT_ADC2_ABS(i*HARMONICS_STEP+1);
if (K_ms[0] > K_ms[2])
{
delta = -K_ms[0] / (K_ms[0] + K_ms[1]);
}
else
{
delta = K_ms[2] / (K_ms[2] + K_ms[1]);
}
A_n[i - 1] = K_ms[1] * PI * delta / sinf(PI * delta);
if (isnan(A_n[i - 1]))
{
A_n[i - 1] = 0;
}
}
THD = 0;
for (i = 1; i <= HARMONICS_NUM; i++)
{
// A_n[i-1] = FFT_ADC2_ABS(i*HARMONICS_STEP);
A_n[i - 1] /= sqrt(2);
A_n[i - 1] *= (i % 2 == 0) ? EVEN_HARMONICS_SCALAR : 1;
THD += (i == 1) ? 0 : (A_n[i - 1] * A_n[i - 1]);
}
arm_sqrt_f32(THD, &THD);
THD /= A_n[0];
THD *= 100;
if (isnan(THD) || A_n[0] <= 0.001)
{
THD = 0;
}
// 更新Arms, Vrms, P, PF
LCD_Showdecimal(75, 0, Vrms_average, 3, 2, 16);
LCD_Showdecimal(75, 20, Arms_average, 1, 3, 16);
LCD_Showdecimal(75, 40, P_average, 3, 3, 16);
if (PF >= 1)
PF = 1;
LCD_Showdecimal(75, 60, PF, 1, 3, 16);
LCD_Showdecimal(75, 80, THD, 1, 3, 16);
unsigned char j = 0;
for (i = 100; i <= 235; i += 15)
{
LCD_Showdecimal(75, i, A_n[j], 1, 3, 16);
j++;
}
j = 0;
}
}
}
/* Timer A3 IRQ Hander */
void TA3_0_IRQHandler(void)
{
Timer_A_clearCaptureCompareInterrupt(TIMER_A3_BASE,
TIMER_A_CAPTURECOMPARE_REGISTER_0); //清除中断状态
Timer_A_clearTimer(TIMER_A3_BASE); //清除定时器计数
ADC1_Buff[cnt1] = MAP_ADC14_getResult(ADC_MEM0);
ADC2_Buff[cnt2] = MAP_ADC14_getResult(ADC_MEM3);
ADC2_Buff[cnt2] = (ADC2_Buff[cnt2] * 2.5) / 16384.0;
ADC1_Buff[cnt1] = (ADC1_Buff[cnt1] * 2.5) / 16384.0;
cnt1++;
cnt2++;
if (cnt1 >= N + TIME_SHIFT)
{
cnt1 = 0;
cnt2 = 0;
Timer_A_stopTimer(TIMER_A3_BASE);
Timer_flag = 0;
ADC_Conversion_flag = 1;
}
MAP_ADC14_toggleConversionTrigger();
}