Have you ever tried using Rigol's Ultrawave software for the DG1022 function generator? Well... How can I put it mildly? It sucks.

The DG1022 is a great waveform generator though. It has served me well for many years. I recently needed it to output some custom waveforms. One option would have been to write the waveforms in txt files and load them with a USB stick. Rigol's formatting is really simple. But moving the stick back and forth didn't seem like a good idea.
So I decided to write a Python script that saves waveforms to the non-volatile memory of the DG1022. Since I have no idea what kind of non-volatile memory it uses and in order not to stress it with multiple writes I also included a function that writes the waveform directly to the volatile memory and outputs it on channel 1.

The script
# ****************************************************************************** | |
# RIGOL DG1022 - Mathematical function loader | |
# this script lets you load waveforms writen in python to the DG1022 | |
# more details here: | |
# https://btbm.ch/rigol-dg1022-how-to-create-waveforms-with-python | |
# 2021 Grease monkey - No rights reserved | |
# !!! Don't laugh at the code. I change engine oils for a living !!! | |
# ****************************************************************************** | |
################################################################################ | |
# Packages | |
################################################################################ | |
import pyvisa | |
import time | |
import math | |
import matplotlib.pyplot as plt | |
################################################################################ | |
# Functions | |
################################################################################ | |
# Example of a function that requires 2 parameters | |
def log_10(x): | |
return math.log(x, 10) | |
def create_data_points(func, x_start, x_end, number_of_steps): | |
""" | |
This function calculates and scales to [-1,1] the Y points of the mathematical function func in the domain | |
[x_start, x_end]. It also plots the results. | |
:param func: The mathematical function to be transferred. Single parameter functions only (e.g. math.cos). | |
The output of the function must be float. | |
:type func: str | |
:param x_start: The first element of the domain that x belongs. Must be less than x_end | |
:type x_start: float | |
:param x_end: The last element of the domain that x belongs. Must be greater than x_start | |
:type x_end: float | |
:param number_of_steps: The number of points on the X axis | |
:type number_of_steps: int | |
:return: The data points separated by commas (e.g. ,-1.0,-0.999,0.123) | |
:rtype: str | |
""" | |
output_string = '' | |
y = [] | |
for i in range(number_of_steps): | |
x = x_start + i * (x_end - x_start) / number_of_steps | |
y.append(func(x)) | |
# Y scaling | |
y_max = max(y) | |
y_min = min(y) | |
y_delta = y_max - y_min | |
y_l_scaled = [] | |
for i in y: | |
y_scaled = (i - y_min) / (y_delta / 2) # Scale Y to [0,2] | |
y_scaled -= 1.0 # Scale Y to [-1,1] | |
output_string += ',' + str(round(y_scaled, 6)) # Round to 6 digits | |
y_l_scaled.append(y_scaled) # append to list | |
# plot function | |
plt.plot(y_l_scaled) | |
plt.ylabel(func.__name__ + '(x) (scaled), [' + str(x_start) + ',' + str(x_end) + ']') | |
plt.show() | |
return output_string | |
def dg_send(message): | |
""" | |
Sends a command to the RIGOL DG1022. | |
It also calculates the time needed for the message to be transmitted. | |
Time estimates may be over-conservative. | |
The function does not return anything. | |
:param message: The command to be sent | |
:type message: str | |
""" | |
DG1022.write(message) | |
delay = 0.001 * len(message) | |
if delay < 0.2: | |
delay = 0.2 | |
print(message) | |
time.sleep(delay) | |
def store_to_arb(points, user_function_name): | |
""" | |
Saves the points to the user memory of the DG1022 under the name user_function_name. | |
The function does not return anything. | |
:param points: the data points separated by commas (e.g. ,-1.0,-0.999,0.123) | |
:type points: str | |
:param user_function_name: The name of the waveform as it will be stored in the non-volatile memory of | |
the DG1022. 12 characters max A-Z, a-z, 0-9 and _ first char must be a letter. | |
:type user_function_name: str | |
""" | |
dg_send('DATA:DEL ' + user_function_name) # First delete any stored waveform with the same name | |
# Delete the volatile memory (probably not needed) | |
dg_send('DATA:DEL VOLATILE') | |
# Load the data to the volatile memory | |
dg_send('DATA VOLATILE' + points) | |
# Copy the VOLATILE memory to the USER memory | |
dg_send('DATA:COPY ' + user_function_name) | |
def load_to_channel1_and_start(frequency, volts_low, volts_high, points): | |
""" | |
:param frequency: The frequency in Hertz | |
:type frequency: float | |
:param volts_low: The lowest voltage of VPP | |
:type volts_low: float | |
:param volts_high: The highest voltage of VPP | |
:type volts_high: float | |
:param points: the data points separated by commas (e.g. ,-1.0,-0.999,0.123) | |
:type points: str | |
""" | |
dg_send('OUTP OFF') # Switch CH1 off | |
dg_send('FUNC USER') # Select user function | |
dg_send('FREQ ' + str(frequency)) # Set the frequency | |
dg_send('VOLT:UNIT VPP') # Set VPP mode | |
dg_send('VOLT:HIGH ' + str(volts_high)) # Set the max value for VPP | |
dg_send('VOLT:LOW ' + str(volts_low)) # Set the min value for VPP | |
dg_send('DATA VOLATILE' + points) # Send the arb function to the VOLATILE memory | |
dg_send('FUNC:USER VOLATILE') # Load the volatile memory | |
dg_send('OUTP ON') # Turn CH1 on | |
# Start pyvisa and print the resources found | |
rm = pyvisa.ResourceManager() | |
print(rm.list_resources()) | |
# Replace 'USB0::0x0400::0x09C4::DG1D162150321::INSTR' with your instrument | |
DG1022 = rm.open_resource('USB0::0x0400::0x09C4::DG1D162150321::INSTR') | |
dg_send('*IDN?') | |
print(DG1022.read()) | |
# Example 1 | |
# Create the data points for sin(x) xE[0,π] | |
data_points = create_data_points(func=math.sin, x_start=0, x_end=math.pi * 2, number_of_steps=1000) | |
# Store the data points to C1 (overwrite) | |
store_to_arb(data_points, 'C1') | |
# Example 2 | |
# Create the data points for log(x) (base 10) xE[1,1000] | |
data_points = create_data_points(func=math.cos, x_start=1, x_end=math.pi * 2, number_of_steps=100) | |
# Store the data points | |
store_to_arb(data_points, 'C2') | |
# Example 3 | |
# Create the data points for acos(x) xE[-1,1] | |
data_points = create_data_points(func=math.acos, x_start=-1.0, x_end=1.0, number_of_steps=1000) | |
load_to_channel1_and_start(frequency=100000, volts_low=-1, volts_high=1, points=data_points) | |
rm.close() | |
exit() |