diff --git a/__pycache__/device_commands.cpython-312.pyc b/__pycache__/device_commands.cpython-312.pyc index 32f0409..3f7dcec 100644 Binary files a/__pycache__/device_commands.cpython-312.pyc and b/__pycache__/device_commands.cpython-312.pyc differ diff --git a/__pycache__/device_conversion.cpython-312.pyc b/__pycache__/device_conversion.cpython-312.pyc index 0780389..baa8aa6 100644 Binary files a/__pycache__/device_conversion.cpython-312.pyc and b/__pycache__/device_conversion.cpython-312.pyc differ diff --git a/__pycache__/device_interaction.cpython-312.pyc b/__pycache__/device_interaction.cpython-312.pyc index b45b308..708036d 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 f6a1bed..d94c7f2 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 a1e5f47..05dc422 100644 --- a/_device_main.py +++ b/_device_main.py @@ -44,6 +44,31 @@ def get_float(values, strId): window['-StartCycle-'].update(disabled = True) return value +def parse_optional_int(value): + if value is None: + return None + s = str(value).strip() + if s == "": + return None + try: + return int(s, 0) + except Exception: + print(f"Invalid integer value: {s}") + return None + +def parse_optional_float(value): + if value is None: + return None + s = str(value).strip() + if s == "": + return None + s = s.replace(",", ".") + try: + return float(s) + except Exception: + print(f"Invalid float value: {s}") + return None + def shorten(i): return "{:.2f}".format(round(i, 2)) @@ -73,6 +98,13 @@ def set_initial_params(): params['Delta_Current_2'] = 0.05 params['Delta_Time'] = 50 params['Tau'] = 10 + params['RampFreq'] = '' + params['RampDuty'] = '' + params['RampSawStep'] = '' + params['RampPatPeriod'] = '' + params['RampPatBase'] = 2 + params['RampDacClk'] = '' + params['RampTriangle'] = True return params def update_data_lists(): @@ -423,6 +455,23 @@ if __name__ == "__main__": params['Iset_2'] = float(values['-InputI2-']) dev.send_control_parameters(prt, params) #print(sending_param) + elif event == '-StartRamp-': + freq_hz = parse_optional_float(values.get('-RampFreq-')) + duty = parse_optional_float(values.get('-RampDuty-')) + if duty is not None: + if duty > 1.0: + duty = duty / 100.0 + if duty <= 0: + duty = None + saw_step = parse_optional_int(values.get('-RampSawStep-')) + pat_period = parse_optional_int(values.get('-RampPatPeriod-')) + pat_period_base = parse_optional_int(values.get('-RampPatBase-')) + dac_clk_mhz = parse_optional_float(values.get('-RampDacClk-')) + dac_clk_hz = dac_clk_mhz * 1e6 if dac_clk_mhz is not None else None + triangle = values.get('-RampTriangle-', True) + dev.start_ramp_max(prt, freq_hz=freq_hz, duty=duty, saw_step=saw_step, + pat_period=pat_period, pat_period_base=pat_period_base, + dac_clk_hz=dac_clk_hz, triangle=triangle) elif event == '-StopCycle-': window['-StopCycle-'].update(disabled = True) current_and_temperature_settings_available = True @@ -466,4 +515,3 @@ if __name__ == "__main__": dev.close_connection(prt) - diff --git a/device_commands.py b/device_commands.py index 741743a..ef71d54 100644 --- a/device_commands.py +++ b/device_commands.py @@ -10,6 +10,13 @@ GET_DATA_TOTAL_LENGTH = 30 # Total number of bytes when getting DATA SEND_PARAMS_TOTAL_LENGTH = 30 # Total number of bytes when sending parameters TASK_ENABLE_COMMAND_LENGTH = 32 # Total number of bytes when sending TASK_ENABLE command +AD9833_CMD_TOTAL_LENGTH = 10 # Total bytes when sending AD9102 saw command +AD9833_CMD_HEADER = "8888" +AD9102_SAW_STEP_DEFAULT = 1 +AD9102_PAT_PERIOD_DEFAULT = 0xFFFF +AD9102_PAT_PERIOD_BASE_DEFAULT = 0x02 +AD9102_DAC_CLK_HZ = None # set to actual DAC clock if you want freq->SAW_STEP conversion + class TaskType(IntEnum): Manual = 0x00 ChangeCurrentLD1 = 0x01 @@ -148,6 +155,17 @@ def send_STATE(prt): pass +def send_AD9833(prt, bytestring): + ''' Start/stop AD9833 output with triangle (ramp) mode (0x8888 + ...). + Expected device answer: STATE. + ''' + if len(bytestring) != AD9833_CMD_TOTAL_LENGTH: + print("Error. Wrong parameter string for AD9833 command.") + return None + prt.write(bytestring) + print("Sent: AD9833 ramp command.") + + # ---- Getting data @@ -262,6 +280,76 @@ def create_TaskEnableCommand(sending_param): return bytearray.fromhex(data) + + +def calc_saw_step_for_freq(freq_hz: float, dac_clk_hz: float, triangle: bool): + if freq_hz <= 0 or dac_clk_hz is None or dac_clk_hz <= 0: + return AD9102_SAW_STEP_DEFAULT + n = 2 if triangle else 1 + step = int(round(dac_clk_hz / (freq_hz * n * 16384.0))) + if step < 1: + step = 1 + if step > 63: + step = 63 + return step + + +def calc_pat_period_for_duty(saw_step: int, duty: float, pat_period_base: int, triangle: bool): + if duty is None or duty <= 0 or duty > 1.0: + return AD9102_PAT_PERIOD_DEFAULT + n = 2 if triangle else 1 + base_cycles = 16 if pat_period_base == 0 else pat_period_base + ramp_cycles = n * 16384 * max(1, min(63, saw_step)) + pat_period = int(round(ramp_cycles / (duty * base_cycles))) + if pat_period < 1: + pat_period = 1 + if pat_period > 0xFFFF: + pat_period = 0xFFFF + return pat_period + + +def create_AD9833_ramp_command(saw_step: int = None, + pat_period: int = None, + pat_period_base: int = None, + enable: bool = True, + triangle: bool = True): + if saw_step is None: + saw_step = AD9102_SAW_STEP_DEFAULT + if pat_period is None: + pat_period = AD9102_PAT_PERIOD_DEFAULT + if pat_period_base is None: + pat_period_base = AD9102_PAT_PERIOD_BASE_DEFAULT + if saw_step < 1: + saw_step = 1 + if saw_step > 63: + saw_step = 63 + if pat_period < 0: + pat_period = 0 + if pat_period > 0xFFFF: + pat_period = 0xFFFF + if pat_period_base < 0: + pat_period_base = 0 + if pat_period_base > 0x0F: + pat_period_base = 0x0F + + flags = 0 + if enable: + flags |= 0x0001 + if triangle: + flags |= 0x0002 + + param0 = ((pat_period_base & 0x0F) << 8) | (saw_step & 0xFF) + crc_word = flags ^ param0 ^ pat_period + + data = flipfour(AD9833_CMD_HEADER) # Word 0 (header) + data += flipfour(int_to_hex(flags)) + data += flipfour(int_to_hex(param0)) + data += flipfour(int_to_hex(pat_period)) + data += flipfour(int_to_hex(crc_word)) + + return bytearray.fromhex(data) + + def encode_Input(params): if params is None: @@ -295,24 +383,38 @@ def encode_Input(params): def decode_STATE(state): st = flipfour(state) - if st == '0000': + if st is None or len(st) != 4: + return "Error: invalid STATE length." + + hi = int(st[0:2], 16) + lo = int(st[2:4], 16) + + errors = [] + if lo & 0x01: + errors.append("SD Card reading/writing error (SD_ERR)") + if lo & 0x02: + errors.append("Command error (UART_ERR)") + if lo & 0x04: + errors.append("Wrong parameter value error (UART_DECODE_ERR)") + if lo & 0x08: + errors.append("Laser 1: TEC driver overheat (TEC1_ERR)") + if lo & 0x10: + errors.append("Laser 2: TEC driver overheat (TEC2_ERR)") + if lo & 0x20: + errors.append("Resetting system error (DEFAULT_ERR)") + if lo & 0x40: + errors.append("File deletion error (REMOVE_ERR)") + if lo & 0x80: + errors.append("AD9102 status check failed (AD9102_ERR)") + + if not errors: status = "All ok." - elif st == '0001': - status = "SD Card reading/writing error (SD_ERR)." - elif st == '0002': - status = "Command error (UART_ERR)." - elif st == '0004': - status = "Wrong parameter value error (UART_DECODE_ERR)." - elif st == '0008': - status = "Laser 1: TEC driver overheat (TEC1_ERR)." - elif st == '0010': - status = "Laser 2: TEC driver overheat (TEC2_ERR)." - elif st == '0020': - status = "Resetting system error (DEFAULT_ERR)." - elif st == '0040': - status = "File deletion error (REMOVE_ERR)." else: - status = "Unknown or reserved error." + status = "; ".join(errors) + + if hi != 0: + status += f" | AD9102_PAT_STATUS=0x{hi:02X}" + return status @@ -342,6 +444,3 @@ def decode_DATA(dh): return data - - - diff --git a/device_interaction.py b/device_interaction.py index 6e2cb78..80f603b 100644 --- a/device_interaction.py +++ b/device_interaction.py @@ -110,6 +110,33 @@ def send_task_command(prt, sending_param): else: print("") + + +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): + # Start AD9102 sawtooth with configurable frequency/duty (via SAW_STEP and PAT_PERIOD) + if pat_period_base is None: + pat_period_base = cmd.AD9102_PAT_PERIOD_BASE_DEFAULT + if saw_step is None and freq_hz is not None: + if dac_clk_hz is None: + dac_clk_hz = cmd.AD9102_DAC_CLK_HZ + saw_step = cmd.calc_saw_step_for_freq(freq_hz, dac_clk_hz, triangle) + if saw_step is None: + saw_step = cmd.AD9102_SAW_STEP_DEFAULT + if pat_period is None and duty is not 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_AD9833_ramp_command(saw_step, pat_period, pat_period_base, enable=True, 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("") + + def request_data(prt): # Request data cmd.send_TRANS_ENABLE(prt) diff --git a/gui.py b/gui.py index 36c469b..c0e1056 100644 --- a/gui.py +++ b/gui.py @@ -38,6 +38,16 @@ SET_TEXT_WIDTH_NEW = 40 SET_START_BUTTON_TEXT = 'Пуск' SET_STOP_BUTTON_TEXT = 'Стоп' +SET_RAMP_BUTTON_TEXT = 'Пила' +SET_RAMP_SECTION_TEXT = 'Настройка пилы (AD9102)' +SET_RAMP_FREQ_TEXT = 'Частота (Гц):' +SET_RAMP_DUTY_TEXT = 'Скважность (%):' +SET_RAMP_SAWSTEP_TEXT = 'SAW_STEP (1-63):' +SET_RAMP_PATPERIOD_TEXT = 'PAT_PERIOD (1-65535):' +SET_RAMP_PATBASE_TEXT = 'PAT_PERIOD_BASE (0-15):' +SET_RAMP_DACCLK_TEXT = 'DAC clk (МГц):' +SET_RAMP_TRI_TEXT = 'Треугольник' + GRAPH_POINTS_NUMBER = 100 # Number of most recent data points shown on charts GRAPH_CANVAS_SIZE = (0, 0) @@ -195,9 +205,32 @@ def setup_gui(params): [sg.Text(SET_TAU_T_TEXT, size=(SET_TEXT_WIDTH_NEW,1)), sg.Input(params['Tau'], size=(SET_INPUT_WIDTH,1), key='-InputTau-', disabled=True, disabled_readonly_background_color="Gray")], + [sg.Text(SET_RAMP_SECTION_TEXT, size=(SET_TEXT_WIDTH_NEW,1))], + + [sg.Text(SET_RAMP_FREQ_TEXT, size=(SET_TEXT_WIDTH_NEW,1)), + sg.Input(params.get('RampFreq', ''), size=(SET_INPUT_WIDTH,1), key='-RampFreq-')], + + [sg.Text(SET_RAMP_DUTY_TEXT, size=(SET_TEXT_WIDTH_NEW,1)), + sg.Input(params.get('RampDuty', ''), size=(SET_INPUT_WIDTH,1), key='-RampDuty-')], + + [sg.Text(SET_RAMP_SAWSTEP_TEXT, size=(SET_TEXT_WIDTH_NEW,1)), + sg.Input(params.get('RampSawStep', ''), size=(SET_INPUT_WIDTH,1), key='-RampSawStep-')], + + [sg.Text(SET_RAMP_PATPERIOD_TEXT, size=(SET_TEXT_WIDTH_NEW,1)), + sg.Input(params.get('RampPatPeriod', ''), size=(SET_INPUT_WIDTH,1), key='-RampPatPeriod-')], + + [sg.Text(SET_RAMP_PATBASE_TEXT, size=(SET_TEXT_WIDTH_NEW,1)), + sg.Input(params.get('RampPatBase', ''), size=(SET_INPUT_WIDTH,1), key='-RampPatBase-')], + + [sg.Text(SET_RAMP_DACCLK_TEXT, size=(SET_TEXT_WIDTH_NEW,1)), + sg.Input(params.get('RampDacClk', ''), size=(SET_INPUT_WIDTH,1), key='-RampDacClk-')], + + [sg.Text(SET_RAMP_TRI_TEXT, size=(SET_TEXT_WIDTH_NEW,1)), + sg.Checkbox('', default=bool(params.get('RampTriangle', True)), key='-RampTriangle-')], + [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_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"))]] layout = [[sg.Column(layout_input_col1, pad=(0,0)), sg.VSeparator(pad=(4,0)), sg.Column(layout_input_col2, pad=(0,0)), sg.VSeparator(pad=(4,0)), sg.Column(layout_input_col3, pad=(0,0))], diff --git a/run b/run index 3d0615b..08a5e29 100755 --- a/run +++ b/run @@ -1,7 +1,7 @@ #!/usr/bin/bash #reset generator PCB -pinctrl set 26 op dl # drive PCB NRST LOW -> reset stm32 -pinctrl set 26 op dh # turn stm32 back ON +#pinctrl set 26 op dl # drive PCB NRST LOW -> reset stm32 +#pinctrl set 26 op dh # turn stm32 back ON source .venv/bin/activate python3 _device_main.py