From 9d5bfe567bd5d87a323cfc9d26ff39b8867239c8 Mon Sep 17 00:00:00 2001 From: Ole Bjerkemo <48286739+Solidedge@users.noreply.github.com> Date: Fri, 12 Aug 2022 13:27:02 +0200 Subject: [PATCH 01/14] Fix: Multiprocessing unable to stop Not sure if this is the best way to fix this, but this fixed Issue #13. I assume this was the intention as the function below (get_data) isn't called anywhere else. Someone more fluent in python should confirm this. --- src/ppk2_api/ppk2_api.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ppk2_api/ppk2_api.py b/src/ppk2_api/ppk2_api.py index 18dae01..fe90115 100644 --- a/src/ppk2_api/ppk2_api.py +++ b/src/ppk2_api/ppk2_api.py @@ -472,7 +472,7 @@ def start_measuring(self): def stop_measuring(self): PPK2_API.stop_measuring(self) - PPK2_API.get_data(self) # flush the serial buffer (to prevent unicode error on next command) + PPK2_MP.get_data(self) # flush the serial buffer (to prevent unicode error on next command) self._quit_evt.set() if self._fetcher is not None: self._fetcher.join() # join() will block if the queue isn't empty From 253c5eee5db0a6c8758a173b1648ea04ef0d1cbd Mon Sep 17 00:00:00 2001 From: Ole Bjerkemo <48286739+Solidedge@users.noreply.github.com> Date: Mon, 15 Aug 2022 10:50:46 +0200 Subject: [PATCH 02/14] Cleaner fix --- src/ppk2_api/ppk2_api.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ppk2_api/ppk2_api.py b/src/ppk2_api/ppk2_api.py index fe90115..a636044 100644 --- a/src/ppk2_api/ppk2_api.py +++ b/src/ppk2_api/ppk2_api.py @@ -472,7 +472,7 @@ def start_measuring(self): def stop_measuring(self): PPK2_API.stop_measuring(self) - PPK2_MP.get_data(self) # flush the serial buffer (to prevent unicode error on next command) + self.get_data() # flush the serial buffer (to prevent unicode error on next command) self._quit_evt.set() if self._fetcher is not None: self._fetcher.join() # join() will block if the queue isn't empty From bbd7680737f3f9c7a79ec3c203485abead5aca60 Mon Sep 17 00:00:00 2001 From: Matthew Beaudoin Date: Mon, 6 Feb 2023 11:45:21 -0600 Subject: [PATCH 03/14] Switch the PPK2_MP class to use threading to fix Issue 18. The PPK2_MP can now be used on windows and fixes a possible issue with queue sync on linux platforms --- src/ppk2_api/ppk2_api.py | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/src/ppk2_api/ppk2_api.py b/src/ppk2_api/ppk2_api.py index a636044..08f7f32 100644 --- a/src/ppk2_api/ppk2_api.py +++ b/src/ppk2_api/ppk2_api.py @@ -3,14 +3,14 @@ The PPK2 uses Serial communication. The official nRF Connect Power Profiler was used as a reference: https://github.com/NordicSemiconductor/pc-nrfconnect-ppk """ - +import threading import time import serial import struct import logging import os import queue -import multiprocessing + class PPK2_Command(): """Serial command opcodes""" @@ -243,7 +243,7 @@ def stop_measuring(self): self._write_serial((PPK2_Command.AVERAGE_STOP, )) def set_source_voltage(self, mV): - """Inits device - based on observation only REGULATOR_SET is the command. + """Inits device - based on observation only REGULATOR_SET is the command. The other two values correspond to the voltage level. 800mV is the lowest setting - [3,32] - the values then increase linearly @@ -290,7 +290,7 @@ def get_adc_result(self, current_range, adc_value): self.rolling_avg = adc else: self.rolling_avg = self.spike_filter_alpha * adc + (1 - self.spike_filter_alpha) * self.rolling_avg - + if self.rolling_avg4 is None: self.rolling_avg4 = adc else: @@ -313,7 +313,7 @@ def get_adc_result(self, current_range, adc_value): adc = self.rolling_avg4 else: adc = self.rolling_avg - + self.after_spike -= 1 self.prev_range = current_range @@ -358,9 +358,9 @@ def get_samples(self, buf): return samples # return list of samples, handle those lists in PPK2 API wrapper -class PPK_Fetch(multiprocessing.Process): +class PPK_Fetch(threading.Thread): ''' - Background process for polling the data in multiprocessing variant + Background process for polling the data in multi-threading variant ''' def __init__(self, ppk2, quit_evt, buffer_len_s=10, buffer_chunk_s=0.5): super().__init__() @@ -380,7 +380,7 @@ def __init__(self, ppk2, quit_evt, buffer_len_s=10, buffer_chunk_s=0.5): if self._buffer_chunk % 4 != 0: self._buffer_chunk = (self._buffer_chunk // 4) * 4 - self._buffer_q = multiprocessing.Queue() + self._buffer_q = queue.Queue() def run(self): s = 0 @@ -442,7 +442,7 @@ def __init__(self, port, buffer_seconds=10): ''' super().__init__(port) self._fetcher = None - self._quit_evt = multiprocessing.Event() + self._quit_evt = threading.Event() self._buffer_seconds = buffer_seconds def __del__(self): @@ -466,7 +466,7 @@ def start_measuring(self): self._quit_evt.clear() if self._fetcher is not None: return - + self._fetcher = PPK_Fetch(self, self._quit_evt, self._buffer_seconds) self._fetcher.start() From 78b08ecdd70d847e4e81913fc7523f6ab8bebada Mon Sep 17 00:00:00 2001 From: NejcKle Date: Mon, 6 Mar 2023 13:15:05 +0100 Subject: [PATCH 04/14] Modify multiprocessing to accept chunk size in seconds --- example.py | 12 ++++++++---- example_mp.py | 36 +++++++++++++++++++----------------- src/ppk2_api/ppk2_api.py | 15 +++++++++------ 3 files changed, 36 insertions(+), 27 deletions(-) diff --git a/example.py b/example.py index 6a20967..e892231 100644 --- a/example.py +++ b/example.py @@ -19,8 +19,10 @@ ppk2_test = PPK2_API(ppk2_port) ppk2_test.get_modifiers() -ppk2_test.use_ampere_meter() # set ampere meter mode -ppk2_test.toggle_DUT_power("OFF") # disable DUT power +ppk2_test.set_source_voltage(3300) + +ppk2_test.use_source_meter() # set source meter mode +ppk2_test.toggle_DUT_power("ON") # enable DUT power ppk2_test.start_measuring() # start measuring # measurements are a constant stream of bytes @@ -34,7 +36,9 @@ print(f"Average of {len(samples)} samples is: {sum(samples)/len(samples)}uA") time.sleep(0.01) -ppk2_test.toggle_DUT_power("ON") +ppk2_test.toggle_DUT_power("OFF") # disable DUT power + +ppk2_test.use_ampere_meter() # set ampere meter mode ppk2_test.start_measuring() for i in range(0, 1000): @@ -42,6 +46,6 @@ if read_data != b'': samples = ppk2_test.get_samples(read_data) print(f"Average of {len(samples)} samples is: {sum(samples)/len(samples)}uA") - time.sleep(0.001) # lower time between sampling -> less samples read in one sampling period + time.sleep(0.01) # lower time between sampling -> less samples read in one sampling period ppk2_test.stop_measuring() \ No newline at end of file diff --git a/example_mp.py b/example_mp.py index bb084b4..bc16bad 100644 --- a/example_mp.py +++ b/example_mp.py @@ -1,15 +1,15 @@ """ -Basic usage of PPK2 Python API - multiprocessing version +Basic usage of PPK2 Python API - multiprocessing version. The basic ampere mode sequence is: 1. read modifiers 2. set ampere mode 3. read stream of data """ import time -from ppk2_api.ppk2_api import PPK2_MP +from ppk2_api.ppk2_api import PPK2_MP as PPK2_API -ppk2s_connected = PPK2_MP.list_devices() +ppk2s_connected = PPK2_API.list_devices() if(len(ppk2s_connected) == 1): ppk2_port = ppk2s_connected[0] print(f'Found PPK2 at {ppk2_port}') @@ -17,33 +17,35 @@ print(f'Too many connected PPK2\'s: {ppk2s_connected}') exit() -ppk2_test = PPK2_MP(ppk2_port) +ppk2_test = PPK2_API(ppk2_port, buffer_max_size_seconds=1, buffer_chunk_seconds=0.01) ppk2_test.get_modifiers() -ppk2_test.use_ampere_meter() # set ampere meter mode -ppk2_test.toggle_DUT_power("OFF") # disable DUT power +ppk2_test.set_source_voltage(3300) -ppk2_test.start_measuring() # start measuring +ppk2_test.use_source_meter() # set source meter mode +ppk2_test.toggle_DUT_power("ON") # enable DUT power +ppk2_test.start_measuring() # start measuring # measurements are a constant stream of bytes -# multiprocessing variant starts a process in the background which constantly -# polls the device in order to prevent losing samples. It will buffer the -# last 10s (by default) of data so get_data() can be called less frequently. -for i in range(0, 10): +# the number of measurements in one sampling period depends on the wait between serial reads +# it appears the maximum number of bytes received is 1024 +# the sampling rate of the PPK2 is 100 samples per millisecond +for i in range(0, 1000): read_data = ppk2_test.get_data() if read_data != b'': samples = ppk2_test.get_samples(read_data) print(f"Average of {len(samples)} samples is: {sum(samples)/len(samples)}uA") - time.sleep(0.5) + time.sleep(0.001) -ppk2_test.toggle_DUT_power("ON") +ppk2_test.toggle_DUT_power("OFF") # disable DUT power + +ppk2_test.use_ampere_meter() # set ampere meter mode ppk2_test.start_measuring() -for i in range(0, 10): +for i in range(0, 1000): read_data = ppk2_test.get_data() if read_data != b'': samples = ppk2_test.get_samples(read_data) print(f"Average of {len(samples)} samples is: {sum(samples)/len(samples)}uA") - time.sleep(0.5) # lower time between sampling -> less samples read in one sampling period - -ppk2_test.stop_measuring() + time.sleep(0.001) # lower time between sampling -> less samples read in one sampling period +ppk2_test.stop_measuring() \ No newline at end of file diff --git a/src/ppk2_api/ppk2_api.py b/src/ppk2_api/ppk2_api.py index a636044..26f0209 100644 --- a/src/ppk2_api/ppk2_api.py +++ b/src/ppk2_api/ppk2_api.py @@ -397,7 +397,7 @@ def run(self): self._buffer_q.get() local_buffer = local_buffer[self._buffer_chunk:] self._last_timestamp = tm_now - #print(len(d), len(local_buffer), self._buffer_q.qsize()) + # print(len(d), len(local_buffer), self._buffer_q.qsize()) # calculate stats s += len(d) @@ -423,7 +423,7 @@ def get_data(self): count = 0 while True: try: - ret += self._buffer_q.get(timeout=0.01) # get_nowait sometimes skips a chunk for some reason + ret += self._buffer_q.get(timeout=0.001) # get_nowait sometimes skips a chunk for some reason count += 1 except queue.Empty: break @@ -435,15 +435,18 @@ class PPK2_MP(PPK2_API): Multiprocessing variant of the object. The interface is the same as for the regular one except it spawns a background process on start_measuring() ''' - def __init__(self, port, buffer_seconds=10): + def __init__(self, port, buffer_max_size_seconds=10, buffer_chunk_seconds=0.1): ''' port - port where PPK2 is connected - buffer_seconds - how many seconds of data to keep in the buffer + buffer_max_size_seconds - how many seconds of data to keep in the buffer + buffer_chunk_seconds - how many seconds of data to put in the queue at once ''' super().__init__(port) + self._fetcher = None self._quit_evt = multiprocessing.Event() - self._buffer_seconds = buffer_seconds + self._buffer_max_size_seconds = buffer_max_size_seconds + self._buffer_chunk_seconds = buffer_chunk_seconds def __del__(self): """Destructor""" @@ -467,7 +470,7 @@ def start_measuring(self): if self._fetcher is not None: return - self._fetcher = PPK_Fetch(self, self._quit_evt, self._buffer_seconds) + self._fetcher = PPK_Fetch(self, self._quit_evt, self._buffer_max_size_seconds, self._buffer_chunk_seconds) self._fetcher.start() def stop_measuring(self): From 7bb1d5bbd5a498f71433b2e7b427418c19bf9128 Mon Sep 17 00:00:00 2001 From: NejcKle Date: Tue, 14 Mar 2023 11:05:32 +0100 Subject: [PATCH 05/14] Add configurable read/write timeouts to PPK2_API class --- src/ppk2_api/ppk2_api.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/ppk2_api/ppk2_api.py b/src/ppk2_api/ppk2_api.py index ea1a9ac..098772d 100644 --- a/src/ppk2_api/ppk2_api.py +++ b/src/ppk2_api/ppk2_api.py @@ -46,10 +46,10 @@ class PPK2_Modes(): class PPK2_API(): - def __init__(self, port): + def __init__(self, port, read_timeout=1, write_timeout=1): self.ser = None - self.ser = serial.Serial(port) + self.ser = serial.Serial(port, exclusive=True, timeout=read_timeout, write_timeout=write_timeout) self.ser.baudrate = 9600 self.modifiers = { From 3450e735bd208c8c63c8b171056b98924b1ff7ce Mon Sep 17 00:00:00 2001 From: NejcKle Date: Wed, 15 Mar 2023 14:58:47 +0100 Subject: [PATCH 06/14] Add kwargs to pySerial constructor --- src/ppk2_api/ppk2_api.py | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/src/ppk2_api/ppk2_api.py b/src/ppk2_api/ppk2_api.py index 098772d..8c56078 100644 --- a/src/ppk2_api/ppk2_api.py +++ b/src/ppk2_api/ppk2_api.py @@ -46,10 +46,14 @@ class PPK2_Modes(): class PPK2_API(): - def __init__(self, port, read_timeout=1, write_timeout=1): + def __init__(self, port: str, **kwargs): + ''' + port - port where PPK2 is connected + **kwargs - keyword arguments to pass to the pySerial constructor + ''' self.ser = None - self.ser = serial.Serial(port, exclusive=True, timeout=read_timeout, write_timeout=write_timeout) + self.ser = serial.Serial(port, **kwargs) self.ser.baudrate = 9600 self.modifiers = { @@ -435,13 +439,14 @@ class PPK2_MP(PPK2_API): Multiprocessing variant of the object. The interface is the same as for the regular one except it spawns a background process on start_measuring() ''' - def __init__(self, port, buffer_max_size_seconds=10, buffer_chunk_seconds=0.1): + def __init__(self, port, buffer_max_size_seconds=10, buffer_chunk_seconds=0.1, **kwargs): ''' port - port where PPK2 is connected buffer_max_size_seconds - how many seconds of data to keep in the buffer buffer_chunk_seconds - how many seconds of data to put in the queue at once + **kwargs - keyword arguments to pass to the pySerial constructor ''' - super().__init__(port) + super().__init__(port, **kwargs) self._fetcher = None self._quit_evt = threading.Event() From 0d01795a28387ce0393a4d18c6cd8cfb18ea7214 Mon Sep 17 00:00:00 2001 From: NejcKle Date: Fri, 17 Mar 2023 13:27:24 +0100 Subject: [PATCH 07/14] Update examples with example serial port parameters --- example.py | 4 ++-- example_mp.py | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/example.py b/example.py index e892231..300d435 100644 --- a/example.py +++ b/example.py @@ -7,7 +7,7 @@ 3. read stream of data """ import time -from ppk2_api.ppk2_api import PPK2_API +from src.ppk2_api.ppk2_api import PPK2_API ppk2s_connected = PPK2_API.list_devices() if(len(ppk2s_connected) == 1): @@ -17,7 +17,7 @@ print(f'Too many connected PPK2\'s: {ppk2s_connected}') exit() -ppk2_test = PPK2_API(ppk2_port) +ppk2_test = PPK2_API(ppk2_port, timeout=1, write_timeout=1, exclusive=True) ppk2_test.get_modifiers() ppk2_test.set_source_voltage(3300) diff --git a/example_mp.py b/example_mp.py index bc16bad..47a0df3 100644 --- a/example_mp.py +++ b/example_mp.py @@ -7,7 +7,7 @@ 3. read stream of data """ import time -from ppk2_api.ppk2_api import PPK2_MP as PPK2_API +from src.ppk2_api.ppk2_api import PPK2_MP as PPK2_API ppk2s_connected = PPK2_API.list_devices() if(len(ppk2s_connected) == 1): @@ -17,7 +17,7 @@ print(f'Too many connected PPK2\'s: {ppk2s_connected}') exit() -ppk2_test = PPK2_API(ppk2_port, buffer_max_size_seconds=1, buffer_chunk_seconds=0.01) +ppk2_test = PPK2_API(ppk2_port, buffer_max_size_seconds=1, buffer_chunk_seconds=0.01, timeout=1, write_timeout=1, exclusive=True) ppk2_test.get_modifiers() ppk2_test.set_source_voltage(3300) From eaaf1ad36b0d79cb58875ecb56a1171a21dd4c9e Mon Sep 17 00:00:00 2001 From: NejcKle Date: Fri, 17 Mar 2023 13:33:38 +0100 Subject: [PATCH 08/14] Update version --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 850f849..b53262e 100644 --- a/setup.py +++ b/setup.py @@ -23,7 +23,7 @@ def read(*names, **kwargs): setup( name="ppk2-api", - version="0.0.2", + version="0.9.1", description="API for Nordic Semiconductor's Power Profiler Kit II (PPK 2).", url="https://github.com/IRNAS/ppk2-api-python", packages=find_packages("src"), From dbce1f376378eff82f0d903b0ae564afd03efa8c Mon Sep 17 00:00:00 2001 From: NejcKle Date: Fri, 2 Jun 2023 11:42:57 +0200 Subject: [PATCH 09/14] Add digital channels --- example.py | 22 ++++++++++++++++++-- example_mp.py | 38 ++++++++++++++++++++++++++++------ src/ppk2_api/ppk2_api.py | 44 ++++++++++++++++++++++++++++++++-------- 3 files changed, 87 insertions(+), 17 deletions(-) diff --git a/example.py b/example.py index e892231..10058e2 100644 --- a/example.py +++ b/example.py @@ -32,8 +32,17 @@ for i in range(0, 1000): read_data = ppk2_test.get_data() if read_data != b'': - samples = ppk2_test.get_samples(read_data) + samples, raw_digital = ppk2_test.get_samples(read_data) print(f"Average of {len(samples)} samples is: {sum(samples)/len(samples)}uA") + + # Raw digital contains the raw digital data from the PPK2 + # The number of raw samples is equal to the number of samples in the samples list + # We have to process the raw digital data to get the actual digital data + digital_channels = ppk2_test.digital_channels(raw_digital) + for ch in digital_channels: + # Print last 10 values of each channel + print(ch[-10:]) + print() time.sleep(0.01) ppk2_test.toggle_DUT_power("OFF") # disable DUT power @@ -44,8 +53,17 @@ for i in range(0, 1000): read_data = ppk2_test.get_data() if read_data != b'': - samples = ppk2_test.get_samples(read_data) + samples, raw_digital = ppk2_test.get_samples(read_data) print(f"Average of {len(samples)} samples is: {sum(samples)/len(samples)}uA") + + # Raw digital contains the raw digital data from the PPK2 + # The number of raw samples is equal to the number of samples in the samples list + # We have to process the raw digital data to get the actual digital data + digital_channels = ppk2_test.digital_channels(raw_digital) + for ch in digital_channels: + # Print last 10 values of each channel + print(ch[-10:]) + print() time.sleep(0.01) # lower time between sampling -> less samples read in one sampling period ppk2_test.stop_measuring() \ No newline at end of file diff --git a/example_mp.py b/example_mp.py index bc16bad..7d68129 100644 --- a/example_mp.py +++ b/example_mp.py @@ -17,10 +17,13 @@ print(f'Too many connected PPK2\'s: {ppk2s_connected}') exit() -ppk2_test = PPK2_API(ppk2_port, buffer_max_size_seconds=1, buffer_chunk_seconds=0.01) +ppk2_test = PPK2_API(ppk2_port, buffer_max_size_seconds=70, buffer_chunk_seconds=0.5) ppk2_test.get_modifiers() ppk2_test.set_source_voltage(3300) +""" +Source mode example +""" ppk2_test.use_source_meter() # set source meter mode ppk2_test.toggle_DUT_power("ON") # enable DUT power @@ -29,23 +32,46 @@ # the number of measurements in one sampling period depends on the wait between serial reads # it appears the maximum number of bytes received is 1024 # the sampling rate of the PPK2 is 100 samples per millisecond -for i in range(0, 1000): +while True: read_data = ppk2_test.get_data() if read_data != b'': - samples = ppk2_test.get_samples(read_data) + samples, raw_digital = ppk2_test.get_samples(read_data) print(f"Average of {len(samples)} samples is: {sum(samples)/len(samples)}uA") + + # Raw digital contains the raw digital data from the PPK2 + # The number of raw samples is equal to the number of samples in the samples list + # We have to process the raw digital data to get the actual digital data + digital_channels = ppk2_test.digital_channels(raw_digital) + for ch in digital_channels: + # Print last 10 values of each channel + print(ch[-10:]) + print() + time.sleep(0.001) ppk2_test.toggle_DUT_power("OFF") # disable DUT power +ppk2_test.stop_measuring() +""" +Ampere mode example +""" ppk2_test.use_ampere_meter() # set ampere meter mode ppk2_test.start_measuring() -for i in range(0, 1000): +while True: read_data = ppk2_test.get_data() if read_data != b'': - samples = ppk2_test.get_samples(read_data) + samples, raw_digital = ppk2_test.get_samples(read_data) print(f"Average of {len(samples)} samples is: {sum(samples)/len(samples)}uA") + + # Raw digital contains the raw digital data from the PPK2 + # The number of raw samples is equal to the number of samples in the samples list + # We have to process the raw digital data to get the actual digital data + digital_channels = ppk2_test.digital_channels(raw_digital) + for ch in digital_channels: + # Print last 10 values of each channel + print(ch[-10:]) + print() time.sleep(0.001) # lower time between sampling -> less samples read in one sampling period -ppk2_test.stop_measuring() \ No newline at end of file +# ppk2_test.stop_measuring() \ No newline at end of file diff --git a/src/ppk2_api/ppk2_api.py b/src/ppk2_api/ppk2_api.py index 8c56078..13b5e43 100644 --- a/src/ppk2_api/ppk2_api.py +++ b/src/ppk2_api/ppk2_api.py @@ -189,11 +189,10 @@ def _generate_mask(self, bits, pos): mask = self._twos_comp(mask) return {"mask": mask, "pos": pos} - def _get_masked_value(self, value, meas): + def _get_masked_value(self, value, meas, is_bits=False): + # print(f"Value: {value}") + # print(f"Meas: {meas}") masked_value = (value & meas["mask"]) >> meas["pos"] - if meas["pos"] == 24: - if masked_value == 255: - masked_value = -1 return masked_value def _handle_raw_data(self, adc_value): @@ -205,10 +204,10 @@ def _handle_raw_data(self, adc_value): bits = self._get_masked_value(adc_value, self.MEAS_LOGIC) analog_value = self.get_adc_result( current_measurement_range, adc_result) * 10**6 - return analog_value + return analog_value, bits except Exception as e: print("Measurement outside of range!") - return None + return None, None @staticmethod def list_devices(): @@ -327,6 +326,26 @@ def _digital_to_analog(self, adc_value): """Convert discrete value to analog value""" return int.from_bytes(adc_value, byteorder="little", signed=False) # convert reading to analog value + def digital_channels(self, bits): + """ + Convert raw digital data to digital channels. + + Returns a 2d matrix with 8 rows (one for each channel). Each row contains HIGH and LOW values for the selected channel. + """ + + # Prepare 2d matrix with 8 rows (one for each channel) + digital_channels = [[], [], [], [], [], [], [], []] + for sample in bits: + digital_channels[0].append((sample & 1) >> 0) + digital_channels[1].append((sample & 2) >> 1) + digital_channels[2].append((sample & 4) >> 2) + digital_channels[3].append((sample & 8) >> 3) + digital_channels[4].append((sample & 16) >> 4) + digital_channels[5].append((sample & 32) >> 5) + digital_channels[6].append((sample & 64) >> 6) + digital_channels[7].append((sample & 128) >> 7) + return digital_channels + def get_samples(self, buf): """ Returns list of samples read in one sampling period. @@ -338,13 +357,16 @@ def get_samples(self, buf): sample_size = 4 # one analog value is 4 bytes in size offset = self.remainder["len"] samples = [] + raw_digital_output = [] first_reading = ( self.remainder["sequence"] + buf[0:sample_size-offset])[:4] adc_val = self._digital_to_analog(first_reading) - measurement = self._handle_raw_data(adc_val) + measurement, bits = self._handle_raw_data(adc_val) if measurement is not None: samples.append(measurement) + if bits is not None: + raw_digital_output.append(bits) offset = sample_size - offset @@ -352,14 +374,18 @@ def get_samples(self, buf): next_val = buf[offset:offset + sample_size] offset += sample_size adc_val = self._digital_to_analog(next_val) - measurement = self._handle_raw_data(adc_val) + measurement, bits = self._handle_raw_data(adc_val) if measurement is not None: samples.append(measurement) + if bits is not None: + raw_digital_output.append(bits) self.remainder["sequence"] = buf[offset:len(buf)] self.remainder["len"] = len(buf)-offset - return samples # return list of samples, handle those lists in PPK2 API wrapper + # return list of samples and raw digital outputs + # handle those lists in PPK2 API wrapper + return samples, raw_digital_output class PPK_Fetch(threading.Thread): From faccfcc3a13dee6e8ddff9c6af12e85e3a055fbc Mon Sep 17 00:00:00 2001 From: NejcKle Date: Fri, 9 Jun 2023 15:31:34 +0200 Subject: [PATCH 10/14] Add newlines to examples, remove unused prints --- example.py | 2 +- example_mp.py | 2 +- src/ppk2_api/ppk2_api.py | 3 --- 3 files changed, 2 insertions(+), 5 deletions(-) diff --git a/example.py b/example.py index 731aadf..5753754 100644 --- a/example.py +++ b/example.py @@ -66,4 +66,4 @@ print() time.sleep(0.01) # lower time between sampling -> less samples read in one sampling period -ppk2_test.stop_measuring() \ No newline at end of file +ppk2_test.stop_measuring() diff --git a/example_mp.py b/example_mp.py index 8a313cd..183d5ad 100644 --- a/example_mp.py +++ b/example_mp.py @@ -74,4 +74,4 @@ print() time.sleep(0.001) # lower time between sampling -> less samples read in one sampling period -# ppk2_test.stop_measuring() \ No newline at end of file +ppk2_test.stop_measuring() diff --git a/src/ppk2_api/ppk2_api.py b/src/ppk2_api/ppk2_api.py index 13b5e43..58bab9c 100644 --- a/src/ppk2_api/ppk2_api.py +++ b/src/ppk2_api/ppk2_api.py @@ -190,8 +190,6 @@ def _generate_mask(self, bits, pos): return {"mask": mask, "pos": pos} def _get_masked_value(self, value, meas, is_bits=False): - # print(f"Value: {value}") - # print(f"Meas: {meas}") masked_value = (value & meas["mask"]) >> meas["pos"] return masked_value @@ -427,7 +425,6 @@ def run(self): self._buffer_q.get() local_buffer = local_buffer[self._buffer_chunk:] self._last_timestamp = tm_now - # print(len(d), len(local_buffer), self._buffer_q.qsize()) # calculate stats s += len(d) From 719a0b5e993b6bb6617c6e4ee3432ab75fe2e9b4 Mon Sep 17 00:00:00 2001 From: NejcKle Date: Fri, 9 Jun 2023 15:33:14 +0200 Subject: [PATCH 11/14] Change version --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index b53262e..bab7e24 100644 --- a/setup.py +++ b/setup.py @@ -23,7 +23,7 @@ def read(*names, **kwargs): setup( name="ppk2-api", - version="0.9.1", + version="0.9.2", description="API for Nordic Semiconductor's Power Profiler Kit II (PPK 2).", url="https://github.com/IRNAS/ppk2-api-python", packages=find_packages("src"), From abefe62344275b44b415d32820af583ec05d79ec Mon Sep 17 00:00:00 2001 From: rgroh1996 Date: Mon, 20 Nov 2023 14:18:20 +0100 Subject: [PATCH 12/14] send reset command in destructor The PPK2 was not reset when the object was deleted. The PPK2 is reset with the PPK2 RESET command. This allows a new connection to be established immediately. --- src/ppk2_api/ppk2_api.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/ppk2_api/ppk2_api.py b/src/ppk2_api/ppk2_api.py index 58bab9c..fc34a18 100644 --- a/src/ppk2_api/ppk2_api.py +++ b/src/ppk2_api/ppk2_api.py @@ -98,6 +98,9 @@ def __init__(self, port: str, **kwargs): def __del__(self): """Destructor""" try: + # reset device + self._write_serial((PPK2_Command.RESET,)) + if self.ser: self.ser.close() except Exception as e: From 29fd996c733cdc47ebe95724cdf99b51233cba42 Mon Sep 17 00:00:00 2001 From: Christian Wilgaard Date: Thu, 18 Apr 2024 14:57:24 +0200 Subject: [PATCH 13/14] list_devices: prints serial when discovering ppk2 --- example.py | 9 +++++---- example_mp.py | 9 +++++---- src/ppk2_api/ppk2_api.py | 15 ++++++++++++--- 3 files changed, 22 insertions(+), 11 deletions(-) diff --git a/example.py b/example.py index 5753754..e89ef7b 100644 --- a/example.py +++ b/example.py @@ -10,11 +10,12 @@ from ppk2_api.ppk2_api import PPK2_API ppk2s_connected = PPK2_API.list_devices() -if(len(ppk2s_connected) == 1): - ppk2_port = ppk2s_connected[0] - print(f'Found PPK2 at {ppk2_port}') +if len(ppk2s_connected) == 1: + ppk2_port = ppk2s_connected[0][0] + ppk2_serial = ppk2s_connected[0][1] + print(f"Found PPK2 at {ppk2_port} with serial number {ppk2_serial}") else: - print(f'Too many connected PPK2\'s: {ppk2s_connected}') + print(f"Too many connected PPK2's: {ppk2s_connected}") exit() ppk2_test = PPK2_API(ppk2_port, timeout=1, write_timeout=1, exclusive=True) diff --git a/example_mp.py b/example_mp.py index 183d5ad..523dcd9 100644 --- a/example_mp.py +++ b/example_mp.py @@ -10,11 +10,12 @@ from ppk2_api.ppk2_api import PPK2_MP as PPK2_API ppk2s_connected = PPK2_API.list_devices() -if(len(ppk2s_connected) == 1): - ppk2_port = ppk2s_connected[0] - print(f'Found PPK2 at {ppk2_port}') +if len(ppk2s_connected) == 1: + ppk2_port = ppk2s_connected[0][0] + ppk2_serial = ppk2s_connected[0][1] + print(f"Found PPK2 at {ppk2_port} with serial number {ppk2_serial}") else: - print(f'Too many connected PPK2\'s: {ppk2s_connected}') + print(f"Too many connected PPK2's: {ppk2s_connected}") exit() ppk2_test = PPK2_API(ppk2_port, buffer_max_size_seconds=1, buffer_chunk_seconds=0.01, timeout=1, write_timeout=1, exclusive=True) diff --git a/src/ppk2_api/ppk2_api.py b/src/ppk2_api/ppk2_api.py index fc34a18..c291cdc 100644 --- a/src/ppk2_api/ppk2_api.py +++ b/src/ppk2_api/ppk2_api.py @@ -213,11 +213,20 @@ def _handle_raw_data(self, adc_value): @staticmethod def list_devices(): import serial.tools.list_ports + ports = serial.tools.list_ports.comports() - if os.name == 'nt': - devices = [port.device for port in ports if port.description.startswith("nRF Connect USB CDC ACM")] + if os.name == "nt": + devices = [ + (port.device, port.serial_number[:8]) + for port in ports + if port.description.startswith("nRF Connect USB CDC ACM") + ] else: - devices = [port.device for port in ports if port.product == 'PPK2'] + devices = [ + (port.device, port.serial_number[:8]) + for port in ports + if port.product == "PPK2" + ] return devices def get_data(self): From 2891553a2ee54a851a93d30ab6b04e9228f3f9dc Mon Sep 17 00:00:00 2001 From: Christian Wilgaard Date: Thu, 25 Jul 2024 11:45:41 +0200 Subject: [PATCH 14/14] list_devices: adds support for several com ports After the firmware change in the official PPK2 application 4.2.0, the device now enumerates two serial ports: one for data and commands, the other for a debug shell which is not used in this library. This forces connection with the port on endpoint 1. --- src/ppk2_api/ppk2_api.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/ppk2_api/ppk2_api.py b/src/ppk2_api/ppk2_api.py index c291cdc..4b39a69 100644 --- a/src/ppk2_api/ppk2_api.py +++ b/src/ppk2_api/ppk2_api.py @@ -219,13 +219,13 @@ def list_devices(): devices = [ (port.device, port.serial_number[:8]) for port in ports - if port.description.startswith("nRF Connect USB CDC ACM") + if port.description.startswith("nRF Connect USB CDC ACM") and port.location.endswith("1") ] else: devices = [ (port.device, port.serial_number[:8]) for port in ports - if port.product == "PPK2" + if port.product == "PPK2" and port.location.endswith("1") ] return devices