-
Notifications
You must be signed in to change notification settings - Fork 106
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
cd12acf
commit b7743cb
Showing
326 changed files
with
72,473 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
# Sphinx build info version 1 | ||
# This file hashes the configuration used when building these files. When it is not found, a full rebuild will be done. | ||
config: 8fb28f2e43cb391eb50e49da46679950 | ||
tags: 645f666f9bcd5a90fca523b33c5a78b7 |
Empty file.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
Attributes | ||
================== | ||
|
||
To simplify hardware configuration through different IIO drivers, basic class properties are exposed at the top-level of each device specific class. These properties abstract away the need to know a specific channel name, attribute type, source device name, and other details required in the libIIO API. Instead properties have easy to understand names, documentation, and error handling to help manage interfacing with different hardware. Property data can be read and written as follows from a given device interface class: | ||
|
||
.. code-block:: python | ||
import adi | ||
lidar = adi.fmclidar1() | ||
# Read current pulse width | ||
print(lidar.laser_pulse_width) | ||
# Change laser frequency to 1 MHz | ||
lidar.laser_frequency = 1000000 | ||
If more detail is required about a specific property it can be directly inspected in the class definitions documnentation or in python itself through the help methods: | ||
|
||
|
||
.. literalinclude:: pluto_help.cli | ||
:language: none | ||
|
||
For complete documentation about class properties reference the :doc:`supported devices</devices/index>` classes. |
164 changes: 164 additions & 0 deletions
164
prs/update_pluto_example/_sources/buffers/index.rst.txt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,164 @@ | ||
Buffers | ||
================== | ||
|
||
Using buffers or transmitting and receiving data is done through interacting with two methods. | ||
|
||
For receivers this is the **rx** method. How data is captured and therefore produced by this method is dependent on two main properties: | ||
|
||
* **rx_enabled_channels**: This is an array of integers (or channel names) and the number of elements in the array will determine the number of list items returned by **rx**. For devices with complex data types these are the indexes of the complex channels, not the individual I or Q channels. When len(**rx_enabled_channels**) == 1, **rx** will return just a single array and not a single array within a list. | ||
* **rx_buffer_size**: This is the number of samples returned in each array within the list. If the device produces complex data, like a transceiver, it will return complex data. This is defined by the author of each device specific class. | ||
|
||
For transmitters this is the **tx** method. How data is sent and therefore must be passed by this method is dependent on one main property: | ||
|
||
* **tx_enabled_channels**: This is an array of integers and the number of elements in the array will determine the number of items in the list to be submitted to **tx**. Like for **rx_enabled_channels**, devices with complex data types these are the indexes of the complex channels, not the individual I or Q channels. When only a single channel is enabled the data can be passed to **tx** as just an array and not an array within a list. | ||
|
||
**rx_enabled_channels** must have a length greater than zero but **tx_enabled_channels** can be set to None or an empty list. In this case when **tx** is called it must be called without inputs. This is a special case and will connect a zero source into the TX input stream within the FPGA for FPGA based devices. For background on how this internally works with FPGA based devices reference the generic `DAC driver <https://wiki.analog.com/resources/tools-software/linux-drivers/iio-dds/axi-dac-dds-hdl>`_. | ||
|
||
Cyclic Mode | ||
-------------- | ||
In many cases, it can be useful to continuously transmit a signal over and over, even for just debugging and testing. This can be especially handy when the hardware you are using has very high transmit or receive rates, and therefore impossible to keep providing data to in real-time. To complement these use cases it is possible to create transmit buffer which repeats, which we call **cyclic buffers**. Cyclic buffers are identical or normal or non-cyclic buffers, except when they reach hardware they will continuously repeat or be transmitted. Here is a small example on how to create a cyclic buffer: | ||
|
||
.. code-block:: python | ||
import adi | ||
sdr = adi.ad9361() | ||
# Create a complex sinusoid | ||
fc = 3000000 | ||
N = 1024 | ||
ts = 1 / 30000000.0 | ||
t = np.arange(0, N * ts, ts) | ||
i = np.cos(2 * np.pi * t * fc) * 2 ** 14 | ||
q = np.sin(2 * np.pi * t * fc) * 2 ** 14 | ||
iq = i + 1j * q | ||
# Enable cyclic buffers | ||
sdr.tx_cyclic_buffer = True | ||
# Send data cyclically | ||
sdr.tx(iq) | ||
At this point, the transmitter will keep transmitting the create sinusoid indefinitely until the buffer is destroyed or the *sdr* object destructor is called. Once data is pushed to hardware with a cyclic buffer the buffer must be manually destroyed or an error will occur if more data push. To update the buffer use the **tx_destroy_buffer** method before passing a new vector to the **tx** method. | ||
|
||
Annotated Buffers | ||
------------------ | ||
|
||
By default buffers appear as an array or a list of arrays. This can be confusing if all your channels do not produce similar data. For example, for IMUs like ADI16495 certain channels are for acceleration data and others are for angular velocity. To label this data the *rx_annotated* property can be used. When setting it to True the output of the **rx** method will be a dictionary with keys as channel names. Here an example: | ||
|
||
.. code-block:: python | ||
import adi | ||
dev = adi.adis16495() | ||
dev.rx_enabled_channels = [0, 3] | ||
print(dev.rx()) | ||
dev.rx_annotated = True | ||
print(dev.rx()) | ||
With output | ||
|
||
.. code-block:: bash | ||
[array([ 35681, 84055, -175914, -203645, 698249, -51670, | ||
-1770250, 1529968, 2586191, -5353355, -827741, 11736339, | ||
-9847894, -17242014, 97421833, 277496774], dtype=int32), | ||
array([ 49151, 753663, 3571711, 9928703, 18956287, | ||
25165823, 18612223, -10125313, -60850176, -114491392, | ||
-131350528, -61521920, 135069695, 466845695, 899235839, | ||
1362378751], dtype=int32)] | ||
{'accel_x': array([1775091711, 2072264703, 2147483647, 2147483647, 2147483647, | ||
2147483647, 2143404031, 2125430783, 2123120639, 2130821119, | ||
2139488255, 2144911359, 2147041279, 2147467263, 2147483647, | ||
2147483647], dtype=int32), | ||
'anglvel_x': array([357750219, 335109279, 323033231, 337667193, 337100396, 330408402, | ||
333459194, 335322576, 333247166, 333223475, 333996322, 333805525, | ||
333659152, 333664680, 333718473, 333895650], dtype=int32)} | ||
Buffer Units | ||
--------------- | ||
|
||
For certain devices it is possible to convert types to scientific units, such as volts, degrees, or meters per second among others. This is controlled by setting the property **rx_output_type** to either *raw* or *SI*. If set to *SI*, returned data from the **rx** method will be in scientific units (assuming its supported by the driver). Below is an example using an IMU: | ||
|
||
.. code-block:: python | ||
import adi | ||
dev = adi.adis16495() | ||
dev.rx_annotated = True # Make channel names appear in data | ||
dev.rx_enabled_channels = [3] # channel 0 is angular velocity in the x direction | ||
print(dev.rx()) | ||
dev.rx_output_type = "SI" | ||
print(dev.rx()) | ||
With output | ||
|
||
.. code-block:: bash | ||
{'anglvel_x': array([ 35644, 84039, -175647, -203867, 697612, -50201, | ||
-1770177, 1526291, 2589741, -5349126, -839188, 11738313, | ||
-9824911, -17267701, 97333042, 277410285], dtype=int32)} | ||
{'anglvel_x': array([9.29996712, 9.71257202, 9.40097973, 9.78345151, 9.77009362, | ||
9.59662456, 9.67300333, 9.71593538, 9.65847317, 9.6580597 , | ||
9.68022501, 9.67715545, 9.67511814, 9.67609361, 9.67323293, | ||
9.67104074])} | ||
To understand the exact scaling the driver documentation should be reviewed. | ||
|
||
Members | ||
-------------- | ||
.. automodule:: adi.rx_tx | ||
:members: | ||
|
||
|
||
Buffer Examples | ||
--------------- | ||
|
||
Collect data from one channel | ||
|
||
.. code-block:: python | ||
import adi | ||
sdr = adi.ad9361() | ||
# Get complex data back | ||
sdr.rx_enabled_channels = [0] | ||
chan1 = sdr.rx() | ||
Collect data from two channels | ||
|
||
.. code-block:: python | ||
import adi | ||
sdr = adi.ad9361() | ||
# Get both complex channel back | ||
sdr.rx_enabled_channels = [0, 1] | ||
data = sdr.rx() | ||
chan1 = data[0] | ||
chan2 = data[1] | ||
Send data on two channels | ||
|
||
.. code-block:: python | ||
import adi | ||
import numpy as np | ||
# Create radio | ||
sdr = adi.ad9371() | ||
sdr.tx_enabled_channels = [0, 1] | ||
# Create a sinewave waveform | ||
N = 1024 | ||
fs = int(sdr.tx_sample_rate) | ||
fc = 40000000 | ||
ts = 1 / float(fs) | ||
t = np.arange(0, N * ts, ts) | ||
i = np.cos(2 * np.pi * t * fc) * 2 ** 14 | ||
q = np.sin(2 * np.pi * t * fc) * 2 ** 14 | ||
iq = i + 1j * q | ||
fc = -30000000 | ||
i = np.cos(2 * np.pi * t * fc) * 2 ** 14 | ||
q = np.sin(2 * np.pi * t * fc) * 2 ** 14 | ||
iq2 = i + 1j * q | ||
# Send data to both channels | ||
sdr.tx([iq, iq2]) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,175 @@ | ||
Developers | ||
=================== | ||
|
||
.. warning:: | ||
This section is only for developers and advanced users. | ||
|
||
When submitting code or running tests, there are a few ways things are done in pyadi-iio. | ||
|
||
Invoke | ||
--------------------------- | ||
To make repetitve tasks easier, pyadi-iio utilizes pyinvoke. To see the available options (once pyinvoke is installed) run: | ||
|
||
.. code-block:: console | ||
invoke --list | ||
Available tasks: | ||
build Build python package | ||
builddoc Build sphinx doc | ||
changelog Print changelog from last release | ||
checkparts Check for missing parts in supported_parts.md | ||
createrelease Create GitHub release | ||
libiiopath Search for libiio python bindings | ||
precommit Run precommit checks | ||
setup Install required python packages for development through pip | ||
test Run pytest tests | ||
Precommit | ||
--------------------------- | ||
**pre-commit** is heavily relied on for keeping code in order and for eliminating certain bugs. Be sure to run these checks before submitting code. This can be run through pyinvoke or directly from the repo root as: | ||
|
||
.. code-block:: console | ||
invoke precommit | ||
.. code-block:: console | ||
pre-commit run --all-files | ||
Testing | ||
--------------------------- | ||
|
||
Testing pyadi-iio requires hardware, but fortunately by default it assumes no hardware is connected unless found. It will only load specific tests for hardware it can find and skip all other tests. **pytest**, which is the framework pyadi-iio uses, can be call as following: | ||
|
||
.. code-block:: console | ||
invoke test | ||
.. code-block:: console | ||
python3 -m pytest <add more arguments as needed> | ||
Test Configuration | ||
^^^^^^^^^^^^^^^^^^ | ||
|
||
There are several advanced features of pytest that are utilized by pyadi-iio. Specifically custom markers and custom plugins. | ||
|
||
Markers are a way of labeling tests, which can be then used to filter specific tests. Markers are provided through the `test_map.py <https://github.com/analogdevicesinc/pyadi-iio/blob/master/test/test_map.py>`_ file in the test directory. These markers are used to map FPGA based boards with daughtercards to specific tests. `Reference design folder names <https://wiki.analog.com/resources/tools-software/linux-software/embedded_arm_images>`_ from the ADI SD cards are using as the markers, which them can be passed through the *-m* flag to enabled certain tests. For example, the following would enable all tests related to *ADRV9009*, assuming the hardware is available: | ||
|
||
.. code-block:: console | ||
python3 -m pytest -m zynqmp-zcu102-rev10-adrv9009 | ||
To help manage libiio contexts, filter tests based on those contexts, and map drivers to board definitions, pyadi-iio utilizes the pytest plugin `pytest-libiio <https://pypi.org/project/pytest-libiio/>`_. This must be installed before tests are run since all test implementations rely on `pytest-libiio fixtures <https://pytest-libiio.readthedocs.io/en/latest/fixtures/>`_. Generally, pyadi-iio will also use the `standard hardware map <https://pytest-libiio.readthedocs.io/en/latest/cli/#hardware-maps>`_ provided by *pytest-libiio* to map drivers to board definitions. To enable the hardware make requires the *--adi-hw-map* flag as: | ||
|
||
.. code-block:: console | ||
python3 -m pytest --adi-hw-map | ||
If you are working on a driver or board that is not in the hardware map, a custom one can be created as documentation in the `pytest-libiio CLI <https://pytest-libiio.readthedocs.io/en/latest/cli/#hardware-maps>`_. | ||
|
||
|
||
|
||
New Hardware Requirements | ||
^^^^^^^^^^^^^^^^^^^^^^^^^ | ||
|
||
In order to maintain pyadi-iio, for all new drivers the development team will require emulation contexts to be submitted alongside the new class interfaces. This is to ensure that the new drivers are tested and maintained. Emulation contexts can be created using `xml_gen <https://pytest-libiio.readthedocs.io/en/latest/emulation/#adding-device-support>`_. CI will automatically validate that all hardware interfaces have emulation contexts and prevent merging if they are missing. | ||
|
||
.. note:: | ||
Note that xml_gen is not the same as iio_genxml, as iio_genxml does not capture default values of properties required for emulation. | ||
|
||
|
||
Test Functions and Fixtures | ||
^^^^^^^^^^^^^^^^^^^^^^^^^^^ | ||
|
||
pyadi-iio has a large set of parameterizable fixtures for testing different device specific class interfaces. See the links belows to the different test categories: | ||
|
||
.. toctree:: | ||
:maxdepth: 4 | ||
|
||
test_attr | ||
test_dma | ||
test_generics | ||
|
||
|
||
Set Up Isolated Environment | ||
--------------------------- | ||
|
||
This section will discuss a method to do isolated development with the correct package versions. The main purpose here is to eliminate any discrepancies that can arise (especially with the linting tools) when running precommit and other checks. This is also useful to not pollute your local global packages. The approach here relies upon leveraging **pyenv** and **pipenv** together. | ||
|
||
|
||
Install pyenv | ||
^^^^^^^^^^^^^^^^^ | ||
|
||
**pyenv** is a handy tool for installing different and isolated versions of python on your system. Since distributions can ship with rather random versions of python, pyenv can help us install exactly the versions we want. The quick way to install pyenv is with their bash script: | ||
|
||
|
||
.. code-block:: bash | ||
curl https://pyenv.run | bash | ||
Add to your path and shell startup script (.bashrc, .zshrc, ...) | ||
|
||
.. code-block:: bash | ||
export PATH="/home/<username>/.pyenv/bin:$PATH | ||
eval "$(pyenv init -)" | ||
eval "$(pyenv virtualenv-init -)" | ||
Install the desired python version | ||
.. code-block:: bash | ||
pyenv install 3.6.9 | ||
Create isolated install with pipenv | ||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | ||
Get the repo, set python version, and setup env | ||
.. code-block:: bash | ||
pip3 install -U pipenv | ||
pyenv local 3.6.9 | ||
git clone [email protected]:analogdevicesinc/pyadi-iio.git | ||
pipenv install | ||
pipenv shell | ||
pipenv install -r requirements.txt | ||
pipenv install -r requirements_dev.txt | ||
Now at this point we have all the necessary development packages to start working. If you close the current shell you will lose the environment. To return to it, go to the project folder and run: | ||
.. code-block:: bash | ||
cd <project folder> | ||
pyenv local 3.6.9 | ||
pipenv shell | ||
Emulation | ||
--------------------------- | ||
By leveraging `iio-emu <https://github.com/analogdevicesinc/iio-emu>`_, hardware or contexts can be emulated for testing without physical devices. However, currently this emulation does not validate attribute rates, states of drivers, or equivalent data sources. This feature should be used to test a library itself rather than hardware drivers. | ||
**pyadi-iio** uses *iio-emu* through *pytest-libiio*, which handles loading the correct context files based on the fixtures used for each test. Essentially, when *pytest* is run, based on the fixture below, *pytest-libiio* will spawn the correct context with *iio-emu* and pass the URI of that context to the test. | ||
.. code-block:: python | ||
import pytest | ||
import iio | ||
@pytest.mark.iio_hardware("pluto", False) # Set True disables test during emulation | ||
def test_libiio_device(iio_uri): | ||
ctx = iio.Context(iio_uri) | ||
... | ||
To create and add more context files for testing with **pyadi-iio** follow `this page <https://pytest-libiio.readthedocs.io/en/latest/emulation/>`_. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
Attribute Tests | ||
================= | ||
|
||
Functions used by test fixtures for evaluating attributes and driver state. | ||
|
||
.. automodule:: test.attr_tests | ||
:members: |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
DMA Tests | ||
================= | ||
|
||
Functions used by test fixtures for evaluating receive and transmit DMA/buffers | ||
|
||
.. automodule:: test.dma_tests | ||
:members: |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
Generic Tests | ||
================= | ||
|
||
Functions used by test fixtures for evaluating drivers without pyadi-iio classes or not by using pyadi-iio classes. | ||
|
||
.. automodule:: test.generics | ||
:members: |
Oops, something went wrong.