diff --git a/__pycache__/device_commands.cpython-312.pyc b/__pycache__/device_commands.cpython-312.pyc index cb7767e..11be430 100644 Binary files a/__pycache__/device_commands.cpython-312.pyc and b/__pycache__/device_commands.cpython-312.pyc differ diff --git a/__pycache__/device_interaction.cpython-312.pyc b/__pycache__/device_interaction.cpython-312.pyc index ae848eb..1abc2f4 100644 Binary files a/__pycache__/device_interaction.cpython-312.pyc and b/__pycache__/device_interaction.cpython-312.pyc differ diff --git a/__pycache__/gui.cpython-312.pyc b/__pycache__/gui.cpython-312.pyc index 5b6048b..a11ad81 100644 Binary files a/__pycache__/gui.cpython-312.pyc and b/__pycache__/gui.cpython-312.pyc differ diff --git a/_device_main.py b/_device_main.py index 9d56c0c..e269164 100644 --- a/_device_main.py +++ b/_device_main.py @@ -3,6 +3,7 @@ import json import math import socket import subprocess +import time import device_interaction as dev @@ -23,6 +24,14 @@ INITIAL_TEMPERATURE_2 = 28.9 # Set initial temperature for Laser 2 in Celsius: f INITIAL_CURRENT_1 = 33 # 64.0879 max # Set initial current for Laser 1, in mA INITIAL_CURRENT_2 = 35 # 64.0879 max # Set initial current for Laser 2, in mA +AD9833_FREQ_DEFAULT_KHZ = 125.0 +AD9833_MCLK_DEFAULT_MHZ = 20.0 +DS1809_MAX_STEP = 63 +DS1809_DEFAULT_STEP = 0 +DS1809_INIT_HOME_PULSES = 64 +DS1809_INIT_PULSE_MS = 2 +DS1809_INIT_STARTUP_DELAY_S = 0.35 + #### ---- Functions def start_task(prt): @@ -73,6 +82,38 @@ def shorten(i): return "{:.2f}".format(round(i, 2)) +def clamp_int(value, min_value, max_value): + if value < min_value: + return min_value + if value > max_value: + return max_value + return value + + +def format_ds1809_status(step): + return f"{step}/{DS1809_MAX_STEP} шагов" + + +def initialize_ds1809_position(prt, default_step): + default_step = clamp_int(int(default_step), 0, DS1809_MAX_STEP) + + # Give the STM32 side a short startup margin before DS1809 pulse traffic. + time.sleep(DS1809_INIT_STARTUP_DELAY_S) + + dev.send_ds1809_pulse(prt, uc=False, dc=True, + count=DS1809_INIT_HOME_PULSES, + pulse_ms=DS1809_INIT_PULSE_MS) + current_step = 0 + + if default_step > 0: + dev.send_ds1809_pulse(prt, uc=True, dc=False, + count=default_step, + pulse_ms=DS1809_INIT_PULSE_MS) + current_step = default_step + + return current_step + + def set_initial_params(): params = {} params['Temp_1'] = INITIAL_TEMPERATURE_1 # Initial temperature for Laser 1 @@ -108,9 +149,11 @@ def set_initial_params(): params['RampSramMode'] = False params['RampSramSamples'] = '' params['RampSramAmp'] = '' - params['Ad9833Freq'] = '' - params['Ad9833Mclk'] = '25' + params['Ad9833Freq'] = str(AD9833_FREQ_DEFAULT_KHZ) + params['Ad9833Mclk'] = str(AD9833_MCLK_DEFAULT_MHZ) params['Ad9833Triangle'] = True + params['DS1809Step'] = DS1809_DEFAULT_STEP + params['DS1809Status'] = format_ds1809_status(DS1809_DEFAULT_STEP) return params def update_data_lists(): @@ -139,12 +182,29 @@ if __name__ == "__main__": # dev.request_state(prt) dev.send_control_parameters(prt, params) - saved_data.append(dev.request_data(prt)) - draw_data.append(saved_data[0]) - + window = gui.setup_gui(params) axes_signs = gui.sign_axes(window) - + + ds1809_step = initialize_ds1809_position(prt, params['DS1809Step']) + params['DS1809Step'] = ds1809_step + params['DS1809Status'] = format_ds1809_status(ds1809_step) + window['-DS1809Status-'].update(params['DS1809Status']) + + initial_data = None + for _ in range(20): + initial_data = dev.request_data(prt) + if isinstance(initial_data, dict): + break + time.sleep(0.05) + + if not isinstance(initial_data, dict): + print('Error: initial DATA packet not received. Closing program...') + exit(1) + + saved_data.append(initial_data) + draw_data.append(initial_data) + current_and_temperature_settings_available = True disableStartButton = False @@ -492,22 +552,33 @@ if __name__ == "__main__": sram_mode=sram_mode, sram_samples=sram_samples, sram_amplitude=sram_amplitude) elif event == '-StartRamp9833-': - freq_hz = parse_optional_float(values.get('-AD9833Freq-')) + freq_khz = parse_optional_float(values.get('-AD9833Freq-')) + freq_hz = int(round(freq_khz * 1000.0)) if freq_khz is not None else None mclk_mhz = parse_optional_float(values.get('-AD9833Mclk-')) mclk_hz = mclk_mhz * 1e6 if mclk_mhz is not None else None triangle = values.get('-AD9833Triangle-', True) dev.start_ad9833_ramp(prt, freq_hz=freq_hz, mclk_hz=mclk_hz, triangle=triangle, enable=True) elif event == '-DS1809UC-': - dev.send_ds1809_pulse(prt, uc=True, dc=False) + dev.send_ds1809_pulse(prt, uc=True, dc=False, count=1, pulse_ms=DS1809_INIT_PULSE_MS) + ds1809_step = clamp_int(ds1809_step + 1, 0, DS1809_MAX_STEP) + params['DS1809Step'] = ds1809_step + params['DS1809Status'] = format_ds1809_status(ds1809_step) + window['-DS1809Status-'].update(params['DS1809Status']) elif event == '-DS1809DC-': - dev.send_ds1809_pulse(prt, uc=False, dc=True) + dev.send_ds1809_pulse(prt, uc=False, dc=True, count=1, pulse_ms=DS1809_INIT_PULSE_MS) + ds1809_step = clamp_int(ds1809_step - 1, 0, DS1809_MAX_STEP) + params['DS1809Step'] = ds1809_step + params['DS1809Status'] = format_ds1809_status(ds1809_step) + window['-DS1809Status-'].update(params['DS1809Status']) elif event == '-StopCycle-': window['-StopCycle-'].update(disabled = True) current_and_temperature_settings_available = True stop_task(prt) elif event == TIMEOUT_KEY: data = dev.request_data(prt) - + if not isinstance(data, dict): + continue + update_data_lists() window['-TOUT_1-'].update(gui.READ_TEMPERATURE_TEXT+' 1: '+shorten(data['Temp_1'])+' C') diff --git a/device_commands.py b/device_commands.py index 7611d3e..14a0ffa 100644 --- a/device_commands.py +++ b/device_commands.py @@ -27,7 +27,7 @@ AD9102_SRAM_HOLD_DEFAULT = 1 AD9102_SRAM_AMP_DEFAULT = 8191 AD9833_FLAG_ENABLE = 0x0001 AD9833_FLAG_TRIANGLE = 0x0002 -AD9833_MCLK_HZ_DEFAULT = 25_000_000 +AD9833_MCLK_HZ_DEFAULT = 20_000_000 DS1809_FLAG_UC = 0x0001 DS1809_FLAG_DC = 0x0002 DS1809_PULSE_MS_DEFAULT = 2 @@ -222,13 +222,15 @@ def get_STATE(prt): def get_DATA(prt): - ''' Get decoded state of the device in byte format (426 bytes). + ''' Get decoded state of the device in byte format (30 bytes). ''' print("Received "+str(prt.inWaiting())+" bytes.\n") if prt.inWaiting()!=GET_DATA_TOTAL_LENGTH: print("Error. Couldn't get DATA data.") print("receiven data len:", prt.inWaiting()) + if prt.inWaiting() > 0: + print("Flushing input data:", prt.read(prt.inWaiting())) return None out_bytes = prt.read(GET_DATA_TOTAL_LENGTH) diff --git a/device_interaction.py b/device_interaction.py index f331aba..ceb721e 100644 --- a/device_interaction.py +++ b/device_interaction.py @@ -1,118 +1,96 @@ - - import time - from datetime import datetime + import device_commands as cmd #### ---- Constants -WAIT_AFTER_SEND = 0.15 # Wait after sending command, before requesting input (in seconds). +WAIT_AFTER_SEND = 0.15 # Wait after sending command before requesting input (s). #### ---- High-level port commands -''' -def create_port_connection(): - prt = None - for port, _, _ in sorted(cmd.list_ports.comports()): - try: - prt = cmd.setup_port_connection(port=port, baudrate=115200, timeout_sec=1) - cmd.open_port(prt) - reset_port_settings(prt) - except: - prt.close() - continue - break - return prt -''' + def create_port_connection(): prt = None print() - ports = [] - for port, _,_ in sorted(cmd.list_ports.comports()): - ports.append(port) -#ONLY FOR LINUX!!! - have_ttyUSB = False - USB_ports = [] - for port in ports: - if "USB" in port: - USB_ports.append(port) - if len(USB_ports): - ports = USB_ports -# print("ports:", ports) + ports = [port for port, _, _ in sorted(cmd.list_ports.comports())] + # Linux-only preference: use USB UART ports first. + usb_ports = [port for port in ports if "USB" in port] + if usb_ports: + ports = usb_ports -# for port, _, _ in sorted(cmd.list_ports.comports()): for port in ports: try: print("PORT:", port) prt = cmd.setup_port_connection(port=port, baudrate=115200, timeout_sec=1) cmd.open_port(prt) reset_port_settings(prt) - except: - prt.close() + return prt + except Exception: + if prt is not None: + try: + prt.close() + except Exception: + pass continue - break - return prt + + return None +def _print_state_reply(state_bytes): + if state_bytes is None: + return False - -# def setup_connection(): -# prt = cmd.setup_port_connection() -# cmd.open_port(prt) -# return prt + status = state_bytes.hex() + print("Received: STATE. State status:", cmd.decode_STATE(status), "(" + cmd.flipfour(status) + ")") + print("") + return True def reset_port_settings(prt): - # Reset port settings and check status cmd.send_DEFAULT_ENABLE(prt) time.sleep(WAIT_AFTER_SEND) - status = cmd.get_STATE(prt).hex() - if status is not None: - print("Received: STATE. State status:", cmd.decode_STATE(status), "("+cmd.flipfour(status)+")") - print("") + return _print_state_reply(cmd.get_STATE(prt)) def request_state(prt): - # Request data cmd.send_STATE(prt) time.sleep(WAIT_AFTER_SEND) - status = cmd.get_STATE(prt).hex() - if status is not None: - print("Received: STATE. State status:", cmd.decode_STATE(status), "("+cmd.flipfour(status)+")") - print("") + return _print_state_reply(cmd.get_STATE(prt)) def send_control_parameters(prt, params): - # Send control parameters hexstring = cmd.encode_Input(params) - cmd.send_DECODE_ENABLE(prt,hexstring) + cmd.send_DECODE_ENABLE(prt, hexstring) time.sleep(WAIT_AFTER_SEND) - status = cmd.get_STATE(prt).hex() - if status is not None: - print("Received: STATE. State status:", cmd.decode_STATE(status), "("+cmd.flipfour(status)+")") - print("") - else: - print("") + return _print_state_reply(cmd.get_STATE(prt)) + def send_task_command(prt, sending_param): # Send task command (TASK_ENABLE state in firmware) hexstring = cmd.create_TaskEnableCommand(sending_param) - cmd.send_TASK_ENABLE(prt,hexstring) + cmd.send_TASK_ENABLE(prt, hexstring) time.sleep(WAIT_AFTER_SEND) - status = cmd.get_STATE(prt).hex() - if status is not None: - print("Received: STATE. State status:", cmd.decode_STATE(status), "("+cmd.flipfour(status)+")") - print("") - else: - print("") + return _print_state_reply(cmd.get_STATE(prt)) - -def start_ramp_max(prt, freq_hz=None, duty=None, saw_step=None, pat_period=None, pat_period_base=None, dac_clk_hz=None, triangle=True, sram_mode=False, sram_samples=None, sram_hold=None, sram_amplitude=None): +def start_ramp_max( + prt, + freq_hz=None, + duty=None, + saw_step=None, + pat_period=None, + pat_period_base=None, + dac_clk_hz=None, + triangle=True, + sram_mode=False, + sram_samples=None, + sram_hold=None, + sram_amplitude=None, +): # Start AD9102 sawtooth with configurable frequency/duty or SRAM ramp mode if sram_mode: if sram_hold is None: @@ -121,9 +99,14 @@ def start_ramp_max(prt, freq_hz=None, duty=None, saw_step=None, pat_period=None, if dac_clk_hz is None: dac_clk_hz = cmd.AD9102_DAC_CLK_HZ sram_samples = cmd.calc_sram_samples_for_freq(freq_hz, dac_clk_hz, sram_hold) - hexstring = cmd.create_AD9102_ramp_command(enable=True, triangle=triangle, sram_mode=True, - sram_samples=sram_samples, sram_hold=sram_hold, - sram_amplitude=sram_amplitude) + hexstring = cmd.create_AD9102_ramp_command( + enable=True, + triangle=triangle, + sram_mode=True, + sram_samples=sram_samples, + sram_hold=sram_hold, + sram_amplitude=sram_amplitude, + ) else: if pat_period_base is None: pat_period_base = cmd.AD9102_PAT_PERIOD_BASE_DEFAULT @@ -137,74 +120,113 @@ def start_ramp_max(prt, freq_hz=None, duty=None, saw_step=None, pat_period=None, pat_period = cmd.calc_pat_period_for_duty(saw_step, duty, pat_period_base, triangle) if pat_period is None: pat_period = cmd.AD9102_PAT_PERIOD_DEFAULT - hexstring = cmd.create_AD9102_ramp_command(saw_step, pat_period, pat_period_base, - enable=True, triangle=triangle) + hexstring = cmd.create_AD9102_ramp_command( + saw_step, + pat_period, + pat_period_base, + enable=True, + triangle=triangle, + ) + cmd.send_AD9102(prt, hexstring) time.sleep(WAIT_AFTER_SEND) - status = cmd.get_STATE(prt).hex() - if status is not None: - print("Received: STATE. State status:", cmd.decode_STATE(status), "("+cmd.flipfour(status)+")") - print("") - else: - print("") + return _print_state_reply(cmd.get_STATE(prt)) def start_ad9833_ramp(prt, freq_hz=None, mclk_hz=None, triangle=True, enable=True): if freq_hz is None: freq_hz = 0.0 - hexstring = cmd.create_AD9833_ramp_command(freq_hz=freq_hz, mclk_hz=mclk_hz, - enable=enable, triangle=triangle) + hexstring = cmd.create_AD9833_ramp_command( + freq_hz=freq_hz, + mclk_hz=mclk_hz, + enable=enable, + triangle=triangle, + ) cmd.send_AD9833(prt, hexstring) time.sleep(WAIT_AFTER_SEND) - status = cmd.get_STATE(prt).hex() - if status is not None: - print("Received: STATE. State status:", cmd.decode_STATE(status), "("+cmd.flipfour(status)+")") - print("") - else: - print("") + return _print_state_reply(cmd.get_STATE(prt)) + + +def _wait_for_min_bytes(prt, expected_len, timeout_s, poll_s=0.01): + deadline = time.time() + timeout_s + while time.time() < deadline: + waiting = prt.inWaiting() + if waiting >= expected_len: + return True + time.sleep(poll_s) + return prt.inWaiting() >= expected_len def send_ds1809_pulse(prt, uc=False, dc=False, count=1, pulse_ms=None): + if count is None or count <= 0: + count = 1 + if pulse_ms is None or pulse_ms <= 0: + pulse_ms = cmd.DS1809_PULSE_MS_DEFAULT + hexstring = cmd.create_DS1809_pulse_command(uc=uc, dc=dc, count=count, pulse_ms=pulse_ms) cmd.send_DS1809(prt, hexstring) - time.sleep(WAIT_AFTER_SEND) - status = cmd.get_STATE(prt).hex() - if status is not None: - print("Received: STATE. State status:", cmd.decode_STATE(status), "("+cmd.flipfour(status)+")") - print("") - else: - print("") + + # Firmware blocks while pulsing DS1809 lines: wait pulse train + safe margin. + pulse_train_time = (2.0 * float(count) * float(pulse_ms)) / 1000.0 + time.sleep(max(WAIT_AFTER_SEND, pulse_train_time + 0.35)) + + # Then poll shortly for STATE bytes; this avoids early read (0 bytes) on startup. + _wait_for_min_bytes(prt, expected_len=2, timeout_s=0.8) + + return _print_state_reply(cmd.get_STATE(prt)) def request_data(prt): - # Request data cmd.send_TRANS_ENABLE(prt) time.sleep(WAIT_AFTER_SEND) - data = cmd.get_DATA(prt).hex() - data_dict = [] - if data is not None: - data_dict = cmd.decode_DATA(data) - return data_dict + + data_bytes = cmd.get_DATA(prt) + if data_bytes is None: + return None + + return cmd.decode_DATA(data_bytes.hex()) def print_data(data): def shorten(i): return str(round(i, 2)) - print("Data from device (time: "+datetime.now().strftime("%H:%M:%S:%f")+"):") - print("Message Header:", data['Header'], " Message ID:", data['Message_ID']) - print("Photodiode Current 1 ("+str(len(data['I1']))+" values):", \ - shorten(data['I1']), shorten(data['I1'][1]), "...", \ - shorten(data['I1']), shorten(data['I1'][-1]), "mA") - print("Photodiode Current 2 ("+str(len(data['I2']))+" values):", \ - shorten(data['I2']), shorten(data['I2'][1]), "...", \ - shorten(data['I2']), shorten(data['I2'][-1]), "mA") - print("Laser Temperature 1:", shorten(data['Temp_1']), "C") - print("Laser Temperature 2:", shorten(data['Temp_2']), "C") - print("Temperature of external thermistor 1:", shorten(data['Temp_Ext_1']), "C") - print("Temperature of external thermistor 2:", shorten(data['Temp_Ext_2']), "C") - print("Voltages 3V3: "+shorten(data['MON_3V3'])+"V 5V1: "+shorten(data['MON_5V1'])+ \ - "V 5V2: "+shorten(data['MON_5V2'])+"V 7V0: "+shorten(data['MON_7V0'])+"V.") + print("Data from device (time: " + datetime.now().strftime("%H:%M:%S:%f") + "):") + print("Message Header:", data["Header"], " Message ID:", data["Message_ID"]) + print( + "Photodiode Current 1 (" + str(len(data["I1"])) + " values):", + shorten(data["I1"]), + shorten(data["I1"][1]), + "...", + shorten(data["I1"]), + shorten(data["I1"][-1]), + "mA", + ) + print( + "Photodiode Current 2 (" + str(len(data["I2"])) + " values):", + shorten(data["I2"]), + shorten(data["I2"][1]), + "...", + shorten(data["I2"]), + shorten(data["I2"][-1]), + "mA", + ) + print("Laser Temperature 1:", shorten(data["Temp_1"]), "C") + print("Laser Temperature 2:", shorten(data["Temp_2"]), "C") + print("Temperature of external thermistor 1:", shorten(data["Temp_Ext_1"]), "C") + print("Temperature of external thermistor 2:", shorten(data["Temp_Ext_2"]), "C") + print( + "Voltages 3V3: " + + shorten(data["MON_3V3"]) + + "V 5V1: " + + shorten(data["MON_5V1"]) + + "V 5V2: " + + shorten(data["MON_5V2"]) + + "V 7V0: " + + shorten(data["MON_7V0"]) + + "V." + ) + def close_connection(prt): cmd.close_port(prt) diff --git a/gui.py b/gui.py index 5e85a64..6bdeddf 100644 --- a/gui.py +++ b/gui.py @@ -51,13 +51,14 @@ SET_RAMP_SRAM_MODE_TEXT = 'SRAM режим' SET_RAMP_SRAM_SAMPLES_TEXT = 'SRAM точки (samples):' SET_RAMP_SRAM_AMP_TEXT = 'SRAM амплитуда (%):' SET_AD9833_SECTION_TEXT = 'Настройка пилы (AD9833)' -SET_AD9833_FREQ_TEXT = 'Частота AD9833 (Гц):' +SET_AD9833_FREQ_TEXT = 'Частота AD9833 (кГц):' SET_AD9833_MCLK_TEXT = 'MCLK AD9833 (МГц):' SET_AD9833_TRI_TEXT = 'Треугольник AD9833' SET_AD9833_BUTTON_TEXT = 'Пила AD9833' SET_DS1809_SECTION_TEXT = 'DS1809 (UC/DC)' SET_DS1809_UC_BUTTON_TEXT = 'UC импульс' SET_DS1809_DC_BUTTON_TEXT = 'DC импульс' +SET_DS1809_STATUS_TEXT = 'Позиция DS1809:' GRAPH_POINTS_NUMBER = 100 # Number of most recent data points shown on charts @@ -290,6 +291,9 @@ def setup_gui(params): [sg.Button(SET_DS1809_UC_BUTTON_TEXT, key='-DS1809UC-', disabled_button_color=("Gray22", "Blue")), sg.Button(SET_DS1809_DC_BUTTON_TEXT, key='-DS1809DC-', disabled_button_color=("Gray22", "Blue"))], + [sg.Text(SET_DS1809_STATUS_TEXT, size=(SET_TEXT_WIDTH_NEW,1)), + sg.Text(params.get('DS1809Status', '--'), key='-DS1809Status-', size=(20,1))], + [sg.HSeparator(pad=H_SEPARATOR_PAD)], [sg.Button(SET_START_BUTTON_TEXT, key='-StartCycle-', disabled_button_color=("Gray22", "Blue"), disabled=True), sg.Button(SET_STOP_BUTTON_TEXT, disabled_button_color=("Gray22", "Blue"), key='-StopCycle-', disabled=True), sg.Button(SET_RAMP_BUTTON_TEXT, key='-StartRamp-', disabled_button_color=("Gray22", "Blue")), sg.Button(SET_AD9833_BUTTON_TEXT, key='-StartRamp9833-', disabled_button_color=("Gray22", "Blue"))]]