diff --git a/DumpWaveform/.gitignore b/DumpWaveform/.gitignore deleted file mode 100644 index 172c0d3..0000000 --- a/DumpWaveform/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -*.raw -.ipynb_checkpoints/ diff --git a/DumpWaveform/AnimateWaveform.ipynb b/DumpWaveform/AnimateWaveform.ipynb deleted file mode 100644 index 8b0dffc..0000000 --- a/DumpWaveform/AnimateWaveform.ipynb +++ /dev/null @@ -1,166 +0,0 @@ -{ - "cells": [ - { - "cell_type": "code", - "execution_count": null, - "id": "eaa22829", - "metadata": {}, - "outputs": [], - "source": [ - "# Animation method from\n", - "# http://louistiao.me/posts/notebooks/embedding-matplotlib-animations-in-jupyter-as-interactive-javascript-widgets/" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "6d0360a7", - "metadata": {}, - "outputs": [], - "source": [ - "import numpy as np\n", - "import matplotlib.pyplot as plt\n", - "from matplotlib import animation, rc\n", - "from IPython.display import HTML" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "1bc60c12", - "metadata": {}, - "outputs": [], - "source": [ - "rc(\"animation\", html=\"jshtml\")\n", - "rc(\"figure\", figsize=(8, 8))" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "ed4fdb04", - "metadata": {}, - "outputs": [], - "source": [ - "wave = np.fromfile(\"WaveformTest.raw\")\n", - "wave = wave.reshape(2, -1)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "16fa531e", - "metadata": {}, - "outputs": [], - "source": [ - "plot_range = 1.0" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "04653002", - "metadata": {}, - "outputs": [], - "source": [ - "fig, ax = plt.subplots()\n", - "ax.set_xlim(-plot_range, plot_range)\n", - "ax.set_ylim(plot_range, -plot_range)\n", - "ax.set_aspect('equal', adjustable='box')\n", - "# static_line = ax.plot(wave[0], wave[1])\n", - "line, = ax.plot([], [])" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "1a0617bb", - "metadata": {}, - "outputs": [], - "source": [ - "tstep = 10\n", - "frames = wave.shape[1] // tstep\n", - "frames" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "5acd8868", - "metadata": {}, - "outputs": [], - "source": [ - "def init():\n", - " line.set_data([], [])\n", - " return (line,)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "19b3d5fa", - "metadata": {}, - "outputs": [], - "source": [ - "def animate(i):\n", - " tstart = 0\n", - " tstop = min(wave.shape[1], i * tstep)\n", - " t = range(tstart, tstop)\n", - " x = wave[0, t]\n", - " y = wave[1, t]\n", - " line.set_data(x, y)\n", - " return (line,)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "6643efc0", - "metadata": {}, - "outputs": [], - "source": [ - "anim = animation.FuncAnimation(fig, animate, init_func=init,\n", - " frames=frames, interval=20, blit=True)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "3c5af8ca", - "metadata": {}, - "outputs": [], - "source": [ - "anim # This will take a while to display" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "2369d7aa", - "metadata": {}, - "outputs": [], - "source": [] - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python 3 (ipykernel)", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.10.0" - } - }, - "nbformat": 4, - "nbformat_minor": 5 -} diff --git a/DumpWaveform/DumpWaveform.c b/DumpWaveform/DumpWaveform.c deleted file mode 100644 index 2ab6d54..0000000 --- a/DumpWaveform/DumpWaveform.c +++ /dev/null @@ -1,108 +0,0 @@ -#include "../src/Waveform.h" - -#include -#include -#include -#include -#include - -// write results to binary file -static void DumpXYWaveform(uint32_t resolution, uint32_t undershoot) { - - struct WaveformParams params; - params.width = resolution; - params.height = resolution; - params.resolution = resolution; - params.zoom = 1; - params.undershoot = undershoot; - params.xOffset = 0; - params.yOffset = 0; - params.xformMatrix[0] = 1; - params.xformMatrix[1] = 0; - params.xformMatrix[2] = 0; - params.xformMatrix[3] = 1; - params.xformOffsetX = 0; - params.xformOffsetY = 0; - - uint32_t totalElementsPerFrame = GetScannerWaveformSize(¶ms); - - uint32_t bufferSize = totalElementsPerFrame * 2; - double *xyWaveform = malloc(sizeof(double) * bufferSize); - if (xyWaveform == NULL) { - fprintf(stderr, "Out of memory\n"); - exit(EXIT_FAILURE); - } - - GenerateGalvoWaveformFrame(¶ms, xyWaveform); - - FILE *testFile = fopen("WaveformTest.raw", "wb"); - fwrite(xyWaveform, sizeof(double), bufferSize, testFile); - fclose(testFile); - - printf("total sample count = %lu\n", (unsigned long)bufferSize); - free(xyWaveform); -} - -static void DumpClockWaveform(uint32_t resolution, uint32_t lineDelay) { - struct WaveformParams WaveformParameters; - WaveformParameters.width = resolution; - WaveformParameters.height = resolution; - WaveformParameters.undershoot = lineDelay; - WaveformParameters.xOffset = 0; - WaveformParameters.yOffset = 0; - - uint32_t elementsPerFramePerChan = - GetClockWaveformSize(&WaveformParameters); - uint32_t bufferSize = elementsPerFramePerChan; - - uint8_t *lineClockPattern = malloc(elementsPerFramePerChan); - uint8_t *lineClockFLIM = malloc(elementsPerFramePerChan); - uint8_t *frameClockFLIM = malloc(elementsPerFramePerChan); - - if (lineClockPattern == NULL || lineClockFLIM == NULL || - frameClockFLIM == NULL) { - fprintf(stderr, "Out of memory\n"); - exit(EXIT_FAILURE); - } - - GenerateLineClock(&WaveformParameters, lineClockPattern); - GenerateFLIMLineClock(&WaveformParameters, lineClockFLIM); - GenerateFLIMFrameClock(&WaveformParameters, frameClockFLIM); - - FILE *clockFile = fopen("clock_uint8_numofwaveforms_3.raw", "wb"); - fwrite(lineClockPattern, sizeof(uint8_t), bufferSize, clockFile); - fwrite(lineClockFLIM, sizeof(uint8_t), bufferSize, clockFile); - fwrite(frameClockFLIM, sizeof(uint8_t), bufferSize, clockFile); - fclose(clockFile); - - printf("total sample count = %lu\n", (unsigned long)bufferSize * 3); - free(lineClockPattern); - free(lineClockFLIM); - free(frameClockFLIM); -} - -int main(int argc, char *argv[]) { - if (argc != 4) { - fprintf( - stderr, - "3 arguments required: waveformtype, resolution, undershoot/linedelay"); - } - - else { - if (strcmp(argv[1], "XYWaveform") == 0) { - uint32_t resolution = atoi(argv[2]); - uint32_t undershoot = atoi(argv[3]); - DumpXYWaveform(resolution, undershoot); - } - - else if (strcmp(argv[1], "ClockWaveform") == 0) { - uint32_t resolution = atoi(argv[2]); - uint32_t lineDelay = atoi(argv[3]); - DumpClockWaveform(resolution, lineDelay); - } - - else { - fprintf(stderr, "Invalid Waveform Type"); - } - } -} diff --git a/DumpWaveform/meson.build b/DumpWaveform/meson.build deleted file mode 100644 index e8f7f9b..0000000 --- a/DumpWaveform/meson.build +++ /dev/null @@ -1,15 +0,0 @@ -dumpwaveform_src = [ - 'DumpWaveform.c', - '../src/Waveform.c', -] - -executable( - 'DumpWaveform', - dumpwaveform_src, - c_args: [ - '-D_CRT_SECURE_NO_WARNINGS', - ], - dependencies: [ - openscandevicelib_dep, - ], -) diff --git a/WaveformTool/WaveformTool.c b/WaveformTool/WaveformTool.c index 48a2b4c..a7c66f3 100644 --- a/WaveformTool/WaveformTool.c +++ b/WaveformTool/WaveformTool.c @@ -29,7 +29,11 @@ struct Args { uint32_t xOffset; uint32_t yOffset; double zoom; - uint32_t undershoot; + double pixelRateHz; + double aoRateHz; + double undershootUs; + double scanPhaseUs; + double retraceScaleUsPerVolt; double xformMatrix[4]; double xformOffsetX; double xformOffsetY; @@ -41,6 +45,8 @@ struct Args { int hasResolution; int hasWidth; int hasHeight; + int hasPixelRate; + int hasAORate; }; static int ParseUint32(const char *str, const char *name, uint32_t *out) { @@ -84,32 +90,39 @@ static void PrintUsage(void) { "Types: raster, clock, park, unpark\n" "\n" "Options:\n" - " -o Output file (required)\n" - " --format csv|raw Override format (auto from extension)\n" - " --resolution Scanner resolution\n" - " --width ROI width (default: resolution)\n" - " --height ROI height (default: resolution)\n" - " --xoffset ROI X offset (default: 0)\n" - " --yoffset ROI Y offset (default: 0)\n" - " --zoom Zoom factor (default: 1.0)\n" - " --undershoot Undershoot / line delay (default: 0)\n" - " --tform Affine 2x2 matrix, row-major\n" - " --tform-offset Affine translation in volts\n" - " --xpark X park position (default: 0)\n" - " --ypark Y park position (default: 0)\n" - " --prev-xpark-voltage Previous X park voltage (default: 0)\n" - " --prev-ypark-voltage Previous Y park voltage (default: 0)\n" + " -o Output file (required)\n" + " --format csv|raw Override format (auto from extension)\n" + " --resolution Scanner resolution\n" + " --width ROI width (default: resolution)\n" + " --height ROI height (default: resolution)\n" + " --xoffset ROI X offset (default: 0)\n" + " --yoffset ROI Y offset (default: 0)\n" + " --zoom Zoom factor (default: 1.0)\n" + " --pixel-rate Pixel rate in Hz (required)\n" + " --ao-rate AO sample rate in Hz (default: pixel rate)\n" + " --undershoot-us Undershoot in microseconds (default: 0)\n" + " --scan-phase-us Scan phase in microseconds (default: 0)\n" + " --retrace-scale Retrace scale in us/V (default: 640)\n" + " --tform Affine 2x2 matrix, row-major\n" + " --tform-offset Affine translation in volts\n" + " --xpark X park position (default: 0)\n" + " --ypark Y park position (default: 0)\n" + " --prev-xpark-voltage Previous X park voltage (default: 0)\n" + " --prev-ypark-voltage Previous Y park voltage (default: 0)\n" "\n" "Required parameters:\n" + " All types: --pixel-rate\n" " raster: --resolution\n" " clock: --width and --height (or --resolution)\n" " park, unpark: --resolution\n" "\n" "Examples:\n" - " WaveformTool raster -o scan.csv --resolution 256\n" - " WaveformTool raster -o scan.raw --resolution 256 --undershoot 10\n" - " WaveformTool clock -o clock.csv --resolution 64 --undershoot 5\n" - " WaveformTool park -o park.csv --resolution 256\n"); + " WaveformTool raster -o scan.csv --resolution 256 --pixel-rate 200000\n" + " WaveformTool raster -o scan.csv --resolution 256 --pixel-rate 200000" + " --ao-rate 500000 --undershoot-us 250\n" + " WaveformTool clock -o clock.csv --resolution 64 --pixel-rate 200000" + " --undershoot-us 250\n" + " WaveformTool park -o park.csv --resolution 256 --pixel-rate 200000\n"); } static int ParseTform(const char *str, double m[4]) { @@ -189,6 +202,7 @@ static int ParseTformOffset(const char *str, double *tx, double *ty) { static int ParseArgs(int argc, char *argv[], struct Args *args) { memset(args, 0, sizeof(*args)); args->zoom = 1.0; + args->retraceScaleUsPerVolt = 640.0; args->xformMatrix[0] = 1.0; args->xformMatrix[1] = 0.0; args->xformMatrix[2] = 0.0; @@ -252,8 +266,24 @@ static int ParseArgs(int argc, char *argv[], struct Args *args) { } else if (strcmp(argv[i], "--zoom") == 0 && i + 1 < argc) { if (!ParseDouble(argv[++i], "--zoom", &args->zoom)) return 0; - } else if (strcmp(argv[i], "--undershoot") == 0 && i + 1 < argc) { - if (!ParseUint32(argv[++i], "--undershoot", &args->undershoot)) + } else if (strcmp(argv[i], "--pixel-rate") == 0 && i + 1 < argc) { + if (!ParseDouble(argv[++i], "--pixel-rate", &args->pixelRateHz)) + return 0; + args->hasPixelRate = 1; + } else if (strcmp(argv[i], "--ao-rate") == 0 && i + 1 < argc) { + if (!ParseDouble(argv[++i], "--ao-rate", &args->aoRateHz)) + return 0; + args->hasAORate = 1; + } else if (strcmp(argv[i], "--undershoot-us") == 0 && i + 1 < argc) { + if (!ParseDouble(argv[++i], "--undershoot-us", + &args->undershootUs)) + return 0; + } else if (strcmp(argv[i], "--scan-phase-us") == 0 && i + 1 < argc) { + if (!ParseDouble(argv[++i], "--scan-phase-us", &args->scanPhaseUs)) + return 0; + } else if (strcmp(argv[i], "--retrace-scale") == 0 && i + 1 < argc) { + if (!ParseDouble(argv[++i], "--retrace-scale", + &args->retraceScaleUsPerVolt)) return 0; } else if (strcmp(argv[i], "--tform") == 0 && i + 1 < argc) { if (!ParseTform(argv[++i], args->xformMatrix)) @@ -289,6 +319,14 @@ static int ParseArgs(int argc, char *argv[], struct Args *args) { return 0; } + if (!args->hasPixelRate) { + fprintf(stderr, "Error: --pixel-rate is required\n"); + return 0; + } + + if (!args->hasAORate) + args->aoRateHz = args->pixelRateHz; + switch (args->type) { case WAVEFORM_RASTER: case WAVEFORM_PARK: @@ -351,7 +389,11 @@ static void PopulateParams(const struct Args *args, params->height = args->height; params->resolution = args->resolution; params->zoom = args->zoom; - params->undershoot = args->undershoot; + params->pixelRateHz = args->pixelRateHz; + params->aoRateHz = args->aoRateHz; + params->undershootUs = args->undershootUs; + params->scanPhaseUs = args->scanPhaseUs; + params->retraceScaleUsPerVolt = args->retraceScaleUsPerVolt; params->xOffset = args->xOffset; params->yOffset = args->yOffset; memcpy(params->xformMatrix, args->xformMatrix, @@ -362,6 +404,12 @@ static void PopulateParams(const struct Args *args, params->yPark = args->yPark; params->prevXParkVoltage = args->prevXParkVoltage; params->prevYParkVoltage = args->prevYParkVoltage; + params->laserBlankingSupported = false; + params->laserManualOn = false; + params->laserOnVoltage = 1.0; + params->laserOffVoltage = 0.0; + params->laserOnLeadUs = 0.0; + params->laserOnLagUs = 0.0; } static int WriteXYCsv(FILE *f, const double *xy, uint32_t n) { diff --git a/WaveformTool/waveform_viewer.py b/WaveformTool/waveform_viewer.py index f1d147a..ee45ea2 100644 --- a/WaveformTool/waveform_viewer.py +++ b/WaveformTool/waveform_viewer.py @@ -40,6 +40,22 @@ def run_tool(args: list[str], tmp: Path) -> subprocess.CompletedProcess: return subprocess.run(cmd, capture_output=True, text=True) +def timing_args( + pixel_rate: int, + ao_rate: int, + undershoot_us: float, + scan_phase_us: float, + retrace_scale: float, +) -> list[str]: + return [ + "--pixel-rate", str(pixel_rate), + "--ao-rate", str(ao_rate), + "--undershoot-us", str(undershoot_us), + "--scan-phase-us", str(scan_phase_us), + "--retrace-scale", str(retrace_scale), + ] + + def generate_raster( resolution: int, width: int, @@ -47,7 +63,11 @@ def generate_raster( x_offset: int, y_offset: int, zoom: float, - undershoot: int, + pixel_rate: int, + ao_rate: int, + undershoot_us: float, + scan_phase_us: float, + retrace_scale: float, tform: tuple[float, float, float, float], tform_offset: tuple[float, float], ) -> tuple[np.ndarray, np.ndarray] | str: @@ -62,7 +82,7 @@ def generate_raster( "--xoffset", str(x_offset), "--yoffset", str(y_offset), "--zoom", str(zoom), - "--undershoot", str(undershoot), + *timing_args(pixel_rate, ao_rate, undershoot_us, scan_phase_us, retrace_scale), "--tform", f"{tform[0]},{tform[1]},{tform[2]},{tform[3]}", "--tform-offset", f"{tform_offset[0]},{tform_offset[1]}", ], tmp) @@ -80,16 +100,24 @@ def generate_raster( def generate_clock( width: int, height: int, - undershoot: int, + resolution: int, + zoom: float, + pixel_rate: int, + ao_rate: int, + undershoot_us: float, + scan_phase_us: float, + retrace_scale: float, ) -> tuple[np.ndarray, np.ndarray, np.ndarray] | str: with tempfile.NamedTemporaryFile(suffix=".raw", delete=False) as f: tmp = Path(f.name) try: result = run_tool([ "clock", + "--resolution", str(resolution), "--width", str(width), "--height", str(height), - "--undershoot", str(undershoot), + "--zoom", str(zoom), + *timing_args(pixel_rate, ao_rate, undershoot_us, scan_phase_us, retrace_scale), ], tmp) if result.returncode != 0: return result.stderr.strip() or f"clock exited with code {result.returncode}" @@ -105,7 +133,11 @@ def generate_clock( def generate_park( resolution: int, zoom: float, - undershoot: int, + pixel_rate: int, + ao_rate: int, + undershoot_us: float, + scan_phase_us: float, + retrace_scale: float, x_offset: int, y_offset: int, xpark: int, @@ -120,7 +152,7 @@ def generate_park( "park", "--resolution", str(resolution), "--zoom", str(zoom), - "--undershoot", str(undershoot), + *timing_args(pixel_rate, ao_rate, undershoot_us, scan_phase_us, retrace_scale), "--xoffset", str(x_offset), "--yoffset", str(y_offset), "--xpark", str(xpark), @@ -142,7 +174,11 @@ def generate_park( def generate_unpark( resolution: int, zoom: float, - undershoot: int, + pixel_rate: int, + ao_rate: int, + undershoot_us: float, + scan_phase_us: float, + retrace_scale: float, x_offset: int, y_offset: int, xpark: int, @@ -159,7 +195,7 @@ def generate_unpark( "unpark", "--resolution", str(resolution), "--zoom", str(zoom), - "--undershoot", str(undershoot), + *timing_args(pixel_rate, ao_rate, undershoot_us, scan_phase_us, retrace_scale), "--xoffset", str(x_offset), "--yoffset", str(y_offset), "--xpark", str(xpark), @@ -205,7 +241,11 @@ def on_generate(_sender=None, _data=None): x_offset = dpg.get_value("x_offset") y_offset = dpg.get_value("y_offset") zoom = dpg.get_value("zoom") - undershoot = dpg.get_value("undershoot") + pixel_rate = dpg.get_value("pixel_rate") + ao_rate = dpg.get_value("ao_rate") + undershoot_us = dpg.get_value("undershoot_us") + scan_phase_us = dpg.get_value("scan_phase_us") + retrace_scale = dpg.get_value("retrace_scale") tform = ( dpg.get_value("tform_a"), dpg.get_value("tform_b"), @@ -221,27 +261,33 @@ def on_generate(_sender=None, _data=None): errors = [] unpark_result = generate_unpark( - resolution, zoom, undershoot, x_offset, y_offset, + resolution, zoom, pixel_rate, ao_rate, undershoot_us, + scan_phase_us, retrace_scale, x_offset, y_offset, xpark, ypark, prev_xpv, prev_ypv, tform, tform_offset, ) if isinstance(unpark_result, str): errors.append(unpark_result) raster_result = generate_raster( - resolution, width, height, x_offset, y_offset, zoom, undershoot, + resolution, width, height, x_offset, y_offset, zoom, + pixel_rate, ao_rate, undershoot_us, scan_phase_us, retrace_scale, tform, tform_offset, ) if isinstance(raster_result, str): errors.append(raster_result) park_result = generate_park( - resolution, zoom, undershoot, x_offset, y_offset, + resolution, zoom, pixel_rate, ao_rate, undershoot_us, + scan_phase_us, retrace_scale, x_offset, y_offset, xpark, ypark, tform, tform_offset, ) if isinstance(park_result, str): errors.append(park_result) - clock_result = generate_clock(width, height, undershoot) + clock_result = generate_clock( + width, height, resolution, zoom, + pixel_rate, ao_rate, undershoot_us, scan_phase_us, retrace_scale, + ) if isinstance(clock_result, str): errors.append(clock_result) @@ -408,11 +454,35 @@ def main(): default_value=1.0, min_value=0.01, min_clamped=True, format="%.3f", callback=on_generate, ) + + dpg.add_separator() + dpg.add_text("Timing") + dpg.add_input_int( - label="Undershoot", tag="undershoot", - default_value=0, min_value=0, min_clamped=True, + label="Pixel Rate (Hz)", tag="pixel_rate", + default_value=200000, min_value=1, min_clamped=True, callback=on_generate, ) + dpg.add_input_int( + label="AO Rate (Hz)", tag="ao_rate", + default_value=200000, min_value=1, min_clamped=True, + callback=on_generate, + ) + dpg.add_input_float( + label="Undershoot (us)", tag="undershoot_us", + default_value=250.0, min_value=0.0, min_clamped=True, + format="%.1f", callback=on_generate, + ) + dpg.add_input_float( + label="Scan Phase (us)", tag="scan_phase_us", + default_value=0.0, min_value=0.0, min_clamped=True, + format="%.1f", callback=on_generate, + ) + dpg.add_input_float( + label="Retrace Scale (us/V)", tag="retrace_scale", + default_value=640.0, min_value=1.0, min_clamped=True, + format="%.1f", callback=on_generate, + ) dpg.add_separator() dpg.add_text("Park / Unpark") diff --git a/meson.build b/meson.build index 2803375..158729b 100644 --- a/meson.build +++ b/meson.build @@ -70,5 +70,4 @@ shared_module( ], ) -subdir('DumpWaveform') subdir('WaveformTool') diff --git a/src/Acquisition.c b/src/Acquisition.c index 720cd09..12ff4ba 100644 --- a/src/Acquisition.c +++ b/src/Acquisition.c @@ -20,6 +20,17 @@ static OScDev_RichError *SetUpDAQ(OScDev_Device *device) { OScDev_Acquisition *acq = GetImplData(device)->acquisition.acquisition; double pixelRateHz = OScDev_Acquisition_GetPixelRate(acq); + + OScDev_RichError *err = EnsureTimingCapsQueried(device); + if (err) + return err; + double aoRateHz; + if (!ComputeAORateHz(pixelRateHz, GetImplData(device)->sampClkTimebaseHz, + GetImplData(device)->aoMaxRateHz, &aoRateHz)) + return OScDev_Error_Create( + "No valid AO sample rate for the selected pixel rate"); + GetImplData(device)->aoRateHz = aoRateHz; + uint32_t resolution = OScDev_Acquisition_GetResolution(acq); double zoomFactor = OScDev_Acquisition_GetZoomFactor(acq); uint32_t xOffset, yOffset, width, height; @@ -33,6 +44,8 @@ static OScDev_RichError *SetUpDAQ(OScDev_Device *device) { GetImplData(device)->clockConfig.mustReconfigureTiming = true; GetImplData(device)->scannerConfig.mustReconfigureTiming = true; GetImplData(device)->detectorConfig.mustReconfigureTiming = true; + GetImplData(device)->clockConfig.mustRewriteOutput = true; + GetImplData(device)->scannerConfig.mustRewriteOutput = true; } if (resolution != GetImplData(device)->configuredResolution) { GetImplData(device)->scannerConfig.mustReconfigureTiming = true; @@ -59,8 +72,6 @@ static OScDev_RichError *SetUpDAQ(OScDev_Device *device) { // Note that additional setting of 'mustReconfigure' flags occurs in // settings - OScDev_RichError *err; - err = SetUpClock(device, &GetImplData(device)->clockConfig, acq); if (err) return err; @@ -184,12 +195,11 @@ static DWORD WINAPI AcquisitionLoop(void *param) { GetImplData(device)->rawDataSize = 0; GetImplData(device)->activeWriteBuffer = 0; - double pixelRateHz = OScDev_Acquisition_GetPixelRate(acq); struct WaveformParams params; SetWaveformParamsFromDevice(device, ¶ms, acq); uint32_t totalElementsPerFramePerChan = GetScannerWaveformSize(¶ms); uint32_t estFrameTimeMs = - (uint32_t)(1e3 * totalElementsPerFramePerChan / pixelRateHz); + (uint32_t)(1e3 * totalElementsPerFramePerChan / params.aoRateHz); err = StartScan(device); if (err) { diff --git a/src/Clock.c b/src/Clock.c index 48bac07..848771e 100644 --- a/src/Clock.c +++ b/src/Clock.c @@ -52,22 +52,21 @@ static OScDev_RichError *CreateClockTasks(OScDev_Device *device, return err; } - double pixelRateHz = OScDev_Acquisition_GetPixelRate(acq); - uint32_t xOffset, yOffset, width, height; - OScDev_Acquisition_GetROI(acq, &xOffset, &yOffset, &width, &height); struct WaveformParams params; SetWaveformParamsFromDevice(device, ¶ms, acq); - uint32_t elementsPerLine = GetLineWaveformSize(¶ms); - double effectiveScanPortion = (double)width / elementsPerLine; - double lineFreqHz = pixelRateHz / elementsPerLine; - double scanPhase = 1.0 / pixelRateHz * GetImplData(device)->lineDelay; + uint32_t elementsPerLine = (uint32_t)GetLineWaveformSize(¶ms); + double initialDelay = + (double)(UndershootSamples(¶ms) + ScanPhaseSamples(¶ms)) / + params.aoRateHz; + double lineFreqHz = params.aoRateHz / elementsPerLine; + double dutyCycle = (double)ScanSamples(¶ms) / elementsPerLine; ss8str ctrTerms; ss8_init_copy(&ctrTerms, &GetImplData(device)->deviceName); ss8_cat_cstr(&ctrTerms, "/ctr0"); err = CreateDAQmxError(DAQmxCreateCOPulseChanFreq( config->lineCtrTask, ss8_cstr(&ctrTerms), "ClockLineCTR", DAQmx_Val_Hz, - DAQmx_Val_Low, scanPhase, lineFreqHz, effectiveScanPortion)); + DAQmx_Val_Low, initialDelay, lineFreqHz, dutyCycle)); ss8_destroy(&ctrTerms); if (err) { err = @@ -83,13 +82,12 @@ static OScDev_RichError *ConfigureClockTiming(OScDev_Device *device, OScDev_Acquisition *acq) { OScDev_RichError *err; - double pixelRateHz = OScDev_Acquisition_GetPixelRate(acq); struct WaveformParams params; SetWaveformParamsFromDevice(device, ¶ms, acq); uint32_t xOffset, yOffset, width, height; OScDev_Acquisition_GetROI(acq, &xOffset, &yOffset, &width, &height); - uint32_t elementsPerLine = GetLineWaveformSize(¶ms); + uint32_t elementsPerLine = (uint32_t)GetLineWaveformSize(¶ms); int32 elementsPerFramePerChan = GetClockWaveformSize(¶ms); uint32_t totalFrames = OScDev_Acquisition_GetNumberOfFrames(acq); @@ -111,7 +109,7 @@ static OScDev_RichError *ConfigureClockTiming(OScDev_Device *device, doSamplesPerChan = totalDOSamples; } err = CreateDAQmxError( - DAQmxCfgSampClkTiming(config->doTask, "", pixelRateHz, + DAQmxCfgSampClkTiming(config->doTask, "", params.aoRateHz, DAQmx_Val_Rising, sampleMode, doSamplesPerChan)); if (err) { err = OScDev_Error_Wrap( @@ -127,9 +125,11 @@ static OScDev_RichError *ConfigureClockTiming(OScDev_Device *device, return err; } - double effectiveScanPortion = (double)width / elementsPerLine; - double lineFreqHz = pixelRateHz / elementsPerLine; - double scanPhase = 1.0 / pixelRateHz * GetImplData(device)->lineDelay; + double dutyCycle = (double)ScanSamples(¶ms) / elementsPerLine; + double lineFreqHz = params.aoRateHz / elementsPerLine; + double initialDelay = + (double)(UndershootSamples(¶ms) + ScanPhaseSamples(¶ms)) / + params.aoRateHz; err = CreateDAQmxError(DAQmxSetChanAttribute( config->lineCtrTask, "", DAQmx_CO_Pulse_Freq, lineFreqHz)); @@ -138,17 +138,17 @@ static OScDev_RichError *ConfigureClockTiming(OScDev_Device *device, return err; } - err = CreateDAQmxError(DAQmxSetChanAttribute( - config->lineCtrTask, "", DAQmx_CO_Pulse_Freq_InitialDelay, scanPhase)); + err = CreateDAQmxError( + DAQmxSetChanAttribute(config->lineCtrTask, "", + DAQmx_CO_Pulse_Freq_InitialDelay, initialDelay)); if (err) { err = OScDev_Error_Wrap(err, "Failed to set clock lineCtr initial delay"); return err; } - err = CreateDAQmxError(DAQmxSetChanAttribute(config->lineCtrTask, "", - DAQmx_CO_Pulse_DutyCyc, - effectiveScanPortion)); + err = CreateDAQmxError(DAQmxSetChanAttribute( + config->lineCtrTask, "", DAQmx_CO_Pulse_DutyCyc, dutyCycle)); if (err) { err = OScDev_Error_Wrap(err, "Failed to set clock lineCtr duty cycle"); return err; diff --git a/src/DAQConfig.c b/src/DAQConfig.c index c73ed5e..ed22219 100644 --- a/src/DAQConfig.c +++ b/src/DAQConfig.c @@ -12,9 +12,105 @@ #include #include +#include +#include #include #include +static const double MIN_AO_RATE_HZ = 100.0e3; // keep galvo waveform smooth +static const double TARGET_AO_RATE_HZ = 200.0e3; // galvo BW ~kHz; higher + // wastes memory/CPU + +// Choose aoRate = timebaseHz / t, where t (AO period in ticks) divides the +// pixel period in ticks. Among candidates within [MIN_AO_RATE_HZ, aoMaxHz], +// pick the frequency closest to TARGET_AO_RATE_HZ; tie -> higher frequency. +// Returns false when no valid AO rate exists for this pixel rate. +bool ComputeAORateHz(double pixelRateHz, double timebaseHz, double aoMaxHz, + double *aoRateHz) { + long pixelPeriodTicks = lround(timebaseHz / pixelRateHz); + bool found = false; + double bestFreq = 0.0, bestDist = 0.0; + for (long t = 1; t <= pixelPeriodTicks; ++t) { + if (pixelPeriodTicks % t != 0) + continue; // K = pixelPeriodTicks / t must be integer + double f = timebaseHz / (double)t; + if (f < MIN_AO_RATE_HZ || f > aoMaxHz) + continue; + double d = fabs(f - TARGET_AO_RATE_HZ); + if (!found || d < bestDist || (d == bestDist && f > bestFreq)) { + found = true; + bestDist = d; + bestFreq = f; + } + } + if (found) + *aoRateHz = bestFreq; + return found; +} + +// Populate GetImplData(device)->sampClkTimebaseHz and ->aoMaxRateHz on first +// call; no-op afterwards (guarded by ->timingCapsQueried). +OScDev_RichError *EnsureTimingCapsQueried(OScDev_Device *device) { + if (GetImplData(device)->timingCapsQueried) + return OScDev_RichError_OK; + + const char *dev = ss8_cstr(&GetImplData(device)->deviceName); + OScDev_RichError *err; + + // AO max rate: plain device attribute, no task needed. + float64 aoMax; + err = CreateDAQmxError(DAQmxGetDevAOMaxRate(dev, &aoMax)); + if (err) + return OScDev_Error_Wrap(err, + "Failed to query AO maximum sample rate"); + + // Timebase: throwaway committed AO task. + ss8str aoChan; + ss8_init_copy(&aoChan, &GetImplData(device)->deviceName); + ss8_cat_cstr(&aoChan, "/ao0"); + + TaskHandle task = 0; + float64 timebase = 0.0; + err = CreateDAQmxError(DAQmxCreateTask("", &task)); + if (err) { + err = OScDev_Error_Wrap(err, "Failed to create timing-query task"); + goto cleanup; + } + err = CreateDAQmxError(DAQmxCreateAOVoltageChan( + task, ss8_cstr(&aoChan), "", -10.0, 10.0, DAQmx_Val_Volts, NULL)); + if (err) { + err = OScDev_Error_Wrap( + err, "Failed to create ao channel for timing query"); + goto cleanup; + } + err = CreateDAQmxError(DAQmxCfgSampClkTiming( + task, "", 100000.0, DAQmx_Val_Rising, DAQmx_Val_ContSamps, 1000)); + if (err) { + err = OScDev_Error_Wrap(err, "Failed to configure timing-query task"); + goto cleanup; + } + err = CreateDAQmxError(DAQmxTaskControl(task, DAQmx_Val_Task_Commit)); + if (err) { + err = OScDev_Error_Wrap(err, "Failed to commit timing-query task"); + goto cleanup; + } + err = CreateDAQmxError(DAQmxGetSampClkTimebaseRate(task, &timebase)); + if (err) { + err = OScDev_Error_Wrap(err, "Failed to query samp clk timebase rate"); + goto cleanup; + } + + GetImplData(device)->aoMaxRateHz = aoMax; + GetImplData(device)->sampClkTimebaseHz = timebase; + GetImplData(device)->timingCapsQueried = true; + +cleanup: + if (task) + DAQmxClearTask(task); + ss8_destroy(&aoChan); + return err; +} + // Return the index-th physical channel, or empty string if no such channel static bool GetAIPhysChan(OScDev_Device *device, int index, ss8str *chan) { if (index < 0) { @@ -58,7 +154,12 @@ void SetWaveformParamsFromDevice(OScDev_Device *device, parameters->zoom = OScDev_Acquisition_GetZoomFactor(acq); OScDev_Acquisition_GetROI(acq, ¶meters->xOffset, ¶meters->yOffset, ¶meters->width, ¶meters->height); - parameters->undershoot = GetImplData(device)->lineDelay; + parameters->pixelRateHz = OScDev_Acquisition_GetPixelRate(acq); + parameters->aoRateHz = GetImplData(device)->aoRateHz; + parameters->undershootUs = GetImplData(device)->undershootUs; + parameters->scanPhaseUs = GetImplData(device)->scanPhaseUs; + parameters->retraceScaleUsPerVolt = + GetImplData(device)->retraceScaleUsPerVolt; for (int i = 0; i < 4; ++i) parameters->xformMatrix[i] = GetImplData(device)->xformMatrix[i]; parameters->xformOffsetX = GetImplData(device)->xformOffsetX; @@ -67,6 +168,12 @@ void SetWaveformParamsFromDevice(OScDev_Device *device, parameters->yPark = GetImplData(device)->yPark; parameters->prevXParkVoltage = GetImplData(device)->prevXParkVoltage; parameters->prevYParkVoltage = GetImplData(device)->prevYParkVoltage; + parameters->laserBlankingSupported = LaserBlankingSupported(device); + parameters->laserManualOn = GetImplData(device)->laserManualOn; + parameters->laserOnVoltage = GetImplData(device)->laserOnVoltage; + parameters->laserOffVoltage = GetImplData(device)->laserOffVoltage; + parameters->laserOnLeadUs = GetImplData(device)->laserOnLeadUs; + parameters->laserOnLagUs = GetImplData(device)->laserOnLagUs; } OScDev_RichError *EnumerateAIPhysChans(OScDev_Device *device) { @@ -118,3 +225,38 @@ int GetNumberOfAIPhysChans(OScDev_Device *device) { } return MAX_PHYSICAL_CHANS; } + +OScDev_RichError *EnumerateAOPhysChans(OScDev_Device *device) { + ss8str *dest = &GetImplData(device)->aoPhysChans; + ss8_set_len(dest, 1024); + ss8_set_front(dest, '\0'); + int32 nierr = DAQmxGetDevAOPhysicalChans( + ss8_cstr(&GetImplData(device)->deviceName), ss8_mutable_cstr(dest), + (uInt32)ss8_len(dest)); + ss8_set_len_to_cstrlen(dest); + ss8_shrink_to_fit(dest); + if (nierr < 0) + return CreateDAQmxError(nierr); + if (ss8_is_empty(dest)) + return OScDev_Error_Create("Device has no AO physical channels"); + return OScDev_RichError_OK; +} + +int GetNumberOfAOPhysChans(OScDev_Device *device) { + const ss8str *chans = &GetImplData(device)->aoPhysChans; + if (ss8_is_empty(chans)) + return 0; + int count = 1; + for (size_t p = ss8_find_ch(chans, 0, ','); p != SIZE_MAX; + p = ss8_find_ch(chans, p + 1, ',')) + ++count; + return count; +} + +bool LaserBlankingSupported(OScDev_Device *device) { + return GetNumberOfAOPhysChans(device) >= 3; +} + +int GetNumberOfScannerAOChannels(OScDev_Device *device) { + return LaserBlankingSupported(device) ? 3 : 2; +} diff --git a/src/DAQConfig.h b/src/DAQConfig.h index 0760a15..0d060e4 100644 --- a/src/DAQConfig.h +++ b/src/DAQConfig.h @@ -6,6 +6,11 @@ #include #include +#include + +bool ComputeAORateHz(double pixelRateHz, double timebaseHz, double aoMaxHz, + double *aoRateHz); +OScDev_RichError *EnsureTimingCapsQueried(OScDev_Device *device); void SetWaveformParamsFromDevice(OScDev_Device *device, struct WaveformParams *parameters, OScDev_Acquisition *acq); @@ -13,3 +18,8 @@ OScDev_RichError *EnumerateAIPhysChans(OScDev_Device *device); void GetEnabledChannels(OScDev_Device *device, ss8str *chans); int GetNumberOfEnabledChannels(OScDev_Device *device); int GetNumberOfAIPhysChans(OScDev_Device *device); + +OScDev_RichError *EnumerateAOPhysChans(OScDev_Device *device); +int GetNumberOfAOPhysChans(OScDev_Device *device); +bool LaserBlankingSupported(OScDev_Device *device); +int GetNumberOfScannerAOChannels(OScDev_Device *device); diff --git a/src/DeviceImplData.c b/src/DeviceImplData.c index d8b2085..4c14bf2 100644 --- a/src/DeviceImplData.c +++ b/src/DeviceImplData.c @@ -10,7 +10,10 @@ void InitializeImplData(struct DeviceImplData *data) { memset(data, 0, sizeof(*data)); ss8_init(&data->deviceName); - data->lineDelay = 50; + data->aoRateHz = 200000.0; + data->undershootUs = 250.0; + data->scanPhaseUs = 0.0; + data->retraceScaleUsPerVolt = 640.0; data->xformMatrix[0] = 1.0; data->xformMatrix[1] = 0.0; data->xformMatrix[2] = 0.0; @@ -24,6 +27,13 @@ void InitializeImplData(struct DeviceImplData *data) { ss8_init(&data->aiPhysChans); data->channelEnabled[0] = true; + ss8_init(&data->aoPhysChans); + data->laserManualOn = false; + data->laserOnVoltage = 0.1; // Avoid high default to reduce danger + data->laserOffVoltage = 0.0; + data->laserOnLeadUs = 5.0; + data->laserOnLagUs = 5.0; + InitializeCriticalSection(&data->frameMutex); InitializeConditionVariable(&data->frameReady); diff --git a/src/DeviceImplData.h b/src/DeviceImplData.h index 833a489..84a0678 100644 --- a/src/DeviceImplData.h +++ b/src/DeviceImplData.h @@ -36,10 +36,14 @@ struct DeviceImplData { bool scannerOnly; - // counted as number of pixels. - // to adjust for the lag between the mirror control signal and the actual - // position of the mirror scan phase (uSec) = line delay / scan rate - uint32_t lineDelay; + bool timingCapsQueried; // guards the one-time query + double sampClkTimebaseHz; // from DAQmxGetSampClkTimebaseRate + double aoMaxRateHz; // from DAQmxGetDevAOMaxRate + + double aoRateHz; // Derived from pixel rate; overwritten per acquisition + double undershootUs; + double scanPhaseUs; + double retraceScaleUsPerVolt; int32_t xPark; int32_t yPark; @@ -60,6 +64,13 @@ struct DeviceImplData { ss8str aiPhysChans; // at least numAIPhysChans elements separated by ", " bool channelEnabled[MAX_PHYSICAL_CHANS]; + ss8str aoPhysChans; // analogous to aiPhysChans + bool laserManualOn; // "Laser On (Disable Blanking)" + double laserOnVoltage; + double laserOffVoltage; + double laserOnLeadUs; + double laserOnLagUs; + // Read, but unprocessed, raw samples; channels interleaved // Leftover data from the previous read, if any, is at the start of the // buffer and consists of rawDataSize samples. diff --git a/src/OpenScanDevice.c b/src/OpenScanDevice.c index bd96983..dbe05d1 100644 --- a/src/OpenScanDevice.c +++ b/src/OpenScanDevice.c @@ -133,15 +133,22 @@ static OScDev_Error NIDAQHasDetector(OScDev_Device *device, static OScDev_Error NIDAQGetPixelRates(OScDev_Device *device, OScDev_NumRange **pixelRatesHz) { - (void)device; // Unused static const double ratesMHz[] = { 0.0500, 0.1000, 0.1250, 0.2000, 0.2500, 0.4000, 0.5000, 0.6250, 1.0000, 1.2500, 0.0 // End mark }; + OScDev_RichError *err = EnsureTimingCapsQueried(device); + if (err) + return OScDev_Error_ReturnAsCode(err); + double timebaseHz = GetImplData(device)->sampClkTimebaseHz; + double aoMaxHz = GetImplData(device)->aoMaxRateHz; *pixelRatesHz = OScDev_NumRange_CreateDiscrete(); for (size_t i = 0; ratesMHz[i] != 0.0; ++i) { - OScDev_NumRange_AppendDiscrete(*pixelRatesHz, 1e6 * ratesMHz[i]); + double rate = 1e6 * ratesMHz[i]; + double aoRate; + if (ComputeAORateHz(rate, timebaseHz, aoMaxHz, &aoRate)) + OScDev_NumRange_AppendDiscrete(*pixelRatesHz, rate); } return OScDev_OK; } diff --git a/src/OpenScanSettings.c b/src/OpenScanSettings.c index 378763f..2280f8f 100644 --- a/src/OpenScanSettings.c +++ b/src/OpenScanSettings.c @@ -33,36 +33,199 @@ GetNumericConstraintTypeImpl_Range(OScDev_Setting *setting, return OScDev_OK; } -static OScDev_Error GetLineDelay(OScDev_Setting *setting, int32_t *value) { - *value = GetSettingDeviceData(setting)->lineDelay; +static OScDev_Error GetUndershoot(OScDev_Setting *setting, double *value) { + *value = GetSettingDeviceData(setting)->undershootUs; + return OScDev_OK; +} + +static OScDev_Error SetUndershoot(OScDev_Setting *setting, double value) { + GetSettingDeviceData(setting)->undershootUs = value; + GetSettingDeviceData(setting)->clockConfig.mustReconfigureTiming = true; + GetSettingDeviceData(setting)->scannerConfig.mustReconfigureTiming = true; + GetSettingDeviceData(setting)->clockConfig.mustRewriteOutput = true; + GetSettingDeviceData(setting)->scannerConfig.mustRewriteOutput = true; + return OScDev_OK; +} +static OScDev_Error GetUndershootRange(OScDev_Setting *setting, double *min, + double *max) { + (void)setting; + *min = 0.0; + *max = 5000.0; return OScDev_OK; } -static OScDev_Error SetLineDelay(OScDev_Setting *setting, int32_t value) { - GetSettingDeviceData(setting)->lineDelay = value; +static OScDev_SettingImpl SettingImpl_Undershoot = { + .GetFloat64 = GetUndershoot, + .SetFloat64 = SetUndershoot, + .GetNumericConstraintType = GetNumericConstraintTypeImpl_Range, + .GetFloat64Range = GetUndershootRange, +}; + +static OScDev_Error GetScanPhase(OScDev_Setting *setting, double *value) { + *value = GetSettingDeviceData(setting)->scanPhaseUs; + return OScDev_OK; +} +static OScDev_Error SetScanPhase(OScDev_Setting *setting, double value) { + GetSettingDeviceData(setting)->scanPhaseUs = value; GetSettingDeviceData(setting)->clockConfig.mustReconfigureTiming = true; GetSettingDeviceData(setting)->scannerConfig.mustReconfigureTiming = true; GetSettingDeviceData(setting)->clockConfig.mustRewriteOutput = true; GetSettingDeviceData(setting)->scannerConfig.mustRewriteOutput = true; + return OScDev_OK; +} +static OScDev_Error GetScanPhaseRange(OScDev_Setting *setting, double *min, + double *max) { + (void)setting; + *min = 0.0; + *max = 1000.0; return OScDev_OK; } -static OScDev_Error GetLineDelayRange(OScDev_Setting *setting, int32_t *min, - int32_t *max) { - (void)setting; // Unused - *min = 1; - *max = 200; +static OScDev_SettingImpl SettingImpl_ScanPhase = { + .GetFloat64 = GetScanPhase, + .SetFloat64 = SetScanPhase, + .GetNumericConstraintType = GetNumericConstraintTypeImpl_Range, + .GetFloat64Range = GetScanPhaseRange, +}; + +static OScDev_Error GetRetraceScale(OScDev_Setting *setting, double *value) { + *value = GetSettingDeviceData(setting)->retraceScaleUsPerVolt; + return OScDev_OK; +} + +static OScDev_Error SetRetraceScale(OScDev_Setting *setting, double value) { + GetSettingDeviceData(setting)->retraceScaleUsPerVolt = value; + GetSettingDeviceData(setting)->clockConfig.mustReconfigureTiming = true; + GetSettingDeviceData(setting)->scannerConfig.mustReconfigureTiming = true; + GetSettingDeviceData(setting)->clockConfig.mustRewriteOutput = true; + GetSettingDeviceData(setting)->scannerConfig.mustRewriteOutput = true; + return OScDev_OK; +} + +static OScDev_Error GetRetraceScaleRange(OScDev_Setting *setting, double *min, + double *max) { + (void)setting; + *min = 100.0; + *max = 10000.0; + return OScDev_OK; +} + +static OScDev_SettingImpl SettingImpl_RetraceScale = { + .GetFloat64 = GetRetraceScale, + .SetFloat64 = SetRetraceScale, + .GetNumericConstraintType = GetNumericConstraintTypeImpl_Range, + .GetFloat64Range = GetRetraceScaleRange, +}; + +static OScDev_Error GetLaserOnVoltage(OScDev_Setting *setting, double *value) { + *value = GetSettingDeviceData(setting)->laserOnVoltage; + return OScDev_OK; +} + +static OScDev_Error SetLaserOnVoltage(OScDev_Setting *setting, double value) { + GetSettingDeviceData(setting)->laserOnVoltage = value; + GetSettingDeviceData(setting)->scannerConfig.mustRewriteOutput = true; + OScDev_Device *device = OScDev_Setting_GetImplData(setting); + return OScDev_Error_ReturnAsCode(ApplyIdleLaserOutput(device)); +} + +static OScDev_Error GetLaserVoltageRange(OScDev_Setting *setting, double *min, + double *max) { + (void)setting; + *min = -1.0; + *max = 1.0; + return OScDev_OK; +} + +static OScDev_SettingImpl SettingImpl_LaserOnVoltage = { + .GetFloat64 = GetLaserOnVoltage, + .SetFloat64 = SetLaserOnVoltage, + .GetNumericConstraintType = GetNumericConstraintTypeImpl_Range, + .GetFloat64Range = GetLaserVoltageRange, +}; + +static OScDev_Error GetLaserOffVoltage(OScDev_Setting *setting, + double *value) { + *value = GetSettingDeviceData(setting)->laserOffVoltage; + return OScDev_OK; +} + +static OScDev_Error SetLaserOffVoltage(OScDev_Setting *setting, double value) { + GetSettingDeviceData(setting)->laserOffVoltage = value; + GetSettingDeviceData(setting)->scannerConfig.mustRewriteOutput = true; + OScDev_Device *device = OScDev_Setting_GetImplData(setting); + return OScDev_Error_ReturnAsCode(ApplyIdleLaserOutput(device)); +} + +static OScDev_SettingImpl SettingImpl_LaserOffVoltage = { + .GetFloat64 = GetLaserOffVoltage, + .SetFloat64 = SetLaserOffVoltage, + .GetNumericConstraintType = GetNumericConstraintTypeImpl_Range, + .GetFloat64Range = GetLaserVoltageRange, +}; + +static OScDev_Error GetLaserManualOn(OScDev_Setting *setting, bool *value) { + *value = GetSettingDeviceData(setting)->laserManualOn; + return OScDev_OK; +} + +static OScDev_Error SetLaserManualOn(OScDev_Setting *setting, bool value) { + GetSettingDeviceData(setting)->laserManualOn = value; + GetSettingDeviceData(setting)->scannerConfig.mustRewriteOutput = true; + OScDev_Device *device = OScDev_Setting_GetImplData(setting); + return OScDev_Error_ReturnAsCode(ApplyIdleLaserOutput(device)); +} + +static OScDev_SettingImpl SettingImpl_LaserManualOn = { + .GetBool = GetLaserManualOn, + .SetBool = SetLaserManualOn, +}; + +static OScDev_Error GetLaserOnLead(OScDev_Setting *setting, double *value) { + *value = GetSettingDeviceData(setting)->laserOnLeadUs; + return OScDev_OK; +} + +static OScDev_Error SetLaserOnLead(OScDev_Setting *setting, double value) { + GetSettingDeviceData(setting)->laserOnLeadUs = value; + GetSettingDeviceData(setting)->scannerConfig.mustRewriteOutput = true; + return OScDev_OK; +} + +static OScDev_Error GetLaserLeadLagRange(OScDev_Setting *setting, double *min, + double *max) { + (void)setting; + *min = 0.0; + *max = 100.0; + return OScDev_OK; +} + +static OScDev_SettingImpl SettingImpl_LaserOnLead = { + .GetFloat64 = GetLaserOnLead, + .SetFloat64 = SetLaserOnLead, + .GetNumericConstraintType = GetNumericConstraintTypeImpl_Range, + .GetFloat64Range = GetLaserLeadLagRange, +}; + +static OScDev_Error GetLaserOnLag(OScDev_Setting *setting, double *value) { + *value = GetSettingDeviceData(setting)->laserOnLagUs; + return OScDev_OK; +} + +static OScDev_Error SetLaserOnLag(OScDev_Setting *setting, double value) { + GetSettingDeviceData(setting)->laserOnLagUs = value; + GetSettingDeviceData(setting)->scannerConfig.mustRewriteOutput = true; return OScDev_OK; } -static OScDev_SettingImpl SettingImpl_LineDelay = { - .GetInt32 = GetLineDelay, - .SetInt32 = SetLineDelay, +static OScDev_SettingImpl SettingImpl_LaserOnLag = { + .GetFloat64 = GetLaserOnLag, + .SetFloat64 = SetLaserOnLag, .GetNumericConstraintType = GetNumericConstraintTypeImpl_Range, - .GetInt32Range = GetLineDelayRange, + .GetFloat64Range = GetLaserLeadLagRange, }; static OScDev_Error GetParkingPositionX(OScDev_Setting *setting, @@ -270,15 +433,35 @@ OScDev_Error NIDAQMakeSettings(OScDev_Device *device, if (err) return OScDev_Error_ReturnAsCode(err); + err = EnumerateAOPhysChans(device); + if (err) + return OScDev_Error_ReturnAsCode(err); + *settings = OScDev_PtrArray_Create(); - OScDev_Setting *lineDelay; + OScDev_Setting *undershoot; + err = OScDev_Error_AsRichError(OScDev_Setting_Create( + &undershoot, "Undershoot (us)", OScDev_ValueType_Float64, + &SettingImpl_Undershoot, device)); + if (err) + goto error; + OScDev_PtrArray_Append(*settings, undershoot); + + OScDev_Setting *scanPhase; err = OScDev_Error_AsRichError(OScDev_Setting_Create( - &lineDelay, "Line Delay (pixels)", OScDev_ValueType_Int32, - &SettingImpl_LineDelay, device)); + &scanPhase, "Scan Phase (us)", OScDev_ValueType_Float64, + &SettingImpl_ScanPhase, device)); if (err) goto error; - OScDev_PtrArray_Append(*settings, lineDelay); + OScDev_PtrArray_Append(*settings, scanPhase); + + OScDev_Setting *retraceScale; + err = OScDev_Error_AsRichError(OScDev_Setting_Create( + &retraceScale, "Retrace Scale (us/V)", OScDev_ValueType_Float64, + &SettingImpl_RetraceScale, device)); + if (err) + goto error; + OScDev_PtrArray_Append(*settings, retraceScale); OScDev_Setting *parkingPositionX; err = OScDev_Error_AsRichError(OScDev_Setting_Create( @@ -353,6 +536,48 @@ OScDev_Error NIDAQMakeSettings(OScDev_Device *device, goto error; OScDev_PtrArray_Append(*settings, inputVoltageRange); + if (LaserBlankingSupported(device)) { + OScDev_Setting *laserOnVoltage; + err = OScDev_Error_AsRichError(OScDev_Setting_Create( + &laserOnVoltage, "Laser On Voltage (V)", OScDev_ValueType_Float64, + &SettingImpl_LaserOnVoltage, device)); + if (err) + goto error; + OScDev_PtrArray_Append(*settings, laserOnVoltage); + + OScDev_Setting *laserOffVoltage; + err = OScDev_Error_AsRichError(OScDev_Setting_Create( + &laserOffVoltage, "Laser Off Voltage (V)", + OScDev_ValueType_Float64, &SettingImpl_LaserOffVoltage, device)); + if (err) + goto error; + OScDev_PtrArray_Append(*settings, laserOffVoltage); + + OScDev_Setting *laserManualOn; + err = OScDev_Error_AsRichError(OScDev_Setting_Create( + &laserManualOn, "Laser On (Disable Blanking)", + OScDev_ValueType_Bool, &SettingImpl_LaserManualOn, device)); + if (err) + goto error; + OScDev_PtrArray_Append(*settings, laserManualOn); + + OScDev_Setting *laserOnLead; + err = OScDev_Error_AsRichError(OScDev_Setting_Create( + &laserOnLead, "Laser On Lead (us)", OScDev_ValueType_Float64, + &SettingImpl_LaserOnLead, device)); + if (err) + goto error; + OScDev_PtrArray_Append(*settings, laserOnLead); + + OScDev_Setting *laserOnLag; + err = OScDev_Error_AsRichError(OScDev_Setting_Create( + &laserOnLag, "Laser On Lag (us)", OScDev_ValueType_Float64, + &SettingImpl_LaserOnLag, device)); + if (err) + goto error; + OScDev_PtrArray_Append(*settings, laserOnLag); + } + return OScDev_OK; error: diff --git a/src/ParkUnpark.c b/src/ParkUnpark.c index cf0b3d4..078ece4 100644 --- a/src/ParkUnpark.c +++ b/src/ParkUnpark.c @@ -17,15 +17,15 @@ OScDev_RichError *ConfigureUnparkTiming(OScDev_Device *device, struct ScannerConfig *config, OScDev_Acquisition *acq) { OScDev_RichError *err; - double pixelRateHz = OScDev_Acquisition_GetPixelRate(acq); + double aoRateHz = GetImplData(device)->aoRateHz; struct WaveformParams params; SetWaveformParamsFromDevice(device, ¶ms, acq); int32 totalElementsPerFramePerChan = GetParkWaveformSize(¶ms); err = CreateDAQmxError(DAQmxCfgSampClkTiming( - config->aoTask, "", pixelRateHz, DAQmx_Val_Rising, - DAQmx_Val_FiniteSamps, totalElementsPerFramePerChan)); + config->aoTask, "", aoRateHz, DAQmx_Val_Rising, DAQmx_Val_FiniteSamps, + totalElementsPerFramePerChan)); if (err) { err = OScDev_Error_Wrap(err, "Failed to configure timing for unpark"); return err; @@ -38,15 +38,15 @@ OScDev_RichError *ConfigureParkTiming(OScDev_Device *device, struct ScannerConfig *config, OScDev_Acquisition *acq) { OScDev_RichError *err; - double pixelRateHz = OScDev_Acquisition_GetPixelRate(acq); + double aoRateHz = GetImplData(device)->aoRateHz; struct WaveformParams params; SetWaveformParamsFromDevice(device, ¶ms, acq); int32 totalElementsPerFramePerChan = GetParkWaveformSize(¶ms); err = CreateDAQmxError(DAQmxCfgSampClkTiming( - config->aoTask, "", pixelRateHz, DAQmx_Val_Rising, - DAQmx_Val_FiniteSamps, totalElementsPerFramePerChan)); + config->aoTask, "", aoRateHz, DAQmx_Val_Rising, DAQmx_Val_FiniteSamps, + totalElementsPerFramePerChan)); if (err) { err = OScDev_Error_Wrap(err, "Failed to configure timing for park"); return err; @@ -61,11 +61,16 @@ OScDev_RichError *WriteUnparkOutput(OScDev_Device *device, struct WaveformParams params; SetWaveformParamsFromDevice(device, ¶ms, acq); + int nch = GetNumberOfScannerAOChannels(device); int32 totalElementsPerFramePerChan = GetParkWaveformSize(¶ms); double *xyWaveformFrame = - (double *)malloc(sizeof(double) * totalElementsPerFramePerChan * 2); + (double *)malloc(sizeof(double) * totalElementsPerFramePerChan * nch); GenerateGalvoUnparkWaveform(¶ms, xyWaveformFrame); + if (nch == 3) + GenerateLaserBlankingConstant(¶ms, totalElementsPerFramePerChan, + xyWaveformFrame + + 2 * totalElementsPerFramePerChan); int32 numWritten = 0; OScDev_RichError *err = CreateDAQmxError(DAQmxWriteAnalogF64( @@ -91,11 +96,16 @@ OScDev_RichError *WriteParkOutput(OScDev_Device *device, struct WaveformParams params; SetWaveformParamsFromDevice(device, ¶ms, acq); + int nch = GetNumberOfScannerAOChannels(device); int32 totalElementsPerFramePerChan = GetParkWaveformSize(¶ms); double *xyWaveformFrame = - (double *)malloc(sizeof(double) * totalElementsPerFramePerChan * 2); + (double *)malloc(sizeof(double) * totalElementsPerFramePerChan * nch); GenerateGalvoParkWaveform(¶ms, xyWaveformFrame); + if (nch == 3) + GenerateLaserBlankingConstant(¶ms, totalElementsPerFramePerChan, + xyWaveformFrame + + 2 * totalElementsPerFramePerChan); GetImplData(device)->prevXParkVoltage = xyWaveformFrame[totalElementsPerFramePerChan - 1]; GetImplData(device)->prevYParkVoltage = @@ -123,13 +133,12 @@ OScDev_RichError *GenerateUnparkOutput(OScDev_Device *device, struct ScannerConfig *config, OScDev_Acquisition *acq) { OScDev_RichError *err; - double pixelRateHz = OScDev_Acquisition_GetPixelRate(acq); + double aoRateHz = GetImplData(device)->aoRateHz; struct WaveformParams params; SetWaveformParamsFromDevice(device, ¶ms, acq); uint32_t totalElementsPerFramePerChan = GetParkWaveformSize(¶ms); - // changed from 1e3 to 1e4 - works but does not reconfigure timing uint32_t estFrameTimeMs = - (uint32_t)(1e3 * totalElementsPerFramePerChan / pixelRateHz); + (uint32_t)(1e3 * totalElementsPerFramePerChan / aoRateHz); uint32_t maxWaitTimeMs = 2 * estFrameTimeMs; if (maxWaitTimeMs < 1000) { maxWaitTimeMs = 1000; @@ -168,12 +177,12 @@ OScDev_RichError *GenerateParkOutput(OScDev_Device *device, struct ScannerConfig *config, OScDev_Acquisition *acq) { OScDev_RichError *err; - double pixelRateHz = OScDev_Acquisition_GetPixelRate(acq); + double aoRateHz = GetImplData(device)->aoRateHz; struct WaveformParams params; SetWaveformParamsFromDevice(device, ¶ms, acq); uint32_t totalElementsPerFramePerChan = GetParkWaveformSize(¶ms); uint32_t estFrameTimeMs = - (uint32_t)(1e3 * totalElementsPerFramePerChan / pixelRateHz); + (uint32_t)(1e3 * totalElementsPerFramePerChan / aoRateHz); uint32_t maxWaitTimeMs = 2 * estFrameTimeMs; if (maxWaitTimeMs < 1000) { maxWaitTimeMs = 1000; diff --git a/src/Scanner.c b/src/Scanner.c index 907554f..9799aa6 100644 --- a/src/Scanner.c +++ b/src/Scanner.c @@ -13,11 +13,19 @@ #include #include +// Append the scanner AO channel range ("/ao0:1" or "/ao0:2") to +// terms +static void AppendScannerAOTerms(OScDev_Device *device, ss8str *terms) { + ss8_cat(terms, &GetImplData(device)->deviceName); + int nch = GetNumberOfScannerAOChannels(device); + ss8_cat_cstr(terms, nch >= 3 ? "/ao0:2" : "/ao0:1"); +} + static OScDev_RichError *ConfigureScannerTiming(OScDev_Device *device, struct ScannerConfig *config, OScDev_Acquisition *acq) { OScDev_RichError *err; - double pixelRateHz = OScDev_Acquisition_GetPixelRate(acq); + double aoRateHz = GetImplData(device)->aoRateHz; struct WaveformParams params; SetWaveformParamsFromDevice(device, ¶ms, acq); @@ -39,9 +47,9 @@ static OScDev_RichError *ConfigureScannerTiming(OScDev_Device *device, samplesPerChan = totalSamples; } - err = CreateDAQmxError(DAQmxCfgSampClkTiming(config->aoTask, "", - pixelRateHz, DAQmx_Val_Rising, - sampleMode, samplesPerChan)); + err = CreateDAQmxError(DAQmxCfgSampClkTiming(config->aoTask, "", aoRateHz, + DAQmx_Val_Rising, sampleMode, + samplesPerChan)); if (err) { err = OScDev_Error_Wrap(err, "Failed to configure timing for scanner"); return err; @@ -63,11 +71,15 @@ static OScDev_RichError *WriteScannerOutput(OScDev_Device *device, struct WaveformParams params; SetWaveformParamsFromDevice(device, ¶ms, acq); + int nch = GetNumberOfScannerAOChannels(device); int32 totalElementsPerFramePerChan = GetScannerWaveformSize(¶ms); double *xyWaveformFrame = - (double *)malloc(sizeof(double) * totalElementsPerFramePerChan * 2); + (double *)malloc(sizeof(double) * totalElementsPerFramePerChan * nch); GenerateGalvoWaveformFrame(¶ms, xyWaveformFrame); + if (nch == 3) + GenerateLaserBlankingWaveform( + ¶ms, xyWaveformFrame + 2 * totalElementsPerFramePerChan); int32 numWritten = 0; OScDev_RichError *err = CreateDAQmxError(DAQmxWriteAnalogF64( @@ -102,8 +114,8 @@ OScDev_RichError *SetUpScanner(OScDev_Device *device, } ss8str aoTerms; - ss8_init_copy(&aoTerms, &GetImplData(device)->deviceName); - ss8_cat_cstr(&aoTerms, "/ao0:1"); + ss8_init(&aoTerms); + AppendScannerAOTerms(device, &aoTerms); err = CreateDAQmxError(DAQmxCreateAOVoltageChan( config->aoTask, ss8_cstr(&aoTerms), "Galvos", -10.0, 10.0, DAQmx_Val_Volts, NULL)); @@ -196,8 +208,8 @@ OScDev_RichError *CreateScannerTask(OScDev_Device *device, } ss8str aoTerms; - ss8_init_copy(&aoTerms, &GetImplData(device)->deviceName); - ss8_cat_cstr(&aoTerms, "/ao0:1"); + ss8_init(&aoTerms); + AppendScannerAOTerms(device, &aoTerms); err = CreateDAQmxError(DAQmxCreateAOVoltageChan( config->aoTask, ss8_cstr(&aoTerms), "Galvos", -10.0, 10.0, DAQmx_Val_Volts, NULL)); @@ -213,3 +225,59 @@ OScDev_RichError *CreateScannerTask(OScDev_Device *device, } return OScDev_RichError_OK; } + +OScDev_RichError *ApplyIdleLaserOutput(OScDev_Device *device) { + if (!LaserBlankingSupported(device)) + return OScDev_RichError_OK; + + CRITICAL_SECTION *mutex = &GetImplData(device)->acquisition.mutex; + EnterCriticalSection(mutex); + if (GetImplData(device)->acquisition.running) { + LeaveCriticalSection(mutex); + return OScDev_RichError_OK; + } + + struct ScannerConfig *config = &GetImplData(device)->scannerConfig; + OScDev_RichError *err; + + // Recreate the task to drop any leftover sample-clock timing from a prior + // scan; the on-demand task has no timing configured. + err = ShutdownScanner(config); + if (err) { + err = OScDev_Error_Wrap(err, "Failed to reset scanner task for idle " + "laser output"); + goto cleanup; + } + err = CreateScannerTask(device, config); + if (err) + goto cleanup; + + float64 buf[3] = { + GetImplData(device)->prevXParkVoltage, + GetImplData(device)->prevYParkVoltage, + GetImplData(device)->laserManualOn + ? GetImplData(device)->laserOnVoltage + : GetImplData(device)->laserOffVoltage, + }; + + int32 numWritten = 0; + err = CreateDAQmxError(DAQmxWriteAnalogF64(config->aoTask, 1, TRUE, 10.0, + DAQmx_Val_GroupByChannel, buf, + &numWritten, NULL)); + if (err) { + err = OScDev_Error_Wrap(err, "Failed to write idle laser output"); + ShutdownScanner(config); + goto cleanup; + } + + err = CreateDAQmxError(DAQmxStopTask(config->aoTask)); + if (err) { + err = OScDev_Error_Wrap(err, "Failed to stop idle laser output task"); + ShutdownScanner(config); + goto cleanup; + } + +cleanup: + LeaveCriticalSection(mutex); + return err; +} diff --git a/src/Scanner.h b/src/Scanner.h index 2c5dbab..17893da 100644 --- a/src/Scanner.h +++ b/src/Scanner.h @@ -20,3 +20,8 @@ OScDev_RichError *StopScanner(struct ScannerConfig *config); OScDev_RichError *CreateScannerTask(OScDev_Device *device, struct ScannerConfig *config); + +// Drive a single static sample on ao0:2 (galvos held at last park voltages, +// EOM at its current manual/blank level) when no acquisition is running. No-op +// if blanking is unsupported or an acquisition is in progress. +OScDev_RichError *ApplyIdleLaserOutput(OScDev_Device *device); diff --git a/src/Waveform.c b/src/Waveform.c index 3f90c01..b8123af 100644 --- a/src/Waveform.c +++ b/src/Waveform.c @@ -1,11 +1,48 @@ #include "Waveform.h" +#include #include #include -// TODO We should probably scale the retrace length according to -// zoomFactor * width_or_height -static const uint32_t X_RETRACE_LEN = 128; +static const double MIN_RETRACE_US = 50.0; +static const double PARK_UNPARK_DURATION_US = 640.0; + +static double ScanAmplitude(const struct WaveformParams *params) { + return (double)params->width / (params->zoom * params->resolution); +} + +uint32_t UndershootSamples(const struct WaveformParams *params) { + return (uint32_t)round(params->undershootUs * 1e-6 * params->aoRateHz); +} + +uint32_t ScanSamples(const struct WaveformParams *params) { + return (uint32_t)round((double)params->width / params->pixelRateHz * + params->aoRateHz); +} + +uint32_t ScanPhaseSamples(const struct WaveformParams *params) { + return (uint32_t)round(params->scanPhaseUs * 1e-6 * params->aoRateHz); +} + +uint32_t LaserOnLeadSamples(const struct WaveformParams *params) { + return (uint32_t)round(params->laserOnLeadUs * 1e-6 * params->aoRateHz); +} + +uint32_t LaserOnLagSamples(const struct WaveformParams *params) { + return (uint32_t)round(params->laserOnLagUs * 1e-6 * params->aoRateHz); +} + +uint32_t RetraceSamples(const struct WaveformParams *params) { + double amplitude = ScanAmplitude(params); + double retraceUs = params->retraceScaleUsPerVolt * amplitude; + if (retraceUs < MIN_RETRACE_US) + retraceUs = MIN_RETRACE_US; + return (uint32_t)round(retraceUs * 1e-6 * params->aoRateHz); +} + +uint32_t ParkSamples(const struct WaveformParams *params) { + return (uint32_t)round(PARK_UNPARK_DURATION_US * 1e-6 * params->aoRateHz); +} // n = number of elements // slope in units of per element @@ -29,27 +66,26 @@ static void SplineInterpolate(int32_t n, double yFirst, double yLast, } } -// Generate 1D (undershoot + trace + retrace). -// The trace part spans voltage scanStart to scanEnd. +// Generate 1D (undershoot + scan + overshoot + retrace). +// The scan part spans voltage scanStart to scanEnd. +// overshootLen extends the linear ramp past scanEnd. static void GenerateXGalvoWaveform(int32_t effectiveScanLen, int32_t retraceLen, int32_t undershootLen, - double scanStart, double scanEnd, - double *waveform) { + int32_t overshootLen, double scanStart, + double scanEnd, double *waveform) { double scanAmplitude = scanEnd - scanStart; double step = scanAmplitude / effectiveScanLen; - int32_t linearLen = undershootLen + effectiveScanLen; + int32_t linearLen = undershootLen + effectiveScanLen + overshootLen; - // Generate the linear scan curve double undershootStart = scanStart - undershootLen * step; for (int i = 0; i < linearLen; ++i) { waveform[i] = undershootStart + step * i; } - // Generate the rescan curve - // Slope at start end end are both equal to the linear scan + double overshootEnd = scanEnd + overshootLen * step; if (retraceLen > 0) { - SplineInterpolate(retraceLen, scanEnd, undershootStart, step, step, - waveform + linearLen); + SplineInterpolate(retraceLen, overshootEnd, undershootStart, step, + step, waveform + linearLen); } } @@ -57,17 +93,14 @@ static void GenerateXGalvoWaveform(int32_t effectiveScanLen, static void GenerateYGalvoWaveform(int32_t linesPerFrame, int32_t retraceLen, size_t xLength, double scanStart, double scanEnd, double *waveform) { - (void)retraceLen; // Unused - double scanAmplitude = scanEnd - scanStart; double step = scanAmplitude / linesPerFrame; - // Generate staircase for one frame for (int j = 0; j < linesPerFrame; ++j) { for (unsigned i = 0; i < xLength; ++i) { waveform[i + j * xLength] = scanStart + step * j; - // stop at last x retrace - if ((j >= linesPerFrame - 1) && (i >= xLength - X_RETRACE_LEN)) { + if ((j >= linesPerFrame - 1) && + (i >= xLength - (unsigned)retraceLen)) { break; } } @@ -77,128 +110,123 @@ static void GenerateYGalvoWaveform(int32_t linesPerFrame, int32_t retraceLen, for (int j = 0; j < linesPerFrame - 1; ++j) { double yThis = scanStart + step * j; double yNext = scanStart + step * (j + 1); - SplineInterpolate(X_RETRACE_LEN, yThis, yNext, 0, 0, - waveform + (j + 1) * xLength - X_RETRACE_LEN); + SplineInterpolate(retraceLen, yThis, yNext, 0, 0, + waveform + (j + 1) * xLength - retraceLen); } - // Generate the rescan curve at end of frame - if (X_RETRACE_LEN > 0) { + // Frame retrace at end + if (retraceLen > 0) { double lastLineY = scanStart + step * (linesPerFrame - 1); - SplineInterpolate(X_RETRACE_LEN, lastLineY, scanStart, 0, 0, - waveform + (linesPerFrame * xLength) - - X_RETRACE_LEN); + SplineInterpolate(retraceLen, lastLineY, scanStart, 0, 0, + waveform + (linesPerFrame * xLength) - retraceLen); } } -/* Line clock pattern for NI DAQ to output from one of its digital IOs */ void GenerateLineClock(const struct WaveformParams *parameters, uint8_t *lineClock) { - uint32_t lineDelay = parameters->undershoot; - uint32_t width = parameters->width; + uint32_t undershootSmp = UndershootSamples(parameters); + uint32_t scanPhaseSmp = ScanPhaseSamples(parameters); + uint32_t scanSmp = ScanSamples(parameters); uint32_t height = parameters->height; - uint32_t x_length = lineDelay + width + X_RETRACE_LEN; + uint32_t highStart = undershootSmp + scanPhaseSmp; + uint32_t x_length = (uint32_t)GetLineWaveformSize(parameters); for (uint32_t j = 0; j < height; j++) for (uint32_t i = 0; i < x_length; i++) lineClock[i + j * x_length] = - ((i >= lineDelay) && (i < lineDelay + width)) ? 1 : 0; + ((i >= highStart) && (i < highStart + scanSmp)) ? 1 : 0; } -// High voltage right after a line acquisition is done -// like a line clock of reversed polarity -// specially for B&H FLIM application void GenerateFLIMLineClock(const struct WaveformParams *parameters, uint8_t *lineClockFLIM) { - uint32_t lineDelay = parameters->undershoot; - uint32_t width = parameters->width; + uint32_t undershootSmp = UndershootSamples(parameters); + uint32_t scanPhaseSmp = ScanPhaseSamples(parameters); + uint32_t scanSmp = ScanSamples(parameters); uint32_t height = parameters->height; - uint32_t x_length = lineDelay + width + X_RETRACE_LEN; + uint32_t highStart = undershootSmp + scanPhaseSmp + scanSmp; + uint32_t x_length = (uint32_t)GetLineWaveformSize(parameters); for (uint32_t j = 0; j < height; j++) for (uint32_t i = 0; i < x_length; i++) - lineClockFLIM[i + j * x_length] = (i >= lineDelay + width) ? 1 : 0; + lineClockFLIM[i + j * x_length] = (i >= highStart) ? 1 : 0; } -// Frame clock for B&H FLIM -// High voltage at the end of the frame void GenerateFLIMFrameClock(const struct WaveformParams *parameters, uint8_t *frameClockFLIM) { - uint32_t lineDelay = parameters->undershoot; - uint32_t width = parameters->width; + uint32_t undershootSmp = UndershootSamples(parameters); + uint32_t scanPhaseSmp = ScanPhaseSamples(parameters); + uint32_t scanSmp = ScanSamples(parameters); uint32_t height = parameters->height; - uint32_t x_length = lineDelay + width + X_RETRACE_LEN; + uint32_t highStart = undershootSmp + scanPhaseSmp + scanSmp; + uint32_t x_length = (uint32_t)GetLineWaveformSize(parameters); for (uint32_t j = 0; j < height; ++j) for (uint32_t i = 0; i < x_length; ++i) frameClockFLIM[i + j * x_length] = - ((j == height - 1) && (i > lineDelay + width)) ? 1 : 0; + ((j == height - 1) && (i > highStart)) ? 1 : 0; } int32_t GetLineWaveformSize(const struct WaveformParams *parameters) { - return parameters->undershoot + parameters->width + X_RETRACE_LEN; + return (int32_t)(UndershootSamples(parameters) + ScanSamples(parameters) + + ScanPhaseSamples(parameters) + + RetraceSamples(parameters)); } int32_t GetClockWaveformSize(const struct WaveformParams *parameters) { - uint32_t elementsPerLine = GetLineWaveformSize(parameters); + uint32_t elementsPerLine = (uint32_t)GetLineWaveformSize(parameters); uint32_t height = parameters->height; - return elementsPerLine * height; + return (int32_t)(elementsPerLine * height); } int32_t GetScannerWaveformSize(const struct WaveformParams *parameters) { - uint32_t elementsPerLine = GetLineWaveformSize(parameters); + uint32_t elementsPerLine = (uint32_t)GetLineWaveformSize(parameters); uint32_t height = parameters->height; - uint32_t yLen = height; - return elementsPerLine * yLen; // including y retrace portion + return (int32_t)(elementsPerLine * height); } int32_t GetScannerWaveformSizeAfterLastPixel(const struct WaveformParams *parameters) { - (void)parameters; // Unused - return X_RETRACE_LEN; + return (int32_t)(ScanPhaseSamples(parameters) + + RetraceSamples(parameters)); } int32_t GetParkWaveformSize(const struct WaveformParams *parameters) { - (void)parameters; // Unused - uint32_t elementsPerLine = X_RETRACE_LEN; - return elementsPerLine; + return (int32_t)ParkSamples(parameters); } -/* -Generate X and Y waveforms in analog format (voltage) for a whole frame scan -Format: X|Y in a 1D array for NI DAQ to simultaneously output in two channels -Analog voltage range (-0.5V, 0.5V) at zoom 1 -Including Y retrace waveform that moves the slow galvo back to its starting -position -*/ void GenerateGalvoWaveformFrame(const struct WaveformParams *parameters, double *xyWaveformFrame) { - uint32_t pixelsPerLine = parameters->width; // ROI size + uint32_t pixelsPerLine = parameters->width; uint32_t linesPerFrame = parameters->height; uint32_t resolution = parameters->resolution; double zoom = parameters->zoom; - uint32_t undershoot = parameters->undershoot; - uint32_t xOffset = parameters->xOffset; // ROI offset + uint32_t xOffset = parameters->xOffset; uint32_t yOffset = parameters->yOffset; const double *m = parameters->xformMatrix; double tx = parameters->xformOffsetX; double ty = parameters->xformOffsetY; - // Voltage ranges of the ROI + uint32_t undershootSmp = UndershootSamples(parameters); + uint32_t scanSmp = ScanSamples(parameters); + uint32_t scanPhaseSmp = ScanPhaseSamples(parameters); + uint32_t retraceSmp = RetraceSamples(parameters); + double xStart = (-0.5 * resolution + xOffset) / (zoom * resolution); double yStart = (-0.5 * resolution + yOffset) / (zoom * resolution); - double xEnd = xStart + pixelsPerLine / (zoom * resolution); - double yEnd = yStart + linesPerFrame / (zoom * resolution); + double xEnd = xStart + (double)pixelsPerLine / (zoom * resolution); + double yEnd = yStart + (double)linesPerFrame / (zoom * resolution); - size_t xLength = undershoot + pixelsPerLine + X_RETRACE_LEN; + size_t xLength = undershootSmp + scanSmp + scanPhaseSmp + retraceSmp; size_t yLength = linesPerFrame; double *xWaveform = (double *)malloc(sizeof(double) * xLength); double *yWaveform = (double *)malloc(sizeof(double) * (yLength * xLength)); - GenerateXGalvoWaveform(pixelsPerLine, X_RETRACE_LEN, undershoot, xStart, - xEnd, xWaveform); - GenerateYGalvoWaveform(linesPerFrame, X_RETRACE_LEN, xLength, yStart, yEnd, - yWaveform); + GenerateXGalvoWaveform((int32_t)scanSmp, (int32_t)retraceSmp, + (int32_t)undershootSmp, (int32_t)scanPhaseSmp, + xStart, xEnd, xWaveform); + GenerateYGalvoWaveform((int32_t)linesPerFrame, (int32_t)retraceSmp, + xLength, yStart, yEnd, yWaveform); for (unsigned j = 0; j < yLength; ++j) { for (unsigned i = 0; i < xLength; ++i) { @@ -210,16 +238,47 @@ void GenerateGalvoWaveformFrame(const struct WaveformParams *parameters, } } - // TODO When we are scanning multiple frames, the Y retrace can be - // simultaneous with the last line's X retrace. (Spline interpolate - // with zero slope at each end of retrace.) - // TODO Simpler to use interleaved x,y format? - free(xWaveform); free(yWaveform); } -// Generate waveform from parking to start before one frame +void GenerateLaserBlankingWaveform(const struct WaveformParams *params, + double *blanking) { + uint32_t lineLength = (uint32_t)GetLineWaveformSize(params); + uint32_t height = params->height; + double onV = params->laserOnVoltage; + + if (params->laserManualOn) { + for (uint32_t i = 0; i < lineLength * height; ++i) + blanking[i] = onV; + return; + } + + double offV = params->laserOffVoltage; + uint32_t highStart = UndershootSamples(params) + ScanPhaseSamples(params); + uint32_t scanSmp = ScanSamples(params); + uint32_t leadSmp = LaserOnLeadSamples(params); + uint32_t lagSmp = LaserOnLagSamples(params); + + uint32_t onStart = highStart > leadSmp ? highStart - leadSmp : 0; + uint32_t onEnd = highStart + scanSmp + lagSmp; + if (onEnd > lineLength) + onEnd = lineLength; + + for (uint32_t j = 0; j < height; ++j) + for (uint32_t i = 0; i < lineLength; ++i) + blanking[i + j * lineLength] = + (i >= onStart && i < onEnd) ? onV : offV; +} + +void GenerateLaserBlankingConstant(const struct WaveformParams *params, + uint32_t length, double *out) { + double v = params->laserManualOn ? params->laserOnVoltage + : params->laserOffVoltage; + for (uint32_t i = 0; i < length; ++i) + out[i] = v; +} + static void InverseTransform2x2(const double *m, double tx, double ty, double ox, double oy, double *lx, double *ly) { double det = m[0] * m[3] - m[1] * m[2]; @@ -235,21 +294,23 @@ void GenerateGalvoUnparkWaveform(const struct WaveformParams *parameters, double zoom = parameters->zoom; uint32_t xOffset = parameters->xOffset; uint32_t yOffset = parameters->yOffset; - int32_t undershoot = parameters->undershoot; const double *m = parameters->xformMatrix; double tx = parameters->xformOffsetX; double ty = parameters->xformOffsetY; - // Inverse-transform previous park voltage to logical space + uint32_t undershootSmp = UndershootSamples(parameters); + uint32_t scanSmp = ScanSamples(parameters); + double step = ScanAmplitude(parameters) / scanSmp; + double xStart, yStart; InverseTransform2x2(m, tx, ty, parameters->prevXParkVoltage, parameters->prevYParkVoltage, &xStart, &yStart); - double xEnd = - (-0.5 * resolution + xOffset - undershoot) / (zoom * resolution); + double xEnd = (-0.5 * resolution + xOffset) / (zoom * resolution) - + undershootSmp * step; double yEnd = (-0.5 * resolution + yOffset) / (zoom * resolution); - size_t length = X_RETRACE_LEN; + size_t length = ParkSamples(parameters); double *xWaveform = (double *)malloc(sizeof(double) * length); double *yWaveform = (double *)malloc(sizeof(double) * length); @@ -267,27 +328,29 @@ void GenerateGalvoUnparkWaveform(const struct WaveformParams *parameters, free(yWaveform); } -// Generate waveform from start to parking after one frame void GenerateGalvoParkWaveform(const struct WaveformParams *parameters, double *xyWaveformFrame) { uint32_t resolution = parameters->resolution; double zoom = parameters->zoom; uint32_t xOffset = parameters->xOffset; uint32_t yOffset = parameters->yOffset; - int32_t undershoot = parameters->undershoot; int32_t xPark = parameters->xPark; int32_t yPark = parameters->yPark; const double *m = parameters->xformMatrix; double tx = parameters->xformOffsetX; double ty = parameters->xformOffsetY; - double xStart = - (-0.5 * resolution + xOffset - undershoot) / (zoom * resolution); + uint32_t undershootSmp = UndershootSamples(parameters); + uint32_t scanSmp = ScanSamples(parameters); + double step = ScanAmplitude(parameters) / scanSmp; + + double xStart = (-0.5 * resolution + xOffset) / (zoom * resolution) - + undershootSmp * step; double yStart = (-0.5 * resolution + yOffset) / (zoom * resolution); double xEnd = (-0.5 * resolution + xPark) / (zoom * resolution); double yEnd = (-0.5 * resolution + yPark) / (zoom * resolution); - size_t length = X_RETRACE_LEN; + size_t length = ParkSamples(parameters); double *xWaveform = (double *)malloc(sizeof(double) * length); double *yWaveform = (double *)malloc(sizeof(double) * length); diff --git a/src/Waveform.h b/src/Waveform.h index f2d961c..4fe54e2 100644 --- a/src/Waveform.h +++ b/src/Waveform.h @@ -1,15 +1,22 @@ #pragma once +#include #include struct WaveformParams { - uint32_t width; // PixelsPerLine + uint32_t width; // PixelsPerLine (ROI) uint32_t height; // numScanLines uint32_t resolution; double zoom; - uint32_t undershoot; // also LineDelay for clock waveforms uint32_t xOffset; uint32_t yOffset; + + double aoRateHz; + double pixelRateHz; + double undershootUs; + double scanPhaseUs; + double retraceScaleUsPerVolt; + double xformMatrix[4]; // {a, b, c, d} — row-major 2x2 double xformOffsetX; // tx (volts) double xformOffsetY; // ty (volts) @@ -17,8 +24,21 @@ struct WaveformParams { int32_t yPark; double prevXParkVoltage; double prevYParkVoltage; + + bool laserBlankingSupported; // device has >=3 AO + bool laserManualOn; // laser always on (blanking disabled) + double laserOnVoltage; + double laserOffVoltage; + double laserOnLeadUs; + double laserOnLagUs; }; +uint32_t UndershootSamples(const struct WaveformParams *params); +uint32_t ScanSamples(const struct WaveformParams *params); +uint32_t ScanPhaseSamples(const struct WaveformParams *params); +uint32_t RetraceSamples(const struct WaveformParams *params); +uint32_t ParkSamples(const struct WaveformParams *params); + void GenerateLineClock(const struct WaveformParams *parameters, uint8_t *lineClock); void GenerateFLIMLineClock(const struct WaveformParams *parameters, @@ -37,3 +57,10 @@ void GenerateGalvoUnparkWaveform(const struct WaveformParams *parameters, double *xyWaveformFrame); void GenerateGalvoParkWaveform(const struct WaveformParams *parameters, double *xyWaveformFrame); + +uint32_t LaserOnLeadSamples(const struct WaveformParams *params); +uint32_t LaserOnLagSamples(const struct WaveformParams *params); +void GenerateLaserBlankingWaveform(const struct WaveformParams *params, + double *blanking); +void GenerateLaserBlankingConstant(const struct WaveformParams *params, + uint32_t length, double *out);