"""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 \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")