The "Holy Bible" for embedded engineers
JTAG (Joint Test Action Group) and SWD (Serial Wire Debug) are industry-standard hardware debugging interfaces that provide direct access to embedded system internals. These protocols enable real-time debugging, memory inspection, and hardware validation without disrupting normal system operation.
Key Benefits:
JTAG operates on a 4-wire interface (TMS, TCK, TDI, TDO) plus optional TRST, creating a state machine that can access internal scan chains. Each device in the scan chain has a unique ID and can be individually addressed for debugging operations.
Scan Chain Architecture:
Host ←→ JTAG Interface ←→ Target Device 1 ←→ Target Device 2 ←→ ...
TMS/TCK/TDI/TDO TMS/TCK/TDI/TDO TMS/TCK/TDI/TDO
SWD is ARM’s simplified 2-wire debug interface that reduces pin count while maintaining full debugging capabilities. It uses SWDIO (bidirectional data) and SWCLK (clock) to communicate with ARM Cortex-M processors.
SWD Interface:
Host ←→ SWD Interface ←→ ARM Cortex-M
SWDIO/SWCLK SWDIO/SWCLK
Both protocols access the Debug Access Port, which provides:
JTAG uses a complex state machine with 16 states, while SWD uses a simpler packet-based protocol. Understanding these state transitions is crucial for reliable debugging.
JTAG State Transitions:
Test-Logic-Reset ←→ Run-Test/Idle ←→ Select-DR-Scan ←→ Capture-DR ←→ Shift-DR ←→ Exit1-DR ←→ Update-DR
↑ ↑ ↑ ↑ ↑ ↑ ↑
└────────────────┴────────────────┴──────────────┴────────────┴───────────┴───────────┘
Debug interfaces support different memory access patterns:
// JTAG interface structure
typedef struct {
uint32_t tms_pin;
uint32_t tck_pin;
uint32_t tdi_pin;
uint32_t tdo_pin;
uint32_t trst_pin;
uint32_t srst_pin;
} jtag_interface_t;
// JTAG state machine
typedef enum {
TEST_LOGIC_RESET,
RUN_TEST_IDLE,
SELECT_DR_SCAN,
CAPTURE_DR,
SHIFT_DR,
EXIT1_DR,
UPDATE_DR,
SELECT_IR_SCAN,
CAPTURE_IR,
SHIFT_IR,
EXIT1_IR,
UPDATE_IR
} jtag_state_t;
// Initialize JTAG interface
void jtag_init(jtag_interface_t *jtag) {
// Configure GPIO pins
gpio_set_mode(jtag->tms_pin, GPIO_MODE_OUTPUT);
gpio_set_mode(jtag->tck_pin, GPIO_MODE_OUTPUT);
gpio_set_mode(jtag->tdi_pin, GPIO_MODE_OUTPUT);
gpio_set_mode(jtag->tdo_pin, GPIO_MODE_INPUT);
gpio_set_mode(jtag->trst_pin, GPIO_MODE_OUTPUT);
gpio_set_mode(jtag->srst_pin, GPIO_MODE_OUTPUT);
// Reset to known state
jtag_reset(jtag);
}
// JTAG state transition
void jtag_state_transition(jtag_interface_t *jtag, jtag_state_t target_state) {
// Navigate through state machine to reach target
// Implementation depends on current state and target
}
// SWD interface structure
typedef struct {
uint32_t swdio_pin;
uint32_t swclk_pin;
uint32_t swdio_dir; // Direction control
} swd_interface_t;
// SWD packet types
typedef enum {
SWD_READ_AP = 0x05,
SWD_WRITE_AP = 0x01,
SWD_READ_DP = 0x04,
SWD_WRITE_DP = 0x00
} swd_packet_type_t;
// SWD packet structure
typedef struct {
uint8_t start;
uint8_t apndp;
uint8_t a[2];
uint8_t parity;
uint8_t stop;
uint8_t park;
} swd_packet_t;
// Initialize SWD interface
void swd_init(swd_interface_t *swd) {
gpio_set_mode(swd->swdio_pin, GPIO_MODE_OUTPUT);
gpio_set_mode(swd->swclk_pin, GPIO_MODE_OUTPUT);
gpio_set_mode(swd->swdio_dir, GPIO_MODE_OUTPUT);
// Generate SWD reset sequence
swd_reset_sequence(swd);
}
// Send SWD packet
uint32_t swd_send_packet(swd_interface_t *swd, swd_packet_t *packet) {
uint32_t ack;
// Set direction to output
gpio_write(swd->swdio_dir, 1);
// Send packet bits
for (int i = 0; i < 8; i++) {
gpio_write(swd->swdio_pin, (packet->start >> i) & 1);
gpio_write(swd->swclk_pin, 1);
gpio_write(swd->swclk_pin, 0);
}
// Switch to input for ACK
gpio_write(swd->swdio_dir, 0);
// Read ACK
ack = 0;
for (int i = 0; i < 3; i++) {
gpio_write(swd->swclk_pin, 1);
ack |= (gpio_read(swd->swdio_pin) << i);
gpio_write(swd->swclk_pin, 0);
}
return ack;
}
// Debug session structure
typedef struct {
uint32_t target_id;
uint32_t core_id;
bool is_halted;
uint32_t breakpoint_count;
uint32_t watchpoint_count;
} debug_session_t;
// Initialize debug session
debug_session_t* debug_session_init(uint32_t target_id) {
debug_session_t *session = malloc(sizeof(debug_session_t));
if (session) {
session->target_id = target_id;
session->core_id = 0;
session->is_halted = false;
session->breakpoint_count = 0;
session->watchpoint_count = 0;
}
return session;
}
// Halt target
bool debug_halt_target(debug_session_t *session) {
// Send halt command through debug interface
if (send_halt_command(session->target_id)) {
session->is_halted = true;
return true;
}
return false;
}
// Read CPU register
uint32_t debug_read_register(debug_session_t *session, uint32_t reg_num) {
if (!session->is_halted) {
return 0xFFFFFFFF; // Error: target not halted
}
// Read register through debug interface
return read_register_value(session->target_id, reg_num);
}
// Multi-core debug session
typedef struct {
uint32_t target_id;
uint32_t core_count;
debug_session_t *cores;
bool all_halted;
} multi_core_debug_t;
// Halt all cores
bool debug_halt_all_cores(multi_core_debug_t *multi_core) {
bool success = true;
for (uint32_t i = 0; i < multi_core->core_count; i++) {
if (!debug_halt_target(&multi_core->cores[i])) {
success = false;
}
}
multi_core->all_halted = success;
return success;
}
// Synchronized stepping across cores
bool debug_step_all_cores(multi_core_debug_t *multi_core) {
if (!multi_core->all_halted) {
return false;
}
// Step all cores simultaneously
for (uint32_t i = 0; i < multi_core->core_count; i++) {
step_core(&multi_core->cores[i]);
}
return true;
}
// Conditional breakpoint structure
typedef struct {
uint32_t address;
uint32_t condition_type;
uint32_t condition_value;
uint32_t hit_count;
bool enabled;
} conditional_breakpoint_t;
// Condition types
typedef enum {
CONDITION_EQUAL,
CONDITION_NOT_EQUAL,
CONDITION_GREATER_THAN,
CONDITION_LESS_THAN,
CONDITION_BIT_SET,
CONDITION_BIT_CLEAR
} breakpoint_condition_t;
// Check breakpoint condition
bool check_breakpoint_condition(conditional_breakpoint_t *bp, uint32_t value) {
if (!bp->enabled) {
return false;
}
switch (bp->condition_type) {
case CONDITION_EQUAL:
return (value == bp->condition_value);
case CONDITION_NOT_EQUAL:
return (value != bp->condition_value);
case CONDITION_GREATER_THAN:
return (value > bp->condition_value);
case CONDITION_LESS_THAN:
return (value < bp->condition_value);
case CONDITION_BIT_SET:
return (value & bp->condition_value) != 0;
case CONDITION_BIT_CLEAR:
return (value & bp->condition_value) == 0;
default:
return false;
}
}
// Memory access optimization
typedef struct {
uint32_t base_address;
uint32_t size;
uint32_t access_count;
uint32_t cache_hits;
uint32_t cache_misses;
} memory_cache_t;
// Optimized memory read
uint32_t debug_read_memory_optimized(debug_session_t *session,
uint32_t address,
memory_cache_t *cache) {
// Check cache first
if (address >= cache->base_address &&
address < cache->base_address + cache->size) {
cache->cache_hits++;
return read_cached_memory(address);
}
cache->cache_misses++;
cache->access_count++;
// Update cache with new region
update_memory_cache(cache, address);
return read_memory_through_debug(address);
}