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 or "fmu-v6s" 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 elif "fmu-v6s" in target: 155 device = "STM32H743II" 156 config = "fmu_v6x" 157 else: 158 raise ValueError(f"Unknown device for '{target}'!") 159 160 px4_dir = Path(px4_directory).absolute().resolve() 161 data_dir = Path(__file__).parent.resolve() / "data" 162 163 if backend in ["stlink", "orbtrace"]: 164 config += f"_{backend}.cfg" 165 backend_obj = emdbg.debug.OpenOcdBackend(config=[data_dir / config]) 166 elif backend == "jlink": 167 rtos_so = px4_dir / f"platforms/nuttx/NuttX/nuttx/tools/jlink-nuttx.so" 168 if not rtos_so.exists(): rtos_so = None 169 backend_obj = emdbg.debug.JLinkBackend(device, speed, rtos_so) 170 elif isinstance(backend, Path): 171 backend_obj = emdbg.debug.CrashProbeBackend(backend) 172 elif ":" in backend: 173 backend_obj = emdbg.debug.ProbeBackend(backend) 174 else: 175 raise ValueError(f"Unknown backend '{backend}'!") 176 177 boot_elf = None 178 if Path(target).suffix == ".elf": 179 elf = Path(target) 180 else: 181 if (elf := next((px4_dir / "build" / target).glob("*.elf"), None)) is None: 182 raise ValueError(f"Cannot find ELF file in build folder '{px4_dir}/build/{target}'!") 183 if "_default" in target: # try to find the bootloader elf too 184 boot_elf = next((px4_dir / "build" / target.replace("_default", "_bootloader")).glob("*.elf"), None) 185 186 cmds = [f"dir {px4_dir}", 187 f"source {data_dir}/fmu.gdb", 188 f"python px4._TARGET='{target.lower()}'"] 189 if boot_elf is not None: 190 cmds += [f"add-symbol-file {boot_elf}"] 191 if ui is not None and backend_obj.name != "crashdebug": 192 cmds += Fmu._DBGMCU_CONFIG(target) 193 cmds += (commands or []) 194 195 return backend_obj, elf, cmds 196 197 198# ----------------------------------------------------------------------------- 199@contextmanager 200def debug(px4_directory: Path, target: str, serial: str = None, 201 digilent: str = None, power: "emdbg.power.base.Base" = None, 202 ui: str = None, commands: list[str] = None, with_rpyc: bool = False, 203 keep_power_on: bool = True, upload: bool = True, backend: str = None, 204 _FmuClass = Fmu) -> Fmu: 205 """ 206 Launches and configures the Fmu test bench. 207 1. Switches on the power relay if specified. 208 2. Starts the J-Link GDB server in the background. 209 3. Launches GDB and connects it to the debug probe backend. 210 4. Connects and initializes the NSH if specified. 211 5. Connects and initializes the Digilent Scope if specified. 212 6. Loads the PX4 specific GDB scripts, the `emdbg.debug.px4` 213 modules Python modules, and the `fmu.gdb` script. 214 7. Uploads the latest firmware to the FMU. 215 8. Yields the test bench in halted reset state. 216 217 :param px4_directory: path to the PX4-Autopilot repository you want to debug. 218 :param target: target name as a string, for example, `px4_fmu-v5x`. 219 Can also be a path to an ELF file. 220 :param serial: optional serial number of the USB-Serial bridge for the NSH. 221 If `None`, then `Fmu.nsh` is empty and cannot be used. 222 :param digilent: optional serial number of the Digilent. If `None`, then 223 `Fmu.io` is empty and cannot be used. 224 :param ui: If not `None`, then this launches the interactive debugger via 225 `emdbg.debug.gdb.call` instead of the scripting API. 226 :param commands: list of additional GDB commands to execute during launch. 227 :param with_rpyc: Use the RPyC GDB interface implementation, rather than the 228 GDB/MI interface. See `emdbg.debug.remote`. 229 :param keep_power_on: Do not shut off the Fmu after the context has closed. 230 :param upload: Automatically upload the firmware after powering on the FMU. 231 :param backend: `openocd`, `jlink`, or `IP:PORT` for a GDB server on another machine. 232 233 :return: A configured test bench with the latest firmware. 234 """ 235 backend, elf, cmds = _px4_config(px4_directory, target, commands, ui, 236 backend=backend or "openocd") 237 238 with (nullcontext() if power is None else power) as pwr: 239 try: 240 if ui is not None: 241 # Manual mode that only connects the debugger (blocking) 242 if power: pwr.on() 243 yield emdbg.debug.gdb.call(backend, elf, commands=cmds, ui=ui) 244 else: 245 # Turn off, then connect the serial 246 if power: pwr.off() 247 serial = emdbg.serial.nsh(serial) if serial is not None else nullcontext() 248 scope = emdbg.io.analog_discovery(digilent) if digilent is not None else nullcontext() 249 with serial as nsh, scope as io: 250 # Then power on and connect GDB to get the full boot log 251 if power: pwr.on() 252 gdb_call = emdbg.debug.gdb.call_rpyc if with_rpyc else emdbg.debug.gdb.call_mi 253 debugger = gdb_call(backend, elf, commands=cmds) 254 with debugger as gdb: 255 bench = _FmuClass(target, elf, gdb, nsh, pwr, io) 256 if upload: bench.upload() 257 yield bench 258 bench._deinit() 259 finally: 260 if not keep_power_on: 261 pwr.off(delay=0) 262 263 264# ----------------------------------------------------------------------------- 265@contextmanager 266def shell(serial: str = None, power = None, keep_power_on: bool = True) -> Fmu: 267 """ 268 Launches and configures the Fmu test bench. 269 1. Switches on the power relay if specified. 270 4. Connects and initializes the NSH if specified. 271 8. Yields the NSH after reset state. 272 273 :param serial: optional serial number of the USB-Serial bridge for the NSH. 274 If `None`, then `Fmu.nsh` is empty and cannot be used. 275 :param keep_power_on: Do not shut off the Fmu after the context has closed. 276 277 :return: A configured test bench with the latest firmware. 278 """ 279 280 if power is None: power = nullcontext() 281 with power as pwr: 282 try: 283 # Turn off, then connect the serial 284 pwr.off() 285 serial = emdbg.serial.nsh(serial) if serial is not None else nullcontext() 286 with serial as nsh: 287 # Then power on and connect GDB to get the full boot log 288 pwr.on() 289 yield nsh 290 finally: 291 if not keep_power_on: 292 pwr.off(delay=0) 293 294# ----------------------------------------------------------------------------- 295def _arguments(description, modifier=None): 296 import argparse, emdbg 297 parser = argparse.ArgumentParser(description=description) 298 parser.add_argument( 299 "--px4-dir", 300 default=".", 301 type=Path, 302 help="The PX4 root directory you are working on.") 303 parser.add_argument( 304 "--target", 305 default="px4_fmu-v5x", 306 help="The target you want to debug.") 307 group = parser.add_mutually_exclusive_group(required=True) 308 group.add_argument( 309 "--jlink", 310 default=False, 311 action="store_true", 312 help="Use a J-Link debug probe") 313 group.add_argument( 314 "--stlink", 315 default=False, 316 action="store_true", 317 help="Use an STLink debug probe") 318 group.add_argument( 319 "--orbtrace", 320 default=False, 321 action="store_true", 322 help="Use an ORBtrace mini debug probe") 323 group.add_argument( 324 "--remote", 325 help="Connect to a remote GDB server: 'IP:PORT'") 326 group.add_argument( 327 "--coredump", 328 type=Path, 329 help="Inspect a GDB coredump or PX4 hardfault log.") 330 parser.add_argument( 331 "--ui", 332 default="cmd", 333 choices=["tui", "gdbgui", "cmd", "batch"], 334 help="The user interface you want to use.") 335 parser.add_argument( 336 "-v", 337 dest="verbosity", 338 action="count", 339 default=0, 340 help="Verbosity level.") 341 parser.add_argument( 342 "-ex", 343 dest="commands", 344 action="append", 345 help="Extra GDB commands.") 346 if modifier: modifier(parser) 347 args = parser.parse_args() 348 emdbg.logger.configure(args.verbosity) 349 backend = args.remote 350 if args.stlink: backend = "stlink" 351 if args.jlink: backend = "jlink" 352 if args.orbtrace: backend = "orbtrace" 353 if args.coredump: backend = args.coredump 354 return args, backend 355 356 357if __name__ == "__main__": 358 args, backend = _arguments("Debug FMU") 359 360 with debug(args.px4_dir, args.target, ui=args.ui, commands=args.commands, 361 backend=backend) as gdb_call: 362 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 or "fmu-v6s" 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.
200@contextmanager 201def debug(px4_directory: Path, target: str, serial: str = None, 202 digilent: str = None, power: "emdbg.power.base.Base" = None, 203 ui: str = None, commands: list[str] = None, with_rpyc: bool = False, 204 keep_power_on: bool = True, upload: bool = True, backend: str = None, 205 _FmuClass = Fmu) -> Fmu: 206 """ 207 Launches and configures the Fmu test bench. 208 1. Switches on the power relay if specified. 209 2. Starts the J-Link GDB server in the background. 210 3. Launches GDB and connects it to the debug probe backend. 211 4. Connects and initializes the NSH if specified. 212 5. Connects and initializes the Digilent Scope if specified. 213 6. Loads the PX4 specific GDB scripts, the `emdbg.debug.px4` 214 modules Python modules, and the `fmu.gdb` script. 215 7. Uploads the latest firmware to the FMU. 216 8. Yields the test bench in halted reset state. 217 218 :param px4_directory: path to the PX4-Autopilot repository you want to debug. 219 :param target: target name as a string, for example, `px4_fmu-v5x`. 220 Can also be a path to an ELF file. 221 :param serial: optional serial number of the USB-Serial bridge for the NSH. 222 If `None`, then `Fmu.nsh` is empty and cannot be used. 223 :param digilent: optional serial number of the Digilent. If `None`, then 224 `Fmu.io` is empty and cannot be used. 225 :param ui: If not `None`, then this launches the interactive debugger via 226 `emdbg.debug.gdb.call` instead of the scripting API. 227 :param commands: list of additional GDB commands to execute during launch. 228 :param with_rpyc: Use the RPyC GDB interface implementation, rather than the 229 GDB/MI interface. See `emdbg.debug.remote`. 230 :param keep_power_on: Do not shut off the Fmu after the context has closed. 231 :param upload: Automatically upload the firmware after powering on the FMU. 232 :param backend: `openocd`, `jlink`, or `IP:PORT` for a GDB server on another machine. 233 234 :return: A configured test bench with the latest firmware. 235 """ 236 backend, elf, cmds = _px4_config(px4_directory, target, commands, ui, 237 backend=backend or "openocd") 238 239 with (nullcontext() if power is None else power) as pwr: 240 try: 241 if ui is not None: 242 # Manual mode that only connects the debugger (blocking) 243 if power: pwr.on() 244 yield emdbg.debug.gdb.call(backend, elf, commands=cmds, ui=ui) 245 else: 246 # Turn off, then connect the serial 247 if power: pwr.off() 248 serial = emdbg.serial.nsh(serial) if serial is not None else nullcontext() 249 scope = emdbg.io.analog_discovery(digilent) if digilent is not None else nullcontext() 250 with serial as nsh, scope as io: 251 # Then power on and connect GDB to get the full boot log 252 if power: pwr.on() 253 gdb_call = emdbg.debug.gdb.call_rpyc if with_rpyc else emdbg.debug.gdb.call_mi 254 debugger = gdb_call(backend, elf, commands=cmds) 255 with debugger as gdb: 256 bench = _FmuClass(target, elf, gdb, nsh, pwr, io) 257 if upload: bench.upload() 258 yield bench 259 bench._deinit() 260 finally: 261 if not keep_power_on: 262 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.
266@contextmanager 267def shell(serial: str = None, power = None, keep_power_on: bool = True) -> Fmu: 268 """ 269 Launches and configures the Fmu test bench. 270 1. Switches on the power relay if specified. 271 4. Connects and initializes the NSH if specified. 272 8. Yields the NSH after reset state. 273 274 :param serial: optional serial number of the USB-Serial bridge for the NSH. 275 If `None`, then `Fmu.nsh` is empty and cannot be used. 276 :param keep_power_on: Do not shut off the Fmu after the context has closed. 277 278 :return: A configured test bench with the latest firmware. 279 """ 280 281 if power is None: power = nullcontext() 282 with power as pwr: 283 try: 284 # Turn off, then connect the serial 285 pwr.off() 286 serial = emdbg.serial.nsh(serial) if serial is not None else nullcontext() 287 with serial as nsh: 288 # Then power on and connect GDB to get the full boot log 289 pwr.on() 290 yield nsh 291 finally: 292 if not keep_power_on: 293 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.