01 J-Link toolchain and connecting with JLinkExe
A SEGGER J-Link talks to the STM32L4R5ZI Cortex-M4F core over the 2-wire SWD interface (SWDIO + SWCLK). The whole workflow — connect, flash, run, halt, read memory, stream printf — is available from the command line with no IDE. This guide targets the NUCLEO-L4R5ZI board and the STM32L4R5ZI device (2 MB dual-bank flash at 0x08000000, 640 KB SRAM at 0x20000000).
The CLI tools in the J-Link pack
The "J-Link Software and Documentation Pack" installs a family of standalone binaries. On Linux they live in /opt/SEGGER/JLink/ and are symlinked into PATH.
| Binary (Linux) | What it does |
|---|---|
JLinkExe | J-Link Commander — interactive shell: connect, flash, halt, memory/register access |
JLinkGDBServer | GDB remote server (GUI); arm-none-eabi-gdb connects over TCP |
JLinkGDBServerCLExe | Same server, pure command-line (headless / CI) |
JLinkRTTClient | Telnet-style terminal for a running SEGGER RTT session |
JLinkRTTLogger | Opens its own connection and logs one RTT channel to a file |
JLinkSWOViewer | Decodes SWO/ITM printf from the TRACESWO pin |
JFlashExe / JFlashLiteExe | Scriptable production flash programmer (project + -auto) |
JLinkRemoteServer | Exposes a J-Link over the network for remote debugging |
There is no separate binary literally called JLinkFlash in the Linux pack. Command-line flashing is done either with JLinkExe -CommandFile (Section 04) or with JFlashExe for production runs. Both are covered below.
Install and udev rules (Debian/Ubuntu)
# Download JLink_Linux_*_x86_64.deb from segger.com/downloads/jlink
sudo dpkg -i JLink_Linux_V818_x86_64.deb
# The package installs /etc/udev/rules.d/99-jlink.rules for non-root USB access
sudo udevadm control --reload-rules && sudo udevadm trigger
# Sanity check: version banner + connected probe
JLinkExe -CommandFile /dev/null # prints DLL version, HW version, S/N and exits
One-line connect to the target
The four things J-Link always needs are the device, the interface, the speed, and a request to actually connect. Pass them on the command line so no dialog appears:
JLinkExe -device STM32L4R5ZI -if SWD -speed 4000 -autoconnect 1
SEGGER J-Link Commander V8.18 (Compiled ...)
Connecting to J-Link via USB...O.K.
Firmware: J-Link ... compiled ...
Hardware version: V10.10
Device "STM32L4R5ZI" selected.
Connecting to target via SWD
Found SW-DP with ID 0x2BA01477
AP map detection skipped. AP[0]: AHB-AP (IDR: 0x24770011)
Cortex-M4 identified.
J-Link>
STM32L4R5ZI is J-Link's built-in device name — it selects the correct flash loader (single- vs dual-bank, see Section 02/07). SWD is the 2-wire interface used by every Cortex-M. 4000 kHz is a safe, fast default; you can raise it to 15000 or use -speed adaptive once the link is proven stable. The SW-DP ID 0x2BA01477 and Cortex-M4 identified lines confirm the electrical connection is good.
02 J-Link Commander command reference
Inside the J-Link> prompt you drive the CPU directly. Commands are case-insensitive; most have a short alias (in parentheses below). Square brackets are optional arguments.
| Command | Alias | Meaning |
|---|---|---|
Connect | con | Establish the debug connection (uses current device/if/speed) |
SelectInterface SWD | si SWD | Choose SWD (0) or JTAG (1) |
Speed 4000 | — | Set SWD clock in kHz, or auto / adaptive |
Reset | r | Reset and halt the CPU |
Halt | h | Stop the core |
Go | g | Resume execution |
Step [n] | s | Single-step n instructions (halted core) |
IsHalted | ih | Report whether the core is halted |
Regs | — | Dump all Cortex-M core registers |
RReg <name> | — | Read one register, e.g. RReg PC |
WReg <name> <val> | — | Write one register |
SetPC <addr> | — | Set the program counter |
Mem <addr> <n> | — | Read n bytes, hex + ASCII dump |
Mem8/Mem16/Mem32 | — | Read n items of that width |
Write4 <addr> <val> | w4 | Write a 32-bit word (also w1/w2/w8) |
SaveBin <f> <addr> <n> | — | Dump n bytes of memory to a binary file |
LoadFile <f> [addr] | — | Program flash from ELF/HEX/SREC/BIN (Section 03) |
VerifyBin <f> <addr> | — | Compare a bin file against memory |
Erase [sAddr eAddr] | — | Erase flash (whole chip or a range) |
Sleep <ms> | — | Pause the script for ms milliseconds |
exec <string> | — | Run a J-Link command string (Section 05) |
Exit | q | Close the connection and quit |
A full inspection session
Reset the core, look at registers, read the vector table (the first word at 0x08000000 is the initial stack pointer; the second is the reset vector), poke a RAM word, then run.
# started with: JLinkExe -device STM32L4R5ZI -if SWD -speed 4000 -autoconnect 1
J-Link> r # reset + halt
Reset delay: 0 ms
Reset type NORMAL: Resets core & peripherals via SYSRESETREQ ...
J-Link> regs
R0 = 0x00000000, R1 = 0x00000000, R2 = 0x00000000, R3 = 0x00000000
...
SP(R13)= 0x2009FFE8, LR(R14)= 0xFFFFFFFF, PC(R15)= 0x08000B34
XPSR = 0x01000000
J-Link> mem32 0x08000000 2 # initial SP, then Reset_Handler address
08000000 = 2009FFE8 08000B35
J-Link> w4 0x20000000 0xDEADBEEF # write a 32-bit word to SRAM1
J-Link> mem32 0x20000000 1
20000000 = DEADBEEF
J-Link> savebin vectors.bin 0x08000000 0x400
Opening binary file for writing... [vectors.bin]
Reading 1024 bytes from addr 0x08000000 into file...O.K.
J-Link> g # resume
J-Link> h # halt again — PC now inside your main loop
J-Link> exit
The low bit of the Reset_Handler value (08000B35) is the Thumb bit — Cortex-M always executes Thumb, so vector table entries are odd. The actual instruction address is 0x08000B34, which is exactly the PC shown by regs.
03 Flashing firmware: loadfile, erase, verify
LoadFile is the workhorse. It auto-detects the format from the extension and programs the correct addresses. J-Link erases only the sectors it touches and verifies programmed data with a CRC as it goes.
| File type | Command | Address source |
|---|---|---|
ELF (.elf/.axf) | loadfile fw.elf | Load addresses from ELF program headers |
Intel HEX (.hex) | loadfile fw.hex | Addresses embedded in the HEX records |
Motorola SREC (.srec) | loadfile fw.srec | Addresses embedded in the SREC records |
Raw binary (.bin) | loadfile fw.bin 0x08000000 | You must supply the address |
J-Link> r
J-Link> halt
J-Link> loadfile firmware.hex
Downloading file [firmware.hex]...
J-Link: Flash download: Bank 0 @ 0x08000000: 1 range affected (65536 bytes)
J-Link: Flash download: Total: 1.204s (Prepare: 0.041s, Compare: 0.010s,
Erase: 0.352s, Program & Verify: 0.760s, Restore: 0.039s)
O.K.
J-Link> # For a raw .bin the base address is mandatory:
J-Link> loadfile firmware.bin 0x08000000
J-Link> # Independent read-back verify of a .bin against flash:
J-Link> verifybin firmware.bin 0x08000000
Verifying data ... O.K.
J-Link> r
J-Link> g
Erasing
J-Link> erase # full-chip erase of internal flash
J-Link> erase 0x08000000 0x08010000 # erase only the first 64 KB range
The STM32L4R5 has 2 MB of flash split into bank 1 (0x08000000) and bank 2 (0x08100000) when the DUALBANK option byte is 1 (the factory default). Since J-Link V7.86a the pack ships two loaders and picks the right one automatically from the device name — you do not normally configure this. Only if you changed DUALBANK yourself should you check that the loader matches, or images near the 1 MB boundary can mis-program.
04 Scripting JLinkExe with command files
For CI and production you want JLinkExe to run unattended. A command file is a plain text file with one Commander command per line. Pass it with -CommandFile (or its alias -CommanderScript).
// One command per line. // and # start comments.
device STM32L4R5ZI
si SWD
speed 4000
connect
r
halt
erase
loadfile firmware.hex
verifybin firmware.hex
r
g
exit
# Device/interface can live in the file OR on the CLI. -ExitOnError makes
# JLinkExe stop (and return non-zero) the moment a command fails. -NoGui 1
# suppresses every dialog so it never blocks on a headless machine.
JLinkExe -NoGui 1 -ExitOnError 1 -CommandFile flash.jlink
A robust wrapper with exit-code checking
#!/usr/bin/env bash
set -euo pipefail
DEVICE="STM32L4R5ZI"
IMAGE="${1:-firmware.hex}"
# Build a command file on the fly so the image path is a variable
SCRIPT="$(mktemp)"
cat > "$SCRIPT" <<EOF
device $DEVICE
si SWD
speed 4000
connect
r
halt
erase
loadfile $IMAGE
r
g
exit
EOF
# -ExitOnError 1 is what makes the return code trustworthy
if JLinkExe -NoGui 1 -ExitOnError 1 -CommandFile "$SCRIPT"; then
echo "FLASH OK: $IMAGE -> $DEVICE"
else
rc=$?
echo "FLASH FAILED (rc=$rc)" >&2
rm -f "$SCRIPT"
exit "$rc"
fi
rm -f "$SCRIPT"
For high-volume programming with serial-number injection and blank-check, JFlashExe is the purpose-built tool. It runs a saved .jflash project non-interactively:
JFlashExe -openprj stm32l4r5.jflash -open firmware.hex -auto -exit
# -auto = erase+program+verify in one go; -exit closes when done.
# Exit code 0 = success; check $? in your build script.
05 JLinkGDBServer and arm-none-eabi-gdb
For source-level debugging, JLinkGDBServer bridges the J-Link to a TCP port; then any GDB (arm-none-eabi-gdb) attaches with target remote. The server owns the probe; GDB just speaks the remote protocol.
Default ports
| Port | Purpose |
|---|---|
2331 | GDB remote protocol — target remote :2331 |
2332 | SWO raw output |
2333 | Telnet / semihosting printf terminal |
Key server options
| Option | Effect |
|---|---|
-device STM32L4R5ZI | Target device (required; skips the dialog) |
-if SWD | Interface: SWD or JTAG |
-speed 4000 | Clock in kHz (or adaptive) |
-port 2331 | Override the GDB port |
-swoport 2332 / -telnetport 2333 | Override SWO / Telnet ports |
-singlerun | Exit after the GDB client disconnects (great for CI) |
-nogui | No window; log to stdout |
-halt | Halt the core on attach (default) |
-strict | Abort on any configuration error |
-SelectEmuBySN <sn> | Pick a specific probe when several are plugged in |
-settingsfile / -jlinkscriptfile | Load a settings or J-Link script file |
# GUI-less variant is JLinkGDBServerCLExe; identical options.
JLinkGDBServerCLExe -device STM32L4R5ZI -if SWD -speed 4000 \
-port 2331 -singlerun -nogui
# ...
# Connecting to target...Connected to target
# Waiting for GDB connection...
arm-none-eabi-gdb firmware.elf
(gdb) target remote localhost:2331
(gdb) monitor reset # J-Link-specific: reset + halt
(gdb) load # flash the ELF over the debug link
(gdb) monitor semihosting enable
(gdb) break main
(gdb) monitor reset
(gdb) continue # runs to main()
Reusable .gdbinit
# Run: arm-none-eabi-gdb -x .gdbinit firmware.elf
target remote localhost:2331
monitor speed 4000
monitor reset
load
monitor semihosting enable
break main
monitor reset
# 'continue' left out so you land at the prompt ready to inspect
monitor command | Effect |
|---|---|
monitor reset | Reset and halt the core |
monitor halt / monitor go | Stop / resume |
monitor speed 4000 | Change SWD clock live |
monitor flash download = 1 | Let load program flash (default on) |
monitor semihosting enable | Route printf/SYS_WRITE0 to the Telnet port |
monitor reg <name> | Read a core register |
monitor RTTStart | Begin RTT auto-detection (see Section 06) |
06 SEGGER RTT: RTTClient and RTTLogger
Real-Time Transfer (RTT) is SEGGER's zero-instrumentation-overhead printf: your firmware writes into a ring buffer in SRAM, and J-Link reads it out over SWD while the CPU keeps running — no UART, no SWO pin, sub-microsecond cost per message. J-Link locates the buffer by scanning RAM for the "SEGGER RTT" ID string in a control block.
Firmware side — complete, compilable
Add SEGGER's SEGGER_RTT.c, SEGGER_RTT_printf.c and the RTT/ + Config/ headers to your build (they ship inside the J-Link pack under Samples/RTT/). This bare-metal example uses direct register access — no HAL — and blinks NUCLEO-L4R5ZI's green LED on PC7 while streaming counters over RTT channel 0.
/* Build with: arm-none-eabi-gcc -mcpu=cortex-m4 -mthumb -mfpu=fpv4-sp-d16
* -mfloat-abi=hard -DSTM32L4R5xx -O2 main.c SEGGER_RTT.c SEGGER_RTT_printf.c
* startup_stm32l4r5xx.s -T STM32L4R5ZITx_FLASH.ld -o firmware.elf */
#include <stdint.h>
#include "stm32l4r5xx.h" /* CMSIS device header from STM32Cube_FW_L4 */
#include "SEGGER_RTT.h" /* from the J-Link pack: Samples/RTT/ */
/* NUCLEO-L4R5ZI user LEDs: LD1 green = PC7, LD2 blue = PB7, LD3 red = PB14 */
#define LED_PIN 7U
static void led_init(void)
{
RCC->AHB2ENR |= RCC_AHB2ENR_GPIOCEN; /* clock GPIOC */
GPIOC->MODER &= ~(3U << (LED_PIN * 2)); /* clear mode bits */
GPIOC->MODER |= (1U << (LED_PIN * 2)); /* 01 = general output*/
}
static void delay(volatile uint32_t n) { while (n--) __asm volatile("nop"); }
int main(void)
{
led_init();
SEGGER_RTT_Init();
SEGGER_RTT_WriteString(0, "STM32L4R5ZI: RTT channel 0 is up\r\n");
uint32_t i = 0;
while (1) {
GPIOC->ODR ^= (1U << LED_PIN); /* toggle the LED */
SEGGER_RTT_printf(0, "tick %u ODR=0x%08X\r\n",
(unsigned)i++, (unsigned)GPIOC->ODR);
delay(1000000);
}
}
Host side — two ways to read it
Option A — JLinkRTTClient. Keep any J-Link connection open (a running JLinkExe or JLinkGDBServer), then attach a terminal. Narrowing the search range makes detection instant and reliable on the 640 KB SRAM:
JLinkExe -device STM32L4R5ZI -if SWD -speed 4000 -autoconnect 1
# Restrict the RAM scan to the 640 KB SRAM block (fast, deterministic):
J-Link> exec SetRTTSearchRanges 0x20000000 0xA0000
J-Link> g
# If you know the exact control-block address (e.g. from the .map file):
J-Link> exec SetRTTAddr 0x20000000
JLinkRTTClient
# ###RTT Client: Connected...
# STM32L4R5ZI: RTT channel 0 is up
# tick 0 ODR=0x00000080
# tick 1 ODR=0x00000000
# tick 2 ODR=0x00000080
Option B — JLinkRTTLogger. A self-contained tool: it opens its own connection (no Commander needed) and streams one channel to a file — ideal for long unattended captures.
JLinkRTTLogger -Device STM32L4R5ZI -If SWD -Speed 4000 \
-RTTChannel 0 rtt_channel0.log
# Point it straight at the control block to skip the RAM search:
JLinkRTTLogger -Device STM32L4R5ZI -If SWD -Speed 4000 \
-RTTAddress 0x20000000 -RTTChannel 0 rtt_channel0.log
| Tool / option | Meaning |
|---|---|
JLinkRTTClient -RTTTelnetPort <p> | Terminal client; default telnet port 19021 |
JLinkRTTClient -LocalEcho Off | Disable local echo of typed input |
JLinkRTTLogger -RTTChannel <n> | Which up-channel to record (0 = default log) |
JLinkRTTLogger -RTTAddress <a> | Exact control-block address (skip search) |
exec SetRTTSearchRanges <a> <len> | Limit the RAM scan; here 0x20000000 0xA0000 |
exec SetRTTAddr <a> | Pin the control block; fastest, most reliable |
07 Registers, memory and SWD/SWO pin reference
Reference tables you will reach for constantly: the Cortex-M4 core registers that regs/monitor reg print, the STM32L4R5 debug pins to wire, and the memory map that anchors your mem, loadfile and RTT addresses.
Cortex-M4 core registers
| Register | Role |
|---|---|
R0–R12 | General-purpose (R0–R3, R12 are caller-saved / argument regs) |
SP (R13) | Stack pointer — banked as MSP / PSP |
LR (R14) | Link register — return address; EXC_RETURN in an ISR |
PC (R15) | Program counter |
XPSR | Program status (APSR flags + IPSR exception# + EPSR Thumb bit) |
MSP / PSP | Main / Process stack pointers |
PRIMASK / FAULTMASK / BASEPRI | Interrupt-masking registers |
CONTROL | Privilege level, stack selection, FPU active bit |
S0–S31 / FPSCR | Single-precision FPU registers (Cortex-M4F) |
STM32L4R5 debug pins (default SWJ-DP)
| Signal | Pin | AF | Notes |
|---|---|---|---|
| SWDIO | PA13 | AF0 | SWD data — do not remap on the debug port |
| SWCLK | PA14 | AF0 | SWD clock |
| SWO / TRACESWO | PB3 | AF0 | ITM/SWO printf and trace (also JTDO) |
| NRST | NRST | — | Optional hardware reset line |
| JTDI | PA15 | AF0 | Only if using 4-wire JTAG |
| NJTRST | PB4 | AF0 | Only if using 4-wire JTAG |
After reset the SWJ-DP has SWDIO/SWCLK enabled automatically, so J-Link can always attach on a blank chip. RTT does not use SWO/PB3 at all — it rides the SWD data lines. Reserve PB3 only if you additionally want ITM/SWO printf via JLinkSWOViewer.
STM32L4R5ZI memory map (essentials)
| Region | Base | Size |
|---|---|---|
| Flash bank 1 | 0x08000000 | 1 MB |
| Flash bank 2 (DUALBANK=1) | 0x08100000 | 1 MB |
| SRAM1 | 0x20000000 | 192 KB |
| SRAM2 | 0x20030000 | 64 KB (also aliased at 0x10000000) |
| SRAM3 | 0x20040000 | 384 KB — total SRAM = 640 KB, contiguous to 0x2009FFFF |
| OCTOSPI mapped flash | 0x90000000 | up to 256 MB (external QSPI/OSPI) |
| Peripherals | 0x40000000 | APB/AHB registers |
| Cortex-M4 PPB (SCS/ITM/DWT) | 0xE0000000 | Debug/trace core registers |
The full 640 KB SRAM is one contiguous block 0x20000000–0x2009FFFF, which is exactly the SetRTTSearchRanges 0x20000000 0xA0000 range used in Section 06.
08 Gotchas and common mistakes
The failures that eat the most time are rarely the tool — they are the device name, the interface state, or a mismatched port. Work down this list first.
loadfile silently does nothing useful. Always use the exact built-in name STM32L4R5ZI — this is what selects the correct dual-bank loader.loadfile firmware.bin programs to address 0 and fails. Raw binaries carry no address — you must write loadfile firmware.bin 0x08000000. ELF/HEX/SREC carry their own addresses.JLinkExe can return 0 even after a failed command. In scripts always add -ExitOnError 1 (and -NoGui 1) so the return code is meaningful and no dialog blocks a headless run.-speed adaptive, lower the speed, or configure connect under reset (exec EnableEraseAllFlashBanks is unrelated — for connect issues use ResetType / connect-under-reset). A held NRST also blocks attach.2331, not the Telnet 2333. Confirm the server printed "Waiting for GDB connection" and that -singlerun did not already close it after a previous session.JLinkGDBServer and a separate JLinkRTTLogger both try to open the J-Link and collide. Use one owner: read RTT through the GDB server (monitor RTTStart + JLinkRTTClient) or through a single JLinkExe — not two independent connections.SEGGER_RTT_Init()/the first RTT write. Let it Go, and narrow the hunt with exec SetRTTSearchRanges 0x20000000 0xA0000 or pin it with SetRTTAddr.Cannot read from AP errors. Drop to -speed 4000 or lower, then raise once stable..deb (or copy 99-jlink.rules), then udevadm control --reload-rules && udevadm trigger, and re-plug the probe.The 30-second workflow
- Flash once:
JLinkExe -device STM32L4R5ZI -if SWD -speed 4000 -NoGui 1 -ExitOnError 1 -CommandFile flash.jlink - Debug:
JLinkGDBServerCLExe -device STM32L4R5ZI -if SWD -speed 4000 -singlerunthenarm-none-eabi-gdb -x .gdbinit firmware.elf - Logs: add
SEGGER_RTT_printf(0, ...), run, attachJLinkRTTClient(or capture withJLinkRTTLogger)