Hacktoberfest 2026: the issues maintainers tagged for October, open and beginner-friendly. Browse Hacktoberfest issues

CMSIS-DAP v2: a zero-length IN packet after a 512-byte reply is read as the next command's reply (Microchip PICkit Basic)

Open
#2,060 0 comments 0 reactions 0 assignees View on GitHub

Maintainers usually reply within 1 day

Nobody has claimed this yet.

Assessment

Difficulty
3/5
Estimated time
1-2 days
Newbie friendliness
50/100
Issue type
Bug
Clarity
Clearly specified
Activity status
Active
Tech stack
python
Domain
devtools

Research direction

Start in PyUSBv2.rx_task, where one ep_in.read() is taken per command and a zero-length read becomes the reply to the next command. Read the linked #1871 for the OUT-direction case. The reported workaround skips empty reads and reads again. Done when a 254-word read_memory_block32 succeeds on the PICkit Basic and the zero-length packets are skipped. Verifying this needs that probe or a CMSIS-DAP v2 device that sends a zero-length packet after a full-size reply.

Written by the indexing model from the issue text.

Description

pyOCD: 0.45.1, Windows 10, libusb from libusb-package, Python 3.14
Probe: Microchip MPLAB PICkit Basic on its CMSIS-DAP v2 firmware (switched with Microchip's pycmsisdapswitcher 2.0.2.19), USB high speed, VID 04D8 PID 90AC. Its CMSIS-DAP interface has bulk OUT 0x01 and bulk IN 0x82 (wMaxPacketSize 512), and SWO IN 0x83. DAP_Info reports a packet size of 512 and a packet count of 1.
Target: nRF51822 (-t nrf51822), SWD at 1 MHz.

What happens

When a reply is exactly 512 bytes, the probe ends the IN transfer with a zero-length packet. PyUSBv2.rx_task reads one USB packet per command, so it reads the 512-byte reply, and then the zero-length packet becomes the "reply" to the next command. In the trace below, a read_memory_block32(0x18000, 254) makes a DAP_TransferBlock of 127 words. Its reply is 4 + 127 × 4 = 512 bytes:

USB OUT> (5) 06 00 7f 00 0f
USB RD < (512) 06 7f 00 01 01 d0 0d 48 17 e0 0d 49 01 aa 0b f0 27 ...
USB OUT> (9) 05 00 02 05 f0 ed 00 e0 0f
USB RD < (0)

The next decode fails: IndexError: bytearray index out of range in _decode_transfer_block_data (if data[0] != Command.DAP_TRANSFER_BLOCK), or a DAP_Transfer decode error. The real reply to that command is never read, so it stays in the probe. Every command after it times out (USBTimeoutError [Errno 10060]) until the probe is unplugged and plugged back in.

Reads whose replies are shorter than 512 bytes work. I tested blocks of 16, 100, 127, 128 and 200 words, with replies of up to 511 bytes. Blocks of 254 and 255 words fail every time.

Workaround

No CMSIS-DAP reply is empty, so in rx_task, reading again when ep_in.read() returns 0 bytes fixes it:

while True:
    data = self.ep_in.read(self.packet_size, timeout=self.DEFAULT_USB_TIMEOUT_MS).tobytes()
    if data:
        break
self.rcv_data.put(data)

With this change, erasing an nRF51822, programming 166,548 bytes and reading all of them back works every time. It skips 160 zero-length packets per run.

Note

This is the IN-direction counterpart of #1871, which was about the OUT direction. The probe may be the one that is wrong to send a ZLP after a reply equal to the DAP packet size. But a zero-length read is never a valid CMSIS-DAP reply, so skipping it in rx_task costs nothing.

Dominant language
Python
Stars
1.5k
Forks
567
Avg merge
2d 6h
Merged PRs (30d)
20

Getting set up

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

More from pyocd/pyOCD

All issues in pyocd/pyOCD

Similar issues

More Python issues

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.