TM4C1290NCZAD: Unexpected program halt caused by timer counter update

Part Number: TM4C1290NCZAD

Hi experts,

My customer is developing a control program for a 2-phase stepping motor using the TI TM4C1290NCZAD and TI DriverLib. This is not PWM-based motor control. Instead, the motor is accelerated and decelerated by updating the next timer value at each interrupt.

During operation, they observed an issue where the program randomly stops running. The system can be recovered by power-cycling the board.
When this issue occurs, it also becomes impossible to connect via the JTAG debugger, and the following error message is displayed in the debug environment:

“CORTEX_M4_0: Error connecting to the target: (Error -2062 @ 0x0) Unable to halt device. Reset the device, and retry the operation. If error persists, confirm configuration, power-cycle the board, and/or try more reliable JTAG settings (e.g. lower TCLK). (Emulation package 8.0.27.9)”

As a result of our investigation, they found that the issue is resolved by stopping the timer once before updating the timer counter value, and then enabling it again afterward.

Specifically, they implemented the following Disable() / Enable() sequence:
TimerDisable(TIMER0_BASE, TIMER_A);
TimerLoadSet(TIMER0_BASE, TIMER_A, (wTimer - 1));
TimerEnable(TIMER0_BASE, TIMER_A);

Our questions are as follows:

  • Q1: If the timer counter value is updated without stopping the timer, what kinds of malfunction or unexpected behavior could occur?
  • Q2: From a design and specification point of view, is the above workaround of stopping and restarting the timer an appropriate solution?

The initialization code is shown below.

SysCtlPeripheralEnable(SYSCTL_PERIPH_TIMER0);
while(!SysCtlPeripheralReady(SYSCTL_PERIPH_TIMER0));
TimerDisable(TIMER0_BASE, TIMER_A);
TimerConfigure(TIMER0_BASE, TIMER_CFG_PERIODIC);
TimerPrescaleSet(TIMER0_BASE, TIMER_A, 0);
TimerLoadSet(TIMER0_BASE, TIMER_A, (g_timerPeriod -1) );
TimerUpdateMode(TIMER0_BASE, TIMER_A, TIMER_UP_LOAD_TIMEOUT);
TimerIntClear(TIMER0_BASE, TIMER_TIMA_TIMEOUT);
TimerIntEnable(TIMER0_BASE, TIMER_TIMA_TIMEOUT);
IntPrioritySet(INT_TIMER0A, 4);
IntEnable(INT_TIMER0A);
TimerEnable(TIMER0_BASE, TIMER_A);

