Files
OptoTest/OpticalChannelTester/SignalProbe.cpp

237 lines
8.0 KiB
C++

#include "SignalProbe.h"
#include "Config.h"
#include <esp32-hal-cpu.h>
namespace {
constexpr uint32_t DISPLAY_INTERVAL_MS = 250;
constexpr uint8_t PERIOD_TOLERANCE_PCT = 10;
bool pwmPointAvailable(uint32_t frequencyHz, uint32_t pulseNs) {
return static_cast<uint64_t>(frequencyHz) * pulseNs < 1000000000ULL;
}
void formatWidth(uint64_t nanoseconds, char *out, size_t size) {
if (nanoseconds < 1000ULL)
snprintf(out, size, "%lluns", nanoseconds);
else if (nanoseconds < 1000000ULL)
snprintf(out, size, "%.2fus", nanoseconds / 1000.0);
else
snprintf(out, size, "%.2fms", nanoseconds / 1000000.0);
}
void formatPulseCount(uint64_t count, char *out, size_t size) {
if (count < 1000000ULL) snprintf(out, size, "%llu", count);
else if (count < 1000000000ULL) snprintf(out, size, "%lluM", count / 1000000ULL);
else snprintf(out, size, "%lluG", count / 1000000000ULL);
}
}
SignalProbe::SignalProbe() : startButton_(GPIO_BUTTON_START),
modeButton_(GPIO_BUTTON_MODE) {}
void SignalProbe::begin() {
Serial.begin(SERIAL_BAUD);
#if ARDUINO_USB_CDC_ON_BOOT
Serial.setTxTimeoutMs(SERIAL_TX_TIMEOUT_MS);
#endif
setCpuFrequencyMhz(160);
startButton_.begin();
modeButton_.begin();
pwm_.begin();
pwm_.configureActiveLight(true);
display_.begin();
receiverInitialized_ = receiver_.begin();
rxReady_ = receiverInitialized_ && captureStart();
applyPwm();
Serial.printf("Signal probe: MODE short=frequency, MODE long=pulse, START short=reset RX minimum, START long=PWM on/off, hold both=RX active level; RX=%s\n",
rxActiveHigh_ ? "HIGH" : "LOW");
show();
}
bool SignalProbe::captureStart() {
if (!receiver_.startRaw()) {
Serial.println("RX capture start failed");
return false;
}
havePulseStart_ = false;
havePulseEnd_ = false;
extraEdgeInPeriod_ = false;
return true;
}
void SignalProbe::resetMinimum() {
if (rxReady_) receiver_.stop();
rxReady_ = receiverInitialized_ && captureStart();
minTicks_ = UINT64_MAX;
pulseCount_ = 0;
edgeCount_ = 0;
rejectedPeriods_ = 0;
displayDirty_ = true;
Serial.println("RX minimum reset");
}
void SignalProbe::updateCapture() {
if (!rxReady_) return;
CaptureEvent edges[64];
for (uint8_t batch = 0; batch < 8; ++batch) {
const size_t count = receiver_.readRawEdges(edges, countOf(edges));
if (!count) break;
uint64_t batchMinimum = minTicks_;
uint64_t batchPulses = 0;
uint64_t batchRejected = 0;
const bool activeRising = rxActiveHigh_;
const uint32_t expectedHz = PWM_FREQUENCY_OPTIONS_HZ[frequencyIndex_];
const uint64_t expectedPeriod = receiver_.tickHz() / expectedHz;
for (size_t i = 0; i < count; ++i) {
if (edges[i].rising == activeRising) {
if (havePulseStart_) {
const uint64_t period = edges[i].tick - pulseStartTick_;
const uint64_t width = havePulseEnd_
? pulseEndTick_ - pulseStartTick_ : 0;
const uint64_t error = period > expectedPeriod
? period - expectedPeriod : expectedPeriod - period;
if (havePulseEnd_ && !extraEdgeInPeriod_ && period && width && width < period &&
error * 100U <= expectedPeriod * PERIOD_TOLERANCE_PCT) {
if (width < batchMinimum) batchMinimum = width;
++batchPulses;
} else {
++batchRejected;
}
}
pulseStartTick_ = edges[i].tick;
havePulseStart_ = true;
havePulseEnd_ = false;
extraEdgeInPeriod_ = false;
} else if (havePulseStart_ && !havePulseEnd_ &&
edges[i].tick > pulseStartTick_) {
pulseEndTick_ = edges[i].tick;
havePulseEnd_ = true;
} else if (havePulseStart_) {
extraEdgeInPeriod_ = true;
}
}
const uint32_t dropped = receiver_.takeDroppedItems();
if (dropped) {
Serial.printf("RX capture overflow: %lu edges lost; restarting capture\n", dropped);
receiver_.stop();
rxReady_ = captureStart();
displayDirty_ = true;
return;
}
edgeCount_ += count;
pulseCount_ += batchPulses;
rejectedPeriods_ += batchRejected;
displayDirty_ = true;
if (batchMinimum < minTicks_) {
minTicks_ = batchMinimum;
displayDirty_ = true;
}
}
}
void SignalProbe::applyPwm() {
pwm_.stop();
if (!pwmEnabled_) {
Serial.println("PWM off");
displayDirty_ = true;
return;
}
const uint32_t frequency = PWM_FREQUENCY_OPTIONS_HZ[frequencyIndex_];
const uint32_t pulse = MAX_PULSE_OPTIONS_NS[pulseIndex_];
if (!pwm_.start(frequency, pulse, actual_)) {
Serial.printf("PWM start failed: %lu Hz, %lu ns\n", frequency, pulse);
pwmEnabled_ = false;
} else {
Serial.printf("PWM GPIO=%u requested=%luHz/%luns actual=%luHz/%luns\n",
GPIO_PWM, frequency, pulse, actual_.actualHz, actual_.actualPulseNs);
}
displayDirty_ = true;
}
void SignalProbe::advanceFrequency() {
const uint8_t count = static_cast<uint8_t>(countOf(PWM_FREQUENCY_OPTIONS_HZ));
for (uint8_t step = 0; step < count; ++step) {
frequencyIndex_ = static_cast<uint8_t>((frequencyIndex_ + 1U) % count);
if (pwmPointAvailable(PWM_FREQUENCY_OPTIONS_HZ[frequencyIndex_],
MAX_PULSE_OPTIONS_NS[pulseIndex_])) break;
}
applyPwm();
resetMinimum();
}
void SignalProbe::advancePulse() {
const uint8_t count = static_cast<uint8_t>(countOf(MAX_PULSE_OPTIONS_NS));
for (uint8_t step = 0; step < count; ++step) {
pulseIndex_ = static_cast<uint8_t>((pulseIndex_ + 1U) % count);
if (pwmPointAvailable(PWM_FREQUENCY_OPTIONS_HZ[frequencyIndex_],
MAX_PULSE_OPTIONS_NS[pulseIndex_])) break;
}
applyPwm();
resetMinimum();
}
void SignalProbe::show() {
char frequency[16], pulse[16], first[64], second[64];
const uint32_t shownFrequency = pwm_.running() ? actual_.actualHz
: PWM_FREQUENCY_OPTIONS_HZ[frequencyIndex_];
const uint32_t shownPulse = pwm_.running() ? actual_.actualPulseNs
: MAX_PULSE_OPTIONS_NS[pulseIndex_];
Display::formatPwmFrequency(shownFrequency,
frequency, sizeof(frequency));
Display::formatPulse(shownPulse, pulse, sizeof(pulse));
snprintf(first, sizeof(first), "PWM%s %s %s", pwmEnabled_ ? "" : " OFF",
frequency, pulse);
if (!rxReady_) {
snprintf(second, sizeof(second), "RX: ERROR");
} else if (minTicks_ == UINT64_MAX) {
snprintf(second, sizeof(second), "RX E:%llu BAD:%llu", edgeCount_, rejectedPeriods_);
} else {
char width[24], count[12];
const uint64_t nanoseconds =
(minTicks_ * 1000000000ULL + receiver_.tickHz() / 2U) / receiver_.tickHz();
formatWidth(nanoseconds, width, sizeof(width));
formatPulseCount(pulseCount_, count, sizeof(count));
snprintf(second, sizeof(second), "MIN:%s N:%s", width, count);
Serial.printf("RX minimum=%lluns, periods=%llu, rejected=%llu, edges=%llu\n",
nanoseconds, pulseCount_, rejectedPeriods_, edgeCount_);
}
display_.show(first, second, 0, 0, rxActiveHigh_ ? "H" : "L");
lastDisplayMs_ = millis();
displayDirty_ = false;
}
void SignalProbe::update() {
const uint32_t now = millis();
const ButtonEvent start = startButton_.update(now);
const ButtonEvent mode = modeButton_.update(now);
if (startButton_.pressed() && modeButton_.pressed()) {
if (!bothHeld_) {
bothHeld_ = true;
bothHeldSinceMs_ = now;
startButton_.suppressUntilRelease();
modeButton_.suppressUntilRelease();
}
if (!bothHeldHandled_ && now - bothHeldSinceMs_ >= BUTTON_LONG_PRESS_MS) {
rxActiveHigh_ = !rxActiveHigh_;
bothHeldHandled_ = true;
resetMinimum();
Serial.printf("RX active level=%s\n", rxActiveHigh_ ? "HIGH" : "LOW");
}
} else if (bothHeld_) {
if (!startButton_.pressed() && !modeButton_.pressed()) {
bothHeld_ = false;
bothHeldHandled_ = false;
}
} else {
if (mode == ButtonEvent::SHORT) advanceFrequency();
else if (mode == ButtonEvent::LONG) advancePulse();
if (start == ButtonEvent::SHORT) resetMinimum();
else if (start == ButtonEvent::LONG) {
pwmEnabled_ = !pwmEnabled_;
applyPwm();
}
}
updateCapture();
if (displayDirty_ && now - lastDisplayMs_ >= DISPLAY_INTERVAL_MS) show();
}