Skip to content

cpu/rp2350: implement timers#22375

Open
AnnsAnns wants to merge 2 commits into
RIOT-OS:masterfrom
AnnsAnns:rp2350_timer
Open

cpu/rp2350: implement timers#22375
AnnsAnns wants to merge 2 commits into
RIOT-OS:masterfrom
AnnsAnns:rp2350_timer

Conversation

@AnnsAnns

Copy link
Copy Markdown
Member

Contribution description

Part of the ongoing "wait people actually use the pico2, oh god why" series. This implements timer support, hooray. Timers are surprisingly not that complicated, I just never looked at them when adding support because I didn't have a proper use outside of xosc_sleep which was enough for my needs.

Testing procedure

cd examples/guides/timers
sudo BOARD=rpi-pico-2-arm make flash term PORT=/dev/ttyUSB0

Issues/PRs references

Declaration of AI-Tools / LLMs usage:

AI-Tools / LLMs that were used are:

  • Claude Fable 5, mostly to experiment with it, I cross checked its claims with the datasheet at all times. As said the timers stuff on the pico 2 is not as complicated as I imagined, chapter 12.8 and related timer tables are fairly understandable.

@github-actions github-actions Bot added Area: boards Area: Board ports Area: cpu Area: CPU/MCU ports labels Jun 11, 2026
@AnnsAnns AnnsAnns added the CI: ready for build If set, CI server will compile all applications for all available boards for the labeled PR label Jun 11, 2026
@riot-ci

riot-ci commented Jun 11, 2026

Copy link
Copy Markdown

Murdock results

✔️ PASSED

bf945ab fix: why do people import timex instead of time_units?

Success Failures Total Runtime
11124 0 11124 10m:17s

Artifacts

@crasbe crasbe added the Type: new feature The issue requests / The PR implemements a new feature for RIOT label Jun 11, 2026
Comment on lines 2 to +4
FEATURES_PROVIDED += periph_uart
FEATURES_PROVIDED += periph_timer
FEATURES_PROVIDED += periph_timer_periodic

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
FEATURES_PROVIDED += periph_uart
FEATURES_PROVIDED += periph_timer
FEATURES_PROVIDED += periph_timer_periodic
FEATURES_PROVIDED += periph_timer
FEATURES_PROVIDED += periph_timer_periodic
FEATURES_PROVIDED += periph_uart

A B C D E F G,
H I J K, LMNOP,
Q R S T U V W,
X, Y, Z, Juche!
Jetzt können wir das ABC,
so geht unser Alphabet.

Comment on lines +213 to +259
void isr_timer0_0(void)
{
_isr(0, 0);
rp_end_isr();
}

void isr_timer0_1(void)
{
_isr(0, 1);
rp_end_isr();
}

void isr_timer0_2(void)
{
_isr(0, 2);
rp_end_isr();
}

void isr_timer0_3(void)
{
_isr(0, 3);
rp_end_isr();
}

void isr_timer1_0(void)
{
_isr(1, 0);
rp_end_isr();
}

void isr_timer1_1(void)
{
_isr(1, 1);
rp_end_isr();
}

void isr_timer1_2(void)
{
_isr(1, 2);
rp_end_isr();
}

