66 lines
3.7 KiB
Python
66 lines
3.7 KiB
Python
# Рецепты и таблицы периодических сигналов для генератора. Интерполяция и квантование
|
|
# передаются общему ядру; экспорт таблицы сохраняет подготовленный порядок отсчётов для
|
|
# циклического воспроизведения.
|
|
|
|
"""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")
|