01 Why roll your own — L4R5 memory, boot & flash registers
A GUI programmer is fine on your desk. But automation — CI hardware-in-the-loop, board-bring-up scripts, field updates, batch programming — needs a deterministic, headless, exit-code-returning command. Every tool below reduces to the same three primitives: erase, program+verify, reset. To script them safely you must know the target's memory map and the FLASH controller it drives.
The STM32L4R5 is a Cortex-M4F (RM0432 reference manual, DS12023 datasheet) with up to 2 MB Flash (e.g. STM32L4R5ZI = 2 MB) and 640 KB SRAM. Flash is programmed as 72-bit words (a 64-bit double-word + 8 ECC bits), erased per page. Page size and bank layout depend on the DBANK option bit.
Memory map — the addresses your scripts pass
| Region | Base address | Size | Notes |
|---|---|---|---|
| Main Flash (user code) | 0x0800 0000 | up to 2 MB | Programming/flash offset for every tool |
| System memory (ST bootloader) | 0x1FFF 0000 | 28 KB | Factory USART/USB-DFU loader; entered via BOOT0 |
| OTP area | 0x1FFF 7000 | 1 KB | One-time-programmable |
| Option bytes | 0x1FFF 7800 | — | RDP, BOOT config, DBANK, WRP |
| SRAM1 | 0x2000 0000 | 192 KB | Default stack/heap; flash-loader RAM |
| SRAM2 | 0x1000 0000 (also aliased at 0x2003 0000) | 64 KB | Parity-protected, retained |
| SRAM3 | 0x2004 0000 | 384 KB | Large buffer RAM |
| Peripherals | 0x4000 0000 | — | FLASH ctrl at 0x4002 2000 |
FLASH controller registers (base 0x4002 2000)
Every host tool that writes flash either (a) drives these registers directly over SWD, or (b) uploads a tiny flash-loader into SRAM that drives them. Knowing them lets you debug a stuck program cycle from the OpenOCD/pyOCD console.
| Register | Offset | Purpose / key fields |
|---|---|---|
| FLASH_ACR | 0x00 | LATENCY wait-states, prefetch, caches |
| FLASH_KEYR | 0x08 | Unlock CR: write 0x45670123 then 0xCDEF89AB |
| FLASH_OPTKEYR | 0x0C | Unlock option bytes: 0x08192A3B then 0x4C5D6E7F |
| FLASH_SR | 0x10 | BSY(16), EOP(0), PROGERR(3), WRPERR(4), PGSERR(7), OPTVERR(15) |
| FLASH_CR | 0x14 | PG(0), PER(1), MER1(2), PNB[10:3], BKER(11), MER2(15), STRT(16), OPTSTRT(17), OBL_LAUNCH(27), OPTLOCK(30), LOCK(31) |
| FLASH_ECCR | 0x18 | ECC error address & flags |
| FLASH_OPTR | 0x20 | RDP[7:0], BFB2(20), DBANK(22), nBOOT1(23), nSWBOOT0(26), nBOOT0(27) |
Boot selection — how your script forces the ROM bootloader
With the default option bytes (nSWBOOT0 = 1) the physical BOOT0 pin chooses the boot source at reset. This is the mechanism serial/DFU flashing relies on.
| BOOT0 pin | nBOOT0 (if nSWBOOT0=0) | Boot source |
|---|---|---|
| 0 (GND) | 1 | Main Flash @ 0x0800 0000 — your app |
| 1 (VDD) | 0 | System memory @ 0x1FFF 0000 — ST bootloader (USART/USB-DFU) |
Empty-flash detection: if the first Flash word is 0xFFFF FFFF the ROM may fall through to the bootloader anyway. In dual-bank mode the BFB2 option bit and the bank-swap logic decide which bank maps to 0x0800 0000 — keep that in mind when a "successful" flash still boots the wrong image.
In this section
- Flash offset for every tool is 0x0800 0000; ROM bootloader lives at 0x1FFF 0000.
- Erase = per page; program = 64-bit double-words; FLASH_CR/SR/KEYR drive the cycle.
- BOOT0 high + reset = enter the factory USART/USB-DFU loader.
02 OpenOCD Tcl scripting: procs for flash + verify + reset
OpenOCD is a full Tcl interpreter. Anything you can type at the telnet localhost 4444 prompt can live in a .cfg/.tcl file as a reusable proc. Start every project with one committed config file so the whole team (and CI) flashes identically.
A reusable target config
# --- probe: ST-Link v2/v3 (Nucleo/Discovery on-board or standalone) ---
source [find interface/stlink.cfg]
# stlink.cfg defaults to SWD via the high-level adapter (hla_swd).
# For a v3 in DAP mode use: source [find interface/stlink-dap.cfg]
# --- target: STM32L4 family (covers L4R5) ---
source [find target/stm32l4x.cfg]
adapter speed 1800 # kHz; drop to 480 for long/flaky wiring
reset_config srst_nogate # use SRST for reliable reset-halt
Custom procs: erase → program → verify → run
The built-in program helper already does reset+verify+exit, but a hand-written proc gives you full control (mass-erase, option bytes, memory pokes). Append these to the config above.
# One-shot: full-chip erase, program ELF, verify, run, then quit OpenOCD.
proc flash_run {image} {
reset init # halt + init clocks for the flash loader
stm32l4x mass_erase 0 # erase bank 0 (use 1 for bank 2)
flash write_image erase "$image" 0 elf
verify_image "$image"
reset run
shutdown # exit with code 0 on success
}
# Program a raw binary at a chosen offset (default main flash).
proc flash_bin {image {offset 0x08000000}} {
program "$image" verify reset exit "$offset"
}
# Read a 32-bit word / poke memory from a script.
proc peek {addr} { mem2array v 32 $addr 1; return $v(0) }
proc poke {addr val} { mww $addr $val }
Invoking it headlessly (returns a shell exit code)
# ELF: verify + reset + exit in one line, no separate GDB needed
openocd -f stm32l4r5.cfg -c "flash_run build/app.elf"
# Built-in helper (equivalent for most cases)
openocd -f interface/stlink.cfg -f target/stm32l4x.cfg \
-c "program build/app.elf verify reset exit"
# Raw binary at the flash base
openocd -f stm32l4r5.cfg -c "flash_bin build/app.bin 0x08000000"
# Chain arbitrary commands with -c; each is one Tcl line
openocd -f stm32l4r5.cfg \
-c "init" -c "reset halt" \
-c "stm32l4x mass_erase 0" \
-c "flash write_image erase build/app.bin 0x08000000 bin" \
-c "reset run" -c "shutdown"
stm32l4x flash-driver command reference
| Command | Effect |
|---|---|
stm32l4x mass_erase 0 | Erase entire bank (0 = first flash bank object) |
stm32l4x unlock 0 | Remove read/write protection (triggers RDP regression, wipes flash) |
stm32l4x lock 0 | Re-enable protection |
stm32l4x option_read 0 0x20 | Read option register at offset (0x20 = FLASH_OPTR) |
stm32l4x option_write 0 0x20 val mask | Modify option bits (e.g. flip DBANK / nBOOT0) |
stm32l4x option_load 0 | Reload option bytes (OBL_LAUNCH — forces a reset) |
flash write_image erase F 0 elf | Erase-as-needed then write ELF file F |
verify_image F | Read-back compare against file F |
dump_image F 0x08000000 0x1000 | Read 4 KB of flash to a file (backup) |
OpenOCD default ports: gdb 3333, telnet 4444, tcl 6666. If a leftover openocd holds the ST-Link you get "LIBUSB_ERROR_BUSY". Always shutdown (or kill %1) at the end of a script.
03 GDB batch & command scripts (--batch -x / -ex)
When you need to flash and inspect — dump a variable, hit a breakpoint, compare-sections after load — script GDB itself. --batch runs a script and exits with a status code, making it CI-safe; -x file loads a command file; -ex "cmd" injects single commands inline.
A committed GDB command file
# Assumes a gdbserver (OpenOCD/pyOCD) is already listening on :3333
target extended-remote :3333
monitor reset halt # "monitor" forwards to OpenOCD/pyOCD
load # download the ELF sections into flash
compare-sections # read-back verify every loadable section
monitor reset run
detach
quit
Running it — server + client, then all-inline
# 1) Start the gdbserver in the background
openocd -f stm32l4r5.cfg &
OCD_PID=$!
# 2) Run the command file in batch mode (exit code propagates)
arm-none-eabi-gdb --batch -x flash.gdb build/app.elf
# 3) Clean up the server
kill $OCD_PID
# --- or: everything inline, no .gdb file needed ---
arm-none-eabi-gdb --batch \
-ex "target extended-remote :3333" \
-ex "monitor reset halt" \
-ex "load" \
-ex "compare-sections" \
-ex "monitor reset run" \
-ex "detach" -ex "quit" \
build/app.elf
Useful in-GDB commands for embedded scripting
| Command | What it does |
|---|---|
load | Write all loadable ELF sections to their addresses (flash on M4) |
compare-sections | CRC-compare each section vs. target — the built-in verify |
monitor reset halt | Pass reset halt to the underlying probe server |
monitor flash write_image erase f 0x08000000 | Drive OpenOCD's flash command from GDB |
x/4xw 0x08000000 | Examine 4 words at the flash base (check vector table) |
p/x $pc | Print the program counter — sanity after reset-halt |
set confirm off | Suppress y/n prompts — mandatory in --batch |
GDB reads .gdbinit from the CWD only if you opt in. In batch/CI use explicit -x/-ex and pass -nx to ignore stray init files, so the flash result is reproducible regardless of the developer's home config.
04 pyOCD: Python flashing, gdbserver & scripting API
pyOCD is a pure-Python programmer/debugger for Cortex-M over CMSIS-DAP, ST-Link and J-Link. It ships a CLI (flash, erase, reset, gdbserver, commander, pack, list) and a scriptable Python API — ideal for test rigs.
Install and add L4R5 device support (CMSIS-Pack)
The L4R5 isn't in the tiny built-in target set, so pull its device family pack. The exact target type string comes from the pack — don't guess it, list it.
pip install pyocd
pyocd pack update # refresh the pack index
pyocd pack find stm32l4r5 # show matching devices
pyocd pack install stm32l4r5 # download + register the DFP
# Confirm the precise target type (e.g. stm32l4r5xg / stm32l4r5xi)
pyocd list --targets | grep -i l4r5
pyocd list --probes # show connected probes + unique IDs
CLI flashing
TGT=stm32l4r5xi # use the string printed by "pyocd list --targets"
# ELF/HEX carry their own addresses; program + verify are automatic
pyocd flash -t $TGT build/app.elf
# Raw .bin needs an explicit base address
pyocd flash -t $TGT --base-address 0x08000000 build/app.bin
pyocd erase -t $TGT --chip # full mass erase (or --sector A B)
pyocd reset -t $TGT # hardware reset the target
# GDB server on :3333; --persist survives client disconnects (CI reuse)
pyocd gdbserver -t $TGT --persist --frequency 4000000
# Pick a specific probe when several are attached
pyocd flash -t $TGT -u 066EFF... build/app.elf
The Python API — full programmatic control
#!/usr/bin/env python3
# pip install pyocd ; requires the L4R5 pack installed (see above)
from pyocd.core.helpers import ConnectHelper
from pyocd.flash.file_programmer import FileProgrammer
TARGET = "stm32l4r5xi"
FLASH_BASE = 0x08000000
# session_with_chosen_probe picks the only probe, or prompts / honours -u
with ConnectHelper.session_with_chosen_probe(
target_override=TARGET,
options={"frequency": 4_000_000}) as session:
target = session.target
# Program a file (chip_erase: "sector" | "chip" | "auto")
prog = FileProgrammer(session, chip_erase="sector")
prog.program("build/app.hex") # .elf/.hex/.bin all supported
# For .bin: prog.program("app.bin", base_address=FLASH_BASE, file_format="bin")
# Inspect / poke memory directly over SWD
target.reset_and_halt()
sp = target.read32(FLASH_BASE) # initial stack pointer
reset = target.read32(FLASH_BASE + 4) # reset handler address
print(f"SP=0x{sp:08x} Reset=0x{reset:08x}")
target.write32(0x20000000, 0xDEADBEEF) # scribble RAM for a test
assert target.read32(0x20000000) == 0xDEADBEEF
target.reset_and_halt()
target.resume() # let the app run
pyOCD CLI subcommand reference
| Subcommand | Use |
|---|---|
pyocd flash | Program + verify an image (elf/hex/bin) |
pyocd erase | --chip mass erase or --sector ranges |
pyocd reset | Reset (and optionally halt) the target |
pyocd gdbserver | GDB remote on :3333 (add --persist) |
pyocd commander | Interactive REPL: read32, write32, reg, halt… |
pyocd pack | find / install / update CMSIS device packs |
pyocd list | --targets, --probes, --boards |
05 pylink: driving a J-Link from Python
If your bench uses a SEGGER J-Link, pylink-square wraps the official J-Link DLL in Python. It's the go-to for test infrastructure: connect by serial number, flash, and poke memory with a clean object API. The J-Link DLL must be installed; the device name is SEGGER's string, e.g. STM32L4R5ZI.
pip install pylink-square # import name is still "pylink"
# Requires SEGGER J-Link Software & Documentation Pack (provides the DLL)
#!/usr/bin/env python3
import pylink
DEVICE = "STM32L4R5ZI" # SEGGER device string (case-insensitive)
FLASH_BASE = 0x08000000
jlink = pylink.JLink()
jlink.open() # or JLink().open(serial_no=504302020)
jlink.set_tif(pylink.enums.JLinkInterfaces.SWD)
jlink.connect(DEVICE, speed="auto", verbose=True)
print("Core ID: 0x%08x" % jlink.core_id())
# flash_file() erases the needed sectors, programs, and verifies
jlink.reset(halt=True)
jlink.flash_file("build/app.bin", FLASH_BASE)
# Direct memory access mirrors pyOCD
sp = jlink.memory_read32(FLASH_BASE, 1)[0]
print("Initial SP: 0x%08x" % sp)
jlink.memory_write32(0x20000000, [0xDEADBEEF])
jlink.reset(halt=False) # reset and let the app run
jlink.close()
Key JLink methods
| Method | Purpose |
|---|---|
open(serial_no=None, ip_addr=None) | Open USB or IP J-Link connection |
set_tif(JLinkInterfaces.SWD) | Select SWD (or .JTAG) transport |
connect(chip_name, speed, verbose) | Attach to the target; speed in kHz or "auto"/"adaptive" |
flash_file(path, addr) | Erase + program + verify a binary at addr |
flash(data, addr) | Program an in-memory byte list |
erase() | Mass-erase; returns bytes erased |
reset(halt=False) / halt() / restart() | Reset control; reset(halt=True) stops at the vector |
memory_read32 / memory_write32(addr, ...) | 32-bit word memory access |
If erase()/flash() throws an "unspecified error", the core wasn't halted. Set a reset strategy before connecting, e.g. jlink.set_reset_strategy(pylink.enums.JLinkResetStrategyCortexM3.RESETPIN), then reset(halt=True).
06 Serial & USB bootloader: stm32flash, DFU, pyserial/pyusb
No SWD probe? Every L4R5 ships a factory ROM bootloader at 0x1FFF 0000 reachable over USART (AN3155 protocol) and USB Full-Speed DFU (DfuSe). This is how field updates and gang programmers work. Force it by holding BOOT0 high at reset.
Bootloader interfaces & pins (STM32L4Rxxx, AN2606)
| Interface | Pins | Host tool |
|---|---|---|
| USART1 | PA9 (TX) / PA10 (RX) | stm32flash |
| USART3 | PB10/PB11 or PC10/PC11 | stm32flash |
| USB FS DFU | PA11 (DM) / PA12 (DP) | dfu-util |
| I2C1/2/3, SPI1/2 | see AN2606 table for L4Rxxx/Sxxx | custom / STM32CubeProgrammer |
The USART bootloader runs at 8 data bits, even parity, 1 stop (8e1) and auto-detects baud from the initial 0x7F byte. The USB DFU device enumerates as VID:PID 0483:DF11, alternate setting 0 = "Internal Flash".
stm32flash — USART bootloader
# Probe: reads chip PID + bootloader version (confirms wiring/BOOT0)
stm32flash /dev/ttyUSB0
# Write + verify + jump to the app at the flash base
stm32flash -w build/app.bin -v -g 0x08000000 /dev/ttyUSB0
# Faster link (bootloader auto-bauds), .hex also accepted
stm32flash -b 115200 -w build/app.hex -v /dev/ttyUSB0
stm32flash -o /dev/ttyUSB0 # mass erase only
stm32flash -r backup.bin -S 0x08000000:0x1000 /dev/ttyUSB0 # read back 4 KB
# Auto-toggle BOOT0/RESET via DTR/RTS, then reset out via RTS after write
stm32flash -R -i '-dtr,rts,:dtr,-rts' -w build/app.bin -v /dev/ttyUSB0
dfu-util — USB DFU (system memory)
# With BOOT0 high + reset, the L4R5 appears as 0483:df11
dfu-util -l # list DFU devices + alt settings
# Program raw binary at the flash base, then leave DFU and run
dfu-util -a 0 -d 0483:df11 -s 0x08000000:leave -D build/app.bin
# DFU wants a .bin (or DfuSe .dfu): convert from ELF first
arm-none-eabi-objcopy -O binary build/app.elf build/app.bin
Roll your own: pyserial handshake (AN3155)
When you need a bespoke protocol (custom baud, encrypted payload, production log), talk the bootloader yourself. This minimal handshake confirms entry and reads the command set — the foundation for a full programmer.
#!/usr/bin/env python3
# pip install pyserial ; usage: boot_probe.py /dev/ttyUSB0
import serial, sys
INIT, ACK, NACK = 0x7F, 0x79, 0x1F
port = serial.Serial(sys.argv[1], 57600,
parity=serial.PARITY_EVEN, # 8e1 — required
stopbits=serial.STOPBITS_ONE,
timeout=1)
# 1) Auto-baud + enter: single 0x7F, expect ACK
port.write(bytes([INIT]))
if port.read(1) != bytes([ACK]):
sys.exit("no ACK - is BOOT0 high and the part in bootloader mode?")
# 2) Get command: 0x00 + its complement 0xFF (every cmd is checksummed)
port.write(bytes([0x00, 0xFF]))
if port.read(1) != bytes([ACK]):
sys.exit("Get command rejected")
n = port.read(1)[0] # number of bytes that follow (minus 1)
body = port.read(n + 1) # [bootloader_version, cmd0, cmd1, ...]
port.read(1) # trailing ACK
print("Bootloader version: 0x%02x" % body[0])
print("Supported cmds:", body[1:].hex())
pyusb: raw DFU enumeration
For USB DFU you can go under dfu-util with pyusb — useful to detect the device, read its interface string (the memory layout), or script a factory jig. For real transfers, dfu-util is still the pragmatic choice.
#!/usr/bin/env python3
# pip install pyusb (needs libusb backend)
import usb.core, usb.util
dev = usb.core.find(idVendor=0x0483, idProduct=0xDF11)
if dev is None:
raise SystemExit("STM32 DFU device not found (BOOT0 high + reset?)")
print("Found STM32 bootloader:", usb.util.get_string(dev, dev.iProduct))
for cfg in dev:
for intf in cfg:
s = usb.util.get_string(dev, intf.iInterface)
print(f" alt {intf.bAlternateSetting}: {s}") # e.g. "@Internal Flash /0x08000000/..."
07 CI flashing & a reusable flash.sh wrapper
Automation's payoff: a self-hosted CI runner with a probe attached that flashes and smoke-tests real hardware on every push. Wrap all methods behind one script so CI, bring-up, and production share the same command.
flash.sh — one wrapper, four backends
#!/usr/bin/env bash
# flash.sh <firmware.elf|.bin> [method] method: openocd|pyocd|dfu|serial
set -euo pipefail
FW="${1:?usage: flash.sh <firmware> [openocd|pyocd|dfu|serial]}"
METHOD="${2:-${FLASH_METHOD:-openocd}}"
FLASH_ADDR="${FLASH_ADDR:-0x08000000}"
CFG="${OOCD_CFG:-stm32l4r5.cfg}"
PORT="${PORT:-/dev/ttyUSB0}"
TGT="${PYOCD_TARGET:-stm32l4r5xi}"
# Return a .bin path (objcopy if the input is an ELF)
to_bin() {
case "$1" in
*.bin) printf '%s' "$1" ;;
*) arm-none-eabi-objcopy -O binary "$1" /tmp/fw.bin; printf '%s' /tmp/fw.bin ;;
esac
}
echo "==> flashing $FW via $METHOD"
case "$METHOD" in
openocd)
openocd -f "$CFG" -c "program $FW verify reset exit" ;;
pyocd)
pyocd flash -t "$TGT" "$FW"
pyocd reset -t "$TGT" ;;
dfu)
BIN="$(to_bin "$FW")"
dfu-util -a 0 -d 0483:df11 -s "${FLASH_ADDR}:leave" -D "$BIN" ;;
serial)
BIN="$(to_bin "$FW")"
stm32flash -w "$BIN" -v -g "$FLASH_ADDR" "$PORT" ;;
*)
echo "unknown method: $METHOD" >&2; exit 2 ;;
esac
echo "==> done"
chmod +x flash.sh
./flash.sh build/app.elf # default: openocd
./flash.sh build/app.elf pyocd
FLASH_METHOD=serial PORT=/dev/ttyUSB0 ./flash.sh build/app.bin
./flash.sh build/app.elf dfu # auto-objcopy to .bin
GitHub Actions — hardware-in-the-loop on a self-hosted runner
Attach the ST-Link to a machine registered as a self-hosted runner (label it, e.g. stm32l4r5). The workflow builds, flashes with the wrapper, then runs a smoke test over the virtual COM port.
name: hil-flash
on: [push]
jobs:
flash:
runs-on: [self-hosted, stm32l4r5] # runner with a probe attached
steps:
- uses: actions/checkout@v4
- name: Build firmware
run: make -j$(nproc)
- name: Flash + verify
env:
OOCD_CFG: ci/stm32l4r5.cfg
PROBE_SERIAL: ${{ secrets.PROBE_SERIAL }}
run: ./flash.sh build/app.elf openocd
- name: Smoke test over UART
run: ./scripts/smoke_test.py --port /dev/ttyACM0 --timeout 10
- name: Save flash log
if: ${{ always() }}
uses: actions/upload-artifact@v4
with:
name: flash-log
path: build/flash.log
Pin the probe by serial number in CI (pyocd -u SERIAL, openocd -c "adapter serial SERIAL", or hla_serial) so a runner with several boards flashes the right one. Gate merges on the smoke-test job's exit code — that's your hardware regression net.
Which tool for which job?
| Tool | Probe / link | gdbserver | Language | Best for |
|---|---|---|---|---|
| OpenOCD | ST-Link, CMSIS-DAP, J-Link, FTDI | :3333 | Tcl | Universal, CI, custom procs |
| GDB batch | via any gdbserver | client | gdb script | Flash + inspect + assert |
| pyOCD | CMSIS-DAP, ST-Link, J-Link | :3333 | Python | Pure-Python test rigs |
| pylink | SEGGER J-Link only | via JLinkGDBServer | Python | J-Link benches |
| dfu-util | USB (no probe) | — | C / CLI | USB field updates |
| stm32flash | USART (no probe) | — | C / CLI | Serial / gang programming |
08 Gotchas & common mistakes
The failure modes below account for most "it flashed but won't run" and "probe busy" tickets. Bake the fixes into your scripts.
.bin has no address. If you pass it without 0x08000000 (OpenOCD/pyOCD --base-address/DfuSe -s), it lands at address 0 (or is rejected). ELF/HEX carry addresses; prefer them.verify (OpenOCD), compare-sections (GDB), or rely on flash_file's built-in read-back (pylink/pyOCD).shutdown / kill the PID; in CI use --persist deliberately or tear it down in a cleanup step.pyocd pack install stm32l4r5). Don't hard-code a guessed type — read it from pyocd list --targets. A wrong type mis-sizes flash and erases the wrong pages.stm32flash -i) so CI can toggle it automatically.parity=serial.PARITY_EVEN.stm32l4x unlock 0 regresses RDP but mass-erases the chip — never run it blindly in a "reflash" script.BFB2 and bank-swap state. A "successful" flash to bank 1 can still boot bank 2's stale image. Fix the boot bank in your bring-up option-byte step.--batch forever in CI. Add set confirm off and pass -nx to ignore developer .gdbinit files.${{ }} in code blocksNot an MCU bug but a docs one: Jekyll/Liquid eats {% raw %}{{ }}. Wrap any Actions YAML you publish in {%- raw -%}{%- endraw -%} or the workflow renders blank.Golden rules
- Commit one config (
stm32l4r5.cfg) + one wrapper (flash.sh); everyone flashes identically. - Always verify and always free the probe at the end.
- ELF/HEX over raw BIN; when you must use BIN, always pass
0x08000000. - For serial/DFU: BOOT0 high, 8e1 parity, VID:PID 0483:DF11 — then reset to run.