cpu/rp2350: implement timers#22375
Conversation
| FEATURES_PROVIDED += periph_uart | ||
| FEATURES_PROVIDED += periph_timer | ||
| FEATURES_PROVIDED += periph_timer_periodic |
There was a problem hiding this comment.
| 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.
| 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(); | ||
| } |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
Maybe something like /* The ISR names are `isr_timer<device>_<channel>` */
| static uint8_t flag_periodic[TIMER_NUMOF]; | ||
| static uint8_t flag_reset[TIMER_NUMOF]; |
There was a problem hiding this comment.
| 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 | ||
| * @{ | ||
| * |
There was a problem hiding this comment.
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 😅
There was a problem hiding this comment.
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) |
There was a problem hiding this comment.
| 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.
| if (!timeout) { | ||
| /* catch case that a tick happens right before arming the alarm */ |
There was a problem hiding this comment.
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) |
There was a problem hiding this comment.
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) |
There was a problem hiding this comment.
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); |
There was a problem hiding this comment.
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)) |
There was a problem hiding this comment.
| #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?
There was a problem hiding this comment.
sizeof data type is already in the the pointer math
|
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. |
|
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); |
There was a problem hiding this comment.
wouldn't
| TICKS->TIMER0_CYCLES = CLOCK_XOSC / MHZ(1); | |
| assert(CLOCK_XOSC % freq == 0) | |
| TICKS->TIMER0_CYCLES = CLOCK_XOSC / freq; |
work
| if (flags & TIM_FLAG_SET_STOPPED) { | ||
| timer_stop(dev); | ||
| } |
There was a problem hiding this comment.
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
left a comment
There was a problem hiding this comment.
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; |
There was a problem hiding this comment.
| 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; |
| /** Reset bit for the TIMER0 peripheral */ | ||
| #define RESET_TIMER0 (1u << 23u) | ||
|
|
||
| /** Reset bit for the TIMER1 peripheral */ | ||
| #define RESET_TIMER1 (1u << 24u) | ||
|
|
There was a problem hiding this comment.
| /** 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; |
There was a problem hiding this comment.
| 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; |
There was a problem hiding this comment.
| TICKS->TIMER1_CTRL = TICKS_CTRL_ENABLE_BITS; | |
| TICKS->TIMER1_CTRL = TICKS_TIMER1_CTRL_ENABLE_BITS; |
| /** Enable bit of a tick generator control register */ | ||
| #define TICKS_CTRL_ENABLE_BITS (1u << 0u) | ||
|
|
There was a problem hiding this comment.
| /** Enable bit of a tick generator control register */ | |
| #define TICKS_CTRL_ENABLE_BITS (1u << 0u) |
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? |
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/timerssudo BOARD=rpi-pico-2-arm make flash term PORT=/dev/ttyUSB0Issues/PRs references
Declaration of AI-Tools / LLMs usage:
AI-Tools / LLMs that were used are: