11from typing import Union
2+ from dataclasses import dataclass
3+ import itertools
24
35from ..core import VisaResource
46
57
8+ @dataclass
9+ class SequenceStep :
10+ current : float
11+ trigger : bool = False
12+
13+
614class Kikusui_PLZ1004WH (VisaResource ): # 1 kW
715 """
816 Kikusui_PLZ1004WH(address)
@@ -17,6 +25,8 @@ class Kikusui_PLZ1004WH(VisaResource): # 1 kW
1725 # logic
1826 # documenation
1927
28+ SequenceStep = SequenceStep
29+
2030 def set_state (self , state : bool ) -> None :
2131 """
2232 set_state(state)
@@ -376,3 +386,149 @@ def measure_power(self) -> float:
376386
377387 response = self .query_resource ("MEAS:POW?" )
378388 return float (response )
389+
390+ def configure_sequence (
391+ self ,
392+ steps : list ["Kikusui_PLZ1004WH.SequenceStep" ],
393+ current_range : str = "HIGH" ,
394+ step_size : float = 1e-3 ,
395+ initialize : bool = True ,
396+ ) -> None :
397+ """
398+ Configure the load fast sequence consisting of a series of steps. Each step
399+ includes a current value and whether or not a trigger pulse should be emitted.
400+
401+ Args:
402+ steps (list[Kikusui_PLZ1004WH.SequenceStep]): A list of SequenceSteps
403+ describing the sequence to be executed. Each step has a load setting and
404+ the option to emit a trigger pulse. A maximum of 1024 steps can be used
405+ but note that transmitting a large number of steps takes significant
406+ time.
407+ current_range (str, optional): Range setting to use (LOW, MED, HIGH). Refer
408+ to manual for the maximum current that each range is capable of.
409+ Typically LOW = 1.32A, MED = 13.2A, and HIGH = 132A. Defaults to "HIGH".
410+ step_size (float, optional): Size (duration) of each step. Valid range is
411+ 100us to 100ms. Defaults to 1ms.
412+ initialize (bool, optional): Send the initialization commands to set up the
413+ load in addition to sending the sequence steps. Setting to false can
414+ save some time if no settings need to be changed. Defaults to True.
415+ """
416+ MIN_STEP_SIZE = 100e-6
417+ MAX_STEP_SIZE = 100e-3
418+ VALID_RANGES = {"LOW" , "MEDIUM" , "MED" , "HIGH" }
419+ MAX_SEQ_LENGTH = 1024
420+
421+ sequence_len = len (steps )
422+
423+ # validate the inputs
424+ if sequence_len > MAX_SEQ_LENGTH :
425+ raise ValueError (f"sequence length is { sequence_len } > { MAX_SEQ_LENGTH = } " )
426+ if not current_range .upper () in VALID_RANGES :
427+ raise ValueError (f"{ current_range = } is not in { VALID_RANGES = } " )
428+ if step_size > MAX_STEP_SIZE or step_size < MIN_STEP_SIZE :
429+ raise ValueError (f"step_size must be <{ MAX_STEP_SIZE } and >{ MIN_STEP_SIZE } " )
430+
431+ if initialize :
432+ # fast sequence requires 11, select it for editing
433+ self .write_resource ("prog:name 11" )
434+ # set sequence to fast CC mode
435+ self .write_resource ("prog:mode fcc" )
436+ # run the sequence just once
437+ self .write_resource ("prog:loop 1" )
438+ # set the step length
439+ self .write_resource (f"prog:fsp:time { step_size } " )
440+ # set the current range for the sequence
441+ self .write_resource (f"prog:cran { current_range } " )
442+ # set the sequence length
443+ self .write_resource (f"prog:fsp:end { sequence_len } " )
444+
445+ # Write the sequence of currents in chunks of 8 using prog:fsp:edit:wave
446+ # to reduce number of transactions. Write individual steps only when
447+ # a trigger pulse is needed
448+ for steps_chunk , first_step_idx in zip (
449+ itertools .batched (steps , 8 ),
450+ itertools .count (start = 1 , step = 8 ),
451+ ):
452+ currents = [step .current for step in steps_chunk ]
453+ # pad the chunk of currents up to 8 with 0s
454+ currents += (8 - len (currents )) * [0 ]
455+ # write the chunk of currents
456+ self .write_resource (
457+ f"prog:fsp:edit:wave { first_step_idx } ,"
458+ + "," .join (str (c ) for c in currents )
459+ )
460+
461+ # set the steps that have triggers
462+ for offset , step in enumerate (steps_chunk ):
463+ if not step .trigger :
464+ continue
465+ step_idx = first_step_idx + offset
466+ # store the current of the step
467+ curr = self .query_resource (f"prog:fsp:edit? { step_idx } " ).split ("," )[0 ]
468+ # write the step with a trigger
469+ self .write_resource (f"prog:fsp:edit { step_idx } ,{ curr } ,1" )
470+
471+ def run_sequence (self ) -> None :
472+ """
473+ Run the current sequence.
474+ """
475+ self .write_resource ("prog:stat run" )
476+
477+ def configure_pulse_seqeunce (
478+ self ,
479+ pulse_current : float ,
480+ pulse_width : float ,
481+ trig_delay : float ,
482+ step_size : float = 1e-3 ,
483+ initial_idle_time : float = 10e-3 ,
484+ idle_current : float = 0.0 ,
485+ current_range : str = "HIGH" ,
486+ keep_load_on : bool = False ,
487+ ) -> None :
488+ """
489+ Configure the load to produce a single pulse sequence consisting of an initial
490+ low current period for the load to "warm up" followed by a high current period
491+ at the specified current and a ending low current period fixed at 1ms. A trigger
492+ pulse is emmited during the pulse with the defined delay.
493+
494+ Args:
495+ pulse_current (float): Current to set during the pulse
496+ pulse_width (float): width of the pulse in seconds
497+ trig_delay (float): delay from the beginning of the pulse to when the
498+ trigger pulse is emmited in seconds
499+ step_size (float, optional): Size of the steps which the sequence will be
500+ broken up into. A smaller step results in more resolution of the
501+ timings. Defaults to 1e-3.
502+ initial_idle_time (float, optional): Time to set the load to the initial
503+ value before starting the pulse. This is needed since the load cannot
504+ immediately produce a high load at the beginning of a sequence. 10ms was
505+ found to be effective. Defaults to 10e-3.
506+ idle_current (float, optional): Current to set during idle times. 0A
507+ typically works fine for this purpose. Defaults to 0.0.
508+ current_range (str, optional): Range setting to use (LOW, MED, HIGH). Refer
509+ to manual for the maximum current that each range is capable of.
510+ Typically LOW = 1.32A, MED = 13.2A, and HIGH = 132A. Defaults to "HIGH".
511+ keep_load_on (bool, optional): Keep the load at the specified idle_current
512+ after the sequence completes. Defaults to False.
513+ """
514+ END_IDLE_TIME = 1e-3
515+ seq_len = initial_idle_time + pulse_width + END_IDLE_TIME
516+ if trig_delay + initial_idle_time > seq_len :
517+ ValueError (f"{ trig_delay = } not valid for { seq_len = } " )
518+ steps = list (
519+ itertools .chain (
520+
521+ (SequenceStep (idle_current ) for _ in range (round (initial_idle_time / step_size ))),
522+
523+ (SequenceStep (pulse_current ) for _ in range (round (pulse_width / step_size ))),
524+
525+ (SequenceStep (idle_current ) for _ in range (round (END_IDLE_TIME / step_size ))),
526+ )
527+ )
528+ # +1 since trigger occurs at the beginning of a step
529+ trigger_idx = round ((initial_idle_time + trig_delay ) / step_size + 1 )
530+ steps [trigger_idx ].trigger = True
531+ self .configure_sequence (steps , current_range , step_size )
532+ if keep_load_on :
533+ self .write_resource (f"prog:linp { 1 if idle_current else 0 } " )
534+ self .write_resource (f"prog:lval { idle_current } " )
0 commit comments