Edit: Please note that this is still a work in progress, and it seems that some results were obtained by shear luck (as it happens in research). You are advised to read the comments for more updates.
Characterising the size of micro and nano-particles is important in many applications such as protein aggregation and complex fluids studies. For particles larger than about a micron, optical microscopy can be used in conjunction with image analysis softwares. For submicron particles however, more advanced and expensive techniques have to be employed such as electron microscopy or light scattering. In particular, Dynamic Light Scattering (DLS) is widely used for dilute particles suspensions. In DLS, particles size is back-calculated from the way light scattered from a laser beam evolves with time as particles undergo Brownian motion in the fluid. Typical commercial DLS equipments are expensive (typically over 40k£) because of the use of high quality lasers and detectors, allowing measurements over a broad range of particle sizes and concentrations. Some recent equipment can even determine the shape of non-spherical particles!
On the contrary, this project aims at exploring DLS principles and boundaries with low-cost components and open-source design. More broadly, we hope that this approach will also contribute to future projects involving light scattering or high frequency data logging.
IntroductionDynamic Light Scattering works as follows (see figure below): a laser beam is shone onto the sample and is scattered to all directions by the particles (particles dimensions are assumed to be smaller than the laser wavelength). The scattered light is collected at a specific angle from the beam (here 90°). Photons coming from different particles interfere in the detector to yield a specific intensity. Since particles in solutions undergo Brownian motion, this collected intensity varies with time and then size can be deconvoluted from the intensity time series.
The light collected at large angle from a transparent solution is really dim, so modern DLS equipments use expensive components such as high intensity lasers and single photon detectors to enhance their signal to noise ratio and enable an accurate measurement within minutes. This is critical for the smallest particles (a few nanometers) which tend to scatter the fewer light. Also, small particles move faster in solution, requiring high frequency signal sampling (up to 100 kHz). However, the aim of this project is to explore the technical boundaries of the DLS technique with accessible components, at the cost of longer measurement time, limited accuracy and reduced particle size range.
What will we try to measure?
Well-characterized high molecular weight proteins (spectrin) and fibres (cellulose) in solution will be studied in the first instance. Then other types of colloidal systems such as bovine milk and milk substitutes will be investigated.
Hardware design goals- Safety - The case fully encloses the system. The laser is turned off if the case is incidentally opened.
- Usability - Simple to measure a sample.
- Cost - Affordable components and 3d printed case.
- Portability - The device will be built around an Arduino board connected to a PC via a USB port, sending intensity time series. The software will be written in Python.
The image below shows the parts of the device. You may need to adapt some parts depending on your hardware (type of Arduino board, here for a RichUno, type of laser...).
The top and bottom plates are laser cut from opaque sheets. We use white PMMA and spray black matt paint inside the cuvette chamber. Cutting directly from a dark matt material should be a better option. The other parts are 3D printed with black PLA. The heat sinks (Fischer Elektronik SK400-37,5SA) are fixed with double sided tape. One heat sink is used as a beam stopper, in a tilted position to direct the non absorbed beam out of the cuvette chamber. The other heat sink is placed next to the laser module, here in a rather inefficient way (the module should be touching it). But this is a low power laser, so it might be ok for now. The case is assembled with self-tapping screws, (No. 6 x 1/2in long).
Laser and safetyDirect viewing of laser diode emission may cause eye damage. Extreme care must be taken to prevent the beam from being viewed directly or through reflections. Wear protective glasses adapted the wavelength. If there are other people around in the same room they should be wearing protections too.
We use a 650nm 4.5mW laser module from Thorlabs (CPS650F), although cheaper alternatives might be available. The main benefits are that it comes with an integrated current driver and a focusing optics. We just need to provide 5V and since the max current drawn is 60mA, we can use the Vcc (or 5V) port from the Arduino.
Let's see how we compare with two commercial DLS:
Anton Paar Litesizer™ 100 | Malvern Zetasizer Nano S90
Laser power 40 mW 4 mW
Wavelength 658 nm 633 nm
Sensitivity (lysozyme) 0.1 mg/ml 10 mg/ml
So we are close to the Malvern's Zetasizer specs, which has less sensitivity than the Anton Paar (the photodetector may be different as well).
In a normal mode, the device should run with a complete enclosure of the laser (like a CD player). To align and focus the laser, we use the LG4 glasses from Thorlabs, and a piece of white paper. The laser is aligned with the screws of the laser holder being loose. The holes in the base late are large enough to provide some flexibility. The beam should be focused in the centre of the cuvette.
In the picture below, the beam is made visible with a lightly turbid water.
This part is likely to be the most challenging of the project.
Option 1 - The photoresistor
The Biomaker challenge pack comes with a breakout board light sensor from Open-Smart. We tested it and although it is surprisingly responsive (up to 20 kHz), its sensitivity is two low.
Option 2 - Grove Digital Light Sensor
Another Arduino-compatible light sensor. It comes with an integrated ADC, but we found that the sampling rate is limited within the library code. It's not clear why, and we decided to go for the next option:
Option 3 - The photodiode with a custom circuit
The photodiode creates a small current when receiving light. The following trans-impedance circuits are used to convert that current into a (largely) amplified voltage signal readable by the Arduino:
In photovoltaic mode (left), there is no bias voltage applied to the diode. The bias is used to reduce the capacitance of the diode, and so increase the bandwidth (right). The gain is controlled by the feedback resistor Rf
. A larger Rf implies a larger gain, but very large resistors tend to have non-negligible intrinsic capacitance. The capacitor Cf
is used to stabilise the op-amp, but it reduces the bandwidth.
Components:
- Photodiode: Vishay, BPW24R;
- Opamp: TLC082IP Texas Instruments
- In photovoltaic mode, Rf = 1 to 10 MOhm, Cf = none. We were unable to make the photodiode work in biased mode (not clear why).
To test the detector, we place an LED in front of it (no need to use the laser at this stage), with a diffuser to avoid saturating the photodiode. A function generator drives the LED with a square signal at a specified frequency. The output voltage of the detector is sampled by the Arduino (more on that below).
We go for a 15 microseconds time step (67 kHz sampling). In theory we can sample a 33 kHz signal, but with a 5 kHz signal, we get more points per period. The first finding is that the 5 MOhm Rf resistor has too large a capacitance, as we can see:
With the 1 MOhm Rf resistor, we get a weaker, but sharp square wave:
So we go for a Rf = 1 MOhm, and, since our op-amp has two channels, we use the second channel to amplify further. There is a high pass filter in front of the second channel to get rid of the offset from the first channel. A better system would use an op-amp with a null-offset function... A logarithmic potentiometer is used to adjust the gain of the second stage. Then a low-pass filter is used to suppress high frequencies that cannot be handled by the ADC (edit: it turns out that the low-pass is not really needed).
Rf1 = 1 MOhm, CHP = 220 nF, RHP = 47 kOms, Rf2 = 0-50 kOhm, R0 = 10 Ohm, RLP = 100 Ohm, CLP=47nF,
If we put a stronger diffuser in front of the LED, we don't get anything from the first channel, but we get a somewhat distorted signal from the second channel:
Being able to adjust the gain is also important because various solutions samples scatter light at different amplitude.
The last part missing is an optics for the detector. At the moment it collects light from a rather broad angle (12°), but it is not clear for the moment whether it has a significant effect on the system. Should a focusing lens and a pinhole be used?
Option 4 - The photodiode in avalanche modeSampling data from the detector
Not implemented yet! The idea is to provide a large bias voltage (100V) to the photodiode in order to produce a large internal gain. When the bias is about the breakdown voltage, the diode is running in Geiger mode.
We will try to use only Arduino capabilities to sample the photodiode signal at about 67 kHz. By default, Arduino Uno can sample around 10kHz, without taking into account the overhead due to serial communication. So some work is required to speed things up. Fortunately, many examples are available on the net. This exhaustive documentation by Willem Maes can help: magelhaes.hzs.be/willem/Arduino/speeding.pdf. We can act at three levels:
- First, the ADC (analogue to digital converter) clock is set by default at a much lower speed than the ATmega. We can change the clock speed in the code.
- Second, the ADC can be used in free-run mode, in which the ADC continuously converts input, saving function calling overhead.
- Third, values are stored in a buffer (here 800 values of 16 bits) before calling the serial communication sender. The size of the buffer is limited by the memory available on the chip. Here 800 values are ok to sample 12ms at 67kHz.
Currently the first and third points are implemented. The following plot shows a 20 kHz square wave made by a function generator and sampled by the Arduino through pin A0 and analogRead
function with a time step of 8.5 microseconds (117kHz).
Let's have a look at the actual code. The first lines define variables:
const unsigned int numReadings = 800;
unsigned int analogVals[numReadings];
long t, t0;
Then we change the clock speed of the ADC. We also setup the Serial port:
#ifndef cbi
#define cbi(sfr, bit) (_SFR_BYTE(sfr) &= ~_BV(bit))
#endif
#ifndef sbi
#define sbi(sfr, bit) (_SFR_BYTE(sfr) |= _BV(bit))
#endif
void setup() {
Serial.begin(115200);
// set prescale to 16
sbi(ADCSRA, ADPS2) ; // cbi means clear bit
cbi(ADCSRA, ADPS1) ; // sbi means set bit
cbi(ADCSRA, ADPS0) ;
}
The main loop start by collecting the 800 points, and then send the table analogVals
to the computer, ending by the total duration of the time series (t
).
void loop() {
t0 = micros();
for (int i=0; i < numReadings ; i++)
{
analogVals[i] = analogRead(A0);
}
t = micros()-t0; // calculate elapsed time
// Send to computer
for (int i=0; i < numReadings ; i++)
{
Serial.print(analogVals[i]);
Serial.print(',');
}
Serial.println(t);
delay(10);
}
Analysing the dataWe use Python (here 2.7) with a few libraries installed (serial, numpy, matplotlib, and scipy). To use the script, open it with a text editor and check the definitions of the variables (in particular the Arduino address and everything related to your experimental conditions).
address='/dev/ttyUSB0' # Arduino address
baud=115200 # baud for serial communication
lambd = 650e-9 # [m] Laser wavelength
n_s = 1.33 # Solvent refractive index at wavelength
k = 1.380649e-23 # [j/k] Boltzmann constant
T = 293 # [K] Temperature
eta_s = 0.001 # [Pa.s] Solvent viscosity measurement at T
theta = np.pi/2 # Scattering angle
Then launch in a terminal, followed by the number of time series you want to get from the device (one by default):
python2 OpenDLS.py 1
Let's have a look at our first signal. After playing with the gain, we get a noisy curve but with strong low frequency variations:
At this stage, it is not clear if the peaks are due to dust particles reflecting the beam. Also, the auto-correlation function has a funny tail: this is because the time series is not long enough to have significant statistics on the long relaxation times.
To improve this, we average over 1000 measurements;
python2 OpenDLS.py 1000
Now the tail is fixed, although it is still very noisy. We are ready to analyze the data!
For monodispersed spheres, the auto-correlation function should be a decreasing exponential, therefore we fit a
, b
and c
in:
g(tau) = a + b*exp(-c*tau)
with c
being in theory 2*q**2*D. q is the scattering vector:
q = 4*pi*n_s*sin(theta/2)/lambda, n_s is the refractive index of the solvent, theta is the scattering angle, and lambda the wavelength of the laser.
and D is the diffusion coefficient of the particles from the Stokes-Einstein relation:
D = k*T/(6*pi*Rh*eta_s), with k the Boltzmann constant, T the temperature, Rh the hydrodynamic radius of the particle and eta_s the viscosity of the solvent.
Example with a 200nm polystyrene dispersionWe have a standard dispersion of 188 (+/-4) nm polystyrene beads 2.2% mass in water (Sigma-Aldrich 95581) which comes as white and opaque as milk. When diluted down to 0.01% mass dispersion, we get a transparent but slightly cloudy liquid that scatters enough light to the detector. The following plot is the average from 1000 time series:
We fit a 167 nm particle size, with the noise contained in a +/-20% window (at the slope of the curve). It is repeatable and very promising!
The systematic underestimation of particle size and strong noise can come from multiple scattering of the beam before reaching the detector. This could be improved by dilution of the sample, but then the detection would be harder, as we are here playing with the limit of our system.
Averaging over more time series does not help reducing the noise, as we can see from this 10,000 average, which to 45 min to complete:
Ideally, we would need to repeat this experiment with other particle sizes to see if the correlation we observe really comes from the liquid, and not from the hardware.Conclusion
This open source DLS device seems very promising for dispersions that scatter enough light to the cheap detector, although the accuracy is still low. We hope that some of the building blocks and code can be useful for other projects.
Regarding the DLS, things for the next version:
- An automatic switch off of the laser when the cover is open.
- A better detector for transparent solutions (op-amp with offset-null function, photo-diode in avalanche mode or Geiger mode, an aperture-pinhole system to select a precise scattering angle).
- A temperature probe to adjust temperature and viscosity in the fit.
- With the current detector, studying back-scattered light from turbid liquids.
Comments