rcbench
Servo and ESC test bench: panel, IOmcu and shared C
Loading...
Searching...
No Matches
tone.h File Reference

ESC (electronic speed controller) tones from the edges of one motor phase. More...

#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>

Go to the source code of this file.

Data Structures

struct  tone_cfg_t
 Detector settings, as given to tone_init(). More...
struct  tone_edge_t
 One edge: the tick it happened at and the level after it. More...
struct  tone_window_t
 One window of window_us. More...
struct  tone_beep_t
 One beep as the page reported it. More...
struct  tone_stats_t
 Counters of the detector, read with tone_stats(). More...
struct  tone_pend_t
 A period waiting for the pitch check. More...
struct  tone_t
 Detector state. The fields belong to tone.c. More...
struct  tone_holdoff_t
 The capture's hold-off: the rule the PIO program applies before an edge reaches the detector, written here so it is tested on the host and the PIO is held to it. More...

Macros

#define TONE_BLOCK   4u
 Periods compared at once for a pitch change.
#define TONE_OUTLIER_PCT   45u
 A period this far from its beep's mean, in percent, is left out of the mean.
#define TONE_BEEP_QUEUE   16u
 Beeps held until the caller takes them.
#define TONE_WINDOW_US_MAX   100000u
 The longest each time setting may be; tone_init() refuses more.
#define TONE_GAP_US_MAX   1000000u
#define TONE_GLITCH_NS_MAX   10000u
#define TONE_HOLD_NS_MAX   100000u

Enumerations

enum  tone_beep_flag_t { TONE_BEEP_AFTER_CHANGE = 1u << 0 , TONE_BEEP_BEFORE_CHANGE = 1u << 1 }

Functions

void tone_cfg_defaults (tone_cfg_t *c, uint32_t tick_hz)
 Defaults for a clock of tick_hz: 8000us windows, 400 to 6500Hz, carrier from 7kHz, 500ns glitch, 8000ns hold-off, 3000us gap, 8% split, 3 periods a beep, 2 a window.
bool tone_init (tone_t *d, const tone_cfg_t *c)
 Start from nothing.
void tone_edge (tone_t *d, uint64_t t, bool level)
 One edge at tick t, level after it.
void tone_feed (tone_t *d, const tone_edge_t *e, size_t n)
 n edges in time order.
void tone_advance (tone_t *d, uint64_t now)
 No edge up to tick now: a silence ends a beep, a window closes.
void tone_flush (tone_t *d)
 End the beep under way at its last edge, as a silence would, and forget the line's level, so the first rise after it starts a run: the caller's edges have a hole (a buffer overrun) or the recording ends.
bool tone_window (const tone_t *d, tone_window_t *w)
 The last window finished; false before the first.
bool tone_next_beep (tone_t *d, tone_beep_t *b)
 Take the oldest beep reported; false with none waiting.
bool tone_busy (const tone_t *d)
 A run of bursts is under way.
const tone_stats_t * tone_stats (const tone_t *d)
 The counters of the detector.
uint64_t tone_ns_ticks (uint64_t tick_hz, uint64_t ns)
 ns as ticks of a tick_hz clock, rounded up: the conversion of every time setting.
uint64_t tone_ticks_us (const tone_t *d, uint64_t ticks)
 ticks of the detector's clock in microseconds, rounded down.
void tone_holdoff_init (tone_holdoff_t *h, uint32_t tick_hz, uint32_t hold_ns)
 hold_ns of a clock of tick_hz, rounded up to whole ticks; the line starts low.
size_t tone_holdoff_edge (tone_holdoff_t *h, uint64_t t, bool level, tone_edge_t out[2])
 One edge at t; the edges reported, 0 to 2, into out in time order: a held fall whose low lasted the hold-off, then the rise.
size_t tone_holdoff_advance (tone_holdoff_t *h, uint64_t now, tone_edge_t out[1])
 No edge up to now: a held fall whose low has lasted the hold-off by then, into out; 0 or 1.
uint64_t tone_holdoff_horizon (const tone_holdoff_t *h, uint64_t now)
 now less the hold-off, at least 0, and never before the latest edge handed out: every edge before it is out, and it never runs backward, so advancing the detector to it after feeding the edges out keeps the detector's time in order.

Detailed Description

ESC (electronic speed controller) tones from the edges of one motor phase.

An ESC beeps by driving the motor windings at the tone frequency, often chopped by its PWM (pulse-width modulation) carrier at 8 to 48kHz. One phase reaches an IOmcu input through a series resistor and a zener clamp, so the input is a logic level: high while the phase is driven high, or floats above the input threshold, and low otherwise. A PIO (programmable input/output) state machine stamps each edge on a counter; this module turns those stamps into tones.

The signal's three time scales, and how each is found:

  • Carrier. A rise that follows the rise before it by at most the period of carrier_min_hz is a carrier-rate rise. The carrier period is the shortest rise interval inside a burst, not counting the interval from the burst's first rise, which a burst gated from a carrier running free can cut short. Until a burst has had a third rise, it is the shortest carrier-rate low plus the longest carrier-rate high. Both are kept from the run's first rise. Once a carrier is seen, a rise starts a burst when the low before it is at least 1.25 carrier periods: a low inside a burst is shorter than one period, and the quarter is room for edges that move. With no carrier seen, the drive is not chopped and every pulse is a burst. A duty that changes inside a beep moves lows and highs and leaves the period. A low shorter than glitch_ns is no low: the pulse goes on. With a capture hold-off, a rise starts a burst only after a low of at least twice the hold-off: a carrier low within the edges' jitter of the hold-off is reported at some periods and not at others, and one reported after a stretch of swallowed ones follows its rise by several carrier periods, too far apart to read as carrier. The tone's own low between bursts has to be that long.
  • Tone. The interval from one burst's first rise to the next is one tone period. A period between half the period of f_max_hz and the period of f_min_hz is in range: a burst start moved by a free-running carrier shortens one period and lengthens the next, and the pair still counts. A window holds a tone when at least window_min_periods in-range periods end in it and their mean lies between the periods of f_max_hz and f_min_hz; its frequency is that mean's.
  • Beep. A beep is a run of bursts. It ends where the line has had no edge for gap_us, or where a block of TONE_BLOCK periods gives a pitch more than split_pct away from the beep's mean: a pitch change with no silence between, which splits the beep at the first period nearer the new pitch than the old. A block's pitch is the mean of its periods within TONE_OUTLIER_PCT of their median; it has moved when all but one of the block's periods lie more than half of split_pct from the beep's mean on its side. A beep with fewer than TONE_BLOCK periods in its mean does not split: when a block moves by TONE_OUTLIER_PCT it starts its mean again and keeps its start, its first periods having come before the carrier was known. A beep reports its first rise and its last edge, the mean of its in-range periods as its frequency, and its burst count. A period more than TONE_OUTLIER_PCT from the beep's mean is a burst but not part of the mean: a burst missed doubles one period. A run with fewer than min_periods in-range periods, or whose mean lies outside the tone range, is not a beep: a click at power-up, a stuck line, a drive faster than f_max_hz.

Bounds, each in whole ticks:

  • Inclusive. A rise interval of exactly the period of carrier_min_hz is carrier-rate. A low of exactly 1.25 carrier periods starts a burst. A low of exactly glitch_ns is a low, and one of exactly twice the hold-off can start a burst. A silence of exactly gap_us ends a beep. A tone of exactly f_min_hz or f_max_hz is in range, and a period of exactly half the period of f_max_hz. A beep of exactly min_periods periods, and a window of exactly window_min_periods, hold a tone. An edge at the same tick as the last is taken.
  • Exclusive. A pitch change is more than split_pct, so a block exactly split_pct away does not split; a period is an outlier when more than TONE_OUTLIER_PCT from the mean. A window is [n, n + 1) window_us: a period that ends on the boundary belongs to the next window.
  • Rounding. The period of f_max_hz rounds down, the periods of f_min_hz and carrier_min_hz round up, so a period exactly at a bound, which the tick clock sees as the tick below or the tick above, is inside it. Every time setting – window_us, glitch_ns, hold_ns, gap_us – rounds up to whole ticks by one rule, and start_low is twice the hold-off so rounded, the hold-off the capture applies: a low or a silence counts once it has lasted that long.

