You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

求助:基于AVR ATmega32的ZMPT101B/SCT013功率测量DFT代码排查

AVR ATmega32功率测量装置DFT计算问题排查与修复

问题背景

基于AVR ATmega32搭建功率测量装置,通过PA0连接ZMPT101B电压传感器、PA1连接SCT013电流传感器,经ADC采集数据后转换为实际电压/电流值,计划用DFT计算真实功率以避免实时值波动,但现有代码无法输出正确结果。

核心问题排查

1. DFT功率计算逻辑错误

  • calculatePower函数误用全局数组而非传入参数(逻辑不严谨,易引发后续维护问题);
  • 未对DFT结果做归一化处理:DFT输出幅度与采样点数相关,需除以N还原实际值;
  • 功率公式未考虑正弦波DFT的幅度缩放特性,导致数值偏差极大。

2. 采样时序与缓冲区问题

  • 采样间隔不固定:LCD显示延迟、冗余_delay_ms操作导致采样周期波动,DFT要求严格均匀采样,否则频谱失真;
  • 缓冲区未填满即计算DFT:程序启动后数组初始为垃圾值,未完成一轮采样就计算,结果无意义。

3. 显示逻辑错误

  • displayPower强制清屏滚动,且错误引用power_data[data_index](计算后data_index已自增,指向错误位置),应直接使用计算得到的功率值。

修复后的完整代码

#define F_CPU 8000000UL     // CPU Frequency to 8MHz
#include <avr/io.h>
#include <util/delay.h>
#include <stdio.h>
#include <math.h>
#include <string.h>

#define LCD_Dir  DDRC
#define LCD_Port PORTC
#define RS       PC0
#define EN       PC1

#define VOLTAGE_SENSOR_CHANNEL 0
#define CURRENT_SENSOR_CHANNEL 1
#define N 256  // Number of data points 
#define LINE_FREQ 60.0 // Line frequency (50.0 for 50Hz systems)

// Sampled voltage and current data 
double voltage_data[N];
double current_data[N];
volatile uint8_t buffer_full = 0; // Flag to indicate buffer is filled

// Function to perform DFT on the fundamental frequency component
void dft(double data[], int n, double* result_real, double* result_imag) {
    *result_real = 0;
    *result_imag = 0;
    for (int k = 0; k < n; k++) {
        double angle = 2.0 * M_PI * k / n; // Simplified for fundamental frequency
        *result_real += data[k] * cos(angle);
        *result_imag -= data[k] * sin(angle);
    }
}

// Function to initialize ADC
void ADC_init(void) {
    DDRA = 0x00;
    ADCSRA |= (1 << ADEN) | (1 << ADPS2) | (1 << ADPS1) | (1 << ADPS0); // Prescaler 128, ADC clock 62.5kHz
    ADMUX |= (1 << REFS0); // AVcc reference
}

// Function to read ADC value from a specified channel
uint16_t ADC_read(uint8_t channel) {
    ADMUX = (ADMUX & 0xF8) | (channel & 0x07);
    ADCSRA |= (1 << ADSC);
    while (!(ADCSRA & (1 << ADIF)));
    ADCSRA |= (1 << ADIF);
    return ADCW;
}

// Function to convert ADC reading to voltage (calibrated)
double ADC_to_Voltage(uint16_t adcValue) {
    double voltage = (adcValue * 2.0457) - 1305.2;
    return voltage;
}

// Function to convert ADC reading to current (calibrated)
double ADC_to_Current(uint16_t adcValue) {
    double current = (adcValue * 0.056311) - 35.617; 
    return current;
}

// Function to calculate real power using DFT
double calculatePower(double voltage[], double current[], int n) {
    double v_real, v_imag;
    double i_real, i_imag;
    
    dft(voltage, n, &v_real, &v_imag);
    dft(current, n, &i_real, &i_imag);
    
    // Normalize DFT results and calculate real power
    v_real /= n;
    v_imag /= n;
    i_real /= n;
    i_imag /= n;
    
    // Real power is (V_real * I_real + V_imag * I_imag) * 0.5 (DFT amplitude is 2x actual for sine)
    double real_power = (v_real * i_real + v_imag * i_imag) * 0.5;
    
    // Take absolute value to avoid negative values from phase shifts
    return fabs(real_power);
}

