pulse modulation added
This commit is contained in:
@@ -172,6 +172,8 @@ AD9102_WAVE_SAMPLE_MIN = -8192
|
||||
AD9102_WAVE_SAMPLE_MAX = 8191
|
||||
AD9102_WAVE_MAX_CHUNK_SAMPLES = 12
|
||||
AD9102_CLOCK_HZ = 150_000_000
|
||||
# Longest custom-pattern repetition period: 65535 ticks x base 15 at 150 MHz.
|
||||
AD9102_WAVE_PERIOD_US_MAX = 6553
|
||||
|
||||
AD9833_FREQ_WORD_MIN = 0
|
||||
AD9833_FREQ_WORD_MAX = 0x0FFFFFFF
|
||||
|
||||
@@ -22,6 +22,7 @@ from .constants import (
|
||||
AD9102_SRAM_SAMPLE_MAX,
|
||||
AD9102_SRAM_SAMPLE_MIN,
|
||||
AD9102_WAVE_MAX_CHUNK_SAMPLES,
|
||||
AD9102_WAVE_PERIOD_US_MAX,
|
||||
AD9102_WAVE_SAMPLE_MAX,
|
||||
AD9102_WAVE_SAMPLE_MIN,
|
||||
AD9833_FREQ_WORD_MAX,
|
||||
@@ -533,8 +534,12 @@ class LaserController:
|
||||
len(waveform_bytes),
|
||||
)
|
||||
|
||||
def upload_ad9102_waveform(self, samples: Sequence[int]) -> None:
|
||||
"""Upload and commit a custom AD9102 waveform from signed 14-bit samples."""
|
||||
def upload_ad9102_waveform(self, samples: Sequence[int], pat_period_us: int = 0) -> None:
|
||||
"""Upload and commit a custom AD9102 waveform from signed 14-bit samples.
|
||||
|
||||
``pat_period_us`` sets the pattern repetition period in microseconds;
|
||||
0 keeps the legacy back-to-back playback.
|
||||
"""
|
||||
if not samples:
|
||||
raise InvalidParameterError("samples", "At least two samples are required")
|
||||
sample_list = [self._validate_wave_sample(sample, index) for index, sample in enumerate(samples)]
|
||||
@@ -544,13 +549,24 @@ class LaserController:
|
||||
"samples",
|
||||
f"Sample count must be in range [{AD9102_SRAM_SAMPLE_MIN}, {AD9102_SRAM_SAMPLE_MAX}]",
|
||||
)
|
||||
if not 0 <= int(pat_period_us) <= AD9102_WAVE_PERIOD_US_MAX:
|
||||
raise InvalidParameterError(
|
||||
"pat_period_us",
|
||||
f"Repetition period must be in range [0, {AD9102_WAVE_PERIOD_US_MAX}] us",
|
||||
)
|
||||
|
||||
self._send_and_expect_ok(Protocol.encode_ad9102_wave_begin(sample_count))
|
||||
self._send_and_expect_ok(
|
||||
Protocol.encode_ad9102_wave_begin(sample_count, int(pat_period_us))
|
||||
)
|
||||
for start in range(0, sample_count, AD9102_WAVE_MAX_CHUNK_SAMPLES):
|
||||
chunk = sample_list[start:start + AD9102_WAVE_MAX_CHUNK_SAMPLES]
|
||||
self._send_and_expect_ok(Protocol.encode_ad9102_wave_data(chunk))
|
||||
self._send_and_expect_ok(Protocol.encode_ad9102_wave_commit())
|
||||
logger.info("Uploaded AD9102 waveform with %d samples", sample_count)
|
||||
logger.info(
|
||||
"Uploaded AD9102 waveform with %d samples, period %d us",
|
||||
sample_count,
|
||||
int(pat_period_us),
|
||||
)
|
||||
|
||||
def cancel_ad9102_waveform_upload(self) -> None:
|
||||
"""Cancel an in-progress AD9102 custom waveform upload."""
|
||||
|
||||
@@ -28,6 +28,7 @@ from laser_control.constants import (
|
||||
AD9102_PAT_PERIOD_MIN,
|
||||
AD9102_SAW_STEP_MAX,
|
||||
AD9102_SAW_STEP_MIN,
|
||||
AD9102_WAVE_PERIOD_US_MAX,
|
||||
AD9102_SRAM_AMPLITUDE_MAX,
|
||||
AD9102_SRAM_AMPLITUDE_MIN,
|
||||
AD9102_SRAM_HOLD_MAX,
|
||||
@@ -526,6 +527,25 @@ def _build_wave_tab(owner) -> QWidget:
|
||||
owner._wave_samples_box.textChanged.connect(owner._on_wave_text_changed)
|
||||
layout.addWidget(owner._wave_samples_box)
|
||||
|
||||
period_row = QWidget()
|
||||
period_layout = QHBoxLayout(period_row)
|
||||
period_layout.setContentsMargins(0, 0, 0, 0)
|
||||
period_layout.setSpacing(8)
|
||||
period_label = QLabel("Период повторения")
|
||||
owner._wave_period_us = _int_spinbox(0, AD9102_WAVE_PERIOD_US_MAX, 0, suffix=" мкс")
|
||||
owner._wave_period_us.setToolTip(
|
||||
"Период запуска формы. Между повторами выход держит последний отсчёт формы.\n"
|
||||
"0 - повтор вплотную (как раньше). Для импульса 1 мкс с частотой 1 кГц:\n"
|
||||
"форма = 150 отсчётов вершины + несколько отсчётов базы, период = 1000 мкс."
|
||||
)
|
||||
owner._wave_period_us.valueChanged.connect(owner._on_wave_period_changed)
|
||||
owner._wave_period_info = QLabel("0 = повтор вплотную, без паузы")
|
||||
owner._wave_period_info.setObjectName("captionLabel")
|
||||
period_layout.addWidget(period_label)
|
||||
period_layout.addWidget(owner._wave_period_us)
|
||||
period_layout.addWidget(owner._wave_period_info, stretch=1)
|
||||
layout.addWidget(period_row)
|
||||
|
||||
buttons = QWidget()
|
||||
buttons_layout = QHBoxLayout(buttons)
|
||||
buttons_layout.setContentsMargins(0, 0, 0, 0)
|
||||
|
||||
@@ -22,6 +22,7 @@ from PyQt6.QtWidgets import (
|
||||
import pyqtgraph as pg
|
||||
|
||||
from laser_control.constants import (
|
||||
AD9102_CLOCK_HZ,
|
||||
AD9833_MCLK_HZ,
|
||||
DEFAULT_AD9102_AMPLITUDE,
|
||||
DEFAULT_AD9102_HOLD_CYCLES,
|
||||
@@ -67,7 +68,7 @@ class MainWindow(QMainWindow):
|
||||
request_pulse_ds1809 = pyqtSignal(bool, int, int)
|
||||
request_set_stm32_dac = pyqtSignal(bool, int)
|
||||
request_apply_tec_modulation = pyqtSignal(bool, int, int, int)
|
||||
request_upload_wave = pyqtSignal(object)
|
||||
request_upload_wave = pyqtSignal(object, int)
|
||||
request_cancel_wave = pyqtSignal()
|
||||
request_save_profile = pyqtSignal(object)
|
||||
request_poll = pyqtSignal()
|
||||
@@ -359,7 +360,15 @@ class MainWindow(QMainWindow):
|
||||
except Exception as exc: # noqa: BLE001
|
||||
self._append_log("ERROR", str(exc))
|
||||
return
|
||||
self._dispatch_command(lambda: self.request_upload_wave.emit(samples))
|
||||
pat_period_us = self._wave_period_us.value()
|
||||
pattern_us = len(samples) * 1e6 / AD9102_CLOCK_HZ
|
||||
if pat_period_us and pat_period_us <= pattern_us:
|
||||
self._append_log(
|
||||
"WARN",
|
||||
f"Период {pat_period_us} мкс короче самой формы ({pattern_us:.2f} мкс) - "
|
||||
"форма будет проигрываться вплотную",
|
||||
)
|
||||
self._dispatch_command(lambda: self.request_upload_wave.emit(samples, pat_period_us))
|
||||
|
||||
def _on_cancel_waveform(self) -> None:
|
||||
self._dispatch_command(self.request_cancel_wave.emit)
|
||||
@@ -387,10 +396,23 @@ class MainWindow(QMainWindow):
|
||||
return
|
||||
try:
|
||||
count = len(self._parse_wave_samples(text))
|
||||
self._wave_info_label.setText(f"Отсчётов: {count}")
|
||||
duration_us = count * 1e6 / AD9102_CLOCK_HZ
|
||||
self._wave_info_label.setText(
|
||||
f"Отсчётов: {count} (длительность формы {duration_us:.2f} мкс "
|
||||
f"при клоке {AD9102_CLOCK_HZ / 1e6:.0f} МГц)"
|
||||
)
|
||||
except Exception:
|
||||
self._wave_info_label.setText("Отсчётов: ошибка формата")
|
||||
|
||||
def _on_wave_period_changed(self) -> None:
|
||||
period_us = self._wave_period_us.value()
|
||||
if period_us == 0:
|
||||
self._wave_period_info.setText("0 = повтор вплотную, без паузы")
|
||||
return
|
||||
self._wave_period_info.setText(
|
||||
f"= {1e6 / period_us:.1f} Гц частота повторения"
|
||||
)
|
||||
|
||||
def _on_reconnect(self) -> None:
|
||||
self._append_log("INFO", "Reconnect requested from UI")
|
||||
self._emit_connect_request()
|
||||
@@ -728,6 +750,7 @@ class MainWindow(QMainWindow):
|
||||
f"waveform_saw_step={self._ad9102_saw_step.value()}",
|
||||
f"waveform_pat_base={self._ad9102_pat_base.value()}",
|
||||
f"waveform_pat_period={self._ad9102_pat_period.value()}",
|
||||
f"waveform_custom_period_us={self._wave_period_us.value() if custom_wave_samples else 0}",
|
||||
f"waveform_sample_count={waveform_sample_count}",
|
||||
f"waveform_hold_cycles={waveform_hold_cycles}",
|
||||
f"waveform_amplitude={self._ad9102_amplitude.value()}",
|
||||
|
||||
@@ -127,13 +127,13 @@ class ControllerWorker(QObject):
|
||||
)
|
||||
)
|
||||
|
||||
@pyqtSlot(object)
|
||||
def upload_ad9102_waveform(self, samples: object) -> None:
|
||||
@pyqtSlot(object, int)
|
||||
def upload_ad9102_waveform(self, samples: object, pat_period_us: int) -> None:
|
||||
"""Upload a custom waveform to AD9102 SRAM."""
|
||||
self._run_command(
|
||||
lambda: (
|
||||
self._ensure_connected(),
|
||||
self._upload_ad9102_waveform_impl(samples),
|
||||
self._upload_ad9102_waveform_impl(samples, pat_period_us),
|
||||
)
|
||||
)
|
||||
|
||||
@@ -298,10 +298,14 @@ class ControllerWorker(QObject):
|
||||
self.log_message.emit("INFO", f"Profile saved to SD: {profile_name}")
|
||||
self._emit_status()
|
||||
|
||||
def _upload_ad9102_waveform_impl(self, samples: object) -> None:
|
||||
def _upload_ad9102_waveform_impl(self, samples: object, pat_period_us: int) -> None:
|
||||
sample_list = list(samples)
|
||||
self._controller.upload_ad9102_waveform(sample_list)
|
||||
self.log_message.emit("INFO", f"AD9102 waveform uploaded ({len(sample_list)} samples)")
|
||||
self._controller.upload_ad9102_waveform(sample_list, pat_period_us)
|
||||
period_note = f", период {pat_period_us} мкс" if pat_period_us else ""
|
||||
self.log_message.emit(
|
||||
"INFO",
|
||||
f"AD9102 waveform uploaded ({len(sample_list)} samples{period_note})",
|
||||
)
|
||||
self._emit_status()
|
||||
|
||||
def _cancel_ad9102_waveform_upload_impl(self) -> None:
|
||||
|
||||
@@ -294,13 +294,17 @@ class Protocol:
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def encode_ad9102_wave_begin(sample_count: int) -> bytes:
|
||||
"""Build an AD9102 custom-wave upload BEGIN packet."""
|
||||
def encode_ad9102_wave_begin(sample_count: int, pat_period_us: int = 0) -> bytes:
|
||||
"""Build an AD9102 custom-wave upload BEGIN packet.
|
||||
|
||||
``pat_period_us`` is the pattern repetition period in microseconds;
|
||||
0 keeps the legacy back-to-back playback.
|
||||
"""
|
||||
return Protocol._encode_short_control(
|
||||
CMD_AD9102_WAVE_CONTROL,
|
||||
AD9102_WAVE_OPCODE_BEGIN,
|
||||
_ensure_uint(sample_count, "sample_count", 0, 0xFFFF),
|
||||
0,
|
||||
_ensure_uint(pat_period_us, "pat_period_us", 0, 0xFFFF),
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
8191 8191 8191 8191 8191 8191 8191 8191 8191 8191 8191 8191 8191 8191 8191
|
||||
8191 8191 8191 8191 8191 8191 8191 8191 8191 8191 8191 8191 8191 8191 8191
|
||||
8191 8191 8191 8191 8191 8191 8191 8191 8191 8191 8191 8191 8191 8191 8191
|
||||
8191 8191 8191 8191 8191 8191 8191 8191 8191 8191 8191 8191 8191 8191 8191
|
||||
8191 8191 8191 8191 8191 8191 8191 8191 8191 8191 8191 8191 8191 8191 8191
|
||||
8191 8191 8191 8191 8191 8191 8191 8191 8191 8191 8191 8191 8191 8191 8191
|
||||
8191 8191 8191 8191 8191 8191 8191 8191 8191 8191 8191 8191 8191 8191 8191
|
||||
8191 8191 8191 8191 8191 8191 8191 8191 8191 8191 8191 8191 8191 8191 8191
|
||||
8191 8191 8191 8191 8191 8191 8191 8191 8191 8191 8191 8191 8191 8191 8191
|
||||
8191 8191 8191 8191 8191 8191 8191 8191 8191 8191 8191 8191 8191 8191 8191
|
||||
-8192 -8192 -8192 -8192 -8192 -8192 -8192 -8192
|
||||
@@ -0,0 +1,21 @@
|
||||
8191 8191 8191 8191 8191 8191 8191 8191 8191 8191 8191 8191 8191 8191 8191
|
||||
8191 8191 8191 8191 8191 8191 8191 8191 8191 8191 8191 8191 8191 8191 8191
|
||||
8191 8191 8191 8191 8191 8191 8191 8191 8191 8191 8191 8191 8191 8191 8191
|
||||
8191 8191 8191 8191 8191 8191 8191 8191 8191 8191 8191 8191 8191 8191 8191
|
||||
8191 8191 8191 8191 8191 8191 8191 8191 8191 8191 8191 8191 8191 8191 8191
|
||||
8191 8191 8191 8191 8191 8191 8191 8191 8191 8191 8191 8191 8191 8191 8191
|
||||
8191 8191 8191 8191 8191 8191 8191 8191 8191 8191 8191 8191 8191 8191 8191
|
||||
8191 8191 8191 8191 8191 8191 8191 8191 8191 8191 8191 8191 8191 8191 8191
|
||||
8191 8191 8191 8191 8191 8191 8191 8191 8191 8191 8191 8191 8191 8191 8191
|
||||
8191 8191 8191 8191 8191 8191 8191 8191 8191 8191 8191 8191 8191 8191 8191
|
||||
8191 8191 8191 8191 8191 8191 8191 8191 8191 8191 8191 8191 8191 8191 8191
|
||||
8191 8191 8191 8191 8191 8191 8191 8191 8191 8191 8191 8191 8191 8191 8191
|
||||
8191 8191 8191 8191 8191 8191 8191 8191 8191 8191 8191 8191 8191 8191 8191
|
||||
8191 8191 8191 8191 8191 8191 8191 8191 8191 8191 8191 8191 8191 8191 8191
|
||||
8191 8191 8191 8191 8191 8191 8191 8191 8191 8191 8191 8191 8191 8191 8191
|
||||
8191 8191 8191 8191 8191 8191 8191 8191 8191 8191 8191 8191 8191 8191 8191
|
||||
8191 8191 8191 8191 8191 8191 8191 8191 8191 8191 8191 8191 8191 8191 8191
|
||||
8191 8191 8191 8191 8191 8191 8191 8191 8191 8191 8191 8191 8191 8191 8191
|
||||
8191 8191 8191 8191 8191 8191 8191 8191 8191 8191 8191 8191 8191 8191 8191
|
||||
8191 8191 8191 8191 8191 8191 8191 8191 8191 8191 8191 8191 8191 8191 8191
|
||||
-8192 -8192 -8192 -8192 -8192 -8192 -8192 -8192
|
||||
@@ -0,0 +1,6 @@
|
||||
8191 8191 8191 8191 8191 8191 8191 8191 8191 8191 8191 8191 8191 8191 8191
|
||||
8191 8191 8191 8191 8191 8191 8191 8191 8191 8191 8191 8191 8191 8191 8191
|
||||
8191 8191 8191 8191 8191 8191 8191 8191 8191 8191 8191 8191 8191 8191 8191
|
||||
8191 8191 8191 8191 8191 8191 8191 8191 8191 8191 8191 8191 8191 8191 8191
|
||||
8191 8191 8191 8191 8191 8191 8191 8191 8191 8191 8191 8191 8191 8191 8191
|
||||
-8192 -8192 -8192 -8192 -8192 -8192 -8192 -8192
|
||||
Reference in New Issue
Block a user