What the edges cannot tell:

  • A drive that commutates, as a motor start does, at a rate in the tone range is a tone here. The caller listens only while the ESC sounds its menu.
  • A tone whose drive is off for less than 1.25 carrier periods between bursts merges them: the tone is heard as one long burst and reported as no tone.
  • The tapped phase sees the bursts of the phase pair the ESC drives. An ESC that reverses the pair every half period shows two bursts a period on the floating phase, and the frequency reads one octave high. Pitches compared on one bench are compared on one phase, so a recorded reference carries the same factor.
  • A carrier that runs free of the tone moves each burst's first rise by up to one carrier period, so a single period is known to one carrier period; a mean over n periods to one carrier period in n.

Time is a count of ticks of tick_hz, 64 bits wide, never wrapping on a bench; the caller extends the PIO's counter. Edges arrive in time order. tone_advance() tells the detector time has passed without an edge, so a silence ends a beep and a window closes without one.

Pure C, no SDK (software development kit), no allocation; no division and no floating point per edge. Host-tested in test_tone.

SPDX-License-Identifier: MIT

Macro Definition Documentation

◆ TONE_GAP_US_MAX

#define TONE_GAP_US_MAX   1000000u

1s

◆ TONE_GLITCH_NS_MAX

#define TONE_GLITCH_NS_MAX   10000u

10us

◆ TONE_HOLD_NS_MAX

#define TONE_HOLD_NS_MAX   100000u

100us

◆ TONE_WINDOW_US_MAX

#define TONE_WINDOW_US_MAX   100000u

The longest each time setting may be; tone_init() refuses more.

At these, every setting is under 2^32 ticks at any 32-bit tick_hz. 100ms

Enumeration Type Documentation

◆ tone_beep_flag_t

Enumerator
TONE_BEEP_AFTER_CHANGE 

Began at a pitch change, with no silence before it.

TONE_BEEP_BEFORE_CHANGE 

Ended at a pitch change, with no silence after it.

Function Documentation

◆ tone_advance()

void tone_advance ( tone_t * d,
uint64_t now )

No edge up to tick now: a silence ends a beep, a window closes.

Parameters
dDetector state.
nowTick the detector is advanced to.

◆ tone_busy()

bool tone_busy ( const tone_t * d)

A run of bursts is under way.

Parameters
dDetector state.
Returns
true while a run is open.

◆ tone_cfg_defaults()

void tone_cfg_defaults ( tone_cfg_t * c,
uint32_t tick_hz )

Defaults for a clock of tick_hz: 8000us windows, 400 to 6500Hz, carrier from 7kHz, 500ns glitch, 8000ns hold-off, 3000us gap, 8% split, 3 periods a beep, 2 a window.

Parameters
cSettings to fill.
tick_hzClock of the edge stamps, Hz.

◆ tone_edge()

void tone_edge ( tone_t * d,
uint64_t t,
bool level )

One edge at tick t, level after it.

Parameters
dDetector state.
tTick of the edge.
levelLevel after the edge: true for a rise.

◆ tone_feed()

void tone_feed ( tone_t * d,
const tone_edge_t * e,
size_t n )

n edges in time order.

Parameters
dDetector state.
eEdges, in time order.
nNumber of edges in e.

◆ tone_flush()

void tone_flush ( tone_t * d)

End the beep under way at its last edge, as a silence would, and forget the line's level, so the first rise after it starts a run: the caller's edges have a hole (a buffer overrun) or the recording ends.

Parameters
dDetector state.

◆ tone_holdoff_advance()

size_t tone_holdoff_advance ( tone_holdoff_t * h,
uint64_t now,
tone_edge_t out[1] )

No edge up to now: a held fall whose low has lasted the hold-off by then, into out; 0 or 1.

Parameters
hHold-off state.
nowTick the hold-off is advanced to.
outReceives the released fall.
Returns
0 or 1, the number of edges written to out.

◆ tone_holdoff_edge()

size_t tone_holdoff_edge ( tone_holdoff_t * h,
uint64_t t,
bool level,
tone_edge_t out[2] )

One edge at t; the edges reported, 0 to 2, into out in time order: a held fall whose low lasted the hold-off, then the rise.

Parameters
hHold-off state.
tTick of the edge.
levelLevel after the edge: true for a rise.
outReceives the edges that are released.
Returns
Number of edges written to out.

◆ tone_holdoff_horizon()

uint64_t tone_holdoff_horizon ( const tone_holdoff_t * h,
uint64_t now )

now less the hold-off, at least 0, and never before the latest edge handed out: every edge before it is out, and it never runs backward, so advancing the detector to it after feeding the edges out keeps the detector's time in order.

Parameters
hHold-off state.
nowCurrent tick.
Returns
The horizon tick.

◆ tone_holdoff_init()

void tone_holdoff_init ( tone_holdoff_t * h,
uint32_t tick_hz,
uint32_t hold_ns )

hold_ns of a clock of tick_hz, rounded up to whole ticks; the line starts low.

Parameters
hHold-off state to initialise.
tick_hzClock, Hz.
hold_nsHold-off, nanoseconds.

◆ tone_init()

bool tone_init ( tone_t * d,
const tone_cfg_t * c )

Start from nothing.

False, leaving d unusable, when c contradicts itself or names a range the tick clock cannot resolve: f_min_hz >= f_max_hz, carrier_min_hz <= f_max_hz, f_max_hz or carrier_min_hz above tick_hz (a period under one tick), hold_ns above TONE_HOLD_NS_MAX, glitch_ns outside 1 to TONE_GLITCH_NS_MAX or under one tick, gap_us under the period of f_min_hz or above TONE_GAP_US_MAX, a window outside 1000us to TONE_WINDOW_US_MAX, f_min_hz under 50Hz, a minimum of 0, split_pct above 100. Also refused, as settings under which no signal makes a tone, in whole ticks: the period of f_min_hz, or the gap, at most the glitch or at most twice the hold-off (no low could part two bursts without being a glitch, being too short to start one, or ending the run); the period of f_max_hz rounded down at most the period of carrier_min_hz rounded up, where the tone and carrier ranges meet in whole ticks; window_min_periods - 1 of the shortest burst-start spacings (tone_t's spacing) filling a window.

Parameters
dDetector state; set up by this call.
cSettings to check and use.
Returns
true when the detector is ready.

◆ tone_next_beep()

bool tone_next_beep ( tone_t * d,
tone_beep_t * b )

Take the oldest beep reported; false with none waiting.

Parameters
dDetector state.
bReceives the beep.
Returns
false with none waiting.

◆ tone_ns_ticks()

uint64_t tone_ns_ticks ( uint64_t tick_hz,
uint64_t ns )

ns as ticks of a tick_hz clock, rounded up: the conversion of every time setting.

Exact for every result that fits 64 bits; no intermediate product overflows.

Parameters
tick_hzClock, Hz.
nsTime, nanoseconds.
Returns
Ticks, rounded up.

◆ tone_stats()

const tone_stats_t * tone_stats ( const tone_t * d)

The counters of the detector.

Parameters
dDetector state.
Returns
Pointer to the counters inside d.

◆ tone_ticks_us()

uint64_t tone_ticks_us ( const tone_t * d,
uint64_t ticks )

ticks of the detector's clock in microseconds, rounded down.

Parameters
dDetector state; gives the clock.
ticksTicks of the detector's clock.
Returns
Microseconds, rounded down.

◆ tone_window()

bool tone_window ( const tone_t * d,
tone_window_t * w )

The last window finished; false before the first.

Parameters
dDetector state.
wReceives the window.
Returns
false before the first window is finished.