void isr_timer1_3(void)
{
_isr(1, 3);
rp_end_isr();
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think in the original PR I also mentioned that it would be nice to add a short comment above what the ISRs are for and how they are assigned etc.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe something like /* The ISR names are `isr_timer<device>_<channel>` */

Comment on lines +36 to +37
static uint8_t flag_periodic[TIMER_NUMOF];
static uint8_t flag_reset[TIMER_NUMOF];

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
static uint8_t flag_periodic[TIMER_NUMOF];
static uint8_t flag_reset[TIMER_NUMOF];
static uint8_t _flag_periodic[TIMER_NUMOF];
static uint8_t _flag_reset[TIMER_NUMOF];

As these are private, global variables, I would propose to give them an underscore too.

/**
* @ingroup cpu_rp2350
* @{
*

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe you can add a comment here which date and version the RP2350 Reference Manual has that is referenced here. That will make it much easier in 10 years when all the references have changed 😅

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

where is the fun in that, future generations need to be scared of me in the git blame

irq_restore(state);
}

static void _isr(tim_t dev, int channel)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
static void _isr(tim_t dev, int channel)
static void _timer_isr(tim_t dev, int channel)

Even though _isr should only be used in the private scope, using _timer_isr is less likely to lead to naming collisions IMO.

Comment on lines +112 to +113
if (!timeout) {
/* catch case that a tick happens right before arming the alarm */

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I find this confusion. Firstly I'd write if(timeout == 0), as it is an integer and not a boolean. Same comparison, but the former is IMO clearer than the latter.

Secondly, the comment does not really fit the code, does it? The alarm won't be armed for timeout == 0, instead the callback is just called once.

return 0;
}

int timer_set_absolute(tim_t dev, int channel, unsigned int value)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does this function need a check for value >= 0?
Does a value of 0 or a negative value make sense?

return 0;
}

int timer_set_periodic(tim_t dev, int channel, unsigned int value, uint8_t flags)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does this function need a check for value >= 0?
Does a value of 0 or a negative value make sense?

unsigned state = irq_disable();
/* The ARMED bits are write-1-clear (table 1236) */
DEV(dev)->ARMED = 1u << channel;
flag_periodic[dev] &= ~(1u << channel);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we also reset the _flag_reset flag here? Not that it would matter much (it is set and reset by timer_set), but maybe good practice?

#include "debug.h"

#define DEV(d) (timer_config[d].dev)
#define ALARM(d, a) ((&(DEV(d)->ALARM0)) + (a))

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
#define ALARM(d, a) ((&(DEV(d)->ALARM0)) + (a))
#define ALARM_REG(d, a) ((&(DEV(d)->ALARM0)) + (a))

Maybe? I think this is a bit of a hack, but I can't come up with a better solution that isn't a static inline function which is not guaranteed to be fully resolved by the compiler.

Also... is this correct? The registers are not spaced one byte apart, but four bytes. Shouldn't it have to be a*4 to get the right register address?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

sizeof data type is already in the the pointer math

@crasbe

crasbe commented Jun 12, 2026

Copy link
Copy Markdown
Contributor

Haven't tested it myself yet and I really don't have the time to at the moment, I just did some minor procrastination here.

@AnnsAnns

Copy link
Copy Markdown
Member Author

Thank you for looking into this, your procrastination counters mine, it's Yin and Yang 🫪


/* Generate one tick per microsecond from clk_ref, which runs from xosc */
if (DEV(dev) == TIMER0) {
TICKS->TIMER0_CYCLES = CLOCK_XOSC / MHZ(1);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

wouldn't

Suggested change
TICKS->TIMER0_CYCLES = CLOCK_XOSC / MHZ(1);
assert(CLOCK_XOSC % freq == 0)
TICKS->TIMER0_CYCLES = CLOCK_XOSC / freq;

work

Comment thread cpu/rp2350_common/periph/timer.c
Comment on lines +155 to +157
if (flags & TIM_FLAG_SET_STOPPED) {
timer_stop(dev);
}

@kfessel kfessel Jun 12, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

strange flag strange documentation

/**
 * @brief   Keep the timer stopped after setting alarm.
 *
 * When set, the timer will remained stopped after a timer_set_periodic() and
 * can be started manually with timer_start().
 */
#ifndef TIM_FLAG_SET_STOPPED
#define TIM_FLAG_SET_STOPPED    (0x04)
#endif

-> implies to me: the timer is started if this flag is not set -- this implementation can be found with start if not set
-> to me the documentation does not imply that the timer might be stopped as in this implementation

but a short check reveals about half of our cpus do it one way and the other half... and then there is a wired one

start if not set

sam0
https://github.com/RIOT-OS/RIOT/blob/master/cpu/sam0_common/periph/timer.c#L305
'native'
https://github.com/RIOT-OS/RIOT/blob/master/cpu/native/periph/timer.c#L179
nrf
https://github.com/RIOT-OS/RIOT/blob/master/cpu/nrf5x_common/periph/timer.c#L222

start or stop but only for channel 0

atmega
https://github.com/RIOT-OS/RIOT/blob/master/cpu/atmega_common/periph/timer.c#L263

stop the timer

lpc
https://github.com/RIOT-OS/RIOT/blob/master/cpu/lpc23xx/periph/timer.c#L208

stm32
https://github.com/RIOT-OS/RIOT/blob/master/cpu/stm32/periph/timer.c#L228

rpx0
https://github.com/RIOT-OS/RIOT/blob/master/cpu/rpx0xx/periph/timer.c#L184

@crasbe crasbe left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why not use the existing register defines?

flag_reset[dev] = 0;

/* Reset the timer block, so we can be sure it is in a known state */
uint32_t reset_bit = (DEV(dev) == TIMER0) ? RESET_TIMER0 : RESET_TIMER1;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
uint32_t reset_bit = (DEV(dev) == TIMER0) ? RESET_TIMER0 : RESET_TIMER1;
uint32_t reset_bit;
reset_bit = (DEV(dev) == TIMER0) ? RESETS_RESET_TIMER0_BITS : RESETS_RESET_TIMER1_BITS;

Comment on lines +22 to +27
/** Reset bit for the TIMER0 peripheral */
#define RESET_TIMER0 (1u << 23u)

/** Reset bit for the TIMER1 peripheral */
#define RESET_TIMER1 (1u << 24u)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
/** Reset bit for the TIMER0 peripheral */
#define RESET_TIMER0 (1u << 23u)
/** Reset bit for the TIMER1 peripheral */
#define RESET_TIMER1 (1u << 24u)

/* Generate one tick per microsecond from clk_ref, which runs from xosc */
if (DEV(dev) == TIMER0) {
TICKS->TIMER0_CYCLES = CLOCK_XOSC / MHZ(1);
TICKS->TIMER0_CTRL = TICKS_CTRL_ENABLE_BITS;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
TICKS->TIMER0_CTRL = TICKS_CTRL_ENABLE_BITS;
TICKS->TIMER0_CTRL = TICKS_TIMER0_CTRL_ENABLE_BITS;

}
else {
TICKS->TIMER1_CYCLES = CLOCK_XOSC / MHZ(1);
TICKS->TIMER1_CTRL = TICKS_CTRL_ENABLE_BITS;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
TICKS->TIMER1_CTRL = TICKS_CTRL_ENABLE_BITS;
TICKS->TIMER1_CTRL = TICKS_TIMER1_CTRL_ENABLE_BITS;

Comment on lines +28 to +30
/** Enable bit of a tick generator control register */
#define TICKS_CTRL_ENABLE_BITS (1u << 0u)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
/** Enable bit of a tick generator control register */
#define TICKS_CTRL_ENABLE_BITS (1u << 0u)

@AnnsAnns

Copy link
Copy Markdown
Member Author

Why not use the existing register defines?

I'm currently looking into getting the RP2350.h to be more compatible in general in the hopes of removing all defines that exist in the sdk, there is some weirdness still

@crasbe

crasbe commented Jun 13, 2026

Copy link
Copy Markdown
Contributor

Why not use the existing register defines?

I'm currently looking into getting the RP2350.h to be more compatible in general in the hopes of removing all defines that exist in the sdk, there is some weirdness still

I don't quite follow?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Area: boards Area: Board ports Area: cpu Area: CPU/MCU ports CI: ready for build If set, CI server will compile all applications for all available boards for the labeled PR Type: new feature The issue requests / The PR implemements a new feature for RIOT

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants