ad9102 prestored saw done

This commit is contained in:
Ayzen
2026-02-03 18:57:30 +03:00
parent 809f19bea4
commit fd1095c50a
9 changed files with 230 additions and 23 deletions

Binary file not shown.

View File

@ -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)

View File

@ -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

View File

@ -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)

35
gui.py
View File

@ -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))],

4
run
View File

@ -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