Skip to content

Commit

Permalink
Add Python (PyVISA) examples
Browse files Browse the repository at this point in the history
- add example to manual
- add Python SCPI/PyVISA example code (WIP)
  • Loading branch information
cedel1 committed Jul 26, 2023
1 parent 6216e89 commit 5796f3d
Show file tree
Hide file tree
Showing 3 changed files with 215 additions and 0 deletions.
Binary file modified SCPI_manual/smartpower3_scpi_manual.pdf
Binary file not shown.
102 changes: 102 additions & 0 deletions SCPI_manual/smartpower3_scpi_manual.tex
Original file line number Diff line number Diff line change
Expand Up @@ -343,4 +343,106 @@ \section{Commands}
\end{enumerate}
\end{enumerate}

\section{Python Examples}

The power supply in SCPI mode can also be controlled by various VISA implementations. This enables the user to programmatically perform and repeat various test and measurements, that might otherwise be quite complicated or outright impossible with sufficient precision.

The following is a simple example of how the device might be used.

More information on how to use PyVISA can be found at\newline
\href{https://pyvisa.readthedocs.io/en/latest/introduction/index.html}{https://pyvisa.readthedocs.io/en/latest/introduction/index.html}.

\subsection{Prerequisites}

It is expected that you have Python3, PyVISA and backed installed. We are going to use PyVISA-py for the backend.

The exact steps how to install these prerequisites depend on your selection of operating system and its version. For example, most Linux distributions will have Python installed by default, so its installation might be skipped.

Once Python is installed, you could, for example, install the remaining dependencies for the current user by running the following in terminal:
\begin{verbatim}
pip install -U pyvisa pyvisa-py
\end{verbatim}

\subsection{Connecting to the Power Supply}

The next step is to connect to the SmartPower3 power supply. To do so, please follow these steps:
\begin{enumerate}

\item Connect SmartPower3 to your computer using serial cable.
\item On the setting screen, set SmartPower3 to \verb|SCPI| mode.
\item Open your terminal application and start Python interpreter by typing:\newline\verb|python|
\item Import PyVISA and connect by typing the following
\begin{verbatim}
>>> import pyvisa
>>> rm = pyvisa.ResourceManager()
>>> rm.list_resources()
('ASRL/dev/ttyUSB0::INSTR')
>>> inst = rm.open_resource('ASRL/dev/ttyUSB0::INSTR')
>>> print(inst.query('*IDN?'))
\end{verbatim}
The \verb|('ASRL/dev/ttyUSB0::INSTR')| string will most likely be different in your case. You might even have more devices listed, depending on your hardware. That is OK, just select the one that corresponds to your serial connection.

The last command (\verb|print(inst.query("*IDN?"))|) should result in the terminal printing out the power supply identification, which should look similar to

\verb|Hardkernel Co Ltd,SmartPower3,<mac_address>,Build date: Jul 19 2023 19:32:24|, where \verb|<mac_address>| will be the WiFi module MAC address (a string similar to \newline\verb|94:3C:C6:CC:AA:78|) and the Build date will reflect your current firmware version.
\item In case the last command didn't produce the desired result and you got timeout error response, there are couple things you can check to remedy the situation:
\begin{enumerate}
\item Check that the device is in SCPI mode.
\item Check device serial baud rate. It is quite possible that your SmartPower3 is set to a higher baud rate than 9600, which is PyVISA default. To set correct baud rate, you can use the following command:
\begin{verbatim}
inst.baud_rate = 115200
\end{verbatim}
where \verb|115200| should be changed to the actual value set on your SmartPower3.
\item It is possible that the response was followed by an empty line. In such a case, there is a mismatch between line endings returned by the device and line endings that PyVISA expects. That can be remedied by issuing the following command:
\begin{verbatim}
inst.read_termination = '\r\n'
\end{verbatim}
\end{enumerate}
For more troubleshooting info please see PyVISA documentation available\newline at \href{https://pyvisa.readthedocs.io/en/latest/introduction/communication.html\#}{https://pyvisa.readthedocs.io/en/latest/introduction/communication.html\#}.

\end{enumerate}
\subsection{Doing Something Usefull}
Once you have succeded in issuing the identification command and getting a proper response, we can get to something more useful.
\subsubsection{Doing a Voltage Sweep}
Let's suppose that you have just finished a construction of a new hardware device that is supposed to be powered by 12 Volts DC. But you also want to check how the device behaves at a lower voltage, gradually bringing the voltage up from 5V to the full 12V. That could be achieved by the following commands:
\begin{verbatim}
# by now, you have pyvisa imported and are connected to the device
import time
start_voltage = 5
end_voltage = 12
inst.write(f'source1:volt {start_voltage}') # the starting voltage on channel 1
# uncommenting the next line turns channel 1 on and the sweep is done under power
# it is advisable to first check the functionality of your program
# with the output turned off, in case there is a mistake
# inst.write('output1 on')
while(start_voltage <= end_voltage):
print(f'start_voltage = {start_voltage}')
inst.write(f'source1:volt {start_voltage}')
start_voltage += 1 # increase the value of start_voltage by 1
time.sleep(2) # wait 2 seconds
\end{verbatim}

The example above is rather rudimentary and does not use the possibilities of the device to its full extent.

If you wanted to set values on a finer scale than whole Volts and not use Python float data type - which is advisable, as it can lead to unpredictable results - you could modify the above script as follows:

\begin{verbatim}
# by now, you have pyvisa imported and are connected to the device
import time
start_voltage = 3000 # the value represented in milliVolts
end_voltage = 5000 # the value represented in milliVolts
inst.write(f'source1:volt {start_voltage}mV') # note the added unit of mV
# uncommenting the next line turns channel 1 on and the sweep is done under power
# it is advisable to first check the functionality of your program
# with the output turned off, in case there is a mistake
# inst.write('output1 on')
while(start_voltage <= end_voltage):
print(f'start_voltage in Volts = {start_voltage/1000}')
inst.write(f'source1:volt {start_voltage}mV') # note the added unit of mV
# increase the value of start_voltage by 20mV, which is the smallest
# possible step in this voltage range
start_voltage += 20
time.sleep(2) # wait 2 seconds
\end{verbatim}

\end{document}
113 changes: 113 additions & 0 deletions contrib/SCPI_examples/sp3-scpi-datalogger.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
#/usr/bin/python3

from datetime import datetime, timedelta
import pdb
import time

import pyvisa

import matplotlib.pyplot as plt
import pandas as pd
import seaborn as sns


# select the instrument
def get_device_index_number(instances):
available_instruments = []
print('Available instruments (Select number and press Enter):')

for num, inst in enumerate(instances):
print(f'{num}: {inst}')
available_instruments.append(str(num))
print(available_instruments)

device = input('Make your selection: ')
print(f'Selected instrument: "{device}"')
if device not in available_instruments:
print('Invalid instrument number')
return -1
else:
return device

# decide on the length of capture
def get_logging_time():
logging_time = input('Logging time in seconds: ')
return int(logging_time)

def connect_to_device(device_num):
rm = pyvisa.ResourceManager('@py')
instances = rm.list_resources()

while (device_num == -1):
device_num = get_device_index_number(instances)

return rm.open_resource(instances[int(device_num)])

# setup the instrument
def setup_instrument(inst, output_channel, device_input_voltage):
inst.baud_rate = 115200
inst.read_termination = '\r\n'
inst.write_termination = '\r\n'
print(inst.query('*idn?', 0.2))
inst.write(f'source{output_channel}:volt {device_input_voltage}')
inst.write(f'source{output_channel}:curr 3') # no limit on current

def capture(inst, output_channel, logging_time, data):
#data = []
line = []
start_time = datetime.now()
print(start_time)
end_time = start_time + timedelta(seconds=logging_time)
print(end_time)
inst.write(f'system:communicate:serial:feed LOG')
inst.write(f'output{output_channel} ON')
time.sleep(1)

while(datetime.now() < end_time):
line = inst.read().split(',')
print(line)
data.append(line)
return(data)

def finish_capture(inst, output_channel):
#inst.write(f'output{output_channel} OFF') # uncomment to cut power to the measured device when the logging is done
inst.write(f'system:communicate:serial:feed NONE')

def plot_graph(data):
df = pd.DataFrame(data, columns =[
'ms',
'input_volt', 'input_ampere', 'input_watt', 'input_on_off',
'ch0_volt', 'ch0_ampere', 'ch0_watt', 'ch0_on_off', 'ch0_interrupts',
'ch1_volt', 'ch1_ampere', 'ch1_watt', 'ch1_on_off', 'ch1_interrupts',
'CheckSum8_2s_Complement', 'CheckSum8_Xor'])

ndf = df.drop([
'input_volt', 'input_ampere', 'input_watt', 'input_on_off',
'ch0_volt', 'ch0_ampere', 'ch0_on_off', 'ch0_interrupts',
'ch1_volt', 'ch1_ampere', 'ch1_watt', 'ch1_on_off', 'ch1_interrupts',
'CheckSum8_2s_Complement', 'CheckSum8_Xor'
], axis=1)
print(ndf)
sns.lineplot(x=ndf['ms'], y=ndf['ch0_watt'], data=ndf)
plt.show()

def run():
device_input_voltage = 5.1 # input voltage (V)
output_channel = 1 # output channel
device_num = -1
logging_time = 10 # in seconds
data = []

inst = connect_to_device(device_num)
setup_instrument(inst, output_channel, device_input_voltage)
time.sleep(5)
try:
capture(inst, output_channel, logging_time, data)
except:
print('Sorry, no data could be captured this time')
finish_capture(inst, output_channel)
if data:
plot_graph(data)

if __name__ == '__main__':
run()

0 comments on commit 5796f3d

Please sign in to comment.