---
title: Target Identification
description: How to identify an unknown microcontroller using mcjtag
sidebar:
  order: 5
---

Unknown board on your bench. No markings, faded markings, or you just want to
confirm what the debug probe actually sees. Here is the systematic approach
using mcjtag.

## Physical Inspection

Before connecting anything:

- **Read chip markings** -- top marking, date codes, package type. A QFP-48 with
  "STM32F103C8T6" on it saves you all the steps below.
- **Count debug header pins** -- 10-pin 1.27 mm pitch is ARM SWD (Cortex Debug
  Connector), 20-pin 2.54 mm is legacy ARM JTAG, 14-pin is STDC14 (ST's
  compact debug + trace connector).
- **Note board markings** -- silk-screened part numbers, FCC IDs, and URLs can
  all lead to datasheets.

Even with legible markings, always verify electronically. Remarked chips
(counterfeit or mislabeled) exist in the wild, especially on cheap dev boards
from marketplace sellers.

## Step 1: Try SWD First

SWD needs only two signal wires (SWDIO + SWCLK), which means fewer things to
get wrong compared to JTAG's four. If the target is any ARM Cortex-M, SWD
is the fastest path to a response.

Use `target/cortex_m.cfg` as a generic config. It works for any Cortex-M
without chip-specific knowledge -- you lose flash programming support, but all
register and memory reads work fine.

```tcl
source [find interface/cmsis-dap.cfg]
transport select swd
adapter speed 1000
source [find target/cortex_m.cfg]
```

Start OpenOCD with this config:

```
start_openocd(config="/path/to/generic-swd.cfg")
```

If SWD fails with "no targets found" or similar, the target might not be ARM,
might not support SWD, or the wiring is wrong. Try JTAG next (swap to
`transport select jtag` and wire TDI/TDO/TMS/TCK). See
[Hardware Setup](/guides/hardware-setup/) for wiring details.

## Step 2: Read CPUID with probe_diagnostics

```
probe_diagnostics()
```

The diagnostics tool reads the CPUID register at `0xE000ED00`. This is an
ARM-defined register present on every Cortex-M core. The diagnostics output
includes the raw CPUID value and a decoded summary.

### CPUID register format

| Bits | Field | Description |
|------|-------|-------------|
| 31:24 | Implementer | `0x41` = ARM |
| 23:20 | Variant | Major revision |
| 19:16 | Architecture | `0xC` = ARMv6-M, `0xF` = ARMv7-M/v8-M |
| 15:4 | Part Number | Core type identifier |
| 3:0 | Revision | Minor revision |

### Part number lookup

| Part Number | Core |
|-------------|------|
| `0xC20` | Cortex-M0 |
| `0xC21` | Cortex-M1 |
| `0xC23` | Cortex-M3 |
| `0xC24` | Cortex-M4 |
| `0xC27` | Cortex-M7 |
| `0xC60` | Cortex-M0+ |
| `0xD20` | Cortex-M23 |
| `0xD21` | Cortex-M33 |

**Example:** CPUID = `0x410FC241` breaks down as:

- Implementer = `0x41` (ARM)
- Variant = `0x0`
- Architecture = `0xF` (ARMv7-M)
- Part Number = `0xC24` (Cortex-M4)
- Revision = `0x1`

This tells you the core type but **not** the chip vendor or specific part
number. For that, continue to the next steps.

## Step 3: Read JTAG IDCODE with jtag_scan

```
jtag_scan()
```

If connected via JTAG, this enumerates all TAPs on the scan chain. Each TAP
reports an IDCODE. The IDCODE contains a manufacturer ID in bits 11:1, defined
by the JEDEC JEP106 standard.

### Manufacturer ID lookup

| Manufacturer ID (bits 11:1) | Vendor |
|-----------------------------|--------|
| `0x020` | STMicroelectronics |
| `0x015` | NXP (Freescale) |
| `0x244` | Nordic Semiconductor |
| `0x108` | Raspberry Pi |
| `0x00B` | GigaDevice |
| `0x049` | Microchip (Atmel) |
| `0x097` | Texas Instruments |
| `0x23B` | ARM (debug components) |

### SWD note

SWD reports a DPIDR instead of an IDCODE. The `probe_diagnostics()` output
includes this value. The DPIDR has a different bit layout, but the lower bits
still contain a manufacturer code.

Many Cortex-M devices share the same DPIDR -- `0x0BC11477` is ARM's standard
CoreSight SWD-DP identifier. The DPIDR identifies the *debug port*, not the
chip. You need CPUID plus vendor-specific registers to pinpoint the exact part.

## Step 4: Vector Table Analysis

```
read_memory("0x08000000", 8, 32)
```

The first two words of flash reveal critical information about the target:

- **Word 0 (Initial SP)** -- Points to the top of SRAM. If it reads
  `0x20005000`, the chip has 20 KB of SRAM (`0x20000000` to `0x20004FFF`).
  This immediately narrows down the chip family.
- **Word 1 (Reset Vector)** -- The entry point address. Confirms code is
  present and where execution begins.

### Alternate flash base addresses

If `0x08000000` returns a bus fault, the chip maps flash elsewhere. Try these:

| Flash Base | Chip Family |
|-----------|-------------|
| `0x08000000` | STM32, GD32 |
| `0x00000000` | NXP LPC, Nordic nRF, RP2040 |
| `0x00400000` | Microchip SAM |
| `0x10000000` | RP2040 (XIP flash) |

### Validating the vector table

A valid vector table has:

- Word 0 pointing into SRAM (`0x2xxxxxxx`)
- Word 1 pointing into flash with bit 0 set (Thumb mode -- all Cortex-M code
  runs in Thumb state, so the reset vector always has the LSB set)

If both words read `0xFFFFFFFF`, the flash is blank or erased. If word 0 does
not point into SRAM, this may not be a vector table at all -- try a different
flash base address.

See [Memory Layout -- The Vector Table](/concepts/memory-layout/#the-vector-table)
for the full vector table structure and what each entry means.

## Step 5: Vendor-Specific ID Registers

Once you know the vendor (from IDCODE manufacturer bits or an educated guess
based on the board), check vendor-specific identification registers to get the
exact part number.

### STMicroelectronics (STM32)

```
read_memory("0xE0042000", 1, 32)
```

This reads `DBGMCU_IDCODE`, the debug MCU identification register:

| Bits | Field | Description |
|------|-------|-------------|
| 31:16 | REV_ID | Silicon revision identifier |
| 11:0 | DEV_ID | Device identifier |

#### Common DEV_ID values

| DEV_ID | Chip |
|--------|------|
| `0x410` | STM32F103 (medium density) |
| `0x412` | STM32F103 (low density) |
| `0x414` | STM32F103 (high density) |
| `0x411` | STM32F2xx |
| `0x413` | STM32F405/407 |
| `0x419` | STM32F42x/43x |
| `0x449` | STM32F74x/75x |
| `0x450` | STM32H743/753 |

#### Flash size register

Most STM32 chips expose their flash size at a fixed address:

```
read_memory("0x1FFFF7E0", 1, 16)
```

Returns flash size in KB as a 16-bit value. For example, `0x0040` = 64 KB,
`0x0080` = 128 KB, `0x0100` = 256 KB.

Note: Some STM32 families (F4, H7) move this register to `0x1FFF7A22` or
`0x1FF1E880`. Check the reference manual for your specific family.

### Nordic Semiconductor (nRF)

```
read_memory("0x10000100", 4, 32)
```

Reads FICR (Factory Information Configuration Register). The four words
starting at this address contain:

- Word 0 (`0x10000100`): Part number (e.g., `0x00052832` = nRF52832)
- Word 1 (`0x10000104`): Variant (e.g., `0x41414230` = "AAB0")
- Word 2 (`0x10000108`): Package type
- Word 3 (`0x1000010C`): RAM size in KB

### GigaDevice (GD32)

GD32 is register-compatible with STM32 at the debug interface level. The
`DBGMCU_IDCODE` lives at the same address (`0xE0042000`), but DEV_ID values
are different. Cross-reference with GigaDevice datasheets.

Common GD32 identifiers:

| DEV_ID | Chip |
|--------|------|
| `0x410` | GD32F103 (yes, same as STM32F103 -- check the JTAG IDCODE manufacturer bits to distinguish) |
| `0x414` | GD32F303 |

### Microchip SAM (Atmel)

```
read_memory("0x41002018", 1, 32)
```

Reads the Device Identification register (DSU DID) on SAM D/E/C series.
Contains processor, family, series, die, and revision fields.

## Step 6: Load SVD for Full Decode

Once you know the exact chip, load an SVD file for complete peripheral
decoding:

```
svd_inspect(svd_path="/path/to/STM32F103xx.svd")
```

With SVD loaded, every peripheral register on the chip becomes inspectable
by name with decoded bitfields. No more manual address lookups.

SVD files can be obtained from:

- **cmsis-svd-data** -- Community-maintained repository on GitHub with SVD
  files for most vendors. See the
  [SVD Register Decoding guide](/guides/svd-register-decoding/#getting-svd-files)
  for download instructions.
- **Vendor IDE installations** -- STM32CubeIDE, Keil, IAR all bundle SVD files.
- **Keil pack files** -- `.pack` files are ZIP archives containing `.svd` files.
  Rename to `.zip` and extract.

## Quick Reference

Summary of the identification workflow:

| Data Point | Tool Call | What It Tells You |
|-----------|----------|-------------------|
| JTAG IDCODE | `jtag_scan()` | Manufacturer (JEDEC code) |
| DPIDR | `probe_diagnostics()` | Debug port type |
| CPUID (`0xE000ED00`) | `probe_diagnostics()` | ARM core type (M0, M3, M4, M7...) |
| Vector table | `read_memory("0x08000000", 8, 32)` | SRAM size, code base, firmware presence |
| DBGMCU_IDCODE | `read_memory("0xE0042000", 1, 32)` | Exact STM32/GD32 part number |
| Flash size | `read_memory("0x1FFFF7E0", 1, 16)` | Flash capacity in KB |
| FICR | `read_memory("0x10000100", 4, 32)` | nRF part number and variant |
| SVD decode | `svd_inspect()` | Full peripheral register map |

## When Identification Fails

Some scenarios where this workflow hits a wall:

- **Readout protection enabled** -- The chip may have RDP/APPROTECT set,
  which blocks all debug access. `probe_diagnostics()` will show a connection
  but memory reads return faults. This is by design -- the firmware author
  locked the chip.
- **Non-ARM targets** -- Everything above assumes ARM Cortex-M. RISC-V, 8051,
  and other architectures have completely different identification mechanisms.
  `jtag_scan()` still works for JTAG IDCODE, but CPUID and the vendor-specific
  registers do not apply.
- **Custom or obscure silicon** -- Some chips have undocumented or nonstandard
  debug interfaces. If `jtag_scan()` returns an IDCODE with a manufacturer ID
  not in the JEDEC tables, you are in datasheet-hunting territory.
- **Damaged or dead target** -- If nothing responds at all, verify wiring,
  power, and adapter speed before concluding the chip is dead. Drop adapter
  speed to `100` kHz and try again. See
  [Troubleshooting](/guides/troubleshooting/) for the full diagnostic checklist.
