BirdNet-goMikrofon/Arduino-audio-tools-main/arduino-audio-tools-main/examples/tests/adc/test-esp32-rx-v1-powerline-rms-stress/test-esp32-rx-v1-powerline-rms-stress.ino

278 lines
8.6 KiB
Arduino
Raw Permalink Normal View History

2026-09-26 01:20:55 +02:00
/**
* @file test-esp32-rx-v1-powerline-rms-stress.ino
* @brief FIFO top-off stress test for a 2-channel power-line RMS use case.
*
* Scenario:
* - line frequency: 60 Hz
* - channels: 2 (voltage and current)
* - RMS window: 8 cycles = 133.33 ms
* - target effective measurements after binning: 64 points/cycle
*
* For each bin size, the sketch increases the raw sample rate so the effective
* post-bin rate stays at 64 points/cycle whenever the platform allows it:
*
* - bin 1 -> raw rate 3840 Hz/channel, 512 raw samples/window/channel
* - bin 2 -> raw rate 7680 Hz/channel, 1024 raw samples/window/channel
* - bin 4 -> raw rate 15360 Hz/channel, 2048 raw samples/window/channel
* - bin 8 -> raw rate 30720 Hz/channel, 4096 raw samples/window/channel
* - bin 16 -> raw rate 61440 Hz/channel, 8192 raw samples/window/channel
*
* The corresponding raw readBytes() window size is:
*
* - bin 1 -> 2048 bytes
* - bin 2 -> 4096 bytes
* - bin 4 -> 8192 bytes
* - bin 8 -> 16384 bytes
* - bin 16 -> 32768 bytes
*
* The larger windows are intentionally much larger than the default ADC RX pool
* (4096 bytes with buffer_size=256 and buffer_count=4), so the driver must top
* off the per-channel FIFOs multiple times during one call.
*
* On some 2-channel ESP32 targets the bin-16 rate may exceed the ADC
* continuous sample-rate limit. In that case the sketch reports the scenario as
* unsupported and skips it.
*
* Expected behavior:
* - each supported window read returns raw_window_bytes / bin_size bytes
* - short_reads remains 0
* - no "FIFO buffer is full" messages appear
*/
#include "Arduino.h"
#include "AudioTools.h"
#include <math.h>
#if defined(ESP32) && defined(USE_ANALOG) && !USE_LEGACY_I2S
using namespace audio_tools;
AnalogDriverESP32V1 analog_driver;
AnalogAudioStream analog_in(analog_driver);
Bin binner;
ConverterStream<int16_t> binned_stream(analog_in, binner);
constexpr uint32_t kLineFrequencyHz = 60;
constexpr uint32_t kCyclesPerWindow = 8;
constexpr uint32_t kTargetPointsPerCycle = 64;
constexpr int kChannels = 2;
constexpr int kBitsPerSample = 16;
constexpr uint8_t kBinSizes[] = {1, 2, 4, 8, 16};
constexpr uint32_t kWindowsPerScenario = 8;
constexpr uint32_t kScenarioPauseMs = 250;
constexpr uint32_t kEffectiveSampleRateTarget =
kLineFrequencyHz * kTargetPointsPerCycle;
constexpr uint32_t kMaxRawWindowSizeBytes =
kEffectiveSampleRateTarget * 16U / kLineFrequencyHz * kCyclesPerWindow *
kChannels * sizeof(int16_t);
uint8_t raw_buffer[kMaxRawWindowSizeBytes];
size_t bin_index = 0;
size_t first_supported_bin_index = 0;
bool cycle_initialized = false;
uint32_t windows_completed = 0;
uint32_t short_reads = 0;
size_t min_bytes_read = kMaxRawWindowSizeBytes;
size_t max_bytes_read = 0;
uint32_t current_raw_sample_rate = 0;
size_t current_raw_samples_per_cycle = 0;
size_t current_raw_samples_per_window_per_channel = 0;
size_t current_raw_window_size_bytes = 0;
size_t currentBinSize() { return kBinSizes[bin_index]; }
uint32_t desiredRawSampleRate() {
return kEffectiveSampleRateTarget * currentBinSize();
}
uint32_t effectiveSampleRate() {
if (currentBinSize() == 0) return 0;
return current_raw_sample_rate / currentBinSize();
}
float effectivePointsPerCycle() {
return (float)effectiveSampleRate() / (float)kLineFrequencyHz;
}
size_t expectedOutputBytes() {
if (currentBinSize() == 0) return 0;
return current_raw_window_size_bytes / currentBinSize();
}
double computeRms(const int16_t *samples, size_t frame_count, int channel) {
if (frame_count == 0) return 0.0;
double sum_sq = 0.0;
for (size_t i = 0; i < frame_count; ++i) {
double value = (double)samples[(i * kChannels) + channel];
sum_sq += value * value;
}
return sqrt(sum_sq / (double)frame_count);
}
void resetScenarioStats() {
windows_completed = 0;
short_reads = 0;
min_bytes_read = current_raw_window_size_bytes;
max_bytes_read = 0;
}
void advanceScenario() {
bin_index++;
if (bin_index >= (sizeof(kBinSizes) / sizeof(kBinSizes[0]))) {
bin_index = 0;
}
}
bool applyScenario() {
analog_in.end();
current_raw_sample_rate = desiredRawSampleRate();
current_raw_samples_per_cycle = current_raw_sample_rate / kLineFrequencyHz;
current_raw_samples_per_window_per_channel =
current_raw_samples_per_cycle * kCyclesPerWindow;
current_raw_window_size_bytes =
current_raw_samples_per_window_per_channel * kChannels *
sizeof(int16_t);
AnalogConfigESP32V1 cfg(RX_MODE);
cfg.sample_rate = current_raw_sample_rate;
cfg.channels = kChannels;
cfg.bits_per_sample = kBitsPerSample;
cfg.buffer_size = 256;
cfg.buffer_count = 4;
cfg.is_auto_center_read = true;
cfg.adc_channels[0] = ADC_CHANNEL_4;
cfg.adc_channels[1] = ADC_CHANNEL_5;
binner.setChannels(cfg.channels);
binner.setBits(cfg.bits_per_sample);
binner.setAverage(true);
binner.setBinSize((int)currentBinSize());
binner.clear();
Serial.println();
Serial.println("ESP32 RX V1 power-line RMS FIFO stress test");
Serial.println(
"2-channel, 8-cycle RMS window with per-bin raw rate and buffer scaling");
Serial.printf("line frequency: %lu Hz, RMS window: %lu cycles (%.2f ms)\n",
(unsigned long)kLineFrequencyHz,
(unsigned long)kCyclesPerWindow,
1000.0f * (float)kCyclesPerWindow / (float)kLineFrequencyHz);
Serial.printf(
"raw sample rate: %lu Hz/channel, raw samples/window/channel: %u\n",
(unsigned long)cfg.sample_rate,
(unsigned)current_raw_samples_per_window_per_channel);
Serial.printf(
"bin: %u, effective sample rate: %lu Hz/channel, points/cycle: %.1f\n",
(unsigned)currentBinSize(), (unsigned long)effectiveSampleRate(),
effectivePointsPerCycle());
Serial.printf("raw request: %u bytes, expected output: %u bytes\n",
(unsigned)current_raw_window_size_bytes,
(unsigned)expectedOutputBytes());
if ((cfg.sample_rate * cfg.channels) > SOC_ADC_SAMPLE_FREQ_THRES_HIGH) {
Serial.printf(
"skipping: effective ADC rate %lu exceeds platform max %u\n",
(unsigned long)(cfg.sample_rate * cfg.channels),
(unsigned)SOC_ADC_SAMPLE_FREQ_THRES_HIGH);
delay(kScenarioPauseMs);
return false;
}
if (!analog_in.begin(cfg)) {
Serial.println("analog_in.begin failed");
return false;
}
delay(50);
resetScenarioStats();
return true;
}
void setup() {
Serial.begin(115200);
while (!Serial) {
delay(10);
}
AudioToolsLogger.begin(Serial, AudioToolsLogLevel::Info);
while (!applyScenario()) {
advanceScenario();
if (bin_index == 0) {
Serial.println("no supported scenarios found");
while (true) {
delay(1000);
}
}
}
first_supported_bin_index = bin_index;
cycle_initialized = true;
}
void loop() {
int avail_before = analog_in.available();
size_t bytes_read =
binned_stream.readBytes(raw_buffer, current_raw_window_size_bytes);
int avail_after = analog_in.available();
windows_completed++;
if (bytes_read != expectedOutputBytes()) {
short_reads++;
}
if (bytes_read < min_bytes_read) min_bytes_read = bytes_read;
if (bytes_read > max_bytes_read) max_bytes_read = bytes_read;
size_t frame_count = bytes_read / (kChannels * sizeof(int16_t));
int16_t *samples = (int16_t *)raw_buffer;
double voltage_rms = computeRms(samples, frame_count, 0);
double current_rms = computeRms(samples, frame_count, 1);
Serial.printf(
"bin=%u window=%lu/%lu bytes=%u expected=%u short_reads=%lu "
"avail_before=%d avail_after=%d Vrms=%.2f Irms=%.2f\n",
(unsigned)currentBinSize(), (unsigned long)windows_completed,
(unsigned long)kWindowsPerScenario, (unsigned)bytes_read,
(unsigned)expectedOutputBytes(), (unsigned long)short_reads,
avail_before, avail_after, voltage_rms, current_rms);
if (windows_completed >= kWindowsPerScenario) {
Serial.printf(
"summary bin=%u bytes[min/max]=%u/%u short_reads=%lu "
"points/cycle=%.1f\n",
(unsigned)currentBinSize(), (unsigned)min_bytes_read,
(unsigned)max_bytes_read, (unsigned long)short_reads,
effectivePointsPerCycle());
delay(kScenarioPauseMs);
do {
advanceScenario();
if (bin_index == 0) {
Serial.println("completed all supported scenarios");
while (true) {
delay(1000);
}
}
if (cycle_initialized && bin_index == first_supported_bin_index) {
analog_in.end();
Serial.println("completed one full supported scenario cycle");
while (true) {
delay(1000);
}
}
if (applyScenario()) break;
} while (true);
}
}
#else
void setup() {}
void loop() {}
#endif