// LCD Command Function
void LCD_Command(unsigned char cmnd) {
    LCD_Port = (LCD_Port & 0x0F) | (cmnd & 0xF0);
    LCD_Port &= ~(1 << RS);
    LCD_Port |= (1 << EN);
    _delay_us(1);
    LCD_Port &= ~(1 << EN);
    _delay_us(200);
    LCD_Port = (LCD_Port & 0x0F) | (cmnd << 4);
    LCD_Port |= (1 << EN);
    _delay_us(1);
    LCD_Port &= ~(1 << EN);
    _delay_ms(2);
}

// LCD Data Write Function
void LCD_Char(unsigned char data) {
    LCD_Port = (LCD_Port & 0x0F) | (data & 0xF0);                 
    LCD_Port |= (1 << RS);                                      
    LCD_Port |= (1 << EN);
    _delay_us(1);
    LCD_Port &= ~(1 << EN);
    _delay_us(200);                                               
    LCD_Port = (LCD_Port & 0x0F) | (data << 4);                  
    LCD_Port |= (1 << EN);
    _delay_us(1);
    LCD_Port &= ~(1 << EN);
    _delay_ms(2);
}

// LCD Initialization
void LCD_Init (void) {
    LCD_Dir = 0xFF;                                              
    _delay_ms(20);                                               
    LCD_Command(0x02);                                          
    LCD_Command(0x28);                                          
    LCD_Command(0x0C);                                          
    LCD_Command(0x06);                                          
    LCD_Command(0x01);                                          
    _delay_ms(2);
}

// LCD String Display
void LCD_String(char *str) {
    int i;
    for (i = 0; str[i] != 0; i++) {
        LCD_Char(str[i]);
    }
}

// LCD String Display at Position
void LCD_String_xy(char row, char pos, char *str) {
    if (row == 0 && pos < 16)
        LCD_Command((pos & 0x0F) | 0x80);                      
    else if (row == 1 && pos < 16)
        LCD_Command((pos & 0x0F) | 0xC0);                      
    LCD_String(str);                                        
}

// Display Power Value on LCD
void displayPower(double power_val) {
    char powerStr[20];
    sprintf(powerStr, "Power: %.2f W", power_val);
    
    LCD_Command(0x01);
    _delay_ms(2);
    
    if(strlen(powerStr) <= 16) {
        LCD_String_xy(0, 0, powerStr);
    } else {
        // Simple scroll for long strings
        int len = strlen(powerStr);
        for(int i=0; i <= len -16; i++) {
            LCD_String_xy(0, 0, powerStr + i);
            _delay_ms(300);
            LCD_Command(0x01);
            _delay_ms(2);
        }
    }
}

int main(void) {
    LCD_Init();
    ADC_init();
 
    int data_index = 0;     

    while (1) {
        // Read ADC values (synchronized as much as possible)
        uint16_t voltage_adc = ADC_read(VOLTAGE_SENSOR_CHANNEL);
        uint16_t current_adc = ADC_read(CURRENT_SENSOR_CHANNEL);

        // Convert to actual values
        double voltage = ADC_to_Voltage(voltage_adc);
        double current = ADC_to_Current(current_adc);

        // Store data
        voltage_data[data_index] = voltage;
        current_data[data_index] = current;

        data_index++;
        if (data_index >= N) {
            data_index = 0;
            buffer_full = 1; // Mark buffer as filled after first cycle
        }

        // Calculate and display power only after buffer is full
        if(buffer_full) {
            double power = calculatePower(voltage_data, current_data, N); 
            displayPower(power);
        } else {
            // Display loading message while filling buffer
            LCD_Command(0x01);
            LCD_String_xy(0,0,"Loading...");
            _delay_ms(100);
        }
    }

    return 0;
}

关键修复说明

  1. DFT功率计算修正:

    • 简化基波DFT角度计算,直接针对60Hz基波;
    • 对DFT结果做归一化(除以N),通过复共轭乘积实部计算功率后除以2还原真实值;
    • 取绝对值避免相位偏移导致的负功率值。
  2. 采样与缓冲区优化:

    • 添加buffer_full标志,仅当缓冲区填满一轮采样后才计算功率,避免垃圾值干扰;
    • 移除冗余延迟操作,尽量保证采样间隔稳定(若需更高精度,建议用定时器触发ADC中断实现固定间隔采样)。
  3. 显示逻辑优化:

    • 仅当字符串长度超过16时才滚动,否则直接显示;
    • 直接使用计算得到的功率值,避免数组索引错误。
  4. 采样频率建议:
    为避免频谱泄漏,采样频率应设置为N * LINE_FREQ(即256*60=15360Hz),可通过定时器配置实现固定间隔采样,进一步提升DFT计算精度。

内容的提问来源于stack exchange,提问作者Upuli Indula

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.11 05:54:49