Files
eumirts5-dds/stub/02-izmerenie-achh.py

139 lines
5.0 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/usr/bin/env python3
"""
Хост-скрипт: автоматическое снятие АЧХ с DDS-генератора (RP2040).
ЧЕРНОВИК (AI-сгенерировано, 2026-10-26). Требует проверки преподавателем.
Управляет генератором 01-dds-generator-rp2040.ino по Serial, запускает
логарифмический свип, собирает точки "P <Гц> <мВ>" и строит АЧХ в дБ.
Использование:
python 02-izmerenie-achh.py --port /dev/ttyACM0 \
--f0 10 --f1 100000 --n 25 --dwell 150 --out achh.csv
Зависимости: pyserial (обязательно), matplotlib (опционально, для графика).
pip install pyserial matplotlib
ПРОТОКОЛ (строки, \n):
-> W S режим синуса
-> SWEEP <f0> <f1> <n> <dwell_ms>
<- P <Гц> <мВ> точка АЧХ
<- DONE свип завершён
ВНИМАНИЕ: вход ADC0 генератора — 0..3,3 В. Сигнал с выхода исследуемой
цепи выше 3,3 В подавать только через делитель/пиковый детектор.
Нормировка на выходе: gain_dB = 20*log10(Uout/Uin). Входное напряжение
Uin берётся из аргумента --uin (действующая или амплитудная — как задано,
скрипт работает с размахом, мВ).
"""
from __future__ import annotations
import argparse
import csv
import math
import sys
import time
try:
import serial # type: ignore
except ImportError: # pragma: no cover
print("Нужен pyserial: pip install pyserial", file=sys.stderr)
raise SystemExit(2)
def open_port(port: str, baud: int = 115200, timeout: float = 0.2):
return serial.Serial(port, baud, timeout=timeout)
def send(ser, line: str) -> None:
ser.write((line + "\n").encode("ascii"))
ser.flush()
def collect_sweep(ser, f0: float, f1: float, n: int, dwell: int,
uin_mv: float, timeout_s: float = 120.0):
"""Запустить свип и вернуть список (f, uout_mv, gain_db)."""
send(ser, "W S") # синус
time.sleep(0.2)
send(ser, f"SWEEP {f0:.3f} {f1:.3f} {n} {dwell}")
points: list[tuple[float, float, float]] = []
t0 = time.time()
while True:
if time.time() - t0 > timeout_s:
print("таймаут свипа", file=sys.stderr)
break
raw = ser.readline().decode("ascii", "replace").strip()
if not raw:
continue
if raw == "DONE":
break
if raw.startswith("P "):
_, fs, mv = raw.split()
f = float(fs)
uout = float(mv)
gain = 20.0 * math.log10(max(uout, 1e-6) / max(uin_mv, 1e-6))
points.append((f, uout, gain))
print(f" {f:>9.1f} Гц {uout:>7.1f} мВ {gain:+6.2f} дБ")
return points
def write_csv(path: str, points) -> None:
with open(path, "w", newline="", encoding="utf-8") as fh:
w = csv.writer(fh)
w.writerow(["freq_hz", "uout_mv", "gain_db"])
for row in points:
w.writerow([f"{row[0]:.3f}", f"{row[1]:.3f}", f"{row[2]:.4f}"])
print(f"CSV: {path}")
def plot(points) -> None:
try:
import matplotlib.pyplot as plt # type: ignore
except ImportError:
print("matplotlib нет — график пропущен (CSV сохранён)", file=sys.stderr)
return
xs = [p[0] for p in points]
ys = [p[2] for p in points]
plt.semilogx(xs, ys, "o-")
plt.xlabel("Частота, Гц")
plt.ylabel("Усиление, дБ")
plt.grid(True, which="both")
plt.title("АЧХ (автоснятие с RP2040 DDS)")
plt.tight_layout()
plt.show()
def main() -> int:
ap = argparse.ArgumentParser(description="Автоснятие АЧХ через RP2040 DDS")
ap.add_argument("--port", required=True, help="например /dev/ttyACM0 или COM5")
ap.add_argument("--f0", type=float, default=10.0)
ap.add_argument("--f1", type=float, default=100000.0)
ap.add_argument("--n", type=int, default=25)
ap.add_argument("--dwell", type=int, default=150, help="мс на точку")
ap.add_argument("--uin", type=float, default=500.0,
help="входное напряжение (размах), мВ")
ap.add_argument("--out", default="achh.csv")
ap.add_argument("--plot", action="store_true")
args = ap.parse_args()
ser = open_port(args.port)
time.sleep(0.5)
ser.reset_input_buffer()
try:
points = collect_sweep(ser, args.f0, args.f1, args.n, args.dwell, args.uin)
finally:
ser.close()
if not points:
print("точек не получено", file=sys.stderr)
return 1
write_csv(args.out, points)
if args.plot:
plot(points)
return 0
if __name__ == "__main__":
raise SystemExit(main())