diff --git a/examples/systick_irq_millis/Makefile b/examples/systick_irq_millis/Makefile
new file mode 100644
index 0000000000000000000000000000000000000000..266cce9d08ec1b7132c81b4d3256f5218df1f44b
--- /dev/null
+++ b/examples/systick_irq_millis/Makefile
@@ -0,0 +1,41 @@
+TARGET:=systick_irq_millis
+
+all : flash
+
+PREFIX:=riscv64-unknown-elf
+
+GPIO_Toggle:=EXAM/GPIO/GPIO_Toggle/User
+
+CH32V003FUN:=../../ch32v003fun
+MINICHLINK:=../../minichlink
+
+CFLAGS:= \
+	-g -Os -flto -ffunction-sections \
+	-static-libgcc \
+	-march=rv32ec \
+	-mabi=ilp32e \
+	-I/usr/include/newlib \
+	-I$(CH32V003FUN) \
+	-nostdlib \
+	-I. -DSTDOUT_UART -Wall
+
+LDFLAGS:=-T $(CH32V003FUN)/ch32v003fun.ld -Wl,--gc-sections -L../../misc -lgcc
+
+SYSTEM_C:=$(CH32V003FUN)/ch32v003fun.c
+
+$(TARGET).elf : $(SYSTEM_C) $(TARGET).c
+	$(PREFIX)-gcc -o $@ $^ $(CFLAGS) $(LDFLAGS)
+
+$(TARGET).bin : $(TARGET).elf
+	$(PREFIX)-size $^
+	$(PREFIX)-objdump -S $^ > $(TARGET).lst
+	$(PREFIX)-objdump -t $^ > $(TARGET).map
+	$(PREFIX)-objcopy -O binary $< $(TARGET).bin
+	$(PREFIX)-objcopy -O ihex $< $(TARGET).hex
+
+flash : $(TARGET).bin
+	$(MINICHLINK)/minichlink -w $< flash -b
+
+clean :
+	rm -rf $(TARGET).elf $(TARGET).bin $(TARGET).hex $(TARGET).lst $(TARGET).map $(TARGET).hex
+
diff --git a/examples/systick_irq_millis/README.md b/examples/systick_irq_millis/README.md
new file mode 100644
index 0000000000000000000000000000000000000000..f9d469a8e1efcd6bea087d822b0133785116b694
--- /dev/null
+++ b/examples/systick_irq_millis/README.md
@@ -0,0 +1,35 @@
+# Systick IRQ demonstration
+This example shows how to set up the built-in Systick IRQ to generate periodic
+interrupts for timing. Many bare-metal and RTOS based embedded applications will
+use the Systick IRQ for timing, periodic housekeeping and task arbitration so
+knowing how to set that up is useful.
+
+Systick.h now also contains an arduino-like millis() function to return the
+current time.
+The example has been adapted to use this millis() function for a lock-free program.
+Here, you compare the difference of current time and the last time a task was
+executed against a desired interval. If enough time has passed, run the next
+step.
+
+The benefit of this dance is that the CPU doesn't spend 250ms (12,000,000
+cycles!) drooling onto the carpet.
+Other important tasks (reading buttons, DDoSing your grandma) can run until it's time.
+
+Of course a real RTos would be much more powerful.
+
+Note that this example is *NOT* compatible with the Delay_Ms() function that's
+part of the ch32v003fun library - that function uses the Systick counter for
+doing busy-wait delays and will interfere with its use in generating predictable
+IRQs. Do not use the built-in Delay_Ms() and related functions when using Systick
+for IRQs.
+
+Note also the use of the  `__attribute__((interrupt))` syntax in declaring the
+IRQ handler. Some of the IRQ examples from the WCH HAL library have slightly
+different syntax to make use of a fast IRQ mode but which is not compatible with
+generic RISC-V GCC so that feature is not used here.
+
+# Use
+Connect LEDs (with proper current limit resistors) to GPIO pins C0, D0, D4 and
+a 3.3V logic level serial terminal to PD5. The LEDs will flash and an incrementing
+count will be printed to the serial port at rates that are controlled by the
+Systick IRQ.
diff --git a/examples/systick_irq_millis/systick.h b/examples/systick_irq_millis/systick.h
new file mode 100644
index 0000000000000000000000000000000000000000..1f3700e660fecb4821dfd9528404fc0c36118618
--- /dev/null
+++ b/examples/systick_irq_millis/systick.h
@@ -0,0 +1,75 @@
+/*
+ * Single-File-Header for using SysTick with millisecond interrupts
+ * 03-25-2023 E. Brombaugh
+ */
+
+#ifndef _SYSTICK_H
+#define _SYSTICK_H
+
+#include <stdint.h>
+
+/* some bit definitions for systick regs */
+#define SYSTICK_SR_CNTIF (1<<0)
+#define SYSTICK_CTLR_STE (1<<0)
+#define SYSTICK_CTLR_STIE (1<<1)
+#define SYSTICK_CTLR_STCLK (1<<2)
+#define SYSTICK_CTLR_STRE (1<<3)
+#define SYSTICK_CTLR_SWIE (1<<31)
+
+volatile uint32_t systick_cnt;
+
+/*
+ * Start up the SysTick IRQ
+ */
+void systick_init(void)
+{
+	/* enable the SysTick IRQ */
+	NVIC_EnableIRQ(SysTicK_IRQn);
+	
+	/* Clear any existing IRQ */
+    SysTick->SR &= ~SYSTICK_SR_CNTIF;
+	
+	/* Set the tick interval to 1ms for normal op */
+    SysTick->CMP = (SYSTEM_CORE_CLOCK/1000)-1;
+	
+	/* Start at zero */
+    SysTick->CNT = 0;
+	systick_cnt = 0;
+	
+	/* Enable SysTick counter, IRQ, HCLK/1, auto reload */
+    SysTick->CTLR = SYSTICK_CTLR_STE | SYSTICK_CTLR_STIE | 
+					SYSTICK_CTLR_STCLK | SYSTICK_CTLR_STRE;
+}
+
+#if 1
+/*
+ * SysTick ISR just counts ticks
+ * note - the __attribute__((interrupt)) syntax is crucial!
+ */
+void SysTick_Handler(void) __attribute__((interrupt));
+void SysTick_Handler(void)
+{
+	/* clear IRQ */
+    SysTick->SR &= 0;
+	
+	/* update counter */
+	systick_cnt++;
+}
+#endif
+
+/*
+ * Millisecond delay routine
+ */
+void systick_delay_ms(uint32_t milliseconds)
+{
+	/* compute end time */
+	uint32_t etime = systick_cnt + milliseconds;
+	
+	/* wait for current time == end time */
+	while(systick_cnt != etime);
+}
+
+uint32_t millis() {
+    return systick_cnt;
+}
+#endif
diff --git a/examples/systick_irq_millis/systick_irq_millis.c b/examples/systick_irq_millis/systick_irq_millis.c
new file mode 100644
index 0000000000000000000000000000000000000000..b306cf6ff2cdeb3df15c5b6a7c27750dc07171b8
--- /dev/null
+++ b/examples/systick_irq_millis/systick_irq_millis.c
@@ -0,0 +1,76 @@
+/*
+ * Example for using SysTick with IRQs
+ * 03-25-2023 E. Brombaugh
+ */
+
+// Could be defined here, or in the processor defines.
+#define SYSTEM_CORE_CLOCK 48000000
+#define APB_CLOCK SYSTEM_CORE_CLOCK
+
+#include "ch32v003fun.h"
+#include <stdio.h>
+#include "systick.h"
+
+int main()
+{
+	uint32_t count = 0;
+	
+	SystemInit48HSI();
+
+	// start serial @ default 115200bps
+	SetupUART( UART_BRR );
+	printf("\r\r\n\nsystick_irq example\n\r");
+
+	printf("SysTick_Handler = 0x%08X\n\r", SysTick_Handler);
+
+	// init systick @ 1ms rate
+	printf("initializing systick...");
+	systick_init();
+	printf("done.\n\r");
+	
+	// Enable GPIOs
+	RCC->APB2PCENR |= RCC_APB2Periph_GPIOD | RCC_APB2Periph_GPIOC;
+
+	// GPIO D0 Push-Pull
+	GPIOD->CFGLR &= ~(0xf<<(4*0));
+	GPIOD->CFGLR |= (GPIO_Speed_10MHz | GPIO_CNF_OUT_PP)<<(4*0);
+
+	// GPIO D4 Push-Pull
+	GPIOD->CFGLR &= ~(0xf<<(4*4));
+	GPIOD->CFGLR |= (GPIO_Speed_10MHz | GPIO_CNF_OUT_PP)<<(4*4);
+
+	// GPIO C0 Push-Pull
+	GPIOC->CFGLR &= ~(0xf<<(4*0));
+	GPIOC->CFGLR |= (GPIO_Speed_10MHz | GPIO_CNF_OUT_PP)<<(4*0);
+
+	const uint16_t led_i = 250;
+	uint32_t led_t_last;
+	uint8_t led_progstep = 0;
+
+	printf("looping...\n\r");
+	while(1)
+	{
+		if (millis() - led_t_last >= led_i) {
+			switch (led_progstep) {
+				case 0:
+					GPIOD->BSHR = 1 | (1<<4);	 // Turn on GPIOs
+					break;
+				case 1:
+					GPIOC->BSHR = 1;
+					break;
+				case 2:
+					GPIOD->BSHR = (1<<16) | (1<<(16+4)); // Turn off GPIODs
+					break;
+				case 3:
+					GPIOC->BSHR = (1<<16);
+					printf( "Count: %lu\n\r", count++ );
+					break;
+			}
+			led_progstep++;
+			led_t_last = millis();
+			if (led_progstep > 3) {
+				led_progstep = 0;
+			}
+		}
+	}
+}