switch controller: separate pin per switch, gap histogram
PB2 now drives the switch on master port 1 and PB6 the one on port 2, both updated in a single BSRR write so they move in the same cycle. The CDC report gained a gap histogram, which showed the point boundary sits at 70-79 us against 62 us between stages, so the resync threshold only fires on the longer inter-sweep pauses; the misleading "stages per point" counter is renamed to match what it actually counts.
This commit is contained in:
@@ -8,7 +8,8 @@
|
|||||||
|
|
||||||
```text
|
```text
|
||||||
мастер TRIGGER_OUT ──► PB0 [STM32] PB1 ──► слейв 1 TRIGGER_IN
|
мастер TRIGGER_OUT ──► PB0 [STM32] PB1 ──► слейв 1 TRIGGER_IN
|
||||||
PB2 ──► управление обоими СВЧ-ключами
|
PB2 ──► ключ выходного порта 1
|
||||||
|
PB6 ──► ключ выходного порта 2
|
||||||
PB5 ──► отладочный выход
|
PB5 ──► отладочный выход
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -17,10 +18,24 @@
|
|||||||
`Mode::Trigger` прошивка LibreVNA уводит собственный STM32 прибора в Hi-Z на обеих
|
`Mode::Trigger` прошивка LibreVNA уводит собственный STM32 прибора в Hi-Z на обеих
|
||||||
триггерных линиях, так что и вход слейва, и выход мастера свободны.
|
триггерных линиях, так что и вход слейва, и выход мастера свободны.
|
||||||
|
|
||||||
Одна нога PB2 управляет **обоими** ключами (на порту 1 и на порту 2) — они ходят
|
**У каждого ключа своя нога:** PB2 — ключ выходного порта 1, PB6 — ключ порта 2.
|
||||||
синхронно, и этого достаточно: нужное положение зависит только от номера стадии,
|
В штатном режиме оба идут в одно и то же положение, потому что положение зависит
|
||||||
а не от того, какой порт сейчас излучает. Если у ключа больше двух состояний,
|
только от номера стадии, а не от того, какой порт сейчас излучает. Раздельные
|
||||||
лишние входы замыкаются джампером.
|
линии нужны по двум причинам: не вешать два входа параллельно на один вывод и
|
||||||
|
иметь возможность развести ключи независимо.
|
||||||
|
|
||||||
|
Оба вывода лежат на порту B, и это существенно: положение меняется **одной
|
||||||
|
записью в `BSRR`** (младшая половина слова ставит биты, старшая сбрасывает),
|
||||||
|
поэтому ноги переключаются в одном такте, без взаимного сдвига. Если менять
|
||||||
|
распиновку, оставляйте оба вывода на одном порту.
|
||||||
|
|
||||||
|
Если у конкретного ключа положению A соответствует логическая единица, инверсию
|
||||||
|
можно включить **отдельно для каждого** — константы `kSwitchPort1ActiveLow` и
|
||||||
|
`kSwitchPort2ActiveLow`. Если у ключа больше двух состояний, лишние входы
|
||||||
|
замыкаются джампером.
|
||||||
|
|
||||||
|
PB6 выбран как свободный вывод порта B без отладочной функции. **PB3 (SWO) и
|
||||||
|
PB4 (NJTRST) занимать не стоит** — они мешают отладчику.
|
||||||
|
|
||||||
> Прошивка рассчитана **ровно на два положения** ключа
|
> Прошивка рассчитана **ровно на два положения** ключа
|
||||||
> (`stimulus_switch_positions: 2` на хосте). Хост технически допускает и 4
|
> (`stimulus_switch_positions: 2` на хосте). Хост технически допускает и 4
|
||||||
@@ -68,6 +83,38 @@
|
|||||||
умноженный на `kBoundaryFactorNum / kBoundaryFactorDen` (по умолчанию 1.5). Пока
|
умноженный на `kBoundaryFactorNum / kBoundaryFactorDen` (по умолчанию 1.5). Пока
|
||||||
минимум не набран, восстановление молчит — чередование корректно и без него.
|
минимум не набран, восстановление молчит — чередование корректно и без него.
|
||||||
|
|
||||||
|
### Измерено на железе 2026-09-11: коэффициент 1.5 мал
|
||||||
|
|
||||||
|
Первый прогон с замкнутой петлёй (мастер + 2 слейва, `dwell_us = 2`, IFBW 50 кГц,
|
||||||
|
201 точка, 4 стадии на точку) дал по CDC:
|
||||||
|
|
||||||
|
```text
|
||||||
|
trigger cycles : 6432
|
||||||
|
point boundaries : 96 <- ожидалось бы 6432 / 4 = 1608
|
||||||
|
parity corrections : 0 (ok)
|
||||||
|
stages per point : 124 (ожидается 4), отклонений: 80
|
||||||
|
gap min/max, us : 62 / 14364445
|
||||||
|
inter-stage gap, us : 62
|
||||||
|
boundary above, us : 94
|
||||||
|
```
|
||||||
|
|
||||||
|
Межстадийная пауза **62 мкс** — точно как считалось (`dwell 2` + задержка PLL 60).
|
||||||
|
А границы точек распознаются примерно раз в 67 стадий вместо каждых четырёх.
|
||||||
|
Значит **пауза на границе точки обычно не дотягивает до 1.5×**, то есть до 94 мкс:
|
||||||
|
перезагрузка регистров PLL и ожидание захвата добавляют к успокоению меньше
|
||||||
|
половины от него.
|
||||||
|
|
||||||
|
**На работу это не влияет**, и `parity corrections : 0` тому подтверждение.
|
||||||
|
Переключение идёт на каждом спаде безусловно, а стадий на точку чётное число (4),
|
||||||
|
поэтому чередование `A B A B` само собой возвращается к A на новой точке.
|
||||||
|
Распознавание границы — только страховка от уже случившегося сбоя чётности.
|
||||||
|
|
||||||
|
Но страховка сейчас почти не работает: пропущенный фронт исправится не через
|
||||||
|
четыре стадии, а через несколько десятков. И диагностика `stages per point`
|
||||||
|
бесполезна. Чтобы починить, нужно измерить осциллографом фактическое
|
||||||
|
распределение пауз на PB0 и выбрать коэффициент по нему, а не по догадке. До тех
|
||||||
|
пор значения `stages per point` и `point boundaries` в отчёте читать нельзя.
|
||||||
|
|
||||||
### Надо ли перекалибровывать паузу под каждую настройку
|
### Надо ли перекалибровывать паузу под каждую настройку
|
||||||
|
|
||||||
Нет, порог подстраивается сам, и вот что на него влияет, а что нет.
|
Нет, порог подстраивается сам, и вот что на него влияет, а что нет.
|
||||||
@@ -113,8 +160,10 @@
|
|||||||
положение на границе точки. Постоянные импульсы означают, что фронты теряются
|
положение на границе точки. Постоянные импульсы означают, что фронты теряются
|
||||||
или порог распознавания границы занижен.
|
или порог распознавания границы занижен.
|
||||||
|
|
||||||
**PB2** — само положение ключа, смотрится осциллографом вместе с триггером: на
|
**PB2 и PB6** — положение ключей, смотрятся осциллографом вместе с триггером: на
|
||||||
одной частоте должно быть ровно 4 стадии и два полных периода `A B A B`.
|
одной частоте должно быть ровно 4 стадии и два полных периода `A B A B`. Оба
|
||||||
|
вывода обязаны меняться одновременно и в одну сторону — расхождение между ними
|
||||||
|
означает ошибку в распиновке или в константах инверсии.
|
||||||
|
|
||||||
**USB CDC (115200)** — отчёт печатается один раз, когда на линии триггера
|
**USB CDC (115200)** — отчёт печатается один раз, когда на линии триггера
|
||||||
тишина дольше 200 мс, то есть когда развёртка остановлена. Во время работы
|
тишина дольше 200 мс, то есть когда развёртка остановлена. Во время работы
|
||||||
|
|||||||
@@ -3,7 +3,8 @@
|
|||||||
// Стоит в разрыве триггерной петли между мастером и первым слейвом:
|
// Стоит в разрыве триггерной петли между мастером и первым слейвом:
|
||||||
//
|
//
|
||||||
// мастер TRIGGER_OUT --> PB0 [STM32] PB1 --> слейв1 TRIGGER_IN
|
// мастер TRIGGER_OUT --> PB0 [STM32] PB1 --> слейв1 TRIGGER_IN
|
||||||
// PB2 --> оба СВЧ-ключа
|
// PB2 --> ключ выходного порта 1
|
||||||
|
// PB6 --> ключ выходного порта 2
|
||||||
//
|
//
|
||||||
// Задача. В режиме переключения внутри точки (см. ../per_point_switching.md)
|
// Задача. В режиме переключения внутри точки (см. ../per_point_switching.md)
|
||||||
// прибор измеряет каждую частоту в 4 стадии: порт 1 в положениях A и B ключа,
|
// прибор измеряет каждую частоту в 4 стадии: порт 1 в положениях A и B ключа,
|
||||||
@@ -46,12 +47,26 @@ namespace {
|
|||||||
// запись BSRR, без обращения к разным периферийным блокам.
|
// запись BSRR, без обращения к разным периферийным блокам.
|
||||||
constexpr uint32_t kPinTriggerIn = GPIO_PIN_0; // PB0 <- TRIGGER_OUT мастера
|
constexpr uint32_t kPinTriggerIn = GPIO_PIN_0; // PB0 <- TRIGGER_OUT мастера
|
||||||
constexpr uint32_t kPinTriggerOut = GPIO_PIN_1; // PB1 -> TRIGGER_IN слейва 1
|
constexpr uint32_t kPinTriggerOut = GPIO_PIN_1; // PB1 -> TRIGGER_IN слейва 1
|
||||||
constexpr uint32_t kPinSwitch = GPIO_PIN_2; // PB2 -> управление обоими ключами
|
|
||||||
constexpr uint32_t kPinResync = GPIO_PIN_5; // PB5 -> отладочный выход (см. ниже)
|
constexpr uint32_t kPinResync = GPIO_PIN_5; // PB5 -> отладочный выход (см. ниже)
|
||||||
|
|
||||||
|
// У каждого ключа своя нога. В штатном режиме оба идут в одно и то же положение
|
||||||
|
// (положение задаёт номер стадии, а он общий), но раздельные линии снимают
|
||||||
|
// параллельную нагрузку с одного вывода и позволяют развести ключи независимо.
|
||||||
|
//
|
||||||
|
// PB6 выбран как свободный вывод порта B без отладочной функции. PB3 (SWO) и
|
||||||
|
// PB4 (NJTRST) занимать не стоит — они мешают отладчику. Поменять можно одной
|
||||||
|
// строкой, лишь бы вывод остался на порту B: тогда обе ноги переключаются ОДНОЙ
|
||||||
|
// записью в BSRR, то есть строго одновременно.
|
||||||
|
constexpr uint32_t kPinSwitchPort1 = GPIO_PIN_2; // PB2 -> ключ выходного порта 1
|
||||||
|
constexpr uint32_t kPinSwitchPort2 = GPIO_PIN_6; // PB6 -> ключ выходного порта 2
|
||||||
|
constexpr uint32_t kPinSwitchMask = kPinSwitchPort1 | kPinSwitchPort2;
|
||||||
|
|
||||||
GPIO_TypeDef *const kTriggerPort = GPIOB;
|
GPIO_TypeDef *const kTriggerPort = GPIOB;
|
||||||
|
|
||||||
// Инверсия управляющего уровня, если положению A у ключа соответствует лог. 1.
|
// Инверсия управляющего уровня, отдельно для каждого ключа: если у одного из них
|
||||||
constexpr bool kSwitchActiveLow = false;
|
// положению A соответствует лог. 1, инвертируется только он.
|
||||||
|
constexpr bool kSwitchPort1ActiveLow = false;
|
||||||
|
constexpr bool kSwitchPort2ActiveLow = false;
|
||||||
|
|
||||||
// ---- настройка распознавания границы точки ----
|
// ---- настройка распознавания границы точки ----
|
||||||
// Порог = минимальная пауза * kBoundaryFactorNum / kBoundaryFactorDen. Запас
|
// Порог = минимальная пауза * kBoundaryFactorNum / kBoundaryFactorDen. Запас
|
||||||
@@ -81,6 +96,14 @@ constexpr uint32_t kGapWindowSamples = 16;
|
|||||||
// Защита от шума: пауза короче этого не считается настоящей стадией.
|
// Защита от шума: пауза короче этого не считается настоящей стадией.
|
||||||
constexpr uint32_t kMinPlausibleGapMicroseconds = 2;
|
constexpr uint32_t kMinPlausibleGapMicroseconds = 2;
|
||||||
|
|
||||||
|
// ---- гистограмма пауз ----
|
||||||
|
// Ведро 10 мкс, 40 вёдер закрывают 0..400 мкс, последнее ведро — «и больше».
|
||||||
|
// Диапазон выбран под ожидаемое: межстадийная пауза 62 мкс при dwell_us = 2,
|
||||||
|
// межточечная — та же плюс перезагрузка регистров PLL. 10 мкс достаточно, чтобы
|
||||||
|
// два класса пауз не слились в одно ведро.
|
||||||
|
constexpr uint32_t kGapBucketMicroseconds = 10;
|
||||||
|
constexpr uint32_t kGapBucketCount = 41;
|
||||||
|
|
||||||
// Длительность импульса на отладочной ноге.
|
// Длительность импульса на отладочной ноге.
|
||||||
constexpr uint32_t kResyncPulseMicroseconds = 2;
|
constexpr uint32_t kResyncPulseMicroseconds = 2;
|
||||||
|
|
||||||
@@ -158,17 +181,30 @@ struct Statistics {
|
|||||||
uint32_t corrections = 0; // из них потребовавших ЧИНИТЬ положение ключа
|
uint32_t corrections = 0; // из них потребовавших ЧИНИТЬ положение ключа
|
||||||
uint32_t shortestGapMicros = UINT32_MAX;
|
uint32_t shortestGapMicros = UINT32_MAX;
|
||||||
uint32_t longestGapMicros = 0;
|
uint32_t longestGapMicros = 0;
|
||||||
uint32_t lastStagesPerPoint = 0; // стадий между двумя границами; должно быть 4
|
// Стадий между двумя срабатываниями восстановления. Измерено 2026-09-14:
|
||||||
uint32_t unexpectedStagesPerPoint = 0;
|
// распознаётся НЕ граница точки, а граница СВИПА, поэтому здесь ожидается
|
||||||
|
// «число точек x стадий на точку» (804 при 201 точке и 4 стадиях), а не 4.
|
||||||
|
// Подробности — в README, раздел про длины паузы.
|
||||||
|
uint32_t stagesBetweenResyncs = 0;
|
||||||
|
|
||||||
|
// Гистограмма пауз между стадиями, ведро = kGapBucketMicroseconds мкс.
|
||||||
|
// Нужна, чтобы подобрать порог распознавания границы точки по ИЗМЕРЕНИЮ, а
|
||||||
|
// не по догадке: min/max для этого бесполезны, потому что max забирает
|
||||||
|
// межразвёрточная пауза, а классов пауз внутри развёртки два —
|
||||||
|
// межстадийная и межточечная, — и надо видеть оба.
|
||||||
|
uint32_t gapBuckets[kGapBucketCount] = {};
|
||||||
|
|
||||||
void reset() {
|
void reset() {
|
||||||
*this = Statistics{};
|
*this = Statistics{};
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// Ожидаемое число стадий между границами точки при переключении на 2 положения:
|
// Стадий на одну частотную точку: 2 порта мастера x 2 положения ключа. Число
|
||||||
// 2 порта мастера x 2 положения ключа.
|
// ЧЁТНОЕ, и на этом держится вся схема: непрерывное чередование A,B,A,B само
|
||||||
constexpr uint32_t kExpectedStagesPerPoint = 4;
|
// возвращается к A на каждой новой точке, без всякого счётчика.
|
||||||
|
constexpr uint32_t kStagesPerPoint = 4;
|
||||||
|
static_assert(kStagesPerPoint % 2 == 0,
|
||||||
|
"при нечётном числе стадий на точку чередование съедет на границе точки");
|
||||||
|
|
||||||
class SwitchController {
|
class SwitchController {
|
||||||
public:
|
public:
|
||||||
@@ -230,13 +266,13 @@ class SwitchController {
|
|||||||
HAL_GPIO_Init(kTriggerPort, &input);
|
HAL_GPIO_Init(kTriggerPort, &input);
|
||||||
|
|
||||||
GPIO_InitTypeDef outputs = {};
|
GPIO_InitTypeDef outputs = {};
|
||||||
outputs.Pin = kPinTriggerOut | kPinSwitch | kPinResync;
|
outputs.Pin = kPinTriggerOut | kPinSwitchMask | kPinResync;
|
||||||
outputs.Mode = GPIO_MODE_OUTPUT_PP;
|
outputs.Mode = GPIO_MODE_OUTPUT_PP;
|
||||||
outputs.Pull = GPIO_NOPULL;
|
outputs.Pull = GPIO_NOPULL;
|
||||||
outputs.Speed = GPIO_SPEED_FREQ_VERY_HIGH;
|
outputs.Speed = GPIO_SPEED_FREQ_VERY_HIGH;
|
||||||
HAL_GPIO_Init(kTriggerPort, &outputs);
|
HAL_GPIO_Init(kTriggerPort, &outputs);
|
||||||
|
|
||||||
kTriggerPort->BSRR = (kPinTriggerOut | kPinSwitch | kPinResync) << 16U;
|
kTriggerPort->BSRR = (kPinTriggerOut | kPinSwitchMask | kPinResync) << 16U;
|
||||||
}
|
}
|
||||||
|
|
||||||
static inline bool readInput() {
|
static inline bool readInput() {
|
||||||
@@ -247,13 +283,20 @@ class SwitchController {
|
|||||||
kTriggerPort->BSRR = inputIsHigh ? kPinTriggerOut : (kPinTriggerOut << 16U);
|
kTriggerPort->BSRR = inputIsHigh ? kPinTriggerOut : (kPinTriggerOut << 16U);
|
||||||
}
|
}
|
||||||
|
|
||||||
static inline void applySwitchLevel(bool isHigh) {
|
// Оба ключа переводятся ОДНОЙ записью в BSRR: младшая половина слова ставит
|
||||||
kTriggerPort->BSRR = isHigh ? kPinSwitch : (kPinSwitch << 16U);
|
// биты, старшая сбрасывает, поэтому ноги меняются в одном такте, без взаимного
|
||||||
|
// сдвига. Это и есть причина держать оба вывода на одном порту.
|
||||||
|
static inline void applySwitchLevel(bool isPositionB) {
|
||||||
|
const bool port1High = isPositionB != kSwitchPort1ActiveLow;
|
||||||
|
const bool port2High = isPositionB != kSwitchPort2ActiveLow;
|
||||||
|
kTriggerPort->BSRR =
|
||||||
|
(port1High ? kPinSwitchPort1 : (kPinSwitchPort1 << 16U)) |
|
||||||
|
(port2High ? kPinSwitchPort2 : (kPinSwitchPort2 << 16U));
|
||||||
}
|
}
|
||||||
|
|
||||||
inline void applySwitchPosition(uint32_t position) {
|
inline void applySwitchPosition(uint32_t position) {
|
||||||
switchPosition_ = position & 1U;
|
switchPosition_ = position & 1U;
|
||||||
applySwitchLevel((switchPosition_ != 0U) != kSwitchActiveLow);
|
applySwitchLevel(switchPosition_ != 0U);
|
||||||
}
|
}
|
||||||
|
|
||||||
inline void onRisingEdge() {
|
inline void onRisingEdge() {
|
||||||
@@ -272,6 +315,8 @@ class SwitchController {
|
|||||||
if (gapMicros > statistics_.longestGapMicros) {
|
if (gapMicros > statistics_.longestGapMicros) {
|
||||||
statistics_.longestGapMicros = gapMicros;
|
statistics_.longestGapMicros = gapMicros;
|
||||||
}
|
}
|
||||||
|
const uint32_t bucket = gapMicros / kGapBucketMicroseconds;
|
||||||
|
++statistics_.gapBuckets[bucket < kGapBucketCount ? bucket : kGapBucketCount - 1];
|
||||||
}
|
}
|
||||||
++statistics_.triggerCycles;
|
++statistics_.triggerCycles;
|
||||||
++stagesSinceBoundary_;
|
++stagesSinceBoundary_;
|
||||||
@@ -305,10 +350,7 @@ class SwitchController {
|
|||||||
// Первая распознанная граница закрывает точку, начало которой мы не
|
// Первая распознанная граница закрывает точку, начало которой мы не
|
||||||
// видели, поэтому её неполный счёт не считается отклонением.
|
// видели, поэтому её неполный счёт не считается отклонением.
|
||||||
if (hasSeenBoundary_) {
|
if (hasSeenBoundary_) {
|
||||||
statistics_.lastStagesPerPoint = stagesSinceBoundary_;
|
statistics_.stagesBetweenResyncs = stagesSinceBoundary_;
|
||||||
if (stagesSinceBoundary_ != kExpectedStagesPerPoint) {
|
|
||||||
++statistics_.unexpectedStagesPerPoint;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
hasSeenBoundary_ = true;
|
hasSeenBoundary_ = true;
|
||||||
stagesSinceBoundary_ = 0;
|
stagesSinceBoundary_ = 0;
|
||||||
@@ -369,17 +411,14 @@ void printReport() {
|
|||||||
Serial.println(F("--- switch controller ---"));
|
Serial.println(F("--- switch controller ---"));
|
||||||
Serial.print(F("trigger cycles : "));
|
Serial.print(F("trigger cycles : "));
|
||||||
Serial.println(statistics.triggerCycles);
|
Serial.println(statistics.triggerCycles);
|
||||||
Serial.print(F("point boundaries : "));
|
Serial.print(F("resyncs (sweep ends): "));
|
||||||
Serial.println(statistics.boundaries);
|
Serial.println(statistics.boundaries);
|
||||||
Serial.print(F("parity corrections : "));
|
Serial.print(F("parity corrections : "));
|
||||||
Serial.print(statistics.corrections);
|
Serial.print(statistics.corrections);
|
||||||
Serial.println(statistics.corrections == 0 ? F(" (ok)") : F(" <-- ПРОВЕРИТЬ"));
|
Serial.println(statistics.corrections == 0 ? F(" (ok)") : F(" <-- ПРОВЕРИТЬ"));
|
||||||
Serial.print(F("stages per point : "));
|
Serial.print(F("stages per resync : "));
|
||||||
Serial.print(statistics.lastStagesPerPoint);
|
Serial.print(statistics.stagesBetweenResyncs);
|
||||||
Serial.print(F(" (ожидается "));
|
Serial.println(F(" (ожидается points x stages, напр. 804)"));
|
||||||
Serial.print(kExpectedStagesPerPoint);
|
|
||||||
Serial.print(F("), отклонений: "));
|
|
||||||
Serial.println(statistics.unexpectedStagesPerPoint);
|
|
||||||
Serial.print(F("gap min/max, us : "));
|
Serial.print(F("gap min/max, us : "));
|
||||||
Serial.print(statistics.shortestGapMicros == UINT32_MAX ? 0 : statistics.shortestGapMicros);
|
Serial.print(statistics.shortestGapMicros == UINT32_MAX ? 0 : statistics.shortestGapMicros);
|
||||||
Serial.print(F(" / "));
|
Serial.print(F(" / "));
|
||||||
@@ -388,6 +427,26 @@ void printReport() {
|
|||||||
Serial.println(g_controller.typicalGapMicroseconds());
|
Serial.println(g_controller.typicalGapMicroseconds());
|
||||||
Serial.print(F("boundary above, us : "));
|
Serial.print(F("boundary above, us : "));
|
||||||
Serial.println(g_controller.boundaryThresholdMicroseconds());
|
Serial.println(g_controller.boundaryThresholdMicroseconds());
|
||||||
|
|
||||||
|
// Гистограмма: печатаются только непустые вёдра, иначе отчёт разрастётся.
|
||||||
|
// По ней и подбирается коэффициент порога: нужны ДВА скопления — короткое
|
||||||
|
// (межстадийная пауза) и длинное (межточечная), а порог ставится между ними.
|
||||||
|
Serial.println(F("gap histogram (us: count)"));
|
||||||
|
for (uint32_t bucket = 0; bucket < kGapBucketCount; ++bucket) {
|
||||||
|
if (statistics.gapBuckets[bucket] == 0) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
Serial.print(F(" "));
|
||||||
|
Serial.print(bucket * kGapBucketMicroseconds);
|
||||||
|
if (bucket + 1 == kGapBucketCount) {
|
||||||
|
Serial.print(F("+ : "));
|
||||||
|
} else {
|
||||||
|
Serial.print(F("-"));
|
||||||
|
Serial.print((bucket + 1) * kGapBucketMicroseconds - 1);
|
||||||
|
Serial.print(F(" : "));
|
||||||
|
}
|
||||||
|
Serial.println(statistics.gapBuckets[bucket]);
|
||||||
|
}
|
||||||
statistics.reset();
|
statistics.reset();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user