|
13 | 13 |
|
14 | 14 | class PulseBlaster(ExperimentPulser): |
15 | 15 |
|
16 | | - def __init__(self, instruction_set_resolution_in_ns = 50): |
| 16 | + def __init__(self, pb_board_number=0, instruction_set_resolution_in_ns=50): |
17 | 17 | self.instruction_set_resolution_in_ns = instruction_set_resolution_in_ns |
| 18 | + self.pb_board_number = pb_board_number |
18 | 19 |
|
19 | 20 | def start(self): |
20 | 21 | self.open() |
@@ -65,16 +66,17 @@ def PBInd(self, *args, **kwargs): |
65 | 66 |
|
66 | 67 | class PulseBlasterArb(PulseBlaster): |
67 | 68 |
|
68 | | - def __init__(self, pb_board_number = 1, *args, **kwargs): |
| 69 | + def __init__(self, *args, **kwargs): |
69 | 70 | super().__init__(*args, **kwargs) |
70 | | - self.pb_board_number = pb_board_number |
71 | | - self.reset() |
| 71 | + self.clear_channel_settings() |
| 72 | + self.clear_clock_channels() |
| 73 | + self.set_full_cycle_length(0) |
72 | 74 |
|
73 | | - def reset(self): |
| 75 | + def clear_clock_channels(self): |
74 | 76 | self.clock_channels = [] |
75 | 77 | self.clock_period = None |
| 78 | + def clear_channel_settings(self): |
76 | 79 | self.channel_settings = [] |
77 | | - self.full_cycle_width = 0 |
78 | 80 |
|
79 | 81 | def set_clock_channels(self, pulse_blaster_channels, clock_period): |
80 | 82 | ''' |
@@ -177,9 +179,92 @@ def __init__(self, pb_board_number = 1, |
177 | 179 | aom_channel output controls the AOM by holding a positive voltage |
178 | 180 | cycle_width - the length of the programmed pulse. Since aom channel is held on, this value is arbitrary |
179 | 181 | """ |
180 | | - super().__init__(pb_board_number, *args, **kwargs) |
| 182 | + super().__init__(pb_board_number=pb_board_number, *args, **kwargs) |
181 | 183 | self.add_channels(aom_channel, 0, cycle_width) |
182 | 184 |
|
| 185 | +class PulseBlasterT1(PulseBlasterArb): |
| 186 | + """ |
| 187 | + Sets up the pulse blaster to emit TTL signals for measuring the T1 relaxation time of |
| 188 | + a quantum state that can be optically initialized and read out. |
| 189 | +
|
| 190 | + pulse sequence |
| 191 | + aom on, aom off for time = ~full_cycle_width/2 - tau, aom on, aom off for time = tau, aom on, aom off for time = ~full_cycle_width/2 |
| 192 | +
|
| 193 | + Note that since this cycle repeats (often for many thousands of times), the first aom on pulse is |
| 194 | + our background signal because it occurs ~full_cycle_width/2 after the third aom pulse. |
| 195 | + The second aom pulse occurs a time tau before the third aom pulse. |
| 196 | + Thus, the third aom pulse acts as a readout that occurs time tau after initialization. |
| 197 | + It also acts as the initialization for the first aom pulse, which is why we use the first aom pulse for background measuremnt |
| 198 | + This all assumes, of course, that the ensemble of states can be fully initialized within the aom pulse duration. |
| 199 | + Default is set to 10 microseconds, which should be sufficient with ~1mW of optical power. |
| 200 | +
|
| 201 | + Also to note: our current reliance on zeeshawn/pulseblaster hinders our ability to create long |
| 202 | + pulse blaster sequences and simultaneously short clock periods. There seems to be a limitation that |
| 203 | + full_cycle_width / clock_period <= 2000. This is probably because 2000 clock ticks requires 4000 |
| 204 | + programming instructions, based upon zeeshawn/pulseblaster code. The pulse blaster has a limit |
| 205 | + of 4k memory for pulse instructions. |
| 206 | + """ |
| 207 | + def __init__(self, aom_channels=0, |
| 208 | + clock_channels=2, |
| 209 | + trigger_channels=3, |
| 210 | + aom_pulse_duration_time=10e-6, |
| 211 | + aom_response_time = 800e-9, |
| 212 | + clock_period=0.25e-6, |
| 213 | + trigger_pulse_duration=1e-6, |
| 214 | + tau_readout_delay_default=10e-6, |
| 215 | + full_cycle_width=0.5e-3, *args, **kwargs): |
| 216 | + """ |
| 217 | + pb_board_number - the board number (0, 1, ...) |
| 218 | + aom_channel output controls the AOM by holding a positive voltage |
| 219 | + full_cycle_width - the length of the programmed pulse. Since aom channel is held on, this value is arbitrary |
| 220 | + """ |
| 221 | + super().__init__(*args, **kwargs) |
| 222 | + self.aom_channels = aom_channels |
| 223 | + self.clock_channels = clock_channels |
| 224 | + self.trigger_channels = trigger_channels |
| 225 | + self.aom_pulse_duration_time = aom_pulse_duration_time |
| 226 | + self.aom_response_time = aom_response_time |
| 227 | + self.trigger_pulse_duration = trigger_pulse_duration |
| 228 | + self.clock_period = clock_period |
| 229 | + self.tau_readout_delay = tau_readout_delay_default |
| 230 | + self.set_full_cycle_length(full_cycle_width) |
| 231 | + self.clock_delay = 0 #artificial delay to handle issue where NIDAQ doesn't start acquireing data until a full clock cycle after the trigger |
| 232 | + # this normally isn't an issue when the AOM response time is greater than a clock cycle. |
| 233 | + |
| 234 | + def program_pulser_state(self, tau_readout_delay=None, *args, **kwargs): |
| 235 | + """ |
| 236 | + tau_readout_delay |
| 237 | + """ |
| 238 | + |
| 239 | + if tau_readout_delay is not None: |
| 240 | + self.tau_readout_delay = np.round(tau_readout_delay,8) |
| 241 | + |
| 242 | + self.clear_channel_settings() |
| 243 | + self.set_clock_channels(self.clock_channels, self.clock_period) |
| 244 | + self.add_channels(self.trigger_channels, 0, self.trigger_pulse_duration) |
| 245 | + |
| 246 | + self.clock_delay = 2*self.clock_period # we have to delay signals such that the pulses arrive after subsequent clock signals to our DAQ, otherwise the readout appears phase shifted |
| 247 | + # # first aom pulse |
| 248 | + self.add_channels(self.aom_channels, self.clock_delay, self.aom_pulse_duration_time) |
| 249 | + # delay |
| 250 | + read_out_start_time = self.full_cycle_width / 2 |
| 251 | + read_out_start_time -= self.tau_readout_delay + self.aom_pulse_duration_time |
| 252 | + # second aom pulse |
| 253 | + self.add_channels(self.aom_channels, self.clock_delay + read_out_start_time, self.aom_pulse_duration_time) |
| 254 | + # third aom pulse |
| 255 | + self.add_channels(self.aom_channels, self.clock_delay + self.full_cycle_width / 2, self.aom_pulse_duration_time) |
| 256 | + |
| 257 | + |
| 258 | + self.raise_for_pulse_width(self.tau_readout_delay) |
| 259 | + return super().program_pulser_state(*args, **kwargs) |
| 260 | + |
| 261 | + def raise_for_pulse_width(self, tau_readout_delay, *args, **kwargs): |
| 262 | + min_required_length = 2 * (self.aom_pulse_duration_time + tau_readout_delay + self.aom_pulse_duration_time) |
| 263 | + min_required_length += self.aom_response_time |
| 264 | + |
| 265 | + if self.full_cycle_width < min_required_length: |
| 266 | + raise PulseTrainWidthError(f'Readout delay is too large: {tau_readout_delay:.2e}. Increase self.full_cycle_width to > {min_required_length:.2e}') |
| 267 | + |
183 | 268 | class PulseBlasterCWODMR(PulseBlaster): |
184 | 269 | ''' |
185 | 270 | Programs the pulse sequences needed for CWODMR. |
|
0 commit comments