求助:基于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; }
关键修复说明
DFT功率计算修正:
- 简化基波DFT角度计算,直接针对60Hz基波;
- 对DFT结果做归一化(除以
N),通过复共轭乘积实部计算功率后除以2还原真实值; - 取绝对值避免相位偏移导致的负功率值。
采样与缓冲区优化:
- 添加
buffer_full标志,仅当缓冲区填满一轮采样后才计算功率,避免垃圾值干扰; - 移除冗余延迟操作,尽量保证采样间隔稳定(若需更高精度,建议用定时器触发ADC中断实现固定间隔采样)。
- 添加
显示逻辑优化:
- 仅当字符串长度超过16时才滚动,否则直接显示;
- 直接使用计算得到的功率值,避免数组索引错误。
采样频率建议:
为避免频谱泄漏,采样频率应设置为N * LINE_FREQ(即256*60=15360Hz),可通过定时器配置实现固定间隔采样,进一步提升DFT计算精度。
内容的提问来源于stack exchange,提问作者Upuli Indula
相关产品推荐
相关产品推荐

