Skip to content
Projects
Groups
Snippets
Help
Loading...
Sign in
Toggle navigation
W
White Rabbit Calibration
Project
Project
Details
Activity
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
1
Issues
1
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
Wiki
Wiki
image/svg+xml
Discourse
Discourse
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Commits
Issue Boards
Open sidebar
Projects
White Rabbit Calibration
Commits
f6638d51
Commit
f6638d51
authored
Dec 22, 2020
by
Peter Jansweijer
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
add Siglent_SDS1000X (hobby) oscilloscope support
parent
9bc9f91b
Hide whitespace changes
Inline
Side-by-side
Showing
1 changed file
with
808 additions
and
0 deletions
+808
-0
Siglent_SDS1000X.py
sw/lib/Siglent_SDS1000X.py
+808
-0
No files found.
sw/lib/Siglent_SDS1000X.py
0 → 100644
View file @
f6638d51
#!/usr/bin/python
"""
Siglent SDS1000X remote control
-------------------------------------------------------------------------------
Copyright (C) 2020 Peter Jansweijer, 'stolen' from Tjeerd Pinkert and freely
adjusted
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>
-------------------------------------------------------------------------------
Usage:
Siglent_SDS1000X.py <IP#> [-c 1234] [-a<averages>] [-o <dir>] [-m <measurments>]
Siglent_SDS1000X.py -h | --help
IP IP number of the Oscilloscope
(for example: 192.168.178.31)
Options:
-h --help Show this screen.
-c 1234 channel number(s)
-a number of averages, default: 1
-o <dir> optional directory for output file storage, default: "data/"
-m <number> number of measurement output files
"""
import
os
import
sys
import
time
import
scipy
import
struct
import
datetime
import
pdb
#TJP: installed from web python-vxi Alex
import
vxi11
import
matplotlib.pyplot
as
plt
plt
.
rcParams
[
'lines.linewidth'
]
=
1
############################################################################
def
get_sweeps
(
scope
,
chan_str
):
sweep_str
=
str
(
scope
.
ask
(
chan_str
+
':INSPECT? SWEEPS_PER_ACQ'
))
.
split
(
':'
)[
2
]
# returns something like:
# u'C1:INSP "SWEEPS_PER_ACQ : 30 "'
sweep
=
int
(
sweep_str
.
strip
(
'"'
)
.
strip
())
# strip the last " and spaces before cast to int
return
sweep
############################################################################
def
replace_comma
(
str
):
"""
Replace any comma or space in str by an underscore
"""
newstr
=
""
for
i
in
str
:
if
(
i
==
","
or
i
==
" "
):
newstr
=
newstr
+
"_"
else
:
newstr
=
newstr
+
i
return
newstr
############################################################################
def
get_waveforms
(
scope
,
channels
=
[
1
,
2
,
3
,
4
],
num_avg
=
1
,
output_dir
=
"data"
):
"""
Measure and save Siglent SDS1000X waveforms
scope -- instance of python-vxi connected to the oscilloscope
channels -- channels that are going to be measured for example '1,2'
num_avg -- the number of averages taken by the oscilloscope
output_dir -- name of the directory where measured waveform files will be stored
the file output format is as described below:
----------------------------------
#WaveformData:Siglent SDS1000X
#version:0.2
#type:RAW
#channel:1,2,..
#time:21 Jan 2016 11:03:38
#byteorder:LSBFIRST
#preamble:
<channel 1 preamble>
#preamble:
<channel 2 preamble>
#waveform_data:
<channel 1 data>
#waveform_data:
<channel 2 data>
----------------------------------
"preamble" data is a comma separated string
"waveform_data" is binary data
"""
#scope.write(":SYSTem:HEADer OFF")
#scope.write(":ACQuire:MODE RTIME")
#scope.write(":ACQuire:COMPlete 100")
#scope.write(":WAVeform:BYTeorder LSBFirst")
#scope.write(":WAVeform:FORMat WORD")
# Set COMMAND_HEADER response mode. Message decode depends on the returned format.
scope
.
write
(
"CHDR OFF"
)
# WAVEFORM_SETUP:
# SP = 1 sends all data points.
# NP = 0 sends all data points.
# FP = 0 corresponds to the first data point.
scope
.
write
(
"WAVEFORM_SETUP SP,1,NP,0,FP,0"
)
# Changing ACQUIRE_WAY is only acceptes when ACQUIRE is in ARM mode
'''
scope.write("ARM")
if num_avg > 1:
scope.write("ACQUIRE_WAY AVERAGE")
scope.write("AVERAGE_ACQUIRE "+str(num_avg))
else:
scope.write("ACQUIRE_WAY SAMPLING")
'''
scope
.
write
(
"STOP"
)
#Stop any running acquisition
#scope.write(":ACQuire:POINts "+str(record_len))
for
chan
in
channels
:
scope
.
write
(
"C"
+
str
(
chan
)
+
":TRACE ON"
)
# Arm single trigger
scope
.
write
(
"TRIG_MODE SINGLE"
)
scope
.
write
(
"ARM"
)
# Wait for trigger (INR bit 0: "A new signal has been acquired")
# and clear triggered bit in INternal state change Register (INR).
while
(
int
(
scope
.
ask
(
"INR?"
))
&
1
!=
1
):
pass
scope
.
ask
(
"INR?"
)
# add trailing slash if not present
output_dir
=
os
.
path
.
join
(
output_dir
,
''
)
if
os
.
path
.
exists
(
output_dir
)
!=
True
:
os
.
mkdir
(
output_dir
)
print
(
"Output directory does not exist => created: "
+
output_dir
)
file_header
=
"#WaveformData:Siglent SDS1000X
\n
"
file_header
+=
"#version:0.2
\n
"
file_header
+=
"#type:RAW
\n
"
file_header
+=
"#channel:"
for
chan
in
channels
:
file_header
+=
str
(
chan
)
+
","
file_header
+=
"
\n
"
timestamp
=
datetime
.
datetime
.
now
()
#micro seconds timing
filename
=
output_dir
+
timestamp
.
strftime
(
"
%
y
%
m
%
dT
%
H
%
M
%
S_
%
f"
)
+
"_scope_siglent_sds1000x_bin"
file_header
+=
"#date:"
+
timestamp
.
strftime
(
"
%
d
%
b
%
Y"
)
+
"
\n
"
file_header
+=
"#time:"
+
timestamp
.
strftime
(
"
%
H:
%
M:
%
S"
)
+
"
\n
"
print
(
"save waveform into file:"
,
filename
)
file_header
+=
"#byteorder:LSBFIRST
\n
"
channel_preamble
=
[]
channel_data
=
[]
_types
=
{
"AVERAGE"
:
2
,
"SAMPLING"
:
6
,
"PEAK_DETECT"
:
10
,
"HIGH_RES"
:
11
,
}
_couplings
=
{
"A1M"
:
0
,
"D1M"
:
1
,
"D50"
:
2
,
"GND"
:
10
,
"A50"
:
11
,
}
_units
=
{
"UNKNOWN"
:
0
,
"V"
:
1
,
"SECOND"
:
2
,
"CONSTANT"
:
3
,
"A"
:
4
,
"DECIBEL"
:
5
,
}
# Bandwidth Limit is returned for all channels (irrespective if enabled)
bwl
=
scope
.
ask
(
"BANDWIDTH_LIMIT?"
)
.
split
(
","
)
bwl_ch
=
[]
for
chan
in
range
(
4
):
if
bwl
[
chan
*
2
+
1
]
==
"ON"
:
bwl_ch
.
append
(
20E6
)
# 20 MHz
else
:
bwl_ch
.
append
(
100E6
)
# 100 MHz
# Detect which python version is used:
python_version
=
sys
.
version_info
[
0
]
# Python 3.x uses type <bytes> instead of <str> (Pyhton 2.x)
# scope.read() returns <str>
# scope.read_raw() returns <bytes>
# returned data = <bytes>
if
python_version
==
3
:
for
chan
in
channels
:
scope
.
write
(
"C"
+
str
(
chan
)
+
":WAVEFORM? DAT2"
)
rawdata
=
scope
.
read_raw
()
# Read first 16 bytes and find the "#" marker
mrk_idx
=
rawdata
[:
16
]
.
decode
(
"utf-8"
)
.
index
(
'#'
)
# First byte after "#" is the amount of bytes used ofr the waveform size
size_byt
=
int
(
rawdata
[
mrk_idx
+
1
:
mrk_idx
+
2
]
.
decode
(
"utf-8"
))
size_wav
=
int
(
rawdata
[
mrk_idx
+
2
:
mrk_idx
+
2
+
size_byt
]
.
decode
(
"utf-8"
))
# Siglent SDS1000X does not read a dedicated preamble; we have to construct it ourselves
# Construct channel preamble
ch_preamble_bytes
=
(
b
"#preamble:
\n
"
)
# format = 1 = BYTE
ch_preamble_bytes
+=
(
b
"1,"
)
# type = SAMPLING,PEAK_DETECT,AVERAGE,HIGH_RES
acquire_way
=
scope
.
ask
(
"ACQUIRE_WAY?"
)
ch_preamble_bytes
+=
(
str
(
_types
[(
acquire_way
.
split
(
","
)[
0
])])
.
encode
()
+
b
","
)
# points
ch_preamble_bytes
+=
(
str
(
int
(
rawdata
[
mrk_idx
+
2
:
mrk_idx
+
2
+
size_byt
]))
.
encode
()
+
b
","
)
# count (when type is AVERAGE)
if
len
(
acquire_way
.
split
(
","
))
>
1
:
ch_preamble_bytes
+=
(
str
(
acquire_way
.
split
(
","
)[
1
])
.
encode
()
+
b
","
)
# count = average
else
:
ch_preamble_bytes
+=
(
b
"1,"
)
# count = 1
# x_increment (= 1/Sample Rate)
ch_preamble_bytes
+=
(
str
(
1
/
float
(
scope
.
ask
(
"SARA?"
)))
.
encode
()
+
b
","
)
# x_origin (Note: SDS1000X display has 14 horizontal divisions, hence origin = tdiv)
ch_preamble_bytes
+=
((
str
(
float
(
scope
.
ask
(
"TIME_DIV?"
))
*
(
-
14
/
2
)))
.
encode
()
+
b
","
)
# x_reference
ch_preamble_bytes
+=
(
scope
.
ask
(
"TRIG_DELAY?"
)
.
encode
()
+
b
","
)
# y_increment, y_origin: see Digital Oscilloscopes Programming Guide PG01-E02C, Page 256:
# voltage value (V) = code value *(vdiv /25) - voffset.
# y_increment
ch_preamble_bytes
+=
((
str
(
float
(
scope
.
ask
(
"C"
+
str
(
chan
)
+
":VDIV?"
))
/
(
25
)))
.
encode
()
+
b
","
)
# y_origin
ch_preamble_bytes
+=
((
str
(
float
(
scope
.
ask
(
"C"
+
str
(
chan
)
+
":OFST?"
))
*
(
1
)))
.
encode
()
+
b
","
)
# y_reference
ch_preamble_bytes
+=
(
b
"0.0,"
)
# coupling _coupling
ch_preamble_bytes
+=
(
str
(
_couplings
[(
scope
.
ask
(
"C"
+
str
(
chan
)
+
":COUPLING?"
)
.
split
(
","
)[
0
])])
.
encode
()
+
b
","
)
# x_display_range
ch_preamble_bytes
+=
(
b
"0.0,"
)
# x_display_origin
ch_preamble_bytes
+=
(
b
"0.0,"
)
# y_display_range
ch_preamble_bytes
+=
(
b
"0.0,"
)
# y_display_origin
ch_preamble_bytes
+=
(
b
"0.0,"
)
# date
ch_preamble_bytes
+=
(
b
"
\"
"
+
timestamp
.
strftime
(
"
%
d
%
b
%
Y"
)
.
encode
()
+
b
"
\"
,"
)
# time
ch_preamble_bytes
+=
(
b
"
\"
"
+
timestamp
.
strftime
(
"
%
H:
%
M:
%
S"
)
.
encode
()
+
b
"
\"
,"
)
# frame_model
id_str
=
scope
.
ask
(
"*IDN?"
)
ch_preamble_bytes
+=
(
b
"
\"
"
+
replace_comma
(
id_str
)
.
encode
()
+
b
"
\"
,"
)
# acquisition_mode (= "RTIME")
ch_preamble_bytes
+=
(
b
"0,"
)
# completion
ch_preamble_bytes
+=
(
b
"100,"
)
# x_units _units (2:"SECOND")
ch_preamble_bytes
+=
(
b
"2,"
)
# y_units _units
ch_preamble_bytes
+=
(
str
(
_units
[(
scope
.
ask
(
"C"
+
str
(
chan
)
+
":UNIT?"
))])
.
encode
()
+
b
","
)
# bandwidth_maximum
ch_preamble_bytes
+=
(
str
(
bwl_ch
[
int
(
chan
)
-
1
])
.
encode
()
+
b
","
)
# bandwidth_minimum
ch_preamble_bytes
+=
(
b
"0.0
\n
"
)
ch_preamble_bytes
+=
(
b
"#preamble_end:
\n
"
)
channel_preamble
.
append
(
ch_preamble_bytes
)
ch_data_bytes
=
(
b
"#waveform:
\n
"
)
ch_data_bytes
+=
rawdata
[
mrk_idx
+
2
+
size_byt
:
mrk_idx
+
2
+
size_byt
+
size_wav
]
channel_data
.
append
(
ch_data_bytes
)
# Raw data should end with \n\n
if
(
rawdata
[
mrk_idx
+
2
+
size_byt
+
size_wav
:]
!=
b
"
\n\n
"
):
print
(
"### Incomplete waveform for channel "
+
str
(
chan
)
+
"
\n
"
)
sys
.
exit
()
# Python 3 write bytes
file
=
open
(
filename
,
"wb"
)
# Write out file_header, followed by all preambles,
# followed by all descriptors, followed by all channel_data
file
.
write
(
file_header
.
encode
())
# encode() <str> into <bytes>
data
=
file_header
.
encode
()
for
i
in
channel_preamble
:
data
+=
i
file
.
write
(
i
)
for
i
in
channel_data
:
data
+=
i
file
.
write
(
i
)
file
.
close
()
# Python 2.x uses type <str>
# scope.read() returns <unicode>
# scope.read_raw() returns <str>
# returned data = <list> with items type <str>
elif
python_version
==
2
:
print
(
"### Python 2.x not supported
\n
"
)
sys
.
exit
()
return
data
,
filename
############################################################################
def
preamble_string_to_dict
(
preamble_string
):
"""
Create DSO preamble dict from raw preamble string
preamble_string -- raw preamble string as obtained from oscilloscope query
returns: dict with parsed preamble string
"""
_formats
=
{
0
:
"ASCII"
,
1
:
"BYTE"
,
2
:
"WORD"
,
3
:
"LONG"
,
}
_types
=
{
1
:
"RAW"
,
2
:
"AVERAGE"
,
3
:
"VHISTOGRAM"
,
4
:
"HHISTOGRAM"
,
6
:
"INTERPOLATE"
,
10
:
"PDETECT"
,
}
_couplings
=
{
0
:
"AC"
,
1
:
"DC"
,
2
:
"DCFIFTY"
,
3
:
"LFREJECT"
,
10
:
"GND"
,
11
:
"ACFIFTY"
,
}
_acquisition_modes
=
{
0
:
"RTIME"
,
1
:
"ETIME"
,
3
:
"PDETECT"
,
}
_units
=
{
0
:
"UNKNOWN"
,
1
:
"VOLT"
,
2
:
"SECOND"
,
3
:
"CONSTANT"
,
4
:
"AMP"
,
5
:
"DECIBEL"
}
_preamble
=
None
_preamble_raw
=
preamble_string
preamble_fields
=
preamble_string
.
split
(
","
)
_preamble
=
{}
_preamble
[
"format"
]
=
_formats
[
int
(
preamble_fields
.
pop
(
0
))]
_preamble
[
"type"
]
=
_types
[
int
(
preamble_fields
.
pop
(
0
))]
_preamble
[
"points"
]
=
int
(
preamble_fields
.
pop
(
0
))
_preamble
[
"count"
]
=
int
(
preamble_fields
.
pop
(
0
))
_preamble
[
"x_increment"
]
=
float
(
preamble_fields
.
pop
(
0
))
_preamble
[
"x_origin"
]
=
float
(
preamble_fields
.
pop
(
0
))
_preamble
[
"x_reference"
]
=
float
(
preamble_fields
.
pop
(
0
))
_preamble
[
"y_increment"
]
=
float
(
preamble_fields
.
pop
(
0
))
_preamble
[
"y_origin"
]
=
float
(
preamble_fields
.
pop
(
0
))
_preamble
[
"y_reference"
]
=
float
(
preamble_fields
.
pop
(
0
))
_preamble
[
"coupling"
]
=
_couplings
[
int
(
preamble_fields
.
pop
(
0
))]
_preamble
[
"x_display_range"
]
=
float
(
preamble_fields
.
pop
(
0
))
_preamble
[
"x_display_origin"
]
=
float
(
preamble_fields
.
pop
(
0
))
_preamble
[
"y_display_range"
]
=
float
(
preamble_fields
.
pop
(
0
))
_preamble
[
"y_display_origin"
]
=
float
(
preamble_fields
.
pop
(
0
))
_preamble
[
"date"
]
=
str
(
preamble_fields
.
pop
(
0
)
.
strip
(
"
\"
"
))
_preamble
[
"time"
]
=
str
(
preamble_fields
.
pop
(
0
)
.
strip
(
"
\"
"
))
_preamble
[
"frame_model_#"
]
=
str
(
preamble_fields
.
pop
(
0
)
.
strip
(
"
\"
"
))
_preamble
[
"acquisition_mode"
]
=
_acquisition_modes
[
int
(
preamble_fields
.
pop
(
0
))]
_preamble
[
"completion"
]
=
int
(
preamble_fields
.
pop
(
0
))
_preamble
[
"x_units"
]
=
_units
[
int
(
preamble_fields
.
pop
(
0
))]
_preamble
[
"y_units"
]
=
_units
[
int
(
preamble_fields
.
pop
(
0
))]
_preamble
[
"bandwidth_maximum"
]
=
float
(
preamble_fields
.
pop
(
0
))
_preamble
[
"bandwidth_minimum"
]
=
float
(
preamble_fields
.
pop
(
0
))
if
not
len
(
preamble_fields
)
==
0
:
_preamble
=
None
raise
Exception
(
"preamble_string_to_dict: Excess information in preamble"
)
return
_preamble
############################################################################
def
raw_to_scipy_array
(
waveform_raw
,
byte_order
,
preamble
):
"""
Interpreting the waveform block of raw data according to the preamble data.
The waveform binary data is transformed to an x_data, y_data array in scale units
given in preamble.
preamble -- preamble dictionary.
waveform_raw -- raw block datastream excluding length header
byteorder -- byteorder (MSBFIRST, LSBFIRST) of the BINARY, BYTE and WORD formats.
returns: <type 'numpy.ndarray'> array([[x1,x2,x3,...,xn],[y1,y2,y3,...,yn]])
"""
#initialise class constants
_byteorder_conversion
=
{
"MSBFIRST"
:{
"BYTE"
:[
">"
,
"b"
],
"WORD"
:[
">"
,
"h"
],
"BINARY"
:[
">"
,
"i"
],
},
"LSBFIRST"
:{
"BYTE"
:[
"<"
,
"b"
],
"WORD"
:[
"<"
,
"h"
],
"BINARY"
:[
"<"
,
"i"
],
},
}
format_string
=
_byteorder_conversion
[
byte_order
][
preamble
[
'format'
]][
0
]
format_string
+=
str
(
preamble
[
"points"
])
format_string
+=
_byteorder_conversion
[
byte_order
][
preamble
[
'format'
]][
1
]
#Convert to unit values.
y_data
=
scipy
.
array
(
struct
.
unpack
(
format_string
,
waveform_raw
),
dtype
=
scipy
.
float64
)
*
preamble
[
"y_increment"
]
-
preamble
[
"y_origin"
]
#y_data=scipy.array(struct.unpack(format_string, waveform_raw),
# dtype=scipy.float64) /25 * float(2.0)-float(2.0)
x_data
=
scipy
.
arange
(
preamble
[
"points"
])
*
preamble
[
"x_increment"
]
+
preamble
[
"x_origin"
]
#pdb.set_trace()
#create array with x,y axis values.
waveform_data
=
scipy
.
array
([
x_data
,
y_data
])
return
waveform_data
############################################################################
def
check_waveforms
(
waveform_data
):
"""
This function checks for the consistency of the captured waveform.
waveform_data -- <type 'dict'> waveform_data (as returned by function "file_to_waveform")
returns: number of points (of the first waveform found)
"""
first
=
True
for
ch
in
waveform_data
.
keys
():
if
first
==
True
:
first
=
False
channel
=
ch
points
=
waveform_data
[
ch
][
"preamble"
][
"points"
]
print
(
"Info: Record Length is"
,
points
,
"samples"
)
count
=
waveform_data
[
ch
][
"preamble"
][
"count"
]
x_inc
=
waveform_data
[
ch
][
"preamble"
][
"x_increment"
]
print
(
"Info: Sample Period is"
,
x_inc
)
timebase
=
waveform_data
[
ch
][
"preamble"
][
"x_display_range"
]
else
:
if
waveform_data
[
ch
][
"preamble"
][
"points"
]
!=
points
:
print
(
"### WARNING! Different array length!"
)
print
(
" Channel:"
,
channel
,
points
)
print
(
" Channel:"
,
ch
,
waveform_data
[
ch
][
"preamble"
][
"points"
])
if
waveform_data
[
ch
][
"preamble"
][
"count"
]
!=
count
:
print
(
"### WARNING! Different sweep counts per acquisition!"
)
print
(
" Channel:"
,
channel
,
count
)
print
(
" Channel:"
,
ch
,
waveform_data
[
ch
][
"preamble"
][
"count"
])
if
waveform_data
[
ch
][
"preamble"
][
"x_increment"
]
!=
x_inc
:
print
(
"### WARNING! Different time base sample interval!"
)
print
(
" Channel:"
,
channel
,
x_inc
)
print
(
" Channel:"
,
ch
,
waveform_data
[
ch
][
"preamble"
][
"x_increment"
])
print
(
" You may want to check the 'Interpolation' setting in the 'pre-Processing' tab of the oscilloscopes channel setup"
)
if
waveform_data
[
ch
][
"preamble"
][
"x_display_range"
]
!=
timebase
:
print
(
"### WARNING! Different time base!"
)
print
(
" Channel:"
,
channel
,
timebase
)
print
(
" Channel:"
,
ch
,
waveform_data
[
ch
][
"preamble"
][
"x_display_range"
])
return
(
points
)
############################################################################
def
file_to_waveform
(
filename
):
"""
Retrieve the waveforms from a bytestring which is normally read from file.
filename -- source file from which to retrieve data.
returns: <type 'dict'> waveform_data with keys to a <type 'dict'> for each channel number
each channel number is again a <type 'dict'> with keys:
'byte_order' : <type 'str'>
'preamble' : <type 'dict'>
'waveform' : <type 'numpy.ndarray'>
each preamble is a <type 'dict'> with keys:
'acquisition_mode' : <type 'str'>
'bandwidth_maximum' : <type 'float'>
'bandwidth_minimum' : <type 'float'>
'completion' : <type 'int'>
'count' : <type 'int'>
'coupling' : <type 'str'>
'date' : <type 'str'>
'format' : <type 'str'>
'frame_model_#' : <type 'str'>
'points' : <type 'int'>
'time' : <type 'str'>
'type' : <type 'str'>
'x_display_origin' : <type 'float'>
'x_display_range' : <type 'float'>
'x_increment' : <type 'float'>
'x_origin' : <type 'float'>
'x_reference' : <type 'float'>
'x_units' : <type 'str'>
'y_display_origin' : <type 'float'>
'y_display_range' : <type 'float'>
'y_increment' : <type 'float'>
'y_origin' : <type 'float'>
'y_reference' : <type 'float'>
'y_units' : <type 'str'>
an example waveform_data dict looks like this:
{1: { 'byte_order': 'LSBFIRST',
'preamble': {'y_display_origin': 30707.0,
'bandwidth_minimum': 0.0,
'y_units': 'Unit Name = V',
:
'x_units': 'Unit Name = S',
'time': '13:50:02'},
'waveform': '
\x8e\xef\x8b
\
.....'
},
2: {'byte_order': 'LSBFIRST',
'preamble': {'y_display_origin': 30707.0,
'bandwidth_minimum': 0.0,
'y_units': 'Unit Name = V',
:
'x_units': 'Unit Name = S',
'time': '13:50:02'},
'waveform': 'C
\x90
X
\x90
]
\x90
N
\x90
@
\x90
G
\x90
a
\
....'}
}
"""
# Open data file for "read", in "binary" format
# Then readline() returns type <bytes> that can be
# decoded using "utf-8" into string
data_file
=
open
(
filename
,
"rb"
)
# create an empty wavefrom_data dictionairy
waveform_data
=
{}
line
=
data_file
.
readline
()
.
decode
(
"utf-8"
)
if
"#WaveformData:Siglent SDS1000X"
not
in
line
:
#print("Exception: file_to_waveform: Not a Siglent SDS1000X Waveform Data file.")
Exception
(
"file_to_waveform: Not a Siglent SDS1000X Waveform Data file."
)
data_file
.
close
()
return
line
=
data_file
.
readline
()
.
decode
(
"utf-8"
)
version
=
line
.
strip
()
.
split
(
":"
)
if
not
((
"#version"
in
version
[
0
])
and
(
"0.2"
in
version
[
1
])):
# if version[0]=="#version" and version[1]=="0.1":
#print("Exception: file_to_waveform: Siglent SDS1000X wrong version Waveform Data file.")
Exception
(
"file_to_waveform: Siglent SDS1000X wrong version Waveform Data file."
)
data_file
.
close
()
return
channels
=
[]
preamble_idx
=
0
waveform_idx
=
0
while
1
:
# Note that readline() scans the file in "binary" mode.
# Some readline actions should be kept binary (the raw
# waveform data) and some should be decoded using "utf-8"
line
=
data_file
.
readline
()
if
len
(
line
)
==
0
:
break
if
"#channel:"
in
str
(
line
):
chan_str
=
line
.
decode
(
"utf-8"
)
.
split
(
":"
)[
1
]
.
strip
()
channels
=
chan_str
.
split
(
','
)
if
"#date:"
in
str
(
line
):
date_in_file
=
line
.
decode
(
"utf-8"
)
.
split
(
":"
)[
1
]
.
strip
()
if
"#time:"
in
str
(
line
):
time_lst
=
line
.
decode
(
"utf-8"
)
.
split
(
":"
)
time_in_file
=
time_lst
[
1
]
.
strip
()
+
":"
+
time_lst
[
2
]
.
strip
()
+
":"
+
time_lst
[
3
]
.
strip
()
if
"#byteorder:"
in
str
(
line
):
byte_order
=
line
.
decode
(
"utf-8"
)
.
split
(
":"
)[
1
]
.
strip
()
# Preambles come first in the file.
# Create a dictionairy channel entry for each
if
"#preamble:"
in
str
(
line
):
preamble_chan
=
int
(
channels
[
preamble_idx
])
waveform_data
[
preamble_chan
]
=
{}
waveform_data
[
preamble_chan
][
"byte_order"
]
=
byte_order
preamble_string
=
data_file
.
readline
()
.
decode
(
"utf-8"
)
waveform_data
[
preamble_chan
][
"preamble"
]
=
preamble_string_to_dict
(
preamble_string
)
preamble_idx
+=
1
# Next comes wavefrom data.
# Create a dictionairy channel entry for each
if
"#waveform:"
in
str
(
line
):
waveform_chan
=
int
(
channels
[
waveform_idx
])
'''
wf_header = data_file.read(1) # read the waveform_header "#"
print(wf_header)
wf_no_of_digits = int(data_file.read(1)) # read the number of digits "5"
print(wf_no_of_digits)
wf_samples = int(data_file.read(wf_no_of_digits))
print(wf_samples)
'''
wf_samples
=
waveform_data
[
waveform_idx
+
1
][
"preamble"
][
"points"
]
#print("chan_no",waveform_chan)
#print(wf_header)
#print(wf_no_of_digits)
#print(wf_samples)
# Add the waveform to the <type 'dict'> waveform_data but first convert it the RAW
# waveform data in the file to scipy array with x,y axis values
waveform_data
[
waveform_chan
][
"waveform"
]
=
raw_to_scipy_array
(
data_file
.
read
(
wf_samples
),
byte_order
,
waveform_data
[
waveform_chan
][
'preamble'
])
#line = data_file.readline() # waveform_data ends with a "\n". Read it!
waveform_idx
+=
1
data_file
.
close
()
return
waveform_data
############################################################################
def
osc_init
(
scope
,
init
):
"""
Initialize the Siglent SDS1000X DSO
scope -- instance of python-vxi connected to the oscilloscope
init -- <dict> for example:
init = {
\
'channel' : [ 1 , 0 , 1 , 0 ],
\
'offset' : [ 0.0, 0.0, 0.0 , 0.0 ],
\
'volt_div' : [ 0.5, 1.0, 0.125, 1.0 ],
\
#'50ohm' : [ 1 , 0 , 1 , 0 ],
\
No 50 Ohm for Siglent SDS1000X
'sinxx' : [ 0 , 0 , 0 , 0 ],
\
'trig' : [ 1 ],
\
'trig_level' : [ 0.14 ],
\
'timebase' : [ 50e-9 ],
\
'refclk' : [ 'ext' ],
\
"""
#scope = vxi11.Instrument("192.168.178.31")
print
(
scope
.
ask
(
"*IDN?"
))
# Returns 'Siglent Technologies,SDS1104X-E,SDSMMEBQ4R5537,8.1.6.1.35R2'
# Initialize the oscilloscope trigger
scope
.
write
(
"TRIG_MODE NORM"
)
#print("TRIG_MODE NORM")
# select trig channel
trig
=
str
(
init
[
'trig'
][
0
])
# trigger level depends on probe attenuation of the selected cannel
probe_attn
=
float
((
scope
.
ask
(
"C"
+
trig
+
":ATTENUATION?"
))
.
split
(
' '
)[
1
])
triglevel
=
str
(
float
(
init
[
'trig_level'
][
0
])
/
probe_attn
)
scope
.
write
(
"C"
+
trig
+
":TRIG_LEVEL "
+
triglevel
)
#print("C"+trig+":TRIG_LEVEL "+ triglevel)
"""
# For Keysight sinxx interpolation is a "Horizontal" setting that
# applies to all enabled channels! Default = False
sinxx = False
"""
for
ch
in
range
(
4
):
# Set channel ON/OFF
if
init
[
'channel'
][
ch
]
==
1
:
scope
.
write
(
"C"
+
str
(
ch
+
1
)
+
":TRACE ON"
)
#print("C"+str(ch+1)+":TRACE ON")
# Set channel Offset
scope
.
write
(
"C"
+
str
(
ch
+
1
)
+
":OFFSET "
+
str
(
float
(
init
[
'offset'
][
ch
])))
#print("C"+str(ch+1)+":OFFSET "+str(float(init['offset'][ch])))
# Set channel Volt/Div
scope
.
write
(
"C"
+
str
(
ch
+
1
)
+
":VOLT_DIV "
+
str
(
float
(
init
[
'volt_div'
][
ch
])))
#print("C"+str(ch+1)+":VOLT_DIV "+str(float(init['volt_div'][ch])))
# Set channel Coupling
#'50ohm' : [ 1 , 0 , 1 , 0 ], \
# Note: No 50 Ohm for Siglent SDS1104X-E! Both D50 and D1M end up in D1M
if
init
[
'50ohm'
][
ch
]
==
1
:
scope
.
write
(
"C"
+
str
(
ch
+
1
)
+
":COUPLING D50"
)
#print("C"+str(ch+1)+":COUPLING D50")
else
:
scope
.
write
(
"C"
+
str
(
ch
+
1
)
+
":COUPLING D1M"
)
#print("C"+str(ch+1)+":COUPLING D1M")
else
:
scope
.
write
(
"C"
+
str
(
ch
+
1
)
+
":TRACE OFF"
)
#print("C"+str(ch+1)+":TRACE OFF")
# Set Interpolation for all used channels to "16 point Sin(x)/x"
#'sinxx' : [ 1 , 0 , 1 , 0 ], \
# Check if one of the channels is set to sinxx then enable sinxx for all
if
init
[
'sinxx'
][
ch
]
==
1
:
sinxx
=
True
if
sinxx
:
scope
.
write
(
"SINXX_SAMPLE ON"
)
else
:
scope
.
write
(
"SINXX_SAMPLE OFF"
)
# Trigger in the centre of the screen and set timebase
scope
.
write
(
"TRIG_DELAY 0"
)
#print("TRIG_DELAY 0")
scope
.
write
(
"TIME_DIV "
+
str
(
float
(
init
[
'timebase'
][
0
])))
#print("TIME_DIV "+str(float(init['timebase'][0])))
# Set internal/external 10 MHz timebase
# Note: no external 10MHz timebase facility on SDS1000X!
if
init
[
'refclk'
][
0
]
==
'ext'
:
print
(
"### no external 10MHz timebase facility on SDS1000X!"
)
return
############################################################################
##
## If run from commandline, we can test the library
##
"""
Siglent SDS1000X remote control
Usage:
Siglent_SDS1000X.py <IP#> [-c 1234] [-a<averages>] [-o <dir>] [-m <number>]
Siglent_SDS1000X.py -h | --help
IP IP number of the Oscilloscope
(for example: 192.168.178.31)
Options:
-h --help Show this screen.
-c 1234 channel number(s)
-a number of averages, default: 1
-o <dir> optional directory for output file storage, default: "data/"
-m <number> number of measurement output files
"""
if
__name__
==
"__main__"
:
import
argparse
parser
=
argparse
.
ArgumentParser
()
parser
.
add_argument
(
"scope"
,
help
=
"IP-Number of the oscilloscope"
)
parser
.
add_argument
(
"-channels"
,
default
=
[
1
,
2
,
3
,
4
],
type
=
list
)
parser
.
add_argument
(
"-num_avg"
,
default
=
1
,
type
=
int
)
parser
.
add_argument
(
"-output_dir"
,
default
=
"data"
)
parser
.
add_argument
(
"-measurements"
,
default
=
1
,
type
=
int
)
args
=
parser
.
parse_args
()
print
(
"Use oscilloscope IP address: "
+
str
(
args
.
scope
))
scope
=
vxi11
.
Instrument
(
args
.
scope
)
#scope = vxi11.Instrument("192.168.178.31")
print
(
scope
.
ask
(
"*IDN?"
))
# Returns 'Siglent Technologies,SDS1104X-E,SDSMMEBQ4R5537,8.1.6.1.35R2'
print
(
"channels:"
,
args
.
channels
,
"num_avg"
,
args
.
num_avg
)
'''
init = {
\
'channel' : [ 1 , 0 , 0 , 0 ],
\
'offset' : [ 0.0 , 0.0, 0.0 , 0.0 ],
\
'volt_div' : [ 1 , 1 , 1 , 1 ],
\
'50ohm' : [ 0 , 0 , 0 , 0 ],
\
'trig' : [ 1 ],
\
'trig_level' : [ 1.4 ],
\
'timebase' : [ 100e-6 ],
\
'refclk' : [ 'ext' ],
\
}
osc_init(scope, init)
'''
# Trigger in the centre of the screen; important for maximum estimations
# forwarded to function average_edge_to_sfd
#scope.write("TRIG_DELAY 0")
for
i
in
range
(
0
,
args
.
measurements
):
d
,
filename
=
get_waveforms
(
scope
,
channels
=
args
.
channels
,
num_avg
=
args
.
num_avg
,
output_dir
=
args
.
output_dir
)
#wf_data = file_to_waveform("data/200220T151435_375210_scope_keysight_dso_s_254A_bin")
#sys.exit()
wf_data
=
file_to_waveform
(
filename
)
check_waveforms
(
wf_data
)
waveforms
=
plt
.
figure
(
"channel:"
+
str
(
args
.
channels
))
ax
=
waveforms
.
add_subplot
(
111
)
ax
.
set_xlabel
(
'Time'
)
ax
.
set_ylabel
(
'Volt'
)
for
chan
in
wf_data
.
keys
():
x
=
wf_data
[
chan
][
"waveform"
][
0
]
y
=
wf_data
[
chan
][
"waveform"
][
1
]
ax
.
plot
(
x
,
y
)
plt
.
show
()
sys
.
exit
()
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment