Repurposing ST‐Link V2 Clones for MSP430 JTAG Debugging - grumat/glossy-msp430 GitHub Wiki
This document describes how a commercial ST-Link V2 clone was repurposed as an affordable MSP430 JTAG debugger/programmer, achieving 2.25 Mbps communication speed compared to the standard BluePill's 9 Mbps. While accepting this lower throughput, the approach gains significant practical benefits: reduced cost ($3-5 vs $15-20), widespread availability, and lower barrier to entry for project visitors to test the firmware.
The key innovation is automated signal generation using timer PWM and DMA, replacing the bit-banging approach with deterministic, frequency-stable waveforms. This addresses a critical limitation of traditional GPIO bit-banging: inherent timing jitter and unreliable high-frequency operation.
Unlike the BluePill, which provides flexible GPIO routing, ST-Link V2 clones have rigid internal constraints:
BluePill Architecture (Flexible GPIO):
STM32F103CB MCU
┌──────────────────────┐
│ GPIO Port A │
│ ┌────────────────┐ │
│ │ PA5 (JTCK) │──┼──→ JTAG Clock (SPI SCK)
│ │ PA6 (JTDO) │──┼──→ JTAG Data In (SPI MISO)
│ │ PA7 (JTDI) │──┼──→ JTAG Data Out (SPI MOSI)
│ │ PA10 (JTMS) │──┼──→ JTAG Mode Select (Timer PWM)
│ └────────────────┘ │
│ [All on same port] │
└──────────────────────┘
Advantage: Atomic 32-bit register writes (one BSRR transaction)
SPI peripheral available for efficient transfers
High speed (9 MHz) with minimal complexity
ST-Link V2 Clone Architecture (Constrained GPIO):
STM32F103CB MCU
┌───────────────────────────┐
│ GPIO Ports A & B │
│ ┌────────────┐ │
│ │ Port A │ │
│ │ PA6 (JTDO) │──┐ │
│ │ PA7 (JTDI) │──┼──→ JTAG Data Lines
│ └────────────┘ │ │
│ ┌────────────┐ │ │
│ │ Port B │ │ │
│ │ PB13(JTCK) │──┼──→ JTAG Clock + Mode
│ │ PB14(JTMS) │──┘ │
│ └────────────┘ │
│ [Split across 2 ports] │
└───────────────────────────┘
Constraint: Separate BSRR writes to Port A and Port B
DMA arbitration delays at high speeds
Maximum 2.25 MHz safely (4× slower than BluePill)
Detailed Pin Comparison:
| Signal | BluePill | ST-Link V2 | Implication |
|---|---|---|---|
| JTCK | PA5 (SPI SCK) | PB13 (GPIO) | SPI unavailable on V2 |
| JTDO | PA6 (SPI MISO) | PA6 (GPIO) | Same pin, different mode |
| JTDI | PA7 (SPI MOSI) | PA7 (GPIO) | Same pin, different mode |
| JTMS | PA10 (Timer) | PB14 (GPIO) | Different port = latency |
ST-Link V2 Hardware Constraints:
- JTDI, JTDO on Port A (pins PA6, PA7)
- JTCK, JTMS on Port B (pins PB13, PB14)
- Physical separation across different GPIO ports
- No SPI peripheral easily available without modifying internal connections
The BluePill's SPI-based approach (OPT_JTAG_IMPL_SPI in platform-defs.h) requires:
- Dedicated SPI peripheral for JTCK/JTDI/JTDO
- Timer output on JTMS pin for TMS signal modulation
- All signals on the same port for DMA efficiency
The ST-Link V2 clone lacks these prerequisites. Attempting to use SPI would require:
- Reverse-engineering internal connections (risky and time-consuming)
- Possible damage to debugging circuitry
- Loss of the onboard ST-Link debugger functionality
Decision: Implement timer/DMA-based JTAG frame generation instead, which is entirely GPIO-based and hardware-agnostic.
Traditional JTAG debuggers often use GPIO bit-banging:
// Naive bit-banging (slow and unreliable)
for (int i = 0; i < bit_count; i++) {
JTCK = 0; // Variable CPU delays
JTMS = tms_bits[i]; // Cache misses, interrupts
delay_ns(50); // Software delays (jittery)
JTCK = 1;
delay_ns(50);
tdo_bits[i] = JTDO;
}Problems:
- Timing jitter: CPU interrupts, cache behavior, compiler optimization variance
- Frequency instability: Target devices become sensitive to clock quality at higher speeds
- Throughput limited: ~100-500 kHz maximum (microsecond-level delays are hard to implement accurately)
- Unreliable at 2+ MHz: Many MSP430 devices fail or behave erratically
Instead of software control, use hardware peripherals to generate precise, deterministic waveforms:
- Timer provides time base with 8-count cycle precision
- PWM output (on JTCK) generates 50% duty-cycle clock automatically
- DMA channels triggered by timer compare events to update JTMS/JTDI data at exact moments
- No CPU involvement during transfers (except setup and cleanup)
Result:
- Frequency stability: Clock accuracy ±0.1% (determined by PLL, not software)
- Jitter-free: Hardware timing is deterministic
- High throughput: 2.25 Mbps sustainable (PWM + DMA handles all complexity)
- Reliable: Works consistently across operating conditions
The GeneratorSTLinkPWM template class in WaveJtag.h implements this approach:
template <
typename SysClk // System clock configuration (72 MHz PLL)
, const Timer::Unit kTim // TIM1 (advanced timer with repetition counter)
, const Timer::Channel kTck // CH1N for PWM clock output
, const Timer::Channel kTms // CH2 for JTMS control DMA
, const Timer::Channel kWriteCh // CH3 for JTDI data DMA
, const Timer::Channel kReadCh // CH4 for JTDO read DMA
, const uint32_t kFreq // Operating frequency (2.25 MHz nominal)
, const Scan kScan // DR or IR scan type
, const NumBits kNumBits // 8, 16, 20, or 32 bits
>
class GeneratorSTLinkPWM { /* ... */ };Hardware Block Diagram:
┌─────────────────────────────────────────────────────────┐
│ STM32F103 MCU (72 MHz) │
│ │
│ ┌────────────────────────────────────────────────────┐ │
│ │ TIM1 (Advanced Timer) │ │
│ │ ┌──────────────────────────────────────────────┐ │ │
│ │ │ Counter (8-count cycle) │ │ │
│ │ │ CCR1 = 4 (PWM 50% duty) │ │ │
│ │ │ CCR2 = 2 (JTMS trigger) │ │ │
│ │ │ CCR3 = 1 (JTDI trigger) │ │ │
│ │ │ CCR4 = 0 (JTDO trigger) │ │ │
│ │ │ RCR = (bit_count - 1) [repetition counter] │ │ │
│ │ └──────────────────────────────────────────────┘ │ │
│ └────────────────────────────────────────────────────┘ │
│ │ │ │ │ │
│ └─────────┼─────────┼─────────┘ │
│ │ │ │
│ ┌────────────▼──┐ ┌────▼────────────┐ │
│ │ DMA1 Ch2 │ │ DMA1 Ch3 │ │
│ │ (JTMS write) │ │ (JTDI write) │ │
│ │ Port B BSRR │ │ Port A BSRR │ │
│ └────────────┬──┘ └────┬────────────┘ │
│ │ │ │
│ ┌────────────▼─────────▼────────┐ │
│ │ DMA1 Ch4 (JTDO read) │ │
│ │ Port A IDR │ │
│ └────────────┬──────────────────┘ │
│ │ │
│ ┌────────────────┴────────────────────────┐ │
│ │ Timer Output Ch1N (PWM, complementary) | │
│ │ Generates JTCK clock (50% duty) | │
│ └────────────────┬────────────────────────┘ │
│ │ │
└───────────────────┼─────────────────────────────────────┘
│
┌──────────┴──────────┐
│ │
▼ ▼
┌─────────┐ ┌─────────┐
│ GPIO PortA │ GPIO PortB
│ ┌─────────────────┐ │ ┌─────────────────┐
│ │ PA6 ← JTDO (in) │ │ │ PB13→JTCK (PWM) │
│ │ PA7 ← JTDI (out)│ │ │ PB14→JTMS (out) │
│ └─────────────────┘ │ └─────────────────┘
└─────────┬───────────┘ └─────────┬───────────
│ │
└───→ To MSP430 Target ←┘
(JTAG Header)
Data Flow During Transfer:
1. Software prepares buffers (JTMS sequence, JTDI values)
↓
2. DMA channels configured with:
- DmaTms: Memory → Port B BSRR (control signals)
- DmaWrite: Memory → Port A BSRR (data bits)
- DmaRead: Port A IDR → Memory (captured data)
↓
3. Timer starts counting (0-7 cycles, repeat)
↓
4. Hardware autonomously:
┌─ Count 0: DmaRead triggered → Sample JTDO
│
├─ Count 1: DmaWrite triggered → Update JTDI
│
├─ Count 2: DmaTms triggered → Update JTMS
│
├─ Count 4-7: PWM clock low phase (JTCK = 0)
│
└─ Repeat until RCR expires (exact bit count)
↓
5. Software reads DmaRead buffer → Extract captured data
↓
6. Return JTAG shift result to caller
Key design decisions:
-
8-count timer cycle: Each JTAG clock period spans 8 timer counts
- Provides precise setup/hold time control
- Allows multiple DMA operations per cycle
-
PWM for clock (50% duty): TIM1_CH1N generates automatic clock
- JTCK high for counts 0-3
- JTCK low for counts 4-7
- No DMA channel consumed
-
Separate DMA for each port: Handles split GPIO constraint
- DmaTms: Writes JTMS (Port B) at count 5
- DmaWrite: Writes JTDI (Port A) at count 1
- DmaRead: Reads JTDO (Port A) at count 6
-
Repetition counter: Advanced timer feature for exact bit counts
- Ensures transmit completes exactly when needed
- No polling or interrupt overhead

Detailed timing diagram with signal relationships:
┌─ Timer Counts: 0 1 2 3 4 5 6 7 [repeat]
│
│ Timer CNT: ┌───┬───┬───┬───┬───┬───┬───┬───┬───
│ │ 0 │ 1 │ 2 │ 3 │ 4 │ 5 │ 6 │ 7 │ 0
│ └───┴───┴───┴───┴───┴───┴───┴───┴───
│
│ JTCK (PWM): ┌───────────────┐ ┌──────────
│ │ HIGH (0-3) │ │ HIGH
│ └───────────────┘ └──────────
│ └───────────────┘
│ LOW (4-7)
│
│ JTDO Read: ▲S
│ │ ▲S
│ └─ Sample JTDO (after edge rise) └─ Next cycle
│
│ JTDI Write: │
│ └─ ▲W ▲W
│ │ Setup JTDI data │
│ │ (before clock fall) │
│
│ JTMS Update: │
│ └─ ▲T ▲T
│ │ Update JTMS │
│ │ (safe margin zone) │
Target's Perspective:
Rising Edge Falling Edge Rising Edge
(count 0→1) (count 4→5) (count 8→1)
▲ │ ▲
│ │ │
└─ Samples └─ Can change └─ Samples
JTDI(old) data JTDI(new)
Timeline (in nanoseconds at 2.25 MHz):
1 count ≈ 55 ns
Full cycle ≈ 444 ns
Setup time (count 1-4): ~165 ns (3 counts)
Hold time (count 4-7): ~220 ns (4 counts)
MSP430 spec: Minimum 100 ns margin ✓
Simplified version (focusing on critical events):
Timer: 0 1 2 3 4 5 6 7
┌───┬───┬───┬───┬───┬───┬───┬───┐
PWM: │███│███│███│███│ │ │ │ │
Read: │█S │ │ │ │ │ │ │ │
Write: │ │█W │ │ │ │ │ │ │
TMS: │ │ │█T │ │ │ │ │ │
└───┴───┴───┴───┴───┴───┴───┴───┘
↑ ↑
CLK CLK
RISE FALL
Why this timing works:
-
JTDO Read at count 0: Samples target output immediately after clock rise
- Target had time to prepare data during high phase (counts 0-3)
- Gives ~220 ns hold time after falling edge
-
JTDI Write at count 1: Updates data before next falling edge
- Provides ~165 ns setup time before clock fall (count 4)
- New data visible by count 4, target samples at next clock edge
-
JTMS Update at count 2: Safe from clock edge transitions
- Far from critical clock moments
- Reduces probability of metastable states
- Medium priority (control less critical than data)
All timing is relative to the clock frequency. For 2.25 MHz (period ≈ 444 ns):
- 8-count cycle duration: 8 × (1 / (2.25 MHz × 8)) = 444 ns (one JTAG clock period)
- Each count: ~55 ns
- Setup/hold time: 1-2 counts (~55-110 ns)
The MSP430 JTAG TAP controller requires:
- Minimum clock period: ~100 ns (10 MHz max), but our 444 ns period provides 4× margin
- Setup time: Data valid before clock edge (JTDI setup at count 1, clock fall at count 4)
- Hold time: Data stable after clock edge (automatically satisfied by PWM)
At 9 MHz (BluePill SPI), the period is ~111 ns and setup time is critical. The SPI-based approach compensates with "pulse anticipation" (OPT_TMS_VERY_HIGH_CLOCK in BluePill's platform.h), adjusting timing to account for internal delays.
At 2.25 MHz, we gain:
- 4× more time per bit transfer
- Relaxed setup/hold requirements
- No jitter sensitivity to internal delays
- Robust operation across voltage variations
File: target.stlinv2/platform.h
/// Platform uses Timer+DMA for STLinkV2 (slower variant with split ports)
#define OPT_JTAG_IMPLEMENTATION OPT_JTAG_IMPL_TIM_DMA_SLOW
/// Timer channels for GeneratorSTLinkPWM
static constexpr Timer::Unit kWaveJtagTimer = Timer::kTim1;
static constexpr Timer::Channel kWaveJtagTck = Timer::Channel::k1; // PWM
static constexpr Timer::Channel kWaveJtagTms = Timer::Channel::k2; // DMA
static constexpr Timer::Channel kWaveJtagWrite = Timer::Channel::k3; // DMA
static constexpr Timer::Channel kWaveJtagReadCh = Timer::Channel::k4; // DMA
/// Pin definitions (split across ports)
typedef TIM1_CH1N_PB13_OUT JTCK_PWM; // Port B, Pin 13, PWM output
typedef AnyOut<Port::PB, 14> JTMS; // Port B, Pin 14, GPIO
typedef AnyOut<Port::PA, 7> JTDI; // Port A, Pin 7, GPIO
typedef AnyInPu<Port::PA, 6> JTDO; // Port A, Pin 6, GPIO inputKey differences from BluePill:
-
Slower operation mode (
OPT_JTAG_IMPL_TIM_DMA_SLOWvsOPT_JTAG_IMPL_SPI) - Port B hosts JTCK and JTMS (separate from JTDI/JTDO)
- PWM output (CH1N complementary) instead of GPIO bit-bang for JTCK
The GeneratorSTLinkPWM::Init() function sets up three DMA channels:
// DmaTms: Port B BSRR writes (JTMS control)
// Triggered at timer count 5
// Channel: Medium priority (control signals)
using DmaTms = Dma::AnyChannel<...>;
// DmaWrite: Port A BSRR writes (JTDI data)
// Triggered at timer count 1
// Channel: High priority (data must be reliable)
using DmaWrite = Dma::AnyChannel<...>;
// DmaRead: Port A IDR reads (JTDO sample)
// Triggered at timer count 0
// Channel: Very high priority (sample must be on-time)
using DmaRead = Dma::AnyChannel<...>;Priority ordering ensures deterministic operation under load:
- DmaRead (very high) – Sample JTDO immediately after clock rise
- DmaWrite (high) – Update JTDI for next cycle
- DmaTms (medium) – Update control signals (less critical)
If all three DMA requests occur simultaneously, the STM32 DMA controller arbitrates using these priorities.
During initialization, JTCK switches from GPIO (bit-bang) mode to PWM mode:
// In OnConnectJtag(): use GPIO bit-bang for TAP reset
JTCK::SetupPinMode(); // PA5 as output
// In GeneratorSTLinkPWM::Init(): switch to PWM
JTCKout::Setup(); // PA5 switches to TIM1_CH1N PWM
// During transmission: PWM hardware handles clock
// No CPU involvement, no bit-bangingThis mode switching allows:
- Initial device discovery with safe GPIO-based control
- Automatic precision clock once communication is established
- Fallback to GPIO if needed
The STLinkV2's split-port architecture creates a fundamental bottleneck:
Single-port (BluePill) vs Split-port (STLinkV2) DMA scenarios:
BluePill (OPT_JTAG_IMPL_TIM_DMA):
- All GPIO writes go to one peripheral address (Port A BSRR)
- All GPIO reads come from one peripheral address (Port A IDR)
- DMA can use single-shot mode with multiple requests efficiently
STLinkV2 (OPT_JTAG_IMPL_TIM_DMA_SLOW):
- Port A writes (JTDI) → address 0x40010C10 (GPIOA BSRR)
- Port B writes (JTMS) → address 0x40010810 (GPIOB BSRR)
- Port A reads (JTDO) → address 0x40010808 (GPIOA IDR)
- Three different peripheral addresses require independent DMA channels
DMA Arbitration Timing Analysis:
┌─ Frequency Timeline ──────────────────────────────────
│
│ 2.25 MHz (Safe Operation):
│ ┌───────────────────────────────────────────┐
│ │ 8-count cycle = 444 ns │
│ │ │
│ │ Timeline: │
│ │ Count 0: ▼ DmaRead request │
│ │ ├─ Setup: ~65 ns │
│ │ ├─ Arbitrate: ~20 ns │
│ │ └─ Transfer: ~30 ns ✓ SAFE │
│ │ │
│ │ Count 1: ▼ DmaWrite request │
│ │ ├─ Gap from Count 0: 55 ns ✓ │
│ │ └─ Completes before Count 2 │
│ │ │
│ │ Count 2: ▼ DmaTms request │
│ │ ├─ Gap from Count 1: 55 ns ✓ │
│ │ └─ Completes before Count 4 │
│ │ │
│ │ Total margin: >100 ns ✓ SAFE │
│ └───────────────────────────────────────────┘
│
│
│ 4.5 MHz (Marginal Operation):
│ ┌───────────────────────────────────────────┐
│ │ 8-count cycle = 222 ns │
│ │ │
│ │ Count 0: ▼ DmaRead │
│ │ Count 1: ▼ DmaWrite (only 28 ns gap!) │
│ │ ├─ Arbitrate: ~20 ns │
│ │ ├─ Latency: ~10 ns │ ⚠ RISKY
│ │ └─ No margin for jitter │
│ │ │
│ │ Count 2: ▼ DmaTms │
│ │ └─ May overlap with DmaWrite │
│ │ │
│ │ Total margin: ~0 ns ⚠ RISKY │
│ └───────────────────────────────────────────┘
│
│
│ 9 MHz (Impossible):
│ ┌───────────────────────────────────────────┐
│ │ 8-count cycle = 111 ns │
│ │ │
│ │ Count 0: ▼ DmaRead starts │
│ │ Count 1: ▼ DmaWrite request (too soon!) │
│ │ └─ Previous DMA still in progress
│ │ ✗ FRAME CORRUPTION │
│ │ │
│ │ Result: Random data, CRC errors, hangs │
│ └───────────────────────────────────────────┘
The constraint: DMA arbitration delays multiply with multiple addresses:
- At 2.25 MHz: One 8-count cycle = 444 ns, each DMA request = 100-150 ns → Safe margin ✓
- At 4.5 MHz: One 8-count cycle = 222 ns, margin shrinks to ~0 ns → Timing errors ⚠
- At 9 MHz: Impossible – DMA cannot keep up, frame corruption ✗
Key insight: Each additional DMA request adds:
- 65 ns: Setup overhead (STM32 DMA controller)
- 20 ns: Arbitration (priority resolution)
- 30 ns: Transfer time (for 32-bit write)
- Total per channel: ~115 ns
With 3 channels (DmaRead, DmaWrite, DmaTms), minimum cycle time = 3 × 115 ns = 345 ns.
At 2.25 MHz, each cycle is 444 ns → 99 ns margin (safe).
At 4.5 MHz, each cycle is 222 ns → Negative margin (impossible).
Mitigation in comments (WaveJtag.h:465-476):
"This approach saturates DMA at approximately 2.5 MHz, when JTMS delays so much that frame is malformed. Applying a safety margin we consider 2,250,000 Hz the topmost speed we can handle."
| Aspect | BluePill | ST-Link V2 |
|---|---|---|
| Cost | $15-20 | $3-5 |
| Availability | Moderate (online order) | Widespread (local shops) |
| Speed | 9 Mbps (SPI) | 2.25 Mbps (Timer+DMA) |
| Setup Complexity | Simple USB connection | Requires firmware load |
| Accessibility for Visitors | Testing requires prior purchase | Cheap "test device" |
Conclusion: The 4× speed reduction is acceptable for an educational/accessibility variant that:
- Costs 75% less
- Requires no prior purchase to test
- Uses ubiquitous hardware found in hobbyist labs
Both implementations solve the same problem (precise JTAG transfers) but use different hardware primitives:
| Aspect | SPI Approach | Timer/DMA Approach |
|---|---|---|
| Clock source | SPI peripheral (9 MHz max) | Timer (configurable up to 72 MHz) |
| TMS signal | Timer PWM + DMA feedback | GPIO BSRR writes via DMA |
| JTDI signal | SPI MOSI (automatic) | GPIO BSRR writes via DMA |
| JTDO signal | SPI MISO (automatic) | GPIO IDR reads via DMA |
| Synchronization | Hardware SPI framing | Timer repetition counter |
| Port requirement | Single port (all signals) | Can use multiple ports |
| Frequency range | 1-9 MHz (hardware ratio 72/8) | 1-2.25 MHz (DMA margin) |
| CPU overhead | ~5% during transfer | ~1% (mostly setup) |
| Complexity | Moderate (SPI configuration) | High (3 DMA channels + PWM) |
Both use C++17 constexpr templates for compile-time optimization:
// SPI approach (JtagDev.spi.cpp)
typedef SpiJtagDataShift<
DmaMode_
, kSelectDR_Scan
, 8
, uint8_t
> XMitDr8Type;
// Timer/DMA approach (JtagDev.tim.cpp)
using JtagDr8 = GeneratorSTLinkPWM<
SysClk
, kWaveJtagTimer
, kWaveJtagTck
, kWaveJtagTms
, kWaveJtagWrite
, kWaveJtagReadCh
, INIT_TIME_FREQ
, Scan::kDR
, NumBits::k8
>;The compiler generates specialized code for each configuration, with unused branches eliminated. Result: Minimal binary size despite complex templates.
Clock (JTCK) via PWM:
- Duty cycle: 50% (exactly 4 high, 4 low timer counts)
- Rise/fall time: ~10 ns (STM32 GPIO typical)
- Jitter: ±1 timer count (~55 ns) → ±12% variation (safe zone)
Data Setup/Hold:
- JTDI setup time: 100-150 ns before JTCK falling edge
- JTDO hold time: Automatic (PWM maintains clock)
- TMS propagation: ~200 ns with DMA arbitration
Target Device Compatibility:
- MSP430G series: ✓ (tested)
- MSP430F1xx/F2xx: ✓ (legacy support)
- MSP430Fxxx: ✓ (flash programming capable)
- MSP430Fxv2: ⚠ (no EEM, reduced speed margin)
| Condition | Impact | Mitigation |
|---|---|---|
| Target power supply noise | Clock jitter increases | 2× frequency margin absorbs it |
| Long cable connections | Signal reflections | 2.25 MHz avoids reflections |
| CPU interrupts during DMA | DMA priority ensures completion | VeryHigh priority (DmaRead) |
| Temperature variations | Timer frequency drift | PLL ±0.5% across 0-70°C |
| Multiple DMA requests | Arbitration delays | Staggered trigger points (counts 0,1,2) |
SPI approach (BluePill):
- 8-bit (IR Shift)
- 8-bit (DR Shift)
- 16-bit (DR Shift)
- 20-bit (DR Shift)
- Dummy bits for SPI alignment (8 or 16 bit frame size)
Timer/DMA approach (ST-Link V2):
- 8-bit (IR Shift)
- 8-bit (DR Shift)
- 16-bit (DR Shift)
- 20-bit (DR Shift)
- 32-bit (DR Shift)
- No dummy bits needed – timer repetition counter handles exact counts
Advantage of Timer/DMA: No need for "bogus bit" alignment. The repetition counter programs exact cycle count, so 8-bit transfer uses exactly 14 cycles (2 head + 8 data + 4 tail), not 16.
Both approaches:
- Support full JTAG TAP state machine traversal
- Maintain JTDI level during Run-Test/Idle (preserve TCLK state)
- Use identical MSP430-specific features (EEM, flash, CPU control)
Difference: SPI uses timer for TMS modulation feedback, while Timer/DMA uses timer as primary clock source.
| Operation | SPI (9 MHz) | Timer+DMA (2.25 MHz) | Ratio |
|---|---|---|---|
| Reset TAP to Device ID | 599 µs | 1.2 ms | 2.0× |
| Ready to Connect | 27.35 ms | 52.4 ms | 1.9× |
| Database lookup | 19.8 ms | 19.8 ms | 1.0× |
Note: The initialization is dominated by database lookup (chip identification), not communication speed. For most operations, the 2× slowdown is negligible.
Reading 256 bytes from target flash:
- SPI at 9 MHz: 16-bit reads × 128 reads = (20 cycles/read × 128) / 9 MHz ≈ 284 µs
- Timer/DMA at 2.25 MHz: Same operation = (20 cycles/read × 128) / 2.25 MHz ≈ 1.14 ms
Real-world impact: Most debugging operations involve small data transfers (1-4 reads/writes) plus serial communication overhead. The 2.25 MHz approach adds only ~100 µs per operation – unnoticeable at human timescales.
-
Optimize DMA arbitration: Rearrange trigger points to reduce conflicts
- Current: counts 0, 1, 2 → potential arbitration
- Option: Spread to counts 0, 3, 6 → guaranteed separation
-
Wider timer cycles: Increase from 8-count to 12-count
- More margin between DMA requests
- Could safely reach 3-4 MHz
-
Single-channel DMA for Port B: Combine JTMS + JTDI writes
- Requires JTMS and JTDI on same port (hardware redesign)
- Would enable ~3.5 MHz operation
This Timer/DMA approach is portable to other STM32 variants:
-
STM32L432: Low-power variant, same TIM1 architecture
- Advantage: Ultra-low power during debugging
- Limitation: 80 MHz clock max (would lower frequency slightly)
-
STM32F401: Faster CPU (84 MHz)
- Advantage: Could run at higher DMA clock speeds
- Limitation: More expensive than F103
-
STM32H7xx: Dual DMA controllers (DMAMUX)
- Advantage: Eliminates arbitration delays entirely
- Could sustain 4-5 MHz on split-port hardware
The ST-Link V2 repurposing demonstrates that hardware constraints need not be limiting factors when addressing them with appropriate tools:
- Problem: ST-Link V2 clone lacks SPI peripheral and has split GPIO ports
- Solution: Use timer PWM for automatic clock + DMA for data transfers
- Trade-off: Accept 4× lower speed (2.25 vs 9 Mbps) for 75% cost reduction
- Result: Reliable, stable operation ideal for education and accessibility
The automated waveform generation approach is:
- More stable than bit-banging (deterministic timing, no jitter)
- More efficient than CPU bit-banging (1% vs 50% CPU load)
- More portable than SPI (works on any GPIO-capable MCU)
- More maintainable than ad-hoc bit-bang code (clear, template-based design)
For project visitors and educational use, this $5 device removes the barrier to entry while sacrificing only throughput – a worthwhile trade-off for accessibility.
-
WaveJtag.h:
Firmware.shared/util/WaveJtag.h– GeneratorSTLinkPWM implementation -
JtagDev.tim.cpp:
Firmware.shared/drivers/JtagDev.tim.cpp– JTAG driver for Timer/DMA -
platform.h:
target.stlinv2/platform.h– ST-Link V2 hardware configuration - SPI-Case-Study.md: Detailed analysis of SPI-based approach (BluePill reference implementation)
- MSP430 JTAG TAP Controller User's Guide: SLAU320 (TI documentation on TCLK, TAP state machine)