diff options
| -rw-r--r-- | .travis.yml | 18 | ||||
| -rw-r--r-- | pyecsca/sca/scope/picoscope.py | 67 | ||||
| -rw-r--r-- | setup.py | 4 |
3 files changed, 64 insertions, 25 deletions
diff --git a/.travis.yml b/.travis.yml index 41a8eb9..b171891 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,12 +1,28 @@ +os: linux language: python dist: xenial python: - "3.7" - "3.8" +addons: + apt: + sources: + - sourceline: "deb https://labs.picotech.com/debian/ picoscope main" + key_url: "https://labs.picotech.com/debian/dists/picoscope/Release.gpg.key" + packages: + - libps4000 + - libps6000 + +before_install: + - git clone https://github.com/picotech/picosdk-python-wrappers + - cd picosdk-python-wrappers + - python setup.py install + - cd .. + install: - pip install codecov - - pip install -e ".[test, typecheck]" + - pip install -e ".[picoscope, chipwhisperer, test, typecheck]" script: - make -i typecheck diff --git a/pyecsca/sca/scope/picoscope.py b/pyecsca/sca/scope/picoscope.py index f9f34f2..8497c64 100644 --- a/pyecsca/sca/scope/picoscope.py +++ b/pyecsca/sca/scope/picoscope.py @@ -13,28 +13,29 @@ from public import public from .base import Scope -class TriggerType(IntEnum): +class TriggerType(IntEnum): # pragma: no cover ABOVE = 1 BELOW = 2 RISING = 3 FALLING = 4 -def adc2volt(adc: Union[np.ndarray, ctypes.c_int16], volt_range: float, adc_minmax: int) -> Union[ - np.ndarray, float]: +def adc2volt(adc: Union[np.ndarray, ctypes.c_int16], + volt_range: float, adc_minmax: int) -> Union[np.ndarray, float]: # pragma: no cover if isinstance(adc, ctypes.c_int16): adc = adc.value return (adc / adc_minmax) * volt_range -def volt2adc(volt: Union[np.ndarray, float], volt_range: float, adc_minmax: int) -> Union[ - np.ndarray, ctypes.c_int16]: +def volt2adc(volt: Union[np.ndarray, float], + volt_range: float, adc_minmax: int) -> Union[ + np.ndarray, ctypes.c_int16]: # pragma: no cover if isinstance(volt, float): return ctypes.c_int16(int((volt / volt_range) * adc_minmax)) return (volt / volt_range) * adc_minmax -class PicoScope(Scope): +class PicoScope(Scope): # pragma: no cover """A PicoScope based scope.""" MODULE: Library PREFIX: str @@ -47,7 +48,7 @@ class PicoScope(Scope): def __init__(self): self.handle: ctypes.c_int16 = ctypes.c_int16() - self.frequency: Optional[int] = None + self.frequency: Optional[float] = None self.samples: Optional[int] = None self.timebase: Optional[int] = None self.buffers: MutableMapping = {} @@ -56,6 +57,13 @@ class PicoScope(Scope): def open(self): assert_pico_ok(self.__dispatch_call("OpenUnit", ctypes.byref(self.handle))) + def get_variant(self): + info = (ctypes.c_int8 * 6)() + size = ctypes.c_int16() + assert_pico_ok(self.__dispatch_call("GetUnitInfo", self.handle, ctypes.byref(info), 6, + ctypes.byref(size), 3)) + return "".join(chr(i) for i in info[:size]) + # channel setup (ranges, coupling, which channel is scope vs trigger) def set_channel(self, channel: str, enabled: bool, coupling: str, range: float): assert_pico_ok( @@ -63,28 +71,31 @@ class PicoScope(Scope): self.COUPLING[coupling], self.RANGES[range])) self.ranges[channel] = range - # frequency setup - def set_frequency(self, frequency: int, samples: int): + def _set_freq(self, frequency: int, samples: int, period_bound: float, timebase_bound: int, + low_freq: int, high_freq: int, high_subtract: int): period = 1 / frequency - if period <= 3.2e-9: - tb = log2(5_000_000_000) - log2(frequency) - tb = floor(tb) - if tb > 4: - tb = 4 - actual_frequency = 5_000_000_000 / 2 ** tb + if low_freq == 0 or period > period_bound: + tb = floor(high_freq / frequency + high_subtract) + actual_frequency = high_freq / (tb - high_subtract) else: - tb = floor(156_250_000 / frequency + 4) - actual_frequency = 156_250_000 / (tb - 4) + tb = floor(log2(low_freq) - log2(frequency)) + if tb > timebase_bound: + tb = timebase_bound + actual_frequency = low_freq / 2 ** tb max_samples = ctypes.c_int32() assert_pico_ok(self.__dispatch_call("GetTimebase", self.handle, tb, samples, None, 0, ctypes.byref(max_samples), 0)) if max_samples.value < samples: - samples = max_samples + samples = max_samples.value self.frequency = actual_frequency self.samples = samples self.timebase = tb return actual_frequency, samples + # frequency setup + def set_frequency(self, frequency: int, samples: int): + raise NotImplementedError + # triggering setup def set_trigger(self, type: TriggerType, enabled: bool, value: float, channel: str, range: float, delay: int, timeout: int): @@ -117,7 +128,7 @@ class PicoScope(Scope): assert_pico_ok(self.__dispatch_call("IsReady", self.handle, ctypes.byref(ready))) # get the data - def retrieve(self, channel: str): + def retrieve(self, channel: str) -> np.ndarray: if self.samples is None: raise ValueError actual_samples = ctypes.c_int32(self.samples) @@ -144,7 +155,7 @@ class PicoScope(Scope): @public -class PS4000Scope(PicoScope): +class PS4000Scope(PicoScope): # pragma: no cover MODULE = ps4000 PREFIX = "ps4000" CHANNELS = { @@ -178,9 +189,18 @@ class PS4000Scope(PicoScope): "DC": ps4000.PICO_COUPLING["DC"] } + def set_frequency(self, frequency: int, samples: int): + variant = self.get_variant() + if variant in ("4223", "4224", "4423", "4424"): + return self._set_freq(frequency, samples, 50e-9, 2, 80_000_000, 20_000_000, 1) + elif variant in ("4226", "4227"): + return self._set_freq(frequency, samples, 32e-9, 3, 250_000_000, 31_250_000, 2) + elif variant == "4262": + return self._set_freq(frequency, samples, 0, 0, 0, 10_000_000, -1) + @public -class PS6000Scope(PicoScope): +class PS6000Scope(PicoScope): # pragma: no cover MODULE = ps6000 PREFIX = "ps6000" CHANNELS = { @@ -216,7 +236,7 @@ class PS6000Scope(PicoScope): def open(self): assert_pico_ok(ps6000.ps6000OpenUnit(ctypes.byref(self.handle), None)) - def set_channel(self, channel: str, enabled: bool, coupling: str, range: str): + def set_channel(self, channel: str, enabled: bool, coupling: str, range: float): assert_pico_ok(ps6000.ps6000SetChannel(self.handle, self.CHANNELS[channel], enabled, self.COUPLING[coupling], self.RANGES[range], 0, ps6000.PS6000_BANDWIDTH_LIMITER["PS6000_BW_FULL"])) @@ -230,3 +250,6 @@ class PS6000Scope(PicoScope): ps6000.ps6000SetDataBuffer(self.handle, self.CHANNELS[channel], ctypes.byref(buffer), self.samples, 0)) + + def set_frequency(self, frequency: int, samples: int): + return self._set_freq(frequency, samples, 3.2e-9, 4, 5_000_000_000, 156_250_000, 4) @@ -34,8 +34,8 @@ setup( "asn1crypto" ], extras_require={ - "picoscope": ["picosdk", "ctypes"], - "chipshiwperer": ["chipwhisperer"], + "picoscope": ["picosdk"], + "chipwhisperer": ["chipwhisperer"], "typecheck": ["mypy"], "test": ["nose2", "parameterized", "green", "coverage"] } |
