diff --git a/Makefile.dep b/Makefile.dep index b092f3ecd6..a8183c5f2f 100644 --- a/Makefile.dep +++ b/Makefile.dep @@ -1050,6 +1050,10 @@ ifneq (,$(filter periph_gpio_irq,$(USEMODULE))) FEATURES_REQUIRED += periph_gpio endif +ifneq (,$(filter periph_timer_periodic,$(USEMODULE))) + FEATURES_REQUIRED += periph_timer +endif + ifneq (,$(filter devfs_hwrng,$(USEMODULE))) FEATURES_REQUIRED += periph_hwrng endif diff --git a/cpu/atmega_common/Makefile.features b/cpu/atmega_common/Makefile.features index 03a73c9243..eb49bcd72f 100644 --- a/cpu/atmega_common/Makefile.features +++ b/cpu/atmega_common/Makefile.features @@ -5,6 +5,7 @@ FEATURES_PROVIDED += periph_cpuid FEATURES_PROVIDED += periph_eeprom FEATURES_PROVIDED += periph_gpio periph_gpio_irq FEATURES_PROVIDED += periph_pm +FEATURES_PROVIDED += periph_timer_periodic FEATURES_PROVIDED += periph_wdt FEATURES_PROVIDED += puf_sram diff --git a/cpu/atmega_common/periph/timer.c b/cpu/atmega_common/periph/timer.c index 4fa9d8dc11..45229de5f7 100644 --- a/cpu/atmega_common/periph/timer.c +++ b/cpu/atmega_common/periph/timer.c @@ -74,6 +74,23 @@ static ctx_t ctx[] = { #endif }; +static unsigned _oneshot; + +static inline void set_oneshot(tim_t tim, int chan) +{ + _oneshot |= (1 << chan) << (TIMER_CHANNELS * tim); +} + +static inline void clear_oneshot(tim_t tim, int chan) +{ + _oneshot &= ~((1 << chan) << (TIMER_CHANNELS * tim)); +} + +static inline bool is_oneshot(tim_t tim, int chan) +{ + return _oneshot & ((1 << chan) << (TIMER_CHANNELS * tim)); +} + /** * @brief Setup the given timer */ @@ -109,7 +126,7 @@ int timer_init(tim_t tim, unsigned long freq, timer_cb_t cb, void *arg) } } if (pre == PRESCALE_NUMOF) { - DEBUG("timer.c: prescaling failed!\n"); + DEBUG("timer.c: prescaling from %lu Hz failed!\n", CLOCK_CORECLOCK); return -1; } @@ -138,9 +155,12 @@ int timer_set_absolute(tim_t tim, int channel, unsigned int value) } unsigned state = irq_disable(); + ctx[tim].dev->OCR[channel] = (uint16_t)value; *ctx[tim].flag &= ~(1 << (channel + OCF1A)); *ctx[tim].mask |= (1 << (channel + OCIE1A)); + set_oneshot(tim, channel); + irq_restore(state); return 0; @@ -154,9 +174,11 @@ int timer_set(tim_t tim, int channel, unsigned int timeout) unsigned state = irq_disable(); unsigned absolute = ctx[tim].dev->CNT + timeout; + ctx[tim].dev->OCR[channel] = absolute; - uint8_t mask = 1 << (channel + OCIE1A); - *ctx[tim].mask |= mask; + *ctx[tim].mask |= (1 << (channel + OCIE1A)); + set_oneshot(tim, channel); + if ((absolute - ctx[tim].dev->CNT) > timeout) { /* Timer already expired. Trigger the interrupt now and loop until it * is triggered. @@ -164,13 +186,53 @@ int timer_set(tim_t tim, int channel, unsigned int timeout) while (!(*ctx[tim].flag & (1 << (OCF1A + channel)))) { ctx[tim].dev->OCR[channel] = ctx[tim].dev->CNT; } - } + irq_restore(state); return 0; } +int timer_set_periodic(tim_t tim, int channel, unsigned int value, uint8_t flags) +{ + int res = 0; + + if (channel >= TIMER_CHANNELS) { + return -1; + } + + if (flags & TIM_FLAG_RESET_ON_SET) { + ctx[tim].dev->CNT = 0; + } + + unsigned state = irq_disable(); + + ctx[tim].dev->OCR[channel] = (uint16_t)value; + + *ctx[tim].flag &= ~(1 << (channel + OCF1A)); + *ctx[tim].mask |= (1 << (channel + OCIE1A)); + + clear_oneshot(tim, channel); + + /* only OCR0 can be use to set TOP */ + if (channel == 0) { + if (flags & TIM_FLAG_RESET_ON_MATCH) { + /* enable CTC mode */ + ctx[tim].dev->CRB |= (1 << 3); + } else { + /* disable CTC mode */ + ctx[tim].dev->CRB &= (1 << 3); + } + } else { + assert((flags & TIM_FLAG_RESET_ON_MATCH) == 0); + res = -1; + } + + irq_restore(state); + + return res; +} + int timer_clear(tim_t tim, int channel) { if (channel >= TIMER_CHANNELS) { @@ -216,7 +278,9 @@ static inline void _isr(tim_t tim, int chan) atmega_enter_isr(); - *ctx[tim].mask &= ~(1 << (chan + OCIE1A)); + if (is_oneshot(tim, chan)) { + *ctx[tim].mask &= ~(1 << (chan + OCIE1A)); + } ctx[tim].cb(ctx[tim].arg, chan); #if defined(DEBUG_TIMER_PORT) diff --git a/cpu/lpc2387/Makefile.features b/cpu/lpc2387/Makefile.features index 296fd508bc..de2cdc6b9a 100644 --- a/cpu/lpc2387/Makefile.features +++ b/cpu/lpc2387/Makefile.features @@ -2,5 +2,6 @@ FEATURES_PROVIDED += backup_ram FEATURES_PROVIDED += periph_dac FEATURES_PROVIDED += periph_gpio periph_gpio_irq +FEATURES_PROVIDED += periph_timer_periodic -include $(RIOTCPU)/arm7_common/Makefile.features diff --git a/cpu/lpc2387/include/periph_cpu.h b/cpu/lpc2387/include/periph_cpu.h index 3b7292b58e..bf1db82f7f 100644 --- a/cpu/lpc2387/include/periph_cpu.h +++ b/cpu/lpc2387/include/periph_cpu.h @@ -126,7 +126,7 @@ typedef struct { /** * @brief Number of available timer channels */ -#define TIMER_CHAN_NUMOF (4U) +#define TIMER_CHANNELS (4U) /** * @brief Declare needed generic SPI functions diff --git a/cpu/lpc2387/periph/timer.c b/cpu/lpc2387/periph/timer.c index 1bedb5d9e2..bf1c7caeb8 100644 --- a/cpu/lpc2387/periph/timer.c +++ b/cpu/lpc2387/periph/timer.c @@ -38,6 +38,23 @@ */ static timer_isr_ctx_t isr_ctx[TIMER_NUMOF]; +static uint32_t _oneshot; + +static inline void set_oneshot(tim_t tim, int chan) +{ + _oneshot |= (1 << chan) << (TIMER_CHANNELS * tim); +} + +static inline void clear_oneshot(tim_t tim, int chan) +{ + _oneshot &= ~((1 << chan) << (TIMER_CHANNELS * tim)); +} + +static inline bool is_oneshot(tim_t tim, int chan) +{ + return _oneshot & ((1 << chan) << (TIMER_CHANNELS * tim)); +} + /** * @brief Forward declarations for interrupt functions * @{ @@ -139,19 +156,53 @@ int timer_init(tim_t tim, unsigned long freq, timer_cb_t cb, void *arg) int timer_set_absolute(tim_t tim, int channel, unsigned int value) { - if (((unsigned) tim >= TIMER_NUMOF) || ((unsigned) channel >= TIMER_CHAN_NUMOF)) { + if (((unsigned) tim >= TIMER_NUMOF) || ((unsigned) channel >= TIMER_CHANNELS)) { return -1; } lpc23xx_timer_t *dev = get_dev(tim); + + set_oneshot(tim, channel); + dev->MR[channel] = value; + /* Match Interrupt */ dev->MCR |= (1 << (channel * 3)); return 0; } +int timer_set_periodic(tim_t tim, int channel, unsigned int value, uint8_t flags) +{ + if (((unsigned) tim >= TIMER_NUMOF) || ((unsigned) channel >= TIMER_CHANNELS)) { + return -1; + } + + lpc23xx_timer_t *dev = get_dev(tim); + + if (flags & TIM_FLAG_RESET_ON_SET) { + /* reset the timer */ + dev->TCR = 2; + /* start the timer */ + /* cppcheck-suppress redundantAssignment + * (reason: TCR is volatile control register. + Bit 2 will put the timer into Reset + Bit 1 will control if the timer is running) */ + dev->TCR = 1; + } + + clear_oneshot(tim, channel); + + uint8_t cfg = (flags & TIM_FLAG_RESET_ON_MATCH) + ? 0x3 /* Match Interrupt & Reset on Match */ + : 0x1; /* Match Interrupt */ + + dev->MR[channel] = value; + dev->MCR |= (cfg << (channel * 3)); + return 0; +} + int timer_clear(tim_t tim, int channel) { - if (((unsigned) tim >= TIMER_NUMOF) || ((unsigned) channel >= TIMER_CHAN_NUMOF)) { + if (((unsigned) tim >= TIMER_NUMOF) || ((unsigned) channel >= TIMER_CHANNELS)) { return -1; } get_dev(tim)->MCR &= ~(1 << (channel * 3)); @@ -173,15 +224,28 @@ void timer_stop(tim_t tim) get_dev(tim)->TCR = 0; } +static inline void chan_handler(lpc23xx_timer_t *dev, unsigned tim_num, unsigned chan_num) +{ + const uint32_t mask = (1 << chan_num); + + if ((dev->IR & mask) == 0) { + return; + } + + dev->IR |= mask; + if (is_oneshot(tim_num, chan_num)) { + dev->MCR &= ~(1 << (chan_num * 3)); + } + + isr_ctx[tim_num].cb(isr_ctx[tim_num].arg, chan_num); +} + static inline void isr_handler(lpc23xx_timer_t *dev, int tim_num) { - for (unsigned i = 0; i < TIMER_CHAN_NUMOF; i++) { - if (dev->IR & (1 << i)) { - dev->IR |= (1 << i); - dev->MCR &= ~(1 << (i * 3)); - isr_ctx[tim_num].cb(isr_ctx[tim_num].arg, i); - } + for (unsigned i = 0; i < TIMER_CHANNELS; ++i) { + chan_handler(dev, tim_num, i); } + /* we must not forget to acknowledge the handling of the interrupt */ VICVectAddr = 0; } diff --git a/cpu/sam0_common/Makefile.dep b/cpu/sam0_common/Makefile.dep index b286abfdfa..2dc27cd379 100644 --- a/cpu/sam0_common/Makefile.dep +++ b/cpu/sam0_common/Makefile.dep @@ -5,6 +5,9 @@ endif # All SAM0 based CPUs provide PM USEMODULE += pm_layered +# the timer implements timer_set_periodic() +FEATURES_PROVIDED += periph_timer_periodic + # include sam0 common periph drivers USEMODULE += sam0_common_periph diff --git a/cpu/sam0_common/include/periph_cpu_common.h b/cpu/sam0_common/include/periph_cpu_common.h index e9be610222..e55e2a109b 100644 --- a/cpu/sam0_common/include/periph_cpu_common.h +++ b/cpu/sam0_common/include/periph_cpu_common.h @@ -343,6 +343,11 @@ typedef struct { uint16_t flags; /**< flags for CTRA, e.g. TC_CTRLA_MODE_COUNT32 */ } tc32_conf_t; +/** + * @brief Number of available timer channels + */ +#define TIMER_CHANNELS (2) + /** * @brief Set up alternate function (PMUX setting) for a PORT pin * diff --git a/cpu/sam0_common/periph/timer.c b/cpu/sam0_common/periph/timer.c index 775360c3a6..6e86a8ff9e 100644 --- a/cpu/sam0_common/periph/timer.c +++ b/cpu/sam0_common/periph/timer.c @@ -37,6 +37,23 @@ */ static timer_isr_ctx_t config[TIMER_NUMOF]; +static uint32_t _oneshot; + +static inline void set_oneshot(tim_t tim, int chan) +{ + _oneshot |= (1 << chan) << (TIMER_CHANNELS * tim); +} + +static inline void clear_oneshot(tim_t tim, int chan) +{ + _oneshot &= ~((1 << chan) << (TIMER_CHANNELS * tim)); +} + +static inline bool is_oneshot(tim_t tim, int chan) +{ + return _oneshot & ((1 << chan) << (TIMER_CHANNELS * tim)); +} + static inline TcCount32 *dev(tim_t tim) { return &timer_config[tim].dev->COUNT32; @@ -89,6 +106,32 @@ static uint8_t _get_prescaler(unsigned long freq_out, unsigned long freq_in) return scale; } +/* TOP value is CC0 */ +static inline void _set_mfrq(tim_t tim) +{ + timer_stop(tim); + wait_synchronization(tim); +#ifdef TC_WAVE_WAVEGEN_MFRQ + dev(tim)->WAVE.reg = TC_WAVE_WAVEGEN_MFRQ; +#else + dev(tim)->CTRLA.bit.WAVEGEN = TC_CTRLA_WAVEGEN_MFRQ_Val; +#endif + timer_start(tim); +} + +/* TOP value is MAX timer value */ +static inline void _set_nfrq(tim_t tim) +{ + timer_stop(tim); + wait_synchronization(tim); +#ifdef TC_WAVE_WAVEGEN_NFRQ + dev(tim)->WAVE.reg = TC_WAVE_WAVEGEN_NFRQ; +#else + dev(tim)->CTRLA.bit.WAVEGEN = TC_CTRLA_WAVEGEN_NFRQ_Val; +#endif + timer_start(tim); +} + /** * @brief Setup the given timer */ @@ -187,7 +230,52 @@ int timer_set_absolute(tim_t tim, int channel, unsigned int value) break; default: return -1; - } + } + + set_oneshot(tim, channel); + + return 0; +} + +int timer_set_periodic(tim_t tim, int channel, unsigned int value, uint8_t flags) +{ + DEBUG("Setting timer %i channel %i to %i (repeating)\n", tim, channel, value); + + /* set timeout value */ + switch (channel) { + case 0: + dev(tim)->INTFLAG.reg = TC_INTFLAG_MC0; + + if (flags & TIM_FLAG_RESET_ON_MATCH) { + _set_mfrq(tim); + } else { + _set_nfrq(tim); + } + + _set_cc(tim, 0, value); + dev(tim)->INTENSET.reg = TC_INTENSET_MC0; + break; + case 1: + + /* only CC0 can be used to set TOP */ + if (flags & TIM_FLAG_RESET_ON_MATCH) { + assert(0); + return -1; + } + + dev(tim)->INTFLAG.reg = TC_INTFLAG_MC1; + _set_cc(tim, 1, value); + dev(tim)->INTENSET.reg = TC_INTENSET_MC1; + break; + default: + return -1; + } + + if (flags & TIM_FLAG_RESET_ON_SET) { + dev(tim)->COUNT.reg = 0; + } + + clear_oneshot(tim, channel); return 0; } @@ -255,13 +343,22 @@ static inline void timer_isr(tim_t tim) tc->INTFLAG.reg = status; if ((status & TC_INTFLAG_MC0) && tc->INTENSET.bit.MC0) { - tc->INTENCLR.reg = TC_INTENCLR_MC0; + + if (is_oneshot(tim, 0)) { + tc->INTENCLR.reg = TC_INTENCLR_MC0; + } + if (config[tim].cb) { config[tim].cb(config[tim].arg, 0); } } + if ((status & TC_INTFLAG_MC1) && tc->INTENSET.bit.MC1) { - tc->INTENCLR.reg = TC_INTENCLR_MC1; + + if (is_oneshot(tim, 1)) { + tc->INTENCLR.reg = TC_INTENCLR_MC1; + } + if (config[tim].cb) { config[tim].cb(config[tim].arg, 1); } diff --git a/drivers/include/periph/timer.h b/drivers/include/periph/timer.h index fd4abc0a04..611efd3538 100644 --- a/drivers/include/periph/timer.h +++ b/drivers/include/periph/timer.h @@ -34,6 +34,7 @@ #define PERIPH_TIMER_H #include +#include #include "periph_cpu.h" /** @todo remove dev_enums.h include once all platforms are ported to the updated periph interface */ @@ -69,6 +70,26 @@ extern "C" { typedef unsigned int tim_t; #endif +/** + * @brief Reset the timer when the set() function is called + * + * When set, calling the timer_set_periodic() function resets the timer count value. + */ +#ifndef TIM_FLAG_RESET_ON_SET +#define TIM_FLAG_RESET_ON_SET (0x01) +#endif + +/** + * @brief Reset the timer on match + * + * When set, a match on this channel will reset the timer count value. + * When set on multiple channels, only the channel with the lowest match value + * will be reached. + */ +#ifndef TIM_FLAG_RESET_ON_MATCH +#define TIM_FLAG_RESET_ON_MATCH (0x02) +#endif + /** * @brief Signature of event callback functions triggered from interrupts * @@ -138,6 +159,21 @@ int timer_set(tim_t dev, int channel, unsigned int timeout); */ int timer_set_absolute(tim_t dev, int channel, unsigned int value); +/** + * @brief Set an absolute timeout value for the given channel of the given timer + * The timeout will be called periodically for each iteration + * + * @param[in] dev the timer device to set + * @param[in] channel the channel to set + * @param[in] value the absolute compare value when the callback will be + * triggered + * @param[in] flags options + * + * @return 0 on success + * @return -1 on error + */ +int timer_set_periodic(tim_t dev, int channel, unsigned int value, uint8_t flags); + /** * @brief Clear the given channel of the given timer device * diff --git a/tests/periph_timer_periodic/Makefile b/tests/periph_timer_periodic/Makefile new file mode 100644 index 0000000000..f3bc668f72 --- /dev/null +++ b/tests/periph_timer_periodic/Makefile @@ -0,0 +1,5 @@ +include ../Makefile.tests_common + +FEATURES_REQUIRED = periph_timer_periodic + +include $(RIOTBASE)/Makefile.include diff --git a/tests/periph_timer_periodic/main.c b/tests/periph_timer_periodic/main.c new file mode 100644 index 0000000000..5aeaee17cb --- /dev/null +++ b/tests/periph_timer_periodic/main.c @@ -0,0 +1,107 @@ +/* + * Copyright (C) 2020 Beuth Hochschule für Technik Berlin + * + * This file is subject to the terms and conditions of the GNU Lesser + * General Public License v2.1. See the file LICENSE in the top level + * directory for more details. + */ + +/** + * @ingroup tests + * @{ + * + * @file + * @brief Periodic timer test application + * + * @author Benjamin Valentin + * + * @} + */ + +#include +#include + +#include "board.h" +#include "test_utils/expect.h" + +#include "mutex.h" +#include "periph/timer.h" +#include "xtimer.h" + +/* We use the timer used for xtimer with the frequency used by xtimer here + * to make sure we have a known valid timer configuration. + * + * DO NOT USE any low-level timer functions demonstrated here when xtimer + * is used with that timer! + * Configure a separate timer, XTIMER_DEV is usually 'owned' by xtimer, but + * as xtimer is not used in this test, we can use it and the fact that every board + * provides a configuration for it. + */ +#define TIMER_CYCL XTIMER_DEV +#define CYCLE_MS 100UL +#define CYCLES_MAX 10 + +static unsigned count[TIMER_CHANNELS]; + +static void cb(void *arg, int chan) +{ + unsigned c = count[chan]++; + + printf("[%d] tick\n", chan); + + if (c > CYCLES_MAX) { + timer_stop(TIMER_CYCL); + mutex_unlock(arg); + } +} + +static const char* _print_ok(int chan, bool *succeeded) +{ + if (chan == 0 && count[chan] > 0) { + return "OK"; + } + + if (chan > 0 && count[chan] == 0) { + return "OK"; + } + + *succeeded = false; + return "ERROR"; +} + +int main(void) +{ + mutex_t lock = MUTEX_INIT_LOCKED; + const unsigned long timer_hz = XTIMER_HZ; + const unsigned steps = (CYCLE_MS * timer_hz) / 1000; + + printf("\nRunning Timer %d at %lu Hz.\n", TIMER_CYCL, timer_hz); + printf("One counter cycle is %u ticks or %lu ms\n", steps, CYCLE_MS); + puts("Will print 'tick' every cycle.\n"); + + expect(timer_init(TIMER_CYCL, timer_hz, cb, &lock) == 0); + + puts("TEST START"); + + /* Only the first channel should trigger and reset the counter */ + /* If subsequent channels trigger this is an error. */ + timer_set_periodic(TIMER_CYCL, 1, 2 * steps, TIM_FLAG_RESET_ON_SET); + timer_set_periodic(TIMER_CYCL, 0, steps, TIM_FLAG_RESET_ON_MATCH); + + mutex_lock(&lock); + + puts("\nCycles:"); + + bool succeeded = true; + for (unsigned i = 0; i < TIMER_CHANNELS; ++i) { + printf("channel %u = %02u\t[%s]\n", i, count[i], _print_ok(i, &succeeded)); + } + + if (succeeded) { + puts("TEST SUCCEEDED"); + } else { + puts("TEST FAILED"); + } + + return 0; +} diff --git a/tests/periph_timer_periodic/tests/01-run.py b/tests/periph_timer_periodic/tests/01-run.py new file mode 100755 index 0000000000..6bc3edba51 --- /dev/null +++ b/tests/periph_timer_periodic/tests/01-run.py @@ -0,0 +1,25 @@ +#!/usr/bin/env python3 + +# Copyright (C) 2020 Benjamin Valentin +# +# This file is subject to the terms and conditions of the GNU Lesser +# General Public License v2.1. See the file LICENSE in the top level +# directory for more details. + +import sys +import time +from testrunner import run + + +def testfunc(child): + child.expect_exact('TEST START') + start = time.time() + child.expect_exact('TEST SUCCEEDED') + end = time.time() + # test should run 10 cycles with 100ms each + assert (end - start) > 1 + assert (end - start) < 1.5 + + +if __name__ == "__main__": + sys.exit(run(testfunc))