Skip to content

Logic Analyzer

lbuild module: modm:driver:logic_analyzer

Samples one to four GPIO ports at a fixed rate into static RAM buffers, which a debugger can read out over SWD. A timer compare event triggers one DMA channel per port, which copies the input data register into its buffer, so the CPU does not have to take part in the capture.

The number of ports is set at build time with the channels option (1 to 4, default 2). Each channel samples one complete GPIO port, so four ports capture 64 pins. This is useful for buses such as SPI with a separate chip-select port:

#include <modm/driver/logic_analyzer.hpp>

// Sample PA8, PB8, PC8 and PD8 at 1 MHz until the buffers are full.
// Requires `modm:driver:logic_analyzer:channels = 4` in the project.
const uint32_t rate =
    modm::LogicAnalyzer::start<Board::SystemClock, 1'000'000, GpioA8, GpioB8, GpioC8, GpioD8>();
// ... do something that is worth looking at ...
modm::LogicAnalyzer::stop();

for (uint8_t channel = 0; channel < modm::LogicAnalyzer::ports(); channel++)
{
    for (size_t ii = 0; ii < modm::LogicAnalyzer::count(channel); ii++)
    {
        const uint16_t idr = modm::LogicAnalyzer::sample(channel, ii);
        (void) idr;
    }
}

The sample rate is a template argument, so the timer division and the unique port map are computed by the compiler. For multiple rates, switch over the requested value and call the corresponding instantiation; this is smaller than computing the timer division at runtime.

The host tool reads the descriptor and all buffers from the target, writes a VCD file for PulseView, GWire or GTKWave, and can decode I2C:

PYTHONPATH=modm python3 -m modm_tools.logic_analyzer --elf build/scons-release/project.elf \
    --config modm/openocd.cfg --channels A8,B8,C8,D8 --vcd capture.vcd --i2c A8,B8

The tool is copied into modm/modm_tools when this driver module is selected. In a modm checkout use PYTHONPATH=tools instead; --channels all (or simply omitting --channels with --vcd) exports every pin of the captured ports.

Sampling

Only the lower 16 bits of the input data registers (GPIOx_IDR) contain the port pins, so the DMA reads and stores 16 bit values. Unlike the modm:driver:gpio_sampler, no interrupts are used, which makes the capture timing exact and independent of the CPU load. Because whole ports are sampled, multiple pins with the same number on different ports can be captured at the same time.

All ports are sampled on the same timer event: compare channels 1 to N of the timer are programmed with the same compare value, so all compare events are generated on the same timer tick. The N DMA requests are then serviced back-to-back by the DMA arbiter. The samples are therefore separated only by a few bus cycles, not by half a timer period as in earlier versions of this driver.

The capture window is Samples / rate. In circular mode the DMA overwrites the buffers, stop() records which part is valid, and the buffers contain the last samples before the stop. sample() accounts for the buffer head; data() and head() give access to the raw buffer.

Sample Rate and Capture Time

Every sample costs one DMA transfer per port. All transfers of one timer event must finish before the next event, otherwise the DMA loses requests. A single DMA transfer from a GPIO register into SRAM takes about 5 to 6 bus cycles on the channel DMA (arbitration, peripheral read, SRAM write, see AN2548) and longer on the stream DMA, which reaches the GPIO through the bus matrix. The maximum rate below assumes half of the DMA bandwidth to leave room for the CPU and other bus masters:

max rate ≈ bus clock / (2 * cycles per transfer * ports)

The capture time is samples / rate and therefore limited by the buffer RAM. At the maximum rate it does not depend on the number of ports: four ports have a quarter of the samples each, but at a quarter of the rate. Each buffer holds at most 65535 samples (the DMA transfer counter is 16 bits), or 32767 samples on the GPDMA, which counts bytes instead of samples, so large devices can capture longer with more ports.

Family DMA Bus clock Max rate, 1 port Max rate, 4 ports Free RAM for buffers Capture time at max rate Tested in hardware
F0 channel 48 MHz ~5 MHz ~1.2 MHz 2–24 kB 0.2–2.5 ms F091RC, 4 ports at 500 kHz
F1 channel 72 MHz ~6 MHz ~1.5 MHz 10–64 kB 0.8–5 ms F103RB, 4 ports at 500 kHz
F2 stream 120 MHz ~7 MHz ~1.8 MHz 48–96 kB 3–7 ms F207ZG, 4 ports at 500 kHz
F3 channel 72 MHz ~7 MHz ~1.8 MHz 6–48 kB 0.4–3.5 ms F303VC, 4 ports at 500 kHz
F4 stream 84–180 MHz ~5–11 MHz ~1.3–2.8 MHz 48–192 kB 2–19 ms F429ZI, 4 ports at 500 kHz
F7 stream 216 MHz ~13 MHz ~3.4 MHz 192–384 kB 5–15 ms F722ZE, 4 ports at 500 kHz
G4 channel 170 MHz ~17 MHz ~4 MHz 24–96 kB 0.7–3 ms G474RE, 4 ports at 500 kHz
H5 GPDMA 250 MHz ~15 MHz ~3.9 MHz 16–320 kB 1–8 ms H563ZI, 4 ports at 500 kHz
H7 stream 200–275 MHz ~7–9 MHz ~1.7–2.3 MHz 256–512 kB 7–40 ms H743ZI, 4 ports at 500 kHz
L1 channel 32 MHz ~2.7 MHz ~0.7 MHz 8–64 kB 1.5–12 ms L152RE, 4 ports at 500 kHz
L4 channel 80–120 MHz ~8–12 MHz ~2–3 MHz 32–256 kB 1.4–16 ms L476RG, 4 ports at 500 kHz
L5 channel 110 MHz ~11 MHz ~2.8 MHz 128–192 kB 6–9 ms L552ZE-Q, 4 ports at 500 kHz
U0 channel 56 MHz ~5 MHz ~1.2 MHz 6–20 kB 0.6–2 ms U083RC, 4 ports at 500 kHz
U3 GPDMA 96 MHz ~6 MHz ~1.5 MHz 40–320 kB 3–22 ms U385RG-Q, 4 ports at 500 kHz
U5 GPDMA 160 MHz ~10 MHz ~2.5 MHz 96–1500 kB 3–13 ms U575ZI-Q, 4 ports at 500 kHz

The bus clock is the AHB clock of the usual modm SystemClock configurations. The free RAM assumes that about half of the internal SRAM that the DMA can access is left for the buffers. The cycle counts for the stream DMA (about 8 on F2/F4/F7, about 15 on H7, where the DMA in the D2 domain reads the GPIO in the D3 domain) and for the GPDMA (about 8) are estimates and not measured. The maximum rates are therefore approximate; check that isRunning() reports no transfer error and that the samples look plausible when getting close to them.

At lower rates the capture time grows proportionally: 16384 samples at 1 MHz cover 16 ms, at 100 kHz 164 ms.

Buffer Placement

The bus matrix arbitrates between the CPU and the DMA with round-robin, so on a shared memory the CPU keeps at least half of the bandwidth, but every CPU access can delay a DMA transfer. The buffers are therefore placed into the .noinit_sram1 section (.noinit_d2_sram1 on STM32H7, .noinit_sram1_ns on STM32H5/U3/U5). modm places this section into the continuous RAM region after the static data, so large buffers extend from SRAM1 into the following SRAM2 and SRAM3, if they are contiguous. These SRAMs are separate bus matrix slaves and usually only used by the heap, so only the part of the buffers in SRAM1 competes with the stack and static data of the CPU. On STM32H7 the CPU uses DTCM and AXI SRAM for its data, so the D2 SRAMs are not shared at all. The buffers reduce the heap by their size and are not zeroed at startup. Targets without SRAM1 (F0, F3, L1 and others with a single SRAM) place the buffers into .bss.

The GPIO read is still shared with the CPU, since both use the same AHB bus to the GPIO.

Timer and DMA Wiring

The timer and its N DMA channels are derived from the device data at build time. Every STM32 family with a DMA controller supported by modm:platform:dma whose GPIO registers are reachable from the DMA is supported:

DMA controller Families
stm32-stream-channel F2, F4, F7
stm32-channel F0 (most devices), F1, F3, L1
stm32-channel-request F0 (F09x, F030xC, F098), L4
stm32-mux G4, L5, U0
stm32-mux-stream H7 (except H7RS)
stm32-dma3 (GPDMA) H5, U3, U5

STM32C0, STM32G0 and STM32L0 place the GPIO registers on the separate IOPORT bus at 0x5000_0000, which the DMA controller cannot access. A DMA read from GPIOx_IDR raises a transfer error on these families, so the analyzer is not available there either. STM32U0 uses the same address, but its reference manual (RM0503) lists the GPIO ports on the AHB instead of the IOPORT, and its DMA can read them.

STM32H7RS has a GPDMA and an HPDMA, but it is not supported yet, since it is untested whether they can read the GPIO registers.

The analyzer picks a timer that has DMA requests for N compare channels and whose request lines do not overlap. On targets with DMAMUX it uses the N highest channels of the first DMA controller, on targets with GPDMA the N highest channels of GPDMA1; on all other targets the fixed timer request mapping determines the DMA channels. The chosen timer can be overridden with the timer option, for example to move the analyzer out of the way of another peripheral.

On STM32F2/F4/F7 the DMA1 peripheral port is not connected to the AHB bus matrix and cannot read the GPIO input registers, only DMA2 can. The analyzer therefore selects a timer whose compare channels are routed to DMA2 on these families (usually TIM1 or TIM8). Some timer compare channels share a DMA request line with other compare channels of the same timer; using both would lose requests, so such combinations are not selected either.

@warning The timer used by the analyzer must not be used by modm:platform:timer at the same time. @note The DMA channels are not allocated dynamically. Check that they do not collide with other DMA users of your application. @note The buffers must be placed in memory that the DMA controller can access. On STM32H7, DTCM is not DMA capable; on other families, CCM may not be accessible by every DMA controller. The analyzer never places its buffers there. If the DMA detects a transfer error, isRunning() returns false and the capture is incomplete. @note If the data cache is enabled, invalidate the buffers before reading them after the capture. @note On STM32H7 all DMA channels are allocated on DMA1; on all other supported families the fixed request mapping of the timer determines which DMA channels are used. @note At most four ports can be sampled with one timer. Sampling more than four ports would require multiple synchronized timers and is not implemented yet.

Options

  • channels: maximum number of GPIO ports to sample, from 1 to 4. The default is 2. Each channel captures one complete port (16 pins); unused channels cost no RAM and no DMA channels.
  • samples: number of samples per port, at most 65535 (32767 on the GPDMA). Each sample uses two bytes per configured channel, so channels ports need 2 * channels * samples bytes of RAM. The default is 16384 or a value that uses about a quarter of the internal SRAM for the maximum number of channels the target supports, whichever is smaller.
  • timer: timer instance to use, or auto (the default) to derive it from the device data.

This module is only available for stm32{f0,f1,f2,f3,f4,f7,g4,h5,h7,l1,l4,l5,u0,u3,u5}.

Options

channels

Maximum number of GPIO ports to sample (1 to 4)

Default: 2
Inputs: [1 .. 2 .. 4]

samples

Number of samples per port

Default: 1024 stm32{f0,f1,f3,f4,g4,h5,l1}
Default: 10240 stm32{f4,l4}
Default: 10752 stm32f7
Default: 12288 stm32f4
Default: 1280 stm32{l4,u0}
Default: 1365 stm32l1
Default: 1536 stm32{f1,f3,l1}
Default: 16384 stm32{f7,h5,h7,l4,u3,u5}
Default: 2048 stm32{f1,f2,f3,f4,l4}
Default: 256 stm32{f0,f1,l1}
Default: 2560 stm32{f3,l1,u3}
Default: 3072 stm32{f2,f4}
Default: 341 stm32{f1,l1}
Default: 3584 stm32g4
Default: 384 stm32u0
Default: 4096 stm32{f2,f4,g4,l4}
Default: 426 stm32{f1,l1}
Default: 512 stm32{f0,f3}
Default: 5120 stm32l4
Default: 6144 stm32{f4,u3}
Default: 640 stm32f1
Default: 682 stm32l1
Default: 768 stm32f100zct6
Default: 8192 stm32{f4,l5,u3}
Default: 8704 stm32{f7,h5,u5}
Default: 9728 stm32h5
Inputs: [16 .. 1024 .. 65535] stm32{f0,f1,f3,f4,g4,h5,l1}
Inputs: [16 .. 10240 .. 65535] stm32{f4,l4}
Inputs: [16 .. 10752 .. 65535] stm32f7
Inputs: [16 .. 12288 .. 65535] stm32f4
Inputs: [16 .. 1280 .. 65535] stm32{l4,u0}
Inputs: [16 .. 1365 .. 65535] stm32l1
Inputs: [16 .. 1536 .. 65535] stm32{f1,f3,l1}
Inputs: [16 .. 16384 .. 65535] stm32{f7,h5,h7,l4,u3,u5}
Inputs: [16 .. 2048 .. 65535] stm32{f1,f2,f3,f4,l4}
Inputs: [16 .. 256 .. 65535] stm32{f0,f1,l1}
Inputs: [16 .. 2560 .. 65535] stm32{f3,l1,u3}
Inputs: [16 .. 3072 .. 65535] stm32{f2,f4}
Inputs: [16 .. 341 .. 65535] stm32{f1,l1}
Inputs: [16 .. 3584 .. 65535] stm32g4
Inputs: [16 .. 384 .. 65535] stm32u0
Inputs: [16 .. 4096 .. 65535] stm32{f2,f4,g4,l4}
Inputs: [16 .. 426 .. 65535] stm32{f1,l1}
Inputs: [16 .. 512 .. 65535] stm32{f0,f3}
Inputs: [16 .. 5120 .. 65535] stm32l4
Inputs: [16 .. 6144 .. 65535] stm32{f4,u3}
Inputs: [16 .. 640 .. 65535] stm32f1
Inputs: [16 .. 682 .. 65535] stm32l1
Inputs: [16 .. 768 .. 65535] stm32f100zct6
Inputs: [16 .. 8192 .. 65535] stm32{f4,l5,u3}
Inputs: [16 .. 8704 .. 65535] stm32{f7,h5,u5}
Inputs: [16 .. 9728 .. 65535] stm32h5

timer

Timer instance used for sampling, or auto

Default: auto
Inputs: [auto, 1] stm32f4
Inputs: [auto, 1, 2, 15, 16] stm32l4
Inputs: [auto, 1, 2, 15, 16, 17] stm32f3
Inputs: [auto, 1, 2, 3] stm32{f1,h5}
Inputs: [auto, 1, 2, 3, 15, 16] stm32{l4,u0}
Inputs: [auto, 1, 2, 3, 15, 16, 17] stm32{f0,f1,f3}
Inputs: [auto, 1, 2, 3, 16, 17] stm32f0
Inputs: [auto, 1, 2, 3, 4] stm32f1
Inputs: [auto, 1, 2, 3, 4, 15, 16, 17] stm32{f1,f3,u3}
Inputs: [auto, 1, 2, 3, 4, 5] stm32f1
Inputs: [auto, 1, 2, 3, 4, 5, 15, 16, 17] stm32f100zct6
Inputs: [auto, 1, 2, 3, 4, 5, 8] stm32f103zch6
Inputs: [auto, 1, 2, 3, 4, 5, 8, 15] stm32h5
Inputs: [auto, 1, 2, 3, 4, 5, 8, 15, 16, 17] stm32{h5,h7,l4,l5,u5}
Inputs: [auto, 1, 2, 3, 4, 5, 8, 15, 16, 17, 20] stm32g4
Inputs: [auto, 1, 2, 3, 4, 5, 8, 15, 16, 17, 23, 24] stm32h7
Inputs: [auto, 1, 2, 3, 4, 8, 15, 16, 17] stm32{f3,g4,u3}
Inputs: [auto, 1, 2, 3, 4, 8, 15, 16, 17, 20] stm32{f3,g4}
Inputs: [auto, 1, 3, 15, 16, 17] stm32f0
Inputs: [auto, 1, 3, 16, 17] stm32f0
Inputs: [auto, 1, 8] stm32{f2,f4,f7}
Inputs: [auto, 2, 3] stm32f1
Inputs: [auto, 2, 3, 4] stm32{f1,l1}
Inputs: [auto, 2, 3, 4, 5] stm32{f1,l1}
Inputs: [auto, 2, 3, 4, 5, 15, 16, 17, 19] stm32f3

Dependencies

modm:driver:logic_analyzer modm_driver_logic_analyzer modm: driver: logic_analyzer modm_architecture_register modm: architecture: register modm_driver_logic_analyzer->modm_architecture_register modm_cmsis_device modm: cmsis: device modm_driver_logic_analyzer->modm_cmsis_device modm_platform_dma modm: platform: dma modm_driver_logic_analyzer->modm_platform_dma modm_platform_gpio modm: platform: gpio modm_driver_logic_analyzer->modm_platform_gpio modm_platform_rcc modm: platform: rcc modm_driver_logic_analyzer->modm_platform_rcc

Limited availability: Check with 'lbuild discover' if this module is available for your target!