Repurposing ST‐Link V2 Clones for MSP430 JTAG Debugging - grumat/glossy-msp430 GitHub Wiki

Executive Summary

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.


Part 1: Hardware Constraints and Motivation

ST-Link V2 Hardware Layout

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

Why SPI Failed on ST-Link V2

The BluePill's SPI-based approach (OPT_JTAG_IMPL_SPI in platform-defs.h) requires:

  1. Dedicated SPI peripheral for JTCK/JTDI/JTDO
  2. Timer output on JTMS pin for TMS signal modulation
  3. 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.


Part 2: The Timer/DMA Solution - Automated Waveform Generation

Fundamental Problem: Bit-Banging Timing Jitter

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

The Solution: Hardware-Driven Waveforms

Instead of software control, use hardware peripherals to generate precise, deterministic waveforms:

  1. Timer provides time base with 8-count cycle precision
  2. PWM output (on JTCK) generates 50% duty-cycle clock automatically
  3. DMA channels triggered by timer compare events to update JTMS/JTDI data at exact moments
  4. 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

Architecture: GeneratorSTLinkPWM Class

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:

  1. 8-count timer cycle: Each JTAG clock period spans 8 timer counts

    • Provides precise setup/hold time control
    • Allows multiple DMA operations per cycle
  2. 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
  3. 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
  4. Repetition counter: Advanced timer feature for exact bit counts

    • Ensures transmit completes exactly when needed
    • No polling or interrupt overhead

Part 3: Timing and Synchronization

8-Count Waveform Cycle

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:

  1. 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
  2. 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
  3. 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)

Timing Constraints

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)

Why Lower Frequencies are Safer

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

Part 4: Implementation Details

Platform Configuration

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 input

Key differences from BluePill:

  • Slower operation mode (OPT_JTAG_IMPL_TIM_DMA_SLOW vs OPT_JTAG_IMPL_SPI)
  • Port B hosts JTCK and JTMS (separate from JTDI/JTDO)
  • PWM output (CH1N complementary) instead of GPIO bit-bang for JTCK

DMA Configuration in WaveJtag.h

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:

  1. DmaRead (very high) – Sample JTDO immediately after clock rise
  2. DmaWrite (high) – Update JTDI for next cycle
  3. DmaTms (medium) – Update control signals (less critical)

If all three DMA requests occur simultaneously, the STM32 DMA controller arbitrates using these priorities.

Runtime JTCK Mode Switching

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-banging

This mode switching allows:

  • Initial device discovery with safe GPIO-based control
  • Automatic precision clock once communication is established
  • Fallback to GPIO if needed

Part 5: Limitations and Trade-offs

Speed Limitation: Why 2.25 MHz?

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."

Accessibility Trade-off

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

Part 6: Architectural Comparison

Design Pattern: From SPI to Timer/DMA

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)

Code Generation Strategy

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.


Part 7: Signal Quality and Reliability

Waveform Characteristics at 2.25 MHz

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)

Stability Under Adverse Conditions

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)

Part 8: Comparison with Original SPI-Case-Study

What Transfer Sizes Are Supported?

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.

TAP State Machine Handling

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.


Part 9: Performance Benchmarks

Initialization Time Comparison

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.

Throughput on Typical Operations

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.


Part 10: Future Improvements

Potential Speed Increases

  1. 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
  2. Wider timer cycles: Increase from 8-count to 12-count

    • More margin between DMA requests
    • Could safely reach 3-4 MHz
  3. 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

Alternative Hardware Targets

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

Conclusion

The ST-Link V2 repurposing demonstrates that hardware constraints need not be limiting factors when addressing them with appropriate tools:

  1. Problem: ST-Link V2 clone lacks SPI peripheral and has split GPIO ports
  2. Solution: Use timer PWM for automatic clock + DMA for data transfers
  3. Trade-off: Accept 4× lower speed (2.25 vs 9 Mbps) for 75% cost reduction
  4. 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.


References

  • 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)
⚠️ **GitHub.com Fallback** ⚠️