3.3V input pin voltage with USB power?

rjsdotorg

Well-known member
I looped the 3.3 V pin to A0, the board is powered by USB/laptop.
I have a new streaming .cpp code which streams one channel of data at 150k back to the laptop running Python serial capture.
It appears to vary from ~3.22 to 3.26V - does that seem typical?

[teensy] dmaCount: 3910
[teensy] maxBytesUsed: 14848
[teensy] elapsed: 6.834 s
[teensy] avg sample rate: 146.5 ksamp/sec
[teensy]
[teensy] Enter to stream again
Captured 1,000,192 samples (1953.5 kB)
min=3901 max=4061 mean=4019.1 std=14.7
 

Attachments

  • Figure_1.png
    Figure_1.png
    41.9 KB · Views: 10
BTW, the .cpp is attached, and serial cap code is below (can't attach...).

Output is 1339.4 ksamp/sec streaming uint32 over USB 2
PS C:\Users\rjs\Documents\PlatformIO\Projects\ADC\tools> python serial_capture.py COM3 --max-samples 1000000 --out adc.n
py
[boot] ADC DMA USB Streamer v0.2.0
[boot] Enter to begin streaming
[preamble] Streaming binary uint16_t samples over USB Serial...
[preamble] Press 'q' to stop.
Capturing from COM3 (Ctrl-C to stop) …
983,040 samples collected …
Reached 1,000,000 sample limit.

Stopped by user.
[teensy] dmaCount: 3923
[teensy] maxBytesUsed: 512
[teensy] elapsed: 0.750 s
[teensy] avg sample rate: 1339.4 ksamp/sec
[teensy]
[teensy] Enter to stream again
Captured 1,000,192 samples (1953.5 kB)
min=392 max=449 mean=421.8 std=3.9
Saved → adc.npy

Array shape: (1000192,), dtype: uint16


Python:
"""
serial_capture.py  –  Capture Teensy ADC DMA USB streamer data into a numpy array.

The Teensy firmware streams raw binary uint16_t samples in 512-byte blocks.
This script:
  1. Opens the port and sends Enter to start streaming.
  2. Discards the text preamble lines sent before binary data begins.
  3. Reads binary blocks into a growing numpy uint16 array.
  4. Stops on Ctrl-C and reports basic stats.

Usage:
    python serial_capture.py [PORT] [--max-samples N] [--out FILE.npy]

Requires:  pip install pyserial numpy
"""

import argparse
import sys
import time

import matplotlib.pyplot as plt
import numpy as np
import serial


BLOCK_BYTES    = 512                        # must match BUF_BLOCK_SIZE in firmware
READ_CHUNK     = 16 * BLOCK_BYTES           # larger host read to reduce Python/USB overhead
SAMPLES_BLOCK  = BLOCK_BYTES // 2           # uint16_t per block = 256
PREAMBLE_LINES = 2                          # "Streaming…" + "Press 'q'…"


def flush_preamble(ser: "serial.Serial") -> None:
    """Read and discard the text lines the firmware sends before binary data."""
    for _ in range(PREAMBLE_LINES):
        line = ser.readline()
        print("[preamble]", line.decode(errors="replace").rstrip())


def capture(port: str, max_samples: "int | None", out_path: "str | None") -> np.ndarray:
    chunks: list[np.ndarray] = []  # dtype: uint16
    total = 0

    with serial.Serial(port, baudrate=115200, timeout=10.0) as ser:
        # Disable DTR/RTS to avoid spurious resets.
        ser.dtr = False
        ser.rts = False

        # Drain anything already in the OS buffer.
        ser.reset_input_buffer()

        # Wait for the Teensy's "Enter to" prompt before triggering, so we
        # don't send Enter before the Teensy is ready (e.g. after a reset).
        ser.timeout = 5.0
        while True:
            line = ser.readline().decode(errors="replace").strip()
            if not line:
                break  # timed out
            print("[boot]", line)
            if "Enter to" in line:
                break

        # Send Enter to trigger the Teensy streaming start.
        # Do not delay here: at >1 Msps, 100 ms can overflow the MCU ring buffer
        # before the host starts draining binary data.
        ser.write(b"\r\n")
        ser.flush()
        ser.timeout = 10.0

        flush_preamble(ser)

        print(f"Capturing from {port}  (Ctrl-C to stop) …")

        partial = bytearray()  # buffer for partial blocks
        timeout_count = 0

        try:
            while True:
                raw = ser.read(READ_CHUNK)
                if len(raw) == 0:
                    timeout_count += 1
                    if timeout_count >= 3:
                        print(f"\nNo data for {timeout_count} read attempts – stopping.")
                        break
                    time.sleep(0.01)
                    continue

                timeout_count = 0  # reset on any data
                partial.extend(raw)

                # Extract complete blocks from partial buffer.
                while len(partial) >= BLOCK_BYTES:
                    block = np.frombuffer(bytes(partial[:BLOCK_BYTES]), dtype=np.uint16)
                    chunks.append(block)
                    total += len(block)
                    del partial[:BLOCK_BYTES]

                    # Progress tick every 256 blocks.
                    if len(chunks) % 256 == 0:
                        print(f"  {total:,} samples collected …", end="\r", flush=True)

                    if max_samples and total >= max_samples:
                        print(f"\nReached {max_samples:,} sample limit.")
                        raise KeyboardInterrupt  # exit cleanly

        except KeyboardInterrupt:
            print(f"\nStopped by user.")
        finally:
            # Send 'q' to tell the Teensy to stop streaming.
            ser.write(b"q")
            ser.flush()
            # Discard any trailing binary data until the stats sentinel.
            ser.timeout = 2.0
            while True:
                line = ser.readline()
                if not line:
                    break
                decoded = line.decode(errors="replace").rstrip()
                if decoded == "---STATS---":
                    break  # found sentinel, stats follow
            while True:
                line = ser.readline()
                if not line:
                    break
                print("[teensy]", line.decode(errors="replace").rstrip())

    if not chunks:
        print("No data received.")
        return np.empty(0, dtype=np.uint16)

    samples = np.concatenate(chunks)
    print(f"Captured {len(samples):,} samples  "
          f"({len(samples) * 2 / 1024:.1f} kB)")
    print(f"  min={samples.min()}  max={samples.max()}  "
          f"mean={samples.mean():.1f}  std={samples.std():.1f}")

    if out_path:
        np.save(out_path, samples)
        print(f"Saved → {out_path}")

    return samples


def main() -> None:
    parser = argparse.ArgumentParser(description="Capture Teensy ADC stream to numpy array.")
    parser.add_argument("port",             nargs="?", default="COM3",
                        help="Serial port (default: COM3)")
    parser.add_argument("--max-samples",    type=int,  default=None,
                        metavar="N",        help="Stop after N samples")
    parser.add_argument("--out",            type=str,  default=None,
                        metavar="FILE.npy", help="Save array to .npy file")
    args = parser.parse_args()

    samples = capture(args.port, args.max_samples, args.out)

    if len(samples):
        print(f"\nArray shape: {samples.shape}, dtype: {samples.dtype}")
        # Values are raw 12-bit ADC counts in range [0, 4095].

        fig, ax = plt.subplots(figsize=(12, 4))
        ax.plot(samples, linewidth=0.5)
        ax.set_xlabel("Sample index")
        ax.set_ylabel("ADC count (uint16)")
        ax.set_title(f"ADC capture  –  {len(samples):,} samples")
        ax.grid(True, alpha=0.3)
        plt.tight_layout()
        plt.show()


if __name__ == "__main__":
    main()
 

Attachments

  • main.cpp
    5.3 KB · Views: 7
As one might expect, the STD rises with rate (12 bit counts):

Captured 1,000,192 samples (1953.5 kB)

[teensy] avg sample rate: 167.4 ksamp/sec
min=412 max=429 mean=421.1 std=1.7

[teensy] avg sample rate: 334.8 ksamp/sec
min=410 max=431 mean=421.7 std=2.1

[teensy] avg sample rate: 1339.3 ksamp/sec
min=389 max=448 mean=421.5 std=3.4
 
I'm curious what the measurements would look like if you sampled the 3.3V with a different Teensy. (Sharing grounds, of course.)
 
I'm curious what the measurements would look like if you sampled the 3.3V with a different Teensy. (Sharing grounds, of course.)
I think I have another to try. I don't have a scope but do have a nice AS-1250 ADC to check the pin V separately. The STD is small.
A 3V battery would be a good test too.
Part of the idea here is to turn any Teensy into a 1.3 MHz triggered O-scope, and I'll put a Zener and resistor on A0 to test simple front-end protection. Maybe a cap to limit aliasing, we'll see.
 
Last edited:
Back
Top