Skip to content

SysTick delay functions conflict with interrupt handling in WCH SDK #62

@owl-programmer

Description

@owl-programmer

Describe the bug

The delay functions (e.g., Delay_Ms, Delay_Us) provided in the WCH EVT examples are implemented by busy-wait polling of the SysTick status flag (SR.CNTIF). If the SysTick interrupt is enabled and the interrupt handler clears this flag (which is necessary to prevent repeated interrupts), the delay functions will never see the flag set and will hang indefinitely.

This is not a hardware bug, but a software design issue in the example code that makes the delay functions incompatible with interrupt‑driven SysTick.

Steps to reproduce

  1. Use any WCH EVT example that includes a SysTick delay implementation (e.g., EVT/EXAMPLES/SYSTICK/Systick_Delay for CH32V00x or CH32V20x).
  2. Enable the SysTick interrupt by setting CTLR to 0xF (enable timer, interrupt, auto‑reload, HCLK clock).
  3. Add an interrupt handler that clears the flag:
    void SysTick_Handler(void) __attribute__((interrupt("WCH-Interrupt-fast")));
    void SysTick_Handler(void) {
        SysTick->SR = 0;   // clear the flag
    }
  4. Call Delay_Ms(1000) in main().
  5. Observe that the program never returns from the delay function.

Expected behavior

· Delay functions should work correctly regardless of whether the SysTick interrupt is enabled or how the flag is managed inside the interrupt handler.
· Standard practice is to use a global tick counter incremented in the interrupt handler, and have delays poll that counter.

Root cause

The WCH delay implementation directly polls the hardware flag:

// Typical WCH delay (simplified)
void Delay_Ms(uint32_t ms) {
    SysTick->CMP = ms * (SystemCoreClock / 1000);
    SysTick->CNT = 0;
    while (!(SysTick->SR & 1));   // waits for flag to be set
    SysTick->SR = 0;
}

When an interrupt handler clears SR (either before or after the delay reads it), the condition in while never becomes true, causing a deadlock.

Suggested fix

Replace the polling‑based delay with a tick counter approach:

volatile uint32_t uwTick = 0;

void SysTick_Handler(void) {
    uwTick++;
    SysTick->SR = 0;   // still necessary
}

void Delay_Ms(uint32_t ms) {
    uint32_t tickstart = uwTick;
    while ((uwTick - tickstart) < ms);
}

This makes the delay independent of the flag and compatible with interrupt operation.

Additional notes

· The issue is present in both CH32V003 and CH32V203 SDK examples, and likely in other WCH RISC‑V MCUs.
· If the SysTick clock source is changed (e.g., to HCLK/8), the polling‑based delays also become inaccurate because they assume a fixed tick rate. The tick‑counter method can easily accommodate any clock by adjusting the reload value accordingly.

Environment

· MCU: CH32V003, CH32V203
· SDK: WCH EVT examples (e.g., EXAMPLES/SYSTICK/Systick_Delay)
· Compiler: MounRiver Studio / GCC

I would be happy to submit a pull request that updates the example code to use a robust tick‑counter delay.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions