<SYSTEM_TASK:>
Given the following text description, write Python code to implement the functionality described below step by step
<END_TASK>
<USER_TASK:>
Description:
 D5b example notebook
Example notebook of the D5b, 8 channel 18-bit module. The module contains the same DACs as in the 16 channel D5a module, but it also contains an ARM microcontroller. This allows for operations where exact timing is needed. 
At the moment of writing it supports setting the DACs in a normal DC mode, or allows them to toggle between two values with fixed and precise timing. This toggling happens after an external trigger (by another module for example). It can be used in conjuction with a B2 or D4b module to act as a lock-in system. The D5b sends out triggers at every DAC toggle, this allows the B2/D4a to sync the measurements to this change
SPI Rack setup
To use the D5b module, we need to import both the D5b_module and the SPI_rack module from the spirack library. All the communication with the SPI Rack runs through the SPI_rack object which communicates through a virtual COM port. This COM port can only be open on one instance on the PC. Make sure you close the connection here before you can use it somewhere else.
Import the logging library to be able to display the logging messages.
Step1:  Open the SPI rack connection and unlock the controller. This is necessary after bootup of the controller module. If not unlocked, no communication with the modules can take place. The virtual COM port baud rate is irrelevant as it doesn't change the actual speed. Timeout can be changed, but 1 second is a good value.
Step2:  Read back the version of the microcontroller software. This should return 1.6 or higher to be able to use the D5b properly. Als read the temperature and the battery voltages through the C1b, this way we verify that the connection with the SPI Rack is working.
Step3:  Create a new D5b module object at the correct module address using the SPI object. By default the module resets the output voltages to 0 Volt. Before it does this, it will read back the current value. If this value is non-zero it will slowly ramp it to zero. If reset_voltages = False then the output will not be changed.
To see that the we have a connection, we read back the firmware version.
Step4:  Configuring the D5b
The D5b module can run from either a local (inside the module) clock or a user provided clock from the backplane. This backplane clock should be 10 MHz and either a square or a sine wave. If there are more modules with microcontrollers in the rack, and they need to run synchronously, it is recommended to use the backplane clock. For a single module it is fine to run it using the local clock.
If the external clock is selected but not present, the user will get an ERROR to the logger and the microcontroller will keep running on the internal clock. Never turn off the external clock if the microcontroller is running on it. This will stop the module from functioning.
In this example we will use the internal clock
Step5:  The toggle time of the DACs is set in steps of 100 ns (the 10 MHz clock) with a minimum of 30 &mu;s. We need to input a value as a multiple of this 100 ns. The toggle amount should be an even number with a minimum of two.
Step6:  The module will start toggling after it receives a trigger from the backplane (either directly controlled from the PC or from another module). If there are any filters and delays in the setup, we might want to wait with toggling the DAC before these are settled. This is what the hold-off time is for. It can be set in steps of 100 ns, with a minimum of 30 &mu;s. This time should be set in seconds.
Step7:  Running the D5b
We will now set two DAC outputs to toggling mode and &pm;4V span. DAC 0 we will set to toggle &pm;2V around 0V and DAC1 to toggle between &pm;2V around 1V.
For more details on the span, stepsize and voltages. See the documentation on the website and the D5a module example notebook.
Step8:  Here we generate the trigger directly from the PC to demonstrate the usage.
Step9:  You should be able to see the following output on your oscilloscope (minus the two top traces)
<ASSISTANT_TASK:>
Python Code:
# Import SPI rack and D5b module
from spirack import SPI_rack, D5b_module
import logging
logger = logging.getLogger(__name__)
logging.basicConfig(level=logging.INFO)
Explanation: D5b example notebook
Example notebook of the D5b, 8 channel 18-bit module. The module contains the same DACs as in the 16 channel D5a module, but it also contains an ARM microcontroller. This allows for operations where exact timing is needed. 
At the moment of writing it supports setting the DACs in a normal DC mode, or allows them to toggle between two values with fixed and precise timing. This toggling happens after an external trigger (by another module for example). It can be used in conjuction with a B2 or D4b module to act as a lock-in system. The D5b sends out triggers at every DAC toggle, this allows the B2/D4a to sync the measurements to this change
SPI Rack setup
To use the D5b module, we need to import both the D5b_module and the SPI_rack module from the spirack library. All the communication with the SPI Rack runs through the SPI_rack object which communicates through a virtual COM port. This COM port can only be open on one instance on the PC. Make sure you close the connection here before you can use it somewhere else.
Import the logging library to be able to display the logging messages.
End of explanation
COM_port = 'COM4' # COM port of the SPI rack
COM_speed = 1e6   # Baud rate, not of much importance
timeout = 1       # Timeout value in seconds
spi_rack = SPI_rack(COM_port, COM_speed, timeout)
spi_rack.unlock() # Unlock the controller to be able to send data to the rack
spi_rack.unlock()
Explanation: Open the SPI rack connection and unlock the controller. This is necessary after bootup of the controller module. If not unlocked, no communication with the modules can take place. The virtual COM port baud rate is irrelevant as it doesn't change the actual speed. Timeout can be changed, but 1 second is a good value.
End of explanation
print('Version: ' + spi_rack.get_firmware_version())
print('Temperature: {:.2f} C'.format(spi_rack.get_temperature()))
battery_v = spi_rack.get_battery()
print('Battery: {:.3f}V, {:.3f}V'.format(battery_v[0], battery_v[1]))
Explanation: Read back the version of the microcontroller software. This should return 1.6 or higher to be able to use the D5b properly. Als read the temperature and the battery voltages through the C1b, this way we verify that the connection with the SPI Rack is working.
End of explanation
d5b = D5b_module(spi_rack, module=1, reset_voltages=True)
print("Firmware version: {}".format(d5b.get_firmware_version()))
Explanation: Create a new D5b module object at the correct module address using the SPI object. By default the module resets the output voltages to 0 Volt. Before it does this, it will read back the current value. If this value is non-zero it will slowly ramp it to zero. If reset_voltages = False then the output will not be changed.
To see that the we have a connection, we read back the firmware version.
End of explanation
d5b.set_clock_source('internal')
print("Clock source: {}".format(d5b.get_clock_source()))
Explanation: Configuring the D5b
The D5b module can run from either a local (inside the module) clock or a user provided clock from the backplane. This backplane clock should be 10 MHz and either a square or a sine wave. If there are more modules with microcontrollers in the rack, and they need to run synchronously, it is recommended to use the backplane clock. For a single module it is fine to run it using the local clock.
If the external clock is selected but not present, the user will get an ERROR to the logger and the microcontroller will keep running on the internal clock. Never turn off the external clock if the microcontroller is running on it. This will stop the module from functioning.
In this example we will use the internal clock:
End of explanation
d5b.set_toggle_time(300)
toggle_value = d5b.get_toggle_time()
print('Toggle time: {} x 100 ns = {} s'.format(toggle_value, round(toggle_value*100e-9, 7)))
d5b.set_toggle_amount(6)
print('Toggle amount: {}'.format(d5b.get_toggle_amount()))
Explanation: The toggle time of the DACs is set in steps of 100 ns (the 10 MHz clock) with a minimum of 30 &mu;s. We need to input a value as a multiple of this 100 ns. The toggle amount should be an even number with a minimum of two.
End of explanation
d5b.set_trigger_holdoff_time(30e-6)
print('Holdoff time: {} s'.format(d5b.get_trigger_holdoff_time()))
Explanation: The module will start toggling after it receives a trigger from the backplane (either directly controlled from the PC or from another module). If there are any filters and delays in the setup, we might want to wait with toggling the DAC before these are settled. This is what the hold-off time is for. It can be set in steps of 100 ns, with a minimum of 30 &mu;s. This time should be set in seconds.
End of explanation
DAC = 0
d5b.set_DAC_span(DAC, '4V_bi')
print("Span DAC {}: {}".format(DAC, d5b.get_DAC_span(DAC)))
d5b.set_DAC_mode(DAC, 'toggle')
print("DAC {} mode: {}\n".format(DAC, d5b.get_DAC_mode(DAC)))
d5b.set_DAC_voltage(DAC, 0)
d5b.set_DAC_neg_toggle_voltage(DAC, -2)
d5b.set_DAC_pos_toggle_voltage(DAC, 2)
values = d5b.get_DAC_voltages(DAC)
print('Voltage: {:.3f} V\nNegative Toggle: {:.3f} V\nPositive Toggle: {:.3f} V'.format(values[0],values[1],values[2]))
DAC = 1
d5b.set_DAC_span(DAC, '4V_bi')
print("Span DAC {}: {}".format(DAC, d5b.get_DAC_span(DAC)))
d5b.set_DAC_mode(DAC, 'toggle')
print("DAC {} mode: {}\n".format(DAC, d5b.get_DAC_mode(DAC)))
d5b.set_DAC_voltage(DAC, 1)
d5b.set_DAC_neg_toggle_voltage(DAC, -1)
d5b.set_DAC_pos_toggle_voltage(DAC, 3)
values = d5b.get_DAC_voltages(DAC)
print('Voltage: {:.3f} V\nNegative Toggle: {:.3f} V\nPositive Toggle: {:.3f} V'.format(values[0],values[1],values[2]))
Explanation: Running the D5b
We will now set two DAC outputs to toggling mode and &pm;4V span. DAC 0 we will set to toggle &pm;2V around 0V and DAC1 to toggle between &pm;2V around 1V.
For more details on the span, stepsize and voltages. See the documentation on the website and the D5a module example notebook.
End of explanation
spi_rack.trigger_now()
Explanation: Here we generate the trigger directly from the PC to demonstrate the usage.
End of explanation
spi_rack.close()
Explanation: You should be able to see the following output on your oscilloscope (minus the two top traces):
<img src="Images/Scope_Image.png" alt="Scope Image" title="Scope Image" width="850" />
When done with this example, it is recommended to close the SPI Rack connection. This will allow other measurement scripts to access the device.
End of explanation
<END_TASK>