Processing math: 100%

Monday, January 20, 2014

SR850 lock-in amplifier, with PyVISA

It's a breeze, as always, to connect instruments to Python. This is a PyVISA sub-class that you can use with an SR850 lock-in amplifier.


# Talk to SR850 lockin amplifier
# version 1, 2014-01-20
# Amar
import visa
import numpy as np
from datetime import date
import time
class LIA(visa.SerialInstrument):
# Lock-in amplifier, SR850
def __init__(self,portName):
visa.SerialInstrument.__init__(self,portName)
self.timeout = 3
self.id = self.ask("*IDN?")
def characterize(self):
warnings = ""
print self.id
print "Sensitivity = ", self.ask("SENS?")
print "Reserve = ", self.ask("RSRV?")
if self.ask("RSRV?")!="0": warnings+= "** Warning! Dynamic reserve is not 0 dB ** \n"
print "Time constant = ", self.ask("OFLT?")
print "Traces = ", self.ask("TRCD?1"), self.ask("TRCD?2"), self.ask("TRCD?3"), self.ask("TRCD?4")
FMOD = self.ask("FMOD?")
if FMOD == "0": FMODString = "internal"
elif FMOD == "1": FMODString = "internal sweep"
elif FMOD == "2": FMODString = "external"
print "Ref. source = ", FMODString
print "Ref. frequency = ", self.ask("FREQ?")
print "Ref. phase = ", self.ask("PHAS?")
print "Ref. sine level = ", self.ask("SLVL?")
print
print warnings
print
def setf(self,fi):
self.write("FREQ " + `fi`)
def setphase(self,p):
self.write("PHAS " +`p`)
def getAverage(self,channel=1,N=400):
# channel #s: 1 = X, 2 = Y, 3 = R, 4 = Theta
S = []
for i in range(N): S.append( float(self.ask("OUTP?" + `channel`)) )
S_avg = np.average(S)
S_std = np.std(S)
return S_avg, S_std
def scanP(self,f,channel=1,N=100,std=False):
# channels: 1 = X, 2 = Y, 3 = R, 4 = Theta
output, err = [], []
for fi in f:
self.setf(fi)
print self.ask("FREQ?"),
x = []
for i in range(N): x.append( float(self.ask("OUTP?"+`channel`)) )
x_avg = np.average(x)
output.append( x_avg )
x_std = np.std(x)
err.append( x_std )
if std: return output, err
else: return output
def scanR(self,f,trace=1,N=100,std=False):
# traces: 1,2,3,4
output, err = [], []
for fi in f:
self.setf(fi)
print self.ask("FREQ?"),
x = []
for i in range(N): x.append( float(self.ask("OUTR?"+`trace`)) )
x_avg = np.average(x)
output.append( x_avg )
x_std = np.std(x)
err.append( x_std )
if std: return output, err
else: return output
view raw SR850-lockin.py hosted with ❤ by GitHub

 Here is how to use these utility functions to measure a noise spectrum with the SR850. (Watch out for the dynamic reserve when making noise measurements!)

# SR850 as a spectrum analyzer
# version 1, 2014-01-21
# Amar
import os
os.chdir("c:/data/googledrive/code")
import SR850
import numpy as np
import pylab as plt
lia= SR850.LIA("COM7")
lia.characterize()
lia.write("TRCD 1,5,0,0,0") # set trace 1 to Xn (rms noise in X quadrature, V/rt-Hz)
lia.write("TRCD 2,6,0,0,0") # set trace 2 to Yn (rms noise in Y quadrature, V/rt-Hz)
background = True
Navg = 1000
f = 10**np.arange(0,4,0.2)
Xn, Xn_err = np.array( lia.scanR(f,trace=1,N=Navg,std=True) )
if background:
print
foo = raw_input("Ready for background measurement?")
Xn_bg, Xn_bg_err = np.array( lia.scanR(f,trace=1,N=Navg,std=True) )
lia.close()
plt.figure(1)
plt.title("Current noise spectrum with lock-in \n G = 1e9 V/A, 10 mV FS, 0 dB reserve")
plt.xlabel("Frequency, f [Hz]")
plt.ylabel("Current noise, \sqrt{W_I} [A/\sqrt{\mathrm{Hz}}]")
plt.xscale('log')
plt.yscale('log')
plt.errorbar(f,np.sqrt(2) * Xn/1e9,yerr = 20 * np.sqrt(2) * Xn_err/1e9,color='b',marker='o',lw=2,label="Beam on")
if background:
plt.errorbar(f,np.sqrt(2) * Xn_bg/1e9,yerr = 20 * np.sqrt(2) * Xn_bg_err/1e9,color='g',marker='^',lw=2,label="Background")
plt.plot(f,np.sqrt(2*1.6e-19*1.7e-9)*np.ones(len(f)),'b--',lw=4, alpha=0.5,label="Shot noise")
plt.legend(loc='upper right')
plt.show()