├── .gitattributes ├── .github └── workflows │ ├── pypi.yaml │ └── test.yml ├── .gitignore ├── LICENSE.md ├── MANIFEST.in ├── README.md ├── doc ├── c_sample │ ├── Makefile │ ├── README.txt │ ├── ps6000AWG.c │ ├── ps6000_broken_siggen.c │ └── ps6000getValuesCrash.c ├── picoscope.html ├── picoscope.picobase.html ├── picoscope.ps2000.html ├── picoscope.ps2000a.html ├── picoscope.ps3000.html ├── picoscope.ps3000a.html ├── picoscope.ps4000.html ├── picoscope.ps5000a.html └── picoscope.ps6000.html ├── examples ├── awgdemo.py ├── discover_devices.py ├── dualview_time_fft.py ├── freqmeasure.py ├── garbageCollectorTest.py ├── just_get_connected.py ├── openUnitAsyncDemo.py ├── ps2000_demo.py ├── radar_capture_functions.py ├── runBlock_callback.py ├── sigGetBuiltIndemo.py ├── specgram_plot.py ├── test_ps3000a.py ├── test_ps4000a.py ├── test_ps5000a.py └── test_ps6000a.py ├── makedocs.bat ├── makedocsLinux ├── picoscope ├── __init__.py ├── _version.py ├── darwin_utils.py ├── error_codes.py ├── picobase.py ├── ps2000.py ├── ps2000a.py ├── ps3000.py ├── ps3000a.py ├── ps4000.py ├── ps4000a.py ├── ps5000.py ├── ps5000a.py ├── ps6000.py └── ps6000a.py ├── setup.cfg ├── setup.py └── versioneer.py /.gitattributes: -------------------------------------------------------------------------------- 1 | picoscope/_version.py export-subst 2 | -------------------------------------------------------------------------------- /.github/workflows/pypi.yaml: -------------------------------------------------------------------------------- 1 | # https://packaging.python.org/en/latest/guides/publishing-package-distribution-releases-using-github-actions-ci-cd-workflows/ 2 | name: Publish Python 🐍 distributions 📦 to PyPI and TestPyPI 3 | 4 | on: 5 | - push 6 | 7 | jobs: 8 | build-n-publish: 9 | name: Build and publish Python 🐍 distributions 📦 to PyPI and TestPyPI 10 | if: startsWith(github.ref, 'refs/tags') 11 | runs-on: ubuntu-latest 12 | steps: 13 | - uses: actions/checkout@master 14 | with: 15 | # https://github.com/actions/checkout#fetch-all-history-for-all-tags-and-branches 16 | fetch-depth: 0 17 | - name: Set up Python 18 | uses: actions/setup-python@v1 19 | with: 20 | python-version: "3.10" 21 | - name: Install pypa/build 22 | run: python -m pip install build --user 23 | - name: Build a binary wheel and a source tarball 24 | run: python -m build --sdist --wheel --outdir dist/ . 25 | - name: Publish distribution 📦 to PyPI 26 | uses: pypa/gh-action-pypi-publish@release/v1 27 | with: 28 | user: __token__ 29 | password: ${{ secrets.PYPI_API_TOKEN }} 30 | print_hash: true 31 | verify_metadata: true 32 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: Testing 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | pull_request: 8 | 9 | jobs: 10 | build: 11 | runs-on: ubuntu-latest 12 | strategy: 13 | matrix: 14 | python-version: ['3.7', '3.11', '3.12'] 15 | 16 | steps: 17 | - uses: actions/checkout@v1 18 | - name: Set up Python ${{ matrix.python-version }} 19 | uses: actions/setup-python@v2 20 | with: 21 | python-version: ${{ matrix.python-version }} 22 | - name: Install dependencies 23 | run: | 24 | python -m pip install --upgrade pip 25 | python -m pip install flake8 numpy 26 | - name: Flake 27 | run: | 28 | flake8 picoscope setup.py 29 | flake8 examples/* 30 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | test_block_file.dat 2 | *.pyc 3 | *.py~ 4 | build 5 | dist 6 | *.egg-info 7 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | pico-python is Copyright (c) 2013-2016 By: 2 | Colin O'Flynn 3 | Mark Harfouche 4 | All rights reserved. 5 | 6 | Redistribution and use in source and binary forms, with or without 7 | modification, are permitted provided that the following conditions are met: 8 | 9 | 1. Redistributions of source code must retain the above copyright notice, this 10 | list of conditions and the following disclaimer. 11 | 2. Redistributions in binary form must reproduce the above copyright notice, 12 | this list of conditions and the following disclaimer in the documentation 13 | and/or other materials provided with the distribution. 14 | 15 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 16 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 17 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 18 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR 19 | ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 20 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 21 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 22 | ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 23 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 24 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include README.md 2 | include LICENSE.md 3 | include examples/*.py 4 | include versioneer.py 5 | include picoscope/_version.py 6 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # pico-python 2 | [![Build Status](https://travis-ci.org/colinoflynn/pico-python.svg?branch=master)](https://travis-ci.org/colinoflynn/pico-python) 3 | 4 | This is a Python 2.7+ library for the Pico Scope. It uses the provided DLL 5 | for actual communications with the instrument. There have been a few examples 6 | around, but this one tries to improve on them via: 7 | * Subclass instrument-specific stuff, so can support more families 8 | * Use exceptions to raise errors, and gives you nice English error messages (copied from PS Manual) 9 | * Provide higher-level functions (e.g. just setup timebase, function deals with instrument-specific limitations) 10 | * Supports both Windows and Linux and Mac 11 | 12 | System has support for: 13 | * PS6000A Class (Picoscope 6xxxE) 14 | * PS6000 15 | * PS5000A Class (PicoScope 5242A/5243A/5244A/5442A/5443A/5444A/5242B/5244B/5442B/5443B/5444B) 16 | * PS4000A Class (PicoScope 4444/4824) 17 | * PS3000 Class (PicoScope 3204/3205/3206/3224/3424/3425) 18 | * PS3000A Class (PicoScope 3204A/3204B/3205A/3205B/3206A/3206B/3207A/3207B/3204/3205/3206/3404A/3404B/3405A/3405A/3406A/3406B) 19 | * PS2000 Class (PicoScope 2104/2105/2202/2203/2204/2205/2204A/2205A) 20 | * PS2000A Class (PicoScope 2206/2206A/2206B/2207/2207A/2207B/2208/2208A/2208B/2205A MSO/2206B MSO/2207B MSO/2208B MSO/2405A/2406B/2407B/2408B) 21 | 22 | Note the 'A' series covers a different ground than the non-A series! Check the programming manuals posted at http://www.picotech.com/document/ for details. 23 | 24 | 25 | ## Installation 26 | You need to install the Python module as well as the Picoscope libraries for your Operating system. 27 | 28 | ### Module installation 29 | #### PyPi 30 | ``` 31 | pip install picoscope 32 | ``` 33 | 34 | #### Git 35 | If you are developping the library, or need some feature that we haven't pushed to PyPi yet, use 36 | git clone to put the directory somewhere. 37 | Then use the setup.py script to install the library in development mode: 38 | ```bash 39 | git clone git@github.com:colinoflynn/pico-python.git 40 | cd pico-python 41 | python setup.py develop 42 | ``` 43 | 44 | ### OS specific 45 | #### Windows 46 | You will require the PicoScope DLLs for this package to work. The easiest method is to install the latest PicoScope software 47 | or SDK from https://www.picotech.com/downloads . 48 | 49 | You may need to add the PicoScope install directory to the path, especially if changing between versions. Be sure you have installed a matching 32-bit or 64-bit version for your Python install. 50 | 51 | ``` 52 | import os 53 | picoinstallpath = os.path.normpath(r"C:\Program Files\Pico Technology\PicoScope 7 T&M Early Access") 54 | if picoinstallpath not in os.environ['PATH']: 55 | print("Adding Pico Install to Path") 56 | os.environ['PATH'] = picoinstallpath + os.pathsep + os.environ['PATH'] 57 | else: 58 | print("Pico Install Already on Path") 59 | ``` 60 | 61 | 62 | #### Linux 63 | Install the PicoScope Beta for Linux version of PicoScope as describe under Getting DLL's (above). Currently this is the only way to install the shared libraries (SDK) 64 | 65 | Once you have PicoScope running you need to add your login account to the pico group in order to access the USB. The examples will crash if you don't have permission to use the USB. This is true for use of the shared libraries in general, even if you're not using pico-python. 66 | 67 | ``` 68 | useradd -G pico $USER 69 | ``` 70 | 71 | #### Mac OSX 72 | You either want to add this every time before you start python or IPython, but I think it is best to add this line to 73 | `.bash_profile` (or the Mac Equivalent ????). 74 | ```bash 75 | export DYLD_LIBRARY_PATH=$DYLD_LIBRARY_PATH:/Applications/PicoScope6.app/Contents/Resources/lib 76 | ``` 77 | 78 | Recently, this directory has moved to a new location See [Issue #143](https://github.com/colinoflynn/pico-python/issues/143) 79 | ``` 80 | export DYLD_LIBRARY_PATH="$DYLD_LIBRARY_PATH:/Applications/PicoScope 6.app/Contents/Resources/lib" 81 | ``` 82 | 83 | See [Issue 80](https://github.com/colinoflynn/pico-python/issues/80#issuecomment-314149552) for more information on how this was found. 84 | 85 | You should also add yourself to the pico group so that your user has access to the picoscope as a USB device 86 | ```bash 87 | # Create the new pico group : 88 | sudo dseditgroup -o create pico 89 | # Add the current user to the pico group : 90 | sudo dseditgroup -o edit -a $USER -t user pico 91 | ``` 92 | 93 | ### Using Anaconda/Conda 94 | Seems like Anaconda has an issue with ctypes. See the comment [here](https://github.com/pymedusa/Medusa/issues/1843#issuecomment-310126049) imdatacenters says to: 95 | > If you are using a special version of Python [like Anaconda] and you can't fix it. 96 | > Navigate to line 362 of lib/ctypes/init.py and change it to: 97 | > `self._handle = _dlopen(str(self._name), mode)` 98 | 99 | # Similar Projects 100 | PicoPy uses Cython to interface with a PicoScope 3000A 101 | https://github.com/hgomersall/PicoPy 102 | 103 | Picoscope offers their official wrappers, 104 | https://github.com/picotech/picosdk-python-wrappers 105 | 106 | # Authors, Copyright, and Thanks 107 | pico-python is Copyright (C) 2013 By: 108 | * Colin O'Flynn 109 | * Mark Harfouche 110 | 111 | All rights reserved. 112 | See LICENSE.md for license terms. 113 | 114 | Inspired by Patrick Carle's code at http://www.picotech.com/support/topic11239.html 115 | which was adapted from http://www.picotech.com/support/topic4926.html 116 | 117 | # Contributing 118 | 1. Fork. 119 | 2. Make a new branch. 120 | 3. Commit to your new branch. 121 | 4. Add yourself to the authors/acknowledgments (whichever you find appropriate). 122 | 5. Submit a pull request. 123 | 124 | Alternatively, you can follow more thorough instructions 125 | [here](http://scikit-image.org/docs/dev/contribute.html). 126 | 127 | # Developer notes 128 | Commit and create a new tag with git 129 | ``` 130 | git commit 131 | git tag -a X.Y.Z -m "Short descriptive message" 132 | ``` 133 | 134 | Push the tags to the repo 135 | ``` 136 | git push origin X.Y.Z 137 | ``` 138 | 139 | or to push all tags 140 | ``` 141 | git push --tags 142 | ``` 143 | 144 | [versioneer](https://github.com/warner/python-versioneer) takes care of updating the version. 145 | New tags will be pushed to PyPi automatically by Travis. 146 | -------------------------------------------------------------------------------- /doc/c_sample/Makefile: -------------------------------------------------------------------------------- 1 | all: 2 | gcc ps6000_broken_siggen.c -lps6000 -o a.out 3 | gcc ps6000getValuesCrash.c -lps6000 -o ps6000getValuesCrash.out 4 | 5 | clean: 6 | rm -f a.out 7 | 8 | .PHONY: all clean 9 | -------------------------------------------------------------------------------- /doc/c_sample/README.txt: -------------------------------------------------------------------------------- 1 | These files are used to reproduce issues using C, which helps reporting bugs. Also can help in confirming our Python code is working 2 | as expected based on the C examples. This is for developers only, as a user you can ignore this directory. -------------------------------------------------------------------------------- /doc/c_sample/ps6000AWG.c: -------------------------------------------------------------------------------- 1 | 2 | #include 3 | #include 4 | #include 5 | 6 | 7 | #ifdef _WIN32 8 | #include "../ps6000Api.h" 9 | #else 10 | #include 11 | #endif 12 | 13 | void check_error(int r){ 14 | if(r != PICO_OK){ 15 | printf("Picoscope Error code is 0x%X", r); 16 | exit(0); 17 | } 18 | } 19 | 20 | int main(){ 21 | int awgDeltaTMultiplier = 2; 22 | uint32_t deltaPhase = 1<<(32 - 14 -awgDeltaTMultiplier); 23 | float awgDeltaT = (float)(5E-9 * (1< 7 | #include 8 | #include 9 | #include 10 | 11 | void check_error(int r){ 12 | if(r != PICO_OK){ 13 | printf("Picoscope Error code is 0x%X", r); 14 | exit(0); 15 | } 16 | } 17 | 18 | int main(){ 19 | short ps_handle; 20 | int r; 21 | printf("This is a demo about the weirdly behaving Picoscope 6403B\n" 22 | "signal generator sticking to low values\n"); 23 | 24 | r = ps6000OpenUnit(&ps_handle, NULL); 25 | check_error(r); 26 | 27 | // taken from page 23 28 | unsigned long timebase = 5; 29 | float timebase_dt = 6.4E-9; 30 | float timeInterval_ns; 31 | uint32_t maxSamples; 32 | 33 | 34 | float wanted_duration = 1E-6; 35 | 36 | unsigned long wanted_samples = 0; 37 | 38 | wanted_samples = wanted_duration / timebase_dt; 39 | 40 | r = ps6000GetTimebase2(ps_handle, timebase, wanted_samples, &timeInterval_ns, 0, &maxSamples, 0); 41 | check_error(r); 42 | 43 | printf("Set timebase to %d = %f ns\n", timebase, timeInterval_ns); 44 | printf("Will measure for %d samples = %f ns\n", wanted_samples, wanted_duration * 1E9); 45 | printf("Max samples = %d\n", maxSamples); 46 | 47 | if(wanted_samples > maxSamples){ 48 | printf("Error, too many samples \n"); 49 | exit(0); 50 | } 51 | 52 | // change me when you change the range of the channel 53 | double channel_pk_to_pk = 0.05; 54 | r = ps6000SetChannel(ps_handle, PS6000_CHANNEL_A, 1, PS6000_DC_1M, 55 | PS6000_50MV, 0, PS6000_BW_FULL); 56 | check_error(r); 57 | 58 | r = ps6000SetSimpleTrigger(ps_handle, 1, PS6000_CHANNEL_A, 0, PS6000_RISING, 0, 1000); 59 | check_error(r); 60 | 61 | float f_gen = 1/(10*timebase_dt); 62 | unsigned long pkToPk = 4.0E6; 63 | r = ps6000SetSigGenBuiltIn(ps_handle, 0, pkToPk, PS6000_SQUARE, 64 | f_gen, f_gen, 0, 0, PS6000_UP, PS6000_ES_OFF, 1, 0, 65 | PS6000_SIGGEN_RISING, PS6000_SIGGEN_NONE, 0); 66 | check_error(r); 67 | printf("Just set signal generator to generate a %d uV pkToPk signal @ %f MHz\n", pkToPk, f_gen/1E6); 68 | 69 | int32_t timeIndisposedMs = 0; 70 | r = ps6000RunBlock(ps_handle, 0, wanted_samples, timebase, 0, &timeIndisposedMs, 0, NULL, NULL); 71 | check_error(r); 72 | 73 | printf("Time indisposed = %d ms\n", timeIndisposedMs); 74 | 75 | short ready = 0; 76 | do{ 77 | r = ps6000IsReady(ps_handle, &ready); 78 | check_error(r); 79 | }while(ready == 0); 80 | sleep(1); 81 | 82 | int16_t *data_ptr; 83 | 84 | data_ptr = (int16_t *)malloc(sizeof(int16_t)*wanted_samples); 85 | if(data_ptr == NULL){ 86 | printf("Error, malloc for data failed .... what.....\n"); 87 | exit(0); 88 | } 89 | uint32_t i; 90 | for(i=0; i 7 | #include 8 | #include 9 | 10 | #ifdef _WIN32 11 | #include "..\ps6000Api.h" 12 | #else 13 | #include 14 | #endif 15 | 16 | void check_error(int r){ 17 | if (r != PICO_OK){ 18 | printf("Picoscope Error code is 0x%X", r); 19 | exit(0); 20 | } 21 | } 22 | 23 | int main(){ 24 | short ps_handle; 25 | int r; 26 | printf("This is a demo about the weirdly behaving Picoscope 6403B\n" 27 | "getValues seems to crash the system after repeated calls\n" 28 | "This code runs a stress test until it crashes the application\n" 29 | "I tested this code with a 2.5V pkToPk 1kHz sine wave attached to Channel A\n"); 30 | 31 | r = ps6000OpenUnit(&ps_handle, NULL); 32 | check_error(r); 33 | 34 | // taken from page 23 35 | // TODO: Set the time base so that we get 4096 samplkes 36 | unsigned long timebase = 5; 37 | float timebase_dt = (float)(6.4E-9); 38 | float timeInterval_ns; 39 | uint32_t maxSamples; 40 | 41 | 42 | unsigned long wanted_samples = 4096; 43 | float wanted_duration = wanted_samples * timebase_dt; 44 | 45 | r = ps6000GetTimebase2(ps_handle, timebase, wanted_samples, &timeInterval_ns, 0, &maxSamples, 0); 46 | check_error(r); 47 | 48 | printf("Set timebase to %d = %f ns\n", timebase, timeInterval_ns); 49 | printf("Will measure for %d samples = %f ns\n", wanted_samples, wanted_duration * 1E9); 50 | printf("Max samples = %d\n", maxSamples); 51 | 52 | if (wanted_samples > maxSamples){ 53 | printf("Error, too many samples \n"); 54 | exit(0); 55 | } 56 | 57 | // change me when you change the range of the channel 58 | double channel_pk_to_pk = 2.0; 59 | r = ps6000SetChannel(ps_handle, PS6000_CHANNEL_A, 1, PS6000_DC_1M, 60 | PS6000_2V, 0, PS6000_BW_FULL); 61 | check_error(r); 62 | 63 | r = ps6000SetSimpleTrigger(ps_handle, 1, PS6000_CHANNEL_A, 0, PS6000_RISING, 0, 1000); 64 | check_error(r); 65 | printf("Set the trigger\n"); 66 | 67 | int nCaptures = 4096; 68 | 69 | int16_t *data_ptr; 70 | 71 | 72 | 73 | uint32_t noSamples; 74 | short overflow; 75 | int32_t timeIndisposedMs = 0; 76 | int i; 77 | for (i = 0; i 2 | Python: package picoscope 3 | 4 | 5 | 6 | 7 | 8 |
 
9 |  
picoscope (version 0.7.11+1.g6b41cf5.dirty)
index
/home/guest/software/pico-python/picoscope/__init__.py
12 |

13 |

14 | 15 | 16 | 18 | 19 | 20 |
 
17 | Package Contents
       
_version
21 | darwin_utils
22 | error_codes
23 | picobase
24 |
ps2000
25 | ps2000a
26 | ps3000
27 | ps3000a
28 |
ps4000
29 | ps4000a
30 | ps5000
31 | ps5000a
32 |
ps6000
33 |

34 | 35 | 36 | 38 | 39 | 40 |
 
37 | Data
       __all__ = ['ps2000', 'ps2000a', 'ps3000', 'ps3000a', 'ps4000', 'ps4000a', 'ps5000a', 'ps6000']
41 | __license__ = 'FreeBSD'

42 | 43 | 44 | 46 | 47 | 48 |
 
45 | Author
       Colin O'Flynn, Mark Harfouche
49 | -------------------------------------------------------------------------------- /doc/picoscope.picobase.html: -------------------------------------------------------------------------------- 1 | 2 | Python: module picoscope.picobase 3 | 4 | 5 | 6 | 7 | 8 |
 
9 |  
picoscope.picobase
index
/home/guest/software/pico-python/picoscope/picobase.py
12 |

This is the base class that all picoscope modules use.
13 |  
14 | As much logic as possible is put into this file.
15 | At minimum each instrument file requires you to modify the name of the API
16 | function call (e.g. ps6000xxxx vs ps4000xxxx).
17 |  
18 | This is to force the authors of the instrument files to actually read the
19 | documentation as opposed to assuming similarities between scopes.
20 |  
21 | You can find pico-python at github.com/colinoflynn/pico-python .

22 |

23 | 24 | 25 | 27 | 28 | 29 |
 
26 | Modules
       
inspect
30 |
numpy
31 |
time
32 |

33 | 34 | 35 | 37 | 38 | 39 |
 
36 | Data
       absolute_import = _Feature((2, 5, 0, 'alpha', 1), (3, 0, 0, 'alpha', 0), 16384)
