From 3bb15bddf343ddb57c550a3fa0b7e16505294134 Mon Sep 17 00:00:00 2001 From: Bertrand Songis Date: Sat, 29 Jun 2019 17:37:09 +0200 Subject: [PATCH] Bsongis/latency end to end (#6532) * Option -DDEBUG_LATENCY=END_TO_END added * Add script to decode logic analyser results --- radio/src/opentx.cpp | 27 +++- radio/src/targets/common/arm/CMakeLists.txt | 4 + tools/latency.py | 154 ++++++++++++++++++++ 3 files changed, 179 insertions(+), 6 deletions(-) create mode 100755 tools/latency.py diff --git a/radio/src/opentx.cpp b/radio/src/opentx.cpp index c635c147e..ab5e36bfa 100644 --- a/radio/src/opentx.cpp +++ b/radio/src/opentx.cpp @@ -88,6 +88,17 @@ void watchdogSuspend(uint32_t timeout) watchdogTimeout = timeout; } +#if defined(DEBUG_LATENCY) +void toggleLatencySwitch() +{ + latencyToggleSwitch ^= 1; + if (latencyToggleSwitch) + sportUpdatePowerOn(); + else + sportUpdatePowerOff(); +} +#endif + void per10ms() { g_tmr10ms++; @@ -111,6 +122,14 @@ void per10ms() else trimsDisplayMask = 0; +#if defined(DEBUG_LATENCY_END_TO_END) + static tmr10ms_t lastLatencyToggle = 0; + if (g_tmr10ms - lastLatencyToggle == 10) { + lastLatencyToggle = g_tmr10ms; + toggleLatencySwitch(); + } +#endif + #if defined(RTCLOCK) /* Update global Date/Time every 100 per10ms cycles */ if (++g_ms100 == 100) { @@ -1326,15 +1345,11 @@ void doMixerCalculations() tmr10ms_t tmr10ms = get_tmr10ms(); -#if defined(DEBUG_LATENCY) +#if defined(DEBUG_LATENCY_MIXER_RF) || defined(DEBUG_LATENCY_RF_ONLY) static tmr10ms_t lastLatencyToggle = 0; if (tmr10ms - lastLatencyToggle >= 10) { lastLatencyToggle = tmr10ms; - latencyToggleSwitch ^= 1; - if (latencyToggleSwitch) - sportUpdatePowerOn(); - else - sportUpdatePowerOff(); + toggleLatencySwitch(); } #endif diff --git a/radio/src/targets/common/arm/CMakeLists.txt b/radio/src/targets/common/arm/CMakeLists.txt index 533a28ab5..75ff6cbab 100644 --- a/radio/src/targets/common/arm/CMakeLists.txt +++ b/radio/src/targets/common/arm/CMakeLists.txt @@ -82,6 +82,10 @@ if(DEBUG_LATENCY STREQUAL RF_ONLY) add_definitions(-DDEBUG_LATENCY_RF_ONLY) endif() +if(DEBUG_LATENCY STREQUAL END_TO_END) + add_definitions(-DDEBUG_LATENCY) + add_definitions(-DDEBUG_LATENCY_END_TO_END) +endif() if(CLI) add_definitions(-DCLI) diff --git a/tools/latency.py b/tools/latency.py new file mode 100755 index 000000000..9213ce5f2 --- /dev/null +++ b/tools/latency.py @@ -0,0 +1,154 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +# +# Copyright (C) OpenTX +# +# Based on code named +# th9x - http://code.google.com/p/th9x +# er9x - http://code.google.com/p/er9x +# gruvin9x - http://code.google.com/p/gruvin9x +# +# License GPLv2: http://www.gnu.org/licenses/gpl-2.0.html +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + + +import argparse +import csv + + +def build_transitions_array(f, column): + f.seek(0) + reader = csv.reader(f, delimiter=',') + last = None + transitions = [] + for i, row in enumerate(reader): + if i == 0: + continue + value = row[column] + if last is None or value != last: + transitions.append((float(row[0]) * 1000, int(value))) + last = value + debounced = [] + i = 0 + while i < len(transitions): + t, val = transitions[i] + if i < len(transitions) - 1 and transitions[i+1][0] - t < 0.002: + i += 2 + continue + debounced.append((t, val)) + i += 1 + return debounced + + +class SBusFrame: + def __init__(self): + self.transitions = [] + + def push(self, t, value): + self.transitions.append((t, value)) + + def start(self): + return self.transitions[0][0] + + def end(self): + return self.transitions[-1][0] + + def is_after(self, t): + return self.start() >= t + + def value(self, time): + value = 0 + for t, v in self.transitions: + if t > time: + return value + value = v + return value + + def byte(self, index): + t = self.start() + 0.12 * index + 0.015 + value = 0 + for bit in range(8): + value += (1 - self.value(t)) << bit + t += 0.010 + return value + + def is_lost(self): + return self.byte(23) != 0 + + def __str__(self): + return "%.03fms " % self.start() + " ".join(["%02X" % self.byte(i) for i in range(25)]) + + @staticmethod + def get_frames(transitions): + result = [] + current_frame = None + last = 0 + for t, value in transitions: + if t - last > 5: + if current_frame: + result.append(current_frame) + current_frame = SBusFrame() + if current_frame is not None: + current_frame.push(t, value) + last = t + return result + + +def print_statistics(trigger_transitions, sbus_frames, highval, lowval): + mini, maxi = None, None + count = 0 + total = 0 + for t0, val in trigger_transitions[1:]: + byte = highval if val == 1 else lowval + for frame in sbus_frames: + if frame.is_after(t0) and frame.byte(1) == byte: + delay = frame.end() - t0 + count += 1 + total += delay + if mini is None or delay < mini[0]: + mini = (delay, t0, frame, val, byte) + if maxi is None or delay > maxi[0]: + maxi = (delay, t0, frame) + break + + print("Delay between the switch toggle and the end of the SBUS frame:") + print(" Count = %d transitions" % count) + print(" Average = %.1fms" % (total / count)) + print(" Mini = %.1fms @ %fs" % (mini[0], mini[1] / 1000)) + print(" Maxi = %.1fms @ %fs" % (maxi[0], maxi[1] / 1000)) + + +def main(): + parser = argparse.ArgumentParser() + parser.add_argument('file', help='File to parse', type=argparse.FileType('r')) + parser.add_argument('--trigger', help='The column in the csv file where is your trigger', type=int, required=True) + parser.add_argument('--pwm', help='The column in the csv file where is your PWM output', type=int) + parser.add_argument('--sbus', help='The column in the csv file where is your SBUS output', type=int) + parser.add_argument('--highval', help='The value of SBUS byte 2 when trigger=HIGH', type=int, default=0x13) + parser.add_argument('--lowval', help='The value of SBUS byte 2 when trigger=LOW', type=int, default=0xAC) + args = parser.parse_args() + if not args.pwm and not args.sbus: + print("Either a PWM or SBUS column in CSV must be specified") + exit() + + trigger_transitions = build_transitions_array(args.file, args.trigger) + + sbus_transitions = build_transitions_array(args.file, args.sbus) + sbus_frames = SBusFrame.get_frames(sbus_transitions) + for frame in sbus_frames: + if frame.is_lost(): + print("Frame lost bit @ %fs" % frame.start()) + + print_statistics(trigger_transitions, sbus_frames, args.highval, args.lowval) + + +if __name__ == "__main__": + main()