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 27, 2023
1 parent 6216e89 commit 0a1f82e
Show file tree
Hide file tree
Showing 4 changed files with 256 additions and 0 deletions.
Binary file added SCPI_manual/Figure_1.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified SCPI_manual/smartpower3_scpi_manual.pdf
Binary file not shown.
131 changes: 131 additions & 0 deletions SCPI_manual/smartpower3_scpi_manual.tex
Original file line number Diff line number Diff line change
Expand Up @@ -343,4 +343,135 @@ \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}

\subsubsection{Plotting Power Data From Measurements on Channel 1}
The \verb|contrib/SCPI_examples| directory of Smartpower3 git repository contains file\newline
\verb|sp3-scpi-datalogger.py| which does exactly what the subtitle advertises.

But before you use it, you should be aware of a couple of important points:
\begin{enumerate}
\item
Check what voltage your device operates on. The file sets output voltage to 5.1V (required for Raspberry Pi 4), so if your device operates on other voltage, change \verb|device_input_voltage| variable in the \verb|run| function to the required value.
\item
The script expects the device to be connected to the first (left) channel.
\item
The script expects that you use Serial connection to your SmartPower3 and it lets you choose your serial device.
\item
It asks you how long you want to run the logging - plese enter numeric value, seconds.
\item
Before you run the script with your device connected to SmartPower3, please run the script without the device to make sure everything is working as expected, the power is set to correct level etc. Doublecheck everything.
\item
The script saves logged data to intermediate .csv file in the same directory as the script. You might want to use that file for further analysis.
\item
The scrip is pretty basic and definitely can be improved upon - don't hesitate to modify it to suit your needs. But please doublecheck before powering your device.
\end{enumerate}
If everything goes well, the script will output window with power plot similar to Figure 1.
\begin{figure}
\begin{center}
\includegraphics[scale=0.24]{Figure_1.png}
\caption{Script output}
\end{center}
\end{figure}

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


from datetime import datetime, timedelta
import matplotlib.pyplot as plt
import pandas as pd
import pdb
import pyvisa
import seaborn as sns
import time
from pandas.core.frame import DataFrame


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

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)])

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))
time.sleep(0.5)
inst.query('*rst')
inst.write(f'source{output_channel}:volt {device_input_voltage}')
inst.write(f'source{output_channel}:curr 3') # no limit on current

def ask_how_long_to_log():
logging_time = '0'
while(int(logging_time) <= 0 or not logging_time.isnumeric()):
print('How long would you like to log? Please enter time in whole seconds.')
print('Your choice:')
logging_time = input()

return int(logging_time)

def capture(inst, output_channel, logging_time):
line_ending='\r\n'
end_time = datetime.now() + timedelta(seconds=logging_time)
inst.write(f'system:communicate:serial:feed LOG')
inst.write(f'output{output_channel} ON')
time.sleep(1)

with open('sp3_dataset.csv', 'w') as f:
while(datetime.now() < end_time):
line = inst.read()
f.write(f'{line}{line_ending}')
print(line)

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

def plot_graph():
data = pd.read_csv('./sp3_dataset.csv', names=[
'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'])

ndata = data.drop(columns=[
'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'])

ndata.insert(2, 'seconds', 1.0)
start_ms = ndata.ms[0]
ndata.seconds = (ndata.ms-start_ms)/1000
print(ndata)

graph = sns.relplot(x='seconds', y='ch0_watt', data=ndata, kind='line')
graph.set_axis_labels("Seconds", "milliWatts")
plt.show()

def run():
# Please change the value on the following line to suit your device
device_input_voltage = 5.1 # input voltage (V)
# Please change the value on the following line to the channel your device is connected to
output_channel = 1 # output channel
device_num = -1
logging_time = 0 # in seconds

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

if __name__ == '__main__':
run()

0 comments on commit 0a1f82e

Please sign in to comment.