237 lines
8.0 KiB
C++
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();
|
|
}
|