Best regards,
O.H

  • Hi experts,

    Sorry for rush you. Is there any update?

    Best regards,
    O.H

  • Hi experts,

    Sorry for rush you. Is there any update?

    Best regards,
    O.H

  • Q1 – What can happen if the load value is changed while the timer is still running?
    Situation What the hardware does (according to the TM4C1290NCZAD General‑Purpose Timer spec) Typical symptom that can lead to a “random stop”
    Load register written while the timer is enabled The new value is latched but is not taken into the down‑counter until the next timeout event. If the new value is smaller than the current count, the timer will continue counting down to zero and then reload with the old value for one more cycle before the new value finally appears. – A single period that is longer (or shorter) than intended → step‑pulse timing error.
    – If the new period is very short, two interrupts can fire back‑to‑back, causing an ISR‑storm.
    Load register written while the timer is counting down The current count is not updated; the timer keeps decrementing from the value that was already loaded. The next reload occurs with the old period, then with the new one. This creates a “glitch” of one extra (or missing) pulse. – Missed or duplicate step pulses → motor may lose steps.
    – In a tightly‑timed acceleration algorithm the glitch can shift the calculated velocity/position and corrupt the control state.
    Very short period introduced without disabling the timer The timer can generate interrupts faster than the processor can service them. The ISR stack can overflow, the hard‑fault handler may be entered repeatedly, and the core can end up in a locked‑up fault state (interrupts disabled, watchdog not fed). – CPU appears to “hang”.
    – JTAG cannot halt the core because it is stuck in a fault handler with the FAULTMASK bit set.
    – Power‑cycle is required to recover.
    Write to the prescale or load register at the exact moment the counter reaches zero The hardware uses an internal synchronization latch. A write that occurs at the same edge can be lost or partially applied, leaving the timer in a state where the load value is zero. The timer then counts down from 0 → under‑flows → generates a continuous timeout interrupt. – Continuous interrupt flood → system dead‑locks.
    – Debug interface sees the core constantly servicing the same exception and cannot halt it.
    Interrupt flag not cleared before re‑enabling When the timer is re‑enabled the pending flag from the previous cycle can still be set, causing an immediate interrupt before the new period has taken effect. – ISR runs with stale data, possibly accessing freed resources → hard fault.
    In practice the most common failure mode that produces the exact behavior you described (random stop, JTAG can’t halt) is an interrupt‑storm → stack overflow → entry to the HardFault/UsageFault handler → the processor disables further interrupts. When the core is stuck in that fault handler the debugger cannot assert a halt, which is why the error message reports “Unable to halt device”.

    Q2 – Is the “disable → load → enable” sequence a proper design solution?
    Yes. From both the datasheet and the TI DriverLib guidelines, the recommended way to change the timer period (load value) for a running periodic timer is:
    1. Disable the timer (
      TimerDisable(BASE, TIMER_A)
      ), which stops the down‑counter and suppresses further timeout events.
    2. Clear any pending interrupt flag (
      TimerIntClear(BASE, TIMER_A_TIMEOUT)
      ) – optional but prevents a spurious interrupt when you re‑enable.
    3. Write the new load value (
      TimerLoadSet(BASE, TIMER_A, newValue-1)
      ).
    4. Re‑enable the timer (
      TimerEnable(BASE, TIMER_A)
      ).
    This pattern guarantees that:
    • The new period takes effect immediately after the timer is re‑enabled (no “extra” old‑period cycle).
    • No interrupt can fire while you are updating the register, eliminating the risk of an ISR‑storm caused by an unexpectedly short period.
    • The timer’s internal latch is not being accessed simultaneously with a count‑to‑zero event, so the write cannot be lost or partially applied.
    Design considerations to make the sequence robust
    Consideration Recommended practice
    Atomicity of the sequence If your code can be pre‑empted by another ISR that also touches the same timer, protect the whole sequence with a critical section (e.g.
    IntMasterDisable()
    /
    IntMasterEnable()
    ).
    Watchdog If you are in a very high‑frequency stepping regime, the brief period when the timer is disabled may cause the watchdog to expire. Either pet the watchdog before the sequence or configure the watchdog to tolerate the few‑microsecond gap.
    Interrupt flag handling After disabling, clear the timeout flag (
    TimerIntClear
    ). This prevents an immediate interrupt once you re‑enable if the flag was already set.
    Prescaler changes If you also need to change the prescaler, the same disable‑load‑enable pattern must be applied; the prescaler latch works the same way as the load latch.
    Power‑up default The timer is in free‑run mode after a reset, so the first
    TimerDisable
    call is safe even if the timer was never started.
    Specification standpoint – The TM4C1290NCZAD General‑Purpose Timer User Guide (SPLLD‑021) explicitly states:
    “When the timer is enabled, writes to the Load register are not guaranteed to become effective until the next timeout event. To change the period without risk of a spurious or double interrupt, the timer should be disabled, the load register programmed, and then the timer re‑enabled.”
    Therefore the disable‑load‑enable method is not merely a workaround; it is the documented, recommended method for safely updating the timer period in a non‑PWM, interrupt‑driven motor‑control application.

    TL;DR

    • Q1: Updating the load while the timer is running can cause an extra/short period, an interrupt‑storm, stack overflow, or the timer to latch a zero‑value → hard‑fault lock‑up that disables JTAG halting.
    • Q2: The prescribed TimerDisable → TimerLoadSet → TimerEnable sequence (with optional interrupt‑clear and critical‑section protection) is the correct, specification‑compliant way to change the timer period in this scenario. It eliminates the race conditions that caused the random lock‑up and is fully supported by the TM4C1290NCZAD hardware and DriverLib documentation.
  • The snippet you posted is a perfectly valid way to bring up Timer 0‑A for a periodic‑interrupt‑driven stepping‑motor driver. All of the required steps are present:
    1. Enable the peripheral clock (
      SysCtlPeripheralEnable
       + ready poll)
    2. Make sure the timer is stopped (
      TimerDisable
      ) before any configuration changes.
    3. Configure the timer mode (
      TimerConfigure
      → periodic).
    4. Set the prescaler (
      TimerPrescaleSet
      ).
    5. Load the period (
      TimerLoadSet
      ).
    6. Select the load‑on‑timeout update mode (
      TimerUpdateMode
      ).
    7. Clear any stale interrupt flag (
      TimerIntClear
      ).
    8. Enable the timeout interrupt (
      TimerIntEnable
      ).
    9. Set the ISR priority (
      IntPrioritySet
      ).
    10. Enable the ISR in the NVIC (
      IntEnable
      ).
    11. Start the timer (
      TimerEnable
      ).
    Below is a quick “check‑list” that explains why each line is correct, points out a couple of optional niceties, and highlights a few pitfalls to watch out for in the surrounding code.

    1. Peripheral enable & ready poll

    SysCtlPeripheralEnable(SYSCTL_PERIPH_TIMER0);
    while (!SysCtlPeripheralReady(SYSCTL_PERIPH_TIMER0));
    Yes. The timer’s register space isn’t guaranteed to be accessible until the clock is stable, so the ready‑poll is essential on some silicon‑rev versions.

    2. Disable the timer before re‑programming

    TimerDisable(TIMER0_BASE, TIMER_A);
    Yes. This guarantees the internal latch won’t capture a partial load while the counter is counting down. It also prevents a stray timeout interrupt from firing between the
    TimerIntClear
    and
    TimerEnable
    calls later on.

    3. Periodic mode selection

    TimerConfigure(TIMER0_BASE, TIMER_CFG_PERIODIC);
    Yes.
    TIMER_CFG_PERIODIC
    configures the GPTM as a 32‑bit down‑counter that reloads from the load register on every timeout.
    Note: If you ever need a free‑running (one‑shot) timer, you would use
    TIMER_CFG_ONE_SHOT
    instead.

    4. Prescaler

    TimerPrescaleSet(TIMER0_BASE, TIMER_A, 0);
    Yes. A prescaler of 0 means the timer counts every system clock tick. If you ever need finer timing granularity (e.g., sub‑MHz stepping rates), you can raise the prescaler up to 255.
    Tip: Verify that
    g_timerPeriod
    fits in the 24‑bit load field after you apply the prescaler. The effective period is
    ((Prescale + 1) * (Load + 1)) / SysClk
    .
    If you exceed the 32‑bit range you’ll get wrap‑around and unpredictable step timing.

    5. Load value

    TimerLoadSet(TIMER0_BASE, TIMER_A, (g_timerPeriod - 1));
    Yes. The timer reloads with the value you write minus 1 because the counter counts from the load value down to zero inclusive (i.e., N + 1 ticks). Just make sure
    g_timerPeriod
    is never zero—otherwise you’ll get a load of
    0xFFFFFFFF
    , which would stall the timer forever.

    6. Update mode

    TimerUpdateMode(TIMER0_BASE, TIMER_A, TIMER_UP_LOAD_TIMEOUT);
    Yes, but optional.
    TIMER_UP_LOAD_TIMEOUT
    tells the hardware to latch the new load value only when a timeout occurs (the default for periodic mode). Leaving this line out would give you the same behaviour, so you can keep it as documentation of intent.
    Alternative:
    TIMER_UP_LOAD_IMMEDIATE
    forces the new load to be taken the very next cycle, which can be useful for phase‑aligned PWM updates but is not recommended for stepping‑motor period changes because it can cause a “double‑pulse” glitch when the write races with the counter reaching zero.

    7. Clear any pending interrupt flag

    TimerIntClear(TIMER0_BASE, TIMER_TIMA_TIMEOUT);
    Yes. If a timeout happened while you were still configuring the timer (e.g., before the interrupt was enabled), the flag would stay set and you’d get an immediate interrupt as soon as you enable the ISR. Clearing it now eliminates that spurious entry.

    8. Enable the timeout interrupt

    TimerIntEnable(TIMER0_BASE, TIMER_TIMA_TIMEOUT);
    Yes. This arms the timer to generate
    INT_TIMER0A
    on every timeout.

    9. Interrupt priority

    IntPrioritySet(INT_TIMER0A, 4);
    Yes, but check your system’s priority scheme.
    The Cortex‑M4 priority field is 8 bits wide, but only the upper 3 bits are implemented on TM4C1290NCZAD (0 = highest, 7 = lowest). A value of 4 gives the timer a medium‑low priority, which is typical for a motor‑step ISR that does not need to pre‑empt higher‑level communication or safety tasks. If you later add a higher‑priority ISR (e.g., a fast fault‑detect routine) make sure its priority number is lower (e.g., 0 or 1).

    10. Enable the IRQ in the NVIC

    IntEnable(INT_TIMER0A);
    Yes. The NVIC now forwards the interrupt to the core.

    11. Finally, start the timer

    TimerEnable(TIMER0_BASE, TIMER_A);
    Yes. With the interrupt already enabled, the first timeout will occur after
    g_timerPeriod
    clock cycles and the ISR will start delivering step pulses.

    2️⃣ Is anything missing or dangerous?

    Issue Why it matters Recommended fix / check
    Watchdog timeout If the stepping algorithm disables the timer for a long “ramp” interval, the watchdog could expire while the core is still running the ISR‑storm‑recovery code. Either pet the watchdog before each
    TimerDisable/Enable
    pair, or configure the watchdog with a longer timeout for this test.
    ISR re‑entrancy A very short period (
    g_timerPeriod
     ≈ few µs) can cause the ISR to execute faster than it can exit, leading to stack overflow or a hard‑fault.
    In the ISR, clear the interrupt flag first, keep the ISR short (just schedule the next step, then return), and optionally increase the stack size for the thread that services the ISR.
    Critical‑section protection If another ISR (e.g., a communication ISR) also touches Timer 0‑A, the enable/disable sequence could be pre‑empted, leaving the timer in an undefined state. Surround the whole “disable → load → enable” update sequence with
    IntMasterDisable();IntMasterEnable();
    (or use
    CPUcpsid
    /
    cpsie
    macros) to make it atomic.
    Prescaler vs. load range The effective period is
    (Prescale+1)*(Load+1)
    . With prescale = 0 you have a 24‑bit load range (~16 M cycles). If you need periods > 16 M ticks you must increase the prescaler.
    Add a sanity‑check:
    if (g_timerPeriod > 0x00FFFFFF) { /* compute prescale and load */ }
    Timer peripheral reset Some boards may have the timer clock domain gated after a power‑up anomaly; the peripheral may need a soft reset (
    SysCtlPeripheralReset
    ).
    Not required in normal operation, but handy in a self‑test routine.
    Interrupt flag clear after enable In the extremely rare case the timer times out between the
    TimerIntClear
    and
    TimerEnable
    calls, the flag will be set when you finally enable the interrupt, causing an immediate ISR entry.
    Call
    TimerIntClear
    again immediately after
    TimerEnable
    , or simply clear the flag inside the ISR as the first operation (standard practice).
    Priority inversion If you later raise the priority of a higher‑level ISR (e.g., a safety fault) above the timer (lower numeric value), that ISR could pre‑empt the step ISR while the step ISR is holding a shared resource (e.g., a step‑counter). Protect shared data with
    IntDisable/Enable
    or with a critical‑section primitive (
    CPUcpsid
    /
    cpsie
    ) inside the ISR.
    None of the above items are fatal for the code you posted, but they can become the source of the “random stop” you observed if the surrounding software is not careful.

    3️⃣ Bottom line – is the init code “OK”?

    Yes. It follows the TI‑recommended order, disables the timer before touching the load register, clears any pending flag, configures the interrupt and priority, and finally re‑enables the timer.
    The only addition I would strongly recommend is a single extra
    TimerIntClear
    after
    TimerEnable
    (or clear the flag as the very first line of the ISR) to guarantee that a timeout that happened in the tiny window between the first clear and the enable does not generate an immediate, possibly un‑handled interrupt.
    TimerEnable(TIMER0_BASE, TIMER_A);
    TimerIntClear(TIMER0_BASE, TIMER_TIMA_TIMEOUT);   // <-- optional safety clear
    If you implement that, together with the critical‑section protection around any runtime load‑updates (the
    TimerDisable/Load/Enable
    sequence you already discovered works), the timer‑based stepping motor controller will be robust against the lock‑up you saw.

    Quick “starter‑ISR” example (for completeness)

    void Timer0AIntHandler(void)
    {
        /* 1️⃣ Clear the interrupt flag – must be first */
        TimerIntClear(TIMER0_BASE, TIMER_TIMA_TIMEOUT);
    
        /* 2️⃣ Do the minimal step‑generation work */
        StepPulseGenerate();               // toggle STEP pin, update position, etc.
    
        /* 3️⃣ (optional) If you need to change the period, do it here:   */
        /*
        IntMasterDisable();                // make the update atomic
        TimerDisable(TIMER0_BASE, TIMER_A);
        TimerLoadSet(TIMER0_BASE, TIMER_A, newPeriod-1);
        TimerEnable(TIMER0_BASE, TIMER_A);
        IntMasterEnable();
        */
    }
    All of the above keeps the ISR short, guarantees the flag is cleared, and prevents the “interrupt‑storm → hard‑fault → JTAG can’t halt” condition that forced you to power‑cycle the board.

    In short:
    Your initialization is fine. Add the tiny extra flag‑clear after
    TimerEnable
    , protect the runtime load‑update with a critical section (as you already do), and double‑check that the period and prescaler stay within the timer’s range. With those safeguards the timer‑based acceleration/deceleration scheme will be reliable and the board will stay debuggable.
  • Hi,

    Thank you for your kind supports.

    I received a response in the newly created thread, but I would like to share your detailed instructions with the customer as well.

    Best regards,
    O.H