Files
templates/python/set_devices/waveform.py

62 lines
3.3 KiB
Python
Raw 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.
"""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")