Skip to content

Add support for PyOCD and labgrid-client reset subcommand - #1944

Open
gumulka wants to merge 10 commits into
labgrid-project:masterfrom
gumulka:mcu-programming
Open

Add support for PyOCD and labgrid-client reset subcommand#1944
gumulka wants to merge 10 commits into
labgrid-project:masterfrom
gumulka:mcu-programming

Conversation

@gumulka

@gumulka gumulka commented Jul 25, 2026

Copy link
Copy Markdown
Contributor

Description

This PR adds support for the pyocd command line tool. A Python based tool for programming Arm Cortex microcontrollers.

The command line tool was used instead of the API, because its integration was easier and it does not add a dependency to the project.

OpenOCD is great, but working with the config files, can become quite convoluted. PyOCD on the other hand tries to figure most stuff out on its own, but does take Hints like --target to define which MCU is targeted. This makes it much easier to use for programming devices using labgrids bootstrap mechanism.

labgrid-client bootstrap <file> has been extended to use pyocd, when a USBDebugging Interface is available on the exporter.

A new subcommand labgrid-client reset has been added to make use of the ResetProtocol to reset a target.

I'm currently using a single CMSIS DAP-Link controller for programming, power and serial. Since Serial and Power are done using the same USB-Port, the boot messages can only be read through a reset command and not with a power-cycle.

I did test some more complex commands like:
labgrid-client bootstrap build/zephyr/zephyr.signed.hex target_name=nrf52832 load_commands="-e sector -a 0x6000" and everything works fine.

Checklist

  • Documentation for the feature
  • Tests for the feature
  • The arguments and description in doc/configuration.rst have been updated
  • Add a section on how to use the feature to doc/usage.rst
  • Add a section on how to use the feature to doc/development.rst
  • PR has been tested
  • Man pages have been regenerated

PyOCD currently does not support distinguishing between adapters based on the USB-Path, but only based on serial. If there is no serial, a pseudo-serial is generated based on the path and some more data.
Maybe at some point it can be added, but not yet.

@codecov

codecov Bot commented Jul 25, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 87.73006% with 20 lines in your changes missing coverage. Please review.
✅ Project coverage is 62.5%. Comparing base (ebe3535) to head (f2abdc6).
✅ All tests successful. No failed tests found.

Files with missing lines Patch % Lines
labgrid/remote/client.py 80.5% 20 Missing ⚠️
Additional details and impacted files
@@           Coverage Diff            @@
##           master   #1944     +/-   ##
========================================
+ Coverage    61.0%   62.5%   +1.4%     
========================================
  Files         182     183      +1     
  Lines       14881   14995    +114     
========================================
+ Hits         9086    9373    +287     
+ Misses       5795    5622    -173     
Flag Coverage Δ
3.10 62.5% <87.7%> (+1.5%) ⬆️
3.11 62.5% <87.7%> (+1.4%) ⬆️
3.12 62.5% <87.7%> (+1.4%) ⬆️
3.13 62.4% <87.7%> (+1.4%) ⬆️
3.14 62.4% <87.7%> (+1.4%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

gumulka added 9 commits July 26, 2026 20:05
the SerialDriver does not support the ResetProtocol, so remove it from
documentation.

Signed-off-by: Fabian Pflug <fabian.pflug@gumulka.eu>
A CMSIS DAP cannot start a gdb server, but it can bootstrap a DUT.

Signed-off-by: Fabian Pflug <fabian.pflug@gumulka.eu>
The PyOCD tool is used for example by the zephyr project to flash the
devices. Add support for pyocd to bootstrap and reset a device under
test.

Signed-off-by: Fabian Pflug <fabian.pflug@gumulka.eu>
Add tests for pyocd and fix errors found during testing.

Since the PyOCDDriver will call the pyocd binary, most tests mock away
the call to pyocd, since it will only work with hardware attached.

Signed-off-by: Fabian Pflug <fabian.pflug@gumulka.eu>
The PyOCDDriver does support the BootstrapProtocol and could be used to
flash the device.

Signed-off-by: Fabian Pflug <fabian.pflug@gumulka.eu>
Move the search for a power driver into a seperate function, which also
respects the priority of power drivers. Not much of an improvement at
the moment, as all have the same priority, but could maybe done later.

This is in preparation for the reset command in client.

Signed-off-by: Fabian Pflug <fabian.pflug@gumulka.eu>
Move the search for IO driver into a seperate function, that also
handles priorities.

This is in preparation of the reset command.

Signed-off-by: Fabian Pflug <fabian.pflug@gumulka.eu>
Add reset subcommand to reset the board. With MCU development, the
Debugger, Serial and Power can all be delivered through one USB-Port. In
order to see the bootlog messages, a reset command needs to be send to
the DUT without powercycling it, as this would also powercycle the
USB->UART converter and defeat the point.

Since the PyOCDDriver supports reset as a NetworkUSBDebugger it should
be the prefered method, but do allow all other sources, that implement
the ResetProtocol.

Signed-off-by: Fabian Pflug <fabian.pflug@gumulka.eu>
Mock out the connection to the coordinator and ressource updates,
because they are handled inside the testcase.

Check that the right ressources get created and used during execution
and that an error is raised, if no resources are available.

Signed-off-by: Fabian Pflug <fabian.pflug@gumulka.eu>

@ozan956 ozan956 left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for implementing this! LGTM overall, I left only one comment regarding load_commands.

Comment thread labgrid/driver/pyocddriver.py Outdated
if isinstance(self.load_commands, str):
commands = self.load_commands.split()
else:
commands = self.load_commands

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I noticed this while testing two consecutive load() calls on the same PyOCDDriver instance with list-based load_commands.

For example:

    load_commands: ["--frequency", "100"]

Then, in a custom test script:

    driver.load("first.hex")
    driver.load("second.hex")

The first call mutates the configured list to:

    ["--frequency", "100", "first.hex"]

As a result, the second call invokes:

    pyocd load --frequency 100 first.hex second.hex

This does not occur across separate labgrid-client bootstrap invocations, since each CLI invocation creates a new process and driver instance. It can occur whenever the same driver object is reused programmatically.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey, thanks for pointing that out! I just added a new commit, that has a pytest and a fix for it.

When calling load multiple times with load_commands as an array, the
file to load was appended to the load_commands array, resulting in the
second load command failing.

Pointed out in labgrid-project#1944 (comment)

Signed-off-by: Fabian Pflug <fabian.pflug@gumulka.eu>
@jluebbe

jluebbe commented Jul 27, 2026

Copy link
Copy Markdown
Member

PyOCD currently does not support distinguishing between adapters based on the USB-Path, but only based on serial. If there is no serial, a pseudo-serial is generated based on the path and some more data.
Maybe at some point it can be added, but not yet.

Path-based matching is relatively important for labs with multiple places. Is there an upstream feature request for this? If not, please crate one and link to it here.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants