emdbg.bench.fmu
PX4 FMU test bench
The test bench consists of a PX4 FMU that is connected to a J-Link and a USB-Serial adapter for the NSH.
The main entry point is the emdbg.bench.fmu
function, which orchestrates
the entire setup and yields an initialized bench object.
The FMU firmware must be compiled externally and is automatically uploaded when the bench is set up. This ensures that you're always debugging the right firmware, otherwise you will see very weird behavior, since GDB uses the ELF file to locate statically allocated objects and understand types.
The FMU is then reset, held in that state, and then yielded from the function so that you can configure the test and then start the firmware execution when ready.
The FMU is configured for debugging by stopping all timers when the debugger halts the CPU, therefore from the device's perspective, no time is missed during debugging. If you want to know about the system load, you must start it before querying it's state, otherwise there is nothing to compare the current sample to.
GDB Configuration File
This module also loads a fmu.gdb
configuration file with many target-specific
GDB commands in addition to the PX4-specific commands defined by the Python
modules in emdbg.debug.gdb
.
px4_log_start {filename}
: Starts logging of the GDB prompt to a file.px4_log_stop
: Stops GDB logging.px4_fbreak {func}
: Places a breakpoint on the return of a function.px4_btfbreak {func}
: Same aspx4_fbreak
but prints a backtrace too.px4_breaktrace{,10,100} {func}
: Sets a breakpoint on a function and callspx4_backtrace
to produce a call chain that can be interpreted byemdbg.analyze.callgraph
. The 10 and 100 suffixes indicate that the backtrace is only generated every 10 or 100 hits of the breakpoint.px4_commands_backtrace{,10,100}
: Attach a backtrace to a previously defined breakpoint or watchpoint.px4_breaktrace
uses this for breakpoints.px4_calltrace_semaphore_boosts
: set breaktraces in thesem_holder.c
file to log task (de-)boosts that can be interpreted byemdbg.analyze.priority
.
To trace a watchpoint, you need to define it yourself and attach a backtrace:
(gdb) watch variable
(gdb) px4_commands_backtrace
Scripting
This module gives you all functionality to automate a test. Remember that the bench automatically uploads the firmware.
with emdbg.bench.fmu(px4_dir, target, nsh_serial) as bench:
# FMU is flashed with the firmware, reset and halted
# Configure your test now
bench.gdb.execute("px4_log_start log.txt")
# Now execute from the reset handler onwards
bench.gdb.continue_nowait()
# wait for NSH prompt
bench.nsh.wait_for_prompt()
# Start the system load monitor
bench.restart_system_load_monitor()
bench.sleep(3)
# Update system load monitor
bench.restart_system_load_monitor()
# Wait a little
bench.sleep(5)
# interrupt and automatically continue
with bench.gdb.interrupt_continue():
# Print the task list and cpu load
print(emdbg.debug.px4.all_tasks_as_table(bench.gdb))
# Dump the core
bench.gdb.coredump()
bench.sleep(10)
# interrupt at the end
bench.gdb.interrupt_and_wait()
# Deconfigure GDB
bench.gdb.execute("px4_log_stop")
Command Line Interface
To quickly debug something interactively on the test bench, you can launch GDB directly with the debug backend of your choice:
--jlink
: connect via J-Link debug probe.--stlink
: connect via STLink debug probe.--orbtrace
: connect via Orbtrace mini debug probe.--coredump
: Use the CrashDebug backend with a coredump or PX4 hardfault log.
python3 -m emdbg.bench.fmu --px4-dir path/to/PX4-Autopilot \
--target px4_fmu-v5x_default -ui tui --stlink
python3 -m emdbg.bench.fmu --px4-dir path/to/PX4-Autopilot \
--target px4_fmu-v6x_default --coredump px4_hardfault.log
1# Copyright (c) 2023, Auterion AG 2# SPDX-License-Identifier: BSD-3-Clause 3 4""" 5.. include:: fmu.md 6""" 7 8from __future__ import annotations 9import time 10from functools import cached_property 11from pathlib import Path 12from contextlib import contextmanager, nullcontext 13import emdbg 14 15 16class Fmu: 17 """ 18 FMU test bench with optional nsh, power, and logic analyzer attachments. 19 """ 20 def _DBGMCU_CONFIG(target): 21 # Vector catch all exceptions, but not reset 22 common = ["px4_enable_vector_catch"] 23 # Halt all timers and peripherals while debugging 24 if "fmu-v5x" in target: 25 return ["set *0xE0042008 = 0xffffffff", 26 "set *0xE004200C = 0xffffffff"] + common 27 if "fmu-v6x" in target: 28 return ["set *0xE00E1034 = 0xffffffff", 29 "set *0xE00E103C = 0xffffffff", 30 "set *0xE00E104C = 0xffffffff", 31 "set *0xE00E1054 = 0xffffffff"] + common 32 return [] 33 34 def __init__(self, target: str, elf: Path, 35 gdb: "emdbg.debug.remote.gdb.Interface", 36 nsh: "emdbg.serial.protocol.Nsh" = None, 37 power: "emdbg.power.base.Base" = None, 38 io: "emdbg.io.digilent.Digilent" = None): 39 self.elf: Path = elf 40 """The ELF file under debug""" 41 self.gdb: "emdbg.debug.remote.gdb.Interface" = gdb 42 """The remote GDB access interface object""" 43 self.nsh: "emdbg.serial.protocol.Nsh" = nsh 44 """The NSH protocol controller""" 45 self.power: "emdbg.power.base.Base" = power 46 """The power relay controlling the FMU""" 47 self.io: "emdbg.io.digilent.Digilent" = io 48 """The Digilent Scope""" 49 self._target = target 50 51 def _init(self): 52 self.gdb.interrupt_and_wait() 53 for cmd in self._DBGMCU_CONFIG(self._target): 54 self.gdb.execute(cmd) 55 self.restart_system_load_monitor() 56 57 def _deinit(self): 58 self.gdb.interrupt_and_wait() 59 self.gdb.execute("disconnect") 60 self.gdb.interrupted = False 61 # Give JLink some time to react to the disconnect before shut down 62 time.sleep(1) 63 64 def restart_system_load_monitor(self): 65 """See `emdbg.debug.px4.system_load.restart_system_load_monitor`""" 66 with self.gdb.interrupt_continue(): 67 if self.gdb.type == "mi": 68 self.gdb.execute("python px4.restart_system_load_monitor(gdb)") 69 else: 70 emdbg.debug.px4.restart_system_load_monitor(self.gdb) 71 72 def coredump(self, filename: Path = None): 73 """ 74 Dumps the FMU core for later analysis with CrashDebug (see 75 `emdbg.debug.crashdebug`). 76 77 :param filename: Default `coredump_{datetime}.txt`. 78 """ 79 with self.gdb.interrupt_continue(): 80 if False: 81 # Connection is remote, therefore we must use slower RPyC interface 82 emdbg.debug.px4.coredump(self.gdb, filename=filename) 83 else: 84 # Executing directly on the GDB process is *significantly* faster! 85 if filename: filename = f"--file '{filename}'" 86 self.gdb.execute(f"px4_coredump {filename or ''}") 87 88 def upload(self, source: Path = None): 89 """ 90 Uploads the ELF file to the FMU, resets the device, clears the NSH 91 :param source: optional path to ELF file, default is the ELF file passed 92 to the constructor. 93 """ 94 with self.gdb.interrupt_continue(): 95 self.gdb.execute("monitor reset") 96 self.gdb.execute(f"load {source or self.elf}", timeout=60) 97 self.reset() 98 if self.nsh is not None: 99 self.nsh.clear() 100 101 def power_on_reset(self, delay_off: float = 1, delay_on: float = 2): 102 """ 103 Disconnects GDB, stops the backend, power cycles the entire bench, 104 restarts the backend, reconnects and reinitializes GDB. 105 **Only works if initialized with a power relay!** 106 """ 107 if self.power is None: 108 LOGGER.warning("Power Relay not configured!") 109 return 110 # Disconnect and stop the backend 111 self._deinit() 112 self.gdb.backend.stop() 113 # Wait for JLink to shut down, os.waitpid is not enough 114 time.sleep(1) 115 116 # Have you tried turning it off and on again? 117 self.power.cycle(delay_off, delay_on) 118 119 # Restart and reattach the backend 120 self.gdb.backend.start() 121 # Wait for JLink to start up again, since it starts non-blocking 122 time.sleep(2) 123 # Reinitialize GDB to attach back to JLink 124 for cmd in self.gdb.backend.init(self.elf): 125 self.gdb.execute(cmd) 126 self._init() 127 128 def reset(self): 129 """Resets the FMU""" 130 with self.gdb.interrupt_continue(): 131 self.gdb.execute("monitor reset") 132 self._init() 133 134 def sleep(self, seconds: float, func=None): 135 """ 136 Sleeps and calls `func` every second. This can be useful for polling if 137 a condition has happened. 138 """ 139 while(seconds > 0): 140 seconds -= 1 141 time.sleep(1) 142 if func is not None: 143 func(seconds) 144 145 146def _px4_config(px4_directory: Path, target: Path, commands: list[str] = None, 147 ui: str = None, speed: int = 16000, backend: str = None) -> tuple: 148 if "fmu-v5x" in target: 149 device = "STM32F765II" 150 config = "fmu_v5x" 151 elif "fmu-v6x" in target: 152 device = "STM32H753II" 153 config = "fmu_v6x" 154 else: 155 raise ValueError(f"Unknown device for '{target}'!") 156 157 px4_dir = Path(px4_directory).absolute().resolve() 158 data_dir = Path(__file__).parent.resolve() / "data" 159 160 if backend in ["stlink", "orbtrace"]: 161 config += f"_{backend}.cfg" 162 backend_obj = emdbg.debug.OpenOcdBackend(config=[data_dir / config]) 163 elif backend == "jlink": 164 rtos_so = px4_dir / f"platforms/nuttx/NuttX/nuttx/tools/jlink-nuttx.so" 165 if not rtos_so.exists(): rtos_so = None 166 backend_obj = emdbg.debug.JLinkBackend(device, speed, rtos_so) 167 elif isinstance(backend, Path): 168 backend_obj = emdbg.debug.CrashProbeBackend(backend) 169 elif ":" in backend: 170 backend_obj = emdbg.debug.ProbeBackend(backend) 171 else: 172 raise ValueError(f"Unknown backend '{backend}'!") 173 174 boot_elf = None 175 if Path(target).suffix == ".elf": 176 elf = Path(target) 177 else: 178 if (elf := next((px4_dir / "build" / target).glob("*.elf"), None)) is None: 179 raise ValueError(f"Cannot find ELF file in build folder '{px4_dir}/build/{target}'!") 180 if "_default" in target: # try to find the bootloader elf too 181 boot_elf = next((px4_dir / "build" / target.replace("_default", "_bootloader")).glob("*.elf"), None) 182 183 cmds = [f"dir {px4_dir}", 184 f"source {data_dir}/fmu.gdb", 185 f"python px4._TARGET='{target.lower()}'"] 186 if boot_elf is not None: 187 cmds += [f"add-symbol-file {boot_elf}"] 188 if ui is not None and backend_obj.name != "crashdebug": 189 cmds += Fmu._DBGMCU_CONFIG(target) 190 cmds += (commands or []) 191 192 return backend_obj, elf, cmds 193 194 195# ----------------------------------------------------------------------------- 196@contextmanager 197def debug(px4_directory: Path, target: str, serial: str = None, 198 digilent: str = None, power: "emdbg.power.base.Base" = None, 199 ui: str = None, commands: list[str] = None, with_rpyc: bool = False, 200 keep_power_on: bool = True, upload: bool = True, backend: str = None, 201 _FmuClass = Fmu) -> Fmu: 202 """ 203 Launches and configures the Fmu test bench. 204 1. Switches on the power relay if specified. 205 2. Starts the J-Link GDB server in the background. 206 3. Launches GDB and connects it to the debug probe backend. 207 4. Connects and initializes the NSH if specified. 208 5. Connects and initializes the Digilent Scope if specified. 209 6. Loads the PX4 specific GDB scripts, the `emdbg.debug.px4` 210 modules Python modules, and the `fmu.gdb` script. 211 7. Uploads the latest firmware to the FMU. 212 8. Yields the test bench in halted reset state. 213 214 :param px4_directory: path to the PX4-Autopilot repository you want to debug. 215 :param target: target name as a string, for example, `px4_fmu-v5x`. 216 Can also be a path to an ELF file. 217 :param serial: optional serial number of the USB-Serial bridge for the NSH. 218 If `None`, then `Fmu.nsh` is empty and cannot be used. 219 :param digilent: optional serial number of the Digilent. If `None`, then 220 `Fmu.io` is empty and cannot be used. 221 :param ui: If not `None`, then this launches the interactive debugger via 222 `emdbg.debug.gdb.call` instead of the scripting API. 223 :param commands: list of additional GDB commands to execute during launch. 224 :param with_rpyc: Use the RPyC GDB interface implementation, rather than the 225 GDB/MI interface. See `emdbg.debug.remote`. 226 :param keep_power_on: Do not shut off the Fmu after the context has closed. 227 :param upload: Automatically upload the firmware after powering on the FMU. 228 :param backend: `openocd`, `jlink`, or `IP:PORT` for a GDB server on another machine. 229 230 :return: A configured test bench with the latest firmware. 231 """ 232 backend, elf, cmds = _px4_config(px4_directory, target, commands, ui, 233 backend=backend or "openocd") 234 235 with (nullcontext() if power is None else power) as pwr: 236 try: 237 if ui is not None: 238 # Manual mode that only connects the debugger (blocking) 239 if power: pwr.on() 240 yield emdbg.debug.gdb.call(backend, elf, commands=cmds, ui=ui) 241 else: 242 # Turn off, then connect the serial 243 if power: pwr.off() 244 serial = emdbg.serial.nsh(serial) if serial is not None else nullcontext() 245 scope = emdbg.io.analog_discovery(digilent) if digilent is not None else nullcontext() 246 with serial as nsh, scope as io: 247 # Then power on and connect GDB to get the full boot log 248 if power: pwr.on() 249 gdb_call = emdbg.debug.gdb.call_rpyc if with_rpyc else emdbg.debug.gdb.call_mi 250 debugger = gdb_call(backend, elf, commands=cmds) 251 with debugger as gdb: 252 bench = _FmuClass(target, elf, gdb, nsh, pwr, io) 253 if upload: bench.upload() 254 yield bench 255 bench._deinit() 256 finally: 257 if not keep_power_on: 258 pwr.off(delay=0) 259 260 261# ----------------------------------------------------------------------------- 262@contextmanager 263def shell(serial: str = None, power = None, keep_power_on: bool = True) -> Fmu: 264 """ 265 Launches and configures the Fmu test bench. 266 1. Switches on the power relay if specified. 267 4. Connects and initializes the NSH if specified. 268 8. Yields the NSH after reset state. 269 270 :param serial: optional serial number of the USB-Serial bridge for the NSH. 271 If `None`, then `Fmu.nsh` is empty and cannot be used. 272 :param keep_power_on: Do not shut off the Fmu after the context has closed. 273 274 :return: A configured test bench with the latest firmware. 275 """ 276 277 if power is None: power = nullcontext() 278 with power as pwr: 279 try: 280 # Turn off, then connect the serial 281 pwr.off() 282 serial = emdbg.serial.nsh(serial) if serial is not None else nullcontext() 283 with serial as nsh: 284 # Then power on and connect GDB to get the full boot log 285 pwr.on() 286 yield nsh 287 finally: 288 if not keep_power_on: 289 pwr.off(delay=0) 290 291# ----------------------------------------------------------------------------- 292def _arguments(description, modifier=None): 293 import argparse, emdbg 294 parser = argparse.ArgumentParser(description=description) 295 parser.add_argument( 296 "--px4-dir", 297 default=".", 298 type=Path, 299 help="The PX4 root directory you are working on.") 300 parser.add_argument( 301 "--target", 302 default="px4_fmu-v5x", 303 help="The target you want to debug.") 304 group = parser.add_mutually_exclusive_group(required=True) 305 group.add_argument( 306 "--jlink", 307 default=False, 308 action="store_true", 309 help="Use a J-Link debug probe") 310 group.add_argument( 311 "--stlink", 312 default=False, 313 action="store_true", 314 help="Use an STLink debug probe") 315 group.add_argument( 316 "--orbtrace", 317 default=False, 318 action="store_true", 319 help="Use an ORBtrace mini debug probe") 320 group.add_argument( 321 "--remote", 322 help="Connect to a remote GDB server: 'IP:PORT'") 323 group.add_argument( 324 "--coredump", 325 type=Path, 326 help="Inspect a GDB coredump or PX4 hardfault log.") 327 parser.add_argument( 328 "--ui", 329 default="cmd", 330 choices=["tui", "gdbgui", "cmd", "batch"], 331 help="The user interface you want to use.") 332 parser.add_argument( 333 "-v", 334 dest="verbosity", 335 action="count", 336 default=0, 337 help="Verbosity level.") 338 parser.add_argument( 339 "-ex", 340 dest="commands", 341 action="append", 342 help="Extra GDB commands.") 343 if modifier: modifier(parser) 344 args = parser.parse_args() 345 emdbg.logger.configure(args.verbosity) 346 backend = args.remote 347 if args.stlink: backend = "stlink" 348 if args.jlink: backend = "jlink" 349 if args.orbtrace: backend = "orbtrace" 350 if args.coredump: backend = args.coredump 351 return args, backend 352 353 354if __name__ == "__main__": 355 args, backend = _arguments("Debug FMU") 356 357 with debug(args.px4_dir, args.target, ui=args.ui, commands=args.commands, 358 backend=backend) as gdb_call: 359 exit(gdb_call)
17class Fmu: 18 """ 19 FMU test bench with optional nsh, power, and logic analyzer attachments. 20 """ 21 def _DBGMCU_CONFIG(target): 22 # Vector catch all exceptions, but not reset 23 common = ["px4_enable_vector_catch"] 24 # Halt all timers and peripherals while debugging 25 if "fmu-v5x" in target: 26 return ["set *0xE0042008 = 0xffffffff", 27 "set *0xE004200C = 0xffffffff"] + common 28 if "fmu-v6x" in target: 29 return ["set *0xE00E1034 = 0xffffffff", 30 "set *0xE00E103C = 0xffffffff", 31 "set *0xE00E104C = 0xffffffff", 32 "set *0xE00E1054 = 0xffffffff"] + common 33 return [] 34 35 def __init__(self, target: str, elf: Path, 36 gdb: "emdbg.debug.remote.gdb.Interface", 37 nsh: "emdbg.serial.protocol.Nsh" = None, 38 power: "emdbg.power.base.Base" = None, 39 io: "emdbg.io.digilent.Digilent" = None): 40 self.elf: Path = elf 41 """The ELF file under debug""" 42 self.gdb: "emdbg.debug.remote.gdb.Interface" = gdb 43 """The remote GDB access interface object""" 44 self.nsh: "emdbg.serial.protocol.Nsh" = nsh 45 """The NSH protocol controller""" 46 self.power: "emdbg.power.base.Base" = power 47 """The power relay controlling the FMU""" 48 self.io: "emdbg.io.digilent.Digilent" = io 49 """The Digilent Scope""" 50 self._target = target 51 52 def _init(self): 53 self.gdb.interrupt_and_wait() 54 for cmd in self._DBGMCU_CONFIG(self._target): 55 self.gdb.execute(cmd) 56 self.restart_system_load_monitor() 57 58 def _deinit(self): 59 self.gdb.interrupt_and_wait() 60 self.gdb.execute("disconnect") 61 self.gdb.interrupted = False 62 # Give JLink some time to react to the disconnect before shut down 63 time.sleep(1) 64 65 def restart_system_load_monitor(self): 66 """See `emdbg.debug.px4.system_load.restart_system_load_monitor`""" 67 with self.gdb.interrupt_continue(): 68 if self.gdb.type == "mi": 69 self.gdb.execute("python px4.restart_system_load_monitor(gdb)") 70 else: 71 emdbg.debug.px4.restart_system_load_monitor(self.gdb) 72 73 def coredump(self, filename: Path = None): 74 """ 75 Dumps the FMU core for later analysis with CrashDebug (see 76 `emdbg.debug.crashdebug`). 77 78 :param filename: Default `coredump_{datetime}.txt`. 79 """ 80 with self.gdb.interrupt_continue(): 81 if False: 82 # Connection is remote, therefore we must use slower RPyC interface 83 emdbg.debug.px4.coredump(self.gdb, filename=filename) 84 else: 85 # Executing directly on the GDB process is *significantly* faster! 86 if filename: filename = f"--file '{filename}'" 87 self.gdb.execute(f"px4_coredump {filename or ''}") 88 89 def upload(self, source: Path = None): 90 """ 91 Uploads the ELF file to the FMU, resets the device, clears the NSH 92 :param source: optional path to ELF file, default is the ELF file passed 93 to the constructor. 94 """ 95 with self.gdb.interrupt_continue(): 96 self.gdb.execute("monitor reset") 97 self.gdb.execute(f"load {source or self.elf}", timeout=60) 98 self.reset() 99 if self.nsh is not None: 100 self.nsh.clear() 101 102 def power_on_reset(self, delay_off: float = 1, delay_on: float = 2): 103 """ 104 Disconnects GDB, stops the backend, power cycles the entire bench, 105 restarts the backend, reconnects and reinitializes GDB. 106 **Only works if initialized with a power relay!** 107 """ 108 if self.power is None: 109 LOGGER.warning("Power Relay not configured!") 110 return 111 # Disconnect and stop the backend 112 self._deinit() 113 self.gdb.backend.stop() 114 # Wait for JLink to shut down, os.waitpid is not enough 115 time.sleep(1) 116 117 # Have you tried turning it off and on again? 118 self.power.cycle(delay_off, delay_on) 119 120 # Restart and reattach the backend 121 self.gdb.backend.start() 122 # Wait for JLink to start up again, since it starts non-blocking 123 time.sleep(2) 124 # Reinitialize GDB to attach back to JLink 125 for cmd in self.gdb.backend.init(self.elf): 126 self.gdb.execute(cmd) 127 self._init() 128 129 def reset(self): 130 """Resets the FMU""" 131 with self.gdb.interrupt_continue(): 132 self.gdb.execute("monitor reset") 133 self._init() 134 135 def sleep(self, seconds: float, func=None): 136 """ 137 Sleeps and calls `func` every second. This can be useful for polling if 138 a condition has happened. 139 """ 140 while(seconds > 0): 141 seconds -= 1 142 time.sleep(1) 143 if func is not None: 144 func(seconds)
FMU test bench with optional nsh, power, and logic analyzer attachments.
35 def __init__(self, target: str, elf: Path, 36 gdb: "emdbg.debug.remote.gdb.Interface", 37 nsh: "emdbg.serial.protocol.Nsh" = None, 38 power: "emdbg.power.base.Base" = None, 39 io: "emdbg.io.digilent.Digilent" = None): 40 self.elf: Path = elf 41 """The ELF file under debug""" 42 self.gdb: "emdbg.debug.remote.gdb.Interface" = gdb 43 """The remote GDB access interface object""" 44 self.nsh: "emdbg.serial.protocol.Nsh" = nsh 45 """The NSH protocol controller""" 46 self.power: "emdbg.power.base.Base" = power 47 """The power relay controlling the FMU""" 48 self.io: "emdbg.io.digilent.Digilent" = io 49 """The Digilent Scope""" 50 self._target = target
65 def restart_system_load_monitor(self): 66 """See `emdbg.debug.px4.system_load.restart_system_load_monitor`""" 67 with self.gdb.interrupt_continue(): 68 if self.gdb.type == "mi": 69 self.gdb.execute("python px4.restart_system_load_monitor(gdb)") 70 else: 71 emdbg.debug.px4.restart_system_load_monitor(self.gdb)
73 def coredump(self, filename: Path = None): 74 """ 75 Dumps the FMU core for later analysis with CrashDebug (see 76 `emdbg.debug.crashdebug`). 77 78 :param filename: Default `coredump_{datetime}.txt`. 79 """ 80 with self.gdb.interrupt_continue(): 81 if False: 82 # Connection is remote, therefore we must use slower RPyC interface 83 emdbg.debug.px4.coredump(self.gdb, filename=filename) 84 else: 85 # Executing directly on the GDB process is *significantly* faster! 86 if filename: filename = f"--file '{filename}'" 87 self.gdb.execute(f"px4_coredump {filename or ''}")
Dumps the FMU core for later analysis with CrashDebug (see
emdbg.debug.crashdebug
).
Parameters
- filename: Default
coredump_{datetime}.txt
.
89 def upload(self, source: Path = None): 90 """ 91 Uploads the ELF file to the FMU, resets the device, clears the NSH 92 :param source: optional path to ELF file, default is the ELF file passed 93 to the constructor. 94 """ 95 with self.gdb.interrupt_continue(): 96 self.gdb.execute("monitor reset") 97 self.gdb.execute(f"load {source or self.elf}", timeout=60) 98 self.reset() 99 if self.nsh is not None: 100 self.nsh.clear()
Uploads the ELF file to the FMU, resets the device, clears the NSH
Parameters
- source: optional path to ELF file, default is the ELF file passed to the constructor.
102 def power_on_reset(self, delay_off: float = 1, delay_on: float = 2): 103 """ 104 Disconnects GDB, stops the backend, power cycles the entire bench, 105 restarts the backend, reconnects and reinitializes GDB. 106 **Only works if initialized with a power relay!** 107 """ 108 if self.power is None: 109 LOGGER.warning("Power Relay not configured!") 110 return 111 # Disconnect and stop the backend 112 self._deinit() 113 self.gdb.backend.stop() 114 # Wait for JLink to shut down, os.waitpid is not enough 115 time.sleep(1) 116 117 # Have you tried turning it off and on again? 118 self.power.cycle(delay_off, delay_on) 119 120 # Restart and reattach the backend 121 self.gdb.backend.start() 122 # Wait for JLink to start up again, since it starts non-blocking 123 time.sleep(2) 124 # Reinitialize GDB to attach back to JLink 125 for cmd in self.gdb.backend.init(self.elf): 126 self.gdb.execute(cmd) 127 self._init()
Disconnects GDB, stops the backend, power cycles the entire bench, restarts the backend, reconnects and reinitializes GDB. Only works if initialized with a power relay!
129 def reset(self): 130 """Resets the FMU""" 131 with self.gdb.interrupt_continue(): 132 self.gdb.execute("monitor reset") 133 self._init()
Resets the FMU
135 def sleep(self, seconds: float, func=None): 136 """ 137 Sleeps and calls `func` every second. This can be useful for polling if 138 a condition has happened. 139 """ 140 while(seconds > 0): 141 seconds -= 1 142 time.sleep(1) 143 if func is not None: 144 func(seconds)
Sleeps and calls func
every second. This can be useful for polling if
a condition has happened.
197@contextmanager 198def debug(px4_directory: Path, target: str, serial: str = None, 199 digilent: str = None, power: "emdbg.power.base.Base" = None, 200 ui: str = None, commands: list[str] = None, with_rpyc: bool = False, 201 keep_power_on: bool = True, upload: bool = True, backend: str = None, 202 _FmuClass = Fmu) -> Fmu: 203 """ 204 Launches and configures the Fmu test bench. 205 1. Switches on the power relay if specified. 206 2. Starts the J-Link GDB server in the background. 207 3. Launches GDB and connects it to the debug probe backend. 208 4. Connects and initializes the NSH if specified. 209 5. Connects and initializes the Digilent Scope if specified. 210 6. Loads the PX4 specific GDB scripts, the `emdbg.debug.px4` 211 modules Python modules, and the `fmu.gdb` script. 212 7. Uploads the latest firmware to the FMU. 213 8. Yields the test bench in halted reset state. 214 215 :param px4_directory: path to the PX4-Autopilot repository you want to debug. 216 :param target: target name as a string, for example, `px4_fmu-v5x`. 217 Can also be a path to an ELF file. 218 :param serial: optional serial number of the USB-Serial bridge for the NSH. 219 If `None`, then `Fmu.nsh` is empty and cannot be used. 220 :param digilent: optional serial number of the Digilent. If `None`, then 221 `Fmu.io` is empty and cannot be used. 222 :param ui: If not `None`, then this launches the interactive debugger via 223 `emdbg.debug.gdb.call` instead of the scripting API. 224 :param commands: list of additional GDB commands to execute during launch. 225 :param with_rpyc: Use the RPyC GDB interface implementation, rather than the 226 GDB/MI interface. See `emdbg.debug.remote`. 227 :param keep_power_on: Do not shut off the Fmu after the context has closed. 228 :param upload: Automatically upload the firmware after powering on the FMU. 229 :param backend: `openocd`, `jlink`, or `IP:PORT` for a GDB server on another machine. 230 231 :return: A configured test bench with the latest firmware. 232 """ 233 backend, elf, cmds = _px4_config(px4_directory, target, commands, ui, 234 backend=backend or "openocd") 235 236 with (nullcontext() if power is None else power) as pwr: 237 try: 238 if ui is not None: 239 # Manual mode that only connects the debugger (blocking) 240 if power: pwr.on() 241 yield emdbg.debug.gdb.call(backend, elf, commands=cmds, ui=ui) 242 else: 243 # Turn off, then connect the serial 244 if power: pwr.off() 245 serial = emdbg.serial.nsh(serial) if serial is not None else nullcontext() 246 scope = emdbg.io.analog_discovery(digilent) if digilent is not None else nullcontext() 247 with serial as nsh, scope as io: 248 # Then power on and connect GDB to get the full boot log 249 if power: pwr.on() 250 gdb_call = emdbg.debug.gdb.call_rpyc if with_rpyc else emdbg.debug.gdb.call_mi 251 debugger = gdb_call(backend, elf, commands=cmds) 252 with debugger as gdb: 253 bench = _FmuClass(target, elf, gdb, nsh, pwr, io) 254 if upload: bench.upload() 255 yield bench 256 bench._deinit() 257 finally: 258 if not keep_power_on: 259 pwr.off(delay=0)
Launches and configures the Fmu test bench.
- Switches on the power relay if specified.
- Starts the J-Link GDB server in the background.
- Launches GDB and connects it to the debug probe backend.
- Connects and initializes the NSH if specified.
- Connects and initializes the Digilent Scope if specified.
- Loads the PX4 specific GDB scripts, the
emdbg.debug.px4
modules Python modules, and thefmu.gdb
script. - Uploads the latest firmware to the FMU.
- Yields the test bench in halted reset state.
Parameters
- px4_directory: path to the PX4-Autopilot repository you want to debug.
- target: target name as a string, for example,
px4_fmu-v5x
. Can also be a path to an ELF file. - serial: optional serial number of the USB-Serial bridge for the NSH.
If
None
, thenFmu.nsh
is empty and cannot be used. - digilent: optional serial number of the Digilent. If
None
, thenFmu.io
is empty and cannot be used. - ui: If not
None
, then this launches the interactive debugger viaemdbg.debug.gdb.call
instead of the scripting API. - commands: list of additional GDB commands to execute during launch.
- with_rpyc: Use the RPyC GDB interface implementation, rather than the
GDB/MI interface. See
emdbg.debug.remote
. - keep_power_on: Do not shut off the Fmu after the context has closed.
- upload: Automatically upload the firmware after powering on the FMU.
- **backend:
openocd
,jlink
, orIP**: PORT
for a GDB server on another machine.
Returns
A configured test bench with the latest firmware.
263@contextmanager 264def shell(serial: str = None, power = None, keep_power_on: bool = True) -> Fmu: 265 """ 266 Launches and configures the Fmu test bench. 267 1. Switches on the power relay if specified. 268 4. Connects and initializes the NSH if specified. 269 8. Yields the NSH after reset state. 270 271 :param serial: optional serial number of the USB-Serial bridge for the NSH. 272 If `None`, then `Fmu.nsh` is empty and cannot be used. 273 :param keep_power_on: Do not shut off the Fmu after the context has closed. 274 275 :return: A configured test bench with the latest firmware. 276 """ 277 278 if power is None: power = nullcontext() 279 with power as pwr: 280 try: 281 # Turn off, then connect the serial 282 pwr.off() 283 serial = emdbg.serial.nsh(serial) if serial is not None else nullcontext() 284 with serial as nsh: 285 # Then power on and connect GDB to get the full boot log 286 pwr.on() 287 yield nsh 288 finally: 289 if not keep_power_on: 290 pwr.off(delay=0)
Launches and configures the Fmu test bench.
- Switches on the power relay if specified.
- Connects and initializes the NSH if specified.
- Yields the NSH after reset state.
Parameters
- serial: optional serial number of the USB-Serial bridge for the NSH.
If
None
, thenFmu.nsh
is empty and cannot be used. - keep_power_on: Do not shut off the Fmu after the context has closed.
Returns
A configured test bench with the latest firmware.