timer-periodic-interrupt
The canonical "periodic ISR plus main-thread coordination" example. A free-running counter re-arms a 100 ms alarm; the ISR sets a flag; the main thread drains the flag and toggles an LED.
Source: examples/power-timing/timer-periodic-interrupt/.
What it does
alp_counter_open()+alp_counter_start()— bring up a free-running counter.alp_counter_us_to_ticks()— portable microsecond → tick conversion that hides the per-SoM tick rate.alp_counter_set_alarm()— a one-shot deadline with an IRQ callback.- Periodic out of one-shot: the alarm callback re-arms itself for the next period. There is no periodic-alarm API — this is the idiom.
alp_gpio_open(BOARD_PIN_LED_RED)+ configure + write — the LED toggle, from the main thread.- Clean shutdown via
alp_counter_cancel_alarm()+alp_counter_close().
The EVK has no plain GPIO LED, so the indicator is the RGB-red pad — whose default function is PWM — claimed as a digital GPIO through the e1m-spec "GPIO secondary" capability.
The ISR-safety pattern
This is the point of the example. The callback runs in ISR context and does the minimum: record the tick value, bump a counter, set g_tick_fired = true, re-arm. Everything else happens on the main thread.
Safe from an alarm callback:
alp_gpio_write()/alp_gpio_read()— register access only, no blockingalp_counter_set_alarm()/alp_counter_get_value()- Zephyr's
printk()(slow, but safe) - Atomic ops (
atomic_inc,atomic_set)
Never from an ISR:
printf(),LOG_INF(),LOG_DBG()— they take locks / allocatealp_i2c_*,alp_spi_*,alp_uart_*— blocking I/Ok_sleep(),k_msleep(),k_mutex_lock()— they block the ISRk_malloc()and other allocation primitives
When in doubt, defer to a worker thread or the main loop via a flag or message queue. A volatile bool is enough here; reach for k_event / k_msgq when fan-out grows.
board.yaml
som:
sku: E1M-AEN801
preset: e1m-evk
supported_boards:
- e1m-evk
- e1m-x-evk
pins:
- { e1m: E1M_GPIO_PWM3, macro: EVK_PIN_LED_RED, doc: "RGB LED red -- the PWM3 pad as a digital GPIO" }
cores:
m55_hp:
app: ./src
peripherals:
- counter # CONFIG_COUNTER=y -- backs alp_counter_*
- gpio # CONFIG_GPIO=y -- backs alp_gpio_*
diagnostics:
log_level: info
Expected output
Real AEN hardware:
[timer] open counter=0
[timer] start -> 0
[timer] 100000 us = 1600 ticks (status=0)
[timer] open LED on BOARD_PIN_LED_RED
[timer] arming first alarm
[timer] tick 0 fired @ 1600 ticks, LED -> 1
[timer] tick 1 fired @ 3200 ticks, LED -> 0
...
[timer] done
The LED toggles every 100 ms — a 5 Hz blink.
native_sim has no counter device, so the run exits after one diagnostic:
[timer] open counter=0
[timer] open counter failed: alp_last_error=-2
[timer] done
V2N: the ISR path is not available
On the V2N supervisor backend the example stops early:
[timer] us_to_ticks not supported on this backend; this example is AEN / native_sim today
[timer] done
The GD32 IO MCU has no interrupt line back to the Renesas host, so alarm callbacks fired in GD32 firmware ISR context can't be relayed across the bridge in bounded time. set_alarm returns NOSUPPORT. Run this example on AEN for the working ISR path.
The twister rows cover both EVKs, but the V2N row's value is the graceful-degradation diagnostic, not a working alarm.
Customising
| Knob | Effect |
|---|---|
ALARM_PERIOD_US | Drop to 10000 for a 100 Hz toggle (visible breathe); raise to 1000000 for a 1 Hz "is it alive?" blink. |
BOARD_PIN_LED_RED | Swap for a plain ALP_E1M_GPIO_IO<N> on a board with a dedicated GPIO LED, or any free GPIO to scope-probe. |
| Drop the GPIO block | The printf trace alone proves the alarm is firing. |
Status
Two twister rows on native_sim/native/64; the harness latches on [timer] done. The alarm path itself is proven on AEN silicon — native_sim exercises only the graceful open-failure diagnostic.
See also
<alp/counter.h>reference<alp/peripheral.h>reference — GPIOcounter-alarm— the single-shot alarm siblinggpio-button-led— GPIO basics and the same pin-as-GPIO trick- Examples overview