diff --git a/SCPI_manual/Figure_1.png b/SCPI_manual/Figure_1.png new file mode 100644 index 0000000..b252477 Binary files /dev/null and b/SCPI_manual/Figure_1.png differ diff --git a/SCPI_manual/smartpower3_scpi_manual.pdf b/SCPI_manual/smartpower3_scpi_manual.pdf index ece7272..3513d47 100644 Binary files a/SCPI_manual/smartpower3_scpi_manual.pdf and b/SCPI_manual/smartpower3_scpi_manual.pdf differ diff --git a/SCPI_manual/smartpower3_scpi_manual.tex b/SCPI_manual/smartpower3_scpi_manual.tex index 965c573..a61ff63 100644 --- a/SCPI_manual/smartpower3_scpi_manual.tex +++ b/SCPI_manual/smartpower3_scpi_manual.tex @@ -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,,Build date: Jul 19 2023 19:32:24|, where \verb|| 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} \ No newline at end of file diff --git a/contrib/SCPI_examples/sp3-scpi-datalogger.py b/contrib/SCPI_examples/sp3-scpi-datalogger.py new file mode 100644 index 0000000..c2a08f7 --- /dev/null +++ b/contrib/SCPI_examples/sp3-scpi-datalogger.py @@ -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()