40 | division = _Feature((2, 2, 0, 'alpha', 2), (3, 0, 0, 'alpha', 0), 8192)
41 | print_function = _Feature((2, 6, 0, 'alpha', 2), (3, 0, 0, 'alpha', 0), 65536)
42 | unicode_literals = _Feature((2, 6, 0, 'alpha', 2), (3, 0, 0, 'alpha', 0), 131072)
43 | -------------------------------------------------------------------------------- /examples/awgdemo.py: -------------------------------------------------------------------------------- 1 | """ 2 | PS6000 AWG Demo. 3 | 4 | By: Mark Harfouche 5 | 6 | This is a demo of how to use AWG with the Picoscope 6000 7 | It was tested with the PS6403B USB2.0 version 8 | 9 | The AWG is connected to Channel A. 10 | Nothing else is required 11 | 12 | Warning, there seems to be a bug with AWG 13 | see http://www.picotech.com/support/topic12969.html 14 | 15 | """ 16 | from __future__ import division 17 | from __future__ import absolute_import 18 | from __future__ import print_function 19 | from __future__ import unicode_literals 20 | 21 | import time 22 | from picoscope import ps6000 23 | import pylab as plt 24 | import numpy as np 25 | 26 | if __name__ == "__main__": 27 | print(__doc__) 28 | 29 | print("Attempting to open Picoscope 6000...") 30 | 31 | # see page 13 of the manual to understand how to work this beast 32 | ps = ps6000.PS6000() 33 | 34 | print("Found the following picoscope:") 35 | print(ps.getAllUnitInfo()) 36 | 37 | waveform_desired_duration = 1E-3 38 | obs_duration = 3 * waveform_desired_duration 39 | sampling_interval = obs_duration / 4096 40 | 41 | (actualSamplingInterval, nSamples, maxSamples) = \ 42 | ps.setSamplingInterval(sampling_interval, obs_duration) 43 | print("Sampling interval = %f ns" % (actualSamplingInterval * 1E9)) 44 | print("Taking samples = %d" % nSamples) 45 | print("Maximum samples = %d" % maxSamples) 46 | 47 | waveformAmplitude = 1.5 48 | waveformOffset = 0 49 | x = np.linspace(-1, 1, num=ps.AWGMaxSamples, endpoint=False) 50 | # generate an interesting looking waveform 51 | waveform = waveformOffset + (x / 2 + (x ** 2) / 2) * waveformAmplitude 52 | 53 | (waveform_duration, deltaPhase) = ps.setAWGSimple( 54 | waveform, waveform_desired_duration, offsetVoltage=0.0, 55 | indexMode="Dual", triggerSource='None') 56 | 57 | # the setChannel command will chose the next largest amplitude 58 | # BWLimited = 1 for 6402/6403, 2 for 6404, 0 for all 59 | channelRange = ps.setChannel('A', 'DC', waveformAmplitude, 0.0, 60 | enabled=True, BWLimited=False) 61 | 62 | print("Chosen channel range = %d" % channelRange) 63 | 64 | ps.setSimpleTrigger('A', 1.0, 'Falling', delay=0, timeout_ms=100, 65 | enabled=True) 66 | # ps.setSimpleTrigger('TriggerAux', 0.0, 'Falling', delay=0, 67 | # timeout_ms=100, enabled=True) 68 | 69 | ps.runBlock() 70 | ps.waitReady() 71 | print("Waiting for awg to settle.") 72 | time.sleep(2.0) 73 | ps.runBlock() 74 | ps.waitReady() 75 | print("Done waiting for trigger") 76 | dataA = ps.getDataV('A', nSamples, returnOverflow=False) 77 | 78 | dataTimeAxis = np.arange(nSamples) * actualSamplingInterval 79 | 80 | ps.stop() 81 | ps.close() 82 | 83 | plt.ion() 84 | plt.figure() 85 | plt.hold(True) 86 | plt.plot(dataTimeAxis, dataA, label="Clock") 87 | plt.grid(True, which='major') 88 | plt.title("Picoscope 6000 waveforms") 89 | plt.ylabel("Voltage (V)") 90 | plt.xlabel("Time (ms)") 91 | plt.legend() 92 | plt.show() 93 | -------------------------------------------------------------------------------- /examples/discover_devices.py: -------------------------------------------------------------------------------- 1 | """ 2 | Shows a demo of use to use the enumerate devices feature. 3 | 4 | By: Mark Harfouche 5 | 6 | """ 7 | from __future__ import division 8 | from picoscope import ps6000 9 | import time 10 | 11 | if __name__ == "__main__": 12 | ps = ps6000.PS6000(connect=False) 13 | 14 | allSerialNumbers = ps.enumerateUnits() 15 | 16 | print("Found the following device serial numbers: ") 17 | for serial in allSerialNumbers: 18 | print(serial + "\n") 19 | 20 | # you can open devices by serial number 21 | serial = allSerialNumbers[0] 22 | ps = ps6000.PS6000(serial) 23 | 24 | # do stuff 25 | ps.flashLed(10) 26 | time.sleep(4.0) # the above flash takes roughly 4 seconds 27 | 28 | ps.close() 29 | -------------------------------------------------------------------------------- /examples/dualview_time_fft.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # 3 | # Based on code from Luke Campagnola, author of amazing pyqtgraph library 4 | 5 | from PyQt4 import QtGui, QtCore 6 | import pyqtgraph as pg 7 | import numpy as np 8 | from picoscope import ps6000 9 | 10 | 11 | def setupScope(): 12 | ps = ps6000.PS6000() 13 | 14 | # Example of simple capture 15 | res = ps.setSamplingFrequency(500E6, 4096) 16 | sampleRate = res[0] 17 | print("Sampling @ %f MHz, %d samples" % (res[0] / 1E6, res[1])) 18 | ps.setChannel("A", "AC", 50E-3) 19 | return [ps, sampleRate] 20 | 21 | 22 | class MainWindow(QtGui.QMainWindow): 23 | def __init__(self): 24 | QtGui.QMainWindow.__init__(self) 25 | 26 | self.plotLayout = pg.GraphicsLayoutWidget() 27 | self.setCentralWidget(self.plotLayout) 28 | 29 | self.plot = self.plotLayout.addPlot(row=0, col=0) 30 | # self.avgPlot = self.plotLayout.addPlot(row=1, col=0) 31 | self.specPlot = self.plotLayout.addPlot( 32 | row=1, col=0, labels={'bottom': ('Frequency', 'Hz')}) 33 | self.specPlot.setYRange(0, 0.2) 34 | 35 | self.wave = ScrollingPlot() 36 | self.plot.addItem(self.wave) 37 | 38 | self.triggerY = pg.InfiniteLine(angle=0, movable=True) 39 | self.triggerX = pg.InfiniteLine(movable=True) 40 | self.plot.addItem(self.triggerX) 41 | self.plot.addItem(self.triggerY) 42 | 43 | self.plot.setXRange(-1000, 1000) 44 | self.plot.setYRange(-0.05, 0.05) 45 | 46 | self.resize(800, 800) 47 | self.show() 48 | 49 | [self.scope, self.rate] = setupScope() 50 | self.scopeRunning = False 51 | 52 | self.timer = QtCore.QTimer() 53 | self.timer.timeout.connect(self.update) 54 | self.timer.start(50) 55 | 56 | self.trigAvg = np.zeros(5000) 57 | self.lastValue = None 58 | self.lastData = None 59 | self.persistent = [] 60 | self.lastUpdate = None 61 | 62 | def update(self): 63 | now = pg.ptime.time() 64 | if self.lastUpdate is None: 65 | self.lastUpdate = now 66 | dt = 0.0 67 | else: 68 | dt = now - self.lastUpdate 69 | self.lastUpdate = now 70 | 71 | # read data from sound device 72 | if self.scopeRunning is False: 73 | self.scope.runBlock() 74 | self.scopeRunning = True 75 | 76 | if self.scope.isReady(): 77 | data = self.scope.getDataV(0, 4096) 78 | self.scopeRunning = False 79 | else: 80 | data = [] 81 | 82 | if self.scopeRunning is False: 83 | self.scope.runBlock() 84 | 85 | # if self.lastData is not None: 86 | # data.append([self.lastData[-1]]) 87 | 88 | """ 89 | while True: 90 | chunk = np.fromstring(self.pcm.read()[1], dtype=np.int16) / 2.0**15 91 | if len(chunk) == 0 and sum(map(len, data)) > 1024: 92 | break 93 | data.append(chunk) 94 | """ 95 | 96 | if len(data) > 0: 97 | # data = np.concatenate(data) 98 | self.wave.append(data) 99 | 100 | # If there is a trigger available, shift wave plot to align 101 | # and add a persistent trace 102 | ty = self.triggerY.value() 103 | tx = self.triggerX.value() 104 | 105 | # if self.lastData is None: 106 | # trigData = data 107 | # else: 108 | # trigData = np.concatenate([self.lastData[:-15], data]) 109 | tind = np.argwhere((data[:-15] < ty) * (data[15:] >= ty)) 110 | if len(tind) > 0: 111 | tind = tind[min(2, tind.shape[0] - 1), 0] + 15 112 | self.wave.setPos(data.shape[0] - tind + tx, 0) 113 | 114 | # update persistent 115 | if len(self.wave.plots) > 1: 116 | for i in [1, 2]: 117 | d = self.wave.plots[-i].yData 118 | p = pg.PlotDataItem(d) 119 | self.persistent.append(p) 120 | self.plot.addItem(p) 121 | p.setPos(self.wave.plots[-i].pos() + self.wave.pos()) 122 | 123 | else: 124 | self.wave.setPos(data.shape[0], 0) 125 | 126 | # update spectrum 127 | spec = np.abs(np.fft.fft(data[-1024:])[:512]) 128 | x = np.linspace(0, self.rate / 2., len(spec)) 129 | self.specPlot.plot(x, spec, clear=True) 130 | self.lastData = data 131 | 132 | while len(self.persistent) > 30: # limit # of persistent plots 133 | p = self.persistent.pop(0) 134 | self.plot.removeItem(p) 135 | for p in self.persistent[:]: 136 | p.setOpacity(p.opacity() * (0.03**dt)) 137 | if p.opacity() < 0.01: 138 | self.persistent.remove(p) 139 | self.plot.removeItem(p) 140 | 141 | def keyPressEvent(self, ev): 142 | if ev.text() == ' ': 143 | if self.timer.isActive(): 144 | self.timer.stop() 145 | else: 146 | self.timer.start() 147 | else: 148 | print(ev.key()) 149 | 150 | 151 | class ScrollingPlot(QtGui.QGraphicsItem): 152 | """Simple appendable plot. 153 | 154 | New data is appended to the right and shifts existing data leftward.""" 155 | def __init__(self): 156 | QtGui.QGraphicsItem.__init__(self) 157 | self.setFlag(self.ItemHasNoContents) 158 | 159 | self.plots = [] 160 | self.lastValue = 0 161 | 162 | def append(self, data): 163 | # remove plots that are too old 164 | scene = self.scene() 165 | while len(self.plots) > 20: 166 | scene.removeItem(self.plots.pop(0)) 167 | 168 | # add the next plot, shift to its correct position 169 | p = pg.PlotDataItem(data) 170 | self.plots.append(p) 171 | p.setParentItem(self) 172 | 173 | shift = len(data) - 1 174 | for p in self.plots: 175 | p.moveBy(-shift, 0) 176 | 177 | def boundingRect(self): 178 | return QtCore.QRectF() 179 | 180 | 181 | if __name__ == '__main__': 182 | app = QtGui.QApplication([]) 183 | win = MainWindow() 184 | app.exec_() 185 | -------------------------------------------------------------------------------- /examples/freqmeasure.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 2 | # Example by Colin O'Flynn 3 | # 4 | import time 5 | import numpy as np 6 | from picoscope import ps5000a 7 | 8 | from matplotlib.mlab import find 9 | 10 | 11 | class freqMeasure(): 12 | def __init__(self): 13 | self.ps = ps5000a.PS5000a(connect=False) 14 | 15 | def openScope(self): 16 | self.ps.open() 17 | 18 | self.ps.setChannel("A", coupling="DC", VRange=5.0, probeAttenuation=10) 19 | self.ps.setChannel("B", enabled=False) 20 | self.ps.setChannel("C", enabled=False) 21 | self.ps.setChannel("D", enabled=False) 22 | res = self.ps.setSamplingFrequency(1000E6, 50000) 23 | self.sampleRate = res[0] 24 | print("Sampling @ %f MHz, %d samples" % (res[0]/1E6, res[1])) 25 | 26 | # Use external trigger to mark when we sample 27 | self.ps.setSimpleTrigger(trigSrc="External", threshold_V=0.150, 28 | timeout_ms=5000) 29 | 30 | def closeScope(self): 31 | self.ps.close() 32 | 33 | def armMeasure(self): 34 | self.ps.runBlock() 35 | 36 | def freq_from_crossings(self, sig): 37 | """Estimate frequency by counting zero crossings""" 38 | # From https://gist.github.com/endolith/255291: 39 | 40 | fs = self.sampleRate 41 | 42 | # Find all indices right before a rising-edge zero crossing 43 | indices = find((sig[1:] >= 0) & (sig[:-1] < 0)) 44 | # More accurate, using linear interpolation to find intersample 45 | # zero-crossings (Measures 1000.000129 Hz for 1000 Hz, for instance) 46 | crossings = [i - sig[i] / (sig[i+1] - sig[i]) for i in indices] 47 | # Some other interpolation based on neighboring points might be better. 48 | # Spline, cubic, whatever 49 | return fs / np.mean(np.diff(crossings)) 50 | 51 | def measure(self): 52 | print("Waiting for trigger") 53 | while not self.ps.isReady(): 54 | time.sleep(0.01) 55 | print("Sampling Done") 56 | data = self.ps.getDataV("A", 50000) 57 | 58 | data = data - np.mean(data) 59 | freq = self.freq_from_crossings(data) 60 | 61 | print(freq) 62 | 63 | 64 | if __name__ == "__main__": 65 | fm = freqMeasure() 66 | fm.openScope() 67 | 68 | try: 69 | while 1: 70 | fm.armMeasure() 71 | fm.measure() 72 | except KeyboardInterrupt: 73 | pass 74 | 75 | fm.closeScope() 76 | -------------------------------------------------------------------------------- /examples/garbageCollectorTest.py: -------------------------------------------------------------------------------- 1 | """ 2 | Test the garbage collection if we pass the picoscope object. 3 | 4 | By: Mark Harfouche 5 | 6 | """ 7 | 8 | from __future__ import division 9 | from __future__ import absolute_import 10 | from __future__ import print_function 11 | from __future__ import unicode_literals 12 | 13 | from picoscope import ps6000 14 | 15 | # import the garbage collection interface 16 | import gc 17 | 18 | if __name__ == "__main__": 19 | ps = ps6000.PS6000() 20 | 21 | print("Found the following picoscope:") 22 | print("Serial: " + ps.getUnitInfo("BatchAndSerial")) 23 | 24 | pd = ps 25 | 26 | print("Copied the picoscope object. " + 27 | " the information of the copied object is:") 28 | print("Serial: " + pd.getUnitInfo("BatchAndSerial")) 29 | 30 | print("\n\n\n") 31 | 32 | print("Using both objects") 33 | ps.setChannel('A') 34 | pd.setChannel('B') 35 | 36 | del ps 37 | 38 | print("Deleting the original object and collecting garbage.") 39 | 40 | gc.collect() 41 | print("Copied object still works:") 42 | print("Serial: " + pd.getUnitInfo("BatchAndSerial")) 43 | 44 | pd.close() 45 | print("Now I closed the other object.") 46 | -------------------------------------------------------------------------------- /examples/just_get_connected.py: -------------------------------------------------------------------------------- 1 | from ctypes import c_short, byref, cdll, c_char, create_string_buffer 2 | import time 3 | import platform 4 | 5 | if platform.system() == "Windows": 6 | from ctypes import windll 7 | 8 | lib = windll.LoadLibrary("ps3000a.dll") 9 | elif platform.system() == "Linux": 10 | lib = cdll.LoadLibrary("libps3000a.so") 11 | 12 | 13 | # Enumerating units 14 | print("Enumerating units...\n\n") 15 | 16 | count = c_short() 17 | serials = (c_char * 100)() 18 | serialLth = c_short() 19 | lib.ps3000aEnumerateUnits(byref(count), byref(serials), byref(serialLth)) 20 | 21 | print("count:" + str(count)) 22 | print("serial numbers: " + str(serials[0:100])) 23 | print("serial string length: " + str(serialLth)) 24 | 25 | # try connecting 26 | serial_len = serialLth.value 27 | serial_num = serials[0:serial_len] 28 | print("\n\nAttempting to connect to" + str(serial_num) + "\n\n") 29 | handle = c_short() 30 | s = create_string_buffer(serial_num) 31 | 32 | m = lib.ps3000aOpenUnit(byref(handle), byref(s)) 33 | print("Result code:" + str(m)) 34 | print("Scope handle:" + str(handle.value)) 35 | 36 | try: 37 | # flash led 38 | print("\n\nFlashing LED...\n\n") 39 | start = c_short(10) 40 | lib.ps3000aFlashLed(handle, start) 41 | time.sleep(5) 42 | except: # noqa 43 | print("led wouldn't flash") 44 | pass 45 | # close unit 46 | print("\n\nClosing scope...\n\n") 47 | m = lib.ps3000aCloseUnit(handle) 48 | if m == 0: 49 | print("Unit" + str(serial_num) + "shut down.") 50 | -------------------------------------------------------------------------------- /examples/openUnitAsyncDemo.py: -------------------------------------------------------------------------------- 1 | """ 2 | OpenUnitAsync Demo. 3 | 4 | By: Mark Harfouche 5 | 6 | Shows how to use the openUnitAsync functionality. 7 | On my computer, it takes 2.8 seconds to open the picoscope. 8 | Maybe you want to do something useful with it :D. 9 | """ 10 | 11 | from __future__ import division 12 | from __future__ import absolute_import 13 | from __future__ import print_function 14 | from __future__ import unicode_literals 15 | 16 | import time 17 | from picoscope import ps6000 18 | 19 | if __name__ == "__main__": 20 | print(__doc__) 21 | 22 | ps = ps6000.PS6000(connect=False) 23 | 24 | print("Attempting to open Picoscope 6000...") 25 | 26 | ps.openUnitAsync() 27 | 28 | t_start = time.time() 29 | while True: 30 | (progress, completed) = ps.openUnitProgress() 31 | print("T = %f, Progress = %d, Completed = %d" % 32 | (time.time() - t_start, progress, completed)) 33 | if completed == 1: 34 | break 35 | time.sleep(0.01) 36 | 37 | print("Completed opening the scope in %f seconds." % 38 | (time.time() - t_start)) 39 | 40 | ps.close() 41 | -------------------------------------------------------------------------------- /examples/ps2000_demo.py: -------------------------------------------------------------------------------- 1 | """ 2 | PS2000 Demo. 3 | 4 | By: Colin O'Flynn, based on Mark Harfouche's software 5 | 6 | This is a demo of how to use AWG with the Picoscope 2204 along with capture 7 | It was tested with the PS2204A USB2.0 version 8 | 9 | The AWG is connected to Channel A. 10 | Nothing else is required. 11 | 12 | NOTE: Must change line below to use with "A" and "B" series PS2000 models 13 | 14 | See http://www.picotech.com/document/pdf/ps2000pg.en-10.pdf for PS2000 models: 15 | PicoScope 2104 16 | PicoScope 2105 17 | PicoScope 2202 18 | PicoScope 2203 19 | PicoScope 2204 20 | PicoScope 2205 21 | PicoScope 2204A 22 | PicoScope 2205A 23 | 24 | See http://www.picotech.com/document/pdf/ps2000apg.en-6.pdf for PS2000A models: 25 | PicoScope 2205 MSO 26 | PicoScope 2206 27 | PicoScope 2206A 28 | PicoScope 2206B 29 | PicoScope 2207 30 | PicoScope 2207A 31 | PicoScope 2208 32 | PicoScope 2208A 33 | """ 34 | from __future__ import division 35 | from __future__ import absolute_import 36 | from __future__ import print_function 37 | from __future__ import unicode_literals 38 | 39 | import time 40 | from picoscope import ps2000 41 | # from picoscope import ps2000a 42 | import pylab as plt 43 | import numpy as np 44 | 45 | if __name__ == "__main__": 46 | print(__doc__) 47 | 48 | print("Attempting to open Picoscope 2000...") 49 | 50 | ps = ps2000.PS2000() 51 | # Uncomment this line to use with the 2000a/2000b series 52 | # ps = ps2000a.PS2000a() 53 | 54 | print("Found the following picoscope:") 55 | print(ps.getAllUnitInfo()) 56 | 57 | waveform_desired_duration = 50E-6 58 | obs_duration = 3 * waveform_desired_duration 59 | sampling_interval = obs_duration / 4096 60 | 61 | (actualSamplingInterval, nSamples, maxSamples) = \ 62 | ps.setSamplingInterval(sampling_interval, obs_duration) 63 | print("Sampling interval = %f ns" % (actualSamplingInterval * 1E9)) 64 | print("Taking samples = %d" % nSamples) 65 | print("Maximum samples = %d" % maxSamples) 66 | 67 | # the setChannel command will chose the next largest amplitude 68 | channelRange = ps.setChannel('A', 'DC', 2.0, 0.0, enabled=True, 69 | BWLimited=False) 70 | print("Chosen channel range = %d" % channelRange) 71 | 72 | ps.setSimpleTrigger('A', 1.0, 'Falling', timeout_ms=100, enabled=True) 73 | 74 | ps.setSigGenBuiltInSimple(offsetVoltage=0, pkToPk=1.2, waveType="Sine", 75 | frequency=50E3) 76 | 77 | ps.runBlock() 78 | ps.waitReady() 79 | print("Waiting for awg to settle.") 80 | time.sleep(2.0) 81 | ps.runBlock() 82 | ps.waitReady() 83 | print("Done waiting for trigger") 84 | dataA = ps.getDataV('A', nSamples, returnOverflow=False) 85 | 86 | dataTimeAxis = np.arange(nSamples) * actualSamplingInterval 87 | 88 | ps.stop() 89 | ps.close() 90 | 91 | # Uncomment following for call to .show() to not block 92 | # plt.ion() 93 | 94 | plt.figure() 95 | plt.hold(True) 96 | plt.plot(dataTimeAxis, dataA, label="Clock") 97 | plt.grid(True, which='major') 98 | plt.title("Picoscope 2000 waveforms") 99 | plt.ylabel("Voltage (V)") 100 | plt.xlabel("Time (ms)") 101 | plt.legend() 102 | plt.show() 103 | -------------------------------------------------------------------------------- /examples/radar_capture_functions.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | import time 3 | 4 | # collect 20% more samples than nominally necessary 5 | SAMPLE_SAFETY_MARGIN = 1.25 6 | LIGHTSPEED = 2.99e8 7 | 8 | 9 | class RadarScope(object): 10 | """Interface to picoscope, connected to radar""" 11 | 12 | def __init__(self, ps, rotation_period=2.4, pulse_rate=2100, 13 | range_resolution=10.0, max_range=1e3, video_chan="A", 14 | video_voltage=[-0.1, 2], trigger_voltage=0.4, 15 | heading_chan="B", heading_voltage=[1, -10], data_dir="."): 16 | ''' 17 | Set up interface to PicoScope for radar data collection. 18 | 19 | Parameters 20 | ---------- 21 | ps : ps3000a object 22 | rotation_period : float 23 | Nominal antenna rotation period. 24 | pulse_rate : int 25 | Nominal radar pulse rate. 26 | max_range : float 27 | range, in meters, to which samples will be recorderd 28 | range_resolution : float 29 | Nominal range resolution for radar data capturing. This is 30 | translated to the digital (time) sampling rate. The actual 31 | physical range resolution is limited by the radar pulse length. 32 | video_chan, heading_chan : String 33 | Oscilloscope channels (e.g. "A", "B") connected to the radar's 34 | video and heading pulse signals. 35 | video_voltage, heading_voltage : List 36 | Lists containing the minimum and maximum DC voltages needed 37 | to capture the input signals 38 | trigger_voltage : float 39 | Trigger data collection when video rises past this threshold 40 | ''' 41 | super(RadarScope, self).__init__() 42 | self.ps = ps 43 | self.rotation_period = rotation_period 44 | self.pulse_rate = pulse_rate 45 | self.max_range = max_range 46 | self.range_resolution = range_resolution 47 | self.video_chan = video_chan 48 | self.heading_chan = heading_chan 49 | self.video_voltage = video_voltage 50 | self.heading_voltage = heading_voltage 51 | self.n_captures = int(self.pulse_rate * rotation_period * 52 | SAMPLE_SAFETY_MARGIN) 53 | self.sample_interval = 2 * self.range_resolution / LIGHTSPEED 54 | self.capture_duration = self.max_range * 2 / LIGHTSPEED 55 | self.data_dir = data_dir 56 | 57 | # set up oscilloscope channels, trigger, and sampling rate 58 | self.ps.setChannel(channel=self.video_chan, coupling="DC", 59 | VRange=max([abs(x) for x in video_voltage])) 60 | self.ps.setChannel(channel=self.heading_chan, coupling="DC", 61 | VRange=max([abs(x) for x in heading_voltage])) 62 | self.ps.setSamplingInterval(self.sample_interval, 63 | self.capture_duration) 64 | 65 | self.ps.setSimpleTrigger("A", threshold_V=trigger_voltage) 66 | self.max_samples_per_segment = self.ps.memorySegments(self.n_captures) 67 | self.samples_per_segment = int(self.capture_duration / 68 | self.sample_interval) 69 | self.ps.setNoOfCaptures(self.n_captures) 70 | 71 | self.video_buffer = np.zeros((self.n_captures, 72 | self.samples_per_segment), 73 | dtype=np.int16) 74 | self.heading_buffer = np.zeros((self.n_captures, 75 | self.samples_per_segment), 76 | dtype=np.int16) 77 | 78 | def run_sweep(self): 79 | ''' 80 | Tells the PicoScope to run one rapid block of radar pulses 81 | according to the pre-specified settings. Does not transfer the data 82 | collected from the PicoScope's memory to the computer. 83 | ''' 84 | self.last_sweep_time = time.strftime("%Y%m%d_%H%M%S") 85 | self.ps.runBlock() 86 | 87 | def transfer_data(self, downsample_ratio=0, downsample_mode=0): 88 | ''' 89 | Transfers stored data from the PicoScope's buffers to the computer's 90 | memory. 91 | ''' 92 | t1 = time.time() 93 | ps.getDataRawBulk(channel=self.video_chan, data=self.video_buffer) 94 | ps.getDataRawBulk(channel=self.heading_chan, data=self.heading_buffer) 95 | print("Time to transfer data: " + str(time.time() - t1)) 96 | 97 | def capture_sweep(self, downsample_ratio=0, downsample_mode=0): 98 | t1 = time.time() 99 | self.run_sweep() 100 | self.ps.waitReady() 101 | print("Time to capture data: " + str(time.time() - t1)) 102 | self.transfer_data(downsample_ratio, downsample_mode) 103 | 104 | def zero_heading_indices(self, threshold=0): 105 | ''' 106 | Returns the indices of the data blocks where the heading signal 107 | indicates that the antenna was at the zero point. 108 | ''' 109 | pass 110 | 111 | def to_file(self, dir=None, echo=False): 112 | t1 = time.time() 113 | if dir is None: 114 | dir = self.data_dir 115 | filename = "sweep_" + self.last_sweep_time + ".swp" 116 | output_file = open(filename, "wb") 117 | output_file.write(self.video_buffer) 118 | output_file.write(self.heading_buffer) 119 | output_file.close() 120 | if echo: 121 | print(filename) 122 | print("Time to write data: " + str(time.time() - t1)) 123 | 124 | def record(self, n_sweeps=1, minimum_interval=5): 125 | ''' 126 | Capture n_sweeps sweeps, waiting no less than minimum_interval 127 | in between them. 128 | ''' 129 | i = 0 130 | t1 = minimum_interval + 1 131 | while i < n_sweeps: 132 | while time.time() - t1 < minimum_interval: 133 | time.sleep(0) 134 | t1 = time.time() 135 | self.capture_sweep() 136 | self.to_file(echo=True) 137 | i += 1 138 | 139 | def disconnect(self): 140 | ''' 141 | Closes the PicoScope API, releasing the oscilloscope. 142 | ''' 143 | self.ps.close() 144 | 145 | 146 | if __name__ == '__main__': 147 | import matplotlib.pyplot as plt 148 | # picoscope = reload(picoscope) 149 | from picoscope import ps3000a 150 | # ps3000a = reload(ps3000a) 151 | 152 | SERIAL_NUM = 'AR911/011\x00' 153 | ps = ps3000a.PS3000a(SERIAL_NUM) 154 | 155 | rscope = RadarScope(ps, max_range=1e3, range_resolution=5) 156 | 157 | rscope.capture_sweep() 158 | # rscope.to_file() 159 | # rscope.record(10, 5) 160 | 161 | plt.imshow(rscope.video_buffer, aspect="auto") 162 | plt.show() 163 | 164 | rscope.disconnect() 165 | -------------------------------------------------------------------------------- /examples/runBlock_callback.py: -------------------------------------------------------------------------------- 1 | from picoscope import ps4000a 2 | import matplotlib.pyplot as plt 3 | import time 4 | 5 | ps = ps4000a.PS4000a() 6 | 7 | 8 | def callbackFunction(handle, status, pParameter=None): 9 | """This function is executed once the block is ready.""" 10 | if status == 0: 11 | print("Block is ready and can be read.") 12 | data = ps.getDataV('A') 13 | 14 | print("data transferred") 15 | plt.plot(data) 16 | plt.show() 17 | else: 18 | print("An error occurred.") 19 | 20 | 21 | # Picoscope setup 22 | ps.setChannel(channel="A", coupling="DC", VRange=1) 23 | ps.setChannel(channel="B", enabled=False) 24 | ps.setChannel(channel="C", enabled=False) 25 | ps.setChannel(channel="D", enabled=False) 26 | 27 | sample_interval = 100e-9 # 100 ns 28 | sample_duration = 2e-3 # 1 ms 29 | ps.setSamplingInterval(sample_interval, sample_duration) 30 | ps.setSimpleTrigger("A", threshold_V=0.1, timeout_ms=1) 31 | 32 | # Run a block 33 | print("Run a block with a callback function") 34 | ps.runBlock(callback=callbackFunction) 35 | 36 | time.sleep(10) 37 | ps.close() 38 | print("end") 39 | -------------------------------------------------------------------------------- /examples/sigGetBuiltIndemo.py: -------------------------------------------------------------------------------- 1 | """ 2 | PS6000 Siggen Demo. 3 | 4 | By: Mark Harfouche 5 | 6 | This is a demo of how to use Siggen with the Picoscope 6000 7 | It was tested with the PS6403B USB2.0 version 8 | 9 | The system is very simple: 10 | 11 | The SigGen is connected to Channel A. No other setup is required 12 | 13 | Warning, the picoscope has a bug, that doesn't let you generate a 14 | waveform correctly for a while 15 | It means that you need to kick it in place 16 | 17 | See http://www.picotech.com/support/topic12969.html 18 | 19 | """ 20 | from __future__ import division 21 | 22 | import time 23 | from picoscope import ps6000 24 | import pylab as plt 25 | import numpy as np 26 | 27 | if __name__ == "__main__": 28 | print(__doc__) 29 | 30 | print("Attempting to open Picoscope 6000...") 31 | 32 | # see page 13 of the manual to understand how to work this beast 33 | ps = ps6000.PS6000() 34 | 35 | print(ps.getAllUnitInfo()) 36 | 37 | waveform_desired_duration = 1E-3 38 | obs_duration = 10 * waveform_desired_duration 39 | sampling_interval = obs_duration / 4096 40 | 41 | (actualSamplingInterval, nSamples, maxSamples) = ps.setSamplingInterval( 42 | sampling_interval, obs_duration) 43 | print("Sampling interval = %f ns" % (actualSamplingInterval * 1E9)) 44 | print("Taking samples = %d" % nSamples) 45 | print("Maximum samples = %d" % maxSamples) 46 | 47 | ps.setChannel('A', 'DC', 5.0, 0.0, True, False) 48 | ps.setSimpleTrigger('A', 0.0, 'Rising', delay=0, timeout_ms=100, 49 | enabled=True) 50 | 51 | ps.setSigGenBuiltInSimple(offsetVoltage=0, pkToPk=4, waveType="Square", 52 | frequency=1 / waveform_desired_duration * 10, 53 | shots=1, triggerType="Rising", 54 | triggerSource="None") 55 | 56 | # take the desired waveform 57 | # This measures all the channels that have been enabled 58 | 59 | ps.runBlock() 60 | ps.waitReady() 61 | print("Done waiting for trigger") 62 | time.sleep(10) 63 | ps.runBlock() 64 | ps.waitReady() 65 | 66 | dataA = ps.getDataV('A', nSamples, returnOverflow=False) 67 | 68 | ps.stop() 69 | ps.close() 70 | 71 | dataTimeAxis = np.arange(nSamples) * actualSamplingInterval 72 | 73 | plt.ion() 74 | 75 | plt.figure() 76 | plt.hold(True) 77 | plt.plot(dataTimeAxis, dataA, label="Waveform") 78 | plt.grid(True, which='major') 79 | plt.title("Picoscope 6000 waveforms") 80 | plt.ylabel("Voltage (V)") 81 | plt.xlabel("Time (ms)") 82 | plt.legend() 83 | plt.draw() 84 | -------------------------------------------------------------------------------- /examples/specgram_plot.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 2 | # 3 | # Colin O'Flynn, Copyright (C) 2013. All Rights Reserved. 4 | # 5 | import time 6 | import numpy as np 7 | from picoscope import ps6000 8 | 9 | import pylab as plt 10 | 11 | import scipy 12 | import scipy.fftpack 13 | 14 | 15 | def fft(signal, freq): 16 | FFT = abs(scipy.fft(signal)) 17 | FFTdb = 20*scipy.log10(FFT) 18 | freqs = scipy.fftpack.fftfreq(len(signal), 1/freq) 19 | 20 | FFTdb = FFTdb[2:len(freqs)/2] 21 | freqs = freqs[2:len(freqs)/2] 22 | 23 | return (freqs, FFTdb) 24 | 25 | 26 | def examplePS6000(): 27 | fig = plt.figure() # noqa 28 | plt.ion() 29 | plt.show() 30 | 31 | print("Attempting to open...") 32 | ps = ps6000.PS6000() 33 | 34 | # Example of simple capture 35 | res = ps.setSamplingFrequency(250E6, 4096) 36 | sampleRate = res[0] # noqa 37 | print("Sampling @ %f MHz, %d samples" % (res[0]/1E6, res[1])) 38 | ps.setChannel("A", "AC", 50E-3) 39 | 40 | blockdata = np.array(0) 41 | 42 | for i in range(0, 50): 43 | ps.runBlock() 44 | while not ps.isReady(): 45 | time.sleep(0.01) 46 | 47 | print("Sampling Done") 48 | data = ps.getDataV("A", 4096) 49 | blockdata = np.append(blockdata, data) 50 | 51 | # Simple FFT 52 | # print "FFT In Progress" 53 | # [freqs, FFTdb] = fft(data, res[0]) 54 | # plt.clf() 55 | # plt.plot(freqs, FFTdb) 56 | # plt.draw() 57 | 58 | start = (i - 5) * 4096 59 | if start < 0: 60 | start = 0 61 | # Spectrum Graph, keeps growing 62 | plt.clf() 63 | plt.specgram(blockdata[start:], NFFT=4096, Fs=res[0], noverlap=512) 64 | plt.xlabel('Measurement #') 65 | plt.ylabel('Frequency (Hz)') 66 | plt.draw() 67 | 68 | ps.close() 69 | 70 | 71 | if __name__ == "__main__": 72 | examplePS6000() 73 | -------------------------------------------------------------------------------- /examples/test_ps3000a.py: -------------------------------------------------------------------------------- 1 | import matplotlib.pyplot as plt 2 | import numpy as np 3 | import time 4 | 5 | from picoscope import ps3000a 6 | 7 | SERIAL_NUM = 'AR911/011\x00' 8 | ps = ps3000a.PS3000a(SERIAL_NUM) 9 | 10 | # now = time.strftime("%Y%m%d_%H%M%S") 11 | # filename = "sweep_" + now + ".swp" 12 | # output_file = open(filename, "wb") 13 | 14 | c = 3e8 15 | 16 | # rapid block mode 17 | 18 | ps.setChannel(channel="A", coupling="DC", VRange=1) 19 | 20 | n_captures = 2300 * 3 # int(600 * 1.4) 21 | sample_interval = 5 / 3e8 22 | sample_duration = 1e3 * 2 / 3e8 23 | 24 | ps.setSamplingInterval(sample_interval, sample_duration) 25 | ps.setSimpleTrigger("A", threshold_V=0.2) 26 | 27 | samples_per_segment = ps.memorySegments(n_captures) 28 | ps.setNoOfCaptures(n_captures) 29 | 30 | data = np.zeros((n_captures, samples_per_segment), dtype=np.int16) 31 | 32 | t1 = time.time() 33 | 34 | ps.runBlock() 35 | ps.waitReady() 36 | 37 | t2 = time.time() 38 | print("Time to get sweep: " + str(t2 - t1)) 39 | 40 | ps.getDataRawBulk(data=data) 41 | 42 | # for i in range(n_captures): 43 | # ps._lowLevelSetDataBuffer(ps.CHANNELS["A"], 44 | # data[i, :], 0, i) 45 | 46 | # # t2 = time.time() 47 | # nsamples = c_int32(ps.noSamples) 48 | # from_segment_index = 0 49 | # to_segment_index = n_captures - 1 50 | # downsample_ratio = 0 51 | # downsample_mode = 0 52 | # overflow = np.zeros(n_captures, dtype=np.int16) 53 | # overflow_ptr = overflow.ctypes.data_as(POINTER(c_int16)) 54 | 55 | # m = ps.lib.ps3000aGetValuesBulk(c_int16(ps.handle), 56 | # byref(nsamples), 57 | # c_int16(from_segment_index), 58 | # c_int16(to_segment_index), 59 | # c_int32(downsample_ratio), 60 | # c_int16(downsample_mode), 61 | # overflow_ptr) 62 | # print m 63 | 64 | # ps.checkResult(m) 65 | 66 | t3 = time.time() 67 | print("Time to read data: " + str(t3 - t2)) 68 | 69 | 70 | # output_file.write(data) 71 | # t4 = time.time() 72 | # print "Time to write data to disk: ", str(t4 - t3) 73 | # output_file.close() 74 | 75 | plt.imshow(data[:, 0:ps.noSamples], aspect='auto', interpolation='none', 76 | cmap=plt.cm.hot) 77 | plt.colorbar() 78 | plt.show() 79 | 80 | ps.close() 81 | -------------------------------------------------------------------------------- /examples/test_ps4000a.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | Testing the ps4000a Series. 4 | """ 5 | 6 | # Imports 7 | import matplotlib.pyplot as plt 8 | import numpy as np 9 | from picoscope import ps4000a 10 | import time 11 | 12 | 13 | def test_general_unit_calls(picoscope): 14 | """Test general unit calls.""" 15 | picoscope.flashLed() 16 | print(picoscope.getAllUnitInfo()) 17 | assert not picoscope.ping(), "Ping failed." 18 | print("General unit calls test passed.") 19 | 20 | 21 | def test_timebase(ps): 22 | """Test the timebase methods.""" 23 | # Preparation 24 | ps.memorySegments(1) 25 | # Tests 26 | if ps.model == "4444": 27 | data = ((20e-9, 3), 28 | (40e-9, 4)) 29 | else: 30 | data = ((25e-9, 1), 31 | (100e-9, 7)) 32 | for (t, timebase) in data: 33 | text = f"time {t} does not fit timebase {timebase}." 34 | assert ps.getTimeBaseNum(t) == timebase, "timebasenum: " + text 35 | assert ps.getTimestepFromTimebase(timebase) == t, "Timestep " + text 36 | timestep, _ = ps._lowLevelGetTimebase(timebase, 10, None, 0) 37 | assert timestep == t, f"lowLevel: {timestep} != {t}" 38 | print("Timebase test passed.") 39 | 40 | 41 | def test_deviceResolution(ps): 42 | """Test setting/getting device resolution.""" 43 | if ps.model == "4444": 44 | ps.setResolution("12") 45 | assert ps.resolution == "12", "Resolution was not set." 46 | # assert ps.getResolution() == "12" not implemented yet 47 | print("Device resolution test passed.") 48 | else: 49 | print("Model does not support resolution.") 50 | 51 | 52 | def test_rapid_block_mode(ps, 53 | n_captures=100, 54 | sample_interval=100e-9, # 100 ns 55 | sample_duration=2e-3, # 1 ms 56 | ): 57 | """Test the rapid block mode.""" 58 | # Configuration of Picoscope 59 | ps.setChannel(channel="A", coupling="DC", VRange=1) 60 | ps.setChannel(channel="B", enabled=False) 61 | 62 | ps.setSamplingInterval(sample_interval, sample_duration) 63 | ps.setSimpleTrigger("A", threshold_V=0.1, timeout_ms=1) 64 | 65 | samples_per_segment = ps.memorySegments(n_captures) 66 | ps.setNoOfCaptures(n_captures) 67 | 68 | data = np.zeros((n_captures, samples_per_segment), dtype=np.int16) 69 | 70 | # Measurement 71 | t1 = time.time() 72 | 73 | ps.runBlock() 74 | ps.waitReady() 75 | 76 | t2 = time.time() 77 | print("Time to record data to scope: ", str(t2 - t1)) 78 | 79 | ps.getDataRawBulk(data=data) 80 | 81 | t3 = time.time() 82 | print("Time to copy to RAM: ", str(t3 - t2)) 83 | 84 | plt.imshow(data[:, 0:ps.noSamples], aspect='auto', interpolation='none', 85 | cmap=plt.cm.hot) 86 | plt.colorbar() 87 | plt.show() 88 | print("Rapid block mode test passed.") 89 | 90 | 91 | def data_ready(handle, status, noOfSamples, overflow, pParameter=None): 92 | """Show the asynchronously received data.""" 93 | if status == 0: 94 | print(f"{noOfSamples} samples received with overflow: {overflow}") 95 | plt.plot(data) 96 | plt.show() 97 | print("Data reading asynchronously test passed.") 98 | else: 99 | print(f"Data receiving error {status}.") 100 | 101 | 102 | def test_read_async(handle=None, status=0, pParameter=None): 103 | """Test reading data asynchronously.""" 104 | if status == 0: 105 | print("Block is ready and can be read.") 106 | channel, numSamples = config 107 | global data 108 | data = np.empty(numSamples, dtype=np.int16) 109 | if not isinstance(channel, int): 110 | channel = ps.CHANNELS[channel] 111 | ps._lowLevelSetDataBuffer(channel, data, 0, 0) 112 | ps._lowLevelGetValuesAsync(numSamples, 0, 1, 0, 0, data_ready, None) 113 | print("Get values async started.") 114 | else: 115 | print("Data is not ready. RunBlock had an error.") 116 | 117 | 118 | def test_runBlock_async(picoscope, channel="A", sample_interval=100e-9, 119 | sample_duration=2e-3): 120 | """Test running a block asynchronously.""" 121 | # Configuration of Picoscope 122 | global ps 123 | ps = picoscope 124 | ps.setChannel(channel=channel, coupling="DC", VRange=1) 125 | ps.memorySegments(1) 126 | ps.setNoOfCaptures(1) 127 | i, samples, m = ps.setSamplingInterval(sample_interval, sample_duration) 128 | ps.setSimpleTrigger("A", threshold_V=0.1, timeout_ms=1) 129 | 130 | global config 131 | config = channel, samples 132 | # Run the block 133 | ps.runBlock(callback=test_read_async) 134 | print("Run Block started, waiting 2 s.") 135 | time.sleep(2) 136 | print("Run block finished") 137 | 138 | 139 | if __name__ == "__main__": 140 | """Run all the tests.""" 141 | # Initialize the picoscope 142 | ps = ps4000a.PS4000a() 143 | 144 | try: 145 | # Run tests. 146 | test_general_unit_calls(ps) 147 | test_deviceResolution(ps) 148 | test_rapid_block_mode(ps) 149 | test_runBlock_async(ps) 150 | finally: 151 | # Close the connection 152 | ps.close() 153 | print("All tests passed.") 154 | -------------------------------------------------------------------------------- /examples/test_ps5000a.py: -------------------------------------------------------------------------------- 1 | from picoscope import ps5000a 2 | import matplotlib.pyplot as plt 3 | import numpy as np 4 | import time 5 | 6 | ps = ps5000a.PS5000a() 7 | 8 | # rapid block mode 9 | 10 | ps.setChannel(channel="A", coupling="DC", VRange=1) 11 | ps.setChannel(channel="B", enabled=False) 12 | 13 | n_captures = 100 14 | sample_interval = 100e-9 # 100 ns 15 | sample_duration = 2e-3 # 1 ms 16 | ps.setResolution('16') 17 | ps.setSamplingInterval(sample_interval, sample_duration) 18 | ps.setSimpleTrigger("A", threshold_V=0.1, timeout_ms=1) 19 | 20 | samples_per_segment = ps.memorySegments(n_captures) 21 | ps.setNoOfCaptures(n_captures) 22 | 23 | data = np.zeros((n_captures, samples_per_segment), dtype=np.int16) 24 | 25 | t1 = time.time() 26 | 27 | ps.runBlock() 28 | ps.waitReady() 29 | 30 | t2 = time.time() 31 | print("Time to record data to scope: ", str(t2 - t1)) 32 | 33 | ps.getDataRawBulk(data=data) 34 | 35 | t3 = time.time() 36 | print("Time to copy to RAM: ", str(t3 - t2)) 37 | 38 | plt.imshow(data[:, 0:ps.noSamples], aspect='auto', interpolation='none', 39 | cmap=plt.cm.hot) 40 | plt.colorbar() 41 | plt.show() 42 | 43 | ps.close() 44 | -------------------------------------------------------------------------------- /examples/test_ps6000a.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | Testing the ps6000a series software-device interaction. 4 | ====================================================== 5 | 6 | This file provides tests in order to verify that the software does with the 7 | oscilloscope, what it is supposed to do. Additionally the tests serve as 8 | examples on how to do certain tasks. 9 | 10 | Usage 11 | ----- 12 | 13 | - Run this file in order to execute all software tests on your device. 14 | - Import this file and execute the tests you want with your already opened 15 | oscilloscope device. 16 | 17 | Created on Tue Jan 25 12:20:14 2022 by Benedikt Moneke 18 | """ 19 | 20 | # Imports 21 | import matplotlib.pyplot as plt 22 | import numpy as np 23 | from picoscope import ps6000a 24 | import time 25 | 26 | 27 | def test_general_unit_calls(ps): 28 | """Test general unit calls""" 29 | ps.flashLed() 30 | print(ps.getAllUnitInfo()) 31 | assert not ps.ping(), "Ping failed." 32 | print("General unit calls test passed.") 33 | 34 | 35 | def test_timebase(ps): 36 | """Test the timebase methods.""" 37 | # Preparation 38 | ps.memorySegments(1) 39 | # Tests 40 | data = ((200e-12, 0), 41 | (800e-12, 2), 42 | (3.2e-9, 4), 43 | (6.4e-9, 5), 44 | (10e-9, 5), 45 | (15e-9, 6)) 46 | for (t, timebase) in data: 47 | text = "Time {} does not give timebase {}".format(t, timebase) 48 | assert ps.getTimeBaseNum(t) == timebase, text 49 | data = ( 50 | (800e-12, 2), 51 | (3.2e-9, 4), 52 | (6.4e-9, 5), 53 | (12.8e-9, 6), 54 | (19.2e-9, 7), 55 | (3.84e-8, 10), 56 | (6.144e-7, 100), 57 | ) 58 | for (t, timebase) in data: 59 | text = "{} s does not fit timebase {}.".format(t, timebase) 60 | assert ps.getTimestepFromTimebase(timebase) == t, "Timestep: " + text 61 | try: 62 | timestep, _ = ps._lowLevelGetTimebase(timebase, 10, None, 0) 63 | except Exception: 64 | print( 65 | "getTimebase failed at time {}, timebase {}.".format(t, 66 | timebase)) 67 | raise 68 | assert timestep == t, "LowLevel: {} != {}".format(timestep, t) 69 | print("Timebase test passed.") 70 | 71 | 72 | def test_deviceResolution(ps): 73 | """Test setting/getting device resolution, including ADC limits.""" 74 | ps.setResolution("12") 75 | assert ps.resolution == 1, "Picoscope variable was not set." 76 | assert ps.getResolution() == "12", "Picoscope resolution is wrong." 77 | assert ps.MIN_VALUE == -32736, "Minimum adc value is wrong." 78 | assert ps.MAX_VALUE == 32736, "Maximum adc value is wrong." 79 | print("Device resolution test passed.") 80 | 81 | 82 | def test_rapid_block_mode(ps, 83 | n_captures=100, 84 | sample_interval=100e-9, # 100 ns 85 | sample_duration=2e-3, # 1 ms 86 | ): 87 | """Test the rapid block mode.""" 88 | # Configuration of Picoscope 89 | ps.setChannel(channel="A", coupling="DC", VRange=1) 90 | ps.setChannel(channel="B", enabled=False) 91 | ps.setChannel(channel="C", enabled=False) 92 | ps.setChannel(channel="D", enabled=False) 93 | 94 | ps.setResolution('12') 95 | ps.setSamplingInterval(sample_interval, sample_duration) 96 | ps.setSimpleTrigger("A", threshold_V=0.1, timeout_ms=1) 97 | 98 | samples_per_segment = ps.memorySegments(n_captures) 99 | ps.setNoOfCaptures(n_captures) 100 | 101 | data = np.zeros((n_captures, samples_per_segment), dtype=np.int16) 102 | 103 | # Measurement 104 | t1 = time.time() 105 | 106 | ps.runBlock() 107 | ps.waitReady() 108 | 109 | t2 = time.time() 110 | print("Time to record data to scope: ", str(t2 - t1)) 111 | 112 | # downSampleMode raw (no downsampling) is 0x80000000. 0 is invalid! 113 | ps.getDataRawBulk(data=data, downSampleMode=0x80000000) 114 | 115 | t3 = time.time() 116 | print("Time to copy to RAM: ", str(t3 - t2)) 117 | 118 | plt.imshow(data[:, 0:ps.noSamples], aspect='auto', interpolation='none', 119 | cmap=plt.cm.hot) 120 | plt.colorbar() 121 | plt.title("rapid block mode") 122 | plt.show() 123 | print("Rapid block mode test passed.") 124 | 125 | 126 | class Handler: 127 | 128 | def printing(self, text): 129 | print(text) 130 | 131 | def data_ready(self, handle, status, noOfSamples, overflow, 132 | pParameter=None): 133 | """Show the asynchronously received data.""" 134 | if status == 0: 135 | self.printing("{} samples received with overflow: {}".format( 136 | noOfSamples, overflow)) 137 | plt.plot(self.data) 138 | plt.title("async") 139 | plt.show() 140 | self.ps._lowLevelClearDataBuffer(self.config[0], 0, 141 | downSampleMode=0x80000000) 142 | self.printing("Data reading asynchronously test passed.") 143 | else: 144 | self.printing("Data receiving error {}.".format(status)) 145 | 146 | def test_read_async(self, handle=None, status=0, pParameter=None): 147 | """ 148 | Test reading data asynchronously. 149 | 150 | If you call it manually instead of using it as a callback, use 151 | pParameter for handing over the picoscope instance. 152 | """ 153 | if pParameter is not None: 154 | ps = pParameter 155 | else: 156 | ps = self.ps 157 | if status == 0: 158 | self.printing("Block is ready and can be read.") 159 | # config is a global variable written by the caller. 160 | channel, numSamples = self.config 161 | # Define data for data_ready. 162 | self.data = np.zeros(ps.noSamples, dtype=np.int16) 163 | if not isinstance(channel, int): 164 | channel = ps.CHANNELS[channel] 165 | self.config = channel, numSamples 166 | ps._lowLevelClearDataBufferAll(channel, 0) 167 | ps._lowLevelSetDataBuffer(channel, self.data, 168 | downSampleMode=0x80000000, 169 | segmentIndex=0) 170 | ps._lowLevelGetValuesAsync(ps.noSamples, 0, 1, 0x80000000, 0, 171 | self.data_ready, None) 172 | self.printing("Get values async started.") 173 | else: 174 | self.printing("Data is not ready. RunBlock had an error.") 175 | 176 | 177 | def test_runBlock_async(ps, channel="A", sample_interval=100e-9, 178 | sample_duration=2e-3): 179 | """Test running a block asynchronously.""" 180 | # Define a handler to exchange data 181 | global handler 182 | handler = Handler() 183 | handler.ps = ps 184 | # Configuration of Picoscope 185 | ps.setChannel(channel=channel, coupling="DC", VRange=1) 186 | ps.memorySegments(1) 187 | ps.setNoOfCaptures(1) 188 | ps.setResolution('12') 189 | interval, samples, maxSamples = ps.setSamplingInterval(sample_interval, 190 | sample_duration) 191 | ps.setSimpleTrigger("A", threshold_V=0.1, timeout_ms=1) 192 | 193 | handler.config = channel, samples 194 | # Run the block 195 | ps.runBlock(callback=handler.test_read_async) 196 | print("Run block started, waiting 1 s.") 197 | time.sleep(1) 198 | print("Run block finished.") 199 | 200 | 201 | def test_downsampling(ps, 202 | sample_interval=100e-9, # 100 ns 203 | sample_duration=2e-3, # 1 ms 204 | ): 205 | """Test for different downsampling methods.""" 206 | ps._lowLevelClearDataBufferAll() 207 | ps.setChannel(channel="A", coupling="DC", VRange=1) 208 | ps.setChannel(channel="B", enabled=False) 209 | ps.setChannel(channel="C", enabled=False) 210 | ps.setChannel(channel="D", enabled=False) 211 | 212 | ps.setResolution('12') 213 | ps.memorySegments(1) 214 | ps.setNoOfCaptures(1) 215 | interval, samples, maxSamples = ps.setSamplingInterval(sample_interval, 216 | sample_duration) 217 | ps.setSimpleTrigger("A", threshold_V=0.1, timeout_ms=1) 218 | 219 | ps.runBlock() 220 | ps.waitReady() 221 | 222 | data0 = np.zeros(ps.noSamples, dtype=np.int16) 223 | data1 = np.zeros(ps.noSamples, dtype=np.int16) 224 | data2 = np.zeros(ps.noSamples, dtype=np.int16) 225 | 226 | # downSampleMode raw (no downsampling) is 0x80000000. 0 is invalid! 227 | ps.getDataRaw(data=data0, downSampleMode=0x80000000) 228 | ps.getDataRaw(data=data1, downSampleMode=ps.RATIO_MODE['decimate'], 229 | downSampleRatio=10) 230 | ps.getDataRaw(data=data2, downSampleMode=ps.RATIO_MODE['average'], 231 | downSampleRatio=10) 232 | 233 | samplesReduced = len(data0) // 10 234 | plt.plot(data0, label="raw") 235 | plt.plot(range(0, 10 * samplesReduced, 10)[:samplesReduced], 236 | data1[:samplesReduced], label="decimate") 237 | plt.plot(range(0, 10 * samplesReduced, 10)[:samplesReduced], 238 | data2[:samplesReduced], label="average") 239 | plt.title("downsampling") 240 | plt.legend() 241 | plt.show() 242 | print("Downsampling test passed.") 243 | 244 | 245 | if __name__ == "__main__": 246 | """Run all the tests.""" 247 | # Initialize the picoscope 248 | ps = ps6000a.PS6000a() 249 | 250 | try: 251 | # Run tests. 252 | test_general_unit_calls(ps) 253 | test_timebase(ps) 254 | test_deviceResolution(ps) 255 | test_rapid_block_mode(ps) 256 | test_downsampling(ps) 257 | test_runBlock_async(ps) 258 | finally: 259 | # Close the connection 260 | ps.close() 261 | print("All tests passed.") 262 | -------------------------------------------------------------------------------- /makedocs.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | REM Set following to python version you want to use 3 | REM Otherwise can comment out... 4 | set PYTHONBIN=c:\Python27-32bit 5 | %PYTHONBIN%\python.exe %PYTHONBIN%\Lib\pydoc.py -w picoscope 6 | %PYTHONBIN%\python.exe %PYTHONBIN%\Lib\pydoc.py -w picoscope.picobase 7 | %PYTHONBIN%\python.exe %PYTHONBIN%\Lib\pydoc.py -w picoscope.ps2000 8 | %PYTHONBIN%\python.exe %PYTHONBIN%\Lib\pydoc.py -w picoscope.ps2000a 9 | %PYTHONBIN%\python.exe %PYTHONBIN%\Lib\pydoc.py -w picoscope.ps3000 10 | %PYTHONBIN%\python.exe %PYTHONBIN%\Lib\pydoc.py -w picoscope.ps3000a 11 | %PYTHONBIN%\python.exe %PYTHONBIN%\Lib\pydoc.py -w picoscope.ps4000 12 | %PYTHONBIN%\python.exe %PYTHONBIN%\Lib\pydoc.py -w picoscope.ps5000a 13 | %PYTHONBIN%\python.exe %PYTHONBIN%\Lib\pydoc.py -w picoscope.ps6000 14 | move *.html doc\. 15 | -------------------------------------------------------------------------------- /makedocsLinux: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | echo "Creating html files..." 3 | pydoc3 -w picoscope 4 | pydoc3 -w picoscope.picobase 5 | pydoc3 -w picoscope.ps2000 6 | pydoc3 -w picoscope.ps2000a 7 | pydoc3 -w picoscope.ps3000 8 | pydoc3 -w picoscope.ps3000a 9 | pydoc3 -w picoscope.ps4000 10 | pydoc3 -w picoscope.ps5000a 11 | pydoc3 -w picoscope.ps6000 12 | echo "Moving html files to doc/" 13 | mv *.html doc 14 | echo "Done!" 15 | -------------------------------------------------------------------------------- /picoscope/__init__.py: -------------------------------------------------------------------------------- 1 | __author__ = "Colin O'Flynn, Mark Harfouche" 2 | __license__ = "FreeBSD" 3 | 4 | __all__ = ["ps2000", 5 | "ps2000a", 6 | "ps3000", 7 | "ps3000a", 8 | "ps4000", 9 | "ps4000a", 10 | "ps5000a", 11 | "ps6000"] 12 | 13 | from . import _version 14 | __version__ = _version.get_versions()['version'] 15 | -------------------------------------------------------------------------------- /picoscope/darwin_utils.py: -------------------------------------------------------------------------------- 1 | from ctypes import cdll 2 | from pathlib import Path 3 | import shutil 4 | import subprocess 5 | import tempfile 6 | 7 | import sys 8 | 9 | 10 | def LoadLibraryDarwin(library): 11 | """Wrapper around cdll.LoadLibrary that works around how SIP breaks 12 | dynamically linked libraries. This improves upon the process described 13 | here: 14 | 15 | http://ulthiel.com/vk2utl/picoscope-python-interface-under-mac-os-x/ 16 | """ 17 | PICO_LIB_PATH = "/Applications/PicoScope6.app/Contents/Resources/lib/" 18 | 19 | # Libraries that depend on libiomp5.dylib 20 | IOMP5_DEPS = ["libpicoipp.dylib", "libpicoipp.1.dylib"] 21 | 22 | try: 23 | # Get the library normally. This should work if SIP is disabled and 24 | # DYLD_LIBRARY_PATH is set properly. 25 | return cdll.LoadLibrary(library) 26 | except OSError: 27 | # 2.7 Fix. This only fixes flake8 28 | if sys.version_info[0] == 2: 29 | FileNotFoundError = IOError 30 | if not Path(PICO_LIB_PATH).is_dir(): 31 | raise FileNotFoundError( 32 | "/Applications/PicoScope6.app is missing") 33 | 34 | # Modifying the libraries in-place breaks their signatures, causing 35 | # PicoScope6.app to fail to detect the oscilloscope. Instead, patch 36 | # copies of the libraries in a temporary directory. 37 | tempLibDir = tempfile.TemporaryDirectory() 38 | patchedPicoPath = tempLibDir.name + "/lib/" 39 | shutil.copytree(PICO_LIB_PATH, patchedPicoPath) 40 | 41 | # Patch libraries that depend on libiomp5.dylib to refer to it by an 42 | # absolute path instead of a relative path, which is not allowed by 43 | # SIP. 44 | for libraryToPatch in IOMP5_DEPS: 45 | subprocess.run(["install_name_tool", "-change", "libiomp5.dylib", 46 | patchedPicoPath + "/libiomp5.dylib", 47 | patchedPicoPath + "/" + libraryToPatch 48 | ]).check_returncode() 49 | 50 | # Finally, patch the originally requested library to look in the 51 | # patched directory. 52 | patchedLibrary = patchedPicoPath + "/" + library 53 | subprocess.run(["install_name_tool", "-add_rpath", patchedPicoPath, 54 | patchedLibrary]).check_returncode() 55 | loadedLibrary = cdll.LoadLibrary(patchedLibrary) 56 | 57 | # Sneak the directory into the library so it's not deleted until the 58 | # library is garbage collected. 59 | loadedLibrary.tempLibDir = tempLibDir 60 | return loadedLibrary 61 | -------------------------------------------------------------------------------- /picoscope/error_codes.py: -------------------------------------------------------------------------------- 1 | """Error codes - copied from the PS6000 programmer's manual.""" 2 | 3 | # To get formatting correct do following copy-replace in 4 | # Programmers Notepad 5 | # 1. Copy/replace ' - ' with '", "' 6 | # 2. Copy/replace '\r' with '"],\r' (enable slash expressions when doing) 7 | # 3. Copy/replace '^([0-9A-F]{2} ){1}' with '0x\1, "' (w/ regex) 8 | # 4. Copy/replace '^([0-9A-F]{3} ){1}' with '0x\1, "' (w/ regex) 9 | # 5. Copy/repplace '0x' with '[0x' 10 | ERROR_CODES = [ 11 | [0x00, "PICO_OK", "The PicoScope XXXX is functioning correctly."], 12 | [0x01, "PICO_MAX_UNITS_OPENED", 13 | "An attempt has been made to open more than PSXXXX_MAX_UNITS."], 14 | [0x02, "PICO_MEMORY_FAIL", 15 | "Not enough memory could be allocated on the host machine."], 16 | [0x03, "PICO_NOT_FOUND", "No PicoScope XXXX could be found."], 17 | [0x04, "PICO_FW_FAIL", "Unable to download firmware."], 18 | [0x05, "PICO_OPEN_OPERATION_IN_PROGRESS"], 19 | [0x06, "PICO_OPERATION_FAILED"], 20 | [0x07, "PICO_NOT_RESPONDING", 21 | "The PicoScope XXXX is not responding to commands from the PC."], 22 | [0x08, "PICO_CONFIG_FAIL", 23 | "The configuration information in the PicoScope XXXX has become " + 24 | "corrupt or is missing."], 25 | [0x09, "PICO_KERNEL_DRIVER_TOO_OLD", 26 | "The picopp.sys file is too old to be used with the device driver."], 27 | [0x0A, "PICO_EEPROM_CORRUPT", 28 | "The EEPROM has become corrupt, so the device will use a default " + 29 | "setting."], 30 | [0x0B, "PICO_OS_NOT_SUPPORTED", 31 | "The operating system on the PC is not supported by this driver."], 32 | [0x0C, "PICO_INVALID_HANDLE", 33 | "There is no device with the handle value passed."], 34 | [0x0D, "PICO_INVALID_PARAMETER", "A parameter value is not valid."], 35 | [0x0E, "PICO_INVALID_TIMEBASE", 36 | "The timebase is not supported or is invalid."], 37 | [0x0F, "PICO_INVALID_VOLTAGE_RANGE", 38 | "The voltage range is not supported or is invalid."], 39 | [0x10, "PICO_INVALID_CHANNEL", 40 | "The channel number is not valid on this device or no channels have " + 41 | "been set."], 42 | [0x11, "PICO_INVALID_TRIGGER_CHANNEL", 43 | "The channel set for a trigger is not available on this device."], 44 | [0x12, "PICO_INVALID_CONDITION_CHANNEL", 45 | "The channel set for a condition is not available on this device."], 46 | [0x13, "PICO_NO_SIGNAL_GENERATOR", 47 | "The device does not have a signal generator."], 48 | [0x14, "PICO_STREAMING_FAILED", 49 | "Streaming has failed to start or has stopped without user request."], 50 | [0x15, "PICO_BLOCK_MODE_FAILED", 51 | "Block failed to start", "a parameter may have been set wrongly."], 52 | [0x16, "PICO_NULL_PARAMETER", "A parameter that was required is NULL."], 53 | [0x18, "PICO_DATA_NOT_AVAILABLE", 54 | "No data is available from a run block call."], 55 | [0x19, "PICO_STRING_BUFFER_TOO_SMALL", 56 | "The buffer passed for the information was too small."], 57 | [0x1A, "PICO_ETS_NOT_SUPPORTED", "ETS is not supported on this device."], 58 | [0x1B, "PICO_AUTO_TRIGGER_TIME_TOO_SHORT", 59 | "The auto trigger time is less than the time it will take to collect " + 60 | "the pre-trigger data."], 61 | [0x1C, "PICO_BUFFER_STALL", 62 | "The collection of data has stalled as unread data would be " + 63 | "overwritten."], 64 | [0x1D, "PICO_TOO_MANY_SAMPLES", 65 | "Number of samples requested is more than available in the current " + 66 | "memory segment."], 67 | [0x1E, "PICO_TOO_MANY_SEGMENTS", 68 | "Not possible to create number of segments requested."], 69 | [0x1F, "PICO_PULSE_WIDTH_QUALIFIER", 70 | "A null pointer has been passed in the trigger function or one of the " + 71 | "parameters is out of range."], 72 | [0x20, "PICO_DELAY", 73 | "One or more of the hold-off parameters are out of range."], 74 | [0x21, "PICO_SOURCE_DETAILS", 75 | "One or more of the source details are incorrect."], 76 | [0x22, "PICO_CONDITIONS", "One or more of the conditions are incorrect."], 77 | [0x23, "PICO_USER_CALLBACK", 78 | "The driver's thread is currently in the psXXXXBlockReady callback " + 79 | "function and therefore the action cannot be carried out."], 80 | [0x24, "PICO_DEVICE_SAMPLING", 81 | "An attempt is being made to get stored data while streaming. " + 82 | "Either stop streaming by calling psXXXXStop, or use " + 83 | "psXXXXGetStreamingLatestValues."], 84 | [0x25, "PICO_NO_SAMPLES_AVAILABLE", 85 | "because a run has not been completed."], 86 | [0x26, "PICO_SEGMENT_OUT_OF_RANGE", 87 | "The memory index is out of range."], 88 | [0x27, "PICO_BUSY", "Data cannot be returned yet."], 89 | [0x28, "PICO_STARTINDEX_INVALID", 90 | "The start time to get stored data is out of range."], 91 | [0x29, "PICO_INVALID_INFO", 92 | "The information number requested is not a valid number."], 93 | [0x2A, "PICO_INFO_UNAVAILABLE", 94 | "The handle is invalid so no information is available about the device." + 95 | " Only PICO_DRIVER_VERSION is available."], 96 | [0x2B, "PICO_INVALID_SAMPLE_INTERVAL", 97 | "The sample interval selected for streaming is out of range."], 98 | [0x2D, "PICO_MEMORY", "Driver cannot allocate memory."], 99 | [0x2E, "PICO_SIG_GEN_PARAM", 100 | "Incorrect parameter passed to signal generator."], 101 | [0x34, "PICO_WARNING_AUX_OUTPUT_CONFLICT", 102 | "AUX cannot be used as input and output at the same time."], 103 | [0x35, "PICO_SIGGEN_OUTPUT_OVER_VOLTAGE", 104 | "The combined peak to peak voltage and the analog offset voltage " + 105 | "exceed the allowable voltage the signal generator can produce."], 106 | [0x36, "PICO_DELAY_NULL", "NULL pointer passed as delay parameter."], 107 | [0x37, "PICO_INVALID_BUFFER", 108 | "The buffers for overview data have not been set while streaming."], 109 | [0x38, "PICO_SIGGEN_OFFSET_VOLTAGE", 110 | "The analog offset voltage is out of range."], 111 | [0x39, "PICO_SIGGEN_PK_TO_PK", 112 | "The analog peak to peak voltage is out of range."], 113 | [0x3A, "PICO_CANCELLED", "A block collection has been cancelled."], 114 | [0x3B, "PICO_SEGMENT_NOT_USED", 115 | "The segment index is not currently being used."], 116 | [0x3C, "PICO_INVALID_CALL", 117 | "The wrong GetValues function has been called for the collection mode " + 118 | "in use."], 119 | [0x3F, "PICO_NOT_USED", "The function is not available."], 120 | [0x40, "PICO_INVALID_SAMPLERATIO", 121 | "The aggregation ratio requested is out of range."], 122 | [0x41, "PICO_INVALID_STATE", 123 | "Device is in an invalid state."], 124 | [0x42, "PICO_NOT_ENOUGH_SEGMENTS", 125 | "The number of segments allocated is fewer than the number of captures " + 126 | "requested."], 127 | [0x43, "PICO_DRIVER_FUNCTION", 128 | "You called a driver function while another driver function was still " + 129 | "being processed."], 130 | [0x45, "PICO_INVALID_COUPLING", 131 | "An invalid coupling type was specified in psXXXXSetChannel."], 132 | [0x46, "PICO_BUFFERS_NOT_SET", 133 | "An attempt was made to get data before a data buffer was defined."], 134 | [0x47, "PICO_RATIO_MODE_NOT_SUPPORTED", 135 | "The selected downsampling mode (used for data reduction) is not " + 136 | "allowed."], 137 | [0x49, "PICO_INVALID_TRIGGER_PROPERTY", 138 | "An invalid parameter was passed to psXXXXSetTriggerChannelProperties."], 139 | [0x4A, "PICO_INTERFACE_NOT_CONNECTED", 140 | "The driver was unable to contact the oscilloscope."], 141 | [0x4D, "PICO_SIGGEN_WAVEFORM_SETUP_FAILED", 142 | "A problem occurred in psXXXXSetSigGenBuiltIn or " + 143 | "psXXXXSetSigGenArbitrary."], 144 | [0x4E, "PICO_FPGA_FAIL"], 145 | [0x4F, "PICO_POWER_MANAGER"], 146 | [0x50, "PICO_INVALID_ANALOGUE_OFFSET", 147 | "An impossible analogue offset value was specified in psXXXXSetChannel."], 148 | [0x51, "PICO_PLL_LOCK_FAILED", 149 | "Unable to configure the PicoScope XXXX."], 150 | [0x52, "PICO_ANALOG_BOARD", 151 | "The oscilloscope's analog board is not detected, or is not connected " + 152 | "to the digital board."], 153 | [0x53, "PICO_CONFIG_FAIL_AWG", 154 | "Unable to configure the signal generator."], 155 | [0x54, "PICO_INITIALISE_FPGA", 156 | "The FPGA cannot be initialized, so unit cannot be opened."], 157 | [0x56, "PICO_EXTERNAL_FREQUENCY_INVALID", 158 | "The frequency for the external clock is not within ±5% of the " + 159 | "stated value."], 160 | [0x57, "PICO_CLOCK_CHANGE_ERROR", 161 | "The FPGA could not lock the clock signal."], 162 | [0x58, "PICO_TRIGGER_AND_EXTERNAL_CLOCK_CLASH", 163 | "You are trying to configure the AUX input as both a trigger and a " + 164 | "reference clock."], 165 | [0x59, "PICO_PWQ_AND_EXTERNAL_CLOCK_CLASH", 166 | "You are trying to configure the AUX input as both a pulse width " + 167 | "qualifier and a reference clock."], 168 | [0x5A, "PICO_UNABLE_TO_OPEN_SCALING_FILE", 169 | "The scaling file set can not be opened."], 170 | [0x5B, "PICO_MEMORY_CLOCK_FREQUENCY", 171 | "The frequency of the memory is reporting incorrectly."], 172 | [0x5C, "PICO_I2C_NOT_RESPONDING", 173 | "The I2C that is being actioned is not responding to requests."], 174 | [0x5D, "PICO_NO_CAPTURES_AVAILABLE", 175 | "There are no captures available and therefore no data can be returned."], 176 | [0x5E, "PICO_NOT_USED_IN_THIS_CAPTURE_MODE", 177 | "The capture mode the device is currently running in does not support " + 178 | "the current request."], 179 | [0x103, "PICO_GET_DATA_ACTIVE", "Reserved"], 180 | [0x104, "PICO_IP_NETWORKED", "The device is currently connected via " + 181 | "the IP Network socket and thus the call made is not supported."], 182 | [0x105, "PICO_INVALID_IP_ADDRESS", "An IP address that is not correct " + 183 | "has been passed to the driver."], 184 | [0x106, "PICO_IPSOCKET_FAILED", "The IP socket has failed."], 185 | [0x107, "PICO_IPSOCKET_TIMEDOUT", "The IP socket has timed out."], 186 | [0x108, "PICO_SETTINGS_FAILED", "The settings requested have failed to " + 187 | "be set."], 188 | [0x109, "PICO_NETWORK_FAILED", "The network connection has failed."], 189 | [0x10A, "PICO_WS2_32_DLL_NOT_LOADED", "Unable to load the WS2 dll."], 190 | [0x10B, "PICO_INVALID_IP_PORT", "The IP port is invalid."], 191 | [0x10C, "PICO_COUPLING_NOT_SUPPORTED", 192 | "The type of coupling requested is not supported on the opened device."], 193 | [0x10D, "PICO_BANDWIDTH_NOT_SUPPORTED", 194 | "Bandwidth limit is not supported on the opened device."], 195 | [0x10E, "PICO_INVALID_BANDWIDTH", 196 | "The value requested for the bandwidth limit is out of range."], 197 | [0x10F, "PICO_AWG_NOT_SUPPORTED", 198 | "The device does not have an arbitrary waveform generator."], 199 | [0x110, "PICO_ETS_NOT_RUNNING", 200 | "Data has been requested with ETS mode set but run block has not been " + 201 | "called, or stop has been called."], 202 | [0x111, "PICO_SIG_GEN_WHITENOISE_NOT_SUPPORTED", 203 | "White noise is not supported on the opened device."], 204 | [0x112, "PICO_SIG_GEN_WAVETYPE_NOT_SUPPORTED", 205 | "The wave type requested is not supported by the opened device."], 206 | [0x116, "PICO_SIG_GEN_PRBS_NOT_SUPPORTED", 207 | "Siggen does not generate pseudorandom bit stream."], 208 | [0x117, "PICO_ETS_NOT_AVAILABLE_WITH_LOGIC_CHANNELS", 209 | "When a digital port is enabled, ETS sample mode is not available for " + 210 | "use."], 211 | [0x118, "PICO_WARNING_REPEAT_VALUE", "Not applicable to this device."], 212 | [0x119, "PICO_POWER_SUPPLY_CONNECTED", 213 | "The DC power supply is connected."], 214 | [0x11A, "PICO_POWER_SUPPLY_NOT_CONNECTED", 215 | "The DC power supply isn’t connected."], 216 | [0x11B, "PICO_POWER_SUPPLY_REQUEST_INVALID", 217 | "Incorrect power mode passed for current power source."], 218 | [0x11C, "PICO_POWER_SUPPLY_UNDERVOLTAGE", 219 | "The supply voltage from the USB source is too low."], 220 | [0x11D, "PICO_CAPTURING_DATA", 221 | "The device is currently busy capturing data."], 222 | [0x11E, "PICO_USB3_0_DEVICE_NON_USB3_0_PORT", 223 | "You must connect the device to a USB 3.0 port, or call " + 224 | "ps4000aChangePowerSource to switch the device into " + 225 | "non-USB 3.0-power mode"], 226 | [0x11F, "PICO_NOT_SUPPORTED_BY_THIS_DEVICE", 227 | "A function has been called that is not supported by the current " + 228 | "device variant."], 229 | [0x120, "PICO_INVALID_DEVICE_RESOLUTION", 230 | "The device resolution is invalid (out of range)."], 231 | [0x121, "PICO_INVALID_NUMBER_CHANNELS_FOR_RESOLUTION", 232 | "The number of channels which can be enabled is limited in " + 233 | "15 and 16-bit modes"], 234 | [0x122, "PICO_CHANNEL_DISABLED_DUE_TO_USB_POWERED", 235 | "USB Power not sufficient to power all channels."], 236 | [0x123, "PICO_SIGGEN_DC_VOLTAGE_NOT_CONFIGURABLE", ""], 237 | [0x124, "PICO_NO_TRIGGER_ENABLED_FOR_TRIGGER_IN_PRE_TRIG", ""], 238 | [0x125, "PICO_TRIGGER_WITHIN_PRE_TRIG_NOT_ARMED", ""], 239 | [0x126, "PICO_TRIGGER_WITHIN_PRE_NOT_ALLOWED_WITH_DELAY", ""], 240 | [0x127, "PICO_TRIGGER_INDEX_UNAVAILABLE", ""], 241 | [0x128, "PICO_AWG_CLOCK_FREQUENCY", ""], 242 | [0x129, "PICO_TOO_MANY_CHANNELS_IN_USE", ""], 243 | [0x12A, "PICO_NULL_CONDITIONS", ""], 244 | [0x12B, "PICO_DUPLICATE_CONDITION_SOURCE", ""], 245 | [0x12C, "PICO_INVALID_CONDITION_INFO", ""], 246 | [0x12D, "PICO_SETTINGS_READ_FAILED", ""], 247 | [0x12E, "PICO_SETTINGS_WRITE_FAILED", ""], 248 | [0x12F, "PICO_ARGUMENT_OUT_OF_RANGE", ""], 249 | [0x130, "PICO_HARDWARE_VERSION_NOT_SUPPORTED", ""], 250 | [0x131, "PICO_DIGITAL_HARDWARE_VERSION_NOT_SUPPORTED", ""], 251 | [0x132, "PICO_ANALOGUE_HARDWARE_VERSION_NOT_SUPPORTED", ""], 252 | [0x133, "PICO_UNABLE_TO_CONVERT_TO_RESISTANCE", ""], 253 | [0x134, "PICO_DUPLICATED_CHANNEL", ""], 254 | [0x135, "PICO_INVALID_RESISTANCE_CONVERSION", ""], 255 | [0x136, "PICO_INVALID_VALUE_IN_MAX_BUFFER", ""], 256 | [0x137, "PICO_INVALID_VALUE_IN_MIN_BUFFER", ""], 257 | [0x138, "PICO_SIGGEN_FREQUENCY_OUT_OF_RANGE", ""], 258 | [0x139, "PICO_EEPROM2_CORRUPT", ""], 259 | [0x13A, "PICO_EEPROM2_FAIL", ""], 260 | [0x13B, "PICO_SERIAL_BUFFER_TOO_SMALL", ""], 261 | [0x13C, "PICO_SIGGEN_TRIGGER_AND_EXTERNAL_CLOCK_CLASH", ""], 262 | [0x13D, "PICO_WARNING_SIGGEN_AUXIO_TRIGGER_DISABLED", ""], 263 | [0x13E, "PICO_SIGGEN_GATING_AUXIO_NOT_AVAILABLE", ""], 264 | [0x13F, "PICO_SIGGEN_GATING_AUXIO_ENABLED", ""], 265 | [0x140, "PICO_RESOURCE_ERROR", ""], 266 | [0x141, "PICO_TEMPERATURE_TYPE_INVALID", ""], 267 | [0x142, "PICO_TEMPERATURE_TYPE_NOT_SUPPORTED", ""], 268 | [0x143, "PICO_TIMEOUT", ""], 269 | [0x144, "PICO_DEVICE_NOT_FUNCTIONING", ""], 270 | [0x145, "PICO_INTERNAL_ERROR", ""], 271 | [0x146, "PICO_MULTIPLE_DEVICES_FOUND", ""], 272 | [0x147, "PICO_WARNING_NUMBER_OF_SEGMENTS_REDUCED", ""], 273 | [0x148, "PICO_CAL_PINS_STATES", ""], 274 | [0x149, "PICO_CAL_PINS_FREQUENCY", ""], 275 | [0x14A, "PICO_CAL_PINS_AMPLITUDE", ""], 276 | [0x14B, "PICO_CAL_PINS_WAVETYPE", ""], 277 | [0x14C, "PICO_CAL_PINS_OFFSET", ""], 278 | [0x14D, "PICO_PROBE_FAULT", ""], 279 | [0x14E, "PICO_PROBE_IDENTITY_UNKNOWN", ""], 280 | [0x14F, "PICO_PROBE_POWER_DC_POWER_SUPPLE_REQUIRED", ""], 281 | [0x150, "PICO_PROBE_NOT_POWERED_THROUGH_DC_POWER_SUPPLY", ""], 282 | [0x151, "PICO_PROBE_CONFIG_FAILURE", ""], 283 | [0x152, "PICO_PROBE_INTERACTION_CALLBACK", ""], 284 | [0x153, "PICO_UNKNOWN_INTELLIGENT_PROBE", ""], 285 | [0x154, "PICO_INTELLIGENT_PROBE_CORRUPT", ""], 286 | [0x155, "PICO_PROBE_COLLECTION_NOT_STARTED", ""], 287 | [0x156, "PICO_PROBE_POWER_CONSUMPTION_EXCEEDED", ""], 288 | [0x157, "PICO_WARNING_PROBE_CHANNEL_OUT_OF_SYNC", ""], 289 | [0x158, "PICO_ENDPOINT_MISSING", ""], 290 | [0x159, "PICO_UNKNOWN_ENDPOINT_REQUEST", ""], 291 | [0x15A, "PICO_ADC_TYPE_ERROR", ""], 292 | [0x15B, "PICO_FPGA2_FAILED", ""], 293 | [0x15C, "PICO_FPGA2_DEVICE_STATUS", ""], 294 | [0x15D, "PICO_ENABLED_PROGRAM_FPGA2_FAILED", ""], 295 | [0x15E, "PICO_NO_CANNELS_OR_PORTS_ENABLED", ""], 296 | [0x15F, "PICO_INVALID_RATIO_MODE", ""], 297 | [0x160, "PICO_READS_NOT_SUPPORTED_IN_CURRENT_CAPTURE_MODE", ""], 298 | [0x161, "PICO_TRIGGER_READ_SELECTION_CHECK_FAILED", ""], 299 | [0x162, "PICO_DATA_READ1_SELECTION_CHECK_FAILED", ""], 300 | [0x164, "PICO_DATA_READ2_SELECTION_CHECK_FAILED", ""], 301 | [0x168, "PICO_DATA_READ3_SELECTION_CHECK_FAILED", ""], 302 | [0x170, "PICO_READ_SELECTION_OUT_OF_RANGE", ""], 303 | [0x171, "PICO_MULTIPLE_RATIO_MODES", ""], 304 | [0x172, "PICO_NO_SAMPLES_READ", ""], 305 | [0x173, "PICO_RATIO_MODE_NOT_REQUESTED", ""], 306 | [0x174, "PICO_NO_USER_READ_REQUESTS", ""], 307 | [0x175, "PICO_ZERO_SAMPLES_INVALID", ""], 308 | [0x176, "PICO_ANALOGUE_HARDWARE_MISSING", ""], 309 | [0x177, "PICO_ANALOGUE_HARDWARE_PINS", ""], 310 | [0x178, "PICO_ANALOGUE_SMPS_FAULT", ""], 311 | [0x179, "PICO_DIGITAL_ANALOGUE_HARDWARE_CONFLICT", ""], 312 | [0x17A, "PICO_RATIO_MODE_BUFFER_NOT_SET", ""], 313 | [0x17B, "PICO_RESOLUTION_NOT_SUPPORTED_BY_VARIENT", ""], 314 | [0x17C, "PICO_THRESHOLD_OUT_OF_RANGE", ""], 315 | [0x17D, "PICO_INVALID_SIMPLE_TRIGGER_DIRECTION", ""], 316 | [0x17E, "PICO_AUX_NOT_SUPPORTED", ""], 317 | [0x17F, "PICO_NULL_DIRECTIONS", ""], 318 | [0x180, "PICO_NULL_CHANNEL_PROPERTIES", ""], 319 | [0x181, "PICO_TRIGGER_CHANNEL_NOT_ENABLED", ""], 320 | [0x182, "PICO_CONDITION_HAS_NO_TRIGGER_PROPERTY", ""], 321 | [0x183, "PICO_RATIO_MODE_TRIGGER_MASKING_INVALID", ""], 322 | [0x184, "PICO_TRIGGER_DATA_REQUIRES_MIN_BUFFER_SIZE_OF_40_SAMPLES", ""], 323 | [0x185, "PICO_NO_OF_CAPTURES_OUT_OF_RANGE", ""], 324 | [0x186, "PICO_RATIO_MODE_SEGMENT_HEADER_DOES_NOT_REQUIRE_BUFFERS", ""], 325 | [0x187, "PICO_FOR_SEGMENT_HEADER_USE_GETTRIGGERINFO", ""], 326 | [0x188, "PICO_READ_NOT_SET", ""], 327 | [0x189, "PICO_ADC_SETTING_MISMATCH", ""], 328 | [0x18A, "PICO_DATATYPE_INVALID", ""], 329 | [0x18B, "PICO_RATIO_MODE_DOES_NOT_SUPPORT_DATATYPE", ""], 330 | [0x18C, "PICO_CHANNEL_COMBINATION_NOT_VALID_IN_THIS_RESOLUTION", ""], 331 | [0x18D, "PICO_USE_8BIT_RESOLUTION", ""], 332 | [0x18E, "PICO_AGGREGATE_BUFFERS_SAME_POINTER", ""], 333 | [0x18F, "PICO_OVERLAPPED_READ_VALUES_OUT_OF_RANGE", ""], 334 | [0x190, "PICO_OVERLAPPED_READ_SEGMENTS_OUT_OF_RANGE", ""], 335 | [0x191, "PICO_CHANNELFLAGSCOMBINATIONS_ARRAY_SIZE_TOO_SMALL", ""], 336 | [0x192, "PICO_CAPTURES_EXCEEDS_NO_OF_SUPPORTED_SEGMENTS", ""], 337 | [0x193, "PICO_TIME_UNITS_OUT_OF_RANGE", ""], 338 | [0x194, "PICO_NO_SAMPLES_REQUESTED", ""], 339 | [0x195, "PICO_INVALID_ACTION", ""], 340 | [0x196, "PICO_NO_OF_SAMPLES_NEED_TO_BE_EQUAL_WHEN_ADDING_BUFFERS", ""], 341 | [0x197, "PICO_WAITING_FOR_DATA_BUFFERS", ""], 342 | [0x198, "PICO_STREAMING_ONLY_SUPPORTS_ONE_READ", ""], 343 | [0x199, "PICO_CLEAR_DATA_BUFFER_INVALID", ""], 344 | [0x19A, "PICO_INVALID_ACTION_FLAGS_COMBINATION", ""], 345 | [0x19B, "PICO_PICO_MOTH_MIN_AND_MAX_NULL_BUFFERS_CANNOT_BE_ADDED", ""], 346 | [0x19C, 347 | "PICO_CONFLICT_IN_SET_DATA_BUFFERS_CALL_REMOVE_DATA_BUFFER_TO_RESET", ""], 348 | [0x19D, 349 | "PICO_REMOVING_DATA_BUFFER_ENTRIES_NOT_ALLOWED_WHILE_DATA_PROCESSING", 350 | ""], 351 | [0x200, "PICO_CYUSB_REQUEST_FAILED", ""], 352 | [0x201, "PICO_STREAMING_DATA_REQUIRED", ""], 353 | [0x202, "PICO_INVALID_NUMBER_OF_SAMPLES", ""], 354 | [0x203, "PICO_INALID_DISTRIBUTION", ""], 355 | [0x204, "PICO_BUFFER_LENGTH_GREATER_THAN_INT32_T", ""], 356 | [0x209, "PICO_PLL_MUX_OUT_FAILED", ""], 357 | [0x20A, "PICO_ONE_PULSE_WIDTH_DIRECTION_ALLOWED", ""], 358 | [0x20B, "PICO_EXTERNAL_TRIGGER_NOT_SUPPORTED", ""], 359 | [0x20C, "PICO_NO_TRIGGER_CONDITIONS_SET", ""], 360 | [0x20D, "PICO_NO_OF_CHANNEL_TRIGGER_PROPERTIES_OUT_OF_RANGE", ""], 361 | [0x20E, "PICO_PROBE_COMPNENT_ERROR", ""], 362 | [0x210, "PICO_INVALID_TRIGGER_CHANNELS_FOR_ETS", ""], 363 | [0x211, "PICO_NOT_AVALIABLE_WHEN_STREAMING_IS_RUNNING", ""], 364 | [0x212, "PICO_INVALID_TRIGGER_WITHIN_PRE_TRIGGER_STATE", ""], 365 | [0x213, "PICO_ZERO_NUMBER_OF_CAPTURES_INVALID", ""], 366 | [0x300, "PICO_TRIGGER_DELAY_OUT_OF_RANGE", ""], 367 | [0x301, "PICO_INVALID_THRESHOLD_DIRECTION", ""], 368 | [0x302, "PICO_INVALID_THRESGOLD_MODE", ""], 369 | [0x6000, "PICO_HARDWARE_CAPTURE_TIMEOUT", 370 | "waiting for the device to capture timed out"], 371 | [0x6001, "PICO_HARDWARE_READY_TIMEOUT", 372 | "waiting for the device be ready for capture timed out"], 373 | [0x6002, "PICO_HARDWARE_CAPTURING_CALL_STOP", 374 | ("the driver is performing a capture requested by RunStreaming or " 375 | "RunBlock to interrupt this capture call Stop on the device first")], 376 | [0x2000001, "PICO_TRIGGER_TIME_NOT_REQUESTED", 377 | "Requesting the TriggerTimeOffset, the trigger time has not been set."], 378 | [0x1000000, "PICO_DEVICE_TIME_STAMP_RESET", ""], 379 | [0x10000000, "PICO_WATCHDOGTIMER", ""], 380 | [0x10000001, "PICO_IPP_NOT_FOUND", ""], 381 | [0x10000002, "PICO_IPP_NO_FUNCTION", ""], 382 | [0x10000003, "PICO_IPP_ERROR", ""], 383 | [0x10000004, "PICO_SHADOW_CAL_NOT_AVAILABLE", ""], 384 | [0x10000005, "PICO_SHADOW_CAL_DISABLED", ""], 385 | [0x10000006, "PICO_SHADOW_CAL_ERROR", ""], 386 | [0x10000007, "PICO_SHADOW_CAL_CORRUPT", ""], 387 | ] 388 | -------------------------------------------------------------------------------- /picoscope/ps2000.py: -------------------------------------------------------------------------------- 1 | """ 2 | This is the low level driver file for a specific Picoscope. 3 | 4 | By this, I mean if parameters want to get passed as strings, they should be 5 | handled by PSBase 6 | All functions here should take things as close to integers as possible, the 7 | only exception here is for array parameters. Array parameters should be passed 8 | in a pythonic way through numpy since the PSBase class should not be aware of 9 | the specifics behind how the clib is called. 10 | 11 | The functions should not have any default values as these should be handled 12 | by PSBase. 13 | """ 14 | 15 | from __future__ import division 16 | from __future__ import absolute_import 17 | from __future__ import print_function 18 | from __future__ import unicode_literals 19 | 20 | # import math 21 | 22 | import inspect 23 | 24 | # to load the proper dll 25 | import platform 26 | 27 | # Do not import or use ill definied data types 28 | # such as short int or long 29 | # use the values specified in the h file 30 | # float is always defined as 32 bits 31 | # double is defined as 64 bits 32 | from ctypes import byref, POINTER, create_string_buffer, c_float, \ 33 | c_int16, c_int32, c_uint32, c_void_p 34 | from ctypes import c_int32 as c_enum 35 | 36 | from picoscope.picobase import _PicoscopeBase 37 | 38 | """ 39 | pico-python is Copyright (c) 2013-2014 By: 40 | Colin O'Flynn 41 | Mark Harfouche 42 | All rights reserved. 43 | 44 | Redistribution and use in source and binary forms, with or without 45 | modification, are permitted provided that the following conditions are met: 46 | 47 | 1. Redistributions of source code must retain the above copyright notice, this 48 | list of conditions and the following disclaimer. 49 | 2. Redistributions in binary form must reproduce the above copyright notice, 50 | this list of conditions and the following disclaimer in the documentation 51 | and/or other materials provided with the distribution. 52 | 53 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 54 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 55 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 56 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR 57 | ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 58 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 59 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 60 | ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 61 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 62 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 63 | 64 | Inspired by Patrick Carle's code at 65 | http://www.picotech.com/support/topic11239.html 66 | which was adapted from 67 | http://www.picotech.com/support/topic4926.html 68 | """ 69 | 70 | 71 | class PS2000(_PicoscopeBase): 72 | """The following are low-level functions for the PS2000.""" 73 | 74 | LIBNAME = "ps2000" 75 | 76 | NUM_CHANNELS = 2 77 | CHANNELS = {"A": 0, "B": 1, "MaxChannels": 2} 78 | 79 | THRESHOLD_TYPE = {"Rising": 0, 80 | "Falling": 1} 81 | 82 | CHANNEL_RANGE = [ 83 | {"rangeV": 20E-3, "apivalue": 1, "rangeStr": "20 mV"}, 84 | {"rangeV": 50E-3, "apivalue": 2, "rangeStr": "50 mV"}, 85 | {"rangeV": 100E-3, "apivalue": 3, "rangeStr": "100 mV"}, 86 | {"rangeV": 200E-3, "apivalue": 4, "rangeStr": "200 mV"}, 87 | {"rangeV": 500E-3, "apivalue": 5, "rangeStr": "500 mV"}, 88 | {"rangeV": 1.0, "apivalue": 6, "rangeStr": "1 V"}, 89 | {"rangeV": 2.0, "apivalue": 7, "rangeStr": "2 V"}, 90 | {"rangeV": 5.0, "apivalue": 8, "rangeStr": "5 V"}, 91 | {"rangeV": 10.0, "apivalue": 9, "rangeStr": "10 V"}, 92 | {"rangeV": 20.0, "apivalue": 10, "rangeStr": "20 V"}] 93 | 94 | CHANNEL_COUPLINGS = {"DC": 1, "AC": 0} 95 | 96 | # has_sig_gen = True 97 | WAVE_TYPES = {"Sine": 0, "Square": 1, "Triangle": 2, 98 | "RampUp": 3, "RampDown": 4, "DCVoltage": 5} 99 | 100 | SWEEP_TYPES = {"Up": 0, "Down": 1, "UpDown": 2, "DownUp": 3} 101 | 102 | TIME_UNITS = {"FS": 0, "PS": 1, "NS": 2, "US": 3, "MS": 4, "S": 5} 103 | 104 | MAX_VALUE = 32767 105 | MIN_VALUE = -32767 106 | 107 | MAX_TIMEBASES = 19 108 | 109 | UNIT_INFO_TYPES = {"DriverVersion": 0x0, 110 | "USBVersion": 0x1, 111 | "HardwareVersion": 0x2, 112 | "VariantInfo": 0x3, 113 | "BatchAndSerial": 0x4, 114 | "CalDate": 0x5, 115 | "ErrorCode": 0x6, 116 | "KernelVersion": 0x7} 117 | 118 | channelBuffersPtr = [c_void_p(), c_void_p()] 119 | channelBuffersLen = [0, 0] 120 | 121 | SIGGEN_TRIGGER_TYPES = {"Rising": 0, "Falling": 1, 122 | "GateHigh": 2, "GateLow": 3} 123 | SIGGEN_TRIGGER_SOURCES = {"None": 0, "ScopeTrig": 1, "AuxIn": 2, 124 | "ExtIn": 3, "SoftTrig": 4, "TriggerRaw": 5} 125 | 126 | AWG_INDEX_MODES = {"Single": 0, "Dual": 1, "Quad": 2} 127 | 128 | AWGPhaseAccumulatorSize = 32 129 | AWGBufferAddressWidth = 12 130 | AWGMaxSamples = 2 ** AWGBufferAddressWidth 131 | 132 | AWGDACInterval = 2.0833e-08 133 | AWGDACFrequency = 1 / AWGDACInterval 134 | 135 | def __init__(self, serialNumber=None, connect=True): 136 | """Load DLL etc.""" 137 | if platform.system() == 'Linux': 138 | from ctypes import cdll 139 | self.lib = cdll.LoadLibrary("lib" + self.LIBNAME + ".so") 140 | elif platform.system() == 'Darwin': 141 | from picoscope.darwin_utils import LoadLibraryDarwin 142 | self.lib = LoadLibraryDarwin("lib" + self.LIBNAME + ".dylib") 143 | else: 144 | from ctypes import windll 145 | from ctypes.util import find_library 146 | self.lib = windll.LoadLibrary( 147 | find_library(str(self.LIBNAME + ".dll")) 148 | ) 149 | 150 | super(PS2000, self).__init__(serialNumber, connect) 151 | 152 | def _lowLevelOpenUnit(self, sn): 153 | if sn is not None: 154 | raise ValueError("PS2000 Doesn't Support Open by Serial Number") 155 | 156 | m = self.lib.ps2000_open_unit() 157 | 158 | if m < 0: 159 | raise IOError("Failed to Find PS2000 Unit." + 160 | " Should you be using PS2000a driver?") 161 | 162 | self.handle = m 163 | self.suggested_time_units = self.TIME_UNITS["NS"] 164 | 165 | def _lowLevelCloseUnit(self): 166 | m = self.lib.ps2000_close_unit(c_int16(self.handle)) 167 | self.checkResult(m) 168 | 169 | def _lowLevelSetChannel(self, chNum, enabled, coupling, VRange, VOffset, 170 | BWLimited): 171 | m = self.lib.ps2000_set_channel( 172 | c_int16(self.handle), c_enum(chNum), c_int16(enabled), 173 | c_enum(coupling), c_enum(VRange)) 174 | self.checkResult(m) 175 | 176 | def _lowLevelStop(self): 177 | m = self.lib.ps2000_stop(c_int16(self.handle)) 178 | self.checkResult(m) 179 | 180 | def _lowLevelGetUnitInfo(self, info): 181 | s = create_string_buffer(256) 182 | 183 | m = self.lib.ps2000_get_unit_info( 184 | c_int16(self.handle), byref(s), c_int16(len(s)), c_enum(info)) 185 | self.checkResult(m) 186 | 187 | # should this bee ascii instead? 188 | # I think they are equivalent... 189 | return s.value.decode('utf-8') 190 | 191 | def _lowLevelFlashLed(self, times): 192 | m = self.lib.ps2000_flash_led(c_int16(self.handle)) 193 | self.checkResult(m) 194 | 195 | def _lowLevelSetSimpleTrigger(self, enabled, trigsrc, threshold_adc, 196 | direction, delay, timeout_ms): 197 | 198 | # TODO: 199 | # Fix 'auto' which is where trigger occurs in block. Delay is not used 200 | 201 | m = self.lib.ps2000_set_trigger( 202 | c_int16(self.handle), c_enum(trigsrc), c_int16(threshold_adc), 203 | c_enum(direction), c_int16(delay), c_int16(timeout_ms)) 204 | self.checkResult(m) 205 | 206 | def _lowLevelRunBlock(self, numPreTrigSamples, numPostTrigSamples, 207 | timebase, oversample, segmentIndex, callback, 208 | pParameter): 209 | # NOT: Oversample is NOT used! 210 | # Note: callback and pParameter are not available. 211 | 212 | # TODO: Fix 'delay' which is where trigger occurs in block 213 | if numPreTrigSamples > 0: 214 | raise ValueError("numPreTrigSamples isn't supported on PS2000") 215 | 216 | timeIndisposedMs = c_int32() 217 | m = self.lib.ps2000_run_block( 218 | c_int16(self.handle), c_uint32(numPostTrigSamples), 219 | c_uint32(timebase), c_int16(1), byref(timeIndisposedMs)) 220 | 221 | self.checkResult(m) 222 | return timeIndisposedMs.value 223 | 224 | def _lowLevelIsReady(self): 225 | ready = c_int16() 226 | ready = self.lib.ps2000_ready(c_int16(self.handle)) 227 | if ready > 0: 228 | return True 229 | elif ready == 0: 230 | return False 231 | else: 232 | raise IOError("ps2000_ready returned %d" % ready.value) 233 | 234 | def _lowLevelGetTimebase(self, tb, noSamples, oversample, segmentIndex): 235 | """Return (timeIntervalSeconds, maxSamples).""" 236 | maxSamples = c_int32() 237 | time_interval = c_int32() 238 | time_units = c_int16() 239 | 240 | m = self.lib.ps2000_get_timebase( 241 | c_int16(self.handle), c_int16(tb), c_uint32(noSamples), 242 | byref(time_interval), byref(time_units), c_int16(1), 243 | byref(maxSamples)) 244 | 245 | self.checkResult(m) 246 | 247 | self.suggested_time_units = time_units.value 248 | 249 | return (time_interval.value / 1.0E9, maxSamples.value) 250 | 251 | def getTimeBaseNum(self, sampleTimeS): 252 | """ps2000 doesn't seem to have published formula like other scopes.""" 253 | time_interval = c_int32() 254 | timebases = [None] * self.MAX_TIMEBASES 255 | 256 | # Convert to nS 257 | sampleTimenS = sampleTimeS * 1E9 258 | 259 | tb = 0 260 | while tb < self.MAX_TIMEBASES: 261 | rv = self.lib.ps2000_get_timebase( 262 | c_int16(self.handle), c_int16(tb), c_uint32(512), 263 | byref(time_interval), c_void_p(), c_int16(1), c_void_p()) 264 | if rv != 0: 265 | timebases[tb] = time_interval.value 266 | 267 | tb += 1 268 | 269 | # Figure out closest option 270 | besterror = 1E99 271 | bestindx = 0 272 | for indx, val in enumerate(timebases): 273 | if val is not None: 274 | error = sampleTimenS - val 275 | if abs(error) < besterror: 276 | besterror = abs(error) 277 | bestindx = indx 278 | 279 | return bestindx 280 | 281 | def getTimestepFromTimebase(self, timebase): 282 | """Return timestep from timebase.""" 283 | time_interval = c_int32() 284 | m = self.lib.ps2000_get_timebase( 285 | c_int16(self.handle), c_int16(timebase), c_uint32(512), 286 | byref(time_interval), c_void_p(), c_int16(1), c_void_p()) 287 | self.checkResult(m) 288 | return (time_interval.value / 1.0E9) 289 | 290 | def _lowLevelSetDataBuffer(self, channel, data, downSampleMode, 291 | segmentIndex): 292 | dataPtr = data.ctypes.data_as(POINTER(c_int16)) 293 | numSamples = len(data) 294 | 295 | self.channelBuffersPtr[channel] = dataPtr 296 | self.channelBuffersLen[channel] = numSamples 297 | 298 | def _lowLevelClearDataBuffer(self, channel, segmentIndex): 299 | self.channelBuffersPtr[channel] = c_void_p() 300 | self.channelBuffersLen[channel] = 0 301 | 302 | def _lowLevelGetValues(self, numSamples, startIndex, 303 | downSampleRatio, downSampleMode, segmentIndex): 304 | 305 | # TODO: Check overflow in channelBuffersLen against numSamples, 306 | # but need to not raise error if channelBuffersPtr is void 307 | 308 | overflow = c_int16() 309 | rv = self.lib.ps2000_get_values( 310 | c_int16(self.handle), 311 | self.channelBuffersPtr[0], 312 | self.channelBuffersPtr[1], 313 | c_void_p(), c_void_p(), 314 | byref(overflow), c_int32(numSamples)) 315 | 316 | self.checkResult(rv) 317 | return (rv, overflow.value) 318 | 319 | def _lowLevelSetSigGenBuiltInSimple(self, offsetVoltage, pkToPk, waveType, 320 | frequency, shots, triggerType, 321 | triggerSource, stopFreq, increment, 322 | dwellTime, sweepType, numSweeps): 323 | if stopFreq is None: 324 | stopFreq = frequency 325 | 326 | m = self.lib.ps2000_set_sig_gen_built_in( 327 | c_int16(self.handle), 328 | c_int32(int(offsetVoltage * 1000000)), 329 | c_int32(int(pkToPk * 1000000)), 330 | c_int16(waveType), 331 | c_float(frequency), c_float(stopFreq), 332 | c_float(increment), c_float(dwellTime), c_enum(sweepType), 333 | c_uint32(numSweeps)) 334 | self.checkResult(m) 335 | 336 | def checkResult(self, ec): 337 | """Check result of function calls, raise exception if not 0.""" 338 | # PS2000 differs from other drivers in that non-zero is good 339 | if ec == 0: 340 | raise IOError('Error calling %s' % (inspect.stack()[1][3])) 341 | 342 | return 0 343 | -------------------------------------------------------------------------------- /picoscope/ps2000a.py: -------------------------------------------------------------------------------- 1 | """ 2 | This is the low level driver file for a specific Picoscope. 3 | 4 | By this, I mean if parameters want to get passed as strings, they should be 5 | handled by PSBase 6 | All functions here should take things as close to integers as possible, the 7 | only exception here is for array parameters. Array parameters should be passed 8 | in a pythonic way through numpy since the PSBase class should not be aware of 9 | the specifics behind how the clib is called. 10 | 11 | The functions should not have any default values as these should be handled 12 | by PSBase. 13 | """ 14 | 15 | # This is the instrument-specific file for the PS2000a series of instruments. 16 | # 17 | # pico-python is Copyright (c) 2013-2016 By: 18 | # Colin O'Flynn 19 | # Mark Harfouche 20 | # All rights reserved. 21 | # 22 | # Redistribution and use in source and binary forms, with or without 23 | # modification, are permitted provided that the following conditions are met: 24 | # 25 | # 1. Redistributions of source code must retain the above copyright notice, 26 | # this list of conditions and the following disclaimer. 27 | # 2. Redistributions in binary form must reproduce the above copyright notice, 28 | # this list of conditions and the following disclaimer in the documentation 29 | # and/or other materials provided with the distribution. 30 | # 31 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 32 | # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 33 | # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 34 | # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE 35 | # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 36 | # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 37 | # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 38 | # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 39 | # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 40 | # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 41 | # POSSIBILITY OF SUCH DAMAGE. 42 | 43 | from __future__ import division 44 | from __future__ import absolute_import 45 | from __future__ import print_function 46 | from __future__ import unicode_literals 47 | 48 | import math 49 | import numpy as np 50 | 51 | # to load the proper dll 52 | import platform 53 | 54 | # Do not import or use ill definied data types 55 | # such as short int or long 56 | # use the values specified in the h file 57 | # float is always defined as 32 bits 58 | # double is defined as 64 bits 59 | from ctypes import byref, POINTER, create_string_buffer, c_float, \ 60 | c_int16, c_int32, c_uint16, c_uint32, c_void_p 61 | from ctypes import c_int32 as c_enum 62 | 63 | from picoscope.picobase import _PicoscopeBase 64 | 65 | 66 | class PS2000a(_PicoscopeBase): 67 | """The following are low-level functions for the PS2000a.""" 68 | 69 | LIBNAME = "ps2000a" 70 | 71 | NUM_CHANNELS = 4 72 | CHANNELS = {"A": 0, "B": 1, "C": 2, "D": 3, 73 | "External": 4, "MaxChannels": 4, "TriggerAux": 5} 74 | 75 | ADC_RESOLUTIONS = {"8": 0, "12": 1, "14": 2, "15": 3, "16": 4} 76 | 77 | CHANNEL_RANGE = [{"rangeV": 10E-3, "apivalue": 0, "rangeStr": "10 mV"}, 78 | {"rangeV": 20E-3, "apivalue": 1, "rangeStr": "20 mV"}, 79 | {"rangeV": 50E-3, "apivalue": 2, "rangeStr": "50 mV"}, 80 | {"rangeV": 100E-3, "apivalue": 3, "rangeStr": "100 mV"}, 81 | {"rangeV": 200E-3, "apivalue": 4, "rangeStr": "200 mV"}, 82 | {"rangeV": 500E-3, "apivalue": 5, "rangeStr": "500 mV"}, 83 | {"rangeV": 1.0, "apivalue": 6, "rangeStr": "1 V"}, 84 | {"rangeV": 2.0, "apivalue": 7, "rangeStr": "2 V"}, 85 | {"rangeV": 5.0, "apivalue": 8, "rangeStr": "5 V"}, 86 | {"rangeV": 10.0, "apivalue": 9, "rangeStr": "10 V"}, 87 | {"rangeV": 20.0, "apivalue": 10, "rangeStr": "20 V"}, 88 | {"rangeV": 50.0, "apivalue": 11, "rangeStr": "50 V"}, 89 | ] 90 | 91 | CHANNEL_COUPLINGS = {"DC": 1, "AC": 0} 92 | 93 | # has_sig_gen = True 94 | WAVE_TYPES = {"Sine": 0, "Square": 1, "Triangle": 2, 95 | "RampUp": 3, "RampDown": 4, 96 | "Sinc": 5, "Gaussian": 6, "HalfSine": 7, "DCVoltage": 8, 97 | "WhiteNoise": 9} 98 | 99 | SWEEP_TYPES = {"Up": 0, "Down": 1, "UpDown": 2, "DownUp": 3} 100 | 101 | SIGGEN_TRIGGER_TYPES = {"Rising": 0, "Falling": 1, 102 | "GateHigh": 2, "GateLow": 3} 103 | SIGGEN_TRIGGER_SOURCES = {"None": 0, "ScopeTrig": 1, "AuxIn": 2, 104 | "ExtIn": 3, "SoftTrig": 4, "TriggerRaw": 5} 105 | 106 | # TIME_UNITS = {'PS2000A_FS':0,'PS2000A_PS':1, 107 | # 'PS2000A_NS':2,'PS2000A_US':3,'PS2000A_MS':4, 108 | # 'PS2000A_S':5,'PS2000A_MAX_TIME_UNITS':6} 109 | TIME_UNITS = {0: 1e-15, 1: 1e-12, 2: 1e-9, 3: 1e-6, 4: 1e-3, 5: 1e0} 110 | 111 | AWGPhaseAccumulatorSize = 32 112 | 113 | # AWG scaling according to programming manual p.72 114 | # Vout = 1uV * (pkToPk/2) * (sample_value / 32767) + offsetVoltage 115 | # The API datatype is a (signed) short 116 | AWGMaxVal = 32767 117 | AWGMinVal = -32767 118 | 119 | AWG_INDEX_MODES = {"Single": 0, "Dual": 1, "Quad": 2} 120 | 121 | MAX_VALUE_8BIT = 32512 122 | MIN_VALUE_8BIT = -32512 123 | MAX_VALUE_OTHER = 32767 124 | MIN_VALUE_OTHER = -32767 125 | 126 | EXT_RANGE_VOLTS = 5 127 | 128 | def __init__(self, serialNumber=None, connect=True): 129 | """Load DLL etc.""" 130 | if platform.system() == 'Linux': 131 | from ctypes import cdll 132 | self.lib = cdll.LoadLibrary("lib" + self.LIBNAME + ".so") 133 | elif platform.system() == 'Darwin': 134 | self.lib = self.loadLibraryDarwin("lib" + self.LIBNAME + ".dylib") 135 | else: 136 | from ctypes import windll 137 | from ctypes.util import find_library 138 | self.lib = windll.LoadLibrary( 139 | find_library(str(self.LIBNAME + ".dll")) 140 | ) 141 | 142 | self.resolution = self.ADC_RESOLUTIONS["8"] 143 | 144 | super(PS2000a, self).__init__(serialNumber, connect) 145 | 146 | def _lowLevelOpenUnit(self, sn): 147 | c_handle = c_int16() 148 | if sn is not None: 149 | serialNullTermStr = byref(create_string_buffer(sn)) 150 | else: 151 | serialNullTermStr = None 152 | # Passing None is the same as passing NULL 153 | m = self.lib.ps2000aOpenUnit(byref(c_handle), serialNullTermStr) 154 | self.checkResult(m) 155 | self.handle = c_handle.value 156 | 157 | # The scaling factor used in the timebase calculation varies based on 158 | # the particular model. See section 2.8 (pg 27) of the 2000a 159 | # programmer's guide 160 | self.model = self.getUnitInfo('VariantInfo') 161 | if self.model in ('2205AMSO', '2206', '2206A', '2206B', '2405A'): 162 | # 500 MS/s 163 | self._timebase_to_timestep = \ 164 | lambda n: (2**n / 5e8) if n < 3 else ((n - 2) / 625e5) 165 | self._timestep_to_timebase = \ 166 | lambda t: math.log(t * 5e8, 2) if t < 16e-9 else ( 167 | (t * 625e5) + 2) 168 | elif self.model in ('2206BMSO', '2207', '2207A', '2207B', '2207BMSO', 169 | '2208', '2208A', '2208B', '2208BMSO', '2406B', 170 | '2407B', '2408B'): 171 | # 1 GS/s 172 | self._timebase_to_timestep = lambda n: (2**n / 1e9) if n < 3 else ( 173 | (n - 2) / 125e6) 174 | self._timestep_to_timebase = \ 175 | lambda t: math.log(t * 1e9, 2) if t < 8e-9 else ( 176 | (t * 125e6) + 2) 177 | elif self.model == '2205MSO': 178 | self._timebase_to_timestep = \ 179 | lambda n: (2**n / 2e8) if n < 1 else (n / 1e8) 180 | self._timestep_to_timebase = \ 181 | lambda t: math.log(t * 2e8, 2) if t < 10e-9 else (t * 1e8) 182 | else: 183 | raise ValueError("Unrecognised variant {}".format(self.model)) 184 | 185 | # The AWG parameters vary based on the particular model. See section 186 | # 3.51.2 of the 2000a programmer's guide 187 | if self.model in ('2205AMSO', '2206', '2206A', '2207', '2207A', '2208', 188 | '2208A', '2405A'): 189 | self.AWGBufferAddressWidth = 13 190 | self.AWGDACInterval = 50E-9 191 | elif self.model in ('2206B', '2206BMSO', '2207B', '2207BMSO', '2208B', 192 | '2208BMSO', '2406B', '2407B', '2408B'): 193 | self.AWGBufferAddressWidth = 15 194 | self.AWGDACInterval = 50E-9 195 | else: 196 | # The programmer's manual indicates that some older models have 197 | # these parameters. Just use them as a catch-all for any models 198 | # not listed above 199 | self.AWGBufferAddressWidth = 13 200 | self.AWGDACInterval = 20.83E-9 201 | 202 | self.AWGMaxSamples = 2 ** self.AWGBufferAddressWidth 203 | self.AWGDACFrequency = 1 / self.AWGDACInterval 204 | 205 | def _lowLevelCloseUnit(self): 206 | m = self.lib.ps2000aCloseUnit(c_int16(self.handle)) 207 | self.checkResult(m) 208 | 209 | def _lowLevelSetChannel(self, chNum, enabled, coupling, VRange, VOffset, 210 | BWLimited): 211 | m = self.lib.ps2000aSetChannel(c_int16(self.handle), c_enum(chNum), 212 | c_int16(enabled), c_enum(coupling), 213 | c_enum(VRange), c_float(VOffset)) 214 | self.checkResult(m) 215 | 216 | def _lowLevelStop(self): 217 | m = self.lib.ps2000aStop(c_int16(self.handle)) 218 | self.checkResult(m) 219 | 220 | def _lowLevelGetUnitInfo(self, info): 221 | s = create_string_buffer(256) 222 | requiredSize = c_int16(0) 223 | 224 | m = self.lib.ps2000aGetUnitInfo(c_int16(self.handle), byref(s), 225 | c_int16(len(s)), byref(requiredSize), 226 | c_enum(info)) 227 | self.checkResult(m) 228 | if requiredSize.value > len(s): 229 | s = create_string_buffer(requiredSize.value + 1) 230 | m = self.lib.ps2000aGetUnitInfo(c_int16(self.handle), byref(s), 231 | c_int16(len(s)), 232 | byref(requiredSize), c_enum(info)) 233 | self.checkResult(m) 234 | 235 | # should this bee ascii instead? 236 | # I think they are equivalent... 237 | return s.value.decode('utf-8') 238 | 239 | def _lowLevelFlashLed(self, times): 240 | m = self.lib.ps2000aFlashLed(c_int16(self.handle), c_int16(times)) 241 | self.checkResult(m) 242 | 243 | def _lowLevelSetSimpleTrigger(self, enabled, trigsrc, threshold_adc, 244 | direction, delay, auto): 245 | m = self.lib.ps2000aSetSimpleTrigger( 246 | c_int16(self.handle), c_int16(enabled), 247 | c_enum(trigsrc), c_int16(threshold_adc), 248 | c_enum(direction), c_uint32(delay), c_int16(auto)) 249 | self.checkResult(m) 250 | 251 | def _lowLevelSetNoOfCaptures(self, numCaptures): 252 | m = self.lib.ps2000aSetNoOfCaptures( 253 | c_int16(self.handle), c_uint16(numCaptures)) 254 | self.checkResult(m) 255 | 256 | def _lowLevelMemorySegments(self, numSegments): 257 | maxSamples = c_int32() 258 | m = self.lib.ps2000aMemorySegments(c_int16(self.handle), 259 | c_uint16(numSegments), 260 | byref(maxSamples)) 261 | self.checkResult(m) 262 | return maxSamples.value 263 | 264 | def _lowLevelGetMaxSegments(self): 265 | maxSegments = c_int16() 266 | m = self.lib.ps2000aGetMaxSegments(c_int16(self.handle), 267 | byref(maxSegments)) 268 | self.checkResult(m) 269 | return maxSegments.value 270 | 271 | def _lowLevelRunBlock(self, numPreTrigSamples, numPostTrigSamples, 272 | timebase, oversample, segmentIndex, callback, 273 | pParameter): 274 | # NOT: Oversample is NOT used! 275 | timeIndisposedMs = c_int32() 276 | m = self.lib.ps2000aRunBlock( 277 | c_int16(self.handle), c_uint32(numPreTrigSamples), 278 | c_uint32(numPostTrigSamples), c_uint32(timebase), 279 | c_int16(oversample), byref(timeIndisposedMs), 280 | c_uint32(segmentIndex), 281 | c_void_p(), c_void_p()) 282 | # According to the documentation, 'callback, pParameter' should work 283 | # instead of the last two c_void_p parameters. 284 | self.checkResult(m) 285 | return timeIndisposedMs.value 286 | 287 | def _lowLevelIsReady(self): 288 | ready = c_int16() 289 | m = self.lib.ps2000aIsReady(c_int16(self.handle), byref(ready)) 290 | self.checkResult(m) 291 | if ready.value: 292 | return True 293 | else: 294 | return False 295 | 296 | def _lowLevelGetTimebase(self, tb, noSamples, oversample, segmentIndex): 297 | """Return (timeIntervalSeconds, maxSamples).""" 298 | maxSamples = c_int32() 299 | intervalNanoSec = c_float() 300 | 301 | m = self.lib.ps2000aGetTimebase2( 302 | c_int16(self.handle), c_uint32(tb), c_uint32(noSamples), 303 | byref(intervalNanoSec), c_int16(oversample), byref(maxSamples), 304 | c_uint32(segmentIndex)) 305 | self.checkResult(m) 306 | # divide by 1e9 to return interval in seconds 307 | return (intervalNanoSec.value * 1e-9, maxSamples.value) 308 | 309 | def getTimeBaseNum(self, sampleTimeS): 310 | """Convert sample time in S to something to pass to API Call.""" 311 | clipped = np.clip(math.floor(self._timestep_to_timebase(sampleTimeS)), 312 | 0, np.iinfo(np.int32).max) 313 | 314 | return int(clipped) 315 | 316 | def getTimestepFromTimebase(self, timebase): 317 | """Convvert API timestep code to sampling interval. 318 | 319 | API timestep as an integer from 0-32, 320 | sampling interval in seconds. 321 | """ 322 | return self._timebase_to_timestep(timebase) 323 | 324 | def _lowLevelSetAWGSimpleDeltaPhase(self, waveform, deltaPhase, 325 | offsetVoltage, pkToPk, indexMode, 326 | shots, triggerType, triggerSource): 327 | """Waveform should be an array of shorts.""" 328 | waveformPtr = waveform.ctypes.data_as(POINTER(c_int16)) 329 | 330 | m = self.lib.ps2000aSetSigGenArbitrary( 331 | c_int16(self.handle), 332 | c_uint32(int(offsetVoltage * 1E6)), # offset voltage in microvolts 333 | c_uint32(int(pkToPk * 1E6)), # pkToPk in microvolts 334 | c_uint32(int(deltaPhase)), # startDeltaPhase 335 | c_uint32(int(deltaPhase)), # stopDeltaPhase 336 | c_uint32(0), # deltaPhaseIncrement 337 | c_uint32(0), # dwellCount 338 | waveformPtr, # arbitraryWaveform 339 | c_int32(len(waveform)), # arbitraryWaveformSize 340 | c_enum(0), # sweepType for deltaPhase 341 | c_enum(0), # operation (adding random noise and whatnot) 342 | c_enum(indexMode), # single, dual, quad 343 | c_uint32(shots), 344 | c_uint32(0), # sweeps 345 | c_uint32(triggerType), 346 | c_uint32(triggerSource), 347 | c_int16(0)) # extInThreshold 348 | self.checkResult(m) 349 | 350 | def _lowLevelSetDataBuffer(self, channel, data, downSampleMode, 351 | segmentIndex): 352 | """Set the buffer for the picoscope. 353 | 354 | Be sure to call _lowLevelClearDataBuffer 355 | when you are done with the data array 356 | or else subsequent calls to GetValue will still use the same array. 357 | """ 358 | dataPtr = data.ctypes.data_as(POINTER(c_int16)) 359 | numSamples = len(data) 360 | 361 | m = self.lib.ps2000aSetDataBuffer( 362 | c_int16(self.handle), c_enum(channel), dataPtr, 363 | c_int32(numSamples), c_uint32(segmentIndex), 364 | c_enum(downSampleMode)) 365 | self.checkResult(m) 366 | 367 | def _lowLevelSetMultipleDataBuffers(self, channel, data, downSampleMode): 368 | max_segments = self._lowLevelGetMaxSegments() 369 | if data.shape[0] < max_segments: 370 | raise ValueError( 371 | "data array has fewer rows" + 372 | " than current number of memory segments") 373 | if data.shape[1] < self.maxSamples: 374 | raise ValueError("data array has fewer columns than maxSamples") 375 | 376 | for i in range(max_segments): 377 | m = self._lowLevelSetDataBuffer(channel, data[i, :], 378 | downSampleMode, i) 379 | self.checkResult(m) 380 | 381 | def _lowLevelClearDataBuffer(self, channel, segmentIndex): 382 | """Clear the data in the picoscope.""" 383 | m = self.lib.ps2000aSetDataBuffer( 384 | c_int16(self.handle), c_enum(channel), 385 | c_void_p(), c_uint32(0), c_uint32(segmentIndex), c_enum(0)) 386 | self.checkResult(m) 387 | 388 | def _lowLevelGetValues(self, numSamples, startIndex, downSampleRatio, 389 | downSampleMode, segmentIndex): 390 | numSamplesReturned = c_uint32() 391 | numSamplesReturned.value = numSamples 392 | overflow = c_int16() 393 | m = self.lib.ps2000aGetValues( 394 | c_int16(self.handle), c_uint32(startIndex), 395 | byref(numSamplesReturned), c_uint32(downSampleRatio), 396 | c_enum(downSampleMode), c_uint32(segmentIndex), 397 | byref(overflow)) 398 | self.checkResult(m) 399 | return (numSamplesReturned.value, overflow.value) 400 | 401 | def _lowLevelGetValuesBulk(self, numSamples, fromSegment, toSegment, 402 | downSampleRatio, downSampleMode, overflow): 403 | m = self.lib.ps2000aGetValuesBulk( 404 | c_int16(self.handle), 405 | byref(c_int16(numSamples)), 406 | c_int16(fromSegment), 407 | c_int16(toSegment), 408 | c_int32(downSampleRatio), 409 | c_int16(downSampleMode), 410 | overflow.ctypes.data_as(POINTER(c_int16)) 411 | ) 412 | self.checkResult(m) 413 | return overflow, numSamples 414 | 415 | def _lowLevelGetTriggerTimeOffset(self, segmentIndex): 416 | timeUpper = c_uint32() 417 | timeLower = c_uint32() 418 | timeUnits = c_int16() 419 | m = self.lib.ps2000aGetTriggerTimeOffset( 420 | c_int16(self.handle), 421 | byref(timeUpper), 422 | byref(timeLower), 423 | byref(timeUnits), 424 | c_uint32(segmentIndex), 425 | ) 426 | self.checkResult(m) 427 | 428 | # timeUpper and timeLower are the upper 4 and lower 4 bytes of a 64-bit 429 | # (8-byte) integer which is scaled by timeUnits to get the precise 430 | # trigger location 431 | return (((timeUpper.value << 32) + timeLower.value) * 432 | self.TIME_UNITS[timeUnits.value]) 433 | 434 | def _lowLevelSetSigGenBuiltInSimple(self, offsetVoltage, pkToPk, waveType, 435 | frequency, shots, triggerType, 436 | triggerSource, stopFreq, increment, 437 | dwellTime, sweepType, numSweeps): 438 | # TODO, I just noticed that V2 exists 439 | # Maybe change to V2 in the future 440 | 441 | if stopFreq is None: 442 | stopFreq = frequency 443 | 444 | m = self.lib.ps2000aSetSigGenBuiltIn( 445 | c_int16(self.handle), 446 | c_int32(int(offsetVoltage * 1000000)), 447 | c_int32(int(pkToPk * 1000000)), 448 | c_int16(waveType), 449 | c_float(frequency), c_float(stopFreq), 450 | c_float(increment), c_float(dwellTime), 451 | c_enum(sweepType), c_enum(0), 452 | c_uint32(shots), c_uint32(numSweeps), 453 | c_enum(triggerType), c_enum(triggerSource), 454 | c_int16(0)) 455 | self.checkResult(m) 456 | 457 | def _lowLevelSigGenSoftwareControl(self, triggerType): 458 | m = self.lib.ps2000aSigGenSoftwareControl( 459 | c_int16(self.handle), c_enum(triggerType)) 460 | self.checkResult(m) 461 | -------------------------------------------------------------------------------- /picoscope/ps3000.py: -------------------------------------------------------------------------------- 1 | """ 2 | This is the low level driver file for a specific Picoscope. 3 | 4 | By this, I mean if parameters want to get passed as strings, they should be 5 | handled by PSBase 6 | All functions here should take things as close to integers as possible, the 7 | only exception here is for array parameters. Array parameters should be passed 8 | in a pythonic way through numpy since the PSBase class should not be aware of 9 | the specifics behind how the clib is called. 10 | 11 | The functions should not have any default values as these should be handled 12 | by PSBase. 13 | """ 14 | 15 | 16 | # pico-python is Copyright (c) 2013-2014 By: 17 | # Colin O'Flynn 18 | # Mark Harfouche 19 | # All rights reserved. 20 | # 21 | # Redistribution and use in source and binary forms, with or without 22 | # modification, are permitted provided that the following conditions are met: 23 | # 24 | # 1. Redistributions of source code must retain the above copyright notice, 25 | # this list of conditions and the following disclaimer. 26 | # 2. Redistributions in binary form must reproduce the above copyright notice, 27 | # this list of conditions and the following disclaimer in the documentation 28 | # and/or other materials provided with the distribution. 29 | # 30 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 31 | # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 32 | # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 33 | # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE 34 | # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 35 | # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 36 | # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 37 | # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 38 | # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 39 | # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 40 | # POSSIBILITY OF SUCH DAMAGE. 41 | 42 | from __future__ import division 43 | from __future__ import absolute_import 44 | from __future__ import print_function 45 | from __future__ import unicode_literals 46 | 47 | # import math 48 | 49 | import inspect 50 | 51 | # to load the proper dll 52 | import platform 53 | 54 | # Do not import or use ill definied data types 55 | # such as short int or long 56 | # use the values specified in the h file 57 | # float is always defined as 32 bits 58 | # double is defined as 64 bits 59 | from ctypes import byref, POINTER, create_string_buffer, c_float, \ 60 | c_int16, c_int32, c_uint32, c_void_p 61 | from ctypes import c_int32 as c_enum 62 | 63 | from picoscope.picobase import _PicoscopeBase 64 | 65 | 66 | class PS3000(_PicoscopeBase): 67 | """The following are low-level functions for the PS3000.""" 68 | 69 | LIBNAME = "ps3000" 70 | 71 | NUM_CHANNELS = 2 72 | CHANNELS = {"A": 0, "B": 1, "MaxChannels": 2} 73 | 74 | THRESHOLD_TYPE = {"Rising": 0, 75 | "Falling": 1} 76 | 77 | CHANNEL_RANGE = [ 78 | {"rangeV": 20E-3, "apivalue": 1, "rangeStr": "20 mV"}, 79 | {"rangeV": 50E-3, "apivalue": 2, "rangeStr": "50 mV"}, 80 | {"rangeV": 100E-3, "apivalue": 3, "rangeStr": "100 mV"}, 81 | {"rangeV": 200E-3, "apivalue": 4, "rangeStr": "200 mV"}, 82 | {"rangeV": 500E-3, "apivalue": 5, "rangeStr": "500 mV"}, 83 | {"rangeV": 1.0, "apivalue": 6, "rangeStr": "1 V"}, 84 | {"rangeV": 2.0, "apivalue": 7, "rangeStr": "2 V"}, 85 | {"rangeV": 5.0, "apivalue": 8, "rangeStr": "5 V"}, 86 | {"rangeV": 10.0, "apivalue": 9, "rangeStr": "10 V"}, 87 | {"rangeV": 20.0, "apivalue": 10, "rangeStr": "20 V"}, 88 | ] 89 | 90 | CHANNEL_COUPLINGS = {"DC": 1, "AC": 0} 91 | 92 | # has_sig_gen = True 93 | WAVE_TYPES = {"Sine": 0, "Square": 1, "Triangle": 2, 94 | "RampUp": 3, "RampDown": 4, "DCVoltage": 5} 95 | 96 | SWEEP_TYPES = {"Up": 0, "Down": 1, "UpDown": 2, "DownUp": 3} 97 | 98 | TIME_UNITS = {"FS": 0, "PS": 1, "NS": 2, "US": 3, "MS": 4, "S": 5} 99 | 100 | MAX_VALUE = 32767 101 | MIN_VALUE = -32767 102 | 103 | MAX_TIMEBASES = 19 104 | 105 | UNIT_INFO_TYPES = {"DriverVersion": 0x0, 106 | "USBVersion": 0x1, 107 | "HardwareVersion": 0x2, 108 | "VariantInfo": 0x3, 109 | "BatchAndSerial": 0x4, 110 | "CalDate": 0x5, 111 | "ErrorCode": 0x6, 112 | "KernelVersion": 0x7} 113 | 114 | channelBuffersPtr = [c_void_p(), c_void_p()] 115 | channelBuffersLen = [0, 0] 116 | 117 | # SIGGEN_TRIGGER_TYPES = {"Rising": 0, "Falling": 1, 118 | # "GateHigh": 2, "GateLow": 3} 119 | # SIGGEN_TRIGGER_SOURCES = {"None": 0, "ScopeTrig": 1, "AuxIn": 2, 120 | # "ExtIn": 3, "SoftTrig": 4, "TriggerRaw": 5} 121 | 122 | def __init__(self, serialNumber=None, connect=True): 123 | """Load DLL etc.""" 124 | if platform.system() == 'Linux': 125 | from ctypes import cdll 126 | self.lib = cdll.LoadLibrary("lib" + self.LIBNAME + ".so") 127 | elif platform.system() == 'Darwin': 128 | from picoscope.darwin_utils import LoadLibraryDarwin 129 | self.lib = LoadLibraryDarwin("lib" + self.LIBNAME + ".dylib") 130 | else: 131 | from ctypes import windll 132 | from ctypes.util import find_library 133 | self.lib = windll.LoadLibrary( 134 | find_library(str(self.LIBNAME + ".dll")) 135 | ) 136 | 137 | super(PS3000, self).__init__(serialNumber, connect) 138 | 139 | def _lowLevelOpenUnit(self, sn): 140 | if sn is not None: 141 | raise ValueError("PS3000 Doesn't Support Open by Serial Number") 142 | 143 | m = self.lib.ps3000_open_unit() 144 | 145 | if m < 0: 146 | raise IOError("Failed to Find PS3000 Unit. " + 147 | "Should you be using PS3000a driver?") 148 | 149 | self.handle = m 150 | self.suggested_time_units = self.TIME_UNITS["NS"] 151 | 152 | def _lowLevelCloseUnit(self): 153 | m = self.lib.ps3000_close_unit(c_int16(self.handle)) 154 | self.checkResult(m) 155 | 156 | def _lowLevelSetChannel(self, chNum, enabled, coupling, VRange, VOffset, 157 | BWLimited): 158 | m = self.lib.ps3000_set_channel( 159 | c_int16(self.handle), c_enum(chNum), 160 | c_int16(enabled), c_enum(coupling), 161 | c_enum(VRange)) 162 | self.checkResult(m) 163 | 164 | def _lowLevelStop(self): 165 | m = self.lib.ps3000_stop(c_int16(self.handle)) 166 | self.checkResult(m) 167 | 168 | def _lowLevelGetUnitInfo(self, info): 169 | s = create_string_buffer(256) 170 | 171 | m = self.lib.ps3000_get_unit_info( 172 | c_int16(self.handle), byref(s), c_int16(len(s)), c_enum(info)) 173 | self.checkResult(m) 174 | 175 | # should this bee ascii instead? 176 | # I think they are equivalent... 177 | return s.value.decode('utf-8') 178 | 179 | def _lowLevelFlashLed(self, times): 180 | m = self.lib.ps3000_flash_led(c_int16(self.handle)) 181 | self.checkResult(m) 182 | 183 | def _lowLevelSetSimpleTrigger(self, enabled, trigsrc, threshold_adc, 184 | direction, delay, timeout_ms): 185 | # TODO: Fix 'auto' which is where trigger occurs in block. 186 | # Delay is not used 187 | 188 | m = self.lib.ps3000_set_trigger( 189 | c_int16(self.handle), c_enum(trigsrc), c_int16(threshold_adc), 190 | c_enum(direction), c_int16(delay), c_int16(timeout_ms)) 191 | self.checkResult(m) 192 | 193 | def _lowLevelRunBlock(self, numPreTrigSamples, numPostTrigSamples, 194 | timebase, oversample, segmentIndex, callback, 195 | pParameter): 196 | # NOT: Oversample is NOT used! 197 | # Note: callback and pParameter are not available. 198 | 199 | # TODO: Fix 'delay' which is where trigger occurs in block 200 | if numPreTrigSamples > 0: 201 | raise ValueError("numPreTrigSamples isn't supported on PS3000") 202 | 203 | timeIndisposedMs = c_int32() 204 | m = self.lib.ps3000_run_block( 205 | c_int16(self.handle), c_uint32(numPostTrigSamples), 206 | c_uint32(timebase), c_int16(1), byref(timeIndisposedMs)) 207 | 208 | self.checkResult(m) 209 | return timeIndisposedMs.value 210 | 211 | def _lowLevelIsReady(self): 212 | ready = c_int16() 213 | ready = self.lib.ps3000_ready(c_int16(self.handle)) 214 | if ready > 0: 215 | return True 216 | elif ready == 0: 217 | return False 218 | else: 219 | raise IOError("ps3000_ready returned %d" % ready.value) 220 | 221 | def _lowLevelGetTimebase(self, tb, noSamples, oversample, segmentIndex): 222 | """Return (timeIntervalSeconds, maxSamples).""" 223 | maxSamples = c_int32() 224 | time_interval = c_int32() 225 | time_units = c_int16() 226 | 227 | m = self.lib.ps3000_get_timebase( 228 | c_int16(self.handle), c_int16(tb), c_uint32(noSamples), 229 | byref(time_interval), byref(time_units), c_int16(1), 230 | byref(maxSamples)) 231 | 232 | self.checkResult(m) 233 | 234 | self.suggested_time_units = time_units.value 235 | 236 | return (time_interval.value / 1.0E9, maxSamples.value) 237 | 238 | def getTimeBaseNum(self, sampleTimeS): 239 | """ps3000 doesn't seem to have published formula like other scopes.""" 240 | time_interval = c_int32() 241 | timebases = [None] * self.MAX_TIMEBASES 242 | 243 | # Convert to nS 244 | sampleTimenS = sampleTimeS * 1E9 245 | 246 | tb = 0 247 | while tb < self.MAX_TIMEBASES: 248 | rv = self.lib.ps3000_get_timebase( 249 | c_int16(self.handle), c_int16(tb), c_uint32(512), 250 | byref(time_interval), c_void_p(), c_int16(1), c_void_p()) 251 | if rv != 0: 252 | timebases[tb] = time_interval.value 253 | 254 | tb += 1 255 | 256 | # Figure out closest option 257 | besterror = 1E99 258 | bestindx = 0 259 | for indx, val in enumerate(timebases): 260 | if val is not None: 261 | error = sampleTimenS - val 262 | if abs(error) < besterror: 263 | besterror = abs(error) 264 | bestindx = indx 265 | 266 | return bestindx 267 | 268 | def getTimestepFromTimebase(self, timebase): 269 | """Return timestep from Timebase.""" 270 | time_interval = c_int32() 271 | m = self.lib.ps3000_get_timebase( 272 | c_int16(self.handle), c_int16(timebase), c_uint32(512), 273 | byref(time_interval), c_void_p(), c_int16(1), c_void_p()) 274 | self.checkResult(m) 275 | return (time_interval.value / 1.0E9) 276 | 277 | def _lowLevelSetDataBuffer(self, channel, data, downSampleMode, 278 | segmentIndex): 279 | dataPtr = data.ctypes.data_as(POINTER(c_int16)) 280 | numSamples = len(data) 281 | 282 | self.channelBuffersPtr[channel] = dataPtr 283 | self.channelBuffersLen[channel] = numSamples 284 | 285 | def _lowLevelClearDataBuffer(self, channel, segmentIndex): 286 | self.channelBuffersPtr[channel] = c_void_p() 287 | self.channelBuffersLen[channel] = 0 288 | 289 | def _lowLevelGetValues(self, numSamples, startIndex, downSampleRatio, 290 | downSampleMode, segmentIndex): 291 | # TODO: 292 | # Check overflow in channelBuffersLen against numSamples, 293 | # but need to not raise error if channelBuffersPtr is void 294 | 295 | overflow = c_int16() 296 | rv = self.lib.ps3000_get_values( 297 | c_int16(self.handle), 298 | self.channelBuffersPtr[0], 299 | self.channelBuffersPtr[1], 300 | c_void_p(), c_void_p(), 301 | byref(overflow), c_int32(numSamples)) 302 | 303 | self.checkResult(rv) 304 | return (rv, overflow.value) 305 | 306 | def _lowLevelSetSigGenBuiltInSimple(self, offsetVoltage, pkToPk, waveType, 307 | frequency, shots, triggerType, 308 | triggerSource, stopFreq, increment, 309 | dwellTime, sweepType, numSweeps): 310 | if stopFreq is None: 311 | stopFreq = frequency 312 | 313 | m = self.lib.ps3000_set_sig_gen_built_in( 314 | c_int16(self.handle), 315 | c_int32(int(offsetVoltage * 1000000)), 316 | c_int32(int(pkToPk * 1000000)), 317 | c_int16(waveType), 318 | c_float(frequency), c_float(stopFreq), 319 | c_float(increment), c_float(dwellTime), c_enum(sweepType), 320 | c_uint32(numSweeps)) 321 | self.checkResult(m) 322 | 323 | def checkResult(self, ec): 324 | """Check result of function calls, raise exception if not 0.""" 325 | # PS3000 differs from other drivers in that non-zero is good 326 | if ec == 0: 327 | raise IOError('Error calling %s' % (inspect.stack()[1][3])) 328 | 329 | return 0 330 | -------------------------------------------------------------------------------- /picoscope/ps3000a.py: -------------------------------------------------------------------------------- 1 | """ 2 | This is the low level driver file for a specific Picoscope. 3 | 4 | By this, I mean if parameters want to get passed as strings, they should be 5 | handled by PSBase 6 | All functions here should take things as close to integers as possible, the 7 | only exception here is for array parameters. Array parameters should be passed 8 | in a pythonic way through numpy since the PSBase class should not be aware of 9 | the specifics behind how the clib is called. 10 | 11 | The functions should not have any default values as these should be handled 12 | by PSBase. 13 | """ 14 | 15 | # pico-python is Copyright (c) 2013-2014 By: 16 | # Colin O'Flynn 17 | # Mark Harfouche 18 | # All rights reserved. 19 | # 20 | # Redistribution and use in source and binary forms, with or without 21 | # modification, are permitted provided that the following conditions are met: 22 | # 23 | # 1. Redistributions of source code must retain the above copyright notice, 24 | # this list of conditions and the following disclaimer. 25 | # 2. Redistributions in binary form must reproduce the above copyright notice, 26 | # this list of conditions and the following disclaimer in the documentation 27 | # and/or other materials provided with the distribution. 28 | # 29 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 30 | # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 31 | # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 32 | # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE 33 | # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 34 | # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 35 | # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 36 | # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 37 | # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 38 | # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 39 | # POSSIBILITY OF SUCH DAMAGE. 40 | 41 | 42 | from __future__ import division 43 | from __future__ import absolute_import 44 | from __future__ import print_function 45 | from __future__ import unicode_literals 46 | 47 | import math 48 | 49 | # to load the proper dll 50 | import platform 51 | 52 | # Do not import or use ill definied data types 53 | # such as short int or long 54 | # use the values specified in the h file 55 | # float is always defined as 32 bits 56 | # double is defined as 64 bits 57 | from ctypes import byref, POINTER, create_string_buffer, c_float, \ 58 | c_int16, c_int32, c_uint16, c_uint32, c_void_p 59 | from ctypes import c_int32 as c_enum 60 | 61 | from picoscope.picobase import _PicoscopeBase 62 | 63 | 64 | class PS3000a(_PicoscopeBase): 65 | """The following are low-level functions for the PS3000a.""" 66 | 67 | LIBNAME = "ps3000a" 68 | 69 | NUM_CHANNELS = 4 70 | CHANNELS = {"A": 0, "B": 1, "C": 2, "D": 3, 71 | "External": 4, "MaxChannels": 4, "TriggerAux": 5} 72 | 73 | ADC_RESOLUTIONS = {"8": 0, "12": 1, "14": 2, "15": 3, "16": 4} 74 | 75 | CHANNEL_RANGE = [{"rangeV": 10E-3, "apivalue": 0, "rangeStr": "10 mV"}, 76 | {"rangeV": 20E-3, "apivalue": 1, "rangeStr": "20 mV"}, 77 | {"rangeV": 50E-3, "apivalue": 2, "rangeStr": "50 mV"}, 78 | {"rangeV": 100E-3, "apivalue": 3, "rangeStr": "100 mV"}, 79 | {"rangeV": 200E-3, "apivalue": 4, "rangeStr": "200 mV"}, 80 | {"rangeV": 500E-3, "apivalue": 5, "rangeStr": "500 mV"}, 81 | {"rangeV": 1.0, "apivalue": 6, "rangeStr": "1 V"}, 82 | {"rangeV": 2.0, "apivalue": 7, "rangeStr": "2 V"}, 83 | {"rangeV": 5.0, "apivalue": 8, "rangeStr": "5 V"}, 84 | {"rangeV": 10.0, "apivalue": 9, "rangeStr": "10 V"}, 85 | {"rangeV": 20.0, "apivalue": 10, "rangeStr": "20 V"}, 86 | {"rangeV": 50.0, "apivalue": 11, "rangeStr": "50 V"}, 87 | ] 88 | 89 | CHANNEL_COUPLINGS = {"DC": 1, "AC": 0} 90 | 91 | # has_sig_gen = True 92 | WAVE_TYPES = {"Sine": 0, "Square": 1, "Triangle": 2, 93 | "RampUp": 3, "RampDown": 4, 94 | "Sinc": 5, "Gaussian": 6, "HalfSine": 7, "DCVoltage": 8, 95 | "WhiteNoise": 9} 96 | 97 | SWEEP_TYPES = {"Up": 0, "Down": 1, "UpDown": 2, "DownUp": 3} 98 | 99 | SIGGEN_TRIGGER_TYPES = {"Rising": 0, "Falling": 1, 100 | "GateHigh": 2, "GateLow": 3} 101 | SIGGEN_TRIGGER_SOURCES = {"None": 0, "ScopeTrig": 1, "AuxIn": 2, 102 | "ExtIn": 3, "SoftTrig": 4, "TriggerRaw": 5} 103 | 104 | # This is actually different depending on the AB/CD models 105 | # I wonder how we could detect the difference between the oscilloscopes 106 | # I believe we can obtain this information from the setInfo function 107 | # by readign the hardware version 108 | # for the PS6403B version, the hardware version is "1 1", 109 | # an other possibility is that the PS6403B shows up as 6403 when using 110 | # VARIANT_INFO and others show up as PS6403X where X = A,C or D 111 | 112 | AWGPhaseAccumulatorSize = 32 113 | AWGBufferAddressWidth = 14 114 | AWGMaxSamples = 2 ** AWGBufferAddressWidth 115 | 116 | AWGDACInterval = 5E-9 # in seconds 117 | AWGDACFrequency = 1 / AWGDACInterval 118 | 119 | # Note this is NOT what is written in the Programming guide as of version 120 | # 10_5_0_28 121 | # This issue was acknowledged in this thread 122 | # http://www.picotech.com/support/topic13217.html 123 | AWGMaxVal = 32767 124 | AWGMinVal = -32767 125 | 126 | AWG_INDEX_MODES = {"Single": 0, "Dual": 1, "Quad": 2} 127 | 128 | MAX_VALUE_8BIT = 32512 129 | MIN_VALUE_8BIT = -32512 130 | MAX_VALUE_OTHER = 32767 131 | MIN_VALUE_OTHER = -32767 132 | 133 | EXT_RANGE_VOLTS = 5 134 | 135 | def __init__(self, serialNumber=None, connect=True): 136 | """Load DLL etc.""" 137 | if platform.system() == 'Linux': 138 | from ctypes import cdll 139 | self.lib = cdll.LoadLibrary("lib" + self.LIBNAME + ".so") 140 | elif platform.system() == 'Darwin': 141 | from picoscope.darwin_utils import LoadLibraryDarwin 142 | self.lib = LoadLibraryDarwin("lib" + self.LIBNAME + ".dylib") 143 | else: 144 | from ctypes import windll 145 | from ctypes.util import find_library 146 | self.lib = windll.LoadLibrary( 147 | find_library(str(self.LIBNAME + ".dll")) 148 | ) 149 | 150 | self.resolution = self.ADC_RESOLUTIONS["8"] 151 | 152 | super(PS3000a, self).__init__(serialNumber, connect) 153 | 154 | def _lowLevelOpenUnit(self, serialNumber): 155 | c_handle = c_int16() 156 | if serialNumber is not None: 157 | serialNumberStr = create_string_buffer(bytes(serialNumber, 158 | encoding='utf-8')) 159 | else: 160 | serialNumberStr = None 161 | # Passing None is the same as passing NULL 162 | m = self.lib.ps3000aOpenUnit(byref(c_handle), serialNumberStr) 163 | self.handle = c_handle.value 164 | 165 | # copied over from ps5000a: 166 | # This will check if the power supply is not connected 167 | # and change the power supply accordingly 168 | # Personally (me = Mark), I don't like this 169 | # since the user should address this immediately, and we 170 | # shouldn't let this go as a soft error 171 | # but I think this should do for now 172 | if m == 0x11A: 173 | self.changePowerSource("PICO_POWER_SUPPLY_NOT_CONNECTED") 174 | else: 175 | self.checkResult(m) 176 | 177 | def _lowLevelCloseUnit(self): 178 | m = self.lib.ps3000aCloseUnit(c_int16(self.handle)) 179 | self.checkResult(m) 180 | 181 | def _lowLevelEnumerateUnits(self): 182 | count = c_int16(0) 183 | m = self.lib.ps3000aEnumerateUnits(byref(count), None, None) 184 | self.checkResult(m) 185 | # a serial number is rouhgly 8 characters 186 | # an extra character for the comma 187 | # and an extra one for the space after the comma? 188 | # the extra two also work for the null termination 189 | serialLth = c_int16(count.value * (8 + 2)) 190 | serials = create_string_buffer(serialLth.value + 1) 191 | 192 | m = self.lib.ps3000aEnumerateUnits(byref(count), serials, 193 | byref(serialLth)) 194 | self.checkResult(m) 195 | 196 | serialList = str(serials.value.decode('utf-8')).split(',') 197 | 198 | serialList = [x.strip() for x in serialList] 199 | 200 | return serialList 201 | 202 | def _lowLevelSetChannel(self, chNum, enabled, coupling, VRange, VOffset, 203 | BWLimited): 204 | m = self.lib.ps3000aSetChannel(c_int16(self.handle), c_enum(chNum), 205 | c_int16(enabled), c_enum(coupling), 206 | c_enum(VRange), c_float(VOffset)) 207 | self.checkResult(m) 208 | 209 | m = self.lib.ps3000aSetBandwidthFilter(c_int16(self.handle), 210 | c_enum(chNum), 211 | c_enum(BWLimited)) 212 | self.checkResult(m) 213 | 214 | def _lowLevelStop(self): 215 | m = self.lib.ps3000aStop(c_int16(self.handle)) 216 | self.checkResult(m) 217 | 218 | def _lowLevelGetUnitInfo(self, info): 219 | s = create_string_buffer(256) 220 | requiredSize = c_int16(0) 221 | 222 | m = self.lib.ps3000aGetUnitInfo(c_int16(self.handle), byref(s), 223 | c_int16(len(s)), byref(requiredSize), 224 | c_enum(info)) 225 | self.checkResult(m) 226 | if requiredSize.value > len(s): 227 | s = create_string_buffer(requiredSize.value + 1) 228 | m = self.lib.ps3000aGetUnitInfo(c_int16(self.handle), byref(s), 229 | c_int16(len(s)), 230 | byref(requiredSize), c_enum(info)) 231 | self.checkResult(m) 232 | 233 | # should this bee ascii instead? 234 | # I think they are equivalent... 235 | return s.value.decode('utf-8') 236 | 237 | def _lowLevelFlashLed(self, times): 238 | m = self.lib.ps3000aFlashLed(c_int16(self.handle), c_int16(times)) 239 | self.checkResult(m) 240 | 241 | def _lowLevelSetSimpleTrigger(self, enabled, trigsrc, threshold_adc, 242 | direction, delay, auto): 243 | m = self.lib.ps3000aSetSimpleTrigger( 244 | c_int16(self.handle), c_int16(enabled), 245 | c_enum(trigsrc), c_int16(threshold_adc), 246 | c_enum(direction), c_uint32(delay), c_int16(auto)) 247 | self.checkResult(m) 248 | 249 | def _lowLevelSetNoOfCaptures(self, numCaptures): 250 | m = self.lib.ps3000aSetNoOfCaptures(c_int16(self.handle), 251 | c_uint16(numCaptures)) 252 | self.checkResult(m) 253 | 254 | def _lowLevelMemorySegments(self, numSegments): 255 | maxSamples = c_int32() 256 | m = self.lib.ps3000aMemorySegments(c_int16(self.handle), 257 | c_uint16(numSegments), 258 | byref(maxSamples)) 259 | self.checkResult(m) 260 | return maxSamples.value 261 | 262 | def _lowLevelGetMaxSegments(self): 263 | maxSegments = c_int16() 264 | m = self.lib.ps3000aGetMaxSegments(c_int16(self.handle), 265 | byref(maxSegments)) 266 | self.checkResult(m) 267 | return maxSegments.value 268 | 269 | def _lowLevelRunBlock(self, numPreTrigSamples, numPostTrigSamples, 270 | timebase, oversample, segmentIndex, callback, 271 | pParameter): 272 | # NOT: Oversample is NOT used! 273 | timeIndisposedMs = c_int32() 274 | m = self.lib.ps3000aRunBlock( 275 | c_int16(self.handle), c_uint32(numPreTrigSamples), 276 | c_uint32(numPostTrigSamples), c_uint32(timebase), 277 | c_int16(oversample), byref(timeIndisposedMs), 278 | c_uint32(segmentIndex), 279 | c_void_p(), c_void_p()) 280 | # According to the documentation, 'callback, pParameter' should work 281 | # instead of the last two c_void_p parameters. 282 | self.checkResult(m) 283 | return timeIndisposedMs.value 284 | 285 | def _lowLevelIsReady(self): 286 | ready = c_int16() 287 | m = self.lib.ps3000aIsReady(c_int16(self.handle), byref(ready)) 288 | self.checkResult(m) 289 | if ready.value: 290 | return True 291 | else: 292 | return False 293 | 294 | def _lowlevelPingUnit(self): 295 | m = self.lib.ps3000aPingUnit(c_int16(self.handle)) 296 | return m 297 | 298 | def _lowLevelGetTimebase(self, tb, noSamples, oversample, segmentIndex): 299 | """Return (timeIntervalSeconds, maxSamples).""" 300 | maxSamples = c_int32() 301 | intervalNanoSec = c_float() 302 | 303 | m = self.lib.ps3000aGetTimebase2(c_int16(self.handle), c_uint32(tb), 304 | c_uint32(noSamples), 305 | byref(intervalNanoSec), 306 | c_int16(oversample), 307 | byref(maxSamples), 308 | c_uint32(segmentIndex)) 309 | self.checkResult(m) 310 | # divide by 1e9 to return interval in seconds 311 | return (intervalNanoSec.value * 1e-9, maxSamples.value) 312 | 313 | def getTimeBaseNum(self, sampleTimeS): 314 | """Convert sample time in S to something to pass to API Call.""" 315 | maxSampleTime = (((2 ** 32 - 1) - 2) / 125000000) 316 | if sampleTimeS < 8.0E-9: 317 | st = math.floor(math.log(sampleTimeS * 1E9, 2)) 318 | st = max(st, 0) 319 | else: 320 | if sampleTimeS > maxSampleTime: 321 | sampleTimeS = maxSampleTime 322 | st = math.floor((sampleTimeS * 125000000) + 2) 323 | 324 | # is this cast needed? 325 | st = int(st) 326 | return st 327 | 328 | def getTimestepFromTimebase(self, timebase): 329 | """Take API timestep code and returns the sampling period. 330 | 331 | API timestep is an integer from 0-32 332 | """ 333 | if timebase < 3: 334 | dt = 2. ** timebase / 1.0E9 335 | else: 336 | dt = (timebase - 2.0) / 125000000. 337 | return dt 338 | 339 | def _lowLevelSetAWGSimpleDeltaPhase(self, waveform, deltaPhase, 340 | offsetVoltage, pkToPk, indexMode, 341 | shots, triggerType, triggerSource): 342 | """Waveform should be an array of shorts.""" 343 | waveformPtr = waveform.ctypes.data_as(POINTER(c_int16)) 344 | 345 | m = self.lib.ps3000aSetSigGenArbitrary( 346 | c_int16(self.handle), 347 | c_uint32(int(offsetVoltage * 1E6)), # offset voltage in microvolts 348 | c_uint32(int(pkToPk * 1E6)), # pkToPk in microvolts 349 | c_uint32(int(deltaPhase)), # startDeltaPhase 350 | c_uint32(int(deltaPhase)), # stopDeltaPhase 351 | c_uint32(0), # deltaPhaseIncrement 352 | c_uint32(0), # dwellCount 353 | waveformPtr, # arbitraryWaveform 354 | c_int32(len(waveform)), # arbitraryWaveformSize 355 | c_enum(0), # sweepType for deltaPhase 356 | c_enum(0), # operation (adding random noise and whatnot) 357 | c_enum(indexMode), # single, dual, quad 358 | c_uint32(shots), 359 | c_uint32(0), # sweeps 360 | c_uint32(triggerType), 361 | c_uint32(triggerSource), 362 | c_int16(0)) # extInThreshold 363 | self.checkResult(m) 364 | 365 | def _lowLevelSetDataBuffer(self, channel, data, downSampleMode, 366 | segmentIndex): 367 | """Set the data buffer. 368 | 369 | Be sure to call _lowLevelClearDataBuffer 370 | when you are done with the data array 371 | or else subsequent calls to GetValue will still use the same array. 372 | """ 373 | dataPtr = data.ctypes.data_as(POINTER(c_int16)) 374 | numSamples = len(data) 375 | 376 | m = self.lib.ps3000aSetDataBuffer( 377 | c_int16(self.handle), c_enum(channel), 378 | dataPtr, c_int32(numSamples), 379 | c_uint32(segmentIndex), 380 | c_enum(downSampleMode)) 381 | self.checkResult(m) 382 | 383 | def _lowLevelSetDataBufferBulk(self, channel, data, segmentIndex, 384 | downSampleMode): 385 | """Set the bulk data buffer. 386 | 387 | In ps3000a, ps3000aSetDataBuffer combines the functionality of 388 | psX000YSetDataBuffer and psX000YSetDataBufferBulk. Since the rapid 389 | block functions in picoscope.py call the Bulk version, a delegator is 390 | needed. Note that the order of segmentIndex and downSampleMode is 391 | reversed. 392 | """ 393 | self._lowLevelSetDataBuffer(channel, data, downSampleMode, 394 | segmentIndex) 395 | 396 | def _lowLevelSetMultipleDataBuffers(self, channel, data, downSampleMode): 397 | max_segments = self._lowLevelGetMaxSegments() 398 | if data.shape[0] < max_segments: 399 | raise ValueError("data array has fewer rows than current number " + 400 | "of memory segments") 401 | if data.shape[1] < self.maxSamples: 402 | raise ValueError("data array has fewer columns than maxSamples") 403 | 404 | for i in range(max_segments): 405 | m = self._lowLevelSetDataBuffer(channel, data[i, :], 406 | downSampleMode, i) 407 | self.checkResult(m) 408 | 409 | def _lowLevelClearDataBuffer(self, channel, segmentIndex): 410 | m = self.lib.ps3000aSetDataBuffer(c_int16(self.handle), 411 | c_enum(channel), 412 | c_void_p(), c_uint32(0), 413 | c_uint32(segmentIndex), 414 | c_enum(0)) 415 | self.checkResult(m) 416 | 417 | def _lowLevelGetValues(self, numSamples, startIndex, downSampleRatio, 418 | downSampleMode, segmentIndex): 419 | numSamplesReturned = c_uint32() 420 | numSamplesReturned.value = numSamples 421 | overflow = c_int16() 422 | m = self.lib.ps3000aGetValues( 423 | c_int16(self.handle), c_uint32(startIndex), 424 | byref(numSamplesReturned), c_uint32(downSampleRatio), 425 | c_enum(downSampleMode), c_uint32(segmentIndex), 426 | byref(overflow)) 427 | self.checkResult(m) 428 | return (numSamplesReturned.value, overflow.value) 429 | 430 | def _lowLevelGetValuesBulk(self, numSamples, fromSegment, toSegment, 431 | downSampleRatio, downSampleMode, overflow): 432 | m = self.lib.ps3000aGetValuesBulk( 433 | c_int16(self.handle), 434 | byref(c_uint32(numSamples)), 435 | c_uint32(fromSegment), 436 | c_uint32(toSegment), 437 | c_uint32(downSampleRatio), 438 | c_int16(downSampleMode), 439 | overflow.ctypes.data_as(POINTER(c_int16)) 440 | ) 441 | self.checkResult(m) 442 | return overflow, numSamples 443 | 444 | def _lowLevelSetSigGenBuiltInSimple(self, offsetVoltage, pkToPk, waveType, 445 | frequency, shots, triggerType, 446 | triggerSource, stopFreq, increment, 447 | dwellTime, sweepType, numSweeps): 448 | # TODO, I just noticed that V2 exists 449 | # Maybe change to V2 in the future 450 | if stopFreq is None: 451 | stopFreq = frequency 452 | 453 | m = self.lib.ps3000aSetSigGenBuiltIn( 454 | c_int16(self.handle), 455 | c_int32(int(offsetVoltage * 1000000)), 456 | c_int32(int(pkToPk * 1000000)), 457 | c_int16(waveType), 458 | c_float(frequency), c_float(stopFreq), 459 | c_float(increment), c_float(dwellTime), 460 | c_enum(sweepType), c_enum(0), 461 | c_uint32(shots), c_uint32(numSweeps), 462 | c_enum(triggerType), c_enum(triggerSource), 463 | c_int16(0)) 464 | self.checkResult(m) 465 | 466 | def _lowLevelChangePowerSource(self, powerstate): 467 | m = self.lib.ps3000aChangePowerSource( 468 | c_int16(self.handle), 469 | c_enum(powerstate)) 470 | self.checkResult(m) 471 | 472 | def _lowLevelSigGenSoftwareControl(self, triggerType): 473 | m = self.lib.ps3000aSigGenSoftwareControl( 474 | c_int16(self.handle), 475 | c_enum(triggerType)) 476 | self.checkResult(m) 477 | -------------------------------------------------------------------------------- /picoscope/ps5000.py: -------------------------------------------------------------------------------- 1 | # This is the instrument-specific file for the PS5000 series of instruments. 2 | # 3 | # pico-python is Copyright (c) 2013-2014 By: 4 | # Colin O'Flynn 5 | # Mark Harfouche 6 | # All rights reserved. 7 | # 8 | # Redistribution and use in source and binary forms, with or without 9 | # modification, are permitted provided that the following conditions are met: 10 | # 11 | # 1. Redistributions of source code must retain the above copyright notice, 12 | # this list of conditions and the following disclaimer. 13 | # 2. Redistributions in binary form must reproduce the above copyright notice, 14 | # this list of conditions and the following disclaimer in the documentation 15 | # and/or other materials provided with the distribution. 16 | # 17 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 18 | # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 19 | # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 20 | # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE 21 | # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 22 | # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 23 | # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 24 | # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 25 | # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 26 | # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 27 | # POSSIBILITY OF SUCH DAMAGE. 28 | 29 | """ 30 | This is the low level driver file for a specific Picoscope. 31 | 32 | By this, I mean if parameters want to get passed as strings, they should be 33 | handled by PSBase 34 | All functions here should take things as close to integers as possible, the 35 | only exception here is for array parameters. Array parameters should be passed 36 | in a pythonic way through numpy since the PSBase class should not be aware of 37 | the specifics behind how the clib is called. 38 | 39 | The functions should not have any default values as these should be handled 40 | by PSBase. 41 | 42 | """ 43 | 44 | # 3.0 compatibility 45 | # see http://docs.python.org/2/library/__future__.html 46 | from __future__ import division 47 | from __future__ import absolute_import 48 | from __future__ import print_function 49 | from __future__ import unicode_literals 50 | 51 | import math 52 | 53 | # to load the proper dll 54 | import platform 55 | 56 | # Do not import or use ill definied data types 57 | # such as short int or long 58 | # use the values specified in the h file 59 | # float is always defined as 32 bits 60 | # double is defined as 64 bits 61 | from ctypes import byref, POINTER, create_string_buffer, c_float, \ 62 | c_int16, c_int32, c_uint32, c_uint64, c_void_p 63 | from ctypes import c_int32 as c_enum 64 | 65 | from picoscope.picobase import _PicoscopeBase 66 | 67 | 68 | class PS5000(_PicoscopeBase): 69 | """The following are low-level functions for the PS5000.""" 70 | 71 | LIBNAME = "ps5000" 72 | 73 | MAX_VALUE = 32521 74 | MIN_VALUE = -32521 75 | 76 | # EXT/AUX seems to have an imput impedence of 50 ohm (PS6403B) 77 | EXT_MAX_VALUE = 32767 78 | EXT_MIN_VALUE = -32767 79 | EXT_RANGE_VOLTS = 1 80 | 81 | # The 10V and 20V ranges are only allowed in high impedence modes 82 | CHANNEL_RANGE = [ 83 | {"rangeV": 10E-3, "apivalue": 1, "rangeStr": "10 mV"}, 84 | {"rangeV": 20E-3, "apivalue": 2, "rangeStr": "20 mV"}, 85 | {"rangeV": 50E-3, "apivalue": 3, "rangeStr": "50 mV"}, 86 | {"rangeV": 100E-3, "apivalue": 4, "rangeStr": "100 mV"}, 87 | {"rangeV": 200E-3, "apivalue": 5, "rangeStr": "200 mV"}, 88 | {"rangeV": 1.0, "apivalue": 6, "rangeStr": "1 V"}, 89 | {"rangeV": 2.0, "apivalue": 7, "rangeStr": "2 V"}, 90 | {"rangeV": 5.0, "apivalue": 8, "rangeStr": "5 V"}, 91 | {"rangeV": 10.0, "apivalue": 9, "rangeStr": "10 V"}, 92 | {"rangeV": 20.0, "apivalue": 10, "rangeStr": "20 V"}, 93 | {"rangeV": 50.0, "apivalue": 11, "rangeStr": "50 V"}] 94 | 95 | NUM_CHANNELS = 4 96 | CHANNELS = {"A": 0, "B": 1, "C": 2, "D": 3, 97 | "External": 4, "MaxChannels": 4, "TriggerAux": 5} 98 | 99 | CHANNEL_COUPLINGS = {"DC": 1, "AC": 0} 100 | 101 | WAVE_TYPES = {"Sine": 0, "Square": 1, "Triangle": 2, 102 | "RampUp": 3, "RampDown": 4, 103 | "Sinc": 5, "Gaussian": 6, "HalfSine": 7, "DCVoltage": 8, 104 | "WhiteNoise": 9} 105 | 106 | SWEEP_TYPES = {"Up": 0, "Down": 1, "UpDown": 2, "DownUp": 3} 107 | 108 | SIGGEN_TRIGGER_TYPES = {"Rising": 0, "Falling": 1, 109 | "GateHigh": 2, "GateLow": 3} 110 | SIGGEN_TRIGGER_SOURCES = {"None": 0, "ScopeTrig": 1, "AuxIn": 2, 111 | "ExtIn": 3, "SoftTrig": 4, "TriggerRaw": 5} 112 | 113 | # This is actually different depending on the AB/CD models 114 | # I wonder how we could detect the difference between the oscilloscopes 115 | # I believe we can obtain this information from the setInfo function 116 | # by readign the hardware version 117 | # for the PS6403B version, the hardware version is "1 1", 118 | # an other possibility is that the PS6403B shows up as 6403 when using 119 | # VARIANT_INFO and others show up as PS6403X where X = A,C or D 120 | 121 | AWGPhaseAccumulatorSize = 32 122 | AWGBufferAddressWidth = 14 123 | AWGMaxSamples = 2 ** AWGBufferAddressWidth 124 | 125 | AWGDACInterval = 5E-9 # in seconds 126 | AWGDACFrequency = 1 / AWGDACInterval 127 | 128 | # Note this is NOT what is written in the Programming guide as of version 129 | # 10_5_0_28 130 | # This issue was acknowledged in this thread 131 | # http://www.picotech.com/support/topic13217.html 132 | AWGMaxVal = 0x0FFF 133 | AWGMinVal = 0x0000 134 | 135 | AWG_INDEX_MODES = {"Single": 0, "Dual": 1, "Quad": 2} 136 | 137 | def __init__(self, serialNumber=None, connect=True): 138 | """Load DLLs.""" 139 | if platform.system() == 'Linux': 140 | from ctypes import cdll 141 | # ok I don't know what is wrong with my installer, 142 | # but I need to include .so.2 143 | self.lib = cdll.LoadLibrary("lib" + self.LIBNAME + ".so.2") 144 | elif platform.system() == 'Darwin': 145 | from picoscope.darwin_utils import LoadLibraryDarwin 146 | self.lib = LoadLibraryDarwin("lib" + self.LIBNAME + ".dylib") 147 | else: 148 | from ctypes import windll 149 | from ctypes.util import find_library 150 | self.lib = windll.LoadLibrary( 151 | find_library(str(self.LIBNAME + ".dll")) 152 | ) 153 | 154 | super(PS5000, self).__init__(serialNumber, connect) 155 | 156 | def _lowLevelOpenUnit(self, serialNumber): 157 | c_handle = c_int16() 158 | if serialNumber is not None: 159 | serialNumberStr = create_string_buffer(bytes(serialNumber, 160 | encoding='utf-8')) 161 | else: 162 | serialNumberStr = None 163 | # Passing None is the same as passing NULL 164 | m = self.lib.ps5000OpenUnit(byref(c_handle), serialNumberStr) 165 | self.checkResult(m) 166 | self.handle = c_handle.value 167 | 168 | def _lowLevelOpenUnitAsync(self, serialNumber): 169 | c_status = c_int16() 170 | if serialNumber is not None: 171 | serialNumberStr = create_string_buffer(bytes(serialNumber, 172 | encoding='utf-8')) 173 | else: 174 | serialNumberStr = None 175 | # Passing None is the same as passing NULL 176 | m = self.lib.ps5000OpenUnitAsync(byref(c_status), serialNumberStr) 177 | self.checkResult(m) 178 | 179 | return c_status.value 180 | 181 | def _lowLevelOpenUnitProgress(self): 182 | complete = c_int16() 183 | progressPercent = c_int16() 184 | handle = c_int16() 185 | 186 | m = self.lib.ps5000OpenUnitProgress(byref(handle), 187 | byref(progressPercent), 188 | byref(complete)) 189 | self.checkResult(m) 190 | 191 | if complete.value != 0: 192 | self.handle = handle.value 193 | 194 | # if we only wanted to return one value, we could do somethign like 195 | # progressPercent = progressPercent * (1 - 0.1 * complete) 196 | return (progressPercent.value, complete.value) 197 | 198 | def _lowLevelCloseUnit(self): 199 | m = self.lib.ps5000CloseUnit(c_int16(self.handle)) 200 | self.checkResult(m) 201 | 202 | def _lowLevelEnumerateUnits(self): 203 | count = c_int16(0) 204 | m = self.lib.ps5000EnumerateUnits(byref(count), None, None) 205 | self.checkResult(m) 206 | # a serial number is rouhgly 8 characters 207 | # an extra character for the comma 208 | # and an extra one for the space after the comma? 209 | # the extra two also work for the null termination 210 | serialLth = c_int16(count.value * (8 + 2)) 211 | serials = create_string_buffer(serialLth.value + 1) 212 | 213 | m = self.lib.ps5000EnumerateUnits(byref(count), serials, 214 | byref(serialLth)) 215 | self.checkResult(m) 216 | 217 | serialList = str(serials.value.decode('utf-8')).split(',') 218 | 219 | serialList = [x.strip() for x in serialList] 220 | 221 | return serialList 222 | 223 | def _lowLevelSetChannel(self, chNum, enabled, coupling, VRange, VOffset, 224 | BWLimited): 225 | m = self.lib.ps5000SetChannel(c_int16(self.handle), c_enum(chNum), 226 | c_int16(enabled), c_enum(coupling), 227 | c_enum(VRange), c_float(VOffset), 228 | c_enum(BWLimited)) # 2 for PS6404 229 | self.checkResult(m) 230 | 231 | def _lowLevelStop(self): 232 | m = self.lib.ps5000Stop(c_int16(self.handle)) 233 | self.checkResult(m) 234 | 235 | def _lowLevelGetUnitInfo(self, info): 236 | s = create_string_buffer(256) 237 | requiredSize = c_int16(0) 238 | 239 | m = self.lib.ps6000GetUnitInfo(c_int16(self.handle), byref(s), 240 | c_int16(len(s)), byref(requiredSize), 241 | c_enum(info)) 242 | self.checkResult(m) 243 | if requiredSize.value > len(s): 244 | s = create_string_buffer(requiredSize.value + 1) 245 | m = self.lib.ps5000GetUnitInfo(c_int16(self.handle), byref(s), 246 | c_int16(len(s)), 247 | byref(requiredSize), c_enum(info)) 248 | self.checkResult(m) 249 | 250 | # should this bee ascii instead? 251 | # I think they are equivalent... 252 | return s.value.decode('utf-8') 253 | 254 | def _lowLevelFlashLed(self, times): 255 | m = self.lib.ps5000FlashLed(c_int16(self.handle), c_int16(times)) 256 | self.checkResult(m) 257 | 258 | def _lowLevelSetSimpleTrigger(self, enabled, trigsrc, threshold_adc, 259 | direction, delay, timeout_ms): 260 | m = self.lib.ps5000SetSimpleTrigger( 261 | c_int16(self.handle), c_int16(enabled), 262 | c_enum(trigsrc), c_int16(threshold_adc), 263 | c_enum(direction), c_uint32(delay), c_int16(timeout_ms)) 264 | self.checkResult(m) 265 | 266 | def _lowLevelRunBlock(self, numPreTrigSamples, numPostTrigSamples, 267 | timebase, oversample, segmentIndex, callback, 268 | pParameter): 269 | timeIndisposedMs = c_int32() 270 | m = self.lib.ps5000RunBlock( 271 | c_int16(self.handle), c_uint32(numPreTrigSamples), 272 | c_uint32(numPostTrigSamples), c_uint32(timebase), 273 | c_int16(oversample), byref(timeIndisposedMs), 274 | c_uint32(segmentIndex), c_void_p(), c_void_p()) 275 | # According to the documentation, 'callback, pParameter' should work 276 | # instead of the last two c_void_p parameters. 277 | # However to avoid the potential for serious crashes, we decided to 278 | # not include them in this function call. 279 | self.checkResult(m) 280 | return timeIndisposedMs.value 281 | 282 | def _lowLevelIsReady(self): 283 | ready = c_int16() 284 | m = self.lib.ps5000IsReady(c_int16(self.handle), byref(ready)) 285 | self.checkResult(m) 286 | if ready.value: 287 | return True 288 | else: 289 | return False 290 | 291 | def _lowLevelGetTimebase(self, tb, noSamples, oversample, segmentIndex): 292 | """Return (timeIntervalSeconds, maxSamples).""" 293 | maxSamples = c_int32() 294 | sampleRate = c_float() 295 | 296 | m = self.lib.ps5000GetTimebase2(c_int16(self.handle), c_uint32(tb), 297 | c_uint32(noSamples), byref(sampleRate), 298 | c_int16(oversample), byref(maxSamples), 299 | c_uint32(segmentIndex)) 300 | self.checkResult(m) 301 | 302 | return (sampleRate.value / 1.0E9, maxSamples.value) 303 | 304 | def getTimeBaseNum(self, sampleTimeS): 305 | """Return sample time in seconds to timebase as int for API calls.""" 306 | maxSampleTime = (((2 ** 32 - 1) - 2) / 125000000) 307 | 308 | if sampleTimeS < 8E-9: 309 | timebase = math.floor(math.log(sampleTimeS * 1E9, 2)) 310 | timebase = max(timebase, 0) 311 | else: 312 | # Otherwise in range 2^32-1 313 | if sampleTimeS > maxSampleTime: 314 | sampleTimeS = maxSampleTime 315 | 316 | timebase = math.floor((sampleTimeS * 125000000) + 2) 317 | 318 | # is this cast needed? 319 | timebase = int(timebase) 320 | return timebase 321 | 322 | def getTimestepFromTimebase(self, timebase): 323 | """Return timebase to sampletime as seconds.""" 324 | if timebase < 3: 325 | dt = 2. ** timebase / 1E9 326 | else: 327 | dt = (timebase - 2) / 125000000. 328 | return dt 329 | 330 | def _lowLevelSetDataBuffer(self, channel, data, downSampleMode, 331 | segmentIndex): 332 | """Set the data buffer. 333 | 334 | Be sure to call _lowLevelClearDataBuffer 335 | when you are done with the data array 336 | or else subsequent calls to GetValue will still use the same array. 337 | 338 | segmentIndex is unused, but required by other versions of the API 339 | (eg PS5000a) 340 | """ 341 | dataPtr = data.ctypes.data_as(POINTER(c_int16)) 342 | numSamples = len(data) 343 | 344 | m = self.lib.ps5000SetDataBuffer(c_int16(self.handle), c_enum(channel), 345 | dataPtr, c_uint32(numSamples), 346 | c_enum(downSampleMode)) 347 | self.checkResult(m) 348 | 349 | def _lowLevelClearDataBuffer(self, channel, segmentIndex): 350 | m = self.lib.ps5000SetDataBuffer(c_int16(self.handle), c_enum(channel), 351 | c_void_p(), c_uint32(0), c_enum(0)) 352 | self.checkResult(m) 353 | 354 | def _lowLevelGetValues(self, numSamples, startIndex, downSampleRatio, 355 | downSampleMode, segmentIndex): 356 | numSamplesReturned = c_uint32() 357 | numSamplesReturned.value = numSamples 358 | overflow = c_int16() 359 | m = self.lib.ps5000GetValues( 360 | c_int16(self.handle), c_uint32(startIndex), 361 | byref(numSamplesReturned), c_uint32(downSampleRatio), 362 | c_enum(downSampleMode), c_uint32(segmentIndex), 363 | byref(overflow)) 364 | self.checkResult(m) 365 | return (numSamplesReturned.value, overflow.value) 366 | 367 | #################################################################### 368 | # Untested functions below # 369 | # # 370 | #################################################################### 371 | def _lowLevelSetAWGSimpleDeltaPhase(self, waveform, deltaPhase, 372 | offsetVoltage, pkToPk, indexMode, 373 | shots, triggerType, triggerSource): 374 | """Waveform should be an array of shorts.""" 375 | waveformPtr = waveform.ctypes.data_as(POINTER(c_int16)) 376 | 377 | m = self.lib.ps5000SetSigGenArbitrary( 378 | c_int16(self.handle), 379 | c_uint32(int(offsetVoltage * 1E6)), # offset voltage in microvolts 380 | c_uint32(int(pkToPk * 1E6)), # pkToPk in microvolts 381 | c_uint32(int(deltaPhase)), # startDeltaPhase 382 | c_uint32(int(deltaPhase)), # stopDeltaPhase 383 | c_uint32(0), # deltaPhaseIncrement 384 | c_uint32(0), # dwellCount 385 | waveformPtr, # arbitraryWaveform 386 | c_int32(len(waveform)), # arbitraryWaveformSize 387 | c_enum(0), # sweepType for deltaPhase 388 | c_enum(0), # operation (adding random noise and whatnot) 389 | c_enum(indexMode), # single, dual, quad 390 | c_uint32(shots), 391 | c_uint32(0), # sweeps 392 | c_uint32(triggerType), 393 | c_uint32(triggerSource), 394 | c_int16(0)) # extInThreshold 395 | self.checkResult(m) 396 | 397 | def _lowLevelSetSigGenBuiltInSimple(self, offsetVoltage, pkToPk, waveType, 398 | frequency, shots, triggerType, 399 | triggerSource, stopFreq, increment, 400 | dwellTime, sweepType, numSweeps): 401 | # TODO, I just noticed that V2 exists 402 | # Maybe change to V2 in the future 403 | 404 | if stopFreq is None: 405 | stopFreq = frequency 406 | 407 | m = self.lib.ps5000SetSigGenBuiltIn( 408 | c_int16(self.handle), 409 | c_int32(int(offsetVoltage * 1000000)), 410 | c_int32(int(pkToPk * 1000000)), 411 | c_int16(waveType), 412 | c_float(frequency), c_float(stopFreq), 413 | c_float(increment), c_float(dwellTime), 414 | c_enum(sweepType), c_enum(0), 415 | c_uint32(shots), c_uint32(numSweeps), 416 | c_enum(triggerType), c_enum(triggerSource), 417 | c_int16(0)) 418 | self.checkResult(m) 419 | 420 | def _lowLevelGetAnalogueOffset(self, range, coupling): 421 | # TODO, populate properties with this function 422 | maximumVoltage = c_float() 423 | minimumVoltage = c_float() 424 | 425 | m = self.lib.ps5000GetAnalogueOffset( 426 | c_int16(self.handle), c_enum(range), c_enum(coupling), 427 | byref(maximumVoltage), byref(minimumVoltage)) 428 | self.checkResult(m) 429 | 430 | return (maximumVoltage.value, minimumVoltage.value) 431 | 432 | def _lowLevelGetMaxDownSampleRatio(self, noOfUnaggregatedSamples, 433 | downSampleRatioMode, segmentIndex): 434 | maxDownSampleRatio = c_uint32() 435 | 436 | m = self.lib.ps5000GetMaxDownSampleRatio( 437 | c_int16(self.handle), c_uint32(noOfUnaggregatedSamples), 438 | byref(maxDownSampleRatio), 439 | c_enum(downSampleRatioMode), c_uint32(segmentIndex)) 440 | self.checkResult(m) 441 | 442 | return maxDownSampleRatio.value 443 | 444 | def _lowLevelGetNoOfCaptures(self): 445 | nCaptures = c_uint32() 446 | 447 | m = self.lib.ps5000GetNoOfCaptures(c_int16(self.handle), 448 | byref(nCaptures)) 449 | self.checkResult(m) 450 | 451 | return nCaptures.value 452 | 453 | def _lowLevelGetTriggerTimeOffset(self, segmentIndex): 454 | time = c_uint64() 455 | timeUnits = c_enum() 456 | 457 | m = self.lib.ps5000GetTriggerTimeOffset64( 458 | c_int16(self.handle), byref(time), 459 | byref(timeUnits), c_uint32(segmentIndex)) 460 | self.checkResult(m) 461 | 462 | if timeUnits.value == 0: # PS5000_FS 463 | return time.value * 1E-15 464 | elif timeUnits.value == 1: # PS5000_PS 465 | return time.value * 1E-12 466 | elif timeUnits.value == 2: # PS5000_NS 467 | return time.value * 1E-9 468 | elif timeUnits.value == 3: # PS5000_US 469 | return time.value * 1E-6 470 | elif timeUnits.value == 4: # PS5000_MS 471 | return time.value * 1E-3 472 | elif timeUnits.value == 5: # PS5000_S 473 | return time.value * 1E0 474 | else: 475 | raise TypeError("Unknown timeUnits %d" % timeUnits.value) 476 | 477 | def _lowLevelMemorySegments(self, nSegments): 478 | nMaxSamples = c_uint32() 479 | 480 | m = self.lib.ps5000MemorySegments(c_int16(self.handle), 481 | c_uint32(nSegments), 482 | byref(nMaxSamples)) 483 | self.checkResult(m) 484 | 485 | return nMaxSamples.value 486 | 487 | def _lowLevelSetDataBuffers(self, channel, bufferMax, bufferMin, 488 | downSampleRatioMode): 489 | bufferMaxPtr = bufferMax.ctypes.data_as(POINTER(c_int16)) 490 | bufferMinPtr = bufferMin.ctypes.data_as(POINTER(c_int16)) 491 | bufferLth = len(bufferMax) 492 | 493 | m = self.lib.ps5000SetDataBuffers(c_int16(self.handle), 494 | c_enum(channel), 495 | bufferMaxPtr, bufferMinPtr, 496 | c_uint32(bufferLth), 497 | c_enum(downSampleRatioMode)) 498 | self.checkResult(m) 499 | 500 | def _lowLevelClearDataBuffers(self, channel): 501 | m = self.lib.ps5000SetDataBuffers( 502 | c_int16(self.handle), c_enum(channel), 503 | c_void_p(), c_void_p(), c_uint32(0), c_enum(0)) 504 | self.checkResult(m) 505 | 506 | # Bulk values. 507 | # These would be nice, but the user would have to provide us 508 | # with an array. 509 | # we would have to make sure that it is contiguous amonts other things 510 | def _lowLevelGetValuesBulk(self, 511 | numSamples, fromSegmentIndex, toSegmentIndex, 512 | downSampleRatio, downSampleRatioMode, 513 | overflow): 514 | noOfSamples = c_uint32(numSamples) 515 | 516 | m = self.lib.ps5000GetValuesBulk( 517 | c_int16(self.handle), 518 | byref(noOfSamples), 519 | c_uint32(fromSegmentIndex), c_uint32(toSegmentIndex), 520 | c_uint32(downSampleRatio), c_enum(downSampleRatioMode), 521 | overflow.ctypes.data_as(POINTER(c_int16)) 522 | ) 523 | self.checkResult(m) 524 | return noOfSamples.value 525 | 526 | def _lowLevelSetDataBufferBulk(self, channel, buffer, waveform, 527 | downSampleRatioMode): 528 | bufferPtr = buffer.ctypes.data_as(POINTER(c_int16)) 529 | bufferLth = len(buffer) 530 | 531 | m = self.lib.ps5000SetDataBufferBulk( 532 | c_int16(self.handle), 533 | c_enum(channel), bufferPtr, c_uint32(bufferLth), 534 | c_uint32(waveform), c_enum(downSampleRatioMode)) 535 | self.checkResult(m) 536 | 537 | def _lowLevelSetDataBuffersBulk(self, channel, bufferMax, bufferMin, 538 | waveform, downSampleRatioMode): 539 | bufferMaxPtr = bufferMax.ctypes.data_as(POINTER(c_int16)) 540 | bufferMinPtr = bufferMin.ctypes.data_as(POINTER(c_int16)) 541 | 542 | bufferLth = len(bufferMax) 543 | 544 | m = self.lib.ps5000SetDataBuffersBulk( 545 | c_int16(self.handle), c_enum(channel), 546 | bufferMaxPtr, bufferMinPtr, c_uint32(bufferLth), 547 | c_uint32(waveform), c_enum(downSampleRatioMode)) 548 | self.checkResult(m) 549 | 550 | def _lowLevelSetNoOfCaptures(self, nCaptures): 551 | m = self.lib.ps5000SetNoOfCaptures(c_int16(self.handle), 552 | c_uint32(nCaptures)) 553 | self.checkResult(m) 554 | 555 | # ETS Functions 556 | def _lowLevelSetEts(): 557 | pass 558 | 559 | def _lowLevelSetEtsTimeBuffer(): 560 | pass 561 | 562 | def _lowLevelSetEtsTimeBuffers(): 563 | pass 564 | 565 | def _lowLevelSetExternalClock(): 566 | pass 567 | 568 | # Complicated triggering 569 | # need to understand structs for this one to work 570 | def _lowLevelIsTriggerOrPulseWidthQualifierEnabled(): 571 | pass 572 | 573 | def _lowLevelGetValuesTriggerTimeOffsetBulk(): 574 | pass 575 | 576 | def _lowLevelSetTriggerChannelConditions(): 577 | pass 578 | 579 | def _lowLevelSetTriggerChannelDirections(): 580 | pass 581 | 582 | def _lowLevelSetTriggerChannelProperties(): 583 | pass 584 | 585 | def _lowLevelSetPulseWidthQualifier(): 586 | pass 587 | 588 | def _lowLevelSetTriggerDelay(): 589 | pass 590 | 591 | # Async functions 592 | # would be nice, but we would have to learn to implement callbacks 593 | def _lowLevelGetValuesAsync(): 594 | pass 595 | 596 | def _lowLevelGetValuesBulkAsync(): 597 | pass 598 | 599 | # overlapped functions 600 | def _lowLevelGetValuesOverlapped(): 601 | pass 602 | 603 | def _lowLevelGetValuesOverlappedBulk(): 604 | pass 605 | 606 | # Streaming related functions 607 | def _lowLevelGetStreamingLatestValues(): 608 | pass 609 | 610 | def _lowLevelNoOfStreamingValues(self): 611 | noOfValues = c_uint32() 612 | 613 | m = self.lib.ps5000NoOfStreamingValues(c_int16(self.handle), 614 | byref(noOfValues)) 615 | self.checkResult(m) 616 | 617 | return noOfValues.value 618 | 619 | def _lowLevelRunStreaming(): 620 | pass 621 | 622 | def _lowLevelStreamingReady(): 623 | pass 624 | -------------------------------------------------------------------------------- /picoscope/ps5000a.py: -------------------------------------------------------------------------------- 1 | # This is the instrument-specific file for the PS5000 series of instruments. 2 | # 3 | # pico-python is Copyright (c) 2013-2014 By: 4 | # Colin O'Flynn 5 | # Mark Harfouche 6 | # All rights reserved. 7 | # 8 | # Redistribution and use in source and binary forms, with or without 9 | # modification, are permitted provided that the following conditions are met: 10 | # 11 | # 1. Redistributions of source code must retain the above copyright notice, 12 | # this list of conditions and the following disclaimer. 13 | # 2. Redistributions in binary form must reproduce the above copyright notice, 14 | # this list of conditions and the following disclaimer in the documentation 15 | # and/or other materials provided with the distribution. 16 | # 17 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 18 | # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 19 | # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 20 | # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE 21 | # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 22 | # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 23 | # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 24 | # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 25 | # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 26 | # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 27 | # POSSIBILITY OF SUCH DAMAGE. 28 | 29 | """ 30 | This is the low level driver file for a specific Picoscope. 31 | 32 | By this, I mean if parameters want to get passed as strings, they should be 33 | handled by PSBase 34 | All functions here should take things as close to integers as possible, the 35 | only exception here is for array parameters. Array parameters should be passed 36 | in a pythonic way through numpy since the PSBase class should not be aware of 37 | the specifics behind how the clib is called. 38 | 39 | The functions should not have any default values as these should be handled 40 | by PSBase. 41 | """ 42 | 43 | from __future__ import division 44 | from __future__ import absolute_import 45 | from __future__ import print_function 46 | from __future__ import unicode_literals 47 | 48 | import math 49 | 50 | # to load the proper dll 51 | import platform 52 | 53 | # Do not import or use ill definied data types 54 | # such as short int or long 55 | # use the values specified in the h file 56 | # float is always defined as 32 bits 57 | # double is defined as 64 bits 58 | from ctypes import byref, POINTER, create_string_buffer, c_float, \ 59 | c_int16, c_int32, c_uint32, c_void_p, c_int64, CFUNCTYPE 60 | from ctypes import c_int32 as c_enum 61 | 62 | from picoscope.picobase import _PicoscopeBase 63 | 64 | 65 | # Decorators for callback functions. PICO_STATUS is uint32_t. 66 | def blockReady(function): 67 | """typedef void (*ps5000aBlockReady) 68 | ( 69 | int16_t handle, 70 | PICO_STATUS status, 71 | void * pParameter 72 | ) 73 | """ 74 | if function is None: 75 | return None 76 | 77 | callback = CFUNCTYPE(c_void_p, c_int16, c_uint32, c_void_p) 78 | return callback(function) 79 | 80 | 81 | class PS5000a(_PicoscopeBase): 82 | """The following are low-level functions for the PS5000.""" 83 | 84 | LIBNAME = "ps5000a" 85 | 86 | NUM_CHANNELS = 4 87 | CHANNELS = {"A": 0, "B": 1, "C": 2, "D": 3, 88 | "External": 4, "MaxChannels": 4, "TriggerAux": 5} 89 | 90 | ADC_RESOLUTIONS = {"8": 0, "12": 1, "14": 2, "15": 3, "16": 4} 91 | 92 | CHANNEL_RANGE = [{"rangeV": 10E-3, "apivalue": 0, "rangeStr": "10 mV"}, 93 | {"rangeV": 20E-3, "apivalue": 1, "rangeStr": "20 mV"}, 94 | {"rangeV": 50E-3, "apivalue": 2, "rangeStr": "50 mV"}, 95 | {"rangeV": 100E-3, "apivalue": 3, "rangeStr": "100 mV"}, 96 | {"rangeV": 200E-3, "apivalue": 4, "rangeStr": "200 mV"}, 97 | {"rangeV": 500E-3, "apivalue": 5, "rangeStr": "500 mV"}, 98 | {"rangeV": 1.0, "apivalue": 6, "rangeStr": "1 V"}, 99 | {"rangeV": 2.0, "apivalue": 7, "rangeStr": "2 V"}, 100 | {"rangeV": 5.0, "apivalue": 8, "rangeStr": "5 V"}, 101 | {"rangeV": 10.0, "apivalue": 9, "rangeStr": "10 V"}, 102 | {"rangeV": 20.0, "apivalue": 10, "rangeStr": "20 V"}, 103 | {"rangeV": 50.0, "apivalue": 11, "rangeStr": "50 V"}, 104 | ] 105 | 106 | CHANNEL_COUPLINGS = {"DC": 1, "AC": 0} 107 | 108 | # has_sig_gen = True 109 | WAVE_TYPES = {"Sine": 0, "Square": 1, "Triangle": 2, 110 | "RampUp": 3, "RampDown": 4, 111 | "Sinc": 5, "Gaussian": 6, "HalfSine": 7, "DCVoltage": 8, 112 | "WhiteNoise": 9} 113 | 114 | SWEEP_TYPES = {"Up": 0, "Down": 1, "UpDown": 2, "DownUp": 3} 115 | 116 | SIGGEN_TRIGGER_TYPES = {"Rising": 0, "Falling": 1, 117 | "GateHigh": 2, "GateLow": 3} 118 | SIGGEN_TRIGGER_SOURCES = {"None": 0, "ScopeTrig": 1, "AuxIn": 2, 119 | "ExtIn": 3, "SoftTrig": 4, "TriggerRaw": 5} 120 | 121 | # This is actually different depending on the AB/CD models 122 | # I wonder how we could detect the difference between the oscilloscopes 123 | # I believe we can obtain this information from the setInfo function 124 | # by readign the hardware version 125 | # for the PS6403B version, the hardware version is "1 1", 126 | # an other possibility is that the PS6403B shows up as 6403 when using 127 | # VARIANT_INFO and others show up as PS6403X where X = A,C or D 128 | 129 | AWGPhaseAccumulatorSize = 32 130 | 131 | AWGDACInterval = 5E-9 # in seconds 132 | AWGDACFrequency = 1 / AWGDACInterval 133 | 134 | AWG_INDEX_MODES = {"Single": 0, "Dual": 1, "Quad": 2} 135 | 136 | MAX_VALUE_8BIT = 32512 137 | MIN_VALUE_8BIT = -32512 138 | MAX_VALUE_OTHER = 32767 139 | MIN_VALUE_OTHER = -32767 140 | 141 | EXT_RANGE_VOLTS = 5 142 | 143 | def __init__(self, serialNumber=None, connect=True): 144 | """Load DLL etc.""" 145 | if platform.system() == 'Linux': 146 | from ctypes import cdll 147 | self.lib = cdll.LoadLibrary("lib" + self.LIBNAME + ".so") 148 | elif platform.system() == 'Darwin': 149 | from picoscope.darwin_utils import LoadLibraryDarwin 150 | self.lib = LoadLibraryDarwin("lib" + self.LIBNAME + ".dylib") 151 | else: 152 | from ctypes import windll 153 | from ctypes.util import find_library 154 | self.lib = windll.LoadLibrary( 155 | find_library(str(self.LIBNAME + ".dll")) 156 | ) 157 | 158 | self.resolution = self.ADC_RESOLUTIONS["8"] 159 | 160 | super(PS5000a, self).__init__(serialNumber, connect) 161 | 162 | def _lowLevelOpenUnit(self, serialNumber): 163 | c_handle = c_int16() 164 | if serialNumber is not None: 165 | serialNumberStr = create_string_buffer(bytes(serialNumber, 166 | encoding='utf-8')) 167 | else: 168 | serialNumberStr = None 169 | # Passing None is the same as passing NULL 170 | m = self.lib.ps5000aOpenUnit(byref(c_handle), serialNumberStr, 171 | self.resolution) 172 | self.handle = c_handle.value 173 | 174 | # This will check if the power supply is not connected 175 | # and change the power supply accordingly 176 | # Personally (me = Mark), I don't like this 177 | # since the user should address this immediately, and we 178 | # shouldn't let this go as a soft error 179 | # but I think this should do for now 180 | # PICO_POWER_SUPPLY_NOT_CONNECTED or PICO_USB3_0_DEVICE_NON_USB3_0_PORT 181 | if m == 0x11A or m == 0x11E: 182 | self.changePowerSource(m) 183 | else: 184 | self.checkResult(m) 185 | 186 | # B models have different AWG buffer sizes 187 | # 5242B, 5442B: 2**14 188 | # 5243B, 5443B: 2**15 189 | # 5444B, 5244B: 3 * 2**14 190 | # Model 5444B identifies itself properly in VariantInfo, I will assume 191 | # the others do as well. 192 | 193 | self.model = self.getUnitInfo('VariantInfo') 194 | # print("Checking variant, found: " + str(self.model)) 195 | if self.model in ('5244B', '5444B'): 196 | self.AWGBufferAddressWidth = math.log(3 * 2**14, 2) 197 | self.AWGMaxVal = 32767 198 | self.AWGMinVal = -32768 199 | self.AWGMaxSamples = 49152 200 | elif self.model in ('5243B', '5443B', '5243D', '5443D'): 201 | self.AWGBufferAddressWidth = 15 202 | self.AWGMaxVal = 32767 203 | self.AWGMinVal = -32768 204 | self.AWGMaxSamples = 2**self.AWGBufferAddressWidth 205 | else: 206 | # This is what the previous PS5000a used for all scopes. 207 | # I am leaving it the same, although I think the AWGMaxVal and 208 | # AWGMinVal issue was fixed and should be -32768 to 32767 for all 209 | # 5000 models 210 | self.AWGBufferAddressWidth = 14 211 | # Note this is NOT what is written in the Programming guide as of 212 | # version # 10_5_0_28 213 | # This issue was acknowledged in this thread 214 | # http://www.picotech.com/support/topic13217.html 215 | self.AWGMaxVal = 0x0FFF 216 | self.AWGMinVal = 0x0000 217 | self.AWGMaxSamples = 2**self.AWGBufferAddressWidth 218 | 219 | def _lowLevelCloseUnit(self): 220 | m = self.lib.ps5000aCloseUnit(c_int16(self.handle)) 221 | self.checkResult(m) 222 | 223 | def _lowLevelSetChannel(self, chNum, enabled, coupling, VRange, VOffset, 224 | bandwidth): 225 | m = self.lib.ps5000aSetChannel(c_int16(self.handle), c_enum(chNum), 226 | c_int16(enabled), c_enum(coupling), 227 | c_enum(VRange), c_float(VOffset)) 228 | self.checkResult(m) 229 | 230 | # The error this might through are 231 | # INVALID_HANDLE 232 | # INVALID_CHANNEL 233 | # INVALID_BANDWIDTH 234 | # Invalid bandwidth is the only case that could go wrong. 235 | # The others would be thrown above (assuming no race condition: 236 | # i.e. unplugging the scope in between this call. 237 | # I decided to keep the logic below to avoid a possible error 238 | # picobase/SetChannel should be changed to the following 239 | # Set the channel 240 | # save the new channel settings 241 | # check if ps5000a 242 | # change the bandwidth separately 243 | # changing the bandwidth would be it's own function (implemented below) 244 | if bandwidth: 245 | m = self.lib.ps5000aSetBandwidthFilter(c_int16(self.handle), 246 | c_enum(chNum), c_enum(1)) 247 | else: 248 | m = self.lib.ps5000aSetBandwidthFilter(c_int16(self.handle), 249 | c_enum(chNum), c_enum(0)) 250 | self.checkResult(m) 251 | 252 | def _lowLevelSetBandwidthFilter(self, channel, bandwidth): 253 | m = self.lib.ps5000aSetBandwidthFilter(c_int16(self.handle), 254 | c_enum(channel), 255 | c_enum(bandwidth)) 256 | self.checkResult(m) 257 | 258 | def _lowLevelStop(self): 259 | m = self.lib.ps5000aStop(c_int16(self.handle)) 260 | self.checkResult(m) 261 | 262 | def _lowLevelGetUnitInfo(self, info): 263 | s = create_string_buffer(256) 264 | requiredSize = c_int16(0) 265 | 266 | m = self.lib.ps5000aGetUnitInfo(c_int16(self.handle), byref(s), 267 | c_int16(len(s)), byref(requiredSize), 268 | c_enum(info)) 269 | self.checkResult(m) 270 | if requiredSize.value > len(s): 271 | s = create_string_buffer(requiredSize.value + 1) 272 | m = self.lib.ps5000aGetUnitInfo(c_int16(self.handle), byref(s), 273 | c_int16(len(s)), 274 | byref(requiredSize), c_enum(info)) 275 | self.checkResult(m) 276 | 277 | # should this bee ascii instead? 278 | # I think they are equivalent... 279 | return s.value.decode('utf-8') 280 | 281 | def _lowLevelFlashLed(self, times): 282 | m = self.lib.ps5000aFlashLed(c_int16(self.handle), c_int16(times)) 283 | self.checkResult(m) 284 | 285 | def _lowLevelSetSimpleTrigger(self, enabled, trigsrc, threshold_adc, 286 | direction, delay, timeout_ms): 287 | m = self.lib.ps5000aSetSimpleTrigger( 288 | c_int16(self.handle), c_int16(enabled), 289 | c_enum(trigsrc), c_int16(threshold_adc), 290 | c_enum(direction), c_uint32(delay), c_int16(timeout_ms)) 291 | self.checkResult(m) 292 | 293 | def _lowLevelRunBlock(self, numPreTrigSamples, numPostTrigSamples, 294 | timebase, oversample, segmentIndex, callback, 295 | pParameter): 296 | # Hold a reference to the callback so that the Python 297 | # function pointer doesn't get free'd. 298 | self._c_runBlock_callback = blockReady(callback) 299 | timeIndisposedMs = c_int32() 300 | m = self.lib.ps5000aRunBlock( 301 | c_int16(self.handle), c_uint32(numPreTrigSamples), 302 | c_uint32(numPostTrigSamples), c_uint32(timebase), 303 | byref(timeIndisposedMs), c_uint32(segmentIndex), 304 | self._c_runBlock_callback, c_void_p()) 305 | self.checkResult(m) 306 | return timeIndisposedMs.value 307 | 308 | def _lowLevelIsReady(self): 309 | ready = c_int16() 310 | m = self.lib.ps5000aIsReady(c_int16(self.handle), byref(ready)) 311 | self.checkResult(m) 312 | if ready.value: 313 | return True 314 | else: 315 | return False 316 | 317 | def _lowLevelPingUnit(self): 318 | m = self.lib.ps5000aPingUnit(c_int16(self.handle)) 319 | return m 320 | 321 | def _lowLevelGetTimebase(self, tb, noSamples, oversample, segmentIndex): 322 | """Return (timeIntervalSeconds, maxSamples).""" 323 | maxSamples = c_int32() 324 | sampleRate = c_float() 325 | 326 | m = self.lib.ps5000aGetTimebase2(c_int16(self.handle), c_uint32(tb), 327 | c_uint32(noSamples), 328 | byref(sampleRate), 329 | byref(maxSamples), 330 | c_uint32(segmentIndex)) 331 | self.checkResult(m) 332 | 333 | return (sampleRate.value / 1.0E9, maxSamples.value) 334 | 335 | def getTimeBaseNum(self, sampleTimeS): 336 | """Convert sample time in S to something to pass to API Call.""" 337 | if self.resolution == self.ADC_RESOLUTIONS["8"]: 338 | maxSampleTime = (((2 ** 32 - 1) - 2) / 125000000) 339 | if sampleTimeS < 8.0E-9: 340 | st = math.floor(math.log(sampleTimeS * 1E9, 2)) 341 | st = max(st, 0) 342 | else: 343 | if sampleTimeS > maxSampleTime: 344 | sampleTimeS = maxSampleTime 345 | st = math.floor((sampleTimeS * 125000000) + 2) 346 | 347 | elif self.resolution == self.ADC_RESOLUTIONS["12"]: 348 | maxSampleTime = (((2 ** 32 - 1) - 3) / 62500000) 349 | if sampleTimeS < 16.0E-9: 350 | st = math.floor(math.log(sampleTimeS * 5E8, 2)) + 1 351 | st = max(st, 1) 352 | else: 353 | if sampleTimeS > maxSampleTime: 354 | sampleTimeS = maxSampleTime 355 | st = math.floor((sampleTimeS * 62500000) + 3) 356 | 357 | elif (self.resolution == self.ADC_RESOLUTIONS["14"]) or ( 358 | self.resolution == self.ADC_RESOLUTIONS["15"]): 359 | maxSampleTime = (((2 ** 32 - 1) - 2) / 125000000) 360 | if sampleTimeS > maxSampleTime: 361 | sampleTimeS = maxSampleTime 362 | st = math.floor((sampleTimeS * 125000000) + 2) 363 | st = max(st, 3) 364 | 365 | elif self.resolution == self.ADC_RESOLUTIONS["16"]: 366 | maxSampleTime = (((2 ** 32 - 1) - 3) / 62500000) 367 | if sampleTimeS > maxSampleTime: 368 | sampleTimeS = maxSampleTime 369 | st = math.floor((sampleTimeS * 62500000) + 3) 370 | st = max(st, 3) 371 | 372 | else: 373 | raise ValueError("Invalid Resolution for Device?") 374 | 375 | # is this cast needed? 376 | st = int(st) 377 | return st 378 | 379 | def getTimestepFromTimebase(self, timebase): 380 | """Return Timestep from timebase.""" 381 | if self.resolution == self.ADC_RESOLUTIONS["8"]: 382 | if timebase < 3: 383 | dt = 2. ** timebase / 1.0E9 384 | else: 385 | dt = (timebase - 2.0) / 125000000. 386 | elif self.resolution == self.ADC_RESOLUTIONS["12"]: 387 | if timebase < 4: 388 | dt = 2. ** (timebase - 1) / 5.0E8 389 | else: 390 | dt = (timebase - 3.0) / 62500000. 391 | elif (self.resolution == self.ADC_RESOLUTIONS["14"]) or ( 392 | self.resolution == self.ADC_RESOLUTIONS["15"]): 393 | dt = (timebase - 2.0) / 125000000. 394 | elif self.resolution == self.ADC_RESOLUTIONS["16"]: 395 | dt = (timebase - 3.0) / 62500000. 396 | return dt 397 | 398 | def _lowLevelSetAWGSimpleDeltaPhase(self, waveform, deltaPhase, 399 | offsetVoltage, pkToPk, indexMode, 400 | shots, triggerType, triggerSource): 401 | """Waveform should be an array of shorts.""" 402 | waveformPtr = waveform.ctypes.data_as(POINTER(c_int16)) 403 | 404 | m = self.lib.ps5000aSetSigGenArbitrary( 405 | c_int16(self.handle), 406 | c_uint32(int(offsetVoltage * 1E6)), # offset voltage in microvolts 407 | c_uint32(int(pkToPk * 1E6)), # pkToPk in microvolts 408 | c_uint32(int(deltaPhase)), # startDeltaPhase 409 | c_uint32(int(deltaPhase)), # stopDeltaPhase 410 | c_uint32(0), # deltaPhaseIncrement 411 | c_uint32(0), # dwellCount 412 | waveformPtr, # arbitraryWaveform 413 | c_int32(len(waveform)), # arbitraryWaveformSize 414 | c_enum(0), # sweepType for deltaPhase 415 | c_enum(0), # operation (adding random noise and whatnot) 416 | c_enum(indexMode), # single, dual, quad 417 | c_uint32(shots), 418 | c_uint32(0), # sweeps 419 | c_uint32(triggerType), 420 | c_uint32(triggerSource), 421 | c_int16(0)) # extInThreshold 422 | self.checkResult(m) 423 | 424 | def _lowLevelSetDataBuffer(self, channel, data, downSampleMode, 425 | segmentIndex): 426 | """Set the data buffer. 427 | 428 | Be sure to call _lowLevelClearDataBuffer 429 | when you are done with the data array 430 | or else subsequent calls to GetValue will still use the same array. 431 | """ 432 | dataPtr = data.ctypes.data_as(POINTER(c_int16)) 433 | numSamples = len(data) 434 | 435 | m = self.lib.ps5000aSetDataBuffer(c_int16(self.handle), 436 | c_enum(channel), 437 | dataPtr, c_int32(numSamples), 438 | c_uint32(segmentIndex), 439 | c_enum(downSampleMode)) 440 | self.checkResult(m) 441 | 442 | def _lowLevelSetDataBufferBulk(self, channel, data, segmentIndex, 443 | downSampleMode): 444 | """Just calls setDataBuffer with argument order changed. 445 | 446 | For compatibility with current picobase.py. 447 | """ 448 | self._lowLevelSetDataBuffer(channel, 449 | data, 450 | downSampleMode, 451 | segmentIndex) 452 | 453 | def _lowLevelClearDataBuffer(self, channel, segmentIndex): 454 | m = self.lib.ps5000aSetDataBuffer(c_int16(self.handle), 455 | c_enum(channel), 456 | c_void_p(), c_uint32(0), 457 | c_uint32(segmentIndex), 458 | c_enum(0)) 459 | self.checkResult(m) 460 | 461 | def _lowLevelGetValues(self, numSamples, startIndex, downSampleRatio, 462 | downSampleMode, segmentIndex): 463 | numSamplesReturned = c_uint32() 464 | numSamplesReturned.value = numSamples 465 | overflow = c_int16() 466 | m = self.lib.ps5000aGetValues( 467 | c_int16(self.handle), c_uint32(startIndex), 468 | byref(numSamplesReturned), c_uint32(downSampleRatio), 469 | c_enum(downSampleMode), c_uint32(segmentIndex), 470 | byref(overflow)) 471 | self.checkResult(m) 472 | return (numSamplesReturned.value, overflow.value) 473 | 474 | def _lowLevelSetSigGenBuiltInSimple(self, offsetVoltage, pkToPk, waveType, 475 | frequency, shots, triggerType, 476 | triggerSource, stopFreq, increment, 477 | dwellTime, sweepType, numSweeps): 478 | # TODO, I just noticed that V2 exists 479 | # Maybe change to V2 in the future 480 | 481 | if stopFreq is None: 482 | stopFreq = frequency 483 | 484 | m = self.lib.ps5000aSetSigGenBuiltIn( 485 | c_int16(self.handle), 486 | c_int32(int(offsetVoltage * 1000000)), 487 | c_int32(int(pkToPk * 1000000)), 488 | c_int16(waveType), 489 | c_float(frequency), c_float(stopFreq), 490 | c_float(increment), c_float(dwellTime), 491 | c_enum(sweepType), c_enum(0), 492 | c_uint32(shots), c_uint32(numSweeps), 493 | c_enum(triggerType), c_enum(triggerSource), 494 | c_int16(0)) 495 | self.checkResult(m) 496 | 497 | def _lowLevelSigGenSoftwareControl(self, state): 498 | m = self.lib.ps5000aSigGenSoftwareControl( 499 | c_int16(self.handle), 500 | c_int16(state)) 501 | self.checkResult(m) 502 | 503 | def _lowLevelSetDeviceResolution(self, resolution): 504 | self.resolution = resolution 505 | m = self.lib.ps5000aSetDeviceResolution( 506 | c_int16(self.handle), 507 | c_enum(resolution)) 508 | self.checkResult(m) 509 | 510 | def _lowLevelChangePowerSource(self, powerstate): 511 | m = self.lib.ps5000aChangePowerSource( 512 | c_int16(self.handle), 513 | c_enum(powerstate)) 514 | self.checkResult(m) 515 | 516 | # Morgan's additions 517 | def _lowLevelGetValuesBulk(self, numSamples, fromSegment, toSegment, 518 | downSampleRatio, downSampleMode, overflow): 519 | """Copy data from several memory segments at once.""" 520 | overflowPoint = overflow.ctypes.data_as(POINTER(c_int16)) 521 | m = self.lib.ps5000aGetValuesBulk( 522 | c_int16(self.handle), 523 | byref(c_int32(numSamples)), 524 | c_int32(fromSegment), 525 | c_int32(toSegment), 526 | c_int32(downSampleRatio), 527 | c_enum(downSampleMode), 528 | overflowPoint 529 | ) 530 | self.checkResult(m) 531 | 532 | def _lowLevelSetNoOfCaptures(self, numCaptures): 533 | m = self.lib.ps5000aSetNoOfCaptures( 534 | c_int16(self.handle), 535 | c_uint32(numCaptures)) 536 | self.checkResult(m) 537 | 538 | def _lowLevelMemorySegments(self, numSegments): 539 | maxSamples = c_int32() 540 | m = self.lib.ps5000aMemorySegments( 541 | c_int16(self.handle), c_uint32(numSegments), byref(maxSamples)) 542 | self.checkResult(m) 543 | return maxSamples.value 544 | 545 | def _lowLevelGetValuesTriggerTimeOffsetBulk(self, fromSegment, toSegment): 546 | """Supposedly gets the trigger times for a bunch of segments at once. 547 | 548 | For block mode. 549 | Can't get it to work yet, however. 550 | """ 551 | import numpy as np 552 | 553 | nSegments = toSegment - fromSegment + 1 554 | # time = c_int64() 555 | times = np.ascontiguousarray( 556 | np.zeros(nSegments, dtype=np.int64) 557 | ) 558 | timeUnits = np.ascontiguousarray( 559 | np.zeros(nSegments, dtype=np.int32) 560 | ) 561 | 562 | m = self.lib.ps5000aGetValuesTriggerTimeOffsetBulk64( 563 | c_int16(self.handle), 564 | times.ctypes.data_as(POINTER(c_int64)), 565 | timeUnits.ctypes.data_as(POINTER(c_enum)), 566 | c_uint32(fromSegment), 567 | c_uint32(toSegment) 568 | ) 569 | self.checkResult(m) 570 | # timeUnits=np.array([self.TIME_UNITS[tu] for tu in timeUnits]) 571 | return times, timeUnits 572 | -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [metadata] 2 | description-file = README.md 3 | 4 | [versioneer] 5 | VCS = git 6 | style = pep440 7 | versionfile_source = picoscope/_version.py 8 | versionfile_build = picoscope/_version.py 9 | tag_prefix = 10 | parentdir_prefix = picoscope 11 | 12 | 13 | [flake8] 14 | exclude = picoscope/_version.py versioneer.py 15 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | from setuptools import setup 2 | import versioneer 3 | 4 | 5 | with open('README.md') as readme_file: 6 | long_description = readme_file.read() 7 | 8 | 9 | setup( 10 | name='picoscope', 11 | version=versioneer.get_version(), 12 | description="Unofficial python wrapper for the PicoScope devices.", 13 | long_description=long_description, 14 | long_description_content_type='text/markdown', 15 | author="Colin O'Flynn, Mark Harfouche", 16 | author_email='coflynn@newae.com, mark.harfouche@gmail.com', 17 | license='BSD', 18 | url='https://github.com/colinoflynn/pico-python/', 19 | packages=['picoscope'], 20 | # See https://PyPI.python.org/PyPI?%3Aaction=list_classifiers 21 | classifiers=['Development Status :: 4 - Beta', 22 | 'Intended Audience :: Developers', 23 | 'Topic :: Software Development :: Libraries', 24 | 'Topic :: System :: Hardware', 25 | 'Topic :: Scientific/Engineering', 26 | 'License :: OSI Approved :: BSD License', 27 | 'Programming Language :: Python :: 2.7', 28 | 'Programming Language :: Python :: 3', 29 | ], 30 | 31 | # What does your project relate to? 32 | keywords='picoscope peripherals hardware oscilloscope ATE', 33 | install_requires=['numpy'], 34 | cmdclass=versioneer.get_cmdclass() 35 | ) 36 | --------------------------------------------------------------------------------