<SYSTEM_TASK:>
Given the following text description, write Python code to implement the functionality described below step by step
<END_TASK>
<USER_TASK:>
Description:
 D4 example notebook
Example notebook of the D4 2 channel, 24-bit ADC module. To use this notebook, we need a D4 module, D5a module and a controller module. The controller can either be the C1b/C2 combination or the C1.
To use the D4 module, we also need to import the SPI rack class. All communication with the SPI Rack will go through this object. Only one SPI_rack object can be active at a time on the PC. So before running another script, the connection in this one should be closed.
We will also import some other libraries for processing and plotting. Matplotlib is used as it is the most common plotting library.
Step1:  Initialisation
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:  Create a new D5a module object at the correct (set) module address using the SPI object. Here we reset the voltages to zero Volt. For information on the D5a module, see the corresponding webpage and the D5a example notebook. We keep the D5a at the default span of +-4 Volt.
Step3:  We now create a new D4 module object in the same way. Make sure that the module number corresponds to the address set in the hardware.
Step4:  Next we set the filters inside the ADCs. These filters determine the analog bandwidth, the 50 Hz rejection, the resolution and the data rate. There are two filter types
Step5:  Calibration
For optimal performance we can calibrate the D4 module for both offset and gain errors. To calibrate the offset error, the user has to put a short or 50 ohm short on the input of the ADC channel that needs to be calibrated. To calibrate the gain error, the user needs to put a 4 Volt signal (from the D5a) on the ADC channel that needs to be calibrated.
Everytime the module/rack is powered down this procedure needs to be performed again. It is recommended to let the system 'warm up' before running the calibration routines.
Step6:  For the gain calibration we connect DAC output 1 to ADC input 1, and DAC output 2 to ADC input 2.
Step7:  Measurements
We can now perform measurements. For both measurements we connect D5a output 1 and 2 to D4 input 1 and 2 respectively. For the first measurement we will just sweep the two DACs in opposite directions and measure with the D4. 
Here we use the start_conversion and get_results functions of the D4. The start conversion function starts a new ADC conversion but does not wait for the result to be ready. This allows us to trigger both ADC channel at (roughly) the same time. After they are both trigger we wait for the results. The get_results function will wait until a result comes in, it will block the rest of the code.
After triggering and converting, the ADC will keep running. This means that if the user waits a long time, the sample that will be read out will not be from the first conversion!
Step8:  Now we just plot the results to see the expected lines.
Step9:  For the second measurement we only look at channel 1. We put the DAC channel at a fixed voltage and take a number of samples. This allows us to take a look at the noise performance. The ADC filter is in setting 16 out of 20 (higher values give better performance with longer conversion times). Setting 16 should give us a bandwith of 13 Hz, 100 dB 50 hz suppression, 23.5 bit resolution and a data rate of 16.67 samples per second.
To read out the ADC, we will use the single_conversion function. This function will both trigger and readout the ADC. It will wait for a result to be returned and will block anything else from running.
Step10:  Now we plot the results on uV scale.
Step11:  When done with the measurement, it is recommended to close the SPI Rack connection. This will allow other measurement scripts to acces the device.
<ASSISTANT_TASK:>
Python Code:
# Import SPI Rack, D5a module and D4 module
from spirack import SPI_rack, D4_module, D5a_module
from time import sleep
import numpy as np
%matplotlib notebook
import matplotlib.pyplot as plt
Explanation: D4 example notebook
Example notebook of the D4 2 channel, 24-bit ADC module. To use this notebook, we need a D4 module, D5a module and a controller module. The controller can either be the C1b/C2 combination or the C1.
To use the D4 module, we also need to import the SPI rack class. All communication with the SPI Rack will go through this object. Only one SPI_rack object can be active at a time on the PC. So before running another script, the connection in this one should be closed.
We will also import some other libraries for processing and plotting. Matplotlib is used as it is the most common plotting library.
End of explanation
COM_speed = 1e6 # Baud rate, doesn't matter much
timeout = 1 # In seconds
spi_rack = SPI_rack('COM4', COM_speed, timeout)
spi_rack.unlock() # Unlock the controller to be able to send data
Explanation: Initialisation
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
D5a = D5a_module(spi_rack, module=2, reset_voltages=True)
Explanation: Create a new D5a module object at the correct (set) module address using the SPI object. Here we reset the voltages to zero Volt. For information on the D5a module, see the corresponding webpage and the D5a example notebook. We keep the D5a at the default span of +-4 Volt.
End of explanation
D4 = D4_module(spi_rack, 6)
Explanation: We now create a new D4 module object in the same way. Make sure that the module number corresponds to the address set in the hardware.
End of explanation
setting = 16
D4.set_filter(adc=0, filter_type='sinc3', filter_setting=setting)
D4.set_filter(adc=1, filter_type='sinc3', filter_setting=setting)
Explanation: Next we set the filters inside the ADCs. These filters determine the analog bandwidth, the 50 Hz rejection, the resolution and the data rate. There are two filter types: sinc3 and sinc5. They both have ups and down, as a rule of thumb: for low frequency/high resolution use sinc3, for high frequency use sinc5. 
The filter setting sets the other variables, it ranges from 0 to 20. How this value relates to the bandwidth etc. can be seen from the filter table on the D4 module webpage.
End of explanation
# First the offset, put 50 Ohm termination on the inputs
D4.offset_calibration(0)
D4.offset_calibration(1)
Explanation: Calibration
For optimal performance we can calibrate the D4 module for both offset and gain errors. To calibrate the offset error, the user has to put a short or 50 ohm short on the input of the ADC channel that needs to be calibrated. To calibrate the gain error, the user needs to put a 4 Volt signal (from the D5a) on the ADC channel that needs to be calibrated.
Everytime the module/rack is powered down this procedure needs to be performed again. It is recommended to let the system 'warm up' before running the calibration routines.
End of explanation
# Next the gain error, apply 4V from the D5a module
D5a.set_voltage(0, 4)
D5a.set_voltage(1, 4)
sleep(1)
D4.gain_calibration(0)
D4.gain_calibration(1)
Explanation: For the gain calibration we connect DAC output 1 to ADC input 1, and DAC output 2 to ADC input 2.
End of explanation
no_points = 20
input_voltage = np.linspace(-4, 4, no_points)
data_ADC1 = np.zeros(no_points)
data_ADC2 = np.zeros(no_points)
for i, value in enumerate(input_voltage):
    D5a.set_voltage(0, value)
    D5a.set_voltage(1, -value)
    
    D4.start_conversion(0)
    D4.start_conversion(1)
    
    data_ADC1[i] = D4.get_result(0)
    data_ADC2[i] = D4.get_result(1)
Explanation: Measurements
We can now perform measurements. For both measurements we connect D5a output 1 and 2 to D4 input 1 and 2 respectively. For the first measurement we will just sweep the two DACs in opposite directions and measure with the D4. 
Here we use the start_conversion and get_results functions of the D4. The start conversion function starts a new ADC conversion but does not wait for the result to be ready. This allows us to trigger both ADC channel at (roughly) the same time. After they are both trigger we wait for the results. The get_results function will wait until a result comes in, it will block the rest of the code.
After triggering and converting, the ADC will keep running. This means that if the user waits a long time, the sample that will be read out will not be from the first conversion!
End of explanation
plt.figure()
plt.plot(input_voltage, data_ADC1, '.-', label='ADC1')
plt.plot(input_voltage, data_ADC2, '.-', label='ADC2')
plt.xlabel('D5a Voltage (V)')
plt.ylabel('D4 Voltage (V)')
plt.legend()
plt.show()
Explanation: Now we just plot the results to see the expected lines.
End of explanation
no_points = 100
D5a.set_voltage(0, 1)
data_ADC1 = np.zeros(no_points)
for i in range(no_points):    
    data_ADC1[i] = D4.single_conversion(0)
Explanation: For the second measurement we only look at channel 1. We put the DAC channel at a fixed voltage and take a number of samples. This allows us to take a look at the noise performance. The ADC filter is in setting 16 out of 20 (higher values give better performance with longer conversion times). Setting 16 should give us a bandwith of 13 Hz, 100 dB 50 hz suppression, 23.5 bit resolution and a data rate of 16.67 samples per second.
To read out the ADC, we will use the single_conversion function. This function will both trigger and readout the ADC. It will wait for a result to be returned and will block anything else from running.
End of explanation
plt.figure()
plt.plot(np.arange(no_points), data_ADC1/1e-6, '.-', label='ADC1')
plt.xlabel('Sample')
plt.ylabel('ADC1 Voltage (uV)')
plt.show()
Explanation: Now we plot the results on uV scale.
End of explanation
spi_rack.close()
Explanation: When done with the measurement, it is recommended to close the SPI Rack connection. This will allow other measurement scripts to acces the device.
End of explanation
<END_TASK>