Add shared waveform processing, generation and firmware image support
This commit is contained in:
61
python/set_devices/waveform.py
Normal file
61
python/set_devices/waveform.py
Normal file
@@ -0,0 +1,61 @@
|
||||
"""Reusable recipes and tables; interpolation/quantization live in C."""
|
||||
from __future__ import annotations
|
||||
import math
|
||||
import json
|
||||
from dataclasses import dataclass
|
||||
from .signal_reconstruction import reconstruct, dac12
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class Waveform:
|
||||
points: tuple
|
||||
codes: tuple
|
||||
sample_rate: int
|
||||
vref: float
|
||||
period_ms: float
|
||||
|
||||
def generate(points, sample_rate=1000, vref=3.3, method="pchip", degree=2):
|
||||
"""One cyclic period, last endpoint excluded (no duplicated seam sample)."""
|
||||
if type(sample_rate) is not int or not 1 <= sample_rate <= 1000000:
|
||||
raise ValueError("Частота отсчётов должна быть 1…1000000 Гц")
|
||||
if not math.isfinite(vref) or not 0 < vref <= 3.6:
|
||||
raise ValueError("Vref должен быть в диапазоне 0…3,6 В")
|
||||
if not 2 <= len(points) <= 4096 or points[0][0] != 0:
|
||||
raise ValueError("Нужно 2…4096 точек; первая временная метка — 0 мс")
|
||||
if any(not math.isfinite(x) or not math.isfinite(y) or not 0 <= y <= vref for x, y in points):
|
||||
raise ValueError("В точках нужны конечные числа, напряжение 0…Vref")
|
||||
if any(b[0] <= a[0] for a, b in zip(points, points[1:])):
|
||||
raise ValueError("Время точек должно строго возрастать")
|
||||
period = points[-1][0]
|
||||
exact_count = period * sample_rate / 1000
|
||||
count = round(exact_count)
|
||||
if not 2 <= count <= 4096:
|
||||
raise ValueError("Период × частота должны давать от 2 до 4096 отсчётов")
|
||||
if abs(exact_count - count) > 1e-7:
|
||||
raise ValueError("Период должен содержать целое число отсчётов при выбранной частоте")
|
||||
result = reconstruct(points, method, count, degree, endpoint=False)
|
||||
# Check the endpoint too: polynomial fitting can move its value.
|
||||
checked = reconstruct(points, method, count + 1, degree)
|
||||
dac12([p[1] for p in checked.points], vref)
|
||||
codes = dac12([p[1] for p in result.points], vref)
|
||||
return Waveform(tuple(result.points), codes, sample_rate, vref, period)
|
||||
|
||||
def recipe(points, sample_rate, vref, method, degree):
|
||||
return {"version": 1, "points_ms_volts": [list(pair) for pair in points], "sample_rate": sample_rate,
|
||||
"vref": vref, "method": method, "degree": degree}
|
||||
|
||||
def load_recipe(text):
|
||||
data = json.loads(text)
|
||||
if not isinstance(data, dict) or data.get("version") != 1:
|
||||
raise ValueError("Неизвестная версия задания генератора")
|
||||
points = tuple((float(x), float(y)) for x, y in data["points_ms_volts"])
|
||||
generate(points, data["sample_rate"], data["vref"], data["method"], data["degree"])
|
||||
return points, data
|
||||
|
||||
def c_header(wave):
|
||||
values = [", ".join(str(v) for v in wave.codes[i:i+16]) for i in range(0, len(wave.codes), 16)]
|
||||
return ("/* Generated cyclic 12-bit DAC table. DMA-accessible SRAM, not CCM. */\n"
|
||||
"#pragma once\n#include <stdint.h>\n"
|
||||
f"#define WAVE_SAMPLE_RATE_HZ {wave.sample_rate}u\n"
|
||||
f"#define WAVE_SAMPLE_COUNT {len(wave.codes)}u\n"
|
||||
f"/* Vref = {wave.vref:g} V; period = {wave.period_ms:g} ms. */\n"
|
||||
"static uint16_t wave_samples[WAVE_SAMPLE_COUNT] = {\n " + ",\n ".join(values) + "\n};\n")
|
||||
Reference in New Issue
Block a user