登录后台

页面导航

本文编写于 103 天前,最后修改于 103 天前,其中某些信息可能已经过时。

硬件部分

电源

电源模块原理图

  电源使用隔离降压模块,电源内部自带滤波电容,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.png

  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();
}