ww_logic_analyzer¶
Part of the IO trait family. ww_logic_analyzer is a global WireWeaver trait family for timestamped capture of many digital channels on one
common time base: an MCU with a DMA-able GPIO port or a PIO, up to a dedicated analyzer. It is no_std by default
and depends on wire_weaver, ww_si (Hertz/Second/Volt), ww_gpio (Level) and ww_i2c (I2cAddress, for
a protocol trigger stage).
Add it to a device API crate with:
It sits next to ww_gpio rather than inside it because GPIO is one pin, one call, no timing; here
the whole point is timing: every sample of every channel lands on one grid. Protocol decoding (UART, I2C, SPI, CAN
on top of captured edges) stays on the host; a device that decodes a protocol itself implements that protocol's own
monitor (Uart::monitor, I2cMonitor, Spi::monitor) instead.
Traits¶
A device with only capture memory implements LogicAnalyzer; one that can stream while sampling adds
LogicStream. Both follow the same conventions as the rest of the IO trait family.
LogicAnalyzer¶
Triggered capture into device RAM, read out after — the same shape as ww_adc::AdcCapture: arm with a
configuration, poll the status, read the data out, abort.
#[ww_trait]
pub trait LogicAnalyzer {
fn capabilities() -> LaCapabilities<'i>;
fn capture(config: CaptureConfig<'i>) -> Result<CaptureActual, LaError>;
fn rearm() -> Result<CaptureActual, LaError>;
fn status() -> CaptureStatus;
fn read_raw(range: SampleRange) -> Result<RawChunk<'i>, LaError>;
fn read_transitions(channel: u8, from: u32) -> Result<TransitionsChunk<'i>, LaError>;
fn abort();
}
capabilities- channels, rates, memory, storage and read encodings, and which triggers work (TriggerCapabilities).capture- arms a capture, replacing the previous one and its data, and returns what was actually set. WithTrigger::Nowit starts at once; otherwise it samplespre_triggersamples continuously and keeps them, waits for the trigger, then takes the rest.rearm- arms again with the last configuration, for repeated captures of the same event without resending it.status-Idle,Armed,Triggered { t }, orDone { t_first, trigger_at, samples }once the data is ready — a host polls this untilDone.read_raw- samples of a sample range as raw words, at mostMAX_READ_LENbytes (Common(WrongState)before a capture is done,Common(InvalidArgument)outside it,Common(Unsupported)if the device only keeps transitions).read_transitions- edges of one channel from a sample index on, up toMAX_EDGESor a full reply;TransitionsChunk::scanned_tosays where the next call starts (same error set, plusLaError::NoSuchChannel).abort- stops an armed or running capture and drops its data.
Reads are in samples, not bytes, and split by encoding, since a byte offset means nothing for transitions (their
size depends on the signal). read_transitions is per channel: it is the cheapest shape for the device (one bit
of every raw word, one pass, a fixed index buffer) and lets a host fetch only the channels it shows; a host
fetching every channel sends one call per channel in one multi-call.
A capture's storage (raw words or transitions) is the device's choice (LaCapabilities::storage) and independent
of how it is read out: either storage can be read back in either encoding the device lists.
LogicStream¶
Continuous capture, streamed while the link keeps up:
#[ww_trait]
pub trait LogicStream {
#[stream(StreamChunk<'i>, counted)]
fn data(config: StreamConfig<'i>) -> Result<StreamActual, LaError>;
fn stream_capabilities() -> StreamCapabilities<'i>;
}
data- a counted stream function: the host opens it with channels, rate and encoding and gets back what was actually set (or anLaError), then receives raw chunks or per-channel transition chunks as they fill. Closing the stream stops sampling, opening it again restarts it with the new configuration.stream_capabilities- the highest raw rate per channel count the link carries, and the highest rate withTransitions(bounded by the edge rate fitting the link, not the sample rate).
The stream's unit is a chunk: the event head says exactly how many chunks the device dropped and where
(stream delivery), and each chunk's own sample range (RawChunk::first,
TransitionsChunk::from) tells which samples they covered. The head's t is the device time of a chunk's first
sample.
Encodings¶
Both traits, and ww_signal_generator's pattern sink, share two encodings so a capture can be replayed without
conversion:
Encoding::Raw, [RawChunk] - every sample, a word ofRawLayout::widthbits (1, 2, 4, 8, 16 or 32, the smallest the device gives for the channel count). Bitiof a word is thei-th channel of the configuration's channel list; sub-byte words are packed starting at bit 0 of a byte, multi-byte words are little-endian — what a DMA from a GPIO port produces with no CPU work.Encoding::Transitions, [TransitionsChunk] - per channel, its level at the start of the range and the sample indices where it toggles, as aSeries<u32>. A steady clock costs a few bytes per chunk, a quiet channel almost nothing (its start level and an empty list), so the edge rate limits a stream, not the sample rate: a busy signal sampled for clean edge timing costs the same at a high rate as at a low one.
Sample indices are u32, counted from a capture's first sample or from the start of a stream session (where they
wrap and a host unwraps them, as with any fixed-width timestamp in the family).
Triggers¶
pub enum Trigger<'i> {
Now,
Stages(MaxLen<RefVec<'i, TriggerStage<'i>>, MAX_STAGES>),
External { edge: Edge },
}
Trigger::Stages is met stage after stage, each one after the one before; the capture triggers when the last one
is. One TriggerStage::Pattern combines level, edge and pattern conditions on several channels at once
(ChannelCondition { channel, when: Condition }, where Condition is Low / High / Rising / Falling /
AnyEdge) — a level trigger is one condition High, an edge trigger one Rising. TriggerCapabilities says how
many stages and conditions a device supports; zero stages means only Now and maybe External work.
Three protocol stages are matched on the decoded stream next to the capture, decoding only as much as the match
needs, and combine with patterns and with each other like any other stage: TriggerStage::UartByte (a UART frame
matching a value under a mask, met at the stop bit), TriggerStage::I2cAddress (an address byte matching, of a
read/write/either, met at the ACK bit), TriggerStage::SpiFirstByte (the first byte after CS goes low, met at its
last bit). TriggerCapabilities lists which protocol stages a device supports.
CaptureConfig::trigger_out pulses the device's trigger output when the capture triggers, so another analyzer, a
scope, or a ww_signal_generator::PatternGenerator on another device can start on the same edge
(Start::External); two devices synced through ww_timebase::Clock::configure_sync line their captures up on the
shared time base for anything less exact than a sample.
Types¶
LaCapabilities<'i>-channels: MaxLen<RefVec<'i, ChannelInfo<'i>>, MAX_CHANNELS>,rates: MaxLen<RefVec<'i, RateLimit>, MAX_RATE_LIMITS>(highest rate per channel count),memory: u32,storage: Encoding,raw/transitions: bool(which encodings reads give),trigger: TriggerCapabilities.ChannelInfo<'i>-name,gpio_pin: Option<u8>(the same pin's index in the device'sww_gpio::Bank),threshold: Option<VoltRange>(if the input threshold is adjustable).RateLimit-channels: u8,max_rate: Hertz.TriggerCapabilities-max_stages: u8,max_conditions: u8,edges: bool,uart_byte/i2c_address/spi_first_byte: bool,external: bool,trigger_out: bool.CaptureConfig<'i>-channels: MaxLen<RefVec<'i, u8>, MAX_CHANNELS>,rate: Hertz,samples: u32(total,pre_triggerincluded),pre_trigger: u32,trigger: Trigger<'i>,threshold: Option<Volt>,trigger_out: bool.CaptureActual-rate: Hertz,samples: u32(the memory may hold fewer than asked at this channel count),layout: RawLayout.CaptureStatus-Idle,Armed,Triggered { t: Second },Done { t_first: Second, trigger_at: Option<u32>, samples: u32 };sized(unib32discriminant).SampleRange-first: u32,count: u32.RawLayout-width: u8.RawChunk<'i>-first: u32,samples: u32,data: MaxLen<RefVec<'i, u8>, MAX_READ_LEN>.TransitionsChunk<'i>-channel: u8,level: Level,from: u32,scanned_to: u32,edges: Series<'i, u32>(ascending, at mostMAX_EDGES, at most 610 bytes).StreamChunk<'i>-Raw(RawChunk<'i>)orTransitions(TransitionsChunk<'i>).StreamCapabilities<'i>-raw_rates: MaxLen<RefVec<'i, RateLimit>, MAX_RATE_LIMITS>,transitions_rate: Option<Hertz>.StreamConfig<'i>-channels,rate: Hertz,encoding: Encoding,flush_interval: Option<Second>,threshold: Option<Volt>.StreamActual-rate: Hertz,layout: RawLayout.Trigger<'i>,TriggerStage<'i>,ChannelCondition,Condition,Edge- see Triggers.LaError<'i>-Common(CommonError)(common errors) (Busy: a stream runs while a capture is asked for, or the other way round, on devices that share one sampler),NoSuchChannel; unsized (unib32discriminant), evolvable, shared by both traits.
All enums and structs are behind #[derive_shrink_wrap] with owned(feature = "std") and
cfg_attr_borrowed(feature = "defmt", derive(defmt::Format)), so owned (*Owned) variants and defmt::Format are
available by enabling the crate's std / defmt features.
Composing into a device API¶
ChannelInfo::gpio_pin cross-references the same pin's index in the device's own ww_gpio::Bank::pin, so a host
or a generic tool can show which logic-analyzer channel is which GPIO pin. A ww_signal_generator::PatternGenerator
on the same or another device replays a capture directly: it accepts the same StreamChunk encodings this crate
produces, so a host reads a capture out in whichever encoding the generator lists and feeds the chunks straight to
its pattern sink — see ww_signal_generator.