libsmu contains abstractions for streaming data to and from USB-connected analog interface devices, currently supporting the Analog Devices' ADALM1000. Building off of LibUSB for cross-platform operation, it offers the sourcing of repeated waveforms, configuration of hardware, and measuring of signals.
Python bindings are also provided in the form of the pysmu module. See instructions below for how to build them.
Conda packages
Conda packages
Conda and Anaconda are cross-platform package-management tools that generally focus around python but can support any language or package generally. You can find documentation on the libsmu conda package here .
Simple installation
Linux
Download the specific libsmu .deb package for your Linux distribution from the Releases section. Currently we are supporting Ubuntu 16, 18 and 20. The package name should start with libsmu and contain the OS version. Go to the folder you downloaded the package in and open a terminal, then run the following command:
If you want to install pysmu (the Python bindings for libsmu), you can download the specific wheel for your version. We provide python wheel packages for the following Python versions: py3.7, py3.8, py3.9, py3.10. You can download the .whl for your Python version from the official releases or use the ones provided on test.pypi.org (soon from the official pypi.org as well).
Download the specific libsmu .pkg package for your MacOS distribution from the Releases section. Currently we are supporting MacOS 10.13, 10.14 and 10.15. The package name should start with libsmu and contain the MacOS version.
Open a terminal and run the following command which will install only the base library in /Library/Frameworks.
A different way to install libsmu on MacOS is by using the .tar.gz located in the Releases section. This will install the .dylib (libraries) into system paths (usr/local) and it will also install the smu CLI.
tar -xzvf <libsmu_package_name>.tar.gz --strip=3 -C /usr/local
Based on this base library installation, you can install the Python bindings manually for the desired Python version. Check the Python section below.
If -DBUILD_PYTHON=ON (from the above options) is specified, this step will also install the Python Bindings using the Python version detected at the CMake configuration step.
analog@analog:~$ sudo make install
Testing
The Google Test framework is used to run various streaming tests. Make sure it's installed on the host system and then use the following to build and run tests:
analog@analog:~$ cmake -DBUILD_TESTS=ON ..
analog@analog:~$ make check
Note that at least one device should be inserted to the system for the checks to run properly.
Python Bindings
Python Bindings are enabled by default and can be disabled using the CMake option mentioned above.
Note that this will build only one version of Python for the first supported implementation it finds installed on the system. To build them for other versions it's easiest to build them manually via the setup.py script in the regular python manner if libsmu has already been built and/or installed on the host machine.
analog@analog:~$ cmake -DBUILD_PYTHON=ON .. #this will generate a setup.py file in the current directory
analog@analog:~$ python3 -m build #this will create .whl files in a dist/ directory
Linux FAQ
By default, libsmu is installed into various directories inside /usr/local which implies that the runtime linker cache often needs to be regenerated, otherwise runtime linking errors will occur.
Regenerate runtime linker cache after install:
analog@analog:~$ sudo ldconfig
If the same errors still happen, make sure the directory the libsmu library is installed to is in the sourced files for /etc/ld.so.conf before running ldconfig.
In addition, the udev rules file (53-adi-m1k-usb.rules) is installed by default to give regular users access to devices supported by libsmu. Udev has to be forced to reload its rules files in order to use the new file without rebooting the system.
Reload udev rules files:
analog@analog:~$ sudo udevadm control --reload-rules
Finally, for python support on Debian/Ubuntu derived distros users will have to export PYTHONPATH or perform a similar method since hand-built modules are installed to the site-packages directory (which isn't in the standard search list) while distro provided modules are placed in dist-packages.
Note the command will have to be altered for targets with different bitness or python versions.
Windows
On Windows, it's easiest to use the provided installers, libsmu-setup-x86.exe and libsmu-setup-x64.exe that install either 32 or 64 bit support, respectively. During the install process options are provided to install drivers, Python bindings and Visual Studio development support.
Device usage
General information about libsmu usage
The most important classes from libsmu are Session and Device. Device is controlling a specific M1k device, while Session is controlling all currently connected devices.
The most important members of these classes are:
Session:
add(Device* device): connects the given device to the current Session.
add_all(): connects all plugged in devices to the current Session.
m_devices: set containing the currently connected devices.
run(uint64_t num_samples): generates num_samples samples on all connected devices. If num_samples == 0, it will run in a continuous fashion.
start(uint64_t num_samples): configures the devices before generating the requested number of samples. It is internally called by run().
cancel(): cancels all currently pending transfers from all devices.
end(): completes the currently active transfers before ending the Session. If the session was running in a continuous fashion, it also cancels the pending transfers.
flush(): deletes all the previously generated samples for all connected devices.
Device:
m_serial: the device's serial number.
m_fwver: the device's firmware version.
read(vector<array<float, 4>> buf, size_t num_samples, int timeout, bool skipsamples): reads num_samples samples from the current device and stores them in buf. It waits up to timeout milliseconds for new samples (if timeout == -1, it waits undefinetly). Skips the oldest samples if more than requested are available. Timeout defaults to 0 and skisamples to false.
write(vector<array<float, 4>> buf, unsigned channel, bool cyclic): writes buf to the given channel (0 for channel A, 1 for channel B). If cyclic == true it writes the buffer continuously. cyclic defaults to false.
run(uint64_t samples): similar to Session.run(), but only for the current device.
flush(int channel, bool read): deletes all the previously generated samples from the specified channel. If read == true, it also deletes the incoming read queue; defaults to false.
set_mode(unsigned channel, unsigned mode, bool restore): sets the specified mode to the specified channel (0 - channel A, 1 - channel B). The available modes are: Mode.HI_Z (0; channel is floating), Mode.SVMI (1; source voltage, measure current), Mode.SIMV (2; source current, measure voltage), Mode.HI_Z_SPLIT (3; HI_Z with enabled switch for the input only pin added in Rev F), Mode.SVMI_SPLIT (4; SVMI with enabled switch for the input only pin added in Rev F) and Mode.SIMV_SPLIT (5; SIMV with enabled switch for the input only pin added in Rev F).
You can find libsmu's complete public API in the Classes menu.
On the main github page you can find various examples using libsmu.
In the following sections we will present you in detail the reading and writing processes using libsmu.
Reading data from a device
/>
// Create session object and add all compatible devices them to the
// session. Note that this currently doesn't handle returned errors.
Session* session = new Session();
session->add_all();
// Grab the first device from the session (we're assuming one exists).
auto dev = *(session->m_devices.begin());
// Run session at the default device rate.
session->configure(dev->get_default_rate());
// Run session in continuous mode.
session->start(0);
// Data to be read from the device is formatted into a vector of four
// floats in an array, specifically in the format
// <Chan A voltage, Chan A current, Chan B coltage, Chan B current>.
std::vector<std::array<float, 4>> buf;
while (true) {
try {
// Read 1024 samples at a time from the device.
// Note that the timeout (3rd parameter to read() defaults to 0
// (nonblocking mode) so the number of samples returned won't
// necessarily be 1024.
dev->read(buf, 1024);
} catch (const std::system_error& e) {
// Ignore sample drops which will occur due to the use of printf()
// which is slow when attached to a terminal.
}
// Iterate over all returned samples, doesn't have to be 1024).
for (auto i: buf) {
printf("Channel A: Voltage %f Current %f\n", i[0], i[1]);
printf("Channel B: Voltage %f Current %f\n", i[2], i[3]);
}
};
}
Writing data to a device
/>
// Create session object and add all compatible devices them to the
// session. Note that this currently doesn't handle returned errors.
Session* session = new Session();
session->add_all();
// Grab the first device from the session (we're assuming one exists).
auto dev = *(session->m_devices.begin());
// Run session at the default device rate.
session->configure(dev->get_default_rate());
// Run session in continuous mode.
session->start(0);
// Data to be written to the device is formatted into a vector of floats.
// Both channels can be written to simultaneously depending on what mode
// they're currently in.
std::vector<float> buf1;
std::vector<float> buf2;
while (true) {
// Generic function to grab new data to write to the device.
refill_data(buf1);
refill_data(buf2);
// Write data channel A of the device.
// Note that the timeout (3rd parameter to write() defaults to 0 (nonblocking mode).
dev->write(buf1, 0);
// Write data to channel B of the device.
dev->write(buf2, 1);
try {
// Read 1024 samples at a time from the device.
// Note that the timeout (3rd parameter to read() defaults to 0
// (nonblocking mode) so the number of samples returned won't
// necessarily be 1024.
dev->read(buf, 1024);
} catch (const std::system_error& e) {
// Ignore sample drops which will occur due to the use of printf()
// which is slow when attached to a terminal.
}
// Iterate over all returned samples, doesn't have to be 1024).
for (auto i: buf) {
printf("Channel A: Voltage %f Current %f\n", i[0], i[1]);
printf("Channel B: Voltage %f Current %f\n", i[2], i[3]);
}
};
}
Generated on Fri Feb 11 2022 13:51:01 for libsmu by 